Skip to content
Audit Trail

AI Agent Audit Trail: What to Record and How to Prove It

An audit trail is evidence, not a debug log. Record who acted, what the policy decided and why, who approved, and what ran, in a form that shows whether anyone changed it later.

Updated Oct 202614 min readAudit Trail, Compliance, SIEM

Definition

An AI agent audit trail is an append-only, per-action record of what an agent tried to do, which identity it acted for, what the policy decided and why, who approved it, and what happened, stored so that edits and deletions are detectable. See also the glossary entry.

Key takeaways

  • Record the decision, not just the action: actor, agent, tool, redacted arguments, matched rule, policy version, approval and result.
  • Write the record at the enforcement point. Provider logs and app logs never see which rule blocked a call or who approved it.
  • Make it tamper-evident with a hash chain plus a keyed HMAC, and verify the chain on a schedule, not only when an auditor asks.
  • Plan retention beyond the product: stream events to your SIEM or archive exports before an obligation outlasts your tier.
  • A log supports an audit. It does not certify anything; map events to controls with your auditor.

Why agent logs are not an audit trail

Most agent stacks already produce logs: model-provider request logs, framework traces, application output. They help engineers debug. They rarely answer the questions an auditor asks: who was this action for, which rule allowed it, who approved it, and how do you know nothing was removed?

Provider logs sit on the wrong side of the decision. Amazon Bedrock model invocation logging, for example, records requests, responses and the calling identity, and it is disabled by default. It cannot show that your policy blocked a git push or that a reviewer approved a payment, because it never saw either decision.

Write the audit record where the decision happens. In Cordum that is the enforcement point in front of the action: the Edge hook for coding agents such as Claude Code, the MCP server for tool calls, and the policy check before a platform job is dispatched. All three send events to the same per-tenant, hash-chained audit stream.

What to record for every agent action

One record per governed action. If a field is missing, an audit question has no answer. The right-hand column shows where Cordum puts each field in its audit events.

FieldWhy auditors need itIn Cordum
Actor identityWho the agent acted for. Separation of duties starts here.identity holds the principal or a stable API-key ID, never the raw key. Gateway events add extra.identity_source and extra.identity_label.
Agent identityWhich agent or runtime made the call.agent_id, agent_name, agent_risk_tier. Edge takes the principal from the authenticated connection, not from labels the agent sends.
Tenant, time, orderScopes the evidence and orders it.tenant_id, timestamp and a per-tenant seq.
Action and targetWhat was attempted, in terms a reviewer can query.action, capabilities, risk_tags. Edge adds classifier labels such as extra.command_family and extra.target_summary instead of the raw command.
Arguments, redactedShows what was asked without copying secrets into the log.MCP: extra.args_redacted with [REDACTED:<rule>] markers. Edge: extra.input_hash and extra.redaction_status.
Decision, rule, reasonWhy the action was allowed, blocked or held.decision, matched_rule, reason.
Policy versionProves which rules were live at decision time.policy_version on gateway events, extra.policy_snapshot on Edge events.
ApprovalWho approved, when, and for which exact request.safety.approval, edge.approval_resolved and mcp.tool_approval events carry the approver identity and the approval reference.
ResultWhat happened, provable without logging the payload.MCP: extra.result_type and extra.result_hash. Edge: extra.artifact_id, a SHA-256 pointer to the redacted evidence bundle.
Chain fieldsMakes edits, deletions and reordering detectable.seq, prev_hash, event_hash, plus hmac on exported events.

Two rules keep the trail usable. Record identities as stable IDs, not display names, and keep any human-readable label in a separate field. And never put raw secrets in the record: store redacted arguments or hashes, and keep raw payloads, if you keep them at all, in an access-controlled store with its own retention.

Example: the trail for an approval-gated git push

Here is one Claude Code session under Cordum Edge. The rule comes from the Edge pack in the Cordum repository:

policy.fragment.yaml
yaml
# examples/cordum-edge-pack/overlays/policy.fragment.yaml (excerpt)
rules:
  - id: claude-code.require-approval-for-vcs-push
    match:
      topics:
        - job.edge.action
      capabilities:
        - exec.shell
      risk_tags:
        - git
      labels:
        "command.family": git_push
    decision: require_approval
    reason: "Publishing repository state with git push requires approval."

The agent runs git push. Edge holds it, a reviewer approves it in Cordum, and the agent retries the same command. Pull the governance events; the API returns newest first, so reverse puts them in order:

query-audit-events.sh
bash
export CORDUM_GATEWAY=https://localhost:8081
export CORDUM_TLS_CA=./certs/ca/ca.crt
export CORDUM_API_KEY="replace-with-your-api-key"
export CORDUM_TENANT_ID=default

curl -s --cacert "$CORDUM_TLS_CA" \
  -H "X-API-Key: $CORDUM_API_KEY" \
  -H "X-Tenant-ID: $CORDUM_TENANT_ID" \
  "$CORDUM_GATEWAY/api/v1/audit/events?category=governance&limit=3" \
  | jq '.items | reverse'
response (trimmed, hashes shortened)
json
[
  {
    "seq": 9141,
    "timestamp": "2026-10-20T14:05:31Z",
    "event_type": "edge.approval_requested",
    "severity": "MEDIUM",
    "action": "bash.exec",
    "decision": "require_approval",
    "matched_rule": "claude-code.require-approval-for-vcs-push",
    "risk_tags": ["deploy", "git", "network"],
    "capabilities": ["exec.shell"],
    "identity": "dev-alice",
    "extra": {
      "tool_name": "Bash",
      "command_family": "git_push",
      "target_summary": "shell:deploy/git_push",
      "input_hash": "sha256:9c0f…",
      "policy_snapshot": "edge-policy-demo-v0:5be1…",
      "approval_ref": "a41d…",
      "redaction_status": "applied"
    },
    "prev_hash": "77e2…",
    "event_hash": "c3a9…"
  },
  {
    "seq": 9142,
    "timestamp": "2026-10-20T14:07:52Z",
    "event_type": "edge.approval_resolved",
    "severity": "INFO",
    "action": "edge_approval_resolved",
    "decision": "approved",
    "matched_rule": "claude-code.require-approval-for-vcs-push",
    "identity": "sec-lead-bob",
    "extra": {
      "approval_ref": "a41d…",
      "action_hash": "sha256:1e88…",
      "input_hash": "sha256:9c0f…",
      "policy_snapshot": "edge-policy-demo-v0:5be1…"
    },
    "prev_hash": "c3a9…",
    "event_hash": "08f4…"
  },
  {
    "seq": 9143,
    "timestamp": "2026-10-20T14:08:05Z",
    "event_type": "edge.policy_decision",
    "severity": "INFO",
    "action": "bash.exec",
    "decision": "allow",
    "identity": "dev-alice",
    "extra": {
      "command_family": "git_push",
      "input_hash": "sha256:9c0f…",
      "approval_ref": "a41d…"
    },
    "prev_hash": "08f4…",
    "event_hash": "5d17…"
  }
]

What these three records prove:

  • The push was held before it ran, by a named rule, under a specific policy snapshot.
  • A different identity approved it. Edge refuses to consume an approval when the caller is the approver.
  • The command that ran is the one that was approved. The retry is allowed only while the action hash and policy snapshot still match, and each approval is consumed once.
  • Each prev_hash is the previous event_hash. Edit or delete the approval and verification fails.
  • The records hold hashes and classifier labels, not the raw command, file contents or prompt.

For destructive actions, Edge also checks the chain before it allows the retry: it needs an approved edge.approval_resolved event with the same approval_ref and action_hash. A requested-only event does not count as proof.

Make the trail tamper-evident

Append-only storage is not enough, because someone with access to the store can still rewrite history. Tamper evidence means a later change is detectable.

  • Hash chain. Each event's event_hash is a SHA-256 over its canonical JSON, which includes prev_hash, the previous event's hash. Editing an event breaks its hash; deleting one breaks the link after it.
  • Keyed HMAC. Anyone who can write to the store can recompute a bare hash chain. With CORDUM_AUDIT_HMAC_KEY set (hex, at least 32 bytes), each event also gets an HMAC-SHA256 tag that cannot be recomputed without the key. In production the gateway will not start the chain without a key unless you set CORDUM_AUDIT_HMAC_OPTIONAL=true.
  • Serialized appends. Each append is a compare-and-set on the tenant's chain head, so concurrent gateways cannot fork the chain.

Verify a period:

verify-chain.sh
bash
# September 2026 (UTC), as unix milliseconds
curl -s --cacert "$CORDUM_TLS_CA" \
  -H "X-API-Key: $CORDUM_API_KEY" \
  -H "X-Tenant-ID: $CORDUM_TENANT_ID" \
  "$CORDUM_GATEWAY/api/v1/audit/verify?tenant=$CORDUM_TENANT_ID&since=1788220800000&until=1790812799999" \
  | jq '{status, total_events, verified_events, gaps, retention_boundary_seq, hmac_verified}'
example response
json
{
  "status": "ok",
  "total_events": 2413,
  "verified_events": 2413,
  "gaps": [],
  "retention_boundary_seq": 1,
  "hmac_verified": 2413
}

ok means every event in the range verified. compromised means a hash or HMAC mismatch, a broken link, or a missing sequence number above the retention boundary. partial means the range held only events older than the retention boundary, so its gaps are expected trimming (retention_trimmed), not tampering. The other gap types are missing, out_of_order, hash_mismatch and hmac_mismatch.

Run verification on a schedule and alert on anything other than ok; an integrity check that only runs during the audit finds problems too late. The endpoint needs the audit.verify permission or the admin role, accepts at most a 30-day range per call, and reads up to 10,000 events by default (limit, up to 100,000).

The chain is written before anything is exported. In the default strict mode (CORDUM_AUDIT_CHAIN_FAIL=strict) an event that cannot be chained is dropped rather than exported, so your SIEM never holds records the verifier cannot cover. Alert on chain-append failures.

Retention and legal hold

Decide retention per evidence type before anyone asks. Three numbers matter: how long Cordum keeps events, how long your SIEM or archive keeps them, and how long your obligations require.

  • In-product retention is a license entitlement: 7 days on Community, 90 days on Team, unlimited on Enterprise (see pricing).
  • The verifier reports retention_boundary_seq and retention_window_hours, so expired history is labeled as such instead of being read as tampering.
  • When an obligation outlasts your tier, stream events to your SIEM or archive exports. That copy then follows the retention rules of wherever it lives.

A legal hold (Enterprise) records that a tenant's audit data must be preserved: who placed it, why, when, and who released it.

legal-hold.sh
bash
# Enterprise license; needs the legalHold.write permission or admin
curl -s --cacert "$CORDUM_TLS_CA" \
  -H "X-API-Key: $CORDUM_API_KEY" -H "X-Tenant-ID: $CORDUM_TENANT_ID" \
  -H "Content-Type: application/json" \
  -X POST "$CORDUM_GATEWAY/api/v1/audit/legal-hold" \
  -d '{"tenant_id": "default", "reason": "Litigation hold for matter 2026-114"}'

# List holds, then release one by its id
curl -s --cacert "$CORDUM_TLS_CA" \
  -H "X-API-Key: $CORDUM_API_KEY" -H "X-Tenant-ID: $CORDUM_TENANT_ID" \
  "$CORDUM_GATEWAY/api/v1/audit/legal-holds"

curl -s --cacert "$CORDUM_TLS_CA" \
  -H "X-API-Key: $CORDUM_API_KEY" -H "X-Tenant-ID: $CORDUM_TENANT_ID" \
  -X DELETE "$CORDUM_GATEWAY/api/v1/audit/legal-hold/$HOLD_ID"

A hold inside Cordum cannot reach copies that already left it. When a hold starts, apply the matching hold in your SIEM and archive as well.

Stream the trail to your SIEM

Your SIEM is where the security team already searches, correlates and retains data. With an Enterprise license, Cordum streams every chained event to one backend. Because the chain runs first, exported events carry seq, prev_hash, event_hash and, when a signing key is set, hmac, so the SIEM copy can be checked against the chain later.

gateway.env
bash
# Gateway environment. Streaming export needs an Enterprise license.
CORDUM_AUDIT_EXPORT_TYPE=webhook
CORDUM_AUDIT_EXPORT_WEBHOOK_URL=https://siem.example.com/ingest/cordum
CORDUM_AUDIT_EXPORT_WEBHOOK_SECRET="replace-with-a-32-plus-character-secret"

# Chain signing key (hex, at least 32 bytes): openssl rand -hex 32
CORDUM_AUDIT_HMAC_KEY="replace-with-64-hex-characters"
receiver.py
python
import hashlib
import hmac


def verify_cordum_signature(raw_body: bytes, header: str, secret: str) -> bool:
    """Check X-Cordum-Signature: sha256=<hex HMAC-SHA256 of the raw body>."""
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header or "")


# Each POST body is a JSON array of events. Store seq, prev_hash, event_hash
# and hmac with every event so the SIEM copy can be checked against the chain.

The other backends use the same event schema:

  • syslog: RFC 5424 over TCP or UDP; CORDUM_AUDIT_EXPORT_SYSLOG_ADDR must start with tcp:// or udp://.
  • datadog: Datadog logs intake; CORDUM_AUDIT_EXPORT_DD_API_KEY, with optional CORDUM_AUDIT_EXPORT_DD_SITE and CORDUM_AUDIT_EXPORT_DD_TAGS.
  • cloudwatch: CloudWatch Logs; CORDUM_AUDIT_EXPORT_CW_LOG_GROUP and CORDUM_AUDIT_EXPORT_CW_LOG_STREAM, plus standard AWS credentials.

Check the pipeline with GET /api/v1/audit/export/health, which reports the backend and its status, and send a test event with POST /api/v1/audit/export/test.

How auditors use the trail

Auditors arrive with questions, not queries. Map each one to evidence you can produce on demand:

QuestionEvidenceSource
Was this production action approved, and by whom?Approval event with the approver identity and the approval reference that ties it to the request.safety.approval, edge.approval_resolved, mcp.tool_approval
Could the requester approve their own action?Self-approval is rejected: job and MCP approvals return 403 self_approval_denied; Edge will not consume an approval for its own approver.Approval APIs and events
Which rule applied, and what did the policy say then?matched_rule plus policy version on every decision; publish and rollback records with before and after snapshots.Audit events, GET /api/v1/policy/audit
Is the record complete for the period?Chain verification over the period, with gaps and HMAC results.GET /api/v1/audit/verify
What was held for approval, and how fast was it handled?Decision log filtered by verdict; approval analytics with p50 and p90 time to approve.GET /api/v1/governance/decisions, /governance/approvals/analytics
Did secrets end up in the log?Redacted arguments and hashes instead of payloads; secret-like keys are dropped from the read API.args_redacted, input_hash, result_hash
Can we keep a copy?CSV or NDJSON export with an integrity manifest.GET /api/v1/audit/export (Enterprise)

Reviews usually cover a period, not a single event. This script builds a monthly evidence pack. Steps 1 to 3 work on every tier; step 4 needs an Enterprise license.

evidence-pack.sh
bash
#!/usr/bin/env bash
set -euo pipefail
# Monthly evidence pack for September 2026 (UTC). Reads CORDUM_GATEWAY,
# CORDUM_TLS_CA, CORDUM_API_KEY and CORDUM_TENANT_ID from the environment.
api() {
  curl -sf --cacert "$CORDUM_TLS_CA" \
    -H "X-API-Key: $CORDUM_API_KEY" -H "X-Tenant-ID: $CORDUM_TENANT_ID" "$@"
}

# 1. Integrity: the chain for the period verifies (fails the script otherwise).
api "$CORDUM_GATEWAY/api/v1/audit/verify?tenant=$CORDUM_TENANT_ID&since=1788220800000&until=1790812799999" \
  | tee verify.json | jq -e '.status == "ok"' > /dev/null

# 2. Every job held for approval, and how each one was resolved.
#    Page with next_cursor if a month holds more than 500 decisions.
api "$CORDUM_GATEWAY/api/v1/governance/decisions?since=2026-09-01T00:00:00Z&until=2026-10-01T00:00:00Z&verdict=require_approval&limit=500" \
  | jq '[.items[] | {job_id, matched_rule, approval_status, approval_decision, policy_version, timestamp}]' > approvals.json

# 3. Policy changes, with the snapshot before and after each one.
api "$CORDUM_GATEWAY/api/v1/policy/audit?after=2026-09-01T00:00:00Z&before=2026-10-01T00:00:00Z" \
  | jq '[.items[] | {action, actor_id, snapshot_before, snapshot_after, created_at}]' > policy-changes.json

# 4. Enterprise only: governance events as CSV. Line 1 is a "#" manifest
#    with chain verification over the full range and the row filter used.
api -o governance-2026-09.csv \
  "$CORDUM_GATEWAY/api/v1/audit/export?format=csv&from=2026-09-01T00:00:00Z&to=2026-09-30T23:59:59Z&category=governance"

Hand the outputs over with the policy files for the period. verify.json is the integrity statement. The export manifest records the chain verification over the full range, the policy snapshots active in it, and the row filter you applied, so a filtered export is not mistaken for a gap.

Compliance mapping, without overclaiming

An audit trail supports compliance work; it does not make a system compliant. Map events to controls with your auditor, and keep that mapping in version control next to your policy.

EU AI Act. These obligations apply to high-risk AI systems only. Article 12(1): high-risk AI systems "shall technically allow for the automatic recording of events (logs) over the lifetime of the system." Article 19(1) has providers keep those logs "for a period appropriate to the intended purpose of the high-risk AI system, of at least six months", and Article 26(6) gives deployers the same six-month floor, in both cases to the extent the logs are under their control. If your agent falls under these articles, the six-month floor is longer than the Community and Team retention entitlements, so the SIEM or archive copy is the one that has to meet it. More in our EU AI Act guide.

SOC 2. Cordum's compliance export labels events with default SOC 2 Trust Services Criteria references: policy decisions map to CC7.2 (plus CC7.3 for denials), approvals to CC6.1 and CC7.2, and policy changes to CC8.1. You can replace the mapping with CORDUM_SOC2_MAPPING_PATH. Cordum's own mapping document says it is not an audit opinion, and neither is this guide. For ISO 27001 and NIST, see SOC 2, ISO 27001 and NIST for AI agents.

What is free and what needs a license

Community is the free, self-hosted tier and needs no license key. Team and Enterprise are license entitlements on the same core.

CapabilityCommunityTeamEnterprise
Hash-chained audit log with HMAC signingYesYesYes
Chain verification APIYesYesYes
Audit events API and dashboard Audit LogYesYesYes
Policy decision log and approval analytics APIsYesYesYes
Edge session export (redacted JSON manifest)YesYesYes
Audit retention entitlement7 days90 daysUnlimited
Approval gatesSingle approverMulti-approverCustom chains
SIEM streaming exportNoNoYes
Compliance export (CSV/NDJSON + manifest)NoNoYes
Legal holdNoNoYes

Limits to plan for

  • Only governed actions are recorded. A call that bypasses the Edge hooks, the MCP server or the job API, such as a tool with its own credentials, leaves no record. Inventory those paths. The standalone stdio MCP server reaches the chain only when CORDUM_NATS_URL is set; otherwise it writes audit events to stderr.
  • Fail-open settings leave gaps on purpose. In Edge observe mode, or enforce without CORDUM_AGENTD_FAIL_CLOSED, an action can run while Cordum is unreachable. cordumctl edge doctor shows the effective posture.
  • The HMAC key is the trust anchor. Keep it in a secret manager, away from the people who administer the store that holds the chain.
  • Redaction cuts both ways. A hash proves which input was approved but cannot show its content later, and redacted evidence artifacts can expire before the audit record does.
  • Volume. Routine events (sessions, MCP invocations, auth) are high-volume operational records. Filter reviews with category=governance; keep routine events for incident reconstruction.
  • Reads of the trail. Audit-API reads are logged only at the rate set by CORDUM_AUDIT_READ_SAMPLE_RATE, which defaults to 0. Raise it if you need a record of who read the log.

Frequently Asked Questions

What is an AI agent audit trail?
An append-only record of each action an AI agent attempted: the identity it acted for, the tool and redacted arguments, the policy decision and matched rule, any approval, and the result. It is stored so that later edits or deletions are detectable.
How is an audit trail different from agent logs or traces?
Logs and traces help engineers debug and are often sampled or mutable. An audit trail is written at the enforcement point, records who acted, why the action was allowed and who approved it, and can be verified for completeness.
How do you make AI agent audit logs tamper-evident?
Hash-chain the events so each one includes the previous event's hash, add a keyed HMAC so the chain cannot be recomputed without the key, and run verification on a schedule. In Cordum that check is GET /api/v1/audit/verify.
Does the EU AI Act require audit logs for AI agents?
For high-risk AI systems, Article 12 requires automatic recording of events, and Articles 19 and 26(6) require providers and deployers to keep those logs for at least six months, to the extent the logs are under their control. Whether an agent is high-risk depends on its use; confirm with legal counsel.
Should audit logs contain prompts and tool arguments?
Record enough to reconstruct the decision, not the secrets: redacted arguments, hashes of inputs and results, and classifier labels. Keep raw payloads, if you keep them at all, in a separate access-controlled store with its own retention.
Which Cordum audit features need a license?
The hash-chained log, HMAC signing, chain verification, the audit events API, the decision log and Edge session export are in every tier, including the free Community tier. SIEM streaming export, the compliance export and legal hold need an Enterprise license. Retention entitlements are 7 days on Community, 90 days on Team and unlimited on Enterprise.

Start where agents act

Cordum Edge puts policy, approvals and redacted audit evidence in front of coding-agent actions such as shell commands and file writes, and records each decision in the same verifiable chain.