How to build a verifiable AI agent audit trail
If an AI agent takes real actions on your behalf, someone will eventually ask you to prove what it did. This guide covers how to build an audit trail that answers that question in a way a regulator or auditor can verify without trusting you.
Why ordinary logs are not enough
Most agent frameworks already log activity. The problem is where that log lives: in the operator's own infrastructure, editable and deletable by the operator. An auditor reviewing it has to trust the exact party whose work they are auditing. For software that moves money, reads records, or calls tools on your behalf, that is the wrong trust model.
The EU AI Act (Regulation (EU) 2024/1689), Article 12, makes this concrete for high-risk systems: they must keep automatically generated logs that enable post-hoc verification of how the system operated. A log the operator can silently rewrite does not clear that bar.
What "verifiable" actually requires
A verifiable audit trail has three properties:
- Tamper-evident: altering or deleting a record is detectable after the fact.
- Independently verifiable: a third party can confirm the record with no access to the operator's systems and no trust in the operator.
- Bound to authority: each action is tied to what the agent was allowed to do, so out-of-scope actions stand out.
Signatures give you the first two. A signed record cannot be altered without invalidating the signature, and anyone holding the public key can check it offline. Hash-chaining the records makes deletion show up as a gap.
The bilateral receipt pattern
The pattern that satisfies all three properties is a pair of signed receipts per action. It is normative guidance in the OWASP Agentic Skills Top 10 (AST09).
- Admission receipt (before the action): who is acting, what they intend to do, the authorized scope, and the policy version in effect.
- Outcome receipt (after the action): what actually happened, linked to the admission by a content-derived hash called
action_ref.
Both records are canonicalized with JCS (RFC 8785) so any implementation serializes them to the same bytes, then signed with Ed25519. The action_ref is derived from the content of the action itself (agent id, action type, scope, timestamp), so it is recomputable by anyone and cannot be faked by the operator.
Step by step
1. Instrument the action
Wrap the function the agent calls so a receipt is emitted around it. With Nobulex that is one decorator:
from nobulex import track
@track(agent_id="billing-bot")
def charge(invoice_id: str, amount_cents: int):
...
2. Emit admission, then outcome
Before execution, the admission receipt records intent and authority. After execution, the outcome receipt records the result and links back by action_ref. A denied action still produces a signed admission record, so "the agent was stopped" is auditable too, not a silent hole.
3. Verify offline
Verification recomputes action_ref from the record and checks the Ed25519 signature against the agent's published public key. Nothing else is needed: no call to the operator, the platform, or Nobulex.
from nobulex.agent import Agent
agent = Agent("billing-bot")
receipt = agent.act("charge", scope="invoice:042")
assert receipt.verify() # True: recomputes action_ref, checks the Ed25519 signature, offline
What this catches
- Deletion breaks the hash chain, so removed records leave a detectable gap.
- Out-of-scope actions make the admission and outcome hashes diverge.
- Policy changes between admission and execution are recorded, so "the policy was different then" is answerable with evidence.
Do you need a blockchain?
No. A signed, content-addressed receipt is verifiable offline against a public key, which gives you non-repudiation without a settlement layer. For long-term anchoring you can optionally batch receipts into an RFC 6962 Merkle tree, which keeps anchoring cost negligible, but the core verification needs no chain and no gas.
Getting started
Nobulex is the open-source (MIT) reference implementation of the bilateral receipt pattern.
pip install nobulex
# or
npm install @nobulex/core
There is a live tamper demo and a ten-line verify in the repository. For the regulatory mapping, see the EU AI Act compliance guide, and for common questions, the FAQ.
