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:

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).

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

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.

Get started Star