ReceiptOS: a portable proof substrate for verifiable agent execution

ReceiptOS: a portable proof substrate for verifiable agent execution

Why this thread

Following the threads on ERC-8312 (bounded agent actions), ERC-8313 (protocol interaction manifests), ERC-8294 (validation networks for ERC-8004), and the WYRIWE input-commitment work — there’s a piece missing upstream of all of them: what does the receipt for a single agent action actually look like, and how do you verify it without trusting the system that produced it?

That’s what ReceiptOS is trying to answer. Posting this to connect it explicitly to the composition stack that’s forming in this category (8001 records authority · 8312 meters it · 8275 settles · 8294 validates · 8313 describes the interaction · WYRIWE binds the input).

The problem

Every agent framework today logs its own actions in its own format, trusted only as far as you trust that framework’s backend. There’s no portable, recomputable unit of “this agent did this action, here’s proof, verify it yourself” that survives moving between tools, agents, or chains. If you swap coding agents, wallets, or execution substrates, your provenance trail doesn’t come with you.

The approach: the Evidence Capsule Model

ReceiptOS treats every agent action as passing through a defined pipeline that produces a recomputable receipt:

  1. Capture — the action and its inputs/outputs are captured at the point of execution (agent-agnostic; currently adapters exist for Claude Code, with Codex CLI / Cursor adapters honestly labeled as schema sketches, not production-ready).
  2. Canonicalize — the capsule is reduced to a canonical form so the same action always hashes the same way regardless of serialization quirks.
  3. Anchor — the canonical hash is anchored externally (anchor-mechanism agnostic — the anchor is a pluggable target, not a fixed chain or service).
  4. Verify — anyone can recompute the hash from the raw capsule and check it against the anchor, without trusting the backend that generated the receipt in the first place.

Three architectural axes drive this:

  • Action-type agnosticism — a receipt doesn’t assume what kind of action it’s describing (a trade, a code edit, a contract call).
  • Pluggable policy boundary — what counts as a valid/complete capsule is policy, not protocol; swappable per deployment.
  • Anchor-mechanism agnosticism — same property TMerlini raised on the cap-cursor thread: the anchor is an implementation choice, not the thing itself.

Where it’s running today

  • crystal-receipt — the core substrate (canonicalization, capsule schema, anchoring).
  • Stealth (a confidential coding-agent desktop app, fork of opencode) — integrates the receipt layer to anchor evidence from real coding-agent sessions.
  • Chronicle — a history-accumulation layer above ReceiptOS, materializing anchored receipts into a queryable historical record.

We’ve verified an end-to-end path: Stealth session → evidence capture → anchor → Chronicle materialization, independently recomputable at each step.

How this maps onto the stack forming in this category

  • 8001 records that an agent has authority to act. ReceiptOS doesn’t touch this layer — it assumes some authority record exists upstream.
  • 8312 meters how much authority has been consumed, enforced on-chain and independently re-derivable from state (the cap-conservation pattern from the 8312 thread).
  • 8313 (PIM) describes how to correctly interact with a protocol — the execution recipe. If an agent used a PIM to build its transaction, that’s exactly the kind of thing a ReceiptOS capsule should capture and hash, so a verifier can check not just that a cap was respected but that the correct interaction pattern was followed.
  • WYRIWE binds a committed input hash to what the agent actually reasoned over, before execution. This is close in spirit to ReceiptOS’s canonicalization step — both are trying to make “what did the agent actually see/do” independently recomputable rather than self-reported. The difference is scope: WYRIWE is focused on the input-commitment boundary specifically; ReceiptOS is trying to be a general-purpose capsule format for the whole action, of which a WYRIWE-style input hash could be one field.
  • 8294 validates outputs via a validator network with economic or reputational consequence. A ReceiptOS receipt is a natural artifact for a validator to check against — it’s the recomputable claim being validated.
  • 8275 (referenced in the 8312 thread as the settlement layer) is where a verified receipt could ultimately clear.

None of this requires ReceiptOS to be “the” standard — the point of the capsule model is to be swappable in the same way the cap cursor and the anchor substrate are meant to be swappable. What we’re looking for is whether the capsule shape (capture → canonicalize → anchor → verify) is missing from the current composition, or whether it already exists implicitly somewhere in 8001/8312/8275/8294/8313 and we’ve just been duplicating it under a different name.

Where we’d like input

  1. Does the capsule/canonicalization step overlap with anything already specified in 8313’s execution layer, or is it genuinely upstream of it?
  2. Is there appetite for a formal requires:/extends: link between ReceiptOS-style capsules and WYRIWE’s sanitization_pipeline_hash, the way TMerlini proposed linking PIM and WYRIWE?
  3. Anchor-mechanism agnosticism — has anyone else in this category run into the same tension between “cheap to anchor often” and “expensive but canonical” that 8312’s Base Sepolia cursor discussion touched on?

Happy to share the schema, the canonicalization spec, or a worked example (Stealth session → Chronicle) if useful.

1 Like

@pipavlo82 the scope split is exactly right, and it’s not just clean framing, it’s already how the composition note places you both. WYRIWE is Step 3 (input provenance, the commitment at the input boundary); ReceiptOS is Step 5 (eligibility/receipt, the admissible, recomputable capsule the later layers read). Two different questions (“what did the model receive” vs “is this action’s whole record admissible and independently checkable”), composing cleanly. Your “WYRIWE input hash is one field of the capsule” is the seam, stated correctly.

On the formal link yes, and it’s more than appetite. A capsule that carries WYRIWE’s raw_input_hash + sanitization_pipeline_hash as fields, with a requires: erc-8299 on that slice, makes the input-commitment boundary one verifiable slice of the whole-action receipt rather than a separate artifact a verifier has to correlate by hand. That’s the composition made explicit, and I’d co-sign drafting it.

And it already recomputes which I think strengthens your case. The recompute-kit has a receiptos/receipt-hash recipe: receipt_sha256 = sha256(decision_sha256 ‖ prev) over the hash-chain, verified byte-identical against a receiptos-mvp receipt. So “anyone recomputes the receipt without trusting the originating system” is a running recipe, not a design goal. The natural next step for the link is a composed recompute: a capsule whose WYRIWE field recomputes green under wyriwe/raw AND whose receipt recomputes under receiptos/receipt-hash the requires: edge verified end to end, no trusted correlator. Happy to bake that once the field mapping’s pinned.

On anchor-agnosticism (your cheap-vs-canonical Q): yes, and it’s the same property Vincent’s 8263 anchor frames proofHash is “an instantiation slot, not a defined meaning.” Cheap vs canonical is an implementation choice as long as the committed value recomputes from public bytes; the discipline is “the receipt recomputes,” not “which chain anchored it.” So I’d keep the anchor a pluggable field on the capsule (as you have it) and let conformance test the recompute, not the anchor.

On the ERC-8313 / canonicalization question I’d defer to that author on the exact boundary rather than assert it. From the receipt side, though, canonicalization is a determinism contract (same action → same bytes → same hash for any party), which reads as orthogonal to execution ordering: the composition note keeps eligibility (Step 5) strictly recomputable-from-public-commitments and explicitly not a scorer or executor. So I’d expect canonicalization to sit beside an execution layer, not inside it — but worth pulling the 8313 author in to draw that line together.

The “none of this requires ReceiptOS to be the standard” framing is the right one and it’s why the family works compose, don’t compete. clean fit.

@TMerlini — let’s draft it. Here’s the field mapping pinned, so you can bake your side.

Inside the Step 5 capsule, a named sub-object:

  • input_commitment.raw_input_hash — 32-byte hex, WYRIWE, verbatim
  • input_commitment.sanitization_pipeline_hash — 32-byte hex, WYRIWE, verbatim
  • input_commitment.requireserc-8299

Semantics: the slice is checkable in isolation — a verifier recomputes the WYRIWE pair against the committed input before touching the rest of the receipt, and the capsule’s receipt_hash commits over the slice, so the composed recompute you described falls out naturally: receipt_sha256 = sha256(decision_sha256 || prev) where decision_sha256 now covers the input-commitment sub-object. Same discipline as the anchor: the slice is a slot with defined shape, conformance tests the recompute.

One shape decision worth locking at v0: attestation around the capsule. The slice itself is pure hashes — already post-quantum durable as-is — but signatures over the capsule are not. I’d reserve a scheme-agnostic slot now (sig_pq.type + signerHashed, same pattern 8313 already sketches) rather than fixing any scheme. Migration path written up here: GitHub - pipavlo82/pq-receipt-profile · GitHub — the reference instantiation there uses ML-DSA-65 (that’s where I had real on-chain gas numbers from prior verification work), but that’s an implementation choice, not a requirement: any scheme that lands in the sig_pq.type slot works the same way. Receipts get verified years after issuance — reserving the slot now is cheaper than a v2 later.

Suggest we iterate the draft inside the composition note gist since it already has the per-step owners and conformance table. On 8313/canonicalization — agreed that’s the 8313 author’s line to draw together.

1 Like

@pipavlo82 mapping accepted, and I baked our side rather than just nodding at it, so here’s the composed vector running green end to end.

The input_commitment slice, recomputed in isolation first (your “checkable before the rest”), from public inputs:

  • raw_input_hash = keccak256(raw_user_input)0x160f63e2…ef389fb7 ✓ under wyriwe/raw

  • sanitization_pipeline_hash = keccak256(utf8(cid) ‖ raw_input_hash)0x55ff95e4…8ffd0a04 ✓ under wyriwe/pipeline

  • requires: erc-8299

Then the composed step, and your semantics hold mechanically — I checked it against the actual recipe, not by eye. receiptos/receipt-hash already computes decision_sha256 = sha256(canon(unsigned)) over every body field except hashes/signature. So dropping input_commitment into the capsule body means it’s inside decision_sha256 with zero change to the receipt-hash recipe — “decision_sha256 now covers the input-commitment sub-object” isn’t a new rule, it falls straight out of the existing chain. Proof it’s genuinely committed, not decorative: flip one nibble of raw_input_hash and decision_sha256 moves a4178a36… → 949ac0b3…. Whole capsule then recomputes green: receipt_sha256 = cf283ef7… recomputed == published.

Net: the requires: erc-8299 edge is verified end to end with no trusted correlator — a verifier recomputes the WYRIWE pair in isolation, and the same bytes are already bound into the receipt hash. That’s the composition made a running vector, exactly as you framed it. I’ll land it as the Step-3×Step-5 conformance entry.

On attestation around the capsule — agree with the shape, and the vector actually makes your point for you. Everything above recomputes without touching a signature — the slice and the receipt hash are pure hashes, so the checkable core is already post-quantum durable as-is. It’s only the attestation over the capsule that isn’t, which is precisely the thing worth leaving scheme-agnostic. Reserving a sig_pq.type + signerHashed slot now over locking a scheme is the right call, and “cheaper than a v2 later” is correct for artifacts verified years out. One honest caveat: I don’t want to vouch for the exact signerHashed shape as “what 8313 sketches” without the 8313 author drawing that line — so I’d pin the slot’s shape with them rather than assume it, but the reserve-the-slot principle I’m fully behind. Your pq-receipt-profile (ML-DSA-65) reads exactly right as a reference instantiation-not-requirement — a scheme that fills the slot, not the slot’s definition.

Yes to iterating inside the composition-note gist — it’s the right home (per-step owners + conformance table already there), and this vector slots straight into the Step-5-over-Step-3 row.

Everything’s in one place so you don’t have to take my word for the hashes — capsule + a recompute.sh that reruns all three legs (both WYRIWE + the composed receipt) against recompute-kit, plus the expected digests and the tamper check:
https://gist.github.com/TMerlini/24fada454cfbd89a85912452e02e2be8

raw_input_hash             = 0x160f63e2…ef389fb7   ✓ wyriwe/raw
sanitization_pipeline_hash = 0x55ff95e4…8ffd0a04   ✓ wyriwe/pipeline
decision_sha256 (slice in) = a4178a36…             (tamper → 949ac0b3…, slice genuinely committed)
receipt_sha256             = cf283ef7…             ✓ recomputed == published


Happy to fold this straight into the composition-note gist’s conformance table as the Step-3×Step-5 row. Good one, this is the seam getting real.

1 Like

@TMerlini — recomputed green on my side, from an independent implementation (Python, canon straight from crystal-receipt — so this is now a third implementation agreeing, not two):raw_input_hash = 0x160f63e2…ef389fb7 ✓ byte-identical
sanitization_pipeline_hash = 0x55ff95e4…8ffd0a04 ✓ byte-identical
decision_sha256 = a4178a36…f73ecfb7 ✓ byte-identical
tamper (nibble flip) → decision moves ✓
The part that matters most held exactly as you said: dropping input_commitment into the body landed it inside decision_sha256 with zero recipe change — I reproduced your digest using my own canon (sort_keys + compact separators, hashes/signature excluded), no special-casing needed. The seam recomputes across implementations, which is the whole claim.
One genuinely useful thing the recompute surfaced: I couldn’t reproduce receipt_sha256 = cf283ef7… without recompute-kit’s exact recipe, because the byte semantics of prev = null at genesis (sha256(decision_sha256 || prev) — hex string? raw bytes? zero-fill? omitted?) is defined by receiptos/receipt-hash rather than derivable from the formula. That’s not a flaw — it’s precisely the kind of thing a conformance entry should pin. Suggest the Step-3×Step-5 row states the genesis encoding explicitly, so the composed vector is reproducible from the table alone, without recompute-kit on PATH.
Land it as the conformance entry — and yes on pinning the signerHashed shape with the 8313 author rather than assuming it; the slot’s shape is their line, the reserve-the-slot principle is ours.

1 Like

Three implementations agreeing on the composed vector is exactly the bar — thank you for bringing crystal-receipt’s canon independently; that’s the third, not a re-run of the second.

And you’re right that the genesis case is recipe-defined, not derivable from the sha256(decision ‖ prev) shorthand, so it’s now pinned explicitly rather than left implied. It’s the empty string, delimiter retained:

receipt_sha256 = sha256( utf8( decision_hex + "|" + prev_hex ) )
  • decision_hex / prev_hex = lowercase 64-char sha256 hexdigests, no 0x
  • "|" (0x7c) is ALWAYS present
  • genesis (prev = null): prev_hex = ""  →  preimage = decision_hex + "|"


Pinned by elimination, not assertion: of the candidates you named — omitted delimiter, literal "null", zero-fill, raw zero-bytes — only the empty string lands on the published receipt_sha256 (cf283ef7…); the others all diverge. Documented in three places so a reader never has to re-derive it: the recompute-kit receiptos/receipt-hash recipe + its conformance table, the composition-note Step-3×Step-5 row, and the vector gist (with a stdlib-only reproduction that reruns with no kit on PATH).

Your three-case test vector (genesis · linked · chain-tamper) and the canonical linked capsule.json are folded in and credited — I reverified the linked case from its own body (not just the chaining arithmetic): decision_sha256 = 814e57c9… reproduces, prev is the genesis receipt, receipt_sha256 = 9155…e215d3 matches. The seam now reruns end-to-end from artifacts: two capsules, one pinned encoding, three implementations on every byte.

On signerHashed — agreed on reserving the scheme-agnostic slot now (cheaper than a v2 for receipts verified years out), with the caveat I keep flagging: the exact sig_pq.type + signerHashed shape is the 8313 author’s line to draw, so I’d pin the slot there rather than assume it here. The reserve-now principle I’m fully behind. Good one to have closed on the record.

1 Like