Your agent finishes a run and reports success. The summary reads well. The ticket gets closed, the client gets billed, the workflow moves on. Then someone asks a simple question: what did the agent actually save, and is the record you are looking at now the same record it produced then? In most stacks, that question has no answer. You have logs the framework felt like writing, traces behind the operator's dashboard, and a final message whose confidence is not evidence. If a saved result gets edited, overwritten, or quietly regenerated next week, nothing in the pile tells you it happened. On zambo.dev, that question comes with a receipt attached: more than 92,000 of them filed so far, including a prompt check from this morning that this article walks through below, open for anyone to recompute.

That hole is what an execution receipt fills, and you do not need anyone's platform to get one. AER-1 is an IETF Internet-Draft (draft-zambo-aer1, currently at version -26) that defines a receipt format for AI agent tool calls, plus a public conformance corpus you can test your own implementation against. This guide walks through the weekend version: fetch the corpus, write a small verifier in whatever language you already use, and run the checks until your code agrees with the reference behavior. At the end you can check a receipt yourself, by hand, without trusting the operator who issued it or the service that stored it.

What a receipt actually is

Keep it minimal. One receipt covers one tool call. It records the tool that was invoked, the inputs it received, and the result as saved. That record is written as canonical JSON, meaning one exact byte form for the same content: keys in a fixed order, no decorative whitespace, no ambiguity about how a value is spelled. The canonical bytes are hashed. Receipts for the same job are chained: each record carries the hash of the record before it, so the job's history is a sequence where every link can be recomputed.

Two consequences fall out of that shape. First, checking is cheap and local. Anyone holding the record can canonicalize it, hash it, and compare. Second, edits are findable. Change one byte of a saved result and its hash no longer matches. Remove a record from the middle of a job and the chain breaks at exactly that point, because the next record still points at the hash of the one you removed.

Be precise about what this proves, because the precision is the point. A receipt proves the saved result was not changed after the fact. It never claims the answer was right. A wrong answer, faithfully recorded, verifies fine, and that is correct behavior: the receipt attests to the record, not to the work. What it kills is the quiet rewrite, the regenerated output presented as the original, the audit trail that always agrees with whoever kept it.

The weekend build

Step 1: fetch the corpus. The conformance corpus lives at https://zambo.dev/aer1/corpus.json. It carries 126 test vectors covering 129 checks, at kit version 1.5.7. The vectors include the awkward cases, not just the happy path: records whose hashes must fail, chains with gaps, anchors pointing at records that do not resolve. Download it first and treat it as the spec you code against, because your implementation is done when it agrees with the corpus, not when it agrees with your reading of the draft.

Step 2: canonicalize and hash. This is the heart of the verifier, and it is small:

function canonical(record): # one exact byte form: keys sorted, no insignificant # whitespace, values spelled in their shortest stable form return utf8(serialize_sorted(record)) function record_hash(record): return sha256(canonical(record)) 

Most first implementations fail here, and the failures are boring in an instructive way: a number serialized two ways, a key order that depends on the language runtime, a Unicode string normalized differently by the producer and the checker. The corpus exists to surface exactly those arguments between implementations before they surface between you and an operator.

Step 3: check the chain. For one job, take the records in order and recompute every link:

function check_chain(records): # one job, in order  prev = first_link # the job's starting link  for r in records: if r.prev_hash != prev: return FAIL at r.seq if record_hash(r) != r.hash: return FAIL at r.seq prev = r.hash return OK 

Step 4: run the 129 checks and read your failures. Expect the first run to disagree with the corpus somewhere. That disagreement is the deliverable of the weekend. Each failing vector names a specific behavior: how an absent anchor is reported, what happens when a record in the middle of a chain is missing, which outcome a malformed record earns. Fix your code against the vector, not against your intuition. Independent implementers have done this in multiple languages already, working from the same corpus, and the places where their code disagreed with the reference are the places where the draft text got sharper. Your failures will be your own version of that list.

The outcome vocabulary, and why "not checked" matters

When a receipt is checked against an external record, AER-1 uses a fixed set of outcomes, reported in the same words by every implementation: verified, verification failed, rejected: unresolvable record, not checked, and absent.

The load-bearing member of that list is "not checked". An anchor that has not been evaluated must be reported as not checked, never dressed up in an evaluated outcome. This sounds like bookkeeping until you have watched a dashboard show green because the check never ran, or an audit summary describe a record as verified when what actually happened is that nobody looked. Half the dishonesty in agent reporting is not fabricated results. It is unevaluated results wearing the uniform of evaluated ones. A vocabulary that forces the distinction, in the output of the tool itself, is worth more than another page of policy about it.

Checking a live receipt by hand

Theory is cheap; here is a real one. This receipt was filed on 2026-11-01 by a prompt_shield job, a prompt checked for hidden instructions: https://zambo.dev/run/84f4d9b9-7222-4c72-8645-7dc6b849f478

Open it. Everything on that page is a claim. Now fetch the underlying record, canonicalize it with the function you wrote this weekend, hash it, and compare against the hash the record carries. Walk the job's chain the same way. Either your recomputation matches, record for record, or it does not, and if it does not, the mismatch sits at a specific sequence number you can point at. No account, no API key, no phone call to the operator. That is the whole proposition: the person checking the work needs nothing from the person who did the work.

The shortcut, if you do not want to hand-roll it

Writing the verifier yourself is the best way to understand what a receipt does and does not prove, but it is not the only way to get one checked. The reference tooling is packaged: pip install aer1-verify (version 0.2.20) or npm install aer-1 (version 0.1.25), and the open-source kit, corpus tooling included, is at https://github.com/zambodotdev/aer-1. The packages earn their keep when you want the corpus suite wired into your own CI, so a receipt format change or a verifier regression trips your build instead of your next client dispute. For context on scale: 92,000 or more receipts have been issued on zambo.dev to date, each one checkable the way the live example above is.

Whether you build it or install it, the habit is the same. When an agent says the work is done, ask for the receipt, and check the record yourself.

Try it free: add Zambo to your AI in one step at zambo.dev/add. 20 calls plus 1 wrap-up free every day, no card and no signup wall. Every job gets a verifiable receipt like the ones in this article, and picking up a job in another AI is always free.