Skip to main content

Evidence Store

How Talon creates, signs, stores, and exports tamper-evident audit records.

For a complete list of explanation codes and remediation guidance, see explanation-catalog.md.

Overview​

Every interaction that passes through Talon generates an evidence record -- a structured JSON document that captures what happened, what policy decided, and what data was involved. The evidence store is the proof layer under Talon's four pillars -- cost control, reliability, shared policy, and session understanding: every budget, policy, and routing decision produces a signed record as it runs, and compliance reports are generated from those records rather than assembled separately.

Request → Pipeline → Evidence Record → HMAC Sign → SQLite Write → Export

Operational projection: Evidence → OperationalEvent → Metrics / UI / CLI. Explanations come from explanations[] first; legacy policy_decision.reasons is a fallback only.

Invariants:

  • Ordering: timestamp DESC, id DESC across /v1/evidence and /api/v1/events/recent.
  • Event rows expose evidence_id, correlation_id, decision, reason_code, reason_text, suggested_fix.
  • No evidence write → no event → no live metric.

Evidence Record Structure​

Each record contains these sections:

SectionFieldsPurpose
Identityid, session_id, correlation_id, timestamp, tenant_id, agent_idWho, when, which tenant; session_id links requests in the same lifecycle session
Policy Decisionallowed, action, reasons, policy_versionWhat the policy engine decided
Deterministic Explanationsexplanations[] (code, decision, stage, reason, trigger, fix, policy_ref, version_identity)Human-readable, reproducible rationale attached to every record
Classificationinput_tier, output_tier, pii_detected, pii_redacted, output_pii_detected, response_scan (how the response control was applied: post-delivery observation vs preventive, complete vs incomplete)What PII was found, and whether the response finding was observed or prevented
Executionmodel_used, cost, tokens, duration_ms, tools_called, errorWhat the LLM did
Audit Trailinput_hash, output_hashSHA-256 content hashes for forensics
Complianceframeworks, data_locationWhich regulations apply
SignaturesignatureHMAC-SHA256 over all other fields

Sessions: When the server or CLI creates a lifecycle session (e.g. for POST /v1/agents/run or talon run), that session’s ID is stored in session_id. Plan-gated runs and their subsequent auto-dispatch share the same session so that all evidence for that flow can be correlated. Export and API responses include session_id when present.

Deterministic Explanations (MVP Contract)​

  • Every evidence record includes a non-empty explanations[] array.
  • Explanations are deterministic and rule-based (no localization/LLM paraphrasing).
  • version_identity is dual-factor: user-declared version + canonical content hash.
  • Legacy free-text policy_decision.reasons is only a migration bridge; explanations are the source of truth for operator UX.

HMAC Signing​

Every evidence record is signed at creation time using HMAC-SHA256.

How it works:

  1. The evidence record is serialized to JSON (excluding the signature field)
  2. HMAC-SHA256 is computed using the server's TALON_SIGNING_KEY
  3. The hex-encoded signature is stored in the signature field
  4. The complete record (including signature) is written to SQLite

Verification: talon audit verify <id> recomputes the HMAC from the stored record and compares it to the stored signature. If any field was modified after creation -- timestamp, cost, PII findings, policy decision -- verification fails.

$ talon audit verify req_a1b2c3d4
✓ Evidence req_a1b2c3d4: signature VALID

$ talon audit verify req_tampered
✗ Evidence req_tampered: signature INVALID

You can also verify signed export files offline:

talon audit verify --file march-signed-evidence.json

The file verifier reports total/valid/invalid/malformed/unsupported counts and exits non-zero if any record is invalid or unverifiable.

What this proves: The signing key never leaves the server. If the signature is valid, the record has not been modified since Talon created it. This is a supporting control for ISO 27001 A.8.15 (tamper-evident logging) without requiring external infrastructure like a blockchain or append-only storage.

For the byte-exact field list, canonical serialization, and signing/verification procedure — written so a third party can verify a record independently — see the Evidence integrity specification.

Progressive Disclosure​

Evidence is exposed at three levels of detail, matching different use cases:

Layer 1: Index (List View)​

talon audit list returns lightweight summaries for scanning:

ID TIME AGENT ALLOWED COST(€) MODEL
req_a1b2c3 2026-03-15T10:23:45 slack-bot true 0.003 gpt-4o-mini
req_d4e5f6 2026-03-15T10:24:12 hr-assistant false 0.000 gpt-4o

Index/list responses also include stable primary explanation fields:

  • primary_explanation_code
  • primary_explanation_reason

Layer 2: Timeline​

talon audit timeline --around <id> shows what happened before and after a specific event, useful for incident investigation.

Layer 3: Full Record​

talon audit show <id> returns the complete evidence record with all fields, PII findings, deterministic explanations, version_identity, policy reasons, and HMAC signature status.

Storage​

Default: SQLite database at ~/.talon/evidence.db. Single file, no external dependencies. Suitable for single-instance deployments.

Upgrade path: PostgreSQL for high-availability deployments. Same evidence schema, different storage backend. Configure via talon.config.yaml.

Retention: Configurable via audit.retention_days in agent.talon.yaml. Default: 90 days.

Export​

Evidence can be exported for compliance officers, auditors, or external systems:

# CSV for spreadsheets and compliance tools
talon audit export --format csv --from 2026-03-01 --to 2026-03-31 > march-audit.csv

# JSON for programmatic access
talon audit export --format json --from 2026-03-01 > march-audit.json

# Signed JSON for offline integrity verification
talon audit export --format signed-json --from 2026-03-01 > march-signed-audit.json

# Signed NDJSON for line-oriented pipelines
talon audit export --format signed-ndjson --from 2026-03-01 > march-signed-audit.ndjson

CSV columns: id, session_id, timestamp, tenant_id, agent_id, invocation_type, allowed, cost, model_used, duration_ms, has_error, input_tier, output_tier, pii_detected, pii_redacted, policy_reasons, tools_called, input_hash, output_hash, primary_explanation_code, primary_explanation_reason, primary_version_identity, plus cache fields when applicable, and two legacy columns — observation_mode_override and shadow_violation_types — kept for column stability: they are always false/empty for records written since #442 and non-empty only for historical records from the removed shadow/passthrough postures. JSON and NDJSON export include the same fields; session_id links evidence to a lifecycle session (e.g. plan-gated run and its dispatch).

Signed exports include full Evidence records with per-record signature fields, so integrity can be verified later with talon audit verify --file.

Practical split:

  • Use reduced csv/json/ndjson export for reporting and spreadsheet workflows.
  • Use signed-json/signed-ndjson when you need cryptographic integrity verification.

Evidence Verify API​

The dashboard and API expose record-level verification:

GET /v1/evidence/{id}/verify

Response:

{
"id": "req_a1b2c3d4",
"valid": true
}

Tenant scoping applies: agent keys can only verify evidence in their own tenant scope.

Buyer-facing wording:

Talon evidence can be independently verified. If a record is changed after creation, verification fails.

OpenTelemetry Export​

Evidence records are also exported as OpenTelemetry spans, following the GenAI semantic conventions. This allows integration with existing observability stacks (Grafana, Datadog, etc.) without custom tooling.

Prompt Storage and Data Minimization​

Evidence records store hashes of prompts and responses (input_hash, output_hash) — never verbatim text by default. When audit.include_prompts is enabled, prompt text is persisted in a separate prompt version store (prompt_versions.db).

Data minimization (GDPR Art. 5(1)(c)): When input PII redaction (redact_input: true) is active, the prompt version store saves the redacted prompt — the text the LLM actually received — not the original PII-bearing input. This ensures that enabling prompt logging does not inadvertently persist personal data that was intentionally stripped.

Forensic opt-in: If forensic reconstruction of original inputs is required (e.g. legal hold), set audit.include_original_prompts: true. This persists both the redacted and original prompts. Use this flag with care and document its use in your data processing records (GDPR Art. 30).

Storage LayerDefault ContentPII Risk
Evidence record (input_hash)SHA-256 hashNone
Step evidence (input_summary)Truncated redacted promptLow
Prompt version storeRedacted promptLow
Prompt version store (include_original_prompts)Original + redactedHigh (opt-in)

See ADR-002 for the full rationale.

Compliance Mapping​

RegulationArticleTalon Feature
GDPRArticle 30 (Processing records)Evidence export with data categories, purposes
ISO 27001A.8.15 (Logging)HMAC-signed tamper-evident records
NIS2Article 21 (Incident reporting)Timeline view, correlation IDs
EU AI ActArticle 13 (Transparency)Full execution records with model, cost, PII
DORAArticle 11 (ICT incidents)Cost tracking, incident timeline

Source Code​

FileResponsibility
internal/evidence/store.goSQLite storage, HMAC signing, querying
internal/evidence/generator.goEvidence record creation from pipeline results
internal/evidence/signature.goHMAC-SHA256 computation and verification
internal/evidence/export.goOpenTelemetry span export
internal/evidence/sanitize.goPII sanitization for exports