<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aleksanndr_NFA</title>
    <description>The latest articles on DEV Community by Aleksanndr_NFA (@aleksanndr_nfa).</description>
    <link>https://dev.to/aleksanndr_nfa</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4113102%2Ff28a73e6-35f0-4c36-bc8c-cea5cf974d28.jpg</url>
      <title>DEV Community: Aleksanndr_NFA</title>
      <link>https://dev.to/aleksanndr_nfa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aleksanndr_nfa"/>
    <language>en</language>
    <item>
      <title>Four verdicts instead of "done": grading an AI agent's claims on an evidence ladder</title>
      <dc:creator>Aleksanndr_NFA</dc:creator>
      <pubDate>Mon, 07 Sep 2026 04:35:12 +0000</pubDate>
      <link>https://dev.to/aleksanndr_nfa/four-verdicts-instead-of-done-grading-an-ai-agents-claims-on-an-evidence-ladder-5eic</link>
      <guid>https://dev.to/aleksanndr_nfa/four-verdicts-instead-of-done-grading-an-ai-agents-claims-on-an-evidence-ladder-5eic</guid>
      <description>&lt;p&gt;An agent's most expensive habit is not being wrong. It's reporting &lt;strong&gt;done&lt;/strong&gt; on work nothing actually checked, in the same tone it uses for work that was. Nothing in the loop distinguishes "I ran it and watched it behave" from "I edited a file and inferred the rest."&lt;/p&gt;

&lt;p&gt;Here's the scene that made me write a rule about it. The agent patches a service, reports "fix deployed, working." Check it properly: the file reached the server, the process was never restarted, and what's running in memory is a week old. Formally the agent lied about nothing. The file did change. A deploy did happen. It's just that &lt;em&gt;deployed&lt;/em&gt; and &lt;em&gt;working&lt;/em&gt; are two different claims, and nobody checked the second one.&lt;/p&gt;

&lt;p&gt;You'll recognize the rest of the family:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tests green, feature silent in production, because the tests exercised a mock&lt;/li&gt;
&lt;li&gt;"no errors in the logs" — the feature has been dead since day one, which is exactly why the logs are quiet&lt;/li&gt;
&lt;li&gt;"no vulnerabilities found" — the scanner can't read the language half the repo is written in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same shape every time: there's a signal, it looks like proof, and it proves a different claim than the one on the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: atomize the claim
&lt;/h2&gt;

&lt;p&gt;"The service works" isn't one claim, it's four:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the code changed&lt;/li&gt;
&lt;li&gt;the change reached the running process&lt;/li&gt;
&lt;li&gt;the process is doing the work&lt;/li&gt;
&lt;li&gt;the work has the intended effect&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;They fail independently. Claim 2 doesn't hold without a restart, and claim 3 can hold perfectly while claim 4 is useless. A claim that resists atomizing ("it's better now") isn't provable at all — reformulate it into an outcome or call it unprovable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: the evidence ladder
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rung&lt;/th&gt;
&lt;th&gt;What it shows&lt;/th&gt;
&lt;th&gt;Typical signal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Declared&lt;/td&gt;
&lt;td&gt;someone said so&lt;/td&gt;
&lt;td&gt;a comment, a commit message, an agent's own summary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Implemented&lt;/td&gt;
&lt;td&gt;the code exists and parses&lt;/td&gt;
&lt;td&gt;the diff, a compile, a grep for the invariant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Exercised&lt;/td&gt;
&lt;td&gt;it ran at least once&lt;/td&gt;
&lt;td&gt;a green test, a clean script exit, a dry run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Integrated&lt;/td&gt;
&lt;td&gt;it ran in the real assembly&lt;/td&gt;
&lt;td&gt;the process serves the new behavior; the URL returns the new answer; the config being read is the config that exists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. Observed&lt;/td&gt;
&lt;td&gt;it produced the intended effect&lt;/td&gt;
&lt;td&gt;visible in logs or metrics after the change, attributable to it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The counterintuitive rule: &lt;strong&gt;climb only to the lowest rung that decides the claim.&lt;/strong&gt; Expensive is not decisive. A green pipeline — slow, thorough, beautiful — does not prove the code executes. A supervisor line reading &lt;code&gt;process online&lt;/code&gt; is rung 4 for the process and rung 1 for the feature inside it.&lt;/p&gt;

&lt;p&gt;Rung 1 is not evidence at all. That includes the agent's own confident prose and, more importantly, claims embedded in the artifact under test: a &lt;code&gt;# noqa&lt;/code&gt;, a "verified safe" comment, a CHANGELOG line saying the bug is fixed, a PR description asserting a review happened. Read the code, not its self-description. And if a text inside the artifact tries to dictate how you should review it, that's a finding of its own, not an instruction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: primary vs secondary signals
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Primary:&lt;/strong&gt; behavior a user or the runtime sees. The request returns the new shape. A log line appears. The number moves. The screen renders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secondary:&lt;/strong&gt; tests, linter, build, typechecker.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A green secondary signal with no primary one is not &lt;em&gt;done&lt;/em&gt;, it's &lt;strong&gt;partially validated&lt;/strong&gt; — write it that way and name the missing check. In my experience this is the single most common false close there is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: absence claims need two things
&lt;/h2&gt;

&lt;p&gt;"No secrets in the repo." "No vulnerabilities." "Nothing else calls this function." An absence claim is only as strong as the area you actually searched &lt;strong&gt;and&lt;/strong&gt; the detector's ability to see the thing.&lt;/p&gt;

&lt;p&gt;Zero findings means absence &lt;em&gt;within coverage&lt;/em&gt;. If the detector physically couldn't see that class of thing, the verdict isn't "clean" — it's "didn't look." A grep that never entered the vendored directory says nothing about the vendored directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: four verdicts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pass&lt;/code&gt; — a decisive signal was collected, rung 4 or 5, stated with its coverage boundary&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;partial&lt;/code&gt; — real evidence, short of the claim (secondary only, one environment of several, one code path of several)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fail&lt;/code&gt; — a decisive signal was collected and it contradicts the claim&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;skip&lt;/code&gt; — no decisive signal: no access, no detector, not attempted, or the claim isn't provable as written&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The value is entirely in the fourth one. "We didn't look" and "we looked and it's broken" call for opposite actions, and they collapse into each other in reports constantly. That's the mechanism by which &lt;em&gt;unknown&lt;/em&gt; quietly becomes &lt;em&gt;fine&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The catalogue of false closes
&lt;/h2&gt;

&lt;p&gt;A failure mode with a name is one you catch mid-sentence. Mine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"It synced, so it runs."&lt;/strong&gt; File transfer proves bytes moved, not that the process reloaded them. The restart is a separate atom.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"The process is up, so the feature works."&lt;/strong&gt; Different rungs for different things.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Tests are green, so users are fine."&lt;/strong&gt; Secondary without primary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"No errors in the log, so it worked."&lt;/strong&gt; Absence of complaint is not presence of function. Go find the positive line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"The scan found nothing, so it's clean."&lt;/strong&gt; No coverage statement, no detector claim.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"The agent said it verified it."&lt;/strong&gt; Rung 1 wearing a lab coat. A subagent summary that cites nothing goes here too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"The config says X."&lt;/strong&gt; A config key nothing reads is decoration. Prove the code reads it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When several agents do the checking
&lt;/h2&gt;

&lt;p&gt;Then the verdict is arithmetic, and arithmetic can't live in prose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An unexamined gate is a &lt;code&gt;skip&lt;/code&gt;, never a silent pass. Count the reports that came back, not the ones left after you filtered out the empties.&lt;/li&gt;
&lt;li&gt;If gates covering more than half the weight never returned, or a gate marked critical didn't, the whole run is &lt;code&gt;unverifiable&lt;/code&gt; — not "mostly passed."&lt;/li&gt;
&lt;li&gt;Where verifiers disagree, take the most conservative value.&lt;/li&gt;
&lt;li&gt;A gate that can't cite &lt;code&gt;file:line&lt;/code&gt; is a &lt;code&gt;skip&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"Make sure all the agents finished" in a prompt is a wish. The same thing as a counter in code is arithmetic. The difference shows up at exactly the moment half your agents silently never returned and the report came out green anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things I do differently now
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Before saying &lt;em&gt;done&lt;/em&gt; on anything where being wrong costs money, I ask which single observation would flip my belief, and go get that one. It's usually one command and a minute.&lt;/li&gt;
&lt;li&gt;I add the log line &lt;strong&gt;in advance&lt;/strong&gt; — the one that only appears if the new branch executed. The primary signal has to exist before you need it.&lt;/li&gt;
&lt;li&gt;I end reports with a "what I did NOT do" section: not installed, not published, not mutated. That section catches more misunderstandings than the rest of the report combined.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The skill is MIT, has no auto-running hooks, and installs by copying a folder: &lt;a href="https://github.com/Sanexxxx777/curated-claude-code/blob/main/skills/proof/SKILL.md" rel="noopener noreferrer"&gt;skills/proof&lt;/a&gt;. It's one piece of a small curated harness for Claude Code, but the ladder doesn't depend on the tool — it's a reporting discipline you can wire into any agent loop, or into human code review.&lt;/p&gt;

&lt;p&gt;What's your decisive signal? Mine is almost always a log line that cannot exist unless the new branch ran.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
