Skip to main content

Decision Trace

Feature row 5 — Core / Sprint T / Sprint PEV

Available on every plan. Public verification is part of the Public Evidence Verification feature.

What this is

A Decision Trace is the causal chain-of-custody artifact that Behavry emits for every proxied tool call. Because the proxy sits inline on the execution path, every trace is a first-hand record of who called what, what policy fired, and what the outcome was. Traces are SHA-256 hash-chained — any tampered or out-of-order row breaks the chain and fails verification.

This is the primary artifact auditors, security teams, and incident responders use when they need to prove — or disprove — that an AI agent took a specific action.

Anatomy of a trace

Every trace is one row in the audit_events TimescaleDB hypertable (backend/behavry/audit/service.py). The row captures:

SectionFields
Identityagent_id, session_id, tenant_id, workflow_id, requester_id
Actiontool_name, mcp_server, action, target, input_hash
Policypolicy_result (allow / deny / escalate), policy_id, policy_reason
Behaviorbehavioral_score (z-score at time of event)
DLPdlp_findings, dlp_action
Integrityevent_hash, previous_hash
Outcomeresponse_code, latency_ms, error
Extraextra JSONB for anything module-specific

input_hash is a SHA-256 over the normalized tool arguments — we don't store the arguments themselves by default, so a trace proves which payload was processed without exposing sensitive content.

The hash chain

Each event is hashed together with the previous event's hash:

event_hash = sha256(
canonical(event_fields_excluding_hashes) || previous_hash
)

canonical() is a stable JSON serializer (sorted keys, no whitespace, UTF-8). If any row between t0 and tN is modified, deleted, or reordered, event_hash_N no longer matches a recomputation from the earlier rows and the chain breaks.

The verifier lives at backend/behavry/audit/verify.py and can recompute the chain for any time range.

Verifying a trace

Internally (admin UI)

Audit → Events → row detail → "Verify chain" recomputes the chain from the event backwards and reports pass / fail with the first breaking row.

Programmatically

curl -H "Authorization: Bearer $ADMIN_TOKEN" \
"$BEHAVRY_URL/api/v1/audit/verify?start=2026-04-01T00:00:00Z&end=2026-04-02T00:00:00Z"

Returns:

{
"events_verified": 12843,
"chain_intact": true,
"first_bad_row": null,
"duration_ms": 412
}

Externally (public verification)

A verifier outside your tenant can prove that a specific event hash exists in your audit log without Behavry credentials:

GET /api/v1/public/verify?hash=<event_hash>

Returns { "found": true, "timestamp": "...", "chain_intact": true } if the hash is in the canonical log. This endpoint is rate-limited and exposes no event content — only existence and integrity. See Public Evidence Verification.

Where traces come from

Every module that makes a policy-relevant decision publishes through the event bus and lands in audit_events:

  • MCP proxy (backend/behavry/proxy/engine.py) — tool calls, tools/list filtering, blast-radius checks
  • Behavioral monitor — baseline updates, anomaly alerts
  • DLP scanner — pattern matches, quarantines
  • SIEM fan-out — outbound delivery success/failure
  • Admin — policy changes, user invites, kill-switch actions

Retention & export

  • Default retention: 90 days on Professional, 1 year on Enterprise (configurable per-tenant)
  • Compressed automatically by TimescaleDB after 7 days
  • Export to CSV / JSON / NDJSON via Audit → Export or GET /api/v1/audit/export
  • Forwarded to SIEM via the SIEM Connectors fan-out