# Run receipt

> A run receipt is a per-run audit trail for an AI agent workflow: a machine-checkable record, written at the end of each run by the harness that executed the calls, of what actually ran — every step with its outcome against an explicit assertion, its timing, and the run's measured LLM token count. A conventional audit log records that activity happened; a run receipt also carries a verdict — each step passed, failed, ran unchecked, or was skipped — so the record can be compared against the previous run and gated on without a human reading it. In Reelier, the receipt is one JSON line appended to `.reelier/runs/<skill>.jsonl` after every replay.

Markdown twin of https://www.reelier.com/learn/run-receipt — one entry in Reelier's glossary of agent-replay terms. The blockquote above is the canonical definition; quote it with the link.

## An audit trail agents can't write for themselves

Audit trail is a settled term. NIST defines it as a chronological record that reconstructs the sequence of activities surrounding an operation, and operations teams have kept them for decades: who did what, when, with what result. Nothing about the concept is new, and Reelier did not invent it.

Agents change one thing: the natural author of the record is now a model, and a model's account of its own work is more model output, subject to the same failure modes as the work itself. An agent that says "done" has made a claim. The trail is still missing. The useful version of an agent audit trail is written by the harness that executed the tool calls, from observed results — never by the model, from memory.

A run receipt is that trail, scoped to one run and given a verdict. It answers the auditor's questions — what executed, in what order, with what outcome, at what cost — in a format a scheduler can act on: pass or fail, per step, measured.

## What's on a receipt — and what isn't

In Reelier, every `reelier run` appends one JSON line to `.reelier/runs/<skill>.jsonl` (a `--dry-run` executes nothing and writes no receipt). Per step it records the number, title, outcome (passed, failed, unchecked, or skipped), milliseconds, and the verbatim failure list; per run it records totals — step counts by outcome, total milliseconds, and two LLM token counters. When a step drifts or heals, a `why` field carries the trigger or the change, taken from the real assertion or tool result, never composed after the fact.

Two rules keep the format honest. A step that ran with zero assertions is recorded unchecked, never passed: success means a check held, not that code executed. And the token counters are measured. At the default `--max-level 0` the runner never constructs an LLM client, so `llmInputTokens: 0` reports a code path that cannot spend; when opt-in BYOK escalation (Level 1 or 2) does run, its token usage lands on the step that used it and in the totals.

One thing stays off the receipt: data values. Bound values flow between steps during the run, but the persisted record keeps structure and outcomes only. `reelier diff` inherits that choice — the same steps returning fresh data reads SAME; a changed outcome, or the same outcome reached at a different escalation level than last run, reads DRIFTED.

## Write steps: the refusal is on the record

Replay is read-only by default. A recorded write step (`effect: idempotent-write`) does not re-fire unless you pass `--allow-writes`; without the flag, the step is recorded failed with a named refusal in its failure list — `Refusing to execute a write step (effect: idempotent-write) — replay is read-only by default` — and every step after it is recorded skipped. The refusal is itself an audit event: the receipt shows the gate holding.

This gate carries most of the weight in practice. In the measured corpus behind Reelier's launch writing, only about 1.1% of scanned sessions were read-only end-to-end. Almost every real agent job writes somewhere, so what an audit trail records about write steps decides whether you can trust it.

## From one receipt to a gate

Because every run appends to the same file, the receipt file is also the run history. `reelier diff <skill>` compares the last two receipts, prints a per-step verdict, and exits 0 on SAME, 1 on DRIFTED. That exit code is the whole CI story: put the replay and the diff in a scheduled job, and a drifted step turns the build red with the failing step named.

Two boundaries, stated before you ask. The receipt attests what the replayer executed and observed; it is plain JSON on disk, nothing signs it, and anyone with the file can edit it — treat it like any other artifact you commit and review, and let `diff` anchor to a baseline you have read. And receipts stay on your machine by default: `reelier push` syncs them to a dashboard only when both `REELIER_CLOUD_URL` and `REELIER_CLOUD_KEY` are set. Opt-in, like everything that leaves the machine.

## A real receipt

The receipt below was produced while writing this page, not staged for it: the `vendor-status-sweep` portfolio skill — two read-only HTTP GETs against npm's and GitHub's public status APIs — replayed with the published CLI, then the receipt file read back with `cat`. The JSON line is the audit trail itself: one line per run, appended.

```
npx -y reelier@latest run vendor-status-sweep.skill.md

✓ Step 1 — npm status [passed] 226ms
✓ Step 2 — GitHub status [passed] 159ms

PASSED: 2/2 steps ok, 0 failed, 385ms total

cat .reelier/runs/vendor-status-sweep.jsonl

{"skill":"vendor-status-sweep","startedAt":"2026-07-21T21:53:31.788Z","finishedAt":"2026-07-21T21:53:32.175Z","passed":true,"steps":[{"n":1,"title":"npm status","level":0,"outcome":"passed","ms":226,"failures":[]},{"n":2,"title":"GitHub status","level":0,"outcome":"passed","ms":159,"failures":[]}],"totals":{"steps":2,"passed":2,"unchecked":0,"skipped":0,"failed":0,"ms":385,"llmInputTokens":0,"llmOutputTokens":0}}
```

Both commands run for real on 2026-07-21 with the published CLI (`npx -y reelier@latest`, resolved to `reelier@0.13.0`), in a scratch directory holding a verbatim copy of `examples/portfolio/vendor-status-sweep.skill.md` from seldonframe/reelier. Read-only, no account, no key, no model in the loop.

## FAQ

**Is a run receipt the same thing as agent observability or session replay?**

No. A trace is a record you watch; a human reads it and decides whether the run was right. A run receipt carries the decision on the paper: each step's outcome against an explicit assertion, plus measured totals, so a cron job or CI gate can act on it with nobody reading. The two compose — keep traces for debugging depth, gate on receipts.

**Can I trust a receipt that says 0 LLM tokens?**

The zeros are measured, not assumed. At the default `--max-level 0` the runner never constructs an LLM client, and the receipt's counters sum per-step escalation usage that never existed. Reelier's published benchmark asserts this from the run records — `llmInputTokens === 0 && llmOutputTokens === 0` on every one of the 1,000 replays in its N=1000 test.

## Related

- [Deterministic replay — the re-run that prints the receipt](https://www.reelier.com/learn/deterministic-replay)
- [Level-0 (zero-token) replay — why the token line reads 0](https://www.reelier.com/learn/level-0-replay)
- [Idempotent-write gating — the refusal line on the receipt, in depth](https://www.reelier.com/learn/idempotent-write-gating)
- [Vendor status sweep — the exact skill replayed in the receipt above](https://www.reelier.com/skills/vendor-status-sweep)
- [From the blog: gate a scheduled replay on its receipt in CI](https://www.reelier.com/blog/scheduled-agents-zero-token)

## Sources

- [NIST CSRC glossary: audit trail — the pre-agent definition this page extends](https://csrc.nist.gov/glossary/term/audit_trail)
- [reelier src/runner.ts — the RunRecord schema: the receipt's exact shape, in source](https://github.com/seldonframe/reelier/blob/main/src/runner.ts)
- [examples/benchmark — the harness that asserts 0 tokens from run records (N=1000)](https://github.com/seldonframe/reelier/tree/main/examples/benchmark)

Other terms: [Deterministic replay](https://www.reelier.com/learn/deterministic-replay) · [Agent drift](https://www.reelier.com/learn/tool-call-drift) · [Level-0 (zero-token) replay](https://www.reelier.com/learn/level-0-replay) · [Snapshot testing for AI agents](https://www.reelier.com/learn/agent-snapshot-testing) · [Session-to-skill (skill compilation)](https://www.reelier.com/learn/skill-compilation) · [Recording MCP tool calls](https://www.reelier.com/learn/mcp-recording) · [Regression testing for AI agents](https://www.reelier.com/learn/agent-regression-testing) · [Idempotent write gating](https://www.reelier.com/learn/idempotent-write-gating) · [Assertion grammar for agent outputs](https://www.reelier.com/learn/assertion-grammar) · [Agent memory vs deterministic replay](https://www.reelier.com/learn/agent-memory-vs-replay) · [Headless agent](https://www.reelier.com/learn/headless-agent)
