An audit trail for AI agents is an append-only record of every consequential action the system took and every human decision that authorized one: who asked, what exactly, who decided, what the machine did, and when each of those happened. It is written as events occur, by the code that causes them, and nothing edits it afterwards.
The distinction that matters most is not technical. It is about the intended reader. Logs are written for whoever is debugging right now. An audit trail is written for someone who was not there — a colleague three months later, a customer asking what happened to their data, an auditor, or you, at two in the morning, trying to establish whether an agent did something it was told to do or something it invented.
Agent systems raise the stakes because the actor is not a person and cannot be asked. A human who did something surprising can explain their reasoning. An agent’s account of its own reasoning is not something anyone should rely on, so the record of what it was asked, what it was allowed, and what it did is the only durable account of intent.
Logs are not a trail
Four properties separate the two, and dropping any one of them turns a trail back into logs with good intentions.
Fixed shape. The set of recorded event types is decided deliberately and changes rarely. Log lines evolve with whoever last touched the code; an audit event that changes shape every release cannot be queried across a year.
Append-only. No update path, no delete path, no administrative tidy-up. This is a property of the code, not of a policy document. If a function exists that edits an audit row, the trail is exactly as trustworthy as everyone with access to that function.
Written with the action. The record and the state change commit in one transaction. A trail written afterwards by a separate process records what a healthy system did and goes quiet in precisely the failures worth investigating.
Retained on purpose. Someone decided how long, and why. NIST’s guide to log management makes the same point about ordinary logs: retention that emerges from disk pressure is not a policy.
Regulation is arriving at the same shape from outside. The EU AI Act’s Article 12 requires high-risk systems to log events automatically over their lifetime, and the AU control family in NIST SP 800-53 has treated non-repudiation and record protection as separate controls for years. Neither will tell you what your agents’ events are. Both assume you can produce them on request.
What Klingbar records
In Klingbar a crew of personas runs on a box you control and reports in Slack, and klingbar.com keeps the record of what the crew did. Concretely, for each crew it keeps:
- Command transitions. Every command klingbar.com sends a box — one of a closed list of 17 verbs — moves through queued and delivered to a final state, and every transition writes an append-only row to
crew_auditin the same transaction as the change. Each row names the actor — a person, the box, or the system — the action, and its subject. - Runs. Each run’s persona, task, and the verdict it ended with; which seat it used, primary or standby; and what it spent as the engine reported it — tokens, turns, and the engine’s own cost estimate.
- Transcripts. Each attempt’s prompt and output, tail-capped at 256 KiB per stream, with a flag when the cap trimmed anything.
- Cards. Every proposed change: who decided it, when, and where — a Slack button, a reaction or a reply, the Home tab, or the web — and the run that carried out an approved one.
- Questions and answers. Who answered, with which option or text, and where.
- Learnings. Which run proposed each one, who approved or rejected it, and when.
- Versions. Every save of the configuration, the standing rules, and each prompt, with its author and time. For configuration, the box’s verdict: applied, or rejected with the error its own loader gave. For standing rules, when the box applied them.
Anything the box did not say renders “not reported”. A run whose engine gave no cost has no cost, not a cost of zero; a reported zero stays a zero.
The four questions a row answers
Who asked. The person, the box, or the background sweep that initiated the action — not the service identity that carried it out. “The system” is only an acceptable answer when the system really did it.
What exactly. The verb and its parameters, at the granularity the machine acted on. “Updated configuration” is not an audit record; “saved version 14” is, because version 14’s exact bytes are still there to read.
Who decided. Present, or explicitly absent. “No decision was required” and “the decision was not recorded” look identical in a trail that leaves the field blank.
What the machine did. The outcome, including the boring ones: refused, expired, delivered but never acknowledged. A trail that records only successes is a marketing artefact.
Record the non-events
The events that matter most in an investigation are often the ones where nothing happened.
Klingbar’s sweep writes rows for the transitions nobody asked for, and they carry the actor system so a later reader can tell them apart. A command nobody picked up within its window — 24 hours by default — is expired, and the expiry is a row. A command delivered to a box that then goes silent is redelivered once and, if it stays silent, failed; both are rows. A box that goes five minutes without a sync is marked stale, and that is a row too. A move to another box that supersedes a stale retirement writes a row naming the move, and a retirement the box already holds — which klingbar.com cannot recall — gets a row saying so. A second import that tries to seed a configuration klingbar.com already holds is refused, one row per refusal.
This is the difference between a trail that answers “what happened” and one that only answers “what succeeded”. The second kind produces the worst outcome an investigation can have: a gap everyone fills with their own theory.
Tie the decision to the thing decided
An approval row that says “Dana approved” is nearly worthless, because it does not say what Dana approved.
In Klingbar a card is minted by the box, not by the web, from a proposal the persona wrote: an imperative title, a short detail, and the exact payload — the command, the pull request link, or the recipient and the text. The card keeps the box’s own id, and a decision travels back to it by that id. Approve resumes the persona with the instruction to make exactly that change and nothing more, and the card’s record names the run that did it. So the trail reads “this person approved this card, whose text said this, and this run carried it out”, rather than “someone clicked yes”.
The same tap arriving twice — once in the channel and once on the Home tab — is recorded once. A member’s decision counts only if their Slack identity is linked and on the crew’s approver list, which the box checks itself. The crew owner may decide from the web without a linked Slack identity, and that decision is recorded under the owner’s web name, so the trail shows it came from the web. The approval flow itself is covered in human approval workflows, and who may approve is set in the crew’s configuration, covered in org chart as code.
Two clocks
Every record in a distributed agent system has at least two timestamps: when the event happened on the machine, and when the control plane learned about it. They can differ by seconds or by a day, and in the incidents worth reconstructing they will.
Klingbar keeps both. Each event carries the box’s own time and the time klingbar.com received it. A box syncs every 30 seconds by default, so the gap is usually small — but a box that lost its connection queues events on disk and ships them on reconnect, and then the gap between the clocks is the story. A trail with one timestamp forces a later reader to guess which clock they are looking at, and the guess will be wrong exactly when it matters.
Keep less, on purpose
The instinct to record everything is the enemy of a trail anyone will keep for years.
Decision records are small, uniform, and worth retaining. Content is neither, which is why Klingbar caps transcripts at a fixed size and marks the ones it trimmed rather than storing whatever an engine printed.
Values are banned outright. The configuration names a secret and never holds its value. A secret travels as ciphertext sealed in your browser to the box’s own key, and klingbar.com clears the envelope once the box confirms it arrived. The pages show that a secret is present, never its value or its length. A credential that reaches a durable store has already failed; the mechanics are in sealing agent credentials.
Applying one retention policy to decisions and content fails in both directions at once — deleting decisions too early and keeping payloads far too long. Splitting them is a modest amount of work and the difference between a trail you can defend and one you are asked to stop keeping.
Reading one after an incident
A good trail supports one motion: start at the surprising outcome, walk backwards, and stop at the first row where a person is named.
Start at the effect — a post, a commit, a message. Open the run that produced it: its verdict, its transcript, what it spent. If it carried out a card, open the card: its exact text, who decided, from which surface, and when. Then open the configuration and standing rules history: which version the box had applied at that point, and what changed in it. Then read the command rows between the decision and the effect, including the delays and the redeliveries. The state of the whole system at any moment is also visible on the control plane.
If that walk produces a coherent story every time — including for commands that expired, failed, or were refused — the trail is doing its job. If any step requires correlating three systems by timestamp, it is not a trail yet. It is source material for one.