2026-08-29 · Updated 2026-08-29 · 10 min read
Task intent, responses, tests, and commits as durable evidence
Auditing what an agent did and why takes four linked records — the task body that states intent, the response that accounts for the work, the validation it cites, and the commit that makes the diff checkable — and this page walks that chain end to end with recorded transcripts, exact-match lookups, and tamper drills.
By Juno AI INC · yylo-ledger · evidence
Every unattended agent run ends with the same question, asked by someone who was not watching: what did the agent actually do, and why? A terminal that has scrolled away answers nothing. A chat transcript is a narrative, not a record — it can be edited, excerpted, or lost with the session. What survives review is a chain of four linked artifacts: the task body that states what was asked, the response that accounts for what was done, the validation the response cites, and the commit that makes the whole thing checkable against a diff. This page walks that chain link by link with YYLO Ledger as the implementation. Every command and transcript below was recorded on 2026-08-29 against the released CLI version this site's package facts bind, inside a throwaway Git repository built for the drill; the task ID and hashes come from that one recorded session, and re-running the drill mints fresh ones — what reproduces is the structure, which is the part that matters to an auditor.
What the four semantics *mean* — why dependency-aware, response-gated, commit-bound state beats a hosted board — is the argument of the task-truth introduction, while the storage-format guide owns the storage contract on disk. Neither restates the auditor's actual procedure. This page owns that procedure: how the four records connect, how to query across them, where each link is honest about what it cannot prove, and how the chain reacts when someone tries to rewrite it.
Four records, one chain — and one query that reads them together
Start from what each link can and cannot prove, because overestimating any one of them is how audits fail:
- The body is the intent record. It states what was asked and the acceptance standard it will be judged against. It proves nothing was done — it is the standard the rest of the chain is audited by.
- The response is the account. Written by the agent at every transition, required by the tool. It proves only that some author, at some time, made a claim — which is exactly why it must be read against its neighbors.
- The validation is the middle link. The commands the response cites, the results it says it observed, and the tests the commit carries. The ledger runs no tests itself; validation enters the chain as recorded claims plus checkable artifacts.
- The commit is the pointer. A hash stored on the task, resolvable in Git to a diff. The ledger checks its shape, never its existence — resolving it is the reviewer's half of the bargain.
The connective tissue is that all of these live on the task itself, and one read returns them together: get TASK_ID returns body, response, and commit hash as one record. That is the audit's entry point — no joins across systems, no export from a board into a ticket into a document. The drill below builds the chain from scratch; its commands are worth copying as a unit because the order is the chain:
Intent is evidence: write the body like a spec
The drill's task began as a body file, not a title: a ## Goal section stating the change wanted, and an ## Acceptance section listing the two behaviors that would count as done. create --title ... --body-file intent.md stored both, and the first ledger event snapshotted the entire task — so the original ask is frozen at creation, before any work exists that could bias its wording.
This is the link most teams skip, and the omission is expensive in exactly the way audits expose. A task body of "fix the total bug" cannot be failed: whatever the agent did, the ask was arguably met. A body carrying acceptance lines — total(["1", 2]) must raise a ValueError naming the bad value; numeric totals must not change — converts the review from opinion into comparison. Months later, when someone asks why a function behaves the way it does, the body answers with the intent as it was recorded, not as anyone now remembers it. And the body is not attic storage: it is indexed query state, one of the search planes — the parser's own flag help reads "Search in task body" — so the record you wrote for the future is reachable from the shell in seconds.
The response is the account — and it cannot be skipped
Every status transition through mark ships an account written by the agent itself, and a transition attempted without one is refused before any byte moves — the refusal mechanics, and why that gate protects the record, belong to the shared-TODO failure anatomy. For the audit, two facts matter more. First, the response is *on the task*: the drill's completion carried its validation record through --response-file validation.md, and get returns body and response side by side, so the claim and the standard sit in one read. Second, responses are a search plane of their own — the flag help states "Search in agent response" — and the drill used it: after completion, search --response pytest found the task through a word that appears only in its validation record, nowhere in the body.
What a response must contain to serve as evidence is a discipline with a home: what changed, the exact commands run, the results observed, the risk left open — the standard is defined in the task-truth introduction, and it exists precisely so the account is auditable rather than decorative. The property this page adds is the audit-side one: because the account is required, stored, and searchable, there is no transition in your history that lacks an author's claim. A record that cannot be skipped cannot be silently backfilled later, either — whatever the response claims, it claimed it at the time.
Tests are the middle link: record validation, not impressions
Here is the honest boundary: the ledger has no test runner, and no task state can execute anything. When a response says "python3 -m pytest test_app.py -q -> 2 passed", that is a recorded claim about a validation event, not the event itself. Validation becomes durable evidence through three mutually reinforcing channels, and the drill used all three.
The first channel is the response's validation record: exact commands, exact observed results, committed as Markdown. The second is the commit: the drill's commit carried app.py *and* test_app.py, so the tests that allegedly passed are themselves in the diff — a reviewer who doubts the claim can run the same tests from the same tree the response describes. The third is whatever re-runs them independently: CI applying the commit, or the next agent's focused check. The chain is strongest when a claim is checkable by someone who does not trust the claimant — and the failure mode to police is the one no tool can police for you: a response asserting results nobody observed, or worse, results observed before the final edit invalidated them. The recorded-command discipline exists so that every "passed" in the account names the command that passed and the tree it ran against, which is what makes the commit the closer of the loop.
The commit closes the chain: a pointer, not a proof
Completion stored the commit hash on the task — the schema describes the field as "Git commit hash when completed" — and that single pointer is what welds the account to a diff that Git itself defends. Two edges keep this link honest, and both were tested live in the drill.
The ledger validates shape, not existence. Feed it --commit notahash and it refuses: "Commit hash must be 7-40 hexadecimal characters". Feed it a perfectly well-formed hash that names no commit anywhere in your repository — deadbeef did, in the drill — and it accepts, stores, and indexes it without complaint. That is not a weakness needing an apology — it is the correct division of labor. The ledger guarantees the pointer cannot be malformed, and Git guarantees what the pointer resolves to. An auditor who resolves every completion's hash in the repository has converted each account into a diff; one who trusts the field blind has proven nothing.
The lookup is exact, and this edge is easy to trip on in the field. search --commit filters by the full stored value — the parser help says "Filter by commit hash", and the drill confirmed the semantics both ways: the complete 40-character hash returned the task; the 7-character prefix people naturally paste from a Git log returned No results found. Pass the exact hash the task carries; anything shorter is a silent miss, not an error. And when a completion arrives without any hash, the CLI still records the transition but prints its standing reminder — the README's own annotation reads "Output: Consider adding commit hash with --commit flag" — a nudge instead of a gate, a distinction with consequences that the autonomous queue guide works through: the gap stays visible in every listing as a null where a hash belongs.
Replay the chain, then try to break it
Reading the chain forward is get; replaying it is history, whose parser help calls it exactly what it is — "Show opt-in per-task ledger history". The summarized replay prints one event per mutation, each carrying the fields the storage layer projects — event_id, task_id, timestamp, operation, source, before_sha256, after_sha256, previous_event_sha256, event_sha256, changed_paths — and the recorded drill read back as three events whose hashes interlock:
Read the transcript as an auditor: each event's before_sha256 is the previous event's after_sha256, so the content of the task at every moment is pinned; the completion event's changed_paths moves /agent_response, /commit_hash, and /status together — the account and the pointer landed in one mutation, not one of them retrofitted. With --include-content, the first event still carries the full creation snapshot, body and all, so even the original wording of the ask survives inside the history.
Then break it, because an evidence chain earns the name only if tampering is visible. Two drills, both run live on a copy of the recorded board. First, edit a task file directly — change one word of the stored response. reconcile --check reports the drift and exits nonzero: {"changed_task_ids": ["6i40Nl"], "check": true}. Run reconcile without the flag and the edit is not erased but *recorded*: a new event, operation reconcile, source external-edit, seals the after-the-fact change into the same chain, and doctor passes again. Second, edit the ledger itself — flip one character inside a stored event. Now history refuses to replay: Error reading history: ledger event hash mismatch .../000001.ndjson:2, exit 1, naming the file and line; doctor reports ok: false with the same location. Current state and history verify each other, and both are checkable from the shell — the integrity claims are commands, not assurances. For mutations that must stand alone as artifacts — release gates, sealed admissions — the receipt flags write the before and after hashes with the ledger event ID to a file you can hand to a machine; the storage-format guide owns that surface, and the workflow-runner side of run-level audit — manifests, step streams, session handoff — is the run-directory guide's ground, with this page owning the task-side chain underneath both. The cross-product evidence architecture that generalizes this chain beyond the ledger is approved on this program's checklist but not yet published.
Install YYLO Ledger from PyPI in the repository your agents already change, write your next task's body with acceptance lines instead of a title alone, and demand the hash at completion — the chain is only as durable as its weakest link, and every link of it is queryable from the shell you already have open.