<?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: Erik Hill</title>
    <description>The latest articles on DEV Community by Erik Hill (@agentdev9).</description>
    <link>https://dev.to/agentdev9</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%2F4029397%2F4f06ed5b-bef3-4e78-b122-092528b3df4f.png</url>
      <title>DEV Community: Erik Hill</title>
      <link>https://dev.to/agentdev9</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/agentdev9"/>
    <language>en</language>
    <item>
      <title>301 duplicate IDs in the browser, 0 on the JVM: one real bug, end to end</title>
      <dc:creator>Erik Hill</dc:creator>
      <pubDate>Wed, 22 Jul 2026 00:01:43 +0000</pubDate>
      <link>https://dev.to/agentdev9/301-duplicate-ids-in-the-browser-0-on-the-jvm-one-real-bug-end-to-end-10cj</link>
      <guid>https://dev.to/agentdev9/301-duplicate-ids-in-the-browser-0-on-the-jvm-one-real-bug-end-to-end-10cj</guid>
      <description>&lt;p&gt;A reader named Ryan left the sharpest comment on my last post. The gist: it was jargon-heavy, kept restating "you have to test the AI's output" in new words, and read more like a pitch deck than a case study. He was right, and he pointed at the fix himself: &lt;em&gt;walk through one real task. What changed, what caught the problem, what proof was required, where did a human step in.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So here is one task, start to finish. Everything below is public and runnable. No withheld details, no diagrams of boxes with arrows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup, in two sentences
&lt;/h2&gt;

&lt;p&gt;My match-3 game runs on a plain-Java rules engine. It runs two ways: on a JVM (where the tests and CI live) and in the browser, compiled to JavaScript by &lt;a href="https://teavm.org" rel="noopener noreferrer"&gt;TeaVM&lt;/a&gt;, so the same Java drives a real playable board.&lt;/p&gt;

&lt;p&gt;That second runtime is not just a demo. Running one piece of logic on two different machines gives me a free check: &lt;strong&gt;where the two disagree, one of them is wrong.&lt;/strong&gt; I did not have to write down the right answer. I just had to notice a disagreement.&lt;/p&gt;

&lt;p&gt;Here is a task where that check earned its keep.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed
&lt;/h2&gt;

&lt;p&gt;I ported the engine to the browser. New &lt;code&gt;demo-js&lt;/code&gt; module, TeaVM config, opaque integer handles in and JSON out so boards never actually cross into JavaScript. Mechanical work. The engine code barely moved.&lt;/p&gt;

&lt;p&gt;Except the port compiled a line I had never once looked at hard. This is how every gem got its ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nanoTime&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;RNG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nextInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Timestamp plus a random number. It had passed every test for months.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the check caught
&lt;/h2&gt;

&lt;p&gt;The IDs are how the renderer tells one gem from another. Two gems with the same ID animate as a single gem. So I ran the same board generation on both runtimes and counted collisions over 128,000 gems.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JVM: &lt;strong&gt;0 duplicates.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Browser (TeaVM): &lt;strong&gt;301 duplicates.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same source code. Same inputs. Different answer. That is the entire signal. A machine counted it; I did not have to guess that something felt off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I stepped in
&lt;/h2&gt;

&lt;p&gt;A number this specific still needs a human to say &lt;em&gt;which&lt;/em&gt; side is wrong and &lt;em&gt;why&lt;/em&gt;. That part was me.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;System.nanoTime()&lt;/code&gt; looks unique but only leans on the clock being high-resolution enough that two calls land on different values. A JVM's timer is fine, so the flaw was invisible there. Browsers deliberately clamp their clock to about 100 microseconds (a Spectre mitigation), so &lt;code&gt;nanoTime&lt;/code&gt; barely advances between gems and &lt;code&gt;RNG.nextInt(1000)&lt;/code&gt; collides on its own often enough to matter.&lt;/p&gt;

&lt;p&gt;Neither runtime was broken. The &lt;strong&gt;code&lt;/strong&gt; was, for depending on clock resolution it was never promised. The browser was just honest about it.&lt;/p&gt;

&lt;p&gt;The fix is boring, which is the point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;idSeq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0L&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;synchronized&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="nf"&gt;nextId&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;idSeq&lt;/span&gt;&lt;span class="o"&gt;++;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-String id = row + "-" + col + "-" + System.nanoTime() + "-" + RNG.nextInt(1000);
&lt;/span&gt;&lt;span class="gi"&gt;+String id = row + "-" + col + "-" + nextId();
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A counter is unique on every clock. The guarantee stops depending on the platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  What had to exist before it could close
&lt;/h2&gt;

&lt;p&gt;The one rule I do not bend: a fix is not done because I say "fixed." It is done when a check that would catch the bug is sitting on disk and passing in CI. For this one, that meant three tests whose whole job is to fail if IDs ever lean on the clock again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;idsAreUniqueWithoutRelyingOnClockResolution&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Mint many gems as fast as possible: a clock-derived id would collide&lt;/span&gt;
    &lt;span class="c1"&gt;// here on any platform whose timer doesn't advance between calls.&lt;/span&gt;
    &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HashSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;GameBoard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Gem&lt;/span&gt;&lt;span class="o"&gt;[][]&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BoardEngine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createBoard&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plain&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GameBoard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Gem&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GameBoard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Gem&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus one for board creation and one for the refill hot path, where new gems get minted every cascade. The suite went from 51 tests to 59. Those three are the receipt that the specific failure cannot come back quietly. Without them, "I fixed the ID thing" is just a sentence.&lt;/p&gt;

&lt;p&gt;The commit and the tests are here: &lt;a href="https://github.com/egnaro9/match3-engine" rel="noopener noreferrer"&gt;&lt;code&gt;match3-engine&lt;/code&gt;&lt;/a&gt; (&lt;code&gt;BoardEngine.java&lt;/code&gt;, &lt;code&gt;IdUniquenessTest.java&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  The same trick, one level up
&lt;/h2&gt;

&lt;p&gt;Around the same time, the same "run it two ways, look for a quiet disagreement" habit pointed the other direction. Auditing where TeaVM and the JVM diverge, I hit a date case they split on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;YEAR = 2002, WEEK_OF_MONTH = 2   (America/New_York, en_US)
JVM:   Sun Jan 06 2002
TeaVM: Sat Jan 12 2002
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time my code was fine. The bug was in TeaVM's reimplementation of Java's &lt;code&gt;GregorianCalendar&lt;/code&gt;. One line used &lt;code&gt;days - 2&lt;/code&gt; where every neighbouring branch, and the Apache Harmony code it was ported from, used &lt;code&gt;days - 3&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-days += (fields[WEEK_OF_MONTH] - 1) * 7 + mod7(skew + dayOfWeek - (days - 2)) - skew;
&lt;/span&gt;&lt;span class="gi"&gt;+days += (fields[WEEK_OF_MONTH] - 1) * 7 + mod7(skew + dayOfWeek - (days - 3)) - skew;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One character. The reason no test had caught it: the suite already had four assertions that reach that exact line, commented out since 2015. My change re-enabled them instead of adding new ones. They fail on the old code and pass on the fix, which is the cleanest proof I could ask for that the fix is real and nothing else moved.&lt;/p&gt;

&lt;p&gt;It &lt;a href="https://github.com/konsoletyper/teavm/pull/1213" rel="noopener noreferrer"&gt;merged into TeaVM&lt;/a&gt; on 2026-07-17 and closed a dormant issue. My whole contribution was a &lt;code&gt;2&lt;/code&gt; to a &lt;code&gt;3&lt;/code&gt; and un-commenting eleven-year-old assertions.&lt;/p&gt;

&lt;h2&gt;
  
  
  That's the whole thing
&lt;/h2&gt;

&lt;p&gt;No cleverer reviewer would have found the first bug by reading the code, because the code looked fine and the tests were green. A second runtime found it by disagreeing. My job was the part a machine can't do: read a "0 vs 301" and decide which side was lying, and why.&lt;/p&gt;

&lt;p&gt;If there's a transferable idea here it's just this: don't keep one source of truth for logic you can't fully check by hand. Run it two ways and treat every disagreement as a bug until you've proven which side it lives on. Sometimes it's yours. Once, it was the compiler's.&lt;/p&gt;

&lt;p&gt;Both examples are runnable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/egnaro9/match3-engine" rel="noopener noreferrer"&gt;&lt;code&gt;match3-engine&lt;/code&gt;&lt;/a&gt; — the Java engine, 59 tests, playable in-browser via TeaVM.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://egnaro9.github.io/evals-differential-oracle/" rel="noopener noreferrer"&gt;&lt;code&gt;evals-differential-oracle&lt;/code&gt;&lt;/a&gt; — a tiny browser demo of the same idea: the same match-3 rule written twice, fuzzed against each other over thousands of boards, plus a deliberately-broken version both nets catch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks to Ryan for the nudge. The last post told you I test things. This one showed you one.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>java</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Distinguishing wrong from absent</title>
      <dc:creator>Erik Hill</dc:creator>
      <pubDate>Tue, 21 Jul 2026 19:42:32 +0000</pubDate>
      <link>https://dev.to/agentdev9/distinguishing-wrong-from-absent-57ep</link>
      <guid>https://dev.to/agentdev9/distinguishing-wrong-from-absent-57ep</guid>
      <description>&lt;p&gt;model-drift grades models weekly on a frozen suite with an exact-match grader — no LLM judge, so a score change is real. That design has a sharp edge: a call that returns no valid answer scores identically to a wrong one. A refusal, a max_tokens truncation, a timeout, a parser failure on a quietly-changed schema — all land as zero, indistinguishable from the capability collapse the board exists to catch.&lt;/p&gt;

&lt;p&gt;I hit the catchable version: a model appeared to drop from 69% to 3% overnight. The run log showed a 429 on 34 of 35 calls — a rate limit scored as a regression. Rate limits are catchable only because they leave a status code; refusals and truncations don't.&lt;/p&gt;

&lt;p&gt;The board excludes by aggregate reliability: it drops a run's accuracy point only when that run's reliability falls below 50% — a catastrophic-infra floor, never a single failed call. That restraint matters because failures aren't missing-at-random: the long, hard prompts are the ones that hit token caps and timeouts, so a blanket drop-every-failure rule would exclude failures that correlate with difficulty and inflate accuracy on the discriminating tasks. An eval board has to tell a wrong answer from an absent one; miss that and you're scoring the provider's uptime, not the model.&lt;/p&gt;

&lt;p&gt;The design was already exclude-by-class, not blanket. Code: github.com/egnaro9/model-drift&lt;/p&gt;

&lt;p&gt;Writing the methodology in public is how it gets read this closely.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>llm</category>
      <category>ai</category>
      <category>testing</category>
    </item>
    <item>
      <title>I built agentic AI that checks its own work — a 90-second tour</title>
      <dc:creator>Erik Hill</dc:creator>
      <pubDate>Tue, 21 Jul 2026 04:19:21 +0000</pubDate>
      <link>https://dev.to/agentdev9/i-built-agentic-ai-that-checks-its-own-work-a-90-second-tour-4e63</link>
      <guid>https://dev.to/agentdev9/i-built-agentic-ai-that-checks-its-own-work-a-90-second-tour-4e63</guid>
      <description></description>
    </item>
    <item>
      <title>I built an AI dev harness that isn't allowed to trust itself</title>
      <dc:creator>Erik Hill</dc:creator>
      <pubDate>Mon, 20 Jul 2026 02:12:53 +0000</pubDate>
      <link>https://dev.to/agentdev9/i-built-an-ai-dev-harness-that-isnt-allowed-to-trust-itself-53mh</link>
      <guid>https://dev.to/agentdev9/i-built-an-ai-dev-harness-that-isnt-allowed-to-trust-itself-53mh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Scope note (read first): this describes a system I built and operate to develop an &lt;strong&gt;unannounced game&lt;/strong&gt;. The game's identity, mechanics, and assets are withheld, and so are the harness's tuned prompts, gate implementations, and internal failure specifics. What's shown here is the &lt;strong&gt;method and the evidence discipline&lt;/strong&gt; — the transferable part.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Over about four months (spring–summer 2026) I built and operated, solo, an operator-supervised multi-agent development harness that builds a real, shipping product. Its defining property isn't speed — it's that &lt;strong&gt;nothing an agent produces closes without machine-checkable proof, and no irreversible action happens without a human.&lt;/strong&gt; This is a case study of the system and the evidence trail it leaves.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;LLM coding agents are fast and unreliable. The engineering problem isn't getting output — it's trusting it. So the harness is built around one rule: &lt;strong&gt;an agent's work is unverified until a gate proves it.&lt;/strong&gt; Coordination is automated; consequences are gated. And it's built to &lt;em&gt;ship&lt;/em&gt;, not to gold-plate: every gate exists so I can move fast without shipping something broken — verification in service of velocity, not instead of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture (concept level)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A five-role loop:&lt;/strong&gt; Strategy → Execution → Critic → Eval → Ops. Judgment roles (Strategy, Critic, Eval) run on stronger models; execution roles on cheaper ones — cost follows the difficulty of the decision, not a flat default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A manager / orchestration layer.&lt;/strong&gt; Above the execution agents sits one orchestration role that I direct — it plans each unit of work, routes it to the right role and model, and holds the system's state between steps. I designed the roles, the gates, and the routing; the harness runs them. I'm not outside the loop supervising a black box — I'm the system's judgment and authority, and the manager is the layer that extends that across many parallel agents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A cold, independent critic gate.&lt;/strong&gt; Before a consequential change closes, it's reviewed by a Critic running on a &lt;em&gt;fresh, zero-context&lt;/em&gt; session — a different strong model with no memory of how the code was written — so it reviews the work itself, not the author's rationale for it. It can send the change back for rework. A self-review rubber-stamps; a cold critic catches what the author already talked themselves past.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A human-in-the-loop autonomy ladder:&lt;/strong&gt; the loop's &lt;em&gt;handoffs&lt;/em&gt; are automated — one role hands to the next without me — but every &lt;em&gt;irreversible&lt;/em&gt; act (deploying a build to a device, committing to git) stays behind an explicit human approval. Automate coordination; never automate the irreversible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A differential oracle for correctness:&lt;/strong&gt; the core logic is implemented twice and the two versions are fuzzed against each other. Where they disagree, one is wrong — no gold labels required.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The evidence discipline (the differentiator)
&lt;/h2&gt;

&lt;p&gt;Every closed unit of work leaves a durable, machine-checkable proof:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NO-PROOF-NO-CLOSE gate.&lt;/strong&gt; A work item cannot close until an automated check confirms its proof exists on disk. The loop physically cannot skip it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provenance-bound proof.&lt;/strong&gt; On-device validation screenshots are sanitized (sensitive regions blacked out), and provenance manifests bind images to the exact git SHA, screen dimensions, and redaction method that produced them — so an artifact traces back to the commit it proves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human-gated checkpoints.&lt;/strong&gt; Each checkpoint records scoped git staging (explicit paths only), a commit/SHA trail across the repos it touches, an artifact-registry audit, and an explicit operator approval.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Periodic self-evaluation.&lt;/strong&gt; An independent evaluation role produces a numeric health score with a delta versus the prior period and a failure taxonomy; regressions feed a failure registry that drives fixes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The testing oracle
&lt;/h2&gt;

&lt;p&gt;The product's core logic is held to &lt;strong&gt;property-based invariant tests&lt;/strong&gt; — generated inputs are thrown at the engine and a set of invariants must hold for every one (e.g. a detector must agree with an independent full re-scan, and detection must be side-effect-free). The suite runs against the authoritative implementation, so an invariant is &lt;em&gt;enforced on the logic&lt;/em&gt;, not asserted in prose (last run: zero failures). As a standalone, fully public demonstration of the same technique, my &lt;a href="https://github.com/egnaro9/match3-engine" rel="noopener noreferrer"&gt;match3-engine&lt;/a&gt; repo carries 16 jqwik property invariants over random inputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operating record (Apr–Jul 2026, from the on-disk archive)
&lt;/h2&gt;

&lt;p&gt;~200 completed work-arcs · ~190 human-gated checkpoints · 74 independent critic reviews · 13 periodic self-evaluations · a growing failure registry with per-item root-cause fixes · a ~200-file sanitized proof archive with ~90 provenance manifests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifiable outcomes (all public)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An arcade game — &lt;strong&gt;Tap Dodge Rush&lt;/strong&gt;, under SeraphLight Studios — shipped end-to-end to &lt;a href="https://play.google.com/store/apps/details?id=com.seraphlight.tapdodgerush" rel="noopener noreferrer"&gt;Google Play&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A one-character bug fix &lt;strong&gt;merged upstream into TeaVM&lt;/strong&gt; (the Java-to-JavaScript compiler), closing a long-dormant issue.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;live public model-drift board&lt;/strong&gt; grading 16 LLMs daily on a frozen, deterministically-graded suite — no LLM-as-judge, so a score change is real.&lt;/li&gt;
&lt;li&gt;Ten public repos, including a differential-oracle testing project and a Model Context Protocol server built from the spec.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'd bring to a team
&lt;/h2&gt;

&lt;p&gt;Treat AI output as unverified until proven. Build the gate before the feature. Make failures loud, not silent. Keep a human on the irreversible path. The discipline transfers to any codebase — the harness just made me practice it a few hundred times.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Full architecture case study &amp;amp; repo: &lt;a href="https://github.com/egnaro9/agentic-dev-harness" rel="noopener noreferrer"&gt;github.com/egnaro9/agentic-dev-harness&lt;/a&gt; · Portfolio: &lt;a href="https://egnaro9.github.io" rel="noopener noreferrer"&gt;egnaro9.github.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>programming</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Set membership is not pairing: a property test that was green for the exact bug it was written to catch</title>
      <dc:creator>Erik Hill</dc:creator>
      <pubDate>Sun, 19 Jul 2026 05:57:09 +0000</pubDate>
      <link>https://dev.to/agentdev9/set-membership-is-not-pairing-a-property-test-that-was-green-for-the-exact-bug-it-was-written-to-3g6b</link>
      <guid>https://dev.to/agentdev9/set-membership-is-not-pairing-a-property-test-that-was-green-for-the-exact-bug-it-was-written-to-3g6b</guid>
      <description>&lt;p&gt;I have a small board that tracks 16 LLMs across 5 labs on a frozen 35-task suite. The charts are the easy part. The paragraph above them is the part I did not trust, because I had written that kind of paragraph by hand twice and both times it went false — true when I typed it, falsified by a later run.&lt;/p&gt;

&lt;p&gt;So I stopped typing it. The paragraph is generated now. Each sentence is a claim: a predicate over the current numbers plus a renderer, returning a string or &lt;code&gt;None&lt;/code&gt;. A claim whose predicate stops holding is dropped, not reworded. The generator can be silent. It can't be wrong.&lt;/p&gt;

&lt;p&gt;That second sentence is the part I got wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sentence that lied
&lt;/h2&gt;

&lt;p&gt;One claim compares a lab's cheaper models against that same lab's flagship. On the live board it produced this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Google's cheaper tiers match or beat its own flagship — Gemini 3.5 Flash at 100% and Gemini 3.1 Flash-Lite at 95%, against Gemini 3.1 Pro at 95%&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Correct. But the first version of that renderer collected the winners, took the highest score among them, and printed one number for the group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;_names&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;winners&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;_pct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;winners&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which reads: &lt;em&gt;Gemini 3.5 Flash and Gemini 3.1 Flash-Lite at 100%&lt;/em&gt;. Flash-Lite scored 95%. The sentence put a real number next to a model that did not earn it.&lt;/p&gt;

&lt;p&gt;Naming several models and then printing one number is not a formatting choice. It is a claim about all of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test that should have caught it
&lt;/h2&gt;

&lt;p&gt;I had written a property test for exactly this class of bug. It asserted that every percentage appearing in the prose was a score some model actually got:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;real&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;acc&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;series&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
&lt;span class="n"&gt;printed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\d+%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;printed&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;real&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was green.&lt;/p&gt;

&lt;p&gt;It is green &lt;em&gt;because the assertion is true&lt;/em&gt;. 100% was a real score — Gemini 3.5 Flash got it. The test asked "does this number exist in the data?" The bug was "is this number attached to the right model?" Set membership where pairing was required.&lt;/p&gt;

&lt;p&gt;The replacement checks the pairing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;_PAIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b([AB] (?:Big|Small|Tiny)) at (\d+)%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;printed&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_PAIR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;printed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The fixture was wrong too
&lt;/h2&gt;

&lt;p&gt;This is the part I would have missed if I had stopped at the test.&lt;/p&gt;

&lt;p&gt;A fixture with one cheap model per lab &lt;strong&gt;cannot reach the failing state&lt;/strong&gt; — with a single cheap model per group, a group is always unanimous, so a renderer printing &lt;code&gt;rows[0]&lt;/code&gt;'s score is always right. That was the shape I started from, and I fixed it before the first commit, so take this part on my word rather than on the history. The property test was not weak because of its assertion alone. It was weak because the data it ran against could not produce a disagreement.&lt;/p&gt;

&lt;p&gt;The fixture now gives LabA two cheaper tiers under one flagship, which is the shape Google actually has (Pro / Flash / Flash-Lite):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;REGISTRY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lab-a:big&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A Big&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;group&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LabA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flagship&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lab-a:small&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A Small&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;group&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LabA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lab-a:tiny&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A Tiny&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;group&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LabA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nano&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the baseline board puts those two cheap tiers at &lt;em&gt;different&lt;/em&gt; scores, both at or above their flagship.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it cost
&lt;/h2&gt;

&lt;p&gt;Eight mutants run against the new guards. &lt;strong&gt;Three survived the first time&lt;/strong&gt; — the tie guard, the single-flagship rule, and a float-truncation fix — and each one needed a test written before it was really covered. The repair was less covered than it felt.&lt;/p&gt;

&lt;p&gt;The float one is my favourite, because it is a bug in the code whose entire job was to never overstate a number. Percentages truncate rather than round, so a model on 99.6% can never print as "100%" — a perfect score it did not get, in the one place a reader checks hardest. But:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The epsilon is not decoration: 0.58 * 100 is 57.99999999999999 in binary
# floating point, so a bare floor prints a measured 58% as "57%". [...]
&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1e-9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A guard against overstating had started understating instead. That is still wrong prose about real data — just wrong in the flattering direction, which is the direction you don't check.&lt;/p&gt;

&lt;p&gt;The suite is 125 tests now, stdlib only, 0.04s. It was 91 at the fix commit, 74 when the generator first landed, and 22 before the generator existed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Ask what state would make this assertion fail, and name it out loud.&lt;/strong&gt; I could not have named it for the set test — not because I hadn't thought about it, but because the fixture made it unreachable and I never noticed that the two facts were connected. An assertion whose failing state you cannot describe is decoration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mutate the code, not just the inputs.&lt;/strong&gt; Property tests over generated data feel rigorous enough to skip this. Three of eight mutants survived my new, careful, adversarially-reviewed guards. That is the number that convinced me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assume the repair is uncovered.&lt;/strong&gt; The fix arrives believing in itself. It has no tests yet.&lt;/p&gt;

&lt;p&gt;And one more, which I found while writing this. The README badge for that repo says &lt;code&gt;tests-91&lt;/code&gt;. It is 125. Four screens further down, the quickstart says &lt;code&gt;# 22 tests&lt;/code&gt; — a badge and its own README disagreeing, both hand-typed, both true on the day they were written and false since. That is the identical failure mode the generator exists to eliminate, sitting in the repo that hosts the generator.&lt;/p&gt;

&lt;p&gt;The verification was broken, not the code. It usually is.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/egnaro9/model-drift" rel="noopener noreferrer"&gt;github.com/egnaro9/model-drift&lt;/a&gt; — the broken test and the real one are both in &lt;code&gt;tests/test_narrative.py&lt;/code&gt;.&lt;br&gt;
Portfolio: &lt;a href="https://egnaro9.github.io" rel="noopener noreferrer"&gt;egnaro9.github.io&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>python</category>
      <category>softwareengineering</category>
      <category>llm</category>
    </item>
    <item>
      <title>The Cold-Context Critic: A Reviewer That Never Remembers Writing the Code</title>
      <dc:creator>Erik Hill</dc:creator>
      <pubDate>Thu, 16 Jul 2026 01:22:41 +0000</pubDate>
      <link>https://dev.to/agentdev9/the-cold-context-critic-a-reviewer-that-never-remembers-writing-the-code-40fp</link>
      <guid>https://dev.to/agentdev9/the-cold-context-critic-a-reviewer-that-never-remembers-writing-the-code-40fp</guid>
      <description>&lt;p&gt;Anyone who has written code and then reviewed it minutes later knows the feeling: the diff looks fine. Of course it does. The person reading it is the same person who just talked themselves into every line of it. The naming made sense because the intent is still warm in memory. The shortcut felt justified because the reason for it is still sitting in working memory, uninspected.&lt;/p&gt;

&lt;p&gt;That is the problem the cold-context critic is built to remove.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea
&lt;/h2&gt;

&lt;p&gt;In the harness I built to develop a game, every change passes through a review step performed by a &lt;strong&gt;fresh model instance that has no memory of writing the code&lt;/strong&gt;. It did not plan the change. It did not argue for the approach. It did not feel the small relief of getting something to finally pass. It receives the diff and evaluates it on its merits, the way a stranger would.&lt;/p&gt;

&lt;p&gt;The intuition is old and boring, which is part of why I trust it: an author rationalizes; a cold reviewer does not. A model — or a person — that just produced the work carries a bias toward the choices it already made. Strip the memory of making them, and the same mistakes stop being self-evidently correct. The reviewer has nothing to defend. It can only look at what is actually there.&lt;/p&gt;

&lt;p&gt;I want to be precise about the claim, because it is easy to oversell. This is a &lt;strong&gt;discipline&lt;/strong&gt;, an architecture choice. I am not going to tell you it caught a specific dramatic production bug, because I am not going to invent an incident to sell an idea. What I can say is the mechanism: cold review catches the class of mistakes an author talks itself into — the plausible-looking shortcut, the assumption never stated out loud, the edge case that "obviously" can't happen. Self-review is structurally bad at exactly those, because the author already accepted them once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it fits
&lt;/h2&gt;

&lt;p&gt;The cold critic is one layer, not the whole story. It sits alongside a few other pieces of the same distrust:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per-role model routing.&lt;/strong&gt; Stronger models run the judgment stages — planning, critique, evaluation — and cheaper ones handle routine execution. The reviewer isn't grading its own homework, and it isn't the same weight class picked for speed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A differential oracle.&lt;/strong&gt; Core game logic is implemented twice and held to shared invariants. When the two implementations disagree, at least one is wrong, and I find out without having to trust either.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A human approval gate on every irreversible action.&lt;/strong&gt; Deploys and commits do not happen because a model decided they should. They happen because a person approves them. That gate never moves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think about the whole thing on an autonomy ladder, L0 through L4 — from fully hands-on toward more independence. The cold critic is what makes climbing that ladder defensible. More autonomy is only safe if the verification underneath it does not depend on the thing being verified vouching for itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup, honestly
&lt;/h2&gt;

&lt;p&gt;Concretely: the critic is a separate invocation with a clean context window. It does not inherit the conversation that produced the change. It gets the diff and the standard it is being held to, and it reports back before a human sees the change. That's the shape of it. The value isn't a clever prompt — it's the missing memory. The setup exists to guarantee the reviewer never accumulated a reason to like the code.&lt;/p&gt;

&lt;p&gt;That's also why I keep the human gate immovable. The cold critic is a good reader, not an authority. It can flag; it doesn't get to ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  An honest note
&lt;/h2&gt;

&lt;p&gt;I'm about five to six months into this, career-changing from roughly six years in professional kitchens, no CS degree — I learned through freeCodeCamp and by building. The game is real and published to Google Play, but it is not serving millions, and I'm not running production RAG or fine-tuning pipelines. I'd rather tell you what this is than dress it up.&lt;/p&gt;

&lt;p&gt;So the honest framing is this: I don't have the scars yet that teach senior engineers where the bodies are buried. What I can do is build the distrust in as a habit from the start — assume the author is biased, including when the author is me or a model I'm running, and make something with no stake in the answer do the checking.&lt;/p&gt;

&lt;p&gt;If you want to see the same idea in code you can actually run, the differential oracle is public: &lt;a href="https://github.com/egnaro9/evals-differential-oracle" rel="noopener noreferrer"&gt;github.com/egnaro9/evals-differential-oracle&lt;/a&gt;. It's the same move as the cold critic — distrust your own output — implemented as two independent versions of the logic checked against each other, plus invariant tests over thousands of random boards, with a deliberately buggy implementation included to prove the checks bite. Clone it, run &lt;code&gt;pytest&lt;/code&gt;, watch it catch the planted bug.&lt;/p&gt;

&lt;p&gt;More at &lt;a href="https://egnaro9.github.io" rel="noopener noreferrer"&gt;egnaro9.github.io&lt;/a&gt; and &lt;a href="https://github.com/egnaro9" rel="noopener noreferrer"&gt;github.com/egnaro9&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>codereview</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>A differential oracle: making agentic code prove its own correctness</title>
      <dc:creator>Erik Hill</dc:creator>
      <pubDate>Wed, 15 Jul 2026 02:58:44 +0000</pubDate>
      <link>https://dev.to/agentdev9/a-differential-oracle-making-agentic-code-prove-its-own-correctness-4ce7</link>
      <guid>https://dev.to/agentdev9/a-differential-oracle-making-agentic-code-prove-its-own-correctness-4ce7</guid>
      <description>&lt;p&gt;Six months ago I was running a kitchen. I taught myself to build agentic systems, and the thing I'm proudest of is the part nobody demos: the evaluation layer.&lt;/p&gt;

&lt;p&gt;Coding agents are easy to demo and hard to &lt;em&gt;trust&lt;/em&gt;. The moment an agent touches a real codebase — deploys, commits, user-facing changes — you need what any production system needs: review, tests, and a way to catch your own regressions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: correctness you can't hand-check
&lt;/h2&gt;

&lt;p&gt;I build a match-3 game. Match-3 resolution has thousands of edge cases — cascades, chain reactions, special-piece rules — where the "right answer" isn't obvious and you can't check them all by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The differential oracle
&lt;/h2&gt;

&lt;p&gt;So instead of trusting one implementation, I use two — written independently. The same game logic runs in a &lt;strong&gt;React build&lt;/strong&gt; and a &lt;strong&gt;native Java engine&lt;/strong&gt;, and logic-invariant tests hold both to the same rules. If the two ever disagree on a board state, one of them is wrong. Agreement between two independently-built systems is a far stronger signal than either one passing its own tests — it has caught real bugs I'd never have found by playing.&lt;/p&gt;

&lt;p&gt;Runnable repo: &lt;a href="https://github.com/egnaro9/evals-differential-oracle" rel="noopener noreferrer"&gt;https://github.com/egnaro9/evals-differential-oracle&lt;/a&gt; — two implementations + invariant tests, plus a deliberately-buggy version that both nets catch. &lt;code&gt;pytest&lt;/code&gt; passes over ~9k random boards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Invariants as a second net
&lt;/h2&gt;

&lt;p&gt;The rules themselves are also tests — e.g. &lt;em&gt;a special gem is generated only by a direct match of 4+, never a 3.&lt;/em&gt; When I planted a bug that awarded one for a 3-run, the differential oracle disagreed &lt;strong&gt;and&lt;/strong&gt; the invariant checker flagged it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger system
&lt;/h2&gt;

&lt;p&gt;The oracle sits inside an autonomous dev harness: a self-reviewing loop (Strategy → Execution → Critic → Evaluation → Ops) with a cold, independent critic, per-role model routing, and a human-in-the-loop autonomy ladder that automates the launches but never the approval on anything irreversible.&lt;/p&gt;

&lt;p&gt;Write-up: &lt;a href="https://github.com/egnaro9/agentic-dev-harness" rel="noopener noreferrer"&gt;https://github.com/egnaro9/agentic-dev-harness&lt;/a&gt; · portfolio: &lt;a href="https://egnaro9.github.io" rel="noopener noreferrer"&gt;https://egnaro9.github.io&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I take from it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Evals and oracles are the hard, valuable part; getting a demo to work once is not the job.&lt;/li&gt;
&lt;li&gt;Two independent implementations that must agree beats one with a big test suite.&lt;/li&gt;
&lt;li&gt;Be honest about what's objectively measurable vs. what needs human judgment — don't fake a metric for the second.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm a self-taught engineer looking for a remote (US) role in agentic / AI / evals engineering. If this is what your team cares about, I'd love to talk.&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
  </channel>
</rss>
