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.
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:
| Source | What it finds | How |
|---|---|---|
| Code | Agent definitions, tool registrations, prompt templates, model IDs | AST scan or a registration decorator that exports a manifest at build time |
| Infrastructure | Deployed services, service accounts, network egress to model APIs | IaC parsing plus cloud asset inventory queries |
| Runtime | Tools actually invoked, MCP servers actually reachable, models actually called | Telemetry 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-14That 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.
| Attribute | Why it is evidence | Note |
|---|---|---|
| Model provider, ID, and version pin | Reproducibility and change attribution | Pin, not alias — aliases move |
| Prompt template identifier and version | Which instructions were in force | Hash the rendered system prompt too |
| Tool name and tool version | The action taken | Tool identity must be stable |
| Argument digest | Proves what was requested without storing it | Salted hash; store full args only for low-sensitivity tools |
| Authorization decision and policy bundle version | Which rule allowed or denied this | The policy decision itself is evidence |
| Human approval reference | Ties the action to a person | Foreign key into the approval store |
| Data classification touched | Scoping for breach and retention analysis | Derived from the inventory, not hand-set |
| Agent identity / principal | Whose authority was used | Non-human identity, and the delegated user if any |
| Outcome and error class | Whether it worked | Normalized enum, not free text |
| Trust level of input source | Distinguishes operator instruction from retrieved content | Critical 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.
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:
| Rule | Reason |
|---|---|
| Deny on timeout for irreversible actions | An unanswered prompt must never become an approval |
| One approval authorizes one action, scoped and expiring | Blanket standing approvals are indistinguishable from no gate |
| Record the rendered context, not just the decision | Proves the approver had adequate information |
| Approver must be able to deny with a reason, cheaply | A gate with a 99.8% approve rate is a rubber stamp, and the rate is itself a metric |
| Sign records and store append-only | Prevents post-hoc editing, which is what “record” means |
| Approvals are queryable by action type, approver, and date | Because 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.validThree properties make this evidence rather than configuration:
- 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.
- 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.
- 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.
| Control | Automated evidence source | Framework clause family it supports |
|---|---|---|
| Every production agent is registered and owned | Inventory reconciliation job: runtime vs registry, alerts on drift | Governance and accountability; asset/scope management |
| Agent identities are unique and scoped | IAM query comparing granted scopes to registry declarations | Access control; accountability of roles |
| Tool permissions are approved and reviewed | Git history on the tool registry plus review-date freshness check | Change management; operational control |
| Authorization is enforced on every tool call | Ratio of tool-call spans carrying an authz.decision attribute (target 100%) | Operational control; record-keeping |
| Irreversible actions have human approval | Join tool-call spans against the approval store; report orphans | Human oversight |
| Model and prompt versions are pinned and traceable | Span attribute coverage report per release | Technical documentation; traceability |
| Adversarial resilience is maintained | CI results from the injection and misuse suite | Accuracy, robustness, cybersecurity |
| Performance has not drifted | Scheduled eval runs against thresholds; tool-call distribution baselines | Post-market monitoring; measurement |
| Logs are complete and retained correctly | Storage retention audit plus trace completeness sampling | Record-keeping; documented information |
| Sensitive data is not in traces | Scanner over span attributes for PII patterns | Data governance; privacy |
| Incidents are detected, triaged, and closed | Incident tracker metrics: time-to-detect, time-to-halt, closure rate | Incident 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.
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.
- A YAML inventory generated from code by a CI job, with a drift check that fails the build on an unregistered tool.
- OpenTelemetry instrumentation emitting the attribute set above, with digesting and a documented two-tier retention policy.
- A signed approval store with an API, deny-on-timeout, and expiring scoped approvals.
- A policy bundle with unit tests, versioned, referenced in every authorization span.
- A control dashboard with five to seven tiles, each linked to its underlying query.
- 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.