Why "tests passed" is the wrong question for an AI coding agent A developer argues that AI coding agents should be auditable through execution evidence rather than trusted based on their reports. The post distinguishes between requests and results, proposes a three-rung evidence hierarchy, and emphasizes binding records to commit hashes. It cites research showing that AI-generated patches can be incorrect and that models struggle to self-correct without external feedback. Your coding agent finishes a task and tells you the tests passed. You accept the report. Later, when one claim does not match what happened, you go back through the log to find where the story diverged - and the log cannot tell you. It records what the agent said. It does not record what ran. That gap is not solved by making the agent's report more persuasive. A report is still a report unless the run is backed by execution evidence. "Did the tests pass" is a question about an outcome. The useful question is one level down: What would have to be recorded for that answer to be checkable later? That changes what you build: you stop making the agent more trustworthy and start making its runs more auditable. That turns a vague trust question into a concrete evidence problem. Three words, kept strictly apart. When you start a task you name things: a model, an effort level, a sandbox, an approval policy. Those are requests. Between your request and the actual invocation sit a provider, a tool, a routing layer, and a stack of defaults. Any of them can end up serving something other than what you named, which is why the request and the result need to stay distinguishable. The failure is not the substitution. It is writing the request into the log in the same shape as a result: model: claude-x-large <- did this run, or was this asked for? Once a request and a result look identical on the page, nobody reading the history afterwards can separate them. Label requests as requests. They are useful in that shape: they tell you what policy intended. They are just not evidence. "What actually ran" feels like a yes-or-no fact. It is not. It is a claim with a source, and sources differ. Three rungs, weakest first: The rule that makes this useful is one line: never round a weaker observation up to a stronger one. If all you have is rung one, the record says rung one. It does not say "confirmed" because "confirmed" reads better in a summary. That discipline is what makes the record worth reading when something does not match the report. I reserve a separate word, effective , for rung three only: provider-native and bound to that specific invocation. Applied loosely, the word stops carrying weight exactly when you need it to. Evidence is only meaningful if you know what it is evidence about. Every exit code, every observation, every approval is a fact about one specific revision. So bind the record to the commit hash, not to a branch name and not to a conversation summary. A branch name is a pointer that moves. "Reviewed on main" says nothing once main has advanced. A conversation summary is worse: it is the model's own recollection of what it did, which is the self-report you were trying to get away from. Binding to a SHA also keeps an easy-to-miss gap visible: a review recorded against one commit says nothing about an amended revision, because that is a different SHA. Worth being blunt about the limit, because it is the part most likely to get oversold. None of this proves the code is correct. A run can be perfectly accounted for, every exit code captured and every digest bound to the right commit, and the change can still be wrong, unsafe, or badly designed. Execution evidence answers a narrower question: what actually happened when this candidate was exercised. You need a reliable answer to that before a review is worth much, because reviewing a run that did not happen as described is reviewing fiction. But it is an input to judgment, not a substitute for it. Two findings sharpen this. Wang, Pradel and Liu found that patches counted as resolved on SWE-bench Verified can still diverge from the developers' own fix, some plainly incorrect. And Huang and colleagues found that language models struggle to correct their own reasoning without external feedback, which is reason enough to treat a self-generated "looks good" as the weakest signal in the room. Some approvals do not belong to the model at all. Merge, production deployment, and changes to physical or network devices are decisions with a blast radius. Automation can gather the evidence and walk up to that line. A person crosses it, reading facts rather than a paragraph explaining that everything went fine. A builder is not its own final reviewer. Keep the roles separate. The independent Reviewer judges an exact SHA it did not build, and it has no authority to remediate that revision - a Reviewer that starts fixing the code stops being independent for that revision. Where policy requires independent review, the Reviewer uses a different provider/model family from the Builder. That is a structural response to the documented tendency of a model acting as judge to favor its own outputs, rather than a prompt-level one. Ask what you would have to record for the agent's answer to be checkable. Record exactly that, at the strength it deserves, bound to an exact SHA. Keep the decisions with real blast radius on the human side of the line. That is not a trust problem. It is a bookkeeping problem, and bookkeeping problems have solutions. I am building Kelruno around this - independent verification for coding agents. Pre-release. Further reading: Requested, observed, effective: what an AI agent run proves https://kelruno.ai/articles/requested-observed-effective