A log is not evidence
When an agent does something it should not have, three people need the same answer: the engineer debugging it, the security team deciding whether it was an attack, and, increasingly, an auditor or regulator asking whether your controls were on. All three will be shown a log, and all three should ask the same question about it: how do I know nobody edited this?
For most AI observability tools the honest answer is that you trust the vendor and whoever has database access. That is fine for debugging. It is not fine for evidence, because the people with the strongest motive to change a record are the ones who can. An AI agent audit trail worth the name has to be checkable by someone who trusts neither.
What to record for each decision
Every allowed call, not only every blocked one. An audit that only shows refusals cannot show the call that should have been refused and was not. For each decision AgentFox records:
- The agent, the surface (prompt, tool arguments, tool result…) and the tool.
- The verdict, and the effective verdict after the policy's mode is applied.
- Every rule that fired, with its effect, its reason in plain language and the controls it maps to.
- The policy version in force, so the decision can be replayed against it.
- Where the inputs came from, and the latency of the check.
Here is one real entry from a demo run: a transfer above the agent's limit.
{
"surface": "tool_args",
"tool": "payments.transfer",
"verdict": "block",
"mode": "enforce",
"policy_version_id": "pvr_01m48yhvhzfh7n96nt",
"rules_fired": [{
"rule_id": "capability.constraint_violated",
"effect": "block",
"reason": "agent:payments-ops holds a grant for 'payments.transfer', so this is not a missing permission. The grant allows amount below 1000, but this call passed 25000.",
"controls": ["NOM-IAM-02"]
}],
"taint": "none",
"latency_ms": 4.6
}Sensitive values are redacted at capture by default: anything under a key like password, token or api_key is written as <redacted> before it reaches the chain, because an audit trail full of secrets is a liability of its own.
How the hash chain works
- Each entry's payload is serialised as canonical JSON (sorted keys, compact separators) and hashed with SHA-256.
- The entry's own digest is SHA-256 over its sequence number, timestamp, action, payload digest and the previous entry's digest. The first entry points at a genesis value of 64 zeros.
- There is no update or delete path for an audit entry in the code. Appending is the only way in.
- Every 100 entries, and on demand with
agentfox admin checkpoint, a checkpoint signs the current head digest with HMAC-SHA256 using a key that is not in the database.
The verifier walks the chain and reports four kinds of break: a gap in sequence numbers (a deletion), a payload or entry digest that does not match its contents (an edit), a previous-digest that does not match (an insertion or reordering), and a checkpoint whose signature or anchored digest is wrong (rewritten history).
Watch it catch an edit
We ran the offline demo, which writes fifteen entries, and verified the chain:
chain: 15 entries, head seq 15, 1 checkpoints
CHAIN INTACT — 15 entries verified (seq 1..15)Then we did what an insider covering for an incident would do: opened the database and changed entry 8, the blocked transfer above, so that it read allow.
chain: 15 entries, head seq 15, 1 checkpoints
CHAIN TAMPERED — 2 break(s)
seq 8 payload_mismatch: payload does not match its recorded digest
seq 8 digest_mismatch: entry digest does not match its contentsThe command exits 1, so the same check can run on a schedule and page someone. The walkthrough, including how to try it on your own data, is in Prove it to an auditor.
The evidence package
An auditor should not need an account on your control plane. The evidence package is a zip they can take away:
agentfox report evidence --agent payments-ops --from 2026-08-01 --to 2026-08-31 \
--requested-by auditor@example.com
unzip <id>.zip && python3 verify_chain.pyIt contains, among other files:
audit_entries.jsonandaudit_checkpoints.json, the chain itself.decisions.jsonandpolicy_versions.json, so every verdict can be traced to the exact policy text that produced it.- Agents, approvals, findings, evaluation runs and control status for the period.
verify_chain.py, which uses only the Python standard library and exits 0 onCHAIN INTACTand 1 onCHAIN INVALID. SetAGENTFOX_AUDIT_KEYand it checks the checkpoint signatures too.manifest.jsonwith a SHA-256 for every file, and a human-readable summary.
A package scoped to one agent still ships every audit entry in the period, so the chain stays verifiable end to end. Entries about other agents are included with their payload withheld: the digests still prove the links, without disclosing what those agents did. Exporting a package is itself written to the chain.
Where the EU AI Act comes in
The EU AI Act asks high-risk systems to log events automatically over their lifetime (Article 12, record-keeping) and to keep those logs (Article 19). Our audit controls are mapped to those articles: recording the full execution path, keeping the log tamper-evident, and producing exportable, verifiable evidence. The same controls are mapped across NIST AI RMF, ISO 42001, SOC 2, the OWASP lists for LLMs and agents, and MITRE ATLAS, 43 controls in all, on the compliance page.
Two things set this apart from a questionnaire. Control status is computed from what the runtime actually decided over a period, not attested. And every mapping ships labelled DRAFT, UNVERIFIED, NOT LEGAL ADVICE until someone signs it off, because engineers wrote them and counsel has not reviewed them. A sign-off is recorded in the chain like everything else.

What the chain does not prove
- That a record was true when written. Only that it has not changed since.
- Anything, if the key leaks. Someone who can rewrite the whole export can recompute every digest and the manifest. Only the checkpoints resist that, and only while the key stays secret and outside the database.
- Who handed over the key. Checkpoints use HMAC, a shared secret, so the auditor has to trust whoever gave them the key. There is no public-key signing, external timestamping or key rotation yet.
- Every field. The entry digest covers the sequence, time, action and payload, not the actor and subject columns.
- Withheld payloads. In a scoped package their links are proven, their contents are not checkable.
- Production without a real key. The development default key is forgeable. Outside development the gateway refuses to start with it and
agentfox doctorreports it.
We would rather you read these here than discover them in an audit. The rest of what nobody outside this project has checked is on the security page.
Frequently asked questions
What should an AI agent audit trail record?
Every decision, allowed as well as blocked: which agent, which tool and arguments, the verdict, every rule that fired and why, the policy version in force, where the inputs came from, and who approved anything that needed a person. Recording only the blocks hides the calls that should have been blocked and were not.
What is a tamper-evident log?
A log where each entry includes a hash of the entry before it, so changing, deleting or reordering any record breaks every hash after it. It does not stop someone editing the log; it makes the edit detectable by anyone who re-runs the verification.
Can an auditor verify the AgentFox audit trail without AgentFox?
Yes. Every evidence package contains verify_chain.py, a script that uses only the Python standard library. It re-computes every digest from the exported entries and exits 0 if the chain is intact and 1 if it is not.
Does the EU AI Act require AI audit logs?
Article 12 of the EU AI Act requires high-risk AI systems to allow automatic recording of events over their lifetime, and Article 19 covers keeping those automatically generated logs. AgentFox maps its audit controls to those articles, but the mappings are drafts written by engineers, not reviewed by counsel, and are not legal advice.
Is a hash-chained log enough on its own?
No. Someone who controls the whole export can recompute every hash. Signed checkpoints held with a key outside the database close that gap, and they only help if the key stays secret. The chain also proves a record was not changed after it was written, not that it was true when written.



