Skip to main content
Read a run’s events and produce one Receipt per operation. Events are grouped by context_id, each group is folded through the assembler on its own, and every receipt is written to .trace/receipts/<run>~<operation>.json, where receipt reads it back.

Usage

Arguments and options

Example

The template file and the run id are printed on every build, not as decoration: a receipt records the template’s declared name, never the file that declared it, and two runs of one program can yield receipts with identical operation ids and different verdicts. The run id is the value receipt show --run takes. Events that carry no context_id — capture attestations, a chain confirmation added out of band — belong to no single operation, so they are reported separately rather than attributed or dropped:

One build per run

Several runs mean several builds, never one merged event set. An event carries no run id — the run is its file name — and context_id is only guaranteed unique within a run, since an adapter may number operations per session. Folding two runs together would merge two unrelated payments that happen to share an id. So each run is read, assembled and reported on its own, and its receipts are stored under its run id.
Two run files with the same name in different directories are refused rather than built: receipts are keyed by run name, so theirs would collide on disk and the second would silently replace the first. Build them one command at a time, or rename one.
If an error interrupts a multi-run build, the message says how many earlier runs were already written — a store that was half updated must not look untouched.

How a template is resolved

A --template name is looked up in .trace/templates/ first, then in the templates shipped with the CLI. So a template written by haia-trace template new is found with no extra flags, and a local x402-buyer.yaml deliberately shadows the built-in one. Anything that is not a bare name — ./ops/refund.yaml, an absolute path — is read as a path to a template file instead. A name that resolves nowhere names both places searched:
A template that exists but does not parse always fails the build. It never falls back to a built-in of the same name, because silently substituting a different template would produce a verdict on a shape you did not write.

Filtering the view

--status narrows what is shown, never what is written. Receipts are derived and reproducible, so the store stays complete for receipt list and receipt show whichever verdict one build chose to look at. The summary line reports everything assembled first, with the filtered subset after it, so a narrowed screen never understates the run:
A filter that excludes everything is a positive verdict on the run, and says so rather than leaving an empty screen:

JSON output for agents

--json emits the full Receipt objects — the machine-readable basis an agent reads (completeness, missing) to decide whether to continue a chain of spending.
  • Receipts are nested per run rather than flattened, because operation_id is only unique within its run: a flat list can hold two different payments under one id.
  • assembled is the count before --status narrowed receipts. An agent gating on “no partial receipts” has to tell a run whose every receipt is full (assembled > 0, receipts empty) from a run that captured nothing (assembled: 0).
  • template.origin is builtin, local or file — which of the two same-named templates won is invisible in the receipt itself.
The command still exits 0 on a partial receipt: a verdict is an answer, not a failure. Branch on completeness yourself.

Producing a run file

build needs an events/*.ndjson run file under the root. Any NDJSON whose lines match the Event Contract works, and two adapters write one for you:
  • @usehaia/trace-x402 — attach it to a live x402 client, resource server or facilitator with trace(instance, { writer: createRunEventWriter(".trace/events") }).
  • @usehaia/trace-circle — record verified Circle webhook v2 deliveries into the same directory, for the escrow-arc flow.
The recorder names its directory explicitly, so that is the one thing that has to agree with the CLI: point it at events/ under whatever root you build with. Build a capture against the template for the side that recorded it — x402-buyer (the default) for a client, --template x402-seller for a resource server, --template x402-facilitator for a facilitator, --template escrow-arc for an escrow. A run that traced two sides in one process assembles into an honest buyer receipt or an honest seller receipt, one build each, never a merge of the two.
An empty events directory is an error that names the path. A directory holding runs from an earlier session is not: build assembles the newest of those without complaint, so clear out a root you have stopped recording into.