An audit trail exists to answer one question, asked by someone who was not there: why does this line exist? Below is a single agent run, recorded end to end, including the moment a gate refused its first attempt. Click any step to see what was written down at that point, and why that field is in the schema.
Everything here is fabricated to be representative. The schema in the right-hand column is the part to steal.
charter_sha. Without it you cannot answer "which rules were in force when this ran", and a year from now the charter will have moved thirty times. It also makes every run joinable against the eval history, so a regression in output quality can be traced to the exact rule edit that preceded it.
billing/refund.py sha 6c1a… read
billing/order.py sha 91be… read
billing/money.py sha 0f4d… read
tests/test_refund.py sha aa20… read
CHARTER.md sha a91f… read
.claude/rules/money.md sha 77c9… read
Store path plus content hash plus the repo sha. Never the file body. Bodies make the trail enormous, duplicate your source of truth, and turn a log store into somewhere secrets go to live forever. The hash is enough to prove what was read, and the repo already has the text.
CHOSEN extend refund_for_order(), branch on order.status
REJECTED new refund_cancelled_order() entry point
reason: duplicates the discount reconciliation
block; charter §consistency
The rejected alternative is the highest-value row in the whole trail and the one almost no system records. It is what makes a review possible six months later, and it is the same artifact an engineer needs for evidence of judgment. Capture it here and it is produced automatically instead of remembered.
Recording only the result makes concurrent edits unresolvable. With the pre-image hash you can prove the agent edited the file it thought it was editing, which is the failure that appears the moment two runs touch one repo.
✗ money.integer-cents
billing/refund.py:48
- refund_total = order.total * (1 - discount_pct / 100)
^^^^^^^^^^^^^^^^^
float division on a money path. Charter §money:
"money is integer cents everywhere; percentages resolve
via Decimal, then round half-up to cents."
A gate that blocks and is never recorded looks, from the outside, exactly like a gate that never fired. Six months later nobody can tell whether the rule is load-bearing or dead weight, and dead rules are what R4 is supposed to delete.
Record every firing, including the ones that pass. The pass count is what tells you a rule still earns its place; the block count is what tells you it is doing work today.
agent received the gate output verbatim and revised:
+ pct = Decimal(discount_pct) / Decimal(100)
+ refund_total = round_half_up(order.total_cents * (1 - pct))
An unbounded correction loop burns budget and eventually talks itself into satisfying the gate rather than solving the problem. Two attempts, then a human. The budget belongs in the trail so that "why did this escalate" has an answer that is not somebody's memory.
Four rules ran, four are written down. A trail that records only failures cannot answer the most useful maintenance question there is: which of our rules has not blocked anything in twelve months?
"412 passed" is the non-claim. The line that matters is the revert check: proof the new test can fail. Without it the trail records a green pipeline, which has never meant the code is correct.
A trail nobody can find is a trail nobody uses. One line in the PR body, pointing at this view, is what converts the store from compliance furniture into the thing a reviewer opens when a diff looks odd.
| field | why it is there |
|---|---|
| run_id | Joins everything. Generate before the first token. |
| parent_run_id | Subagents. Without it a fan-out is unreconstructable. |
| initiated_by | A human is accountable for every run. Name them. |
| model | Exact id. "the model" is not a value. |
| charter_sha | Which rules were in force. The most-forgotten field. |
| repo_sha | What the world looked like going in. |
| prompt_sha | Hash, not text. Proves sameness without storing it. |
| context_refs | path + content hash. Never bodies. |
| tool_calls[] | name, args (redacted), result hash, ms. |
| gates[] | rule_id, verdict, matched location. Passes included. |
| alternatives[] | What was rejected and why. The rarest, most valuable. |
| retry_index | With the budget, so escalations explain themselves. |
| pre_sha / post_sha | Both. One of them is useless alone. |
| outcome | pr number, merge sha, or the reason it stopped. |
Raw prompts containing customer data, tool arguments carrying credentials or personal information, whole file bodies, and full model outputs. Hash or redact at the point of capture, not in a cleanup job later. An audit store is append-only by design, which means a mistake in it is permanent by design.
Different retention per tier is what keeps the store affordable: 30 days for tool detail, forever for the step summary.
Pick a merged change from three months ago at random. Using only the trail, answer: why does this line look like this? If you can name the alternative that was rejected, the rule that constrained it, and the human who is accountable, you have an audit trail. If you can only see what happened and in what order, you have logs.
Timestamps, tool names, token counts. Answers "what happened". Cannot answer "why this and not the other thing".
Gates recorded but not which charter produced them. You know a rule fired; you cannot reconstruct the ruleset it belonged to.
Alternatives, gate verdicts including passes, charter sha, accountable human. Reconstructable by a stranger.
Do not build a store. Append one JSON line per run to a file, with eight fields:
{"run_id","initiated_by","model","charter_sha",
"repo_sha","gates","alternatives","outcome"}
That is an afternoon, it fits in the repo, and it passes the replay test. Everything on this page is what you add after the cheap version has proved somebody actually opens it.
Teams build the dashboard first and the schema second, and end up with a beautiful view over data that cannot answer the only question that matters. The schema is the product. This page is just a way of looking at it.