Every agent demo looks magical until something goes wrong.
It says it ran the tests. Did it?
It says it fixed the bug. Which diff did that?
It says it read the docs. Which docs, and what line changed its mind?
It says the build failed because of a flaky dependency. Or maybe because it hallucinated the command output and then confidently continued from there.
At that moment, your “autonomous coding agent” starts to feel less like a senior engineer and more like a very confident intern with no notebook.
Here is the hot take:
Most agent systems are not failing because the model needs one more tool.
They are failing because nobody can prove what happened.
Before you add another MCP server, another prompt file, another framework, or another “self-healing” loop, give your agent something boring and powerful:
receipts.
An append-only event log.
A flight recorder.
A source of truth that the model can read, but cannot fake.
The problem is not intelligence. It is provenance.
When humans review each other’s work, we ask for evidence:
- What changed?
- Why did it change?
- What command did you run?
- What failed before the fix?
- What passed after the fix?
- What did you not check?
But many agent workflows collapse all of this into a final message like:
“I fixed the issue and all tests pass.”
That sentence is almost useless.
It might be true. It might be partially true. It might be the model summarizing a plan it never executed. It might be the result of a stale terminal buffer. It might be a hallucinated success after a timeout.
The fix is not to yell at the model harder.
The fix is to move truth outside the model.
What counts as a receipt?
A receipt is a structured event created when something actually happens.
Not a vibe. Not a summary. Not “the agent remembers doing it.”
An event.
For example:
{"type":"tool_call","command":"npm test -- --run auth","actor":"agent","ts":"2026-07-09T12:00:01Z"}
{"type":"tool_result","command":"npm test -- --run auth","exitCode":1,"stdoutTail":"Expected 200, received 401","actor":"tool","ts":"2026-07-09T12:00:14Z"}
{"type":"file_diff","file":"src/auth/session.ts","added":12,"removed":4,"actor":"agent","ts":"2026-07-09T12:02:20Z"}
{"type":"tool_result","command":"npm test -- --run auth","exitCode":0,"stdoutTail":"8 passed","actor":"tool","ts":"2026-07-09T12:03:10Z"}
Now the final message can say:
“The auth test failed with a 401, I changed
src/auth/session.ts, then the auth test passed.”
And if someone asks “prove it,” the answer is not another paragraph.
It is the log.
A tiny TypeScript flight recorder
You do not need a distributed tracing platform to start.
You can begin with a JSONL file.
import { mkdir, appendFile } from "node:fs/promises";
import { createHash, randomUUID } from "node:crypto";
import path from "node:path";
type Actor = "human" | "agent" | "tool" | "ci";
type EventType =
| "goal"
| "plan"
| "tool_call"
| "tool_result"
| "file_diff"
| "decision"
| "human_approval"
| "rollback"
| "final_summary";
type AgentEvent = {
id: string;
runId: string;
ts: string;
actor: Actor;
type: EventType;
data: Record<string, unknown>;
prevHash: string | null;
hash: string;
};
export class FlightRecorder {
private prevHash: string | null = null;
constructor(
private runId = randomUUID(),
private file = `.agent/runs/${runId}.jsonl`
) {}
async record(
type: EventType,
data: Record<string, unknown>,
actor: Actor = "agent"
) {
const base = {
id: randomUUID(),
runId: this.runId,
ts: new Date().toISOString(),
actor,
type,
data,
prevHash: this.prevHash,
};
const hash = createHash("sha256")
.update(JSON.stringify(base))
.digest("hex");
const event: AgentEvent = { ...base, hash };
await mkdir(path.dirname(this.file), { recursive: true });
await appendFile(this.file, JSON.stringify(event) + "\n");
this.prevHash = hash;
return event;
}
}
Then wrap the actions your agent already takes:
const log = new FlightRecorder();
await log.record("goal", {
request: "Fix the auth bug where expired sessions return 500 instead of 401."
}, "human");
await log.record("tool_call", {
tool: "shell",
command: "npm test -- --run auth"
});
// Your real shell wrapper should create this event,
// not the language model.
await log.record("tool_result", {
command: "npm test -- --run auth",
exitCode: 1,
stdoutTail: "Expected 401, received 500"
}, "tool");
await log.record("file_diff", {
file: "src/auth/session.ts",
added: 9,
removed: 3,
summary: "Return UnauthorizedError for expired sessions."
});
await log.record("tool_result", {
command: "npm test -- --run auth",
exitCode: 0,
stdoutTail: "8 passed"
}, "tool");
The important part is this:
The model may request an action, but the environment records whether the action actually happened.
That single rule prevents a surprising amount of agent nonsense.
Add one prompt rule: cite the receipts
Once the log exists, update your agent instructions:
You may not claim that a command ran, a test passed, a file changed,
or a document was read unless there is a matching event in the run log.
In your final answer, include a short "Receipts" section with event IDs.
If you planned an action but did not execute it, say so explicitly.
Do not convert intentions into completed work.
This changes the behavior of the whole system.
The agent stops writing final answers like:
“Everything is fixed.”
And starts writing answers like:
“I changed
src/auth/session.tsin eventevt_17. The auth test failed before the change inevt_09and passed after the change inevt_21. I did not run the full test suite.”
That last sentence matters.
“I did not run the full test suite” is not weakness. It is honesty.
Honesty is what makes automation reviewable.
The five events I would log first
If you do nothing else, log these.
1. Tool calls
What the agent tried to do.
{"type":"tool_call","data":{"tool":"shell","command":"npm run build"}}
2. Tool results
What actually happened.
{"type":"tool_result","data":{"exitCode":0,"durationMs":18421}}
3. File diffs
What changed.
{"type":"file_diff","data":{"file":"api/users.ts","added":18,"removed":6}}
4. Decisions
Why the agent chose one path over another.
{"type":"decision","data":{"choice":"add guard clause","rejected":"rewrite middleware","reason":"smaller diff and lower regression risk"}}
5. Human approvals
Where responsibility intentionally crosses back to a person.
{"type":"human_approval","data":{"approvedBy":"maintainer","scope":"deploy to staging only"}}
That is enough to make your agent dramatically more debuggable.
Receipts also make agents cheaper
This is the part people miss.
A good log is not just for compliance or debugging. It can reduce cost.
Without receipts, an agent often re-discovers the same facts over and over:
- reading the same files,
- re-running the same tests,
- asking the same model to summarize the same state,
- losing context and rebuilding it from scratch.
With receipts, you can route smarter:
- If the last relevant test already passed after the latest diff, do not run it again.
- If a cheap model can classify the next step from the log, do not send the whole repository to an expensive model.
- If the log shows three failed attempts in the same area, escalate to a human instead of looping.
- If the agent only needs the last five events, do not stuff the entire conversation into context.
Bigger context windows are nice.
Not needing to resend everything is nicer.
Summaries are cache. Logs are source of truth.
I still like summaries.
Agents need summaries because raw logs get long. Humans need summaries because we have other things to do.
But a summary should be treated like a cache.
Useful? Yes.
Authoritative? No.
If the summary says “tests passed” but the log has no passing tool_result, the summary is wrong.
If the summary says “the agent read the documentation” but there is no retrieval event, the summary is wrong.
If the summary says “the user approved deployment” but there is no human_approval, the summary is dangerously wrong.
This is the difference between an agent that sounds organized and an agent that is actually auditable.
The uncomfortable part
A receipt-driven agent will make your workflow look worse at first.
You will see how often the agent:
- runs commands without using the result,
- edits three files when one would do,
- repeats failed strategies,
- summarizes things it did not verify,
- spends expensive model calls on cheap classification tasks,
- hides uncertainty behind confident language.
Good.
That is not a reason to avoid logging.
That is the reason to start.
Observability always ruins the fantasy before it improves the system.
What this unlocks
Once you have a real event log, a lot of agent features become simpler.
Resume
If the process crashes, replay the log and continue from the last verified state.
Fork
Try two strategies from the same event and compare results.
Review
Ask a human to review only the decisions, diffs, and failed tests.
Evaluation
Compare agents by what they actually did, not what they claimed they did.
Security
Flag suspicious behavior: unexpected network calls, secret access, deploy attempts, or tool use outside scope.
Team learning
Turn messy agent runs into reusable docs, checklists, and guardrails.
The log becomes the bridge between automation and engineering judgment.
A simple rule for production agents
Here is the rule I would use before trusting an agent with serious work:
No receipt, no claim.
No approval, no irreversible action.
No passing test event, no “tests pass.”
This is not glamorous.
It will not look as exciting in a demo as an agent spawning five sub-agents and rewriting half the repo.
But it is the kind of boring infrastructure that makes the exciting stuff safe enough to use.
Final thought
The next generation of useful agents will not just be better talkers.
They will be better witnesses.
They will know what happened, what did not happen, what failed, what changed, and what still needs a human.
So before you give your agent another tool, give it a notebook.
Better yet, give it a flight recorder.
Then make it show the receipts.
Would you trust an AI coding agent without a receipt trail?
If you are building agents today, what events are you logging — and what do you wish you had logged sooner?
Top comments (9)
Append-only receipts is the right primitive, and the part that makes or breaks it is who writes the entry. If the agent writes 'ran tests, passed' into the log, you've just given it a nicer place to store the same hallucination. The receipt only helps when it's emitted at the tool boundary, not by the model narrating itself: the test runner writes the exit code and the captured output, the VCS writes the actual diff hash, and the model is only allowed to read those back. The rule we landed on is that the model can append intentions but never outcomes. Outcomes come from the thing that did the work. Then 'it says it ran the tests' becomes a joinable claim against a receipt it couldn't forge.
“Summaries are cache. Logs are source of truth.” is the line I kept coming back to. A lot of agent stacks add more tools or more prompting before they add the boring event model that would let someone replay what actually happened, and then every debugging session turns into archaeology. I also like the narrowness of the first five events you would log, because tool calls, results, diffs, decisions, and human approvals are enough to make a workflow auditable without pretending you need to persist every token forever. This is very aligned with why I care about local-first traceability in tools like agent-inspect: the main win is not prettier telemetry, it is being able to challenge an agent claim with receipts. Curious how opinionated you think the receipt schema needs to be before traces stay portable across frameworks instead of becoming one more silo.
The receipt only helps if the agent can't author it. An append-only log the agent writes into is still self-report with better formatting: "tool_call: npm test" is an event the model can emit without the command ever running, the same way it can say "tests pass" without them passing. The log has to be written by the runtime that executes the call, not by the thing being audited, or provenance dies at the storage boundary and you have moved the trust problem rather than closed it.
There is a second gap even when the emitter is independent: a receipt proves the event happened, not that it answers the claim. "ran the test suite" and "ran the test that actually pins the behavior I changed" both produce a clean receipt. Truth outside the model is the right axis. Independence of the writer, and scope-match between the event and the claim, are the two things that make the receipt worth more than the sentence it replaced.
Receipts are the missing primitive in a lot of agent tooling. More tools expand what the agent can do, but receipts explain what it actually did. Append-only events, command output, file diffs, and decision traces make the system debuggable after the impressive demo is over.
Receipts before tools is the right ordering. More tools expand what an agent can do; receipts expand what a team can trust. An append-only event log also changes debugging from "what did the model probably mean" to "what happened, in what order, with which inputs and outputs."
Receipts are the missing layer in a lot of agent stacks. More tools increase reach, but receipts increase trust. I want to know what the agent saw, what it changed, which command proved the result, and what still needs human judgment. Without that, a successful run is still hard to replay.
The line about summaries being cache and logs being the source of truth is the whole argument in one sentence. I stopped trusting an agent's own account of what changed a while back, not because it lies exactly, but because 'tests pass' compresses away the one line that actually matters. Reading the actual diff instead of the agent's claim about the diff catches more real bugs than any extra tool call would. Of your five event types, human approval is the one people skip first, and it's the one that would have caught most of the incidents I've seen.
The append-only log plus hash chain is doing less than it looks: it buys tamper-evidence, nobody rewrote the entry, but not provenance, that the recorded outcome is the real one. Emitting at the tool boundary is necessary but it isn't sufficient. If the agent still chooses which test runner to invoke, or can point it at a stub, it never has to forge the receipt, it just curates what gets measured, and the boundary faithfully writes down a green it engineered. So the scarce primitive isn't the receipt format or even the writer, it's a boundary the agent can't reconfigure or aim. Receipts move the trust question from "is the model's claim true" to "who controls what the writer sees," which is the sharper question but not automatically a solved one.
Excellent insight. Receipts provide accountability by separating what an AI claims from what actually happened, making debugging and reviews much more reliable.