2026-09-23 · Holon

What a receipt must say

Every call on Holon leaves a receipt: who called which agent version, under which mandate, the status, the worst case, the real cost and how it was split. A receipt never contains the input or the output. Delegated runs produce a tree of receipts, so an operator can reconcile a whole run without reading anyone's data.

The fields, and why each one is there

A receipt is written for every request, including denied ones. Here is one from the repository, for an orchestrated run:

{
  "id": "r4", "parent": null, "mandate": "alice/bookkeeping-q4", "caller": "alice/assistant",
  "agent": "holon-labs/invoice-pipeline@0.4.0", "status": "succeeded", "approval": "a3",
  "worst_case": "3.10", "currency": "EUR", "payer": "alice",
  "sub_mandate": "alice/bookkeeping-q4.r4",
  "billed": true, "cost": { "own": "0.10", "downstream": "0.092", "total": "0.192" },
  "splits": [{ "to": "holon", "amount": "0.01" }, { "to": "holon-labs", "amount": "0.09" }],
  "children": ["r5", "r6", "…"], "duration_ms": 10
}

Each field answers a question someone will eventually ask.

agent carries the exact version, not the name. Versions are immutable, so @0.4.0 pins the manifest: its price, its interface, its data policy and the commit that ran. A receipt that named only holon-labs/invoice-pipeline would be worthless a month later.

mandate and caller say under whose authority the call happened. payer says whose balance moved, which is not always the same account: under passthrough delegation the original payer pays for the children.

worst_case is the promise made before the call, cost is what happened. Keeping both on the same row is what makes the rule checkable rather than a claim on a web page. own and downstream are separated because an orchestrator's bill is two different things: its own work, and what it bought on your behalf.

splits name every account that received money, so the sum of the splits equals the cost. On the hosted gateway the same movement exists as a journal entry whose lines sum to zero, written in the same transaction as the receipt. A settlement and its receipt cannot come apart.

status and billed are separate fields on purpose. A failed call has a receipt, with billed: false. So does a denied one, and one held for approval. Absence of a charge is recorded, not inferred from silence.

What we refuse to put in it

Never the data. Not the input, not the output, not a truncated version of either, not a summary. The gateway itself keeps nothing from a call, so there is nothing to attach.

This is a constraint we accept knowing what it costs. It means a receipt cannot help you debug why an extractor returned the wrong total. It means support cannot look at your document. We think that is the right trade. An operator who can read the content of calls is one subpoena, one misconfigured backup or one curious employee away from a breach, and agents are handed exactly the documents people care about most: invoices, contracts, medical letters.

There is one narrow exception, and it is bounded. A call held for approval stores its input, since the human has to approve something concrete and the call has to run afterwards. That input is deleted as soon as the approval is used or rejected, and pending approvals expire after seven days and forget their input then. The input is validated against the schema before admission, so nothing is stored for a call the agent would have refused anyway.

Uploaded files are the other thing Holon holds between calls: they are deleted twenty four hours after upload, only the owner can read them, and agents receive a signed link valid fifteen minutes. The rules are in the gateway standard.

Reconciling without reading anything

The test of a receipt design is whether an operator can answer a money question with it alone.

Take a delegated run. The parent receipt lists its children, each child is a receipt of its own with its own status, cost and splits, and the parent's cost.downstream is the sum of the billed children. A run is not settled until every downstream call has settled, so the tree is complete before the total is final. "Why did this cost 0.192 EUR?" is answered by walking a tree of rows: the orchestrator's own 0.10, plus the children that succeeded, with the failed ones showing billed: false.

Take a budget question. A mandate's spend is the sum of its receipts, and reservations are made at the worst case when a call starts, so parallel calls cannot together overrun a budget. Take an author's earnings: they are computed from the journal, per version, including royalties from forks. Take a dispute: the receipt names the version, and the version names the repository and the commit.

None of those answers needs a single byte of the content. That is the property we were after. An operator should be able to run the platform, reconcile it, and back it up without ever being in a position to read your work. Giving an agent a budget explains the other half, the mandate that decides what may happen before the receipt records what did.

What is still missing

Receipts are loaded in memory at start-up. That is fine for a prototype and will have to be paged before volume. Mandates are not yet signed documents another gateway could verify: the hosted gateway authenticates the issuer, which is weaker. And there is no page yet where a human can browse a receipt tree outside the console. None of those change what a receipt contains.

Go further

Published 2026-09-23. Figures are measured as explained in how we measure.