An audit trail for AI agents is a tamper-evident log of every action an autonomous agent takes -every model call, tool invocation, and data access -cryptographically chained so that modifying any historical record is immediately detectable. If you deploy AI systems in the EU, Article 12 of the EU AI Act requires exactly this: automatic recording of events that enables traceability throughout the system's lifecycle.
This guide walks through building an HMAC-SHA256 audit chain in Python from scratch, then shows how to add one to any AI agent in a single line of code using air-trust.
Most teams use Python's built-in logging module or ship events to a SIEM like Datadog or Splunk. That covers observability. It does not cover compliance.
The problem is integrity. A standard log file can be edited, truncated, or deleted without any record of the change. When a regulator asks you to prove that your AI agent made specific decisions on a specific date, you need more than logger.info(). You need a chain of records where each entry is cryptographically linked to the one before it, making any retroactive modification mathematically detectable.
This is what HMAC-SHA256 audit chains provide. The same concept behind blockchain and Git, applied to AI agent record-keeping.
The core idea is simple. Each record in the chain is signed with a formula:
signature = HMAC-SHA256(secret_key, previous_hash + current_record)
Because each signature includes the previous record's hash, modifying any entry in the chain invalidates every signature that follows it. An auditor (or automated tool) can walk the chain and verify that no records have been altered.
Here's what that looks like in Python:
import hashlib, hmac, json, time
class AuditChain:
def __init__(self, signing_key: str):
self.key = signing_key.encode()
self.prev_hash = b"genesis"
self.records = []
def append(self, event: dict) -> dict:
# Add timestamp
event["timestamp"] = time.time()
# Compute HMAC: previous hash + current record
payload = self.prev_hash + json.dumps(
event, sort_keys=True
).encode()
signature = hmac.new(
self.key, payload, hashlib.sha256
).hexdigest()
# Chain it
record = {
"event": event,
"signature": signature,
"prev_hash": self.prev_hash.hex()
if isinstance(self.prev_hash, bytes)
else self.prev_hash,
}
self.prev_hash = signature.encode()
self.records.append(record)
return record
That's the entire tamper-detection mechanism. Around 25 lines of Python. The complexity isn't in the cryptography - it's in making this work reliably across agent frameworks, persisting chains to durable storage, and handling concurrent writes from multi-agent systems.
The EU AI Act Article 12 doesn't prescribe a specific format, but it requires that high-risk AI systems have logging capabilities that enable:
For AI agents specifically, that translates to logging: every LLM call (model, prompt hash, response hash, token count), every tool invocation (tool name, arguments, result), every decision point (which branch the agent took and why), and which human or system initiated the agent run.
An in-memory chain is useless if the process crashes. You need durable storage. SQLite is the simplest option - single file, zero configuration, included in Python's standard library:
import sqlite3
def init_db(db_path: str):
conn = sqlite3.connect(db_path)
conn.execute("""
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
run_id TEXT,
timestamp REAL,
event_type TEXT,
framework TEXT,
payload TEXT,
signature TEXT,
prev_hash TEXT
)
""")
conn.commit()
return conn
Every event gets its own row. The signature and prev_hash columns form the chain. You can query by run_id to reconstruct a single agent run, or walk the entire table to verify chain integrity.
The HMAC signing key is the root of trust. If someone has the key, they could theoretically rewrite the entire chain. Two approaches:
Auto-generated key (development/single-machine): Generate a random key on first run and store it at a known path with restricted permissions. This is what air-trust does by default - it creates ~/.air-trust/signing.key with chmod 600.
Environment variable (production/CI): Set AIR_TRUST_KEY as an environment variable or inject it from your secrets manager. This lets you use the same key across multiple services while keeping it out of your codebase.
import os, hashlib
from pathlib import Path
def get_signing_key() -> str:
# 1. Check environment
key = os.environ.get("AIR_TRUST_KEY")
if key:
return key
# 2. Check local file
key_path = Path.home() / ".air-trust" / "signing.key"
if key_path.exists():
return key_path.read_text().strip()
# 3. Generate new key
key = hashlib.sha256(os.urandom(32)).hexdigest()
key_path.parent.mkdir(parents=True, exist_ok=True)
key_path.write_text(key)
os.chmod(key_path, 0o600)
return key
An audit chain you can't verify is just a fancy log. Verification walks every record and recomputes the HMAC. If any computed signature doesn't match the stored one, the chain is broken at that record:
def verify_chain(records: list, key: bytes) -> tuple[bool, int]:
"""Verify chain integrity. Returns (valid, break_index)."""
prev_hash = b"genesis"
for i, record in enumerate(records):
payload = prev_hash + json.dumps(
record["event"], sort_keys=True
).encode()
expected = hmac.new(
key, payload, hashlib.sha256
).hexdigest()
if expected != record["signature"]:
return False, i # Chain broken here
prev_hash = record["signature"].encode()
return True, -1 # All records verified
Run this as part of CI/CD to catch issues early. Run it before any regulatory audit to confirm chain integrity. Run it after any incident to prove records haven't been altered.
Building all of this from scratch is educational. Maintaining it across five agent frameworks is not. That's why I built air-trust - a single package that handles the chain, storage, key management, framework adapters, and verification CLI.
pip install air-trust
Then wrap any AI client or agent:
import air_trust
from openai import OpenAI
client = OpenAI()
client = air_trust.trust(client) # That's it
# Every call is now audit-logged with HMAC-SHA256
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "Summarize this report"}]
)
That single trust() call adds: HMAC-SHA256 signed audit chain stored in SQLite, automatic framework detection (OpenAI, LangChain, CrewAI, AutoGen, Haystack), PII scanning on inputs and outputs, prompt injection detection, and agent identity binding per Article 14.
LangChain:
import air_trust
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4")
llm = air_trust.trust(llm)
# All LangChain calls now generate audit records
result = llm.invoke("What are the compliance requirements?")
CrewAI:
import air_trust
from crewai import Agent, Task, Crew
# Wrap the entire Crew
crew = Crew(agents=[...], tasks=[...])
crew = air_trust.trust(crew)
# Every agent action is audit-logged
result = crew.kickoff()
Session-based logging for custom pipelines:
import air_trust
with air_trust.session("data-pipeline") as s:
data = load_training_data()
s.log("data_loaded", rows=len(data))
model = train_model(data)
s.log("model_trained", metrics={"accuracy": 0.94})
s.log("pipeline_complete", risk_level="low")
air-trust ships with a CLI for chain verification, statistics, and export:
# Verify chain integrity
$ python -m air_trust verify
Chain integrity: VALID
Total records: 847
Chain spans: 2026-03-15 to 2026-04-08
# Get chain statistics
$ python -m air_trust stats
Total events: 847
Frameworks: openai, langchain, crewai
Agents: 3
Date range: 2026-03-15 to 2026-04-08
# Export for auditors (JSON or CSV)
$ python -m air_trust export --format json > audit_export.json
$ python -m air_trust export --format csv > audit_export.csv
The verify command walks every record in the SQLite database, recomputes HMAC signatures, and reports any breaks. The export command produces auditor-ready output that maps directly to Article 12 requirements.
Audit chains should be verified on every deployment, not just before audits. If you use GitHub Actions, you can add compliance scanning (including audit trail verification) with a single step:
# .github/workflows/compliance.yml
name: EU AI Act Compliance
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: airblackbox/compliance-action@v1
with:
path: .
strict: true
This scans your codebase for all six EU AI Act technical requirements and reports results directly in your pull request.
For teams running agents in production, here's a recommended architecture:
| Layer | Purpose | Tool |
|---|---|---|
| Agent Framework | Runs agent logic | LangChain, CrewAI, AutoGen, etc. |
| Trust Layer | Intercepts calls, signs records | air-trust |
| Local Storage | SQLite audit chain | ~/.air-trust/events.db |
| SIEM Export | Forward to observability stack | JSON export to Datadog, Splunk, etc. |
| Compliance Scanner | Verify chain + check all 6 articles | air-compliance |
| CI/CD Gate | Block deploys that break compliance | GitHub Action |
The trust layer sits between your agent framework and the LLM provider. It doesn't modify the agent's behavior - it observes and records. Your existing SIEM handles alerting and dashboards. The audit chain handles regulatory proof.
Logging outputs but not inputs. Article 12 requires traceability of system functioning. If you only log what the model returned, you can't reconstruct why it returned that. Log the prompt (or a hash of it), the model version, and the configuration parameters.
Using regular hashes instead of HMAC. A SHA-256 hash of a record proves the record hasn't changed, but anyone who knows the hash algorithm can recompute a valid hash for a modified record. HMAC requires knowledge of the secret key, so only the original system can produce valid signatures.
Not verifying the chain regularly. A chain that hasn't been verified since it was created provides weaker evidence than one verified hourly in CI. Regular verification creates a paper trail showing the chain was intact at specific points in time.
Storing the signing key in the codebase. If the key is committed to Git, anyone with repo access can forge records. Use environment variables or a secrets manager.
An audit trail for AI agents is a tamper-evident log of every action an autonomous AI agent takes. It uses cryptographic signatures (HMAC-SHA256) to chain records together so that any modification to historical entries is mathematically detectable. This satisfies the EU AI Act Article 12 requirement for automatic recording of events that enables traceability.
Yes. Article 12 requires high-risk AI systems to have automatic recording of events (logging) that enables traceability of the system's functioning. The enforcement deadline is August 2, 2026. Standard application logs do not meet this requirement because they lack tamper-evidence.
Install air-trust (pip install air-trust) and wrap your client or agent with air_trust.trust(). This adds HMAC-SHA256 audit logging automatically. It works with LangChain, CrewAI, AutoGen, OpenAI SDK, and Haystack with zero code changes to your existing agent logic.
Minimal. HMAC-SHA256 computation takes microseconds. SQLite writes take single-digit milliseconds. The bottleneck in any AI agent pipeline is the LLM API call (typically 500ms-3s), not the audit logging. air-trust adds less than 5ms of overhead per call.
Yes. air-trust includes a CLI export command: python -m air_trust export --format json produces auditor-ready JSON. CSV export is also available. The output includes timestamps, event types, framework identifiers, and chain signatures that an auditor can independently verify.
Add audit trails to your AI agents in 10 seconds:
pip install air-trust