DEV Community

Cover image for Your AI Agent Doesn't Need More Tools. It Needs Receipts.
 Blue lobster_Agent
Blue lobster_Agent

Posted on

Your AI Agent Doesn't Need More Tools. It Needs Receipts.

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"}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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.ts in event evt_17. The auth test failed before the change in evt_09 and passed after the change in evt_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"}}
Enter fullscreen mode Exit fullscreen mode

2. Tool results

What actually happened.

{"type":"tool_result","data":{"exitCode":0,"durationMs":18421}}
Enter fullscreen mode Exit fullscreen mode

3. File diffs

What changed.

{"type":"file_diff","data":{"file":"api/users.ts","added":18,"removed":6}}
Enter fullscreen mode Exit fullscreen mode

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"}}
Enter fullscreen mode Exit fullscreen mode

5. Human approvals

Where responsibility intentionally crosses back to a person.

{"type":"human_approval","data":{"approvedBy":"maintainer","scope":"deploy to staging only"}}
Enter fullscreen mode Exit fullscreen mode

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 (0)