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.
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
| Field | Example | Why it cannot be dropped |
|---|---|---|
| Accountable identity | Claude Code 2.1 on macOS, user jdoe, host MBA-0441 | Every action needs a person and a machine behind it, or the record cannot support a review |
| Model and route | Provider and model id used for the turn | Behaviour changes by model, and an incident review starts with which one |
| Task and instructions | Ticket key, initial prompt, hash of AGENTS.md or CLAUDE.md | Scope is what "went beyond the task" is measured against |
| Tool calls | Time, server, tool, digest of arguments, result class | This is the action surface; file paths alone miss shell writes and API calls |
| Files touched | Path, operation, size, digest | Ties the machine record to the commit and to the review |
| Policy decisions | Rule id, decision, reason, mode, policy version | Explains why the agent stopped, or why it was allowed to continue |
| Approvals | Approver, what they were shown, decision, expiry | The human half, which most trails leave in an email thread |
| Gates and release | Checks, results, revision, environment | Connects 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}/auditwithfrom,to,limit(1 to 100, default 50) andoffset. 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 thetotalyou 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:
- Put the run or session id in the PR body, and in a commit trailer if your tooling reads trailers.
- Attach the gate result to the PR as a check with its revision, not to a chat message.
- Store the exported decision file with the release, beside the SBOM you already keep.
| A reviewer asks | The answer should come from | Where it usually comes from today |
|---|---|---|
| Who asked for this change | Task record joined to the PR | A Slack thread |
| Which model wrote it | Session record | Nobody knows |
| What did it read first | Tool call record with arguments | Guessing from the diff |
| Why was this allowed | Policy decision with rule id | A wiki page |
| Who approved it | Approval record in the chain | An email |
| What actually shipped | Deployment record with revision | Release 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.