Skip to content
All posts
7 min readBrian McManus

Evidence for AI-written code: what to record from prompt to merge

Which agent, which model, which prompt, which tool call, which files, which decision, which approval. The eight fields that let a reviewer reconstruct an AI-written change without trusting a screenshot.

auditevidencegovernancecoding-agents

Evidence for AI-written code is the record that lets a reviewer who was not there reconstruct what an agent was asked, what it did, what was decided about it, and who approved it. Eight fields have to be in that record, joined to each other, or you have eight systems that each prove a fragment.

The fields that have to be there

FieldExampleWhy it cannot be dropped
Accountable identityClaude Code 2.1 on macOS, user jdoe, host MBA-0441Every action needs a person and a machine behind it, or the record cannot support a review
Model and routeProvider and model id used for the turnBehaviour changes by model, and an incident review starts with which one
Task and instructionsTicket key, initial prompt, hash of AGENTS.md or CLAUDE.mdScope is what "went beyond the task" is measured against
Tool callsTime, server, tool, digest of arguments, result classThis is the action surface; file paths alone miss shell writes and API calls
Files touchedPath, operation, size, digestTies the machine record to the commit and to the review
Policy decisionsRule id, decision, reason, mode, policy versionExplains why the agent stopped, or why it was allowed to continue
ApprovalsApprover, what they were shown, decision, expiryThe human half, which most trails leave in an email thread
Gates and releaseChecks, results, revision, environmentConnects the change to the merge and the deploy

Why the systems you already have do not add up

Four sources look like evidence and are not, on their own.

A screenshot of a terminal is not something a system can query. It has no ordering across machines, no way to show what is missing, and no verification. It is useful in an incident call and useless in an audit.

SIEM events arrive from different producers. Native telemetry can export local tool and approval events: Claude Code emits claude_code.tool_decision with the accept or reject, where the decision came from, and the tool_use_id that joins it to the tool call (monitoring). Each vendor's export is still its own schema with its own identifiers, so joining two of them, and joining either to a commit, is work you do yourself. A stdio MCP call does not cross the network, so observing it needs endpoint instrumentation rather than a network sensor.

Vendor audit logs are scoped by their own docs. GitHub's Copilot audit records cover Copilot itself, and they are not a record of what an agent did locally on a laptop (GitHub audit log docs).

Git history records the commit and the committer. It does not record that a model wrote the code, what it was asked, what it read first, or which rules were evaluated while it worked.

What a reviewer needs is one ordered record per session, with the policy decisions and the approvals inside it, joined to a commit.

What a hash chain proves, and what it does not

The chained events are the policy decisions and the detections. Each one commits to the previous link in its session, so deleting an entry, editing one, or reordering two breaks verification without anyone having to trust the writer. Verification runs locally and offline: alyria verify-chain reads an NDJSON export and prints VALID, or INVALID (first broken link at index N) with a non-zero exit (exporting the audit trail).

Two limits belong in the sentence you use with an auditor. A chain is tamper-evident, not tamper-proof: it shows that an entry changed after the fact, and it does not stop someone with write access from deleting the whole file. And an action that was never recorded leaves no trace, so coverage is the claim that matters, not chain strength.

The record for a single decision

A decision on its own looks like this, which is the shape the endpoint writes before the chain wraps it:

{
  "decision": "deny",
  "plane": "A",
  "rule": "alyria.lyra/deny.tool-not-brokered",
  "tool": "shell.exec",
  "reason": "capability not granted for role software-dev",
  "asi": "ASI05"
}

In the chain, each entry also carries the link to the entry before it and its signature. That is what makes the export verifiable by someone who does not trust the machine it came from.

Three export paths

  • Local verification: alyria verify-chain --events ./decisions.ndjson. Verify the file in the order it was written. Filtering it down to decisions first produces a false failure once an interleaved detection has advanced the sequence.
  • Tenant log: GET /v1/tenants/{tenantId}/audit with from, to, limit (1 to 100, default 50) and offset. Fix the window for the whole export so a live tenant cannot shift it underneath you, and keep the pages with the query bounds and the total you saw.
  • Streaming: decisions are signed at the endpoint and forwarded to Spectra, which takes a destination policy and key set, terminates TLS itself and exposes a health check, so your collector sits in front of a component that already speaks the format (spectra-service serve --listen 127.0.0.1:8787 --destination-policy <path> --destination-policy-keys <path>).

If your SIEM is the system of record, streaming is the integration to build. Collecting chain exports and verifying them on your side is the other supported path, and it is more manual.

Join the record to the code

If nothing in the pipeline carries an identifier from the session into the commit or the pull request, the two records can only be joined on timestamp and file path, which is not a join. Three cheap habits close it:

  1. Put the run or session id in the PR body, and in a commit trailer if your tooling reads trailers.
  2. Attach the gate result to the PR as a check with its revision, not to a chat message.
  3. Store the exported decision file with the release, beside the SBOM you already keep.
A reviewer asksThe answer should come fromWhere it usually comes from today
Who asked for this changeTask record joined to the PRA Slack thread
Which model wrote itSession recordNobody knows
What did it read firstTool call record with argumentsGuessing from the diff
Why was this allowedPolicy decision with rule idA wiki page
Who approved itApproval record in the chainAn email
What actually shippedDeployment record with revisionRelease notes

Retention is part of the design

The endpoint chain and the tenant log are two records with two retention rules. Audit history is included for 30 days within plan capacity, with longer retention agreed for enterprise deployments. Export on a schedule, verify at export time, and store the digest of each export. If someone asks in month eight for a session from month two, the only honest answer is the export you took in month two.

The minimum that changes an answer

If you do one thing this quarter: keep the inventory on so you know which agent and which version produced a change, keep policy decisions running in monitor mode and export them weekly, and put the session id in the pull request. That moves you from "an agent probably did this" to a dated, ordered record with the decisions attached, for the cost of one scheduled job.

This post is the evidence part of a series on governing agents across the SDLC. The other two cover where to put gates and how to require human approvals. It describes the Alyria Beacon decision shape, the alyria verify-chain verifier and the tenant audit query as documented in November 2026.

How is your team using AI agents?

Pick your function and answer 20 questions on agent workflows and controls. Your result appears on the page.

Governed AI agents for the work your teams do.