# 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:

```json
{
  "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](/spec/gateway).

## 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](/guides/give-your-ai-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.

Published 2026-09-23 by Holon.
