How to produce tamper-proof evidence of AI agent actions for customers and auditors

8 min read

To give a customer or an auditor evidence of what your AI agent did, record each action as an ordered event at the moment your runtime dispatches it, commit the events to a Merkle tree, sign the root with a key you publish, and hand over the record together with a verifier that runs without your servers. The auditor checks it on their own machine. Change one byte after signing and the check fails.

This guide uses “tamper-proof” because it is the search term. The property you get is tamper-evidence: anyone can edit the bytes, and an edit after signing makes verification fail. For evidence that is the property you need, because it moves the question from “do we trust this company’s logs?” to “does this record verify?”

Application logs are your word, written down

Your agent’s logs are written by the system they describe and stored where your own engineers can edit them. An auditor reading them is trusting you. Shipping them to a SIEM moves that trust to whoever runs the SIEM.

A hash-chained log, where each line carries the hash of the one before, makes an edit in the middle visible. Anyone who can rewrite the log can also recompute the chain, though, and it still says nothing about who allowed each action. That is fine for debugging. It stops being fine the first time a customer disputes what your agent did to their account and the only record is one you could have rewritten.

Five properties turn a record into evidence

  1. The runtime writes it, at dispatch. An agent’s own account of its actions is a claim. The component that sends the tool call is a witness. Emit the event there, in the same code path that executes the action.
  2. Events are ordered and append-only. Each event carries a sequence number that goes up by exactly one. A reordered event or a gap in the middle is detectable.
  3. The events are committed and signed. Each event is a leaf in a Merkle tree built the way Certificate Transparency builds its logs (RFC 9162). The root is signed with Ed25519 inside a COSE_Sign1 envelope. Change any event and the recomputed root no longer matches the signed one.
  4. Authorization is recorded next to every action, and so are refusals. In the Provetrail conformance vectors, and in Flynn, an action’s lifecycle is dispatch.start (admitted), dispatch.end (completed) and dispatch.rejected (refused). A verifier that checks the governance level (L4) rejects any completed action with no matching admission earlier in the record (gov.unadmitted_action), and any action that was both refused and completed (gov.admission_denied_but_executed).
  5. Claimed results point at checks. An outcome.recorded event carries the id of the check.recorded event that backs it, so “the refund was issued” has to cite the check that confirmed it.

The first three make the record tamper-evident. The last two make it say something an auditor cares about: what was allowed, what was blocked, and what succeeded.

Producing the record, step by step

  1. Find the dispatch point. Most agent frameworks have one place where a model’s tool call is turned into a function call that runs. That is where events are emitted. A wrapper that logs around the agent can be bypassed; the dispatcher cannot be bypassed without skipping the action itself.
  2. Create a signing key and publish the public half. Give it a stable key id and put the public key somewhere the auditor already trusts: your website, your repository, a well-known URL. A key id derived from the public key, as Flynn’s is, proves that one key signed every record; publishing it is what ties the key to your company. Keep the private key where the agent and its tools cannot read it.
  3. Emit an event for every admission, completion and refusal. Pair them with a correlation id per call, so a verifier can match each completion to the admission before it. What decides admission is covered in pre-execution validation for AI agent tool calls.
  4. Seal the run. Compute the Merkle root over the events and sign a checkpoint containing it: every few events during a long run, so a crash loses little, and once more when the run ends.
  5. Hand over three things. The record file, the key id, and a pointer to a verifier. The provetrail npm package checks integrity: the signature and the Merkle root. The governance and outcome checks (properties 4 and 5 above) need an L4 verifier such as flynn spine verify. Any verifier that passes the public conformance vectors at a level agrees with the others at that level.

The home page shows the auditor’s side of the integrity check: a signed record, verified in the browser, with a button that flips one byte so you can watch verification fail.

Tell the auditor what the record does not prove

A signed record proves it has not changed since it was signed, by whom it was signed, and, with the governance checks, that no action completed without an admission. On its own it does not prove three things.

  • Completeness. The record shows what the emitter recorded and signed. It cannot show that nothing was left out.
  • An honest emitter. If the binary that writes and signs records is modified, it can sign a consistent record of work that never happened.
  • When. Order is fixed by sequence number. Timestamps come from the emitter’s own clock, so they are only as good as the emitter, unless the signed root is also registered with an outside log.

Emitting from the dispatcher narrows the first gap: an action that skipped the record also skipped the code that performs actions. Publishing each run’s signed root to an append-only transparency log as the run closes narrows the third, and stops a later swap: you cannot replace the whole record with a cleaner one, because the original root, and when it was logged, are already public.

Write these limits into the evidence package, next to the guarantees. Signed vs proven covers them in detail.

State the format’s maturity too. The Provetrail specification is a draft (0.1.0-draft): its wire format is not frozen, and the spec says not to rely on it as a production security control until v0.1.0 is tagged. Today it gives a customer a record they can check for themselves; it is not yet a control an auditor can certify against.

What the EU AI Act asks for

Article 12 of the EU AI Act requires high-risk AI systems to allow events to be recorded automatically over their lifetime. It does not require the logs to be signed. Under the Digital Omnibus on AI (Regulation (EU) 2026/1744, in force since 27 July 2026), that requirement applies to the high-risk uses listed in Annex III from 2 December 2027, and to high-risk AI built into products covered by EU product-safety law from 2 August 2028. Many agent deployments fall outside the Act’s high-risk categories. For those inside them, a signed record is one way to make the logs checkable by someone other than the provider.

Getting it without building it

A customer letting your agent near their accounts can ask two things: what it is allowed to do, and how you would show what it did. A record they verify themselves answers the second.

Flynn is the open-source reference implementation of Provetrail: a single Go binary that checks every governed action at dispatch and writes the record as it runs, sealing checkpoints as it goes. flynn spine verify checks all four conformance levels (integrity at L1 to L3, governance and ground truth at L4), and flynn spine export writes the portable record file you hand over.

If your agents already run on another stack, the same records can be emitted from its dispatcher. The Flynn team takes on that work.