Skip to content

Evidence Automation

Every framework on the preceding pages ends in the same place: someone asks you to prove a control works. The default corporate answer is a quarterly scramble — screenshots pasted into a spreadsheet, a Slack thread reconstructed from memory, an intern exporting logs. That approach fails for agents specifically, because an agent takes thousands of consequential actions a day and no human reconstruction can cover them.

The alternative is to treat evidence as a product feature: the system emits proof of its own correct operation as a side effect of running. Build it that way and audits become queries. This page is the engineering blueprint, in five layers — inventory, traceability, approval records, policy-as-code, and continuous control monitoring.

The test for whether you have built this properly: pick a random production action from three months ago and answer, in under five minutes and without asking a colleague, which model version and prompt template produced it, which tools it called, which policy allowed it, whether a human approved it, and what data classification it touched. If you can, you are done. If you cannot, you have logs, not evidence.

Layer 1 — AI inventory

You cannot govern what you have not enumerated, and the spreadsheet approach is wrong within a week. Agents, tools, and MCP servers get added in pull requests, so discovery belongs in CI.

Three complementary discovery sources:

SourceWhat it findsHow
CodeAgent definitions, tool registrations, prompt templates, model IDsAST scan or a registration decorator that exports a manifest at build time
InfrastructureDeployed services, service accounts, network egress to model APIsIaC parsing plus cloud asset inventory queries
RuntimeTools actually invoked, MCP servers actually reachable, models actually calledTelemetry aggregation — catches what code scanning misses

Runtime discovery is the one that finds the surprises: an MCP server registered dynamically, a model called through a proxy nobody declared, a tool reachable only under a feature flag. Reconcile all three and alert on the differences — anything present at runtime but absent from the registry is an unregistered asset and should page someone.

Store the registry as version-controlled files next to the code so changes go through review:

- id: agent.support.refund
  kind: agent
  owner: support-platform@example.com
  model: {provider: vendor-x, id: model-name, pin: "2025-11-01"}
  prompt_template: tpl.support.v7
  tools: [get_order, get_customer, draft_reply, send_reply, issue_refund]
  identity: svc-support-agent
  data_classes: [customer_pii, payment_metadata]
  autonomy: human_approval_on_irreversible
  reviewed: 2026-05-14

That single record answers ownership, model provenance, tool surface, identity, data classification, and review recency — six recurring audit questions, generated and diffed automatically.

Layer 2 — traceability with OpenTelemetry

Distributed tracing is the right evidence substrate because it already models what you need: a causal tree of operations with attributes and timing, with mature storage, sampling, and query tooling. An agent run is a trace; each model call, tool call, retrieval, and policy decision is a span.

The design decision that matters is what attributes you record. Record too little and the trace is useless as evidence; record raw arguments and you have built a compliance liability containing customer PII.

AttributeWhy it is evidenceNote
Model provider, ID, and version pinReproducibility and change attributionPin, not alias — aliases move
Prompt template identifier and versionWhich instructions were in forceHash the rendered system prompt too
Tool name and tool versionThe action takenTool identity must be stable
Argument digestProves what was requested without storing itSalted hash; store full args only for low-sensitivity tools
Authorization decision and policy bundle versionWhich rule allowed or denied thisThe policy decision itself is evidence
Human approval referenceTies the action to a personForeign key into the approval store
Data classification touchedScoping for breach and retention analysisDerived from the inventory, not hand-set
Agent identity / principalWhose authority was usedNon-human identity, and the delegated user if any
Outcome and error classWhether it workedNormalized enum, not free text
Trust level of input sourceDistinguishes operator instruction from retrieved contentCritical for injection incident analysis

A single tool-call span, trimmed:

{
  "name": "agent.tool_call",
  "trace_id": "8f4c1e...", "span_id": "a91b2c...",
  "attributes": {
    "agent.id": "agent.support.refund",
    "agent.principal": "svc-support-agent",
    "llm.model.id": "model-name", "llm.model.pin": "2025-11-01",
    "prompt.template": "tpl.support.v7",
    "tool.name": "issue_refund", "tool.args_digest": "sha256:1c9d...",
    "authz.decision": "allow", "authz.policy_bundle": "policy-2026.05.2",
    "approval.ref": "apr_01HZK9", "data.classes": ["payment_metadata"],
    "input.trust_level": "untrusted_retrieved",
    "outcome": "success"
  }
}

Retention is a policy decision you must make explicitly, not a default. Set different retention per tier: full traces for a short operational window, a reduced evidence record (decisions, approvals, authorization outcomes, digests) for the longer period your obligations require, and metrics indefinitely. Write the tiers down, enforce them in storage configuration, and keep the evidence tier append-only so nobody can quietly rewrite history. Observability backends and instrumentation options are covered in Observability Tools; Lab 4 builds this trace pipeline from scratch.

Never put raw tool arguments, retrieved document contents, or model outputs into span attributes by default. Traces are widely readable inside most organizations, are exported to third-party backends, and are retained for a long time — the exact three properties that turn a debugging convenience into a data-protection incident. Digest by default, and allow-list the specific low-sensitivity fields you genuinely need in the clear.

Layer 3 — approval records

Human-in-the-loop is worthless as evidence if the only trace is a Slack message. An approval gate should emit a record that is structured, attributable, signed, and queryable.

A usable approval record contains: a stable ID, the trace and span it authorizes, the exact action and its parameters digest, what the approver was actually shown (rendered summary hash — this is what defends against “I approved something different”), the approver’s identity and how they authenticated, the decision, a timestamp, an expiry, and a signature over all of it.

Design rules that survive contact with an auditor:

RuleReason
Deny on timeout for irreversible actionsAn unanswered prompt must never become an approval
One approval authorizes one action, scoped and expiringBlanket standing approvals are indistinguishable from no gate
Record the rendered context, not just the decisionProves the approver had adequate information
Approver must be able to deny with a reason, cheaplyA gate with a 99.8% approve rate is a rubber stamp, and the rate is itself a metric
Sign records and store append-onlyPrevents post-hoc editing, which is what “record” means
Approvals are queryable by action type, approver, and dateBecause that is literally how the audit sample is drawn

Track approve-rate and median time-to-decision per gate. If either degrades — near-100% approvals or sub-two-second decisions on complex actions — you have automation bias, and the honest response is to reduce the number of gates so the remaining ones get real attention.

Layer 4 — policy-as-code

If tool permissions live in prose, they cannot be tested, versioned, or diffed, and the reviewer of a permission change cannot see what actually changed. Expressing them as code fixes all four problems at once and produces a decision log as a bonus.

What belongs in policy: which agents may call which tools, argument constraints (amount caps, scoping to the requesting customer, allowed domains), which data classifications each agent may touch, when human approval is required, and rate or budget limits. A Rego-style sketch:

package agent.tools

default allow := false

allow if {
  input.tool == "issue_refund"
  input.agent == "agent.support.refund"
  input.args.amount <= 200
  input.args.customer_id == input.context.ticket_customer_id
  approval_ok
}

approval_ok if input.args.amount <= 50
approval_ok if input.approval.valid

Three properties make this evidence rather than configuration:

  1. Versioned. The policy bundle has a version, it is referenced in every authorization span, and you can answer “what rule was in force on that date” from git.
  2. Tested. Policy has unit tests, including negative cases: refund above cap denied, cross-customer refund denied, missing approval denied. These tests are directly presentable as control-effectiveness evidence.
  3. Logged. Every evaluation emits a decision record with the input, the outcome, and the bundle version. Denials are the interesting signal — a spike in denials is often the first indication of a prompt injection attempt.

The same discipline applies to infrastructure guardrails: IaC policy checks that refuse to deploy an agent whose service account has broader scopes than its registry entry declares, or whose egress rules permit endpoints outside the declared tool set. That turns a paper control into one that cannot be bypassed by forgetting.

Layer 5 — continuous control monitoring

The final layer maps every control to an automated check that runs on a schedule and surfaces on a dashboard, so “is this control working?” is answered by a tile rather than an investigation.

ControlAutomated evidence sourceFramework clause family it supports
Every production agent is registered and ownedInventory reconciliation job: runtime vs registry, alerts on driftGovernance and accountability; asset/scope management
Agent identities are unique and scopedIAM query comparing granted scopes to registry declarationsAccess control; accountability of roles
Tool permissions are approved and reviewedGit history on the tool registry plus review-date freshness checkChange management; operational control
Authorization is enforced on every tool callRatio of tool-call spans carrying an authz.decision attribute (target 100%)Operational control; record-keeping
Irreversible actions have human approvalJoin tool-call spans against the approval store; report orphansHuman oversight
Model and prompt versions are pinned and traceableSpan attribute coverage report per releaseTechnical documentation; traceability
Adversarial resilience is maintainedCI results from the injection and misuse suiteAccuracy, robustness, cybersecurity
Performance has not driftedScheduled eval runs against thresholds; tool-call distribution baselinesPost-market monitoring; measurement
Logs are complete and retained correctlyStorage retention audit plus trace completeness samplingRecord-keeping; documented information
Sensitive data is not in tracesScanner over span attributes for PII patternsData governance; privacy
Incidents are detected, triaged, and closedIncident tracker metrics: time-to-detect, time-to-halt, closure rateIncident response; improvement

Deliberately vague clause references are the point — map to families of requirements rather than asserting specific numbered clauses, and confirm the actual mapping against the primary sources in Standards & References. An auditor will accept “this check supports our human-oversight obligation” and will not accept a confidently wrong clause number.

Three dashboard rules: each tile shows a coverage percentage, not a pass/fail (coverage degrades gradually and you want to see the slope); every tile links to the query that produced it so anyone can re-derive it; and a stale check counts as a failed check, because an evidence job that silently stopped running is the most common way this whole system rots.

Build the checks before you need them and let them run for a quarter. Evidence has a warm-up period — a control monitor deployed the week the audit starts proves the control worked for one week. The single best time to instrument is when the agent is first built, when adding a span attribute costs minutes rather than a migration.

Building this as a portfolio project

This is the strongest artifact in this section for hiring purposes, because it is engineering work with a governance outcome, and very few candidates have it. A scoped version you can finish in two to three weeks:

Scope. One small agent with three or four tools, at least one of which is irreversible (issue a refund, send an email, delete a record — a mock is fine).

Deliverables.

  1. A YAML inventory generated from code by a CI job, with a drift check that fails the build on an unregistered tool.
  2. OpenTelemetry instrumentation emitting the attribute set above, with digesting and a documented two-tier retention policy.
  3. A signed approval store with an API, deny-on-timeout, and expiring scoped approvals.
  4. A policy bundle with unit tests, versioned, referenced in every authorization span.
  5. A control dashboard with five to seven tiles, each linked to its underlying query.
  6. A two-page README mapping each control to the framework families it supports — hedged in general terms, exactly as above.

How to present it. Do not lead with “I built a compliance tool.” Lead with the demo: run the agent, plant an indirect prompt injection that tries to trigger an over-limit refund, show the policy denial, show the denial spike on the dashboard, then pull up the trace and the approval record and reconstruct the whole decision chain in front of the interviewer. That sequence demonstrates threat understanding, engineering ability, and governance literacy in about four minutes.

Related material: Lab 4 for the trace pipeline, Lab 3 for the payloads to demo with, Lab 5 for the risk register this evidence backs, and Portfolio to Offer for framing it in interviews.