On this page
The short answer
An audit trail is the ability to reconstruct a past decision from evidence you pinned at the time: the exact rendered prompt, the model version that answered, the tool code that ran, the documents that were retrieved by content hash, the policy version in force, and the clock. Application logs do not do this, because they record what your code said, not what the agent saw. Build the record at decision time. It cannot be reconstructed afterwards, and the request always arrives months later.
- 6pins that make a run reconstructable: prompt, model, tool, evidence, policy, clock
- args_sourcethe field that answers whether the model invented a number or read it
- Not evidencehow a model's own explanation must be labelled in the record
- Write timewhen redaction and retention classes have to be decided, never at read time
The trigger is rarely a regulator. It is a customer disputing a refund, a finance lead asking why a batch went out wrong, or an engineer asking whether Tuesday's prompt change caused the complaints. All three ask the same thing: what did the system see, and why did it act.
What auditable actually means
Auditable is not a property of a system. It is a list of questions you can answer without guessing, so write your own list and check that each row has an artifact behind it.
| The question someone asks | The artifact that answers it | What you had to record at the time |
|---|---|---|
| Why did it refund this customer? | The decision record for that action | Rendered prompt, evidence hashes, argument sources |
| Which policy version applied? | The policy pin on the record | Policy id and version, not a link to the current page |
| Did a human approve it? | The controls block | Approver id, timestamp, decision, edited fields |
| Did the model invent that number? | args_source for that argument | Provenance per argument, captured before execution |
| Which runs did the bad prompt affect? | A query on prompt version and time range | Prompt version on every run, indexed |
| Would it decide the same today? | A replay against pinned inputs | Tool responses recorded, model version pinned |
| Delete everything about this person | Subject index plus per subject key | subject_ids on every record, PII fields encrypted |
Two rows tend to be missing entirely. Nobody records where each argument came from, so the invented-number question is unanswerable, and nobody records subject ids, so an erasure request means a full text scan or a promise you cannot keep.
The Six Pins
A run is reconstructable when six things are pinned at the moment of the decision. Miss one and the record becomes a story about the past rather than a copy of it.
The Six Pins
Each pin costs a few bytes and a line of code at call time. Each one is impossible to add retrospectively, which is the entire reason this list exists.
The template id, its version, the bindings, and a hash of the fully rendered prompt. Store the hash always and the rendered text where retention allows. Without it, "we changed the prompt around then" is unfalsifiable, and it is the first thing anyone says in an incident.
Provider, the model id you requested, the model id the response actually reported, and the sampling parameters. Requested and returned are different fields on purpose: aliases move underneath you, and the returned string is the one that tells you what really answered.
Name, semantic version, code revision and a hash of the tool schema. A tool that quietly changed its filter behaviour makes every earlier record misleading, and the schema hash turns that from an argument into a diff.
Every retrieved document by content hash with an as_of timestamp, never by URL. URLs mutate, files get edited in place, and a page that reads differently today proves nothing about March. Hash the bytes, store them or point into an immutable store, and dedupe by hash.
The business rules in force: thresholds, ceilings, allowlists, the refund policy version. This is usually the thing that actually changed, it usually lives outside the code repository, and it is almost never versioned until the first time someone asks.
The logical time of the run and any time-dependent input, such as a balance, a rate or anything described as current. Replay at a different clock gives a different answer, and without the pin nobody can tell a genuine bug from time having passed.
Record the model identifier returned in the response metadata beside the one you requested. Deployments alias to moving targets, and a request for a general model name can be served by different snapshots over a month. When behaviour shifts on a day you did not deploy, those two fields are the difference between a two minute diagnosis and a week of theories.
The decision record
One record per decision that has an effect, not one per model call. Chat turns are telemetry. Decisions are what an auditor, a customer or a court asks about, and they deserve their own durable object with its own retention class.
{
"record_version": "2",
"run_id": "run_2b91f7",
"step_id": "step_04",
"occurred_at": "2026-08-26T09:14:37.221Z",
"logical_time": "2026-08-26T09:14:00Z",
"actor": { "type": "agent", "name": "refund-agent", "deployment": "prod-eu-1" },
"subject_ids": ["cust_1099"],
"pins": {
"prompt": { "template": "refund/v14", "rendered_sha256": "b41c..." },
"model": { "provider": "acme-ai", "id_requested": "model-x",
"id_returned": "model-x-2026-07-19", "params": { "temperature": 0, "top_p": 1 } },
"tools": [ { "name": "issue_refund", "version": "3.2.0", "code_sha": "9d21f7c",
"schema_sha256": "0af3..." } ],
"policy": { "id": "refunds", "version": "2026-03" },
"evidence":[ { "id": "doc_7a1f", "sha256": "9c1e...", "as_of": "2026-03-02T00:00:00Z" },
{ "id": "orders/88213", "sha256": "77bd...", "as_of": "2026-08-26T08:02:11Z" } ]
},
"decision": {
"action": "issue_refund",
"args": { "invoice_id": "INV-88213", "amount": 420.00, "currency": "GBP" },
"args_source": {
"invoice_id": "evidence:orders/88213",
"amount": "policy:refunds#4.2",
"currency": "evidence:orders/88213"
}
},
"controls": {
"gate": "human_approval",
"human_actor": "u_442",
"decided_at": "2026-08-26T09:16:02Z",
"decision": "approve_with_edit",
"edited_fields": ["amount"]
},
"outcome": {
"status": "applied",
"external_ref": "pmt_re_88213_01",
"idempotency_key": "run_2b91f7:issue_refund:5f2a..."
},
"model_narrative": {
"text": "Policy 4.2 applies because delivery exceeded seven days.",
"label": "generated after the action, treat as a hint, not as evidence"
},
"retention": { "class": "financial_7y", "pii_fields": ["subject_ids"], "key_id": "k_cust_1099" }
}- args_source is the field people leave out and later need most. For every argument, say where it came from: an evidence id, a policy clause, a human edit, or model generation. An amount tagged model_generated in a financial action is either a bug or a policy decision, and either way you want to know before the customer does.
- subject_ids belong on every record. They are how you answer access and erasure requests without scanning free text, and adding them later means backfilling a store you may not be allowed to read.
- Link the outcome to the external reference. The payment id, the message id, the ticket id. Without it you can prove what was decided but not what happened, and those diverge exactly when it matters.
- Keep the model narrative in its own labelled field. It is useful context for a human reader. It is not evidence, and mixing it into the record is how it ends up quoted as though it were.
The model's explanation is not evidence
A model asked why it did something produces a fluent, plausible account that need not correspond to the computation behind the action. It is generated after the fact, by the same process that produced the output. Treat it as a hint for a human reader, never as the record.
The reliable substitute is mechanical. Record the inputs, the retrieved evidence, the exact arguments and where each one came from. Those are checkable facts. If the refund amount is tagged as sourced from policy clause 4.2, anyone can open clause 4.2 and confirm it. If it is tagged model_generated, you have learned something important, and no explanation the model offers changes either conclusion.
The tempting pattern is a nightly job that summarises runs into readable narratives, which then become what people read during an incident and what gets pasted into an audit response. Summaries drop the fields that turn out to matter, and they are themselves model output with no provenance. Keep summaries as a derived view, clearly labelled, with the raw record one click away and addressable by id.
Three levels of replay
Replay is what turns a record into an investigation tool. There are three levels, and most teams need the middle one.
- Reconstruct. Re-render the prompt from the template version and bindings, and display it beside the retrieved evidence. No model call. This answers most questions and costs nothing.
- Deterministic replay. Re-execute the run against recorded tool responses, so the tools return exactly what they returned then. This isolates the model from a changed world and is how you test whether a fix would have prevented the incident.
- Live replay. Re-execute against live systems in a sandbox. Useful for checking whether current behaviour is acceptable, useless for reconstructing the past, and dangerous if anything writes.
One caution about level two. Even with sampling parameters fixed, providers do not generally guarantee identical output for identical input across time and infrastructure. Replay is evidence about inputs and control flow, not proof that the model would emit the same token sequence. Say that out loud in your audit documentation, because a claim of exact reproducibility is one someone will eventually test.
Decide early how much of each tool response you keep. Full responses make level two possible and grow fast; hashes alone are cheap and make it impossible. The usual answer is full responses for decisions with an effect, hashes for the rest.
What it costs to keep
Audit storage is usually cheap and occasionally surprising, and the surprise is always evidence rather than text. Run the arithmetic with your own numbers, because the difference between 90 days and seven years should be a deliberate decision.
Rough sizing before compression and before evidence deduplication. Substitute your own storage price, which you should read from your provider rather than from any article.
Two levers matter more than the price. Content addressing collapses the evidence term, because the same policy document referenced by ten thousand runs is stored once and pointed at ten thousand times. And retention classes let a financial decision keep its seven years while routine telemetry expires in ninety days, instead of the whole store inheriting the longest obligation in the building.
Personal data against an immutable record
Append only storage and a legal duty to erase look like a contradiction, and they are not. Three techniques resolve it, and all three have to be in place before the first record is written.
- Per subject encryption keys. Encrypt subject-linked fields with a key held per person, and delete the key on an erasure request. The record and its hash chain survive, the personal data becomes unreadable, and the audit story stays intact. This is the approach that holds up when someone asks how you erased data from an immutable store.
- Structure before you store. You cannot redact what you did not structure. Personal data hides in free text, and the worst offender is the model narrative, so give it its own field inside the subject-keyed envelope rather than sprinkling names through prose.
- Pointers, not copies. Where a system of record already holds the personal data and honours deletion, store an id and a content hash instead of the bytes. Your audit trail proves what was read without becoming a second uncontrolled copy of it.
- Record what left your estate. Which provider processed the content, under which configuration, and what that configuration says about retention on their side. The question arrives in every security review and is unanswerable later if nobody wrote it down. Wider rules are in handling personal data in an AI pipeline.
Building it in the right order
This is cheap when built in sequence and expensive when retrofitted. The first step is the one that makes every later step possible.
- Emit and propagate one run id
Generated at the entry point and attached to every log line, tool call, queue message and retry across every service. Nothing else here works if you cannot join the pieces of a run afterwards.
- Pin at call time
Prompt version and rendered hash, requested and returned model ids, sampling parameters, tool versions and schema hashes. A few lines around the call site, and impossible to add later.
- Content address the evidence
Hash every retrieved document, store it once, reference it by hash with an as_of timestamp. This is also the change that stops storage growing linearly with traffic.
- Write decision records
One durable record per action with an effect, including args_source, the controls block and the external reference from the outcome. Separate store, separate retention, indexed on run id, subject id, action type and prompt version.
- Build the replay harness
Recorded tool responses, a command that takes a run id and re-executes against them, and a diff against the original outcome. Until this exists, every incident review is an argument between recollections.
- Add retention classes and subject keys
Classify records by obligation, encrypt subject-linked fields per person, then run an erasure drill on a test subject and confirm the record survives while the personal data does not.
- Rehearse an audit quarterly
Pick a random decision from three months ago and answer the seven questions in the table with a stopwatch running. Anything taking more than a few minutes is the gap, and a rehearsal is a cheaper place to find it than a dispute.
The vocabulary, used precisely
- Audit trail
- A durable, queryable record of what a system decided, what it saw when it decided, and which versions of prompt, model, tools and policy were in force. It is distinct from application logging, which records what your code chose to print.
- Pin
- A version identifier or content hash captured at execution time so a past run can be reconstructed exactly. Pins are cheap to write and impossible to add retrospectively, which is why they belong in the first commit rather than the first audit.
- Content addressing
- Referring to a document by the hash of its bytes rather than by a location such as a URL or a file path. It makes evidence tamper evident, deduplicates storage automatically, and proves which version an agent actually read.
- Deterministic replay
- Re-executing a recorded run against the tool responses captured at the time, so the outside world is held constant. It shows whether a change to prompts or code would have altered the decision, without depending on live systems.
- Argument provenance
- A per argument record of where each value in an action came from: a retrieved document, a policy clause, a human edit, or the model itself. It is the shortest path to answering whether a number was read or invented.
- Crypto-shredding
- Encrypting personal data under a key held per data subject and destroying the key to satisfy an erasure request. The surrounding record and its integrity chain remain intact while the personal data becomes permanently unreadable.
The tracing, the replay harness and the decision store are what we build in agent observability. The field level detail sits in what to log so future you can debug it, the failures these records exist to explain are in how agents fail, and the approval fields in the controls block come from human in the loop design.
ChatGPTalker, "Making an Agent's Decisions Auditable" (2026). A run is auditable when six things are pinned at decision time: prompt version, model identity, tool version, evidence by content hash, policy version and the clock.
Questions readers ask next
What is an AI agent audit trail?
Is the model's chain of reasoning enough to explain a decision?
How long should we keep agent decision records?
How do you delete personal data from an immutable audit log?
Can we reproduce an old agent run exactly?
ChatGPTalker. "Making an Agent's Decisions Auditable, Months Later." chatgptalker.com, 2026-08-26. https://chatgptalker.com/guides/auditable-agent-decisions/