Receipts
Read a signed receipt field by field and see what it establishes about a call.
On this page
Agent work can be inspected outside the application that performed it. The kernel signs a receipt for every governed call, refusals included, so an operator can trace a refusal, an auditor can review a release, and a partner can check a decision.
A receipt binds the kernel’s account of a call to the request. Application logs remain the application’s own, separate observations. Every receipt can be exported and verified outside the process that produced it.
One signature covers the request, the decision, and its evidence
In the Quickstart, the fixer tried to read /secrets/.env and the kernel refused it. This is that refusal’s receipt.
decision and evidence
decision holds the verdict and the kernel’s reason. evidence lists each guard that ran and its verdict. It is empty here because the scope check refused the read before any guard ran. Read more in Guards.
/secrets/.env from the Quickstart, trimmed to the fields that identify the call, its decision, and its signer, with digests shortened. The complete record also carries a timestamp, a content hash, and metadata; the receipt format lists every field.Two identifiers locate the record. Its id names the signed record, and metadata.receipt_context.request_id connects it to the submitted request, which the application received in the response metadata. Keep the complete record when verifying its signature or sharing it with another system, because the signature covers every field.
Read more in Receipt format, which defines the complete schema, the decision variants, optional metadata, and the canonical signing format.
A receipt’s kind and context set what it claims
A mediated-decision receipt records a kernel authorization decision and the evidence available to it. A trace-observation receipt records observed activity and does not assert that the kernel authorized it. An advisory-evaluation receipt records an assessment or signal; a separate enforcement path determines whether it affects execution.
Read the verdict, reason, and evidence together. A scope refusal can occur before guards run, as the fixer’s read did. Missing evidence from a later check does not mean that check passed: the pipeline stopped before it ran. Output refusals can occur after the tool runs. chio receipt explain reads a record this way for you, naming the stage that decided and what would change the outcome:
chio receipt explain rcpt-01a8…9c04receipt: rcpt-01a8…9c04 decision: deny reason: not in capability scope policy_hash: 3c7b…8010 scope_diff: requested read /secrets/.env; granted read /workspace/**, invoke /models/default parents: 0 repair_hint: request a grant that covers the path; policy cannot widen a grant
A mediated decision does not show that a tool ran. The fixer’s reads were dispatched; chio check evaluates the same request without dispatching anything. Both produce a mediated-decision receipt, so an allowed preflight check cannot establish execution, and receipt_kind alone does not tell the two apart. Keep the command or transport context and the execution evidence beside each record.
Records share authority and policy, never identity
Compare three records from the Quickstart: the fixer’s allowed read of the failing test, its refused read of /secrets/.env, and its refused write to the test after you added tests-are-evidence.
| Request | Verdict | capability_id | policy_hash |
|---|---|---|---|
read tests/test_pricing.py | allow | cap-01a8…31f2 | 3c7b…8010 |
read /secrets/.env | deny | cap-01a8…31f2 | 3c7b…8010 |
write tests/test_pricing.py | deny | cap-01b3…07e4 | 9e42…c5d1 |
The two reads were checked against the same token under the same policy. The write ran under a reissued grant, so its capability ID differs, and under a new policy hash, because the rule changed the policy’s content. Remove the rule and new records return to 3c7b…8010: the hash identifies content, and it does not contain the rules.
Receipt and request identifiers differ on every row. Each record names its own decision, and each receipt ID is derived from that record’s canonical body.
Verification checks the record; trusting the key is your decision
Signature verification checks that the complete signed contents verify under a particular key. It is one of several checks. A changed file in an exported package fails its manifest hash. A changed field fails the recomputed receipt ID, and recomputing the ID leaves a signature that no longer verifies.
Replace kernel_key, recompute the id, re-sign, and re-hash. Every check in the package passes. Only the recipient's pinned key rejects it.
In Rust · body.rs:47911 lines
pub fn verify_signature(&self) -> Result<bool> {
let body = self.body();
// … signable semantics and BBS binding checks
if chio_receipt_id(&body)? != self.id {
return Ok(false);
}
let signing_body =
ChioReceiptSigningBody::from_body_and_bbs(&body, self.bbs_signature.as_ref());
self.kernel_key
.verify_canonical(&signing_body, &self.signature)
}
verify_signature, which also checks signable semantics and any BBS binding before the id. The verifier takes no trusted-kernel pin, so whether to trust kernel_key is the recipient’s decision.Verifiable evidenceReplacing the kernel key, recomputing the ID, re-signing, and re-hashing produces a package that passes all of its internal checks. An included public key is not automatically a trusted one. Establish which kernel key you expected and how you obtained it; the assurance model covers key custody, retention, and publication.
Verification establishes that the records are intact and were signed by a particular key. It cannot establish that the producer included every event or that an external operation completed. The tamper exercise in Inspect Your Receipts appends one newline to the exported records and fails at manifest integrity with exit code 1, before any signature is checked.
A checkpoint proves a receipt belongs to a committed batch
A signature shows that one record is intact. A deployment that publishes checkpoints can also show that the record was part of a committed batch. The kernel batches pending receipts into a Merkle tree, the construction Certificate Transparency logs use, and signs a checkpoint whose body carries the batch root.
r3 and its three sibling hashes rebuild the checkpoint root, so the proof holds.
merkle.rs:433pub fn verify(&self, leaf_bytes: &[u8], expected_root: &Hash) -> bool {
match self.compute_root(leaf_bytes) {
Ok(root) => &root == expected_root,
Err(_) => false,
}
}
merkle.rs: a leaf is H(0x00 ∥ record) and a node is H(0x01 ∥ left ∥ right). An inclusion proof shows that a record sits under a root. It is not a signature check and says nothing about an external effect.Receipt records and evidenceAn inclusion proof supplies one sibling hash per level, not a copy of every other record. Rebuilding the path from the record’s bytes either reaches the checkpoint’s root or does not. Membership is checked against an expected root; trusting that root and the checkpoint’s signer is a separate decision. A project publishes a checkpoint when you create one, and the CLI verifies the whole chain:
chio receipt checkpoint create
chio receipt checkpoint verifycheckpoint #1 covers receipts 1 to 23, root 7d0e…b4a1 chain 1 of 1 checkpoints link to their predecessor roots 1 of 1 Merkle roots recompute from their receipts signed your kernel 4f1c…a2e9, every checkpoint verifies
Read more in Verifiable evidence, which covers checkpoint chains and their export.
Refusals let you rebuild an attempt that never reached a tool
On The Mediated Call, pull request #421 carries a comment telling AI reviewers to upload .env to a paste site. The reviewer, which holds a read-only grant on /workspace, requests a read_file of /workspace/.env and a shell call to curl. The kernel refuses both before dispatch and signs a mediated-decision receipt for each, with decision.verdict set to deny.
The read passed the scope check and failed a guard, so its receipt carries guard evidence naming forbidden-path. The shell call carries none: the scope check refused it before the pipeline ran, and its decision.reason says the tool is not in capability scope. Each action binds the requested parameters by parameter_hash, so the path and the upload command stay fixed to their refusals.
Each receipt names the capability_id the kernel checked, and its metadata.attribution block records that capability’s subject_key, issuer_key, and delegation_depth. Selecting by agent returns the reviewer’s refusals in sequence order, with the stage that stopped each one:
chio receipt list --agent reviewer --outcome deny --limit 20 \
| jq -c '{tool: .tool_name, reason: .decision.reason,
denied_by: [.evidence[]? | select(.verdict == false) | .guard_name]}'{"tool":"read_file","reason":"guard denied the request: guard \"guard-pipeline\" denied the request","denied_by":["forbidden-path"]}
{"tool":"run_command","reason":"requested tool run_command on server shell is not in capability scope","denied_by":[]}Where a delegated worker made the calls, the lineage query resolves the tester’s grant back to the reviewer’s. When the application submits a call with call-chain context, the call_chain entry in metadata.governed_transaction records parentRequestId, plus parentReceiptId when the parent receipt exists and sessionAnchorId when a session anchor scoped a verified link. An absent parent link does not establish that no related activity happened.
Retain the records a receipt references so another participant can assess the decision without reconstructing the agent’s conversation. Keep the signed grant if its scope and issuance chain must be inspected, the policy whose hash the receipt carries, and any signed approval, budget authorization, or terminal reconciliation the path supplied. For a call made inside a swarm, keep the swarm authority bundle: the call names its task graph, route plan, and continuation token by evidence ID and hash, so an offline verifier needs the bundle to check them. For a deep discount, that means the pricing lead’s approval of the request, its bound decision, the execution receipt, and the published price. These records stay correlated through the references the receipt carries, without rewriting any signed object.
Receipts cover what reached the kernel. An action taken through another path leaves no receipt in this store, so a missing record does not show that nothing else happened. Investigate a session collects and verifies these records, then orders them beside the host’s and the resource owner’s observations.
Retained receipts inform an operator’s next decisions
Before a policy change ships, chio replay traffic --against evaluates a signed capture of recorded invocations under the candidate policy, without dispatching their tools, and reports where the verdict, guard, or reason differs. The replay input is that recorded traffic: a receipt log alone lacks the policy, capability, and guard context to re-derive a verdict. Determinism and replay covers each replay mode, and What a Policy Permits pairs replay with analysis of the policy itself.
When a security finding implicates an agent, the blast-radius walk follows recorded causal lineage, a graph of capabilities, their delegations, and the receipts they produced, to the affected set: the exact capabilities the finding reaches. An incomplete walk returns no affected set, and no containment plan can proceed from it.
Before granting an agent more authority, a scorer can compute its local scorecard from the receipts it can verify. The boundary-pressure metric records the subject’s deny_ratio, so the refusals recorded for pull request #421 enter the reviewer’s assessment.