<?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: zxpmail</title>
    <description>The latest articles on DEV Community by zxpmail (@zxpmail).</description>
    <link>https://dev.to/zxpmail</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%2F3971221%2Ffda4417c-010a-42c4-9008-b16ca30960cf.png</url>
      <title>DEV Community: zxpmail</title>
      <link>https://dev.to/zxpmail</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zxpmail"/>
    <language>en</language>
    <item>
      <title>The Channel Gap: Why Your LLM Judge is Blind in One Eye</title>
      <dc:creator>zxpmail</dc:creator>
      <pubDate>Thu, 06 Aug 2026 09:16:41 +0000</pubDate>
      <link>https://dev.to/zxpmail/the-channel-gap-why-your-llm-judge-is-blind-in-one-eye-35ne</link>
      <guid>https://dev.to/zxpmail/the-channel-gap-why-your-llm-judge-is-blind-in-one-eye-35ne</guid>
      <description>&lt;h1&gt;
  
  
  The Channel Gap: Why Your LLM Judge is Blind in One Eye
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Agent Determinism Illusions (Part 8)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Part 6 ended with a functioning layered pipeline built from community corrections. Part 7 then fixed the escalation trigger: divergence alone routes humans to the safe-ambiguous set and auto-passes the confidently-wrong set. L0/L1 filter deterministically, L2 handles semantic residual, L3 detects divergence — plus class tripwires for unanimous misses. It's better than what came before. But it still has a fundamental design flaw that I only recognized after reading the tool that implements the &lt;em&gt;opposite&lt;/em&gt; design choice.&lt;/p&gt;

&lt;p&gt;This article compares two competing designs for the verification layer — one reading text through an LLM, one reading the filesystem through deterministic checks — and shows why neither works alone, and why a combined approach narrows the gap without closing it: every named evasion becomes a deterministic catch, while the unenumerated rest stays UNCLEAR and routes to human instead of silently passing.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Comment That Changed the Frame
&lt;/h2&gt;

&lt;p&gt;After the series went live, René Zander (&lt;a href="https://dev.to/reneza/comment/3akon"&gt;@reneza on dev.to&lt;/a&gt;) left this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Lexical overlap, a temperature-0 judge, and a phase gate are all trying to make a probabilistic judgment call ('is this done', 'is this a new task') return a binary fact, and dressing it in code does not change what it is."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They were saying that every "deterministic fix" in the series was a &lt;strong&gt;deterministic wrapper on a semantic decision&lt;/strong&gt;. Vocabulary overlap thresholds, temperature-0 evaluation, Phase Gate formalism — all of them put a probabilistic judgment inside a code structure that looked deterministic, but the underlying decision was still a model output. The code didn't make the judgment more reliable; it made the unreliability harder to see.&lt;/p&gt;

&lt;p&gt;The commenter didn't name it, but what they described is the &lt;strong&gt;Data Processing Inequality&lt;/strong&gt;: when the evaluator shares the same communication channel as the producer (both read/write text), information available to the evaluator is a &lt;em&gt;subset&lt;/em&gt; of what the producer output. If the deviation doesn't appear in the text, the evaluator — human or LLM — cannot detect it.&lt;/p&gt;

&lt;p&gt;They had created a tool to implement the alternative: skillgate.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.1 The alternative: skillgate
&lt;/h3&gt;

&lt;p&gt;Skillgate (&lt;code&gt;@reneza/skillgate&lt;/code&gt; on npm) is a deterministic, model-independent gate that checks the &lt;strong&gt;filesystem&lt;/strong&gt; instead of reading the model's output. Its thesis: don't ask whether the task was done — ask whether the evidence exists.&lt;/p&gt;

&lt;p&gt;The implementation is minimal — a compact TypeScript package with two external dependencies (a YAML parser and a glob matcher). Its gate types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;file-exists&lt;/code&gt; — path exists&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;evidence&lt;/code&gt; — file exists AND non-empty&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;not-empty&lt;/code&gt; — directory at &lt;code&gt;path&lt;/code&gt; contains at least &lt;code&gt;min&lt;/code&gt; entries&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;absent&lt;/code&gt; — regex pattern does NOT appear in matched files&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;file-contains&lt;/code&gt; — file matches regex&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;command&lt;/code&gt; — shell command exits 0&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;instruction-sync&lt;/code&gt; — instruction files haven't drifted (LCS similarity ≥ 0.95)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note what's missing: &lt;strong&gt;no LLM judge&lt;/strong&gt;. Every gate is a pure function over the filesystem. Same disk state, same verdict. Always.&lt;/p&gt;

&lt;p&gt;The enforcement mechanism is a &lt;code&gt;PreToolUse&lt;/code&gt; hook in Claude Code that intercepts &lt;code&gt;git commit&lt;/code&gt;, &lt;code&gt;git push&lt;/code&gt;, &lt;code&gt;npm publish&lt;/code&gt;, runs the gates, and exits with code 2 (blocking the tool call) if any gate fails. The agent sees the failure messages and can attempt fixes, but it cannot override the gate — the gate lives outside the model's control flow.&lt;/p&gt;

&lt;p&gt;Three deployment tiers, each enforcing the same &lt;code&gt;.skillgate/done.yaml&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;th&gt;Who controls it&lt;/th&gt;
&lt;th&gt;Bypassable?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Soft&lt;/td&gt;
&lt;td&gt;Claude Code PreToolUse hook&lt;/td&gt;
&lt;td&gt;Project config&lt;/td&gt;
&lt;td&gt;Model can modify the hook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Soft&lt;/td&gt;
&lt;td&gt;pre-commit git hook&lt;/td&gt;
&lt;td&gt;Local repo&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git commit --no-verify&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hard&lt;/td&gt;
&lt;td&gt;pre-receive hook on self-hosted VPS&lt;/td&gt;
&lt;td&gt;Server admin&lt;/td&gt;
&lt;td&gt;Agent cannot log into the box&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The hard layer is the architectural innovation: a pre-receive hook that materializes the pushed tree, runs &lt;code&gt;skillgate check&lt;/code&gt; against the authoritative &lt;code&gt;.skillgate/done.yaml&lt;/code&gt; (which lives on the server, not in the repo), and &lt;strong&gt;rejects the push&lt;/strong&gt; if any gate fails. The agent cannot git-push its way past the gate because the gate runs after &lt;code&gt;git push&lt;/code&gt; completes on the server.&lt;/p&gt;

&lt;p&gt;This is not a theoretical design. It shipped as v0.5.0 and the source is readable on GitHub.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 What skillgate does differently
&lt;/h3&gt;

&lt;p&gt;ReqForge's &lt;code&gt;forge-verify&lt;/code&gt; and skillgate solve the same problem — "is the work actually done?" — through completely different channels:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;forge-verify (L0-L3)&lt;/th&gt;
&lt;th&gt;skillgate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Channel&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Text&lt;/strong&gt; — reads model output&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Filesystem&lt;/strong&gt; — checks artifacts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evaluator&lt;/td&gt;
&lt;td&gt;Code + LLM&lt;/td&gt;
&lt;td&gt;Code only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DPI bound&lt;/td&gt;
&lt;td&gt;Yes — evaluates text through text&lt;/td&gt;
&lt;td&gt;No — evaluates actions through artifacts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Judge variance&lt;/td&gt;
&lt;td&gt;LLM temperature, model version, prompt&lt;/td&gt;
&lt;td&gt;Zero — pure function&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scope&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;What&lt;/strong&gt; was produced&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;That&lt;/strong&gt; it was produced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enforcement&lt;/td&gt;
&lt;td&gt;Stop-time hook (soft)&lt;/td&gt;
&lt;td&gt;Pre-receive hook (hard)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The contrast exposes the question I should have asked in Part 6 but didn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If we have two completely different channels for evaluating compliance, what does each one catch that the other misses — and what does both together still miss?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question drove two rounds of experiments.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Phase 1: Channel Comparison
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2.1 Design
&lt;/h3&gt;

&lt;p&gt;12 scenarios where an agent claims to have completed a task. Each scenario has:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Agent output text&lt;/strong&gt; — what the agent says it did&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence files&lt;/strong&gt; — what artifact files exist (or don't)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ground truth&lt;/strong&gt; — whether the agent actually complied&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two evaluators, each limited to its own channel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Channel A (in-channel):&lt;/strong&gt; LLM (deepseek-v4-flash, temperature 0) reads the agent's output text + any evidence file content. Decides: "is the work done?" N=3 votes per scenario, majority decides.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Channel B (out-of-band):&lt;/strong&gt; File system check — for each required evidence file, does it exist and is it non-empty? No LLM involved. Pure function.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 12 scenarios cover: full compliance, missing evidence, empty evidence, fabricated evidence, partial evidence, stale evidence, semantic compliance gaps, and agent rationalization.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 Results
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Channel A (LLM text)&lt;/th&gt;
&lt;th&gt;Channel B (filesystem)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Overall accuracy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;83.3%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;75.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Detection rate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;77.8%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;66.7%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;False acceptance (FA)&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;False rejection (FR)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each channel has &lt;strong&gt;zero false rejections&lt;/strong&gt; — when they flag something, it's real. The difference is in what they miss.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Channel A caught that Channel B missed:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fabricated evidence (SC04):&lt;/strong&gt; Evidence files exist with content like "All tests passed!" but no real test output. Channel A read the content and rejected it as too vague. Channel B said "file exists and non-empty → PASS."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale evidence (SC07):&lt;/strong&gt; Evidence files from yesterday claiming "cleaned 3 files" while the agent's log claims "cleaned 5 files today." Channel A noticed the date mismatch. Channel B said "file exists → PASS."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Channel B caught that Channel A missed:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Review-only (SC11):&lt;/strong&gt; Agent wrote a code review but didn't run tests. Its self-review justified: "No test run needed — change is minimal." Channel A found this reasoning persuasive (2/3 votes PASS). Channel B checked: test-output.txt absent → REJECT.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The most revealing scenario — semantic gap (SC10):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent's task: "Implement rate limiting middleware with key write-invalidation."&lt;/li&gt;
&lt;li&gt;Agent produced: A RateLimiter class with IP/User level limiting, Redis storage, 24 passing tests, 92% coverage. Everything looked complete.&lt;/li&gt;
&lt;li&gt;Not present: Write-invalidation — and the agent's self-review never mentions it. The review reads as a clean handoff: implemented the class, added the storage backend, tests pass, lint clean.&lt;/li&gt;
&lt;li&gt;Channel A: 3/3 PASS — confident the work was done.&lt;/li&gt;
&lt;li&gt;Channel B: PASS — all evidence files present and non-empty.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Both channels missed the same one: a structurally perfect output that doesn't meet the requirement.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 The blind spot map
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure mode&lt;/th&gt;
&lt;th&gt;Channel A (text)&lt;/th&gt;
&lt;th&gt;Channel B (filesystem)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Evidence missing&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Empty evidence&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fabricated content&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stale content&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Partial evidence&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent rationalization ("no test needed")&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Semantic gap (DPI bound)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;❌&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;❌&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Combined (A or B rejects) catches 8 of the 9 non-compliant scenarios (88.9% detection) and correctly passes all 3 compliant ones — 11/12 correct verdicts overall (91.7%). The only shared blind spot is the semantic gap — and that's provably undetectable from text or filesystem evidence alone.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Phase 2: Contract Verification
&lt;/h2&gt;

&lt;p&gt;The Phase 1 blind spot map says: to close the remaining gap, we need to check &lt;em&gt;what&lt;/em&gt; the evidence says, not just &lt;em&gt;that&lt;/em&gt; it exists. But how? Channel A (LLM reading text) already failed on that exact scenario.&lt;/p&gt;

&lt;p&gt;The answer: &lt;strong&gt;don't ask the LLM to judge the whole picture. Decompose "done" into individual requirements and check each one separately.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 Design
&lt;/h3&gt;

&lt;p&gt;7 scenarios, each gated by a 5-requirement contract — five share a rate-limiting middleware contract, the other two reuse the Phase 1 CI-pipeline baselines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REQ-1: IP-level rate limiting       → evidence: test-output.txt
REQ-2: User-level rate limiting     → evidence: test-output.txt  
REQ-3: Write-invalidation on write  → evidence: diff-review.md (review text)
REQ-4: Coverage ≥ 85%               → evidence: coverage-summary.txt
REQ-5: Lint 0 errors                → evidence: lint-report.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three evaluators:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;C0 (free-text LLM):&lt;/strong&gt; Same as Phase 1 Channel A — LLM reads everything and judges holistically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C1 (contract regex):&lt;/strong&gt; For each requirement, read the evidence file and regex match a pattern. No model. Zero cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C2 (per-requirement LLM):&lt;/strong&gt; For each requirement, ask the LLM: "Does this evidence prove requirement N?" Then check whether ALL pass. Cost: ~1s per requirement instead of ~1s for one holistic call.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.2 Results
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;C0 (free-text)&lt;/th&gt;
&lt;th&gt;C1 (contract regex)&lt;/th&gt;
&lt;th&gt;C2 (per-req LLM)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Overall accuracy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;42.9%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85.7%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85.7%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Detection rate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;80.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;False acceptance&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;False rejection&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-item accuracy&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;88.6%&lt;/td&gt;
&lt;td&gt;82.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The numbers tell a clear story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;C0 (free-text LLM) collapses on the hardest set.&lt;/strong&gt; 4 of 5 non-compliant scenarios were missed. The LLM read the agent's output, saw "tests pass, lint clean, coverage 92%," and declared it done — even when the core requirement (write-invalidation) was missing. Same mechanism as Phase 1's SC10: the LLM is doing an impression judgment, not a line-by-line audit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;C1 (contract regex) is surprisingly effective for a zero-cost solution.&lt;/strong&gt; 85.7% overall, 80% detection. The one failure is instructive: SC10a (the write-invalidation gap again). The diff-review.md says "keys expire via TTL, not write-invalidation" — and the regex &lt;code&gt;write.?invalidat&lt;/code&gt; matched the word "write-invalidation" in the negation. &lt;strong&gt;C1 has a "negation blind spot": it matches the mention of a requirement, not its satisfaction.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But C1 is &lt;strong&gt;excellent on numerical and format constraints:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern type&lt;/th&gt;
&lt;th&gt;Reliability&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Numerical comparison&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;`(8[5-9]\&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixed-format text&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;{% raw %}&lt;code&gt;0\s+errors&lt;/code&gt; for lint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Functional keywords&lt;/td&gt;
&lt;td&gt;⚠️&lt;/td&gt;
&lt;td&gt;Naming-sensitive (purgeCacheOnWrite vs write-invalidation)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Negated mentions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;❌&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;"not write-invalidation"&lt;/code&gt; matches &lt;code&gt;write.?invalidat&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;C2 (per-requirement LLM) achieves 100% detection rate — the only mechanism in either experiment to catch every non-compliant scenario.&lt;/strong&gt; It works because forcing the LLM to judge one requirement at a time prevents the "everything looks fine" gestalt from overriding the missing-item signal.&lt;/p&gt;

&lt;p&gt;On SC10a (the semantic gap), the per-requirement LLM correctly rejected REQ-3 — it noted the evidence explicitly describes TTL expiration instead of write-invalidation, so the requirement isn't satisfied.&lt;/p&gt;

&lt;p&gt;The same LLM in C0 mode on the same scenario judged the work done: tests pass, lint clean, coverage 92% — all requirements met.&lt;/p&gt;

&lt;p&gt;The difference isn't model capability. It's &lt;strong&gt;the same model, same temperature, different framing.&lt;/strong&gt; The holistic frame asks "is this good?" The per-item frame asks "is this specific thing proven?"&lt;/p&gt;

&lt;p&gt;C2's one false rejection (SC10c — which was genuinely compliant) was on REQ-3: the per-req LLM rejected write-invalidation because the diff-review.md "only mentions it, doesn't prove it with code." This is arguably correct behavior — a review file &lt;em&gt;shouldn't&lt;/em&gt; be sufficient evidence for code-level requirements. The false rejection exposed a contract design issue, not an evaluator issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 Contract regex failure pattern
&lt;/h3&gt;

&lt;p&gt;The C1 negation blind spot deserves deeper analysis because it mirrors the Data Processing Inequality at the regex level:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Regex&lt;/th&gt;
&lt;th&gt;Match?&lt;/th&gt;
&lt;th&gt;Correct?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"Keys expire via TTL, not &lt;strong&gt;write-invalidation&lt;/strong&gt;"&lt;/td&gt;
&lt;td&gt;&lt;code&gt;write.?invalidat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;YES&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ False pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Coverage: &lt;strong&gt;72.3%&lt;/strong&gt;"&lt;/td&gt;
&lt;td&gt;`(8[5-9]\&lt;/td&gt;
&lt;td&gt;90+)\%`&lt;/td&gt;
&lt;td&gt;NO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Implemented &lt;strong&gt;purgeCacheOnWrite&lt;/strong&gt;"&lt;/td&gt;
&lt;td&gt;&lt;code&gt;purge.*write&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;YES&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Correct pass&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The numerical constraint (&lt;code&gt;85%+&lt;/code&gt;) is immune to the negation problem because a number below threshold is factually wrong regardless of context. The keyword constraint (&lt;code&gt;write.?invalidat&lt;/code&gt;) is vulnerable because the regex can't tell the difference between "I implemented X" and "I didn't implement X."&lt;/p&gt;

&lt;p&gt;A regex constraint can be strengthened with negative lookahead — &lt;code&gt;(?!not.*)write.?invalidat&lt;/code&gt; — but this quickly becomes fragile and regex-specific. The practical fix is to route semantic requirements (where negation matters) to C2 (per-req LLM) and reserve C1 for numerical and format constraints.&lt;/p&gt;

&lt;p&gt;This makes C1 a &lt;strong&gt;ratchet on named evasions, not a closure&lt;/strong&gt;. Every pattern you write is one lie permanently caught — but each unenumerated semantic dimension (a fresh synonym, a reframed justification like "the cache converges via eventual-consistency guarantees without explicit invalidation") is an isomorphic gap C1 cannot see, because it lives in word-space and you didn't name those words. C2 widens the ratchet: it reads the same text the producer wrote, so it catches any deviation that &lt;em&gt;surfaces&lt;/em&gt; in the evidence. But C2 shares the text channel — a deviation the producer never puts into any evidence file is invisible to C2 as well.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Synthesis: The Evidence Gate Pipeline
&lt;/h2&gt;

&lt;p&gt;Putting Phase 1 and Phase 2 together produces a three-layer post-L1 pipeline that replaces the single L2 free-text LLM from Part 6:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                   output file
                        │
                    L0 → L0e → L1
                        │ pass
              ┌─────────▼──────────┐
              │  Evidence Gate     │  file exists + non-empty
              │  (deterministic)   │  ~0ms
              └─────────┬──────────┘
                        │ pass
              ┌─────────▼──────────┐
              │  C1 Contract Regex │  per-requirement regex
              │  (deterministic)   │  best for: numerical, format
              └─────────┬──────────┘
                   │          │
                UNCLEAR     ALL PASS
                   │          │
              ┌────▼───┐      │
              │ C2 Per- │      │
              │ Req LLM │      │
              │ ~1s/REQ │      │
              └────┬───┘      │
                   │          │
              ┌────▼────┐     │
              │   L3    ◄─────┘
              │ (shared)│
              └─────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.1 When to use each path
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;No contract defined (no &lt;code&gt;evidence_gates&lt;/code&gt; config):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Traditional L0 → L0e → L1 → L2 (free-text LLM) → L3&lt;/li&gt;
&lt;li&gt;Same as Part 6 architecture. Works when there's no predefined set of verifiable requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Contract defined (with &lt;code&gt;evidence_gates&lt;/code&gt;):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;L0 → L0e → L1 → Evidence Gate → C1 → (C2 if needed) → L3&lt;/li&gt;
&lt;li&gt;C1 handles numerical and format constraints deterministically (zero cost)&lt;/li&gt;
&lt;li&gt;C2 handles semantic constraints per-requirement (higher cost, but higher detection rate)&lt;/li&gt;
&lt;li&gt;The contract defines requirements with a &lt;code&gt;type&lt;/code&gt; field: &lt;code&gt;"regex"&lt;/code&gt; for C1, &lt;code&gt;"llm"&lt;/code&gt; for C2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The combined channel performance:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Detection rate&lt;/th&gt;
&lt;th&gt;Cost per scenario&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C0 free-text LLM (old L2)&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;td&gt;~1s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evidence Gate (Phase 1 Channel B)&lt;/td&gt;
&lt;td&gt;66.7%&lt;/td&gt;
&lt;td&gt;~0ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C1 contract regex&lt;/td&gt;
&lt;td&gt;80.0%&lt;/td&gt;
&lt;td&gt;~0ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C2 per-req LLM&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~1s × N_req&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Combined A+B (Phase 1, any reject)&lt;/td&gt;
&lt;td&gt;88.9%&lt;/td&gt;
&lt;td&gt;~1s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Rates are per-mechanism on each experiment's own scenario set — Phase 1 (12 scenarios) for Evidence Gate and A+B, Phase 2 (7 scenarios) for C0/C1/C2 — not a single cumulative run.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 The remaining gap
&lt;/h3&gt;

&lt;p&gt;One scenario is the hardest across both experiments: &lt;strong&gt;SC10/SC10a, the semantic compliance gap&lt;/strong&gt; — structurally complete output, correct evidence files, all tests pass, adequate coverage, coherent self-review, but the output doesn't satisfy the actual requirement. In Phase 1 it evaded both channels (A and B both passed SC10). In Phase 2, C2 &lt;em&gt;caught&lt;/em&gt; SC10a — because the deviation &lt;strong&gt;surfaced in the review text&lt;/strong&gt; ("keys expire via TTL, not write-invalidation"). C2 reads the same text channel the producer wrote, so a deviation the producer put into words is detectable.&lt;/p&gt;

&lt;p&gt;The case no text-channel mechanism can catch — the genuine DPI bound — is the opposite: a deviation the producer &lt;strong&gt;never surfaces&lt;/strong&gt; in any evidence file. If the agent omits write-invalidation entirely and never mentions TTL, the missing requirement leaves no trace in any text the evaluator can read. Regex can't match what isn't there; an LLM reading the evidence can't notice an absence it has no signal for. This is the theoretically provable blind spot the Data Processing Inequality names: the evaluator's information is a subset of the producer's output, and an absence the producer never writes down is absent from that subset too.&lt;/p&gt;

&lt;p&gt;The fixes are all outside the evaluation channel — and they're the prescription René named in the original comment, which the experiments backfill:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Human review on material calls&lt;/strong&gt; — a person reads the requirement vs the implementation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A hard step budget&lt;/strong&gt; — the agent runs out of steps before it can simulate compliance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic checks on what is actually binary&lt;/strong&gt; — skillgate's VPS pre-receive hook cannot be fooled by plausible output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Skillgate implements the third. The first two are a human reviewer and the orchestrator's step limit.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 Implementation
&lt;/h3&gt;

&lt;p&gt;The pipeline described here has been integrated into &lt;code&gt;forge-verify&lt;/code&gt;'s &lt;code&gt;content-verify.mjs&lt;/code&gt; as an optional evidence gate mode. Configured via &lt;code&gt;.forge/content-verify.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Implement rate limiting middleware"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/rate-limit.ts"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evidence_gates"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"evidence_dir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".skillgate/evidence"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"requirements"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REQ-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"desc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IP level rate limiting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"evidence_file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test-output.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"(?i)(RateLimiter.*IP|isRateLimited.*IP)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"regex"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REQ-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"desc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Write-invalidation on cache writes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"evidence_file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"diff-review.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"llm"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REQ-3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"desc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Coverage &amp;gt;= 85%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"evidence_file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"coverage-summary.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"(8[5-9]|90+)%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"regex"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;evidence_gates&lt;/code&gt; is configured, the pipeline runs the evidence gate → C1 (regex requirements) → C2 (LLM requirements) → L3 path. When absent, it falls back to the traditional L0 → L2 → L3 path. Backward compatible — existing configurations don't need changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. What the Two Experiments Established
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;First, the channel you evaluate through determines what you can detect.&lt;/strong&gt; An LLM reading text catches content-level failures that a filesystem check cannot — fabricated evidence, stale dates, implausible narratives. A filesystem check catches mechanical gaps cheaply and deterministically, and in this run the LLM caught those too — redundant coverage, not complementarity. The divergence is narrow but real: filesystem accepts fabricated and stale evidence at face value, while the LLM accepted the agent's "no test needed" rationalization. Neither channel alone is sufficient, and the shared blind spot is provably uncloseable from either channel alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, per-requirement evaluation outperforms holistic evaluation by a wide margin.&lt;/strong&gt; The same LLM, same temperature, same evidence — but asking "does this evidence prove requirement N?" instead of "is the work done?" raised detection rate from 20% to 100%. The frame matters more than the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third, contract regex is a practical zero-cost filter for a surprisingly wide set of constraints.&lt;/strong&gt; Numerical thresholds, format validity, and fixed-pattern detection all work reliably. The exception — negation patterns — can be handled by routing them to the per-requirement LLM. Caveat: "wide" applies to numerical/format constraints; for functional/semantic requirements, real-agent-authored evidence is far harsher — C1 collapses under agent vocabulary drift.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fourth, the negation blind spot in regex evaluation is the same problem as the DPI blind spot, one level down.&lt;/strong&gt; A regex that matches "write-invalidation" in "not write-invalidation" is making the same error as an LLM that reads "all tests pass" and misses that the wrong test suite was run. Both are pattern-matchers that can't distinguish "mentioned" from "satisfied."&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Experiment&lt;/th&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Phase 1 (12 scenarios)&lt;/td&gt;
&lt;td&gt;Text channel vs filesystem channel&lt;/td&gt;
&lt;td&gt;Complementary blind spots; combined = 88.9% detection / 91.7% overall&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Phase 2 (7 scenarios)&lt;/td&gt;
&lt;td&gt;Free-text vs contract regex vs per-req LLM&lt;/td&gt;
&lt;td&gt;Per-req = 100% detection; contract regex = 85.7% at zero cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Combined (19 scenarios)&lt;/td&gt;
&lt;td&gt;What catches the surfaced-deviation gap?&lt;/td&gt;
&lt;td&gt;Per-requirement LLM (C2), when the deviation appears in evidence text; a non-surfaced deviation (genuine DPI bound) is uncloseable from any text channel&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The architectural conclusion: replace the single free-text LLM evaluation (old L2) with a three-stage pipeline — evidence gate (file system) → contract regex (text patterns) → per-requirement LLM (semantic checks). Each stage catches what the previous one misses. The combination narrows the gap on every scenario we constructed — every named evasion becomes a deterministic catch — but it does not close it. Two residues remain. (1) &lt;strong&gt;Unenumerated evasions in word-space&lt;/strong&gt;: a fresh synonym or reframed justification clears the regex layers until you name it — the ratchet turns, the gap doesn't vanish. (2) &lt;strong&gt;The genuine DPI bound&lt;/strong&gt;: a deviation the producer never surfaces in any text channel is invisible to every text-reading mechanism, regex or LLM. That floor lives in &lt;strong&gt;argument-space&lt;/strong&gt; — exercising the code path and observing the side effect on the referent the claim names — which is outside this pipeline and outside any text channel.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;All experiment scripts: &lt;a href="https://github.com/zxpmail/blog/tree/main/agent-determinism-illusions/scripts" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Phase 1: &lt;code&gt;channel-comparison-test.py&lt;/code&gt; — 12 scenarios, deepseek-v4-flash&lt;/li&gt;
&lt;li&gt;Phase 2: &lt;code&gt;contract-comparison-test.py&lt;/code&gt; — 7 scenarios, 3 mechanisms&lt;/li&gt;
&lt;li&gt;skillgate source: &lt;a href="https://www.npmjs.com/package/@reneza/skillgate" rel="noopener noreferrer"&gt;npm&lt;/a&gt; and &lt;a href="https://github.com/renezander030/skillgate" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; (v0.5.0, the version described; now at 0.6.x)&lt;/li&gt;
&lt;li&gt;Pipeline implementation: &lt;code&gt;ReqForge/scripts/forge-verify/content-verify.mjs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Previous: &lt;a href="https://dev.to/zxpmail/divergence-escalates-the-wrong-population-unanimous-misses-auto-pass-1513"&gt;Part 7 — Divergence escalates the wrong population: unanimous misses auto-pass&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Series start:&lt;/em&gt; &lt;a href="https://dev.to/zxpmail/i-tested-the-deterministic-agent-loop-claims-with-four-experiments-they-all-failed-including-38kj"&gt;I tested the 'deterministic agent loop' claims with four experiments. They all failed — including my own fix.&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>Round 2: when the reply triggers another revision</title>
      <dc:creator>zxpmail</dc:creator>
      <pubDate>Tue, 04 Aug 2026 12:21:01 +0000</pubDate>
      <link>https://dev.to/zxpmail/round-2-when-the-reply-triggers-another-revision-5h8m</link>
      <guid>https://dev.to/zxpmail/round-2-when-the-reply-triggers-another-revision-5h8m</guid>
      <description>&lt;h1&gt;
  
  
  Round 2: when the reply triggers another revision
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Agent Determinism Illusions (Part 17)&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where this fits:&lt;/strong&gt; Part 16 collected four reader-driven revisions — Mike HHI pair-join, Tom Jones position-adjacency, Xiao Man shape-routing (rename_keys), Mike quiet-failure. Before Part 16 shipped, Xiao Man replied to the rename_keys section of the draft with a refinement: not "pick a better anchor," but "remove the anchor from the probe's responsibility." This part is the response — what the refinement predicts, what the experiments on this fixture support, and the methodological rule that falls out.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. The refinement
&lt;/h2&gt;

&lt;p&gt;Xiao Man's reply (2026-07-30) on the rename_keys cell:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The probe should never re-find what the router already resolved. The mutation suite then becomes: "did we accidentally put lookup responsibility back into the probe?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two claims, separable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Probe layer:&lt;/strong&gt; if the router passes the resolved path (e.g., "services is at &lt;code&gt;art['components']&lt;/code&gt; after rename"), the probe stops doing key-name lookup and becomes rename-immune by construction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System layer:&lt;/strong&gt; the anchor doesn't disappear; it relocates from probe to declaration/router. The new anchor layer has its own survival question.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Round 1 said: shape-routing is fragile under rename. Round 2 sharpens: the fix isn't a better anchor, it's a boundary redraw.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Path-passing probe — confirmed on fixture
&lt;/h2&gt;

&lt;p&gt;Script: &lt;code&gt;probe-path-passing-redesign-test.py&lt;/code&gt; → &lt;code&gt;results-v2/probe-path-passing-redesign.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two probe designs on the same rename_keys population (T3 good artifacts, &lt;code&gt;services → components&lt;/code&gt; + inner renames, n=40, seed=7):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Probe&lt;/th&gt;
&lt;th&gt;Design&lt;/th&gt;
&lt;th&gt;false_reject on rename_keys (n=40)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;v1 (current)&lt;/td&gt;
&lt;td&gt;hardcoded &lt;code&gt;art.get("services")&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;100%&lt;/strong&gt; (40/40)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2 (refined)&lt;/td&gt;
&lt;td&gt;path passed by declaration-aware router&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0%&lt;/strong&gt; (0/40)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Probe-side fix works. The probe becomes a value-checker at a router-resolved coordinate, not a finder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implementation leak worth naming.&lt;/strong&gt; First v2 draft only took the services path from the router; it still hardcoded the inner &lt;code&gt;timeout_ms&lt;/code&gt; lookup — and rename_keys also changes &lt;code&gt;timeout_ms → request_timeout_ms&lt;/code&gt;. v2 still rejected everything. The probe had to take &lt;strong&gt;all&lt;/strong&gt; renamed keys as router-resolved paths before it became rename-immune. Exactly the failure mode Xiao Man named: "did we accidentally put lookup responsibility back into the probe?"&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Declaration-anchor survival matrix
&lt;/h2&gt;

&lt;p&gt;Script: &lt;code&gt;declaration-anchor-survival-test.py&lt;/code&gt; → &lt;code&gt;results-v2/declaration-anchor-survival.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Probe-side fix is one half. The other half: the new anchor layer (declaration/router) has its own survival envelope. Four anchors × eight perturbations; each cell is a single deterministic resolution check, not a stochastic trial:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;anchor&lt;/th&gt;
&lt;th&gt;P0&lt;/th&gt;
&lt;th&gt;P1&lt;/th&gt;
&lt;th&gt;P2&lt;/th&gt;
&lt;th&gt;P3&lt;/th&gt;
&lt;th&gt;P4&lt;/th&gt;
&lt;th&gt;P5&lt;/th&gt;
&lt;th&gt;P6&lt;/th&gt;
&lt;th&gt;P7&lt;/th&gt;
&lt;th&gt;survived&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;synonym_list&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;✗&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;7/8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;structural&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;✗&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;7/8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cardinality&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;✗&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;✗&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;6/8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cross_field&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;✗&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7/8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Perturbations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;P0 baseline; P1 services→components (in synonym list); P2 services→instances (out-of-decl); P3 add decoy field; P4 inner shape corrupt; P5 add third entry; P6 decoy-with-limits inserted before services (shape clone); P7 inner field rename (port→port_number).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;No anchor survives 8/8.&lt;/strong&gt; Each has a distinct failure signature:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;synonym_list&lt;/strong&gt; dies on out-of-declaration rename (P2). Narrow but predictable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;structural&lt;/strong&gt; dies on shape clone (P6). Can't distinguish &lt;code&gt;services&lt;/code&gt; from a decoy that mimics list-of-dicts-with-limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cardinality&lt;/strong&gt; dies on count change (P5) and shape clone (P6).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cross_field&lt;/strong&gt; dies on inner field rename (P7). Semantic-structural breaks under inner synonym rename.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The "wide" anchors (structural, cross_field) trade robustness on outer rename for fragility on shape clone and inner rename. &lt;strong&gt;Narrow vs wide is a trade-off, not a monotone improvement.&lt;/strong&gt; Any "X is more robust than Y" claim must name the attack class.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Boundary-leak detector rule
&lt;/h2&gt;

&lt;p&gt;Xiao Man's deeper reframe — mutation suite as architectural-violation detector, not bug-finder:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;rename_keys doesn't introduce a defect; it only swaps key names. If the system boundary is clean, rename should be a no-op. If rename triggers failure, someone put lookup where it doesn't belong.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Codified as a fixture-design rule (&lt;code&gt;working-notes/boundary-leak-detector-rule.md&lt;/code&gt;):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Any fixture with router/probe or judge/lookup layering must include a set of &lt;strong&gt;neutral mutations&lt;/strong&gt; — rename, position-permute, cardinality-preserve. Neutral mutations introduce no defect by design. Failures under neutral mutation count as &lt;strong&gt;boundary leaks&lt;/strong&gt;, reported independently of catch rate.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Neutral-mutation classes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mutation&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Failure implies&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rename_keys&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;synonym rename&lt;/td&gt;
&lt;td&gt;probe hardcoded key lookup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;position_permute&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;swap siblings&lt;/td&gt;
&lt;td&gt;probe did index-based lookup router didn't sanction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cardinality_preserve_add&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;add shape-identical sibling&lt;/td&gt;
&lt;td&gt;anchor used cardinality over cross-field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inner_field_rename&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rename inner field&lt;/td&gt;
&lt;td&gt;anchor checked key-presence over semantic invariant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;decoy_with_same_shape&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;insert shape-identical decoy&lt;/td&gt;
&lt;td&gt;anchor only inspects shape&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The rule is a label, not a framework.&lt;/strong&gt; Existing fixtures (rename_keys, decoy_nest, cue_erase, cross-model pair-join) already run neutral mutations — they just weren't called that. Future fixtures should declare their neutral-mutation inventory up front and report boundary-leak count as a primary metric, alongside catch rate.&lt;/p&gt;

&lt;p&gt;What this rule does &lt;strong&gt;not&lt;/strong&gt; do: replace catch rate. A fixture with zero boundary leaks can still have wrong catch rate. The two metrics are independent.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Closing
&lt;/h2&gt;

&lt;p&gt;Round 1 said: depth-from-shape is fragile under rename. Round 2 sharpens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Probe layer:&lt;/strong&gt; anchor can be removed. Path-passing redesign confirmed (n=40, seed=7); probe becomes rename-immune by construction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System layer:&lt;/strong&gt; anchor doesn't vanish, it relocates. Declaration/router is the new anchor site, with its own measurable survival envelope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Methodological consequence:&lt;/strong&gt; neutral mutations are boundary-leak detectors. Future fixtures should report leak count alongside catch rate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Xiao Man named the architectural principle. The empirical work on this fixture supports it: probe becomes anchor-free; system stays anchor-bound at a different layer; the survival question moves with the anchor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Probe without anchor, system with anchor at a different layer. That's the relocation.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; Agent Determinism Illusions · Scripts: &lt;a href="https://github.com/zxpmail/blog/tree/main/agent-determinism-illusions/scripts" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Previous:&lt;/strong&gt; &lt;a href="https://dev.to/zxpmail/reader-driven-revisions-four-comments-that-bit-back-30p8"&gt;Part 16 — Reader-driven revisions: four comments that bit back&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Comment thread origin:&lt;/strong&gt; &lt;a href="https://dev.to/zxpmail/five-comments-that-redesigned-my-llm-verification-pipeline-388f"&gt;Part 6&lt;/a&gt; · &lt;a href="https://dev.to/zxpmail/divergence-escalates-the-wrong-population-unanimous-misses-auto-pass-1513"&gt;Part 7&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>Reader-driven revisions: four comments that bit back</title>
      <dc:creator>zxpmail</dc:creator>
      <pubDate>Thu, 30 Jul 2026 09:30:30 +0000</pubDate>
      <link>https://dev.to/zxpmail/reader-driven-revisions-four-comments-that-bit-back-30p8</link>
      <guid>https://dev.to/zxpmail/reader-driven-revisions-four-comments-that-bit-back-30p8</guid>
      <description>&lt;h1&gt;
  
  
  Reader-driven revisions: four comments that bit back
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Agent Determinism Illusions (Part 16)&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where this fits:&lt;/strong&gt; Part 15 closed on dual-line ops — Trigger∥Rank, Shadow∥Enforce, fail-closed fallback when shadow goes vacuous. After it shipped, four readers ran four challenges. Each one named a fixture limitation the original piece didn't hedge. This part collects the four experiments, the four concessions, and the four scope-narrowing fixes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Part 15's Update already sharpened one — Tom Jones's conf_desc challenge ("fixture joint, not safe fallback law"). This part covers the four that came after. Each is a different kind of fixture blind spot.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Mike Czerwinski — HHI pair-join is not a concentration signal
&lt;/h2&gt;

&lt;p&gt;Mike's push: my defect-class concentration numbers used HHI on class labels. The production-relevant cut is pair-join — &lt;code&gt;P(route ∧ CD | MISS)&lt;/code&gt; — whether route changes cluster with defect-class changes &lt;em&gt;within a single miss&lt;/em&gt;. HHI on labels can't see pair-join; only a per-trial 2×2 contingency can.&lt;/p&gt;

&lt;p&gt;Script: &lt;code&gt;pair-join-empirical-test.py&lt;/code&gt; → &lt;code&gt;results-v2/pair-join-empirical.json&lt;/code&gt;. Three probes per trial on qwen3:0.6b (V: verdict defines MISS; R: routing audit; C: defect classifier). 20 scenarios × N=5 × 3 probes = 300 calls.&lt;/p&gt;

&lt;p&gt;Result on 30 MISSes across 10 scenarios:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;CD=0&lt;/th&gt;
&lt;th&gt;CD=1&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;route=0&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;route=1&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Lift over independence: &lt;strong&gt;1.14&lt;/strong&gt;. Joint HHI: 0.322. Scenario HHI: 0.124.&lt;/p&gt;

&lt;p&gt;Reading: pair-join is essentially independent — route changes don't cluster with defect-class changes within a miss. Mike was right that pair-join is the production cut; the empirical answer is, there isn't concentration to exploit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; drop HHI on labels. If pair-join concentration matters operationally, measure it with per-trial contingencies, not label aggregation.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Tom Jones — position-adjacency is model- and directive-specific
&lt;/h2&gt;

&lt;p&gt;Tom's push: on his fixture, the note adjacent to the question (position 100%) was obeyed 60/60 while other positions sat at 80–85%. Edge padding (12 notes of separation) erased the ends advantage. The privileged position is adjacency to the question, not budget position. Two filters: budget names who gets seen; adjacency names who gets obeyed.&lt;/p&gt;

&lt;p&gt;Clean, generalizable claim. Tried to replicate it.&lt;/p&gt;

&lt;p&gt;Scripts: &lt;code&gt;position-adjacency-obedience-test.py&lt;/code&gt; (v1, BANANA prefix) and &lt;code&gt;position-adjacency-obedience-v2.py&lt;/code&gt; (v2, uppercase override).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v1&lt;/strong&gt; (BANANA prefix, binary task): both glm-5.2 and qwen3:0.6b ceiling at 100% across all positions — no variance. Tom's binary-verdict caveat predicts this — on a binary task the same-model arm saturates near 1.0.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v2&lt;/strong&gt; (uppercase override, sustained constraint, escapes ceiling). deepseek-v4-flash, K=12 inner block, 200 calls:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;pos=0&lt;/th&gt;
&lt;th&gt;pos=25&lt;/th&gt;
&lt;th&gt;pos=50&lt;/th&gt;
&lt;th&gt;pos=75&lt;/th&gt;
&lt;th&gt;pos=100&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;no_padding&lt;/td&gt;
&lt;td&gt;95%&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;90%&lt;/td&gt;
&lt;td&gt;90%&lt;/td&gt;
&lt;td&gt;85%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;with_padding&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;95%&lt;/td&gt;
&lt;td&gt;85%&lt;/td&gt;
&lt;td&gt;85%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Position 100 (adjacent to question) is not the highest — 85% vs 95% at position 0. Position 25 is the lowest in both conditions — a middle dip, not an ends advantage. Edge padding did not systematically change obedience.&lt;/p&gt;

&lt;p&gt;Reading: Tom's 60/60 is real on his fixture. On this one the shape differs — the effect appears model- and directive-specific, not universal. Same shape as Part 15's conf↔slot shuffle: edges don't transfer. The conceptual cut (two filters: seen vs obeyed) still stands; the second filter remains unmeasured on production traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; don't claim position-adjacency as a law — call it a fixture property until replicated across more models and directive types.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Xiao Man — depth signal from artifact shape is not stable
&lt;/h2&gt;

&lt;p&gt;Xiao Man's push: the cascade's depth-from-keys rule (budget → P4, services[] → P3) is schema-deterministic and cheap, but the determinism is on surface shape — exactly what an adversarial artifact can rewrite. Move the stable-referent test one level up: don't ask "does this case have a stable referent?" — ask "is the depth signal stable under minor shape changes?"&lt;/p&gt;

&lt;p&gt;Three perturbation cells, two failure axes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Perturbation&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Effect on routing&lt;/th&gt;
&lt;th&gt;Failure axis&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;cue_erase&lt;/td&gt;
&lt;td&gt;strip budget cue, force wrong fingerprint residual&lt;/td&gt;
&lt;td&gt;80/80 routes T4 → T3&lt;/td&gt;
&lt;td&gt;catch 100% → 82.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;decoy_nest&lt;/td&gt;
&lt;td&gt;inject decorative services[] into T2&lt;/td&gt;
&lt;td&gt;80/80 routes T2 → T3&lt;/td&gt;
&lt;td&gt;catch 100% → 0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rename_keys&lt;/td&gt;
&lt;td&gt;services → components on T3, schema-synonym&lt;/td&gt;
&lt;td&gt;80/80 routes T3 → T1&lt;/td&gt;
&lt;td&gt;false_reject 100% in BOTH arms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The rename_keys cell is worse than expected: not only does shape routing break, the probe layer also breaks — &lt;code&gt;probe()&lt;/code&gt; hardcodes &lt;code&gt;art.get("services")&lt;/code&gt;, so even fixed_matched loses. Two layers key-coupled.&lt;/p&gt;

&lt;p&gt;Scripts: &lt;code&gt;probe-artifact-shape-routing-test.py&lt;/code&gt; (cue_erase / decoy_nest) → &lt;code&gt;results-v2/probe-artifact-shape-routing.json&lt;/code&gt;; &lt;code&gt;probe-shape-routing-rename-keys-test.py&lt;/code&gt; → &lt;code&gt;results-v2/probe-shape-routing-rename-keys.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; don't infer depth from shape; route to a fixed mid-depth probe as baseline; escalate when the probe signals cross-field. Anchor the baseline probe to structural invariants (checksum fixture), not to key names.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Mike Czerwinski — quiet-failure fallback gap
&lt;/h2&gt;

&lt;p&gt;Mike's push: the fallback trigger &lt;code&gt;shadow catches 0 while oracle &amp;gt; 0&lt;/code&gt; is doing real work, but it misses the quieter failure — shadow catches something (nonzero), just consistently the wrong somethings. Shadow catching 0 is loud and easy to fall back on. Shadow catching a nonzero number that's wrong is the harder case.&lt;/p&gt;

&lt;p&gt;Part 15's fallback rule (&lt;code&gt;dual-line-ops-sim.py&lt;/code&gt; line 346):&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;shadow_c&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;oracle&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;enforce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fallback_arrival&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;shadow_c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shadow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gap: &lt;code&gt;shadow ∈ (0, enforce)&lt;/code&gt; — shadow still catches something, less than enforce would have. Vacuous check never fires; dual-line ships a compromised rank.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pure-math scan
&lt;/h3&gt;

&lt;p&gt;Script: &lt;code&gt;partial-stale-shadow-test.py&lt;/code&gt; → &lt;code&gt;results-v2/partial-stale-shadow.json&lt;/code&gt;. 81-cell (shadow, enforce) grid at oracle=8. Three rules: vacuous (current), noninferior (proposed: &lt;code&gt;shadow &amp;lt; enforce ⟹ fallback&lt;/code&gt;), god (upper bound).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cells in quiet-gap regime&lt;/td&gt;
&lt;td&gt;28 / 81&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mean catches lost by vacuous vs noninferior (gap cells)&lt;/td&gt;
&lt;td&gt;3.0/cell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max catches lost per cell&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Empirical stress
&lt;/h3&gt;

&lt;p&gt;Script: &lt;code&gt;partial-stale-injection-test.py&lt;/code&gt; → &lt;code&gt;results-v2/partial-stale-injection.json&lt;/code&gt;. Stratified class stream (n=164, k=8, enforce=8, oracle=8, pure R_hist=8). Inject per-item R_hist score perturbation (with probability p, replace score with prior). 30 draws per p:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;p&lt;/th&gt;
&lt;th&gt;shadow mean&lt;/th&gt;
&lt;th&gt;gap fraction&lt;/th&gt;
&lt;th&gt;vacuous loss vs noninferior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.3&lt;/td&gt;
&lt;td&gt;7.90&lt;/td&gt;
&lt;td&gt;3%&lt;/td&gt;
&lt;td&gt;3.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;7.17&lt;/td&gt;
&lt;td&gt;40%&lt;/td&gt;
&lt;td&gt;2.08&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.7&lt;/td&gt;
&lt;td&gt;4.73&lt;/td&gt;
&lt;td&gt;93%&lt;/td&gt;
&lt;td&gt;3.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.8&lt;/td&gt;
&lt;td&gt;3.17&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;4.83&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.9&lt;/td&gt;
&lt;td&gt;2.70&lt;/td&gt;
&lt;td&gt;97%&lt;/td&gt;
&lt;td&gt;5.21&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pure R_hist on this fixture lands at corners (0 on temporal diluted, 8 on stratified class) — partial-stale doesn't surface natively. Stress test fills it in: ranker partially loses calibration → vacuous ships compromised shadow while enforce would have caught more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; change &lt;code&gt;shadow==0&lt;/code&gt; to &lt;code&gt;shadow &amp;lt; enforce&lt;/code&gt;. One line. Noninferior strictly dominates on gap cells, ties at corners.&lt;/p&gt;




&lt;h2&gt;
  
  
  Synthesis: fixture limits, named by readers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reader&lt;/th&gt;
&lt;th&gt;Fixture limit named&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mike (HHI)&lt;/td&gt;
&lt;td&gt;label aggregation hides pair-join independence&lt;/td&gt;
&lt;td&gt;measure pair-join directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tom (position)&lt;/td&gt;
&lt;td&gt;effects don't transfer across models/directives&lt;/td&gt;
&lt;td&gt;call replication failures, not laws&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Xiao Man (shape-routing)&lt;/td&gt;
&lt;td&gt;routing and probe both key-coupled&lt;/td&gt;
&lt;td&gt;fixed mid-depth probe, structural-anchored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mike (quiet-failure)&lt;/td&gt;
&lt;td&gt;vacuous rule misses partial-stale regime&lt;/td&gt;
&lt;td&gt;&lt;code&gt;shadow &amp;lt; enforce ⟹ fallback&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pattern: each reader named a specific blind spot in Part 15's fixture. None of the fixes are "the fixture was wrong" — the fixture measured what it measured. The fixes are about &lt;em&gt;what the fixture measurement does not license&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Reader-driven revision isn't a bug in fixture-based research. It's the necessary complement — a single fixture answers a single question, and the readers name the adjacent questions the original framing missed.&lt;/p&gt;

&lt;p&gt;Four comments, four experiments, four scope-narrowing fixes. The fixture measures what it measures — the rest is for readers to name.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; Agent Determinism Illusions · Scripts: &lt;a href="https://github.com/zxpmail/blog/tree/main/agent-determinism-illusions/scripts" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Previous in arc:&lt;/strong&gt; &lt;a href="https://dev.to/zxpmail/dt2-names-who-enters-budget-names-who-gets-seen-4f9g"&gt;Part 15 — D+T2 names who enters; budget names who gets seen&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Comment thread origin:&lt;/strong&gt; &lt;a href="https://dev.to/zxpmail/five-comments-that-redesigned-my-llm-verification-pipeline-388f"&gt;Part 6&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>D+T2 names who enters; budget names who gets seen</title>
      <dc:creator>zxpmail</dc:creator>
      <pubDate>Tue, 28 Jul 2026 08:15:11 +0000</pubDate>
      <link>https://dev.to/zxpmail/dt2-names-who-enters-budget-names-who-gets-seen-4f9g</link>
      <guid>https://dev.to/zxpmail/dt2-names-who-enters-budget-names-who-gets-seen-4f9g</guid>
      <description>&lt;h1&gt;
  
  
  D+T2 names who enters; budget names who gets seen
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Agent Determinism Illusions (Part 15)&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where this fits:&lt;/strong&gt; This part does &lt;strong&gt;not&lt;/strong&gt; continue Part 8's channel-gap / skillgate thread (still unpublished in this numbering). It continues &lt;a href="https://dev.to/zxpmail/divergence-escalates-the-wrong-population-unanimous-misses-auto-pass-1513"&gt;Part 7&lt;/a&gt;'s escalation line — and the &lt;a href="https://dev.to/zxpmail/five-comments-that-redesigned-my-llm-verification-pipeline-388f"&gt;Part 6&lt;/a&gt; comment thread where Alexey Spinov and Mike Czerwinski pushed past “which stream.” Numbering jumps to 15 on purpose: Parts 8–14 already hold other arcs; publish order here is 7 → &lt;strong&gt;15&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Part 7 closed with: divergence stays; T1/T2 join it; none of them is the novelty arm. That answers &lt;strong&gt;who enters&lt;/strong&gt; the escalate set. It does not answer what happens when the set is larger than the human budget.&lt;/p&gt;

&lt;p&gt;On the Part 6 thread, Alexey posted a wiring warning: merging a second stream S into D under arrival order at a 2% budget can catch fewer MISSes than D alone. Mike reframed the open problem as &lt;strong&gt;rank-inside-stream&lt;/strong&gt; — coverage-limited is a load-bearing property of any history-built trigger R, and nobody had proposed a design yet.&lt;/p&gt;

&lt;p&gt;We do not reproduce Alexey's 720-cell grid: that was his parametric model (π·h·r_m), not in this repo. We ran a related check on df_proxy (&lt;code&gt;scripts/merge-displacement-grid-test.py&lt;/code&gt;) and got a &lt;strong&gt;structural NULL&lt;/strong&gt;: the D stream (conf&amp;lt;0.9) is miss-starved on this fixture — qwen3-0.5b's D has 2 MISS in 115 items, gemma3's has 0 in 147; three models, eight budgets, four added streams — 96 cells, and D@arrival exceeds 0 in none. The displacement shape has nowhere to fire. That itself is the finding: real single-judge outputs don't give D enough miss-mass to dilute.&lt;/p&gt;

&lt;p&gt;This part pivots to a weaker proposition: can rank move the needle &lt;strong&gt;inside any&lt;/strong&gt; escalate stream — and the shape dual-line production must take.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Floor volume beats stream choice
&lt;/h2&gt;

&lt;p&gt;Once true positives in the escalate stream exceed budget &lt;code&gt;k&lt;/code&gt;, no trigger definition supplies “who gets seen.” Class filters cannot shrink the TP mass without discarding the failures the trigger exists to find. The fight over divergence vs class vs UHC vs blend is secondary to &lt;strong&gt;queue order under a hard cap&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Part 7's D+T2 remains the right &lt;em&gt;entry&lt;/em&gt; policy on the fixtures where it was measured. It is not a ranking policy.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Rank-inside-stream (existence)
&lt;/h2&gt;

&lt;p&gt;Script: &lt;code&gt;scripts/rank-inside-stream-test.py&lt;/code&gt; → &lt;code&gt;results-v2/rank-inside-stream.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On DF v2 all-run traffic (N=585), under budgets 1%/2%/5%:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Floor-volume holds:&lt;/strong&gt; at B=2% (k=11), oracle leaves ~80+ of ~92–94 stream TPs unseen on fat streams.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployable proxy can move the needle:&lt;/strong&gt; leave-one-out &lt;code&gt;P(MISS|class)&lt;/code&gt; beats arrival in 6/18 cells; on D+T2 at 5%, arrival 10 → loo 29 (= oracle).&lt;/li&gt;
&lt;li&gt;Natural multiperspective (N=60) is too small for the deployable claim; it still shows arrival-order cruelty: UHC∧class catches 3 while D+T2 catches 0 at B=5%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus the open problem holds: same stream, same B, order alone changes catch.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Can a ranker “ship”? Pre-registered gates
&lt;/h2&gt;

&lt;p&gt;Script: &lt;code&gt;scripts/ranker-prod-acceptance-test.py&lt;/code&gt; → &lt;code&gt;results-v2/ranker-prod-acceptance.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Candidate &lt;strong&gt;R_hist&lt;/strong&gt;: train-window &lt;code&gt;P̂(MISS|class)&lt;/code&gt;; unseen → global prior. Never peeks at the item's own label.&lt;/p&gt;

&lt;p&gt;Gates G0–G6 (then G7 in the stress sweep): holdout miss mass; ≥ arrival; beat heuristic baselines on the constructed queue; merge non-inferiority; LOMO; cold-start; &lt;strong&gt;ranking headroom&lt;/strong&gt; (arrival &amp;lt; oracle).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Natural D+T2 on stratified holdout is 29/29 MISS&lt;/strong&gt; — arrival already equals oracle; G6 fails; a SHIP there would be vacuous. So the decisive protocol &lt;strong&gt;constructs&lt;/strong&gt; a diluted escalate queue: all holdout D+T2 MISSes + non-MISS distractors ≈ 35% miss-rate, distractors first.&lt;/p&gt;

&lt;p&gt;On that protocol: &lt;strong&gt;SHIP&lt;/strong&gt; — at B=5% k=8, arrival &lt;strong&gt;0&lt;/strong&gt; → R_hist &lt;strong&gt;8&lt;/strong&gt; = oracle &lt;strong&gt;8&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Scope caveat, stated hard: certificate is for this traffic + these B + this dilution stress — not a universal production warrant.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Stress: dilution does not kill hist; time does
&lt;/h2&gt;

&lt;p&gt;Script: &lt;code&gt;scripts/ranker-acceptance-stress-sweep.py&lt;/code&gt; → &lt;code&gt;results-v2/ranker-acceptance-stress-sweep.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Axes: features × dilution miss-rate (15%→95%) × holdout (stratified / within-model temporal / global temporal). G7 blocks 0=0 SHIP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stratified:&lt;/strong&gt; R_hist / R_hist_conf &lt;strong&gt;SHIP at every swept dilution&lt;/strong&gt; (other candidates do not — e.g. R_conf_asc goes NO_SHIP at 0.95). The 35% result was not a knife-edge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;within_model_temporal&lt;/strong&gt; (per-model first 70% → train): R_hist &lt;strong&gt;NO_SHIP at 15–65%&lt;/strong&gt; (G2/G7 — hist catch 0 while conf_desc saturates); recovers SHIP only on dense queues 80–95%. global_temporal still fails G0 (miss_test=1).&lt;/p&gt;

&lt;p&gt;Reading: dilution stresses mixture and arrival order; the class table still works. Temporal holdout stresses &lt;strong&gt;whether the table is still calibrated&lt;/strong&gt;. Stale &lt;code&gt;P(MISS|class)&lt;/code&gt; ranks true misses below distractors; high-confidence ordering accidentally matches this fixture's miss shape. No single feature ships everywhere.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Dual-line ops: Trigger∥Rank and Shadow∥Enforce
&lt;/h2&gt;

&lt;p&gt;Script: &lt;code&gt;scripts/dual-line-ops-sim.py&lt;/code&gt; → &lt;code&gt;results-v2/dual-line-ops-sim.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Production dual-line is not a metaphor:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Line&lt;/th&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trigger&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Who enters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rank&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Who is seen under &lt;code&gt;k&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Shadow&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What the ranker would review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enforce&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What humans actually review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fallback&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;If shadow catches 0 while oracle &amp;gt; 0 → stay on arrival&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Stratified + diluted 35%:&lt;/strong&gt; Rank lift on fixed diluted (R_hist − arrival) ties/beats best trigger-only under arrival; shadow hist lifts +3/+8 — &lt;strong&gt;SUPPORT dual-publish&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;within_model_temporal:&lt;/strong&gt; R_hist lift 0; best trigger under arrival is T2 (catch 3/8) — &lt;strong&gt;Trigger knob wins&lt;/strong&gt; when the rank table is stale. Shadow hist goes vacuous → &lt;strong&gt;fallback_arrival&lt;/strong&gt;; safe ≥ enforce. (conf_desc shadow would have lifted — feature choice still matters.)&lt;/p&gt;




&lt;h3&gt;
  
  
  Update (2026-07-29): conf_desc is fixture-shaped; agree-set mirror (Tom Jones)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://dev.to/zxpmail/dt2-names-who-enters-budget-names-who-gets-seen-4f9g"&gt;Tom Jones&lt;/a&gt;, on the agree-set, provider-name temporal collapse, G6/G7 headroom, and the open question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In your within-model temporal holdout, high-confidence ordering accidentally matches the fixture's miss shape. Do you know whether that is a property of the fixture, or whether confidence-descending is picking up something real about when that model fails? Those have opposite implications for whether conf_desc is a safe fallback line.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;1. conf_desc disentangling&lt;/strong&gt; (&lt;code&gt;conf-desc-miss-shape-test.py&lt;/code&gt; → &lt;code&gt;results-v2/conf-desc-miss-shape.json&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Same DF v2 dumps. Diluted escalate queues (~35% miss; low-conf rejects as distractors first so arrival catch@small-k ≈ 0):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cut&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Raw&lt;/td&gt;
&lt;td&gt;conf_desc beats arrival on &lt;strong&gt;9/9&lt;/strong&gt; (model × B) cells&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conf↔slot shuffle&lt;/td&gt;
&lt;td&gt;conf_desc edge over &lt;em&gt;random&lt;/em&gt; collapses (raw &lt;strong&gt;+1.56&lt;/strong&gt; → shuffle &lt;strong&gt;−0.89&lt;/strong&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-model donor conf&lt;/td&gt;
&lt;td&gt;unstable on &lt;strong&gt;5/6&lt;/strong&gt; pairs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Reading: the "accidental match" reproduces, and it is the fixture's joint &lt;code&gt;(conf, miss)&lt;/code&gt; — same dump already has 95.8% of MISS at conf≥0.9 (qwen-heavy). &lt;strong&gt;conf_desc is not a safe universal dual-line fallback warrant.&lt;/strong&gt; Fallback stays fail-closed to arrival when shadow goes vacuous; conf_desc may be a shadow &lt;em&gt;candidate&lt;/em&gt;, not the safety floor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Agree-set HaluEval mirror&lt;/strong&gt; (&lt;code&gt;agree-set-halueval-probe.py&lt;/code&gt; → &lt;code&gt;results-v2/agree-set-halueval.json&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Stratified n=70, seed=7, DeepSeek-v4-flash × local gemma3:latest (not Tom's 70B pair — same question shape, different tier). Cross-model usable n=52 after parse drops:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agreement&lt;/td&gt;
&lt;td&gt;78.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P(wrong|agree)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;19.5%&lt;/strong&gt; (8/41), Wilson 95% &lt;strong&gt;[10.2%, 34.0%]&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qa / summarization&lt;/td&gt;
&lt;td&gt;7.7% / &lt;strong&gt;40%&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same qualitative warning as Tom's 27.5% [16.1, 42.8]: the auto-pass lane can carry non-trivial error mass; summarization worse here. Do &lt;strong&gt;not&lt;/strong&gt; read P(both wrong|disagree)=0 as evidence (0/11; construction under binary + single gold — Tom's caveat).&lt;/p&gt;

&lt;p&gt;Same-model mirror: gemma×gemma at temperature 0 agreed 100% (70/70) — mostly determinism. Informative gap under controlled backends: same(1.00) − cross(0.79) ≈ +0.21. Tom's silent provider rename remains the cleaner temporal instance; this is only the controllable same-vs-cross wedge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. G6/G7&lt;/strong&gt; — agreed. A vacuous SHIP is worse than a wrong number; refusing to credit a win before proving headroom is why those gates exist.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Part 7 named who enters. Alexey named the unshrinkable floor. Mike named the open problem as rank-inside-stream. The offline suite says:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Order under budget is load-bearing.
&lt;/li&gt;
&lt;li&gt;A toy hist ranker can clear a pre-registered gate on a &lt;em&gt;constructed&lt;/em&gt; diluted queue under stratified holdout.
&lt;/li&gt;
&lt;li&gt;The same candidate collapses under time-like holdout — so a SHIP is not a ship.
&lt;/li&gt;
&lt;li&gt;Dual-line is the ops shape that matches the data: separate Trigger from Rank; Shadow before Enforce; fail closed when the shadow goes vacuous.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Tom's follow-up sharpens (4): &lt;strong&gt;conf_desc matching miss shape on this dump is fixture joint, not a safe fallback law&lt;/strong&gt;; agree-set error mass is real on a HaluEval probe even at a smaller model tier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;D+T2 names who enters. Budget names who gets seen. Rank is a calibrated, degradable line — not another tripwire. conf_desc is not the safety floor.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What this does &lt;em&gt;not&lt;/em&gt; claim: production labels, agent gaming, live catch@k confidence intervals, or that R_hist is the right production scorer. Those are product gates (shadow weeks, sliding recalibration, real audit outcomes) — not this fixture.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; Agent Determinism Illusions · Scripts: &lt;a href="https://github.com/zxpmail/blog/tree/main/agent-determinism-illusions/scripts" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Previous (argument arc):&lt;/strong&gt; &lt;a href="https://dev.to/zxpmail/divergence-escalates-the-wrong-population-unanimous-misses-auto-pass-1513"&gt;Part 7 — Divergence escalates the wrong population&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Comment thread origin:&lt;/strong&gt; &lt;a href="https://dev.to/zxpmail/five-comments-that-redesigned-my-llm-verification-pipeline-388f"&gt;Part 6&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>I Fabricated a Claim About LLM Judges. Then I Ran the Apology Experiment.</title>
      <dc:creator>zxpmail</dc:creator>
      <pubDate>Sat, 25 Jul 2026 10:24:25 +0000</pubDate>
      <link>https://dev.to/zxpmail/i-fabricated-a-claim-about-llm-judges-then-i-ran-the-apology-experiment-3ke6</link>
      <guid>https://dev.to/zxpmail/i-fabricated-a-claim-about-llm-judges-then-i-ran-the-apology-experiment-3ke6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where this fits:&lt;/strong&gt; A series aside, not a numbered Part. It is first an apology for a fabricated claim under Part 3; the experiment below is how I made amends. Numbers: &lt;code&gt;scripts/results-v2/*_summary.json&lt;/code&gt; (+ matching &lt;code&gt;.jsonl&lt;/code&gt;). One nesting note: the original comment was wrong, and the first version of this apology was also wrong about one scenario (DS4). The figures here are the corrected ones.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This piece is an apology.&lt;/p&gt;

&lt;p&gt;In a comment under Part 3, I wrote:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Part 3 found that judges fail on directional failures — outputs that were semantically reversed (delete → keep, stop → continue) but structurally pristine."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That claim was false. (&lt;a href="https://dev.to/zxpmail/comment/3alj4"&gt;Public retraction&lt;/a&gt; on the same thread.)&lt;/p&gt;

&lt;p&gt;Part 3 contained zero directional-failure experiments. I wrote from conversation memory without re-reading my own article. The confidence-vs-danger correlation I mentioned also does not appear in any published data of mine.&lt;/p&gt;

&lt;p&gt;This was not a misreading. It was a fabrication — not malicious, but a data-integrity failure all the same. Quietly deleting the comment would have been easier. Running the missing experiment and publishing the result was the honest option.&lt;/p&gt;

&lt;p&gt;I chose that. The first protocol's findings broke down on retest, so I ran it again at larger scale: &lt;strong&gt;20 directional-failure scenarios × 3 model tiers × 600 individual judgments&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What follows is the evidence. The redo overturned parts of my own first correction. That is part of the apology too — correcting the correction in public.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Design
&lt;/h2&gt;

&lt;p&gt;Twenty scenarios in four categories, judged by three models:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;N scenarios&lt;/th&gt;
&lt;th&gt;Scenarios&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Runs/scenario&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Explicit DF&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;DF1–DF6&lt;/td&gt;
&lt;td&gt;Output keyword directly contradicts task&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subtle DF&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;DS1–DS10&lt;/td&gt;
&lt;td&gt;Output sounds plausible but direction is wrong&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Valid control&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;V1–V2&lt;/td&gt;
&lt;td&gt;Output correctly fulfills task&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Garbage control&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;G1–G2&lt;/td&gt;
&lt;td&gt;Output is obvious nonsense&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Total: 20 scenarios, 200 calls per model, 600 calls across all three.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Backend&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;qwen3:0.5b&lt;/td&gt;
&lt;td&gt;0.5B&lt;/td&gt;
&lt;td&gt;Local (Ollama)&lt;/td&gt;
&lt;td&gt;OpenAI-compat API&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemma3:latest&lt;/td&gt;
&lt;td&gt;4.3B&lt;/td&gt;
&lt;td&gt;Local (Ollama)&lt;/td&gt;
&lt;td&gt;OpenAI-compat API&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;deepseek-v4-flash&lt;/td&gt;
&lt;td&gt;~200B?&lt;/td&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;DeepSeek API&lt;/td&gt;
&lt;td&gt;~$0.30/200 calls&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Judge prompt (temperature 0; same for every scenario — no rubrics, no few-shot):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;Judge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;whether&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;satisfies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;task.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Output:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;output&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Task:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;task&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Respond&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JSON:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"passes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0-1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Overall Results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;qwen3:0.5b&lt;/th&gt;
&lt;th&gt;gemma3:latest&lt;/th&gt;
&lt;th&gt;deepseek-v4-flash&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Global accuracy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;61.5%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;92.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;92.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explicit DF (6 scenarios)&lt;/td&gt;
&lt;td&gt;63.3%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subtle DF (10 scenarios)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;56.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;89.3%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;90.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subtle DF miss rate&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;44.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10.7%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Valid controls&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;90%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Garbage controls&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Not a smooth gradient — a cliff. Below ~1B, the model misses &lt;strong&gt;one in three explicit contradictions&lt;/strong&gt; and &lt;strong&gt;nearly half of subtle ones&lt;/strong&gt;. Above ~4B, explicit DF hits 100%, and subtle DF misses fall to single digits.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Explicit Directional Failures
&lt;/h2&gt;

&lt;p&gt;N=5 runs per scenario. The output's keyword directly contradicts the task.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;th&gt;Task → Wrong output&lt;/th&gt;
&lt;th&gt;qwen3&lt;/th&gt;
&lt;th&gt;gemma3&lt;/th&gt;
&lt;th&gt;deepseek&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DF1&lt;/td&gt;
&lt;td&gt;Delete file → file "kept" with details&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;40%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DF2&lt;/td&gt;
&lt;td&gt;Stop service → service "started"&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DF3&lt;/td&gt;
&lt;td&gt;Disable feature → "enabled for all"&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DF4&lt;/td&gt;
&lt;td&gt;Rollback to v1.0 → "v2.0 deployed"&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DF5&lt;/td&gt;
&lt;td&gt;Exclude from mailing → "added to send queue"&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;60%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DF6&lt;/td&gt;
&lt;td&gt;Limit to 10 connections → "set to 100"&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;qwen3:0.5b fails 4 of 6.&lt;/strong&gt; Worst case is DF6: &lt;code&gt;max_connections=100&lt;/code&gt; against "limit to 10." Judged correct 5/5 at &lt;strong&gt;confidence 1.0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The heuristic looks like "bigger number = better," not "does the number match?" Surface plausibility overrides the constraint. Same mechanism as claiming "all required fields present" when one is missing: the check asks whether the output &lt;em&gt;could&lt;/em&gt; be correct, not whether it &lt;em&gt;is&lt;/em&gt; correct against the specification.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DF1&lt;/strong&gt; (40%): task "delete," output "file retained — contains important business data." Three of five runs accepted the justification.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;gemma3 and deepseek catch every explicit DF.&lt;/strong&gt; Below ~1B, you cannot trust a judge even on keyword-level contradictions. Above ~4B, this class of failure vanishes.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Subtle Directional Failures — Five Patterns
&lt;/h2&gt;

&lt;p&gt;No direct contradiction keyword — only a plausible action in the wrong direction. N=15 per scenario.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;th&gt;Rationalization pattern&lt;/th&gt;
&lt;th&gt;qwen3&lt;/th&gt;
&lt;th&gt;gemma3&lt;/th&gt;
&lt;th&gt;deepseek&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DS1&lt;/td&gt;
&lt;td&gt;"archived" instead of "deleted"&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DS2&lt;/td&gt;
&lt;td&gt;"restarted" instead of "stopped"&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DS3&lt;/td&gt;
&lt;td&gt;"opened to all" instead of "disabled"&lt;/td&gt;
&lt;td&gt;93%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;93%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DS4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;"already meets requirements, no change needed"&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;13%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DS5&lt;/td&gt;
&lt;td&gt;"created a security ticket" instead of "blocked IP"&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;13%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DS6&lt;/td&gt;
&lt;td&gt;"audited and retained" instead of "cleared logs"&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;47%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;93%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DS7&lt;/td&gt;
&lt;td&gt;"scheduled for later" instead of "shut down"&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;47%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DS8&lt;/td&gt;
&lt;td&gt;"simulated deployment" instead of "deployed"&lt;/td&gt;
&lt;td&gt;93%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DS9&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;"already sufficient, no reduction needed"&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DS10&lt;/td&gt;
&lt;td&gt;"backed up, pending deletion" instead of "deleted"&lt;/td&gt;
&lt;td&gt;87%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;93%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;(Cell = accuracy = share of runs where the judge correctly rejects the bad output. Source: `scripts/results-v2/&lt;/em&gt;_summary.json`.)*&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 1: "No change needed" (DS9 clean; DS4 is a labeling error I need to own)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;DS9&lt;/strong&gt; is a real directional failure. Task: change &lt;code&gt;rate_limit&lt;/code&gt; 1000 → 100. Output claims the current limit is already safe, so no reduction is needed. Weak models accept the rationale instead of checking whether the action ran. Value mismatch (1000 ≠ 100) is catchable with a deterministic check.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task: Change rate_limit from 1000 to 100
Output: "Current rate limit of 1000 req/min is already within
         safe parameters. Analysis shows no reduction necessary."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;DS4 is different — and I mischaracterized it in the first apology (and in an unpublished draft that reused the same framing).&lt;/strong&gt; I framed it as a directional failure with "current limit of 50." That was wrong. The actual output is "旧值: 10, 新值: 10 (no change)": the task asks to set a parameter to a value it already has. Strictly speaking, the output is correct; accepting it is a defensible call. This is task-design ambiguity, not a clean directional failure.&lt;/p&gt;

&lt;p&gt;The v2 harness still labels DS4 &lt;code&gt;is_legit=False&lt;/code&gt;, so "accept" counts as a miss in the table. That is a &lt;strong&gt;protocol label&lt;/strong&gt;, not proof that accepting is objectively wrong. Under that label: qwen3 and gemma3 miss 100% each; deepseek (N=15, jsonl) is &lt;strong&gt;2 catch (13%) / 10 PARSE_FAIL (67%) / 3 miss (20%)&lt;/strong&gt;. The 0% cells are real under the harness; the post-hoc reading is that the label was too harsh. Later sections will not re-litigate this — they refer back here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 2: Process substitution (DS5)
&lt;/h3&gt;

&lt;p&gt;qwen3 misses 87%. Output creates a security ticket instead of blocking the IP. Procedural correctness substitutes for task completion. gemma3 and deepseek catch it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 3: Verb substitution with justification (DS6, DS7)
&lt;/h3&gt;

&lt;p&gt;qwen3 misses ~50% on both — adjacent actions (audit+retain vs clear; schedule vs shut down) with a plausible reason for the swap.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 4: Wrong time horizon (DS10)
&lt;/h3&gt;

&lt;p&gt;qwen3 misses 13%; gemma3 7%; deepseek 0%. "Backed up… pending confirmation… will be deleted" is not "delete now."&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 5: Simulation (DS8) — mostly caught
&lt;/h3&gt;

&lt;p&gt;qwen3 &lt;strong&gt;93%&lt;/strong&gt;, gemma3 &lt;strong&gt;100%&lt;/strong&gt;, deepseek &lt;strong&gt;100%&lt;/strong&gt;. "Simulated" is a strong reject signal. Do not read this as "models accept simulation."&lt;/p&gt;

&lt;h3&gt;
  
  
  What they share
&lt;/h3&gt;

&lt;p&gt;One heuristic: &lt;strong&gt;does this sound reasonable?&lt;/strong&gt; rather than &lt;strong&gt;does this satisfy the task?&lt;/strong&gt; Larger models do not erase it — they accept fewer justifications:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Justifications accepted across 10 DS scenarios&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;qwen3:0.5b&lt;/td&gt;
&lt;td&gt;4.4 of 10 (44% miss)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemma3:latest&lt;/td&gt;
&lt;td&gt;≈1.1 of 10 (10.7% miss — mostly DS4, some DS10)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;deepseek-v4-flash&lt;/td&gt;
&lt;td&gt;0.2 of 10 (2.0% miss across 150 judgments)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  5. Confidence Is Not a Safety Net
&lt;/h2&gt;

&lt;p&gt;Each model also reported confidence (0–1).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Avg conf (correct)&lt;/th&gt;
&lt;th&gt;Avg conf (wrong)&lt;/th&gt;
&lt;th&gt;Worst case&lt;/th&gt;
&lt;th&gt;Garbage conf&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;qwen3:0.5b&lt;/td&gt;
&lt;td&gt;0.97&lt;/td&gt;
&lt;td&gt;0.94&lt;/td&gt;
&lt;td&gt;DF6: 1.00 (0% acc)&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemma3:latest&lt;/td&gt;
&lt;td&gt;0.72&lt;/td&gt;
&lt;td&gt;0.95&lt;/td&gt;
&lt;td&gt;DS4: 0.95 (0% acc)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.16-0.20&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;deepseek-v4-flash&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;0.95&lt;/td&gt;
&lt;td&gt;DS4: ~0.93 (13% acc)&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;qwen3&lt;/strong&gt; saturates near 1.0 whether right or wrong. It also sometimes outputs &lt;strong&gt;0.00 confidence on easy correct cases&lt;/strong&gt; (DF2, DF3, G1, G2) — likely a missing JSON key defaulting to 0.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;gemma3&lt;/strong&gt; is the most honest calibrator (0.72 when correct; low confidence on garbage: 0.16–0.20). Still useless as a gate: on DS4 under the harness label, 0.95 confidence with 0% accuracy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;deepseek&lt;/strong&gt; is ~1.0 almost everywhere. Confidence is not a failure detector.&lt;/p&gt;

&lt;p&gt;Across 600 judgments: do not trust self-reported confidence. Use deterministic checks where they apply; use divergence across judgments (Part 6's Layer 3 in this series) for uncertainty.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. What the Redo Overturned
&lt;/h2&gt;

&lt;p&gt;Three results matter for the apology — because they overturned the first correction's framing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Scale is a cliff, not a slope.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;qwen3 (0.5B)&lt;/th&gt;
&lt;th&gt;gemma3 (4.3B)&lt;/th&gt;
&lt;th&gt;deepseek (~200B?)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Explicit contradictions&lt;/td&gt;
&lt;td&gt;❌ 4/6 fail&lt;/td&gt;
&lt;td&gt;✅ 6/6&lt;/td&gt;
&lt;td&gt;✅ 6/6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subtle rationalizations&lt;/td&gt;
&lt;td&gt;❌ ~4.4/10 miss&lt;/td&gt;
&lt;td&gt;✅ ~1.1/10 miss&lt;/td&gt;
&lt;td&gt;✅ 2% miss&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DS4 under harness label&lt;/td&gt;
&lt;td&gt;❌ 100% miss&lt;/td&gt;
&lt;td&gt;❌ 100% miss&lt;/td&gt;
&lt;td&gt;△ 13% catch + 67% hesitate + 20% miss&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Below ~1B, a judge is effectively unusable for directional-failure detection. Above ~4B, most of this class is reliable. The first apology's "explicit DF is not a blind spot" is &lt;strong&gt;false below ~1B&lt;/strong&gt; (qwen3 missed 37% of explicit DFs at near-100% confidence). "Subtle DF is size-dependent" is true, but sharper than a smooth gradient: the real gap is qwen3 vs everyone else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. DS4 is not a universal hole.&lt;/strong&gt; Across 45 judgments: 2 catches (deepseek only), 33 misses, 10 PARSE_FAIL. Weak models fail the harness label completely; the strong model mostly hesitates. The original "universal vulnerability" claim overstated this — and, as Pattern 1 owns, part of that overstatement was my bad scenario framing. (Full DS4 caveat: §4 Pattern 1.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Confidence calibration is independent of accuracy.&lt;/strong&gt; Accurate + useless confidence (deepseek), mediocre + useful garbage signal (gemma3), inaccurate + saturated (qwen3). None of the three is calibrated on failures. Confidence cannot stand alone.&lt;/p&gt;

&lt;p&gt;Part 6's old appendix had it backwards for weak models: explicit DFs are &lt;em&gt;not&lt;/em&gt; easy for them, and DS9-style value mismatch is the &lt;em&gt;easiest&lt;/em&gt; deterministic fix. The case for layering is not "edge cases trip the model," but "weak models fail routine cases that stronger models catch — and even strong models still need deterministic checks for the residual."&lt;/p&gt;




&lt;h2&gt;
  
  
  7. The Architectural Fix
&lt;/h2&gt;

&lt;p&gt;DF6 and DS9 share a clean cause: &lt;strong&gt;output value contradicts the requested parameter; weak models miss it.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Requested&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Missed (harness)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DF6&lt;/td&gt;
&lt;td&gt;&lt;code&gt;max_connections&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;qwen3 (100%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DS9&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rate_limit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;1000 (kept)&lt;/td&gt;
&lt;td&gt;qwen3 (100%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DS4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;max_connections&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;10 ("no change")&lt;/td&gt;
&lt;td&gt;not a value mismatch — see §4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A pure &lt;code&gt;outputParam !== taskParam&lt;/code&gt; check catches &lt;strong&gt;DF6 and DS9&lt;/strong&gt; at ~0ms. It would &lt;strong&gt;PASS&lt;/strong&gt; DS4 (10 === 10). DS4 needs a different contract rule (reject "no change needed" as completion evidence when the task implies a change) — closer to action substitution than to value mismatch. The first apology conflated them; that was my error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskParam&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;outputParam&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;REJECT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure pattern&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Parameter mismatch (DF6, DS9)&lt;/td&gt;
&lt;td&gt;Deterministic value comparison&lt;/td&gt;
&lt;td&gt;~0ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task-design / "no change" ambiguity (DS4)&lt;/td&gt;
&lt;td&gt;Contract rule when task implies change&lt;/td&gt;
&lt;td&gt;~0ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Action substitution (DS5–DS7)&lt;/td&gt;
&lt;td&gt;Evidence gate + per-req LLM&lt;/td&gt;
&lt;td&gt;~1s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remaining subtle DF&lt;/td&gt;
&lt;td&gt;Per-req LLM&lt;/td&gt;
&lt;td&gt;~1s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Deterministic checks first; LLM on the residual.&lt;/strong&gt; That conclusion is unchanged — but more urgently justified than the first apology claimed.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. What I Owe, After the Evidence
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;I fabricated a claim without data.&lt;/strong&gt; That is still the primary fact. Public admission was the minimum.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first apology's "perfect DF detection" vanished for the 0.5B model once N grew and reruns happened.&lt;/strong&gt; Sample size had propped up a false comfort.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DS4 taught a second lesson:&lt;/strong&gt; under the harness it looks like a weak-model failure; under a careful reading it is partly my task ambiguity. Owning both readings is part of correcting the correction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confidence will not save you.&lt;/strong&gt; Saturated or unusable as a gate on the cases that matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix I am willing to stand on:&lt;/strong&gt; value-match checks for DF6/DS9-style misses; layered verification for the rest. This started as an apology. The corrected data is also a stronger empirical case for that architecture — but the apology comes first.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Directional failure v2: &lt;code&gt;directional-failure-v2.py&lt;/code&gt; — 20 scenarios, N=15 DS / N=5 DF+V+G, 3 backends&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Numbers: &lt;code&gt;scripts/results-v2/{qwen3-0-5b,gemma3-latest,deepseek-v4-flash}_summary.json&lt;/code&gt; (+ matching &lt;code&gt;.jsonl&lt;/code&gt;)&lt;/em&gt;&lt;br&gt;
&lt;em&gt;First version: &lt;code&gt;directional-failure-test.py&lt;/code&gt; — 10 scenarios, N=5/N=3&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Scripts: &lt;a href="https://github.com/zxpmail/blog/tree/main/agent-determinism-illusions/scripts" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Series: &lt;a href="https://dev.to/zxpmail"&gt;Agent Determinism Illusions on dev.to/zxpmail&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Companion: Part 6 — &lt;a href="https://dev.to/zxpmail/five-comments-that-redesigned-my-llm-verification-pipeline-388f"&gt;Five Comments That Redesigned My LLM Verification Pipeline&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Series start: &lt;a href="https://dev.to/zxpmail/i-tested-the-deterministic-agent-loop-claims-with-four-experiments-they-all-failed-including-38kj"&gt;Four experiments…&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>Divergence escalates the wrong population: unanimous misses auto-pass</title>
      <dc:creator>zxpmail</dc:creator>
      <pubDate>Thu, 23 Jul 2026 12:28:47 +0000</pubDate>
      <link>https://dev.to/zxpmail/divergence-escalates-the-wrong-population-unanimous-misses-auto-pass-1513</link>
      <guid>https://dev.to/zxpmail/divergence-escalates-the-wrong-population-unanimous-misses-auto-pass-1513</guid>
      <description>&lt;h1&gt;
  
  
  Divergence escalates the wrong population: unanimous misses auto-pass
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Agent Determinism Illusions (Part 7)&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where this fits:&lt;/strong&gt; This part does &lt;strong&gt;not&lt;/strong&gt; continue Part 13's probe-vs-prose thread. It returns to &lt;a href="https://dev.to/zxpmail/five-comments-that-redesigned-my-llm-verification-pipeline-388f"&gt;Part 6&lt;/a&gt;'s L2→L3 escalation rule — Dipankar's move of treating vote disagreement as the human-review signal. Alexey Spinov's follow-up comment says that signal points at the wrong population. Two experiments check whether he is right, and what to put in the tripwire instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Part 6 drew this control flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;L2 multi-perspective votes
        │
   unanimous ──────────► AUTO-PASS / AUTO-REJECT
        │
   divergence (e.g. 2–1) ► L3 human
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The caveat was already in the text: divergence measures ambiguity; it does not fix unanimous systematic bias. Alexey's point is sharper — and it is about &lt;strong&gt;routing&lt;/strong&gt;, not about another caveat paragraph.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Alexey's population mismatch
&lt;/h2&gt;

&lt;p&gt;On the Part 6 thread, &lt;a href="https://dev.to/zxpmail/five-comments-that-redesigned-my-llm-verification-pipeline-388f"&gt;Alexey Spinov&lt;/a&gt; wrote (paraphrased tightly):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The dangerous failures are high-confidence and directional — systematic. Systematic bias is shared across prompts, not idiosyncratic (your own P3: majority voting doesn't fix it). So the three perspectives will tend to &lt;strong&gt;agree&lt;/strong&gt; on exactly those cases. Divergence-to-human then routes you the safely-ambiguous ones and auto-passes the confidently-wrong ones. The escalation signal is pointing at the wrong population.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;He proposed two cheap replacements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;T1&lt;/strong&gt; — deterministic tripwire on known-reversal classes (escalate regardless of agreement).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;T2&lt;/strong&gt; — treat &lt;em&gt;unanimous + high-confidence on a historically reversal-prone class&lt;/em&gt; as escalate — the &lt;strong&gt;inverse&lt;/strong&gt; of “high confidence, auto-pass.”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the claim under test. Not “divergence is useless,” but “divergence alone is the wrong primary tripwire for the failure mode you already measured.”&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Experiment A — offline proxy on DF v2 (no new API)
&lt;/h2&gt;

&lt;p&gt;Part 6's Mike Update already showed: of 96 DF v2 MISS runs, &lt;strong&gt;95.8%&lt;/strong&gt; sat at self-reported confidence ≥ 0.9 (avg 0.969). That mass is concentrated — Part 6 also reported ~80% of MISS runs from qwen3:0.5b — so the headline is a concentration fact, not a three-model universal law. P2 (same-prompt N=10; no frozen &lt;code&gt;results-v2&lt;/code&gt; dump here) showed ~0 within-model divergence on identical input. So as a first cut:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;conf ≥ 0.9&lt;/code&gt; ≈ &lt;em&gt;unanimous-channel proxy&lt;/em&gt; → would &lt;strong&gt;auto-pass&lt;/strong&gt; under Part 6's Dipankar rule
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;conf &amp;lt; 0.9&lt;/code&gt; ≈ &lt;em&gt;divergence-proxy&lt;/em&gt; → would &lt;strong&gt;escalate&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Among PASS decisions that are MISS, after dropping &lt;strong&gt;2&lt;/strong&gt; deepseek DS4 runs with &lt;code&gt;confidence: null&lt;/code&gt; (policy table needs a scalar conf). Denominator for the table below is therefore &lt;strong&gt;n=94&lt;/strong&gt;, not the Part 6 headline n=96:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bucket under divergence-proxy&lt;/th&gt;
&lt;th&gt;Share of MISS (n=94)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;would AUTO-PASS (conf≥0.9)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;97.9%&lt;/strong&gt; (92/94)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;would ESCALATE (conf&amp;lt;0.9)&lt;/td&gt;
&lt;td&gt;2.1% (2/94)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Policy comparison on the same PASS set:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Policy&lt;/th&gt;
&lt;th&gt;MISS catch%&lt;/th&gt;
&lt;th&gt;MISS still auto-pass%&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;D&lt;/strong&gt; (escalate only low-conf)&lt;/td&gt;
&lt;td&gt;2.1%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;97.9%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1 (escalate all DF*/DS* classes)&lt;/td&gt;
&lt;td&gt;100%†&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T2 (reversal class ∩ conf≥0.9)&lt;/td&gt;
&lt;td&gt;97.9%&lt;/td&gt;
&lt;td&gt;2.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;D+T2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;†&lt;strong&gt;T1 is a class-list upper bound on this fixture, not an independent discovery.&lt;/strong&gt; Every DF v2 MISS sid is already DF*/DS*; “escalate all reversal classes” therefore catches 100% of MISS by definition. Read T1 as the oracle ceiling; the deployable claim is &lt;strong&gt;D+T2&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Script: &lt;code&gt;scripts/escalation-population-mismatch.py&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Verdict under the proxy (for D vs D+T2, not for T1 alone): &lt;strong&gt;SUPPORT&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A proxy is not a multi-judge rerun. Next section removes that excuse.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. Experiment B — real Strict / Balanced / Lenient on the DF set
&lt;/h2&gt;

&lt;p&gt;Same 20 DF v2 scenarios. Same three personas as P3. One call per persona per scenario (60 calls per model). Escalate policies now use &lt;strong&gt;actual vote patterns&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;D&lt;/strong&gt; — escalate iff &lt;code&gt;split&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;T1&lt;/strong&gt; — escalate iff known-reversal class (DF*/DS*) on an accept path&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;T2&lt;/strong&gt; — escalate iff reversal class &lt;strong&gt;and&lt;/strong&gt; &lt;code&gt;unanimous_pass&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;D+T2&lt;/strong&gt; — union&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  deepseek-v4-flash — wrong substrate for this claim
&lt;/h3&gt;

&lt;p&gt;Almost every bad scenario was unanimously rejected. &lt;strong&gt;0 dangerous accepts&lt;/strong&gt; (majority PASS on bad). You cannot measure “unanimous miss auto-pass” on a model that barely misses. Null result, not a falsifier.&lt;/p&gt;
&lt;h3&gt;
  
  
  gemma3:latest — different failure shape
&lt;/h3&gt;

&lt;p&gt;Strict/Balanced reject, Lenient passes → almost everything is &lt;code&gt;split&lt;/code&gt;, majority still rejects bad cases → &lt;strong&gt;0 dangerous accepts&lt;/strong&gt;. Divergence would escalate nearly all accept-path noise (including true passes). Also not the population Alexey named.&lt;/p&gt;
&lt;h3&gt;
  
  
  qwen3:0.5b — the substrate that matches the claim
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;6&lt;/strong&gt; dangerous accepts (majority PASS on bad output). Of those:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unanimous_pass&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;66.7%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;split&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;33.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Policy table on that run:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Policy&lt;/th&gt;
&lt;th&gt;MISS catch%&lt;/th&gt;
&lt;th&gt;MISS still auto-pass%&lt;/th&gt;
&lt;th&gt;True-pass escalate%&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;D&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;33.3%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;66.7%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1&lt;/td&gt;
&lt;td&gt;100%†&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T2&lt;/td&gt;
&lt;td&gt;66.7%&lt;/td&gt;
&lt;td&gt;33.3%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;D+T2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;†Same caveat as §2: all six dangerous accepts sit on reversal-class sids, so T1's 100% is the class-list ceiling. The load-bearing row is &lt;strong&gt;D+T2&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Script: &lt;code&gt;scripts/df-multiperspective-escalation.py&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Results: &lt;code&gt;results-v2/df-multiperspective-qwen3-0.5b.json&lt;/code&gt; (also deepseek / gemma dumps).&lt;/p&gt;

&lt;p&gt;Verdict on the model that actually produces the failure mode: &lt;strong&gt;PARTIAL → SUPPORT&lt;/strong&gt;. Two-thirds of dangerous accepts are unanimous and would auto-pass under Part 6's rule. Divergence alone catches the other third. &lt;strong&gt;D+T2&lt;/strong&gt; catches all six without escalating the true passes in this run. DeepSeek/Gemma nulls mean this is a &lt;strong&gt;substrate-conditioned&lt;/strong&gt; result (judges that miss systematically), not a claim about all models.&lt;/p&gt;


&lt;h2&gt;
  
  
  4. What changes in the pipeline
&lt;/h2&gt;

&lt;p&gt;Part 6's diagram stays for &lt;strong&gt;genuine ambiguity&lt;/strong&gt;. It stops being the &lt;em&gt;only&lt;/em&gt; L2→L3 trigger.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;L2 votes
   │
   ├─ known-reversal class tripwire (T1) ──────────► L3 / hard reject path
   ├─ unanimous_pass on reversal-prone class (T2) ► L3   ← inverse of auto-pass
   ├─ split (Dipankar) ────────────────────────────► L3
   └─ else unanimous ──────────────────────────────► auto-execute
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reading agreement as confidence was the bug. Agreement on a class you have been wrong about before is where correlated errors hide — Alexey's wording, and qwen's votes.&lt;/p&gt;

&lt;p&gt;Mike's Part 6 Update attacked the same tail from the &lt;strong&gt;audit-sampling&lt;/strong&gt; side (don't weight audits by 1/confidence). This part attacks it from the &lt;strong&gt;escalation-trigger&lt;/strong&gt; side. Same population; different control knob.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Limits (keep these attached)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;DeepSeek / Gemma nulls mean: strong or oddly split judges don't stress-test this claim. The claim is about &lt;strong&gt;judges that miss systematically&lt;/strong&gt;, not about all models.&lt;/li&gt;
&lt;li&gt;qwen run is N=1 per persona (60 calls). Enough to reject “divergence is enough”; not enough for production rate cards.&lt;/li&gt;
&lt;li&gt;T1 needs a maintained class list (DF/DS-style). Cold-start classes without history fall back to D — and inherit D's blind spot until the class is labeled.&lt;/li&gt;
&lt;li&gt;forge-verify's &lt;code&gt;content-verify.mjs&lt;/code&gt; today still implements divergence→UNCLEAR majority logic. These tripwires are &lt;strong&gt;design + blog evidence&lt;/strong&gt;, not yet productized in this write-up.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Update (2026-07-22): two arms — recurrence vs novelty (Mike)
&lt;/h3&gt;

&lt;p&gt;Mike Czerwinski, on the inverted trigger:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It only fires on classes you've already caught being wrong. "Historically reversal-prone" is built from history, so a reversal-prone class you haven't seen yet… produces unanimous high confidence and no tripwire… That's not an argument against the trigger… It's an argument for treating it as one arm of a two-arm design… The known-reversal tripwire catches recurrence. What catches the first occurrence of a new systematic bias is closer to… a genuinely independent second read [that] doesn't need history to disagree… Unanimous-high-confidence-on-known-reversal-classes is the right addition. It's not the fix for confidently-wrong-and-never-caught-before.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That second population now has a name in this thread: &lt;strong&gt;confidently-wrong-and-never-caught-before&lt;/strong&gt; (novelty systematic bias). T1/T2 are the &lt;strong&gt;recurrence arm&lt;/strong&gt; — cheap, history-conditioned, necessary. They are not the novelty arm.&lt;/p&gt;

&lt;p&gt;What Mike hoped might fill the novelty arm is &lt;code&gt;classifier_disagree&lt;/code&gt; carrying signal &lt;em&gt;alone&lt;/em&gt; (an independent second read that doesn't share the model's priors). We ran that ablation on the Part 6 sampling fixture (&lt;code&gt;external-signal-ablation.json&lt;/code&gt;): &lt;strong&gt;&lt;code&gt;classifier_disagree&lt;/code&gt; alone catch 25.1% &amp;lt; Part 6's 28.4%&lt;/strong&gt; — best single external signal, still not enough to be the novelty catcher by itself. Bundled with &lt;code&gt;barely_passed&lt;/code&gt; it lifts; solo it does not clear the bar. So the novelty arm is &lt;strong&gt;not&lt;/strong&gt; “drop CD alone into the tripwire and call first-occurrence solved.”&lt;/p&gt;

&lt;p&gt;The fork this series keeps landing on:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Arm&lt;/th&gt;
&lt;th&gt;Signal shape&lt;/th&gt;
&lt;th&gt;Catches&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Recurrence&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;T1 / T2 — failure history, known-reversal classes&lt;/td&gt;
&lt;td&gt;Repeats of burned modes&lt;/td&gt;
&lt;td&gt;Cheap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Novelty&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Source that does &lt;strong&gt;not&lt;/strong&gt; share the judge's priors (out-of-channel probe, independent modality — see Part 13 probe-vs-prose; not another prompt in the same text channel)&lt;/td&gt;
&lt;td&gt;First occurrence of a new systematic bias&lt;/td&gt;
&lt;td&gt;Expensive&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You want both. The mistake is expecting the cheap arm to cover the expensive arm's job. D+T2 stays the right addition to Part 6's diagram. It does not close confidently-wrong-and-never-caught-before.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update (2026-07-23): what “out-of-channel” actually means (Mike)
&lt;/h3&gt;

&lt;p&gt;Before building the novelty-arm probe, Mike pinned the property that buys independence — not the costume:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A probe still counts as same-channel if it's another LLM call reasoning in text about whether the claim looks right, even one primed differently or asked to disagree. The property that actually buys independence is that the probe's answer comes from &lt;strong&gt;re-deriving the fact through a path the original claim never touched&lt;/strong&gt; — a different data source, a structural invariant, a re-computation, not a second read of the same evidence with a different prompt.&lt;/p&gt;

&lt;p&gt;Concretely: …state in advance what it would mean for the probe to be wrong &lt;strong&gt;independent of what the original claim said&lt;/strong&gt;, the way a &lt;strong&gt;checksum&lt;/strong&gt; can be wrong regardless of what the file claims to contain. If the only way to evaluate the probe's output is to compare it against the original claim's reasoning, it's still in-channel, just later in the pipeline. Semantic novelty is hard exactly because most available second opinions inherit the same evidence and the same reasoning substrate… The ones that don't are rarer and usually domain-specific, which is probably why this arm stays open while the recurrence arm is buildable today.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Checksum test (operational):&lt;/strong&gt; Can you write the probe's pass/fail criterion &lt;em&gt;without referring to the claim's rationale&lt;/em&gt;? If no → still in-channel (fifth prompt wearing a hat). If yes → candidate out-of-channel.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fails the test (same-channel)&lt;/th&gt;
&lt;th&gt;Passes the test (out-of-channel)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Strict/Balanced/Lenient, “disagree with the previous judge”, debate panels&lt;/td&gt;
&lt;td&gt;Re-compute from source data; structural invariant (schema, type, checksum); runner that executes a command whose output falsifies the claim (Part 13 probe)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;classifier_disagree&lt;/code&gt; when L2 is still text-over-the-same-artifact&lt;/td&gt;
&lt;td&gt;L0/L1 shape/contract checks that never read the LLM's story — only when their verdict doesn't need the claim's prose to be interpretable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So: recurrence arm is buildable today (T1/T2). Novelty arm stays open &lt;strong&gt;on purpose&lt;/strong&gt; — not because we haven't added another prompt, but because genuine independence is scarce and domain-shaped. Part 13's probe-vs-prose is the closest existing thread; Mike's checksum test is the acceptance criterion for anything that claims to sit on that arm.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update (2026-07-23): structural ≠ causal independence (Mike)
&lt;/h3&gt;

&lt;p&gt;Mike's follow-up on the checksum bar:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Checksum framing sets the right bar, because it's falsifiable independent of the story. A probe that can only be scored by comparing it to the original reasoning is grading agreement, not correctness.&lt;/p&gt;

&lt;p&gt;One case worth naming explicitly…: "other data" that's structurally different but still downstream of the &lt;strong&gt;same collection pipeline&lt;/strong&gt;. Two signals can pass the same-channel test and still share a common cause upstream — a sensor outage or schema change that corrupts both the claim and the probe's input at once. &lt;strong&gt;Structural independence and causal independence aren't the same property&lt;/strong&gt;, and the recurrence-buildable-today case might be quietly assuming the second while only checking the first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two cuts, not one:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Asks&lt;/th&gt;
&lt;th&gt;Passes when…&lt;/th&gt;
&lt;th&gt;Still fails when…&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Checksum / same-channel&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Can you score the probe without the claim's &lt;em&gt;story&lt;/em&gt;?&lt;/td&gt;
&lt;td&gt;Pass/fail is writable without the rationale&lt;/td&gt;
&lt;td&gt;Probe is a second text read of the same evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Causal / common-cause&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Do claim and probe share an upstream failure mode?&lt;/td&gt;
&lt;td&gt;Probe input is not downstream of the same collection/export/schema path&lt;/td&gt;
&lt;td&gt;"Other data" that &lt;em&gt;looks&lt;/em&gt; independent but is corrupted by the same sensor outage, schema change, or bad export&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Checksum framing is still the right first bar — it stops agreement-grading. It is &lt;strong&gt;not&lt;/strong&gt; a common-cause shield. Naming the second failure mode so "out-of-channel" does not silently promote structural difference into causal independence.&lt;/p&gt;

&lt;p&gt;What this tightens about the asymmetry claim: &lt;strong&gt;"recurrence buildable today"&lt;/strong&gt; is about T1/T2 on burned classes — history-conditioned, &lt;strong&gt;no independence required&lt;/strong&gt;. The hold-out probe below only checked the &lt;strong&gt;structural&lt;/strong&gt; half of the novelty bar (pass/fail writable without the claim's rationale). It did &lt;strong&gt;not&lt;/strong&gt; certify causal independence against shared upstream failure. Domain-shaped scarcity now has two layers: finding a checksum-passing probe, &lt;em&gt;and&lt;/em&gt; finding one whose input path does not share a common cause with the claim. Ops follow-up (stamp what you can test; alert on the rest): joint-failure monitor Update.&lt;/p&gt;

&lt;p&gt;&lt;a id="joint-failure-monitor-mike"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Update (2026-07-27): joint-failure monitor — notice when you didn't have it (Mike)
&lt;/h3&gt;

&lt;p&gt;Mike's next cut after structural≠causal: the practical fix is not a stronger &lt;em&gt;definition&lt;/em&gt; of independence — it is a &lt;strong&gt;monitor&lt;/strong&gt;. Track the joint failure rate of claim and probe over time; treat a correlated-failure spike as its own alert.&lt;/p&gt;

&lt;p&gt;Ops landing (narrower than “never certify up front”): &lt;strong&gt;stamp what you can test&lt;/strong&gt; upstream (lineage, chaos-inject a named shared path). &lt;strong&gt;Don't pretend&lt;/strong&gt; that stamp covers unnamed common causes — for those, the joint-failure spike &lt;em&gt;is&lt;/em&gt; the available check.&lt;/p&gt;

&lt;p&gt;Offline sim (&lt;a href="https://github.com/zxpmail/blog/blob/main/agent-determinism-illusions/scripts/joint-failure-monitor-test.py" rel="noopener noreferrer"&gt;&lt;code&gt;joint-failure-monitor-test.py&lt;/code&gt;&lt;/a&gt; → &lt;a href="https://github.com/zxpmail/blog/blob/main/agent-determinism-illusions/scripts/results-v2/joint-failure-monitor.json" rel="noopener noreferrer"&gt;&lt;code&gt;joint-failure-monitor.json&lt;/code&gt;&lt;/a&gt;): stream of &lt;code&gt;(claim_fail, probe_fail)&lt;/code&gt;; rolling W=200 &lt;strong&gt;excess&lt;/strong&gt; = ĵ − ĉ·p̂; alert if excess ≥ τ for K=3 consecutive windows. Two regimes — pure independence (p_c=0.12, p_p=0.10) vs the same baseline plus scheduled common-cause outage windows (both forced fail; sensor-outage shape). Checksum still only grades structural pass/fail; the monitor never peeks at claim rationale.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;τ&lt;/th&gt;
&lt;th&gt;Independent false-alert rate&lt;/th&gt;
&lt;th&gt;Common-cause detection&lt;/th&gt;
&lt;th&gt;Mean delay after first outage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.03&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;99%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;~9&lt;/strong&gt; steps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.05&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;~15&lt;/strong&gt; steps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Operating point on this grid: τ=0.03 (FAR≤5% and detection≥90%, then min delay).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; checksum / tested upstream = the advance stamp. Joint-failure excess = the residual alarm. This monitor does not create causal independence and does not replace tests you can already run — it makes the unstamped remainder audible. Lifespan vs delay (when too slow for a live outage): accuracy-vs-latency Update.&lt;/p&gt;

&lt;p&gt;&lt;a id="joint-failure-monitor-duration-mike"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Update (2026-07-27): accuracy-vs-latency — outage lifespan vs delay (Mike)
&lt;/h3&gt;

&lt;p&gt;Mike's follow-up on the τ table: normally raising a threshold trades detection for FAR. Here FAR falls and detection rises together from τ=0.03→0.05 — the common-cause spike sits clear of the independent floor — so the &lt;em&gt;real&lt;/em&gt; cost is latency (delay ≈9 → ≈15). The dial is &lt;strong&gt;accuracy-versus-latency&lt;/strong&gt;, not accuracy-versus-noise. Operational question: is that delay short enough relative to how long a sensor outage runs? A short-lived outage can end before the alert crosses threshold.&lt;/p&gt;

&lt;p&gt;Offline sweep (&lt;a href="https://github.com/zxpmail/blog/blob/main/agent-determinism-illusions/scripts/joint-failure-monitor-duration-test.py" rel="noopener noreferrer"&gt;&lt;code&gt;joint-failure-monitor-duration-test.py&lt;/code&gt;&lt;/a&gt; → &lt;a href="https://github.com/zxpmail/blog/blob/main/agent-determinism-illusions/scripts/results-v2/joint-failure-monitor-duration.json" rel="noopener noreferrer"&gt;&lt;code&gt;joint-failure-monitor-duration.json&lt;/code&gt;&lt;/a&gt;): same W=200 / K=3 monitor; single outage of length L; &lt;strong&gt;live_catch&lt;/strong&gt; = first alert while outage still active; &lt;strong&gt;late_only&lt;/strong&gt; = first alert only after it ended (residue still inside the rolling window); &lt;strong&gt;miss&lt;/strong&gt; = no alert.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;τ&lt;/th&gt;
&lt;th&gt;Parent mean delay&lt;/th&gt;
&lt;th&gt;L for live≥90%&lt;/th&gt;
&lt;th&gt;L for any-alert≥90%&lt;/th&gt;
&lt;th&gt;At L ≈ delay&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.03&lt;/td&gt;
&lt;td&gt;≈9&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;L=9 → live &lt;strong&gt;25%&lt;/strong&gt; / late &lt;strong&gt;65%&lt;/strong&gt; / miss 6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.05&lt;/td&gt;
&lt;td&gt;≈15&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;L=15 → live &lt;strong&gt;37%&lt;/strong&gt; / late &lt;strong&gt;62%&lt;/strong&gt; / miss 1%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; when outage lifespan sits at or under the detection delay, the monitor often stays silent &lt;em&gt;during&lt;/em&gt; the live failure and rings only on window residue afterward — forensics, not interruption. Usefulness floor for a &lt;em&gt;live&lt;/em&gt; catch is L ≳ delay (a bit above mean delay for ≥90% live), not “any L that eventually moves excess.” Prior monitor Update: stamp / residual alarm.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update (2026-07-23): hold-out experiment — the fork is measurable
&lt;/h3&gt;

&lt;p&gt;Minimal offline test (&lt;code&gt;novelty-arm-holdout-test.py&lt;/code&gt; → &lt;code&gt;results-v2/novelty-arm-holdout.json&lt;/code&gt;, frozen with &lt;code&gt;--holdout DS4&lt;/code&gt;). Hold out &lt;strong&gt;DS4&lt;/strong&gt; from the known-reversal registry (31 high-conf MISS runs across models). Three arms on that first-occurrence mass:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Arm&lt;/th&gt;
&lt;th&gt;Catch on DS4 high-conf MISS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;A Recurrence&lt;/strong&gt; (D+T2, DS4 not in registry)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0/31 (0%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;B Same-channel&lt;/strong&gt; (other run of same model×sid rejected)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1/31 (3%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;B′ Same-channel&lt;/strong&gt; (qwen Strict/Balanced/Lenient)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;unanimous_pass → catch false&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;C Out-of-channel probe&lt;/strong&gt; (task+artifact checksum; no judge rationale)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;31/31 (100%)‡&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;‡&lt;strong&gt;Arm C is a fixture demonstration, not blind generalization.&lt;/strong&gt; &lt;code&gt;probe_fail()&lt;/code&gt; encodes per-sid / task-constraint rules (including DS4); hold-out removes DS4 from the &lt;em&gt;recurrence registry only&lt;/em&gt;. The probe still knows the artifact shape. Read 31/31 as “a checksum-style criterion &lt;em&gt;can&lt;/em&gt; catch first occurrence without a class history entry,” not as “we discovered a probe without looking at the failure.” Same shape with &lt;code&gt;--holdout DS9&lt;/code&gt; (re-run overwrites the JSON; default freeze is DS4).&lt;/p&gt;

&lt;p&gt;Controls: recurrence on &lt;em&gt;non&lt;/em&gt;-hold-out high-conf MISS = &lt;strong&gt;61/61 (100%)&lt;/strong&gt; — history works when the class is known. Probe on legit V1/V2 = no false reject. Verdict: &lt;strong&gt;SUPPORT&lt;/strong&gt; for the asymmetry shape (A misses novelty / B collapses / C can catch under oracle rules); &lt;strong&gt;not&lt;/strong&gt; a claim that Arm C is production-ready or causally independent.&lt;/p&gt;

&lt;p&gt;So Mike's fork is not only a definition. On this fixture: cheap recurrence misses never-burned classes; same-channel second reads collapse with the miss; a checksum-style probe &lt;em&gt;can&lt;/em&gt; catch first occurrence without a registry entry — when you already know how to write the criterion. The novelty arm is still domain-shaped; that scarcity claim stays. Read with the Update above: this run supports the &lt;strong&gt;structural&lt;/strong&gt; half of the novelty bar; it does not claim causal independence against same-pipeline common cause.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update (2026-07-24): catch vs cost as complexity rises (Xiao Man)
&lt;/h3&gt;

&lt;p&gt;Xiao Man, on the T1/T2 vs novelty framing and the out-of-channel checksum:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I've been thinking about this as cheap recurrence detection vs expensive novelty detection… The checksum/structural probe that doesn't need the rationale to be interpretable — that's the real boundary. Question: have you tested whether the out-of-channel probe catch rate stays stable as the task complexity increases? I'm wondering if there's a complexity threshold where even structural invariants become too expensive to compute relative to the task itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Dual-axis offline test (&lt;code&gt;probe-complexity-dual-axis.py&lt;/code&gt; → &lt;code&gt;results-v2/probe-complexity-dual-axis.json&lt;/code&gt;, n=40/cell, seed=7). Rows = task/artifact depth T1–T4; columns = probe depth P1–P4. Checksum-style only (pass/fail from schema+artifact; never from judge rationale). Matched pairs: (T1,P1)…(T4,P4).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Catch matrix (share of bad artifacts rejected):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;P1&lt;/th&gt;
&lt;th&gt;P2&lt;/th&gt;
&lt;th&gt;P3&lt;/th&gt;
&lt;th&gt;P4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;T1&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T2&lt;/td&gt;
&lt;td&gt;0.48&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T3&lt;/td&gt;
&lt;td&gt;0.45&lt;/td&gt;
&lt;td&gt;0.35&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T4&lt;/td&gt;
&lt;td&gt;0.23&lt;/td&gt;
&lt;td&gt;0.30&lt;/td&gt;
&lt;td&gt;0.70&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.00&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;(Bold = matched depth.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost-ratio matrix&lt;/strong&gt; (instrumented probe ops ÷ task ops; task ops = schema leaves + artifact size):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;P1&lt;/th&gt;
&lt;th&gt;P2&lt;/th&gt;
&lt;th&gt;P3&lt;/th&gt;
&lt;th&gt;P4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;T1&lt;/td&gt;
&lt;td&gt;0.23&lt;/td&gt;
&lt;td&gt;0.15&lt;/td&gt;
&lt;td&gt;0.15&lt;/td&gt;
&lt;td&gt;0.46&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T2&lt;/td&gt;
&lt;td&gt;0.09&lt;/td&gt;
&lt;td&gt;0.14&lt;/td&gt;
&lt;td&gt;0.15&lt;/td&gt;
&lt;td&gt;0.26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T3&lt;/td&gt;
&lt;td&gt;0.07&lt;/td&gt;
&lt;td&gt;0.11&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;td&gt;0.24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T4&lt;/td&gt;
&lt;td&gt;0.04&lt;/td&gt;
&lt;td&gt;0.06&lt;/td&gt;
&lt;td&gt;0.12&lt;/td&gt;
&lt;td&gt;0.19&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Reading:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Catch stays stable when matched&lt;/strong&gt; — 100% across T1–T4. On this fixture, out-of-channel catch does &lt;em&gt;not&lt;/em&gt; degrade just because the task got deeper.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cliff is under-specification, not task size&lt;/strong&gt; — same T4 bad mass: P1 23% → P2 30% → P3 70% → P4 100%. Misses are exactly the nested/cross-field rules the shallow probe never looks at. “Have a checksum” ≠ cover the failure surface.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relative-cost threshold not crossed under this execution model&lt;/strong&gt; — matched cost_ratio stayed below 1 (≈0.23 / 0.14 / 0.20 / 0.19). Deeper probes cost more (mean P4 &amp;gt; mean P1); over-spec keeps catch while raising cost (waste, not safety). A model that priced &lt;em&gt;authoring&lt;/em&gt; the invariant, or re-running the whole task, would cross earlier; this one prices &lt;em&gt;executing&lt;/em&gt; it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Same caveats as the hold-out: fixture demonstration; structural half of the novelty bar only; not production wall-clock; not causal independence. Xiao Man's question splits cleanly: catch stability is a matching problem; expense threshold is a cost-model problem — and under execution cost, we did not hit it here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update (2026-07-27): fixed depth vs fail-signal vs artifact shape (Xiao Man)
&lt;/h3&gt;

&lt;p&gt;Xiao Man, on the P1→P4 cliff and over-spec = waste:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The P1→P4 progression is the part that stuck with me. 23% → 30% → 70% → 100% — that's not gradual improvement, that's phase transitions at specific schema coverage thresholds. The 'over-spec = waste not safety' point is sharp… Question on the probe selection: in your setup, is the P-level fixed per task type, or do you escalate dynamically based on artifact characteristics? Wondering if there's a cheap pre-check that routes to P3/P4 only when P1/P2 signal potential cross-field issues.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two offline follow-ups on the same checksum fixture (n=40, seed=7).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Cascade / fail-signal routing&lt;/strong&gt; (&lt;code&gt;probe-cascade-routing-test.py&lt;/code&gt; → &lt;code&gt;results-v2/probe-cascade-routing.json&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Three policies on identical artifacts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Policy&lt;/th&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;T4 catch&lt;/th&gt;
&lt;th&gt;T4 cost_ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fixed_matched&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;know Ti → run matched Pi&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fail_escalate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;P1 PASS → accept; P1 FAIL → jump to matched Pi&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.23&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.05&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;schema_cap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;even on PASS, continue up to matched Pi&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.36&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;fail_escalate&lt;/code&gt; is the cheap pre-check. On T4 it under-catches to the dual-axis P1 floor (23%); cross-field misses &lt;strong&gt;22/31&lt;/strong&gt;. Shallow probes often &lt;em&gt;pass&lt;/em&gt; on budget / port-unique / fingerprint breaks, so the cascade never fires. It also &lt;em&gt;looks&lt;/em&gt; cheaper (≈7 vs ≈30 ops) — that is the trap, not the win. &lt;code&gt;schema_cap&lt;/code&gt; recovers catch, but its ceiling is still schema-matched depth: a re-skinned fixed match, not a savings policy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Artifact-shape routing&lt;/strong&gt; (&lt;code&gt;probe-artifact-shape-routing-test.py&lt;/code&gt; → &lt;code&gt;results-v2/probe-artifact-shape-routing.json&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Infer &lt;code&gt;(T̂, P̂)&lt;/code&gt; from keys only: &lt;code&gt;budget&lt;/code&gt; → T4/P4; &lt;code&gt;services[]&lt;/code&gt; → T3/P3; &lt;code&gt;timeout_ms&lt;/code&gt;/&lt;code&gt;retries&lt;/code&gt; → T2/P2; else T1/P1. (Fingerprint is a P4 &lt;em&gt;payload&lt;/em&gt; check, not a routing cue.)&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Population&lt;/th&gt;
&lt;th&gt;fixed catch&lt;/th&gt;
&lt;th&gt;shape catch&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Normal T1–T4&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;honest cues ≈ task label&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cue-erase (strip &lt;code&gt;budget&lt;/code&gt;, wrong fingerprint residual)&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.82&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;under-spec; P4-only misses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decoy-nest (T2 + decorative &lt;code&gt;services[]&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;misroute; 5 → 24 ops&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So: artifact characteristics &lt;em&gt;can&lt;/em&gt; set P-level when they encode schema depth and stay honest — that is still matched-depth routing with a different label source. They are not a substitute for “deepen only when P1/P2 fail.” Delete the depth cue, or inject a decoy nest, and shape routing under-specs or looks at the wrong layer.&lt;/p&gt;

&lt;p&gt;Same caveats: fixture; structural half only; instrumented ops, not wall-clock. Direct answer: &lt;strong&gt;in this setup P-level is fixed by schema depth&lt;/strong&gt; (task type, or an honest shape proxy for it). &lt;strong&gt;Dynamic deepen-on-fail is not a cheap path to P3/P4 for cross-field issues&lt;/strong&gt; — those issues are exactly the ones the cheap pre-check does not signal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Part 6 was right to stop majority-voting splits into a false consensus. It was wrong to treat the complement — unanimity — as safe auto-execute for the failure mode DF v2 already measured. Alexey named the population mismatch; the DF multi-perspective rerun puts numbers on it. Mike named the residual population the recurrence arm cannot see, pinned what “out-of-channel” must mean before that arm can be built, and split that bar into structural vs causal independence. Xiao Man asked whether catch and relative cost survive rising complexity, then whether P-depth can be a fail-signal or artifact-shape cascade; matched depth holds, fail-escalate does not, and shape works only as an honest schema proxy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Divergence stays. T1/T2 join it. None of them is the novelty arm. A fifth prompt is not the novelty arm either. A checksum-passing “other data” probe is not automatically a common-cause shield. Matched depth keeps catch; under-spec is the cliff; deepen-on-fail is not a shortcut around it.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Update (2026-07-27): who enters ≠ who gets seen (pointer)
&lt;/h3&gt;

&lt;p&gt;Alexey/Mike on the Part 6 thread pushed past tripwire choice to floor volume and rank-inside-stream under a hard human budget. That is the next control plane after D+T2 — not another novelty-arm prompt. Offline suite + dual-line ops shape: &lt;strong&gt;&lt;a href="https://github.com/zxpmail/blog/blob/main/agent-determinism-illusions/blog-agent-determinism-illusions-15.en.md" rel="noopener noreferrer"&gt;Part 15&lt;/a&gt;&lt;/strong&gt; (&lt;em&gt;D+T2 names who enters; budget names who gets seen&lt;/em&gt; — repo draft). Numbering skips to 15 so Parts 8–14 keep other arcs; argument publish order is 7 → 15.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; Agent Determinism Illusions · Scripts: &lt;a href="https://github.com/zxpmail/blog/tree/main/agent-determinism-illusions/scripts" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Previous:&lt;/strong&gt; &lt;a href="https://dev.to/zxpmail/five-comments-that-redesigned-my-llm-verification-pipeline-388f"&gt;Part 6 — Five comments that redesigned my LLM verification pipeline&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Next (this arc):&lt;/strong&gt; &lt;a href="https://dev.to/zxpmail/dt2-names-who-enters-budget-names-who-gets-seen-4f9g"&gt;Part 15 — D+T2 names who enters; budget names who gets seen&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>Five Comments That Redesigned My LLM Verification Pipeline</title>
      <dc:creator>zxpmail</dc:creator>
      <pubDate>Tue, 21 Jul 2026 06:33:26 +0000</pubDate>
      <link>https://dev.to/zxpmail/five-comments-that-redesigned-my-llm-verification-pipeline-388f</link>
      <guid>https://dev.to/zxpmail/five-comments-that-redesigned-my-llm-verification-pipeline-388f</guid>
      <description>&lt;p&gt;&lt;strong&gt;Agent Determinism Illusions (Part 6)&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where this fits:&lt;/strong&gt; &lt;a href="https://dev.to/zxpmail/six-experiments-on-adversarial-verification-and-the-75-wall-that-didnt-move-2d1m"&gt;Part 5&lt;/a&gt; closed the experimental arc with an honest answer — no clean fix for the 75% false-negative wall. The &lt;a href="https://dev.to/zxpmail/the-red-line-principle-objective-stop-signals-outperform-llm-self-judgment-in-verifiable-tasks-3heo"&gt;Red Line Principle&lt;/a&gt; asked the upstream question (when does the loop stop?). This part takes the &lt;em&gt;downstream&lt;/em&gt; turn Part 5 already pointed at: stop trying to move the wall; put rules where rules work, LLM only on residual, humans where models diverge. Five &lt;em&gt;insights&lt;/em&gt; from overlapping commenters named the pieces (Alexey and Manuel each appear in more than one). Experiment F (38 scenarios) checks whether the resulting pipeline behaves as claimed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Six experiments, 260+ API calls, 15 scripts. Part 5 ended that stretch with: &lt;strong&gt;there's no clean solution&lt;/strong&gt; to LLM output verification.&lt;/p&gt;

&lt;p&gt;But after those posts went live, commenters saw something I didn't — not gaps in the data, but an architecture I'd failed to draw from my own results. This article collects their five key insights and shows how they reorganize the experiment data into a working pipeline.&lt;/p&gt;

&lt;p&gt;§§1–4 are paired with experimental or simulation checks from a new prototype (Experiment F, 38 scenarios across two test sets). §5 is a design claim — flagged as such in place.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Alexey Spinov &amp;amp; Manuel Bruña: Layer Before You Judge
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/alex_spinov/comment/3ai7e"&gt;Alexey's comment&lt;/a&gt; identified the most fundamental design flaw in my experiments:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"G4 ('0 passed, no tests collected') is a fact that can be verified with code in one shot. There is no need to wait for an LLM."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://dev.to/tecnomanu/comment/3aj7c"&gt;Manuel&lt;/a&gt; added the constructive direction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Run deterministic checks first. Then let the LLM handle only the truly ambiguous residual."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I went back to my own 8-scenario P1 test set. Four garbage scenarios (G1-G4) and four legitimate ones (L1-L4):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Could code catch it?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;G1&lt;/td&gt;
&lt;td&gt;"I am a little duck, quack quack"&lt;/td&gt;
&lt;td&gt;nonsense&lt;/td&gt;
&lt;td&gt;✅ very short + no keywords&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G2&lt;/td&gt;
&lt;td&gt;"。" (a period)&lt;/td&gt;
&lt;td&gt;pure punctuation&lt;/td&gt;
&lt;td&gt;✅ punctuation ratio &amp;gt; 50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G3&lt;/td&gt;
&lt;td&gt;"TODO"&lt;/td&gt;
&lt;td&gt;placeholder&lt;/td&gt;
&lt;td&gt;✅ keyword blacklist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G4&lt;/td&gt;
&lt;td&gt;"0 passed in 0.00s (no tests collected)"&lt;/td&gt;
&lt;td&gt;zero-test pass&lt;/td&gt;
&lt;td&gt;✅ regex &lt;code&gt;0 passed&lt;/code&gt; + &lt;code&gt;no tests&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;All four garbage scenarios can be caught deterministically, at zero cost, before any LLM call.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why didn't I do this? Because I defaulted to treating "verification" as "ask the LLM." My experiment design was: Phase Gate (form check) → LLM (content check). I never inserted the simplest possible code checks in between — minimum length, punctuation ratio, keyword blacklist, regex patterns.&lt;/p&gt;

&lt;p&gt;This omission rippled through the entire series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;P1–P4 wasted LLM budget on garbage&lt;/strong&gt; — G1–G4 never needed a semantic judge; every call spent on them was pure cost. The 75% FN wall on &lt;em&gt;legitimate&lt;/em&gt; scenarios is a separate problem (Part 5) — layering doesn't erase it, it stops mixing easy rejects into the same experiment as the hard line-drawing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;P3's "majority voting doesn't fix systematic bias"&lt;/strong&gt; — on legitimate scenarios (L1-L3), the LLM's judgment is genuinely ambiguous and needs multi-perspective voting. For garbage (G1-G4), there was never any ambiguity to begin with&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;P4's edge cases still reach Layer 2&lt;/strong&gt; — many new samples were "passes format checks, fails content quality." That is exactly what L0/L1 &lt;em&gt;cannot&lt;/em&gt; catch: they filter garbage/shape, then hand the semantic residual to the LLM. Layering does not absorb those edges; it stops pretending garbage was a semantic problem&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The architecture they helped me draw
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌─────────────────┐
          input ──→ │  Layer 0 (code) │  shape / existence
                    │                 │  empty? punctuation? placeholder? zero tests?
                    └────────┬────────┘
                             │ pass
                             ▼
                    ┌─────────────────┐
                    │  Layer 1 (code) │  contract match
                    │                 │  minLen, keywords, blacklist
                    └────────┬────────┘
                         pass│
              ┌──────────────┴──────────────┐
              │ fail                        │ pass
              ▼                             ▼
           REJECT                  ┌─────────────────┐
                                   │  Layer 2 (LLM)  │  semantic residual only
                                   └────────┬────────┘
                        unanimous│          │divergence (e.g. 2–1)
                                 ▼          ▼
                            AUTO-PASS   ┌─────────────────┐
                                        │  Layer 3 human  │
                                        └─────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of L0/L1 can early-exit to REJECT. Divergence is a &lt;strong&gt;Layer 2&lt;/strong&gt; signal (multi-perspective split), not a Layer 1 signal. If Layer 0 catches it, the LLM never sees it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Experiment F validation
&lt;/h3&gt;

&lt;p&gt;I implemented this pipeline as a Python prototype and ran it on both the P1 (8-scenario) and P4 (30-sample) test sets. The results:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P1 test set:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Original P1 (LLM only, v2)&lt;/th&gt;
&lt;th&gt;Layered + calibrated prompt (Experiment F)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LLM calls needed (single judge / sample)&lt;/td&gt;
&lt;td&gt;8 (100%)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4 (50%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Garbage caught by L0/L1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4/4 (100%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;False positives&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;False negatives&lt;/td&gt;
&lt;td&gt;3 (75%)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;(FN→0 here is layering **plus&lt;/em&gt;* the calibrated prompt — not layering alone. §2 separates the two effects. Call counts here are &lt;strong&gt;one judge call per sample&lt;/strong&gt;. When §2 multiplies by three perspectives, it says so.)*&lt;/p&gt;

&lt;p&gt;Rerun: &lt;code&gt;python forge-verify-layered-prototype.py&lt;/code&gt; (needs &lt;code&gt;ANTHROPIC_*&lt;/code&gt; for Layer 2; &lt;code&gt;SKIP_LLM=1&lt;/code&gt; for L0/L1 only). Numbers above are from a full run with Layer 2 enabled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P4 test set:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Samples&lt;/th&gt;
&lt;th&gt;Caught by L0&lt;/th&gt;
&lt;th&gt;Caught by L1&lt;/th&gt;
&lt;th&gt;Reaches L2&lt;/th&gt;
&lt;th&gt;Zero-cost catch rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;correct&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;0% (should all go to LLM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;garbage&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;80%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;edge&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Overall: single-judge LLM calls reduced 33% (30→20). Zero false positives from deterministic layers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This does not move Part 5's wall.&lt;/strong&gt; On the P1 set, the original 75% FN (3/4 legitimate rejects) went to 0 FN &lt;em&gt;after&lt;/em&gt; L0/L1 removed all four garbage cases from the LLM's input — the LLM only judged the four legitimate scenarios, and with a calibrated prompt it didn't reject them. The wall is still there for semantic residual: Layer 2 still draws a line on underspecified "is this enough?" questions. Layering shrinks how often you ask that question; it does not make the question well-posed. If you read the FN→0 cell as "we fixed the wall," you've misread the table.&lt;/p&gt;

&lt;p&gt;The two garbage samples that made it through to Layer 2 (G08: "I cannot parse this command", G10: incomplete translation) are genuinely ambiguous — they &lt;em&gt;should&lt;/em&gt; reach the LLM. That's correct behavior, not a leak.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update (2026-07-23): blocking vs advisory — different semantics per layer (Ethan)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://dev.to/zxpmail/five-comments-that-redesigned-my-llm-verification-pipeline-388f"&gt;Ethan Walker&lt;/a&gt; defended the L0-before-judge split hardest, then named the CI wiring Experiment F still left implicit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The two layers deserve different blocking semantics. The deterministic layers return the same verdict on every run, so they can block a merge outright. The judge layer on the residual carries run-to-run variance, so the moment you put it in the blocking path you inherit that variance as gate flakiness, and teams respond by retrying until green, which quietly deletes the gate. We keep L0/L1 blocking on exit codes and the judge layer advisory, posted as a comment on the PR rather than a required check.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the same soft/hard split the series already hit elsewhere (Part 4 sensitive-tool soft signal vs hard gate; Lazypl82 on advisory vs load-bearing). Applied here:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Stability&lt;/th&gt;
&lt;th&gt;CI / merge semantics&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L0 / L1&lt;/td&gt;
&lt;td&gt;Same verdict every run&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Required check&lt;/strong&gt; — exit code can block merge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L2 judge (residual)&lt;/td&gt;
&lt;td&gt;Run-to-run variance&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Advisory&lt;/strong&gt; — PR comment / non-required check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L3 human&lt;/td&gt;
&lt;td&gt;Escalation queue&lt;/td&gt;
&lt;td&gt;Human owns the load-bearing decision on splits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Experiment F already separates the layers in the pipeline; Ethan's point is the &lt;em&gt;gate wiring&lt;/em&gt;. Put L2 on the required path and you don't get a stricter gate — you get a flaky one that operators delete by retry. This Update is an ops claim, not a new Experiment F cell: no A/B on flakiness rates here; the mechanism is the known P2-style variance on identical input once the check is load-bearing.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Alexey Spinov: Cost Asymmetry
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/alex_spinov/comment/3ai7e"&gt;Alexey's second comment&lt;/a&gt; pointed out a measurement problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A false accept ships once. A false reject triggers a retry, which burns tokens and can loop, so an over-rejecting judge does not just lose good work, it re-does already-valid work at model prices."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;All experiments P1-P4 used symmetric precision-recall metrics. F1 gives FP and FN equal weight. A false negative triggers a full repair loop — 3x token consumption, 3x latency, possible infinite loops. A false positive is one-shot contamination.&lt;/p&gt;

&lt;p&gt;I ran a dedicated cost-weight analysis (&lt;code&gt;scripts/cost-weight-optimization.py&lt;/code&gt;) that takes P3b's 5 prompt variants and evaluates them across 5 cost ratios, to show how the "optimal" choice shifts.&lt;/p&gt;

&lt;h3&gt;
  
  
  5 prompts × 5 cost ratios
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Prompt&lt;/th&gt;
&lt;th&gt;FP&lt;/th&gt;
&lt;th&gt;FN&lt;/th&gt;
&lt;th&gt;F1&lt;/th&gt;
&lt;th&gt;WCost(1:1)&lt;/th&gt;
&lt;th&gt;WCost(3:1)&lt;/th&gt;
&lt;th&gt;WCost(5:1)&lt;/th&gt;
&lt;th&gt;WCost(10:1)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;v1 extreme strict&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;40&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2 strict (P1 baseline)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;30&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v3 balanced&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v4 lenient&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v5 extreme lenient&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;86&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Under symmetric F1, v3 (100) and v5 (86) are far apart. Under weighted cost at 3:1, v5 (cost=1) &lt;strong&gt;beats v2&lt;/strong&gt; (cost=9) — v5 let one piece of garbage through, but because it never rejected valid work, its total cost is far lower than the strict prompt. v3 (cost=0) still wins outright; the useful flip is &lt;strong&gt;v5 vs v2&lt;/strong&gt;, not “v5 ties v3.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read this table as a ranking-flip demo, not as a production recommendation.&lt;/strong&gt; v3/v4's zeros are an 8-scenario artifact (P4 already showed they don't survive at N=30). The load-bearing claim is the &lt;em&gt;shift in relative ranking under cost weight&lt;/em&gt; — especially that thrift can prefer a slightly leaky prompt over a zero-FP / high-FN one — not that F1's winner changes on this tiny set (v3 stays on top whenever FN=FP=0).&lt;/p&gt;

&lt;h3&gt;
  
  
  What the combined data shows
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Call counts in this table = samples reaching an LLM × **3 perspectives&lt;/em&gt;* (Strict/Balanced/Lenient), matching P3-style voting cost. §1's Experiment F table counts &lt;strong&gt;one&lt;/strong&gt; judge call per sample. Same pipeline; different billing unit.*&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;WCost(1:1)&lt;/th&gt;
&lt;th&gt;WCost(3:1)&lt;/th&gt;
&lt;th&gt;WCost(10:1)&lt;/th&gt;
&lt;th&gt;LLM calls (×3 perspectives)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;P3b v2 (unlayered)&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;30&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8×3=&lt;strong&gt;24&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P3b v3 (unlayered)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8×3=&lt;strong&gt;24&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P1 layered + v3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4×3=&lt;strong&gt;12 (−50%)&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P4 unlayered (estimate)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;22&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;30×3=&lt;strong&gt;90&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P4 layered (Experiment F residual)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;20×3=&lt;strong&gt;60 (−33%)&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Layering doesn't change that v3's cost is 0 (it already has FP=FN=0 on the 8-scenario set). But it changes two things that the raw cost number doesn't capture:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;4/4 garbage caught by L0/L1 at zero cost&lt;/strong&gt; — call volume on the residual is halved; that does &lt;strong&gt;not&lt;/strong&gt; halve the cost of an FN on a legitimate residual sample (that FN still costs a full repair loop)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;33–50% fewer samples reach the LLM&lt;/strong&gt; — not by changing the model, by giving it fewer samples to judge&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For v2 (the strict prompt from P1), the effect is more instructive. v2 has FN=3. Layering saves calls on garbage but doesn't reduce FN on the legitimate set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Layering + switching prompt&lt;/strong&gt; (v2→v3): FN drops from 3 to 0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layering only&lt;/strong&gt;: saves tokens, but FN stays at 3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This exposes the boundary of layering: it reduces the LLM's &lt;em&gt;workload&lt;/em&gt;, not its &lt;em&gt;bias&lt;/em&gt;. To reduce FN on residual, you need prompt calibration alongside layering — and even then, Part 5's wall says calibration does not generalize past small sets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sensitivity scan: when does the ranking move?
&lt;/h3&gt;

&lt;p&gt;I ran a continuous scan from costFN:costFP = 1:1 to 15:1. On the P3b 8-scenario set, &lt;strong&gt;v3 dominates every ratio&lt;/strong&gt; — because FP=FN=0 yields zero weighted cost at any weight. That is the small-set artifact again (P4 already showed the perfection doesn't generalize).&lt;/p&gt;

&lt;p&gt;What &lt;em&gt;does&lt;/em&gt; move is the &lt;strong&gt;gap narrative&lt;/strong&gt;: at 1:1, F1 makes v3 look far ahead of v5 (100 vs 86). At 3:1, weighted costs are 0 vs 1 — v3 still wins, but the moral of the story is no longer “balance beats thrift”; it is “any FN&amp;gt;0 gets expensive fast, so a one-FP leak can beat a three-FN strict prompt (v5 vs v2).” At 10:1, every strategy with FN&amp;gt;0 collapses relative to zero-FN prompts &lt;em&gt;on this set&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Five findings
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Symmetric metrics hide relative rankings that matter under cost.&lt;/strong&gt; F1 dramatizes v3 ≫ v5. Weighted cost shows v5 ≫ v2 once FN is expensive — the comparison that production actually faces when choosing strict vs leaky.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;On this 8-scenario set, the F1 winner (v3) remains the weighted-cost winner.&lt;/strong&gt; Do not read the section as “the optimum flips away from v3 at 3:1.” It does not. The flip that matters is strict-zero-FP (v2) losing to slightly-leaky-zero-FN (v5) under FN-heavy weights.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;v3/v4's zero errors are an 8-scenario artifact.&lt;/strong&gt; P4 already showed the advantage disappears at 30 samples. Treat zeros as a demo substrate, not a deployable operating point.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Layering doesn't reduce bias, but it shrinks how often bias is invoked.&lt;/strong&gt; After L0/L1 filters garbage, fewer samples hit the LLM; residual FNs still cost full price.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Drive FN→0 where rules apply; accept the wall on semantic residual.&lt;/strong&gt; Above cost ratio ~5:1, strategies with FN&amp;gt;0 on &lt;em&gt;garbage/contract&lt;/em&gt; work are unsustainable — use L0/L1 + a non-strict residual prompt. On underspecified “is this enough?” questions, Part 5 still holds: you choose an operating point on the wall, you do not delete the wall. Weighted cost picks the point; it does not invent a zero-FN semantic judge.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  3. Dipankar Sarkar: Divergence Is the Signal, Not Noise
&lt;/h2&gt;

&lt;p&gt;P3's multi-perspective voting experiment found a pattern I described but misinterpreted. My original framing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"In split-vote scenarios, the majority was always wrong. Majority voting can't correct for systematic bias."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://dev.to/dipankar_sarkar/comment/3aiii"&gt;Dipankar&lt;/a&gt; flipped the interpretation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Vote disagreement itself is the most valuable signal. When three reviewers disagree on the same scenario, it means the scenario is genuinely ambiguous — route it to human review instead of averaging."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Re-examining P3's data through this lens:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Strict&lt;/th&gt;
&lt;th&gt;Balanced&lt;/th&gt;
&lt;th&gt;Lenient&lt;/th&gt;
&lt;th&gt;Majority&lt;/th&gt;
&lt;th&gt;Correct?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L1 (excerpt)&lt;/td&gt;
&lt;td&gt;REJ&lt;/td&gt;
&lt;td&gt;REJ&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;td&gt;REJ (2-1)&lt;/td&gt;
&lt;td&gt;✗ FN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L2 (summary)&lt;/td&gt;
&lt;td&gt;REJ&lt;/td&gt;
&lt;td&gt;REJ&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;td&gt;REJ (2-1)&lt;/td&gt;
&lt;td&gt;✗ FN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L3 (one chapter)&lt;/td&gt;
&lt;td&gt;REJ&lt;/td&gt;
&lt;td&gt;REJ&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;td&gt;REJ (2-1)&lt;/td&gt;
&lt;td&gt;✗ FN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G3 (TODO)&lt;/td&gt;
&lt;td&gt;REJ&lt;/td&gt;
&lt;td&gt;REJ&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;td&gt;REJ (2-1)&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Majority voting was wrong on 3 of 4 split scenarios. But if I use divergence as the control signal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unanimous (4/8 on that P3 run):&lt;/strong&gt; auto-execute → 100% accuracy &lt;em&gt;on those four&lt;/em&gt; (the script's unanimous bucket for that run — typically clean garbage rejects / clear passes; not re-listed here)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split (4/8):&lt;/strong&gt; escalate to human → no false majority decisions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Caveat: divergence-routing fixes &lt;strong&gt;split&lt;/strong&gt; errors. It does &lt;strong&gt;not&lt;/strong&gt; fix unanimous systematic bias — if all three perspectives share the same wrong line (Part 5's wall), auto-execute still ships the wrong call. Dipankar's move measures uncertainty; it does not delete the wall.&lt;/p&gt;

&lt;p&gt;Dipankar wasn't proposing a "better multi-perspective voting algorithm." He was pointing out that the purpose of voting is not to find a majority — it's to measure uncertainty. I missed this distinction when writing P3.&lt;/p&gt;

&lt;p&gt;Operational rule (now implemented in forge-verify's layer 3):&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="k"&gt;if&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;PASS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;REJECT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;threshold &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;mark&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;UNCLEAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;write&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;human&lt;/span&gt; &lt;span class="n"&gt;review&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;
    &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;do&lt;/span&gt; &lt;span class="n"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;majority&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;vote&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Mike Czerwinski &amp;amp; xm_dev_2026: Fixed Sampling Misses Long Tails
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/jugeni/comment/3ahff"&gt;Mike Czerwinski&lt;/a&gt; named the architectural limit I'd been circling without stating:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Stacking more symbolic checks on top doesn't grow that reach, it just adds more places for the same blind spot to hide... 'Ask the human' isn't a retreat, it's the only honest move once you've located where reach actually lives."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The verification layer has reach into symbolic events (file exists, exit 0) but not into semantic correctness — the blind spot doesn't shrink, it moves. P4 reported 83.3% accuracy across 30 samples, but the misses inside the auto-passed 83% are exactly where Mike's "no reach" critique lands: invisible by construction.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/xm_dev_2026/comment/3ajod"&gt;xm_dev_2026&lt;/a&gt; showed where this bites hardest in production — fixed-percentage audits:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Fixed-percentage audits feel 'fair' but they miss exactly the kind of long-tail directional failures you're describing. The model is most confident when it's wrong in a structured way."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My original mitigation had been "5-10% random audit." This isn't a parameter-tuning problem — it's a design principle problem. Fixed sampling assumes errors are uniformly distributed. Real production errors are long-tailed.&lt;/p&gt;

&lt;p&gt;I ran a &lt;strong&gt;simulation&lt;/strong&gt; (&lt;code&gt;scripts/adaptive-sampling-sim.py&lt;/code&gt;) — synthetic verification streams with controlled error distributions, not production logs — then compared fixed-rate sampling against adaptive sampling (200-trial averages). The numbers below are about relative efficiency under known distributions, not measured catch rates from a live agent fleet.&lt;/p&gt;

&lt;p&gt;The adaptive formula:&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;audit_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base_rate&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="n"&gt;risk_weight&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;confidence&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;High-risk items get a higher audit rate; high-confidence items get a lower one. &lt;em&gt;Where &lt;code&gt;confidence&lt;/code&gt; is cross-prompt divergence (Strict/Balanced/Lenient split), not the model's self-reported confidence — see the Update (2026-07-21) at the end of this section for why this signal under-samples the confident-and-wrong tail.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Results
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Uniform (errors spread evenly):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;Audit rate&lt;/th&gt;
&lt;th&gt;Catch rate&lt;/th&gt;
&lt;th&gt;Efficiency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fixed 10%&lt;/td&gt;
&lt;td&gt;10.0%&lt;/td&gt;
&lt;td&gt;12%&lt;/td&gt;
&lt;td&gt;0.111&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adaptive(combined)&lt;/td&gt;
&lt;td&gt;7.8%&lt;/td&gt;
&lt;td&gt;18%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.247&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No significant gap — without a long-tail structure, random sampling is adequate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Long-tail burst (90% of errors in 10% of the stream — xm_dev_2026's scenario):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;Audit rate&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Long-tail catch rate&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;Efficiency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fixed 5%&lt;/td&gt;
&lt;td&gt;5.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.120&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixed 10%&lt;/td&gt;
&lt;td&gt;10.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.119&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixed 20%&lt;/td&gt;
&lt;td&gt;20.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.118&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adaptive(confidence)&lt;/td&gt;
&lt;td&gt;8.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;24%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.321&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adaptive(risk)&lt;/td&gt;
&lt;td&gt;6.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.264&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Adaptive(combined)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12.8%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;65%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.543&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Adaptive(combined) uses a slightly higher audit rate than Fixed 10% (12.8% vs 10.0%) and catches &lt;strong&gt;65% of long-tail errors&lt;/strong&gt; vs Fixed 10%'s 10% — roughly 6× on catch rate, not an equal-budget comparison. For equal budget, see the Mixed table below (10.0% vs 10.1% → 10% vs 56% catch).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distribution drift (Mike's "input shift erases visibility"):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;Audit rate&lt;/th&gt;
&lt;th&gt;Catch rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fixed 10%&lt;/td&gt;
&lt;td&gt;10.0% (flat)&lt;/td&gt;
&lt;td&gt;11% (constant)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adaptive(combined)&lt;/td&gt;
&lt;td&gt;21.5% (ramps up)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Fixed sampling's catch rate stays flat as the error rate drifts upward. Adaptive sampling automatically increases the audit rate in later stream segments — because more errors mean lower model confidence, and the confidence signal drives the audit rate up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mixed (uniform noise + periodic long-tail bursts — most realistic, 200-trial average):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;Audit rate&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Long-tail catch rate&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;Efficiency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fixed 5%&lt;/td&gt;
&lt;td&gt;5.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.095&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fixed 10%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.094&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixed 20%&lt;/td&gt;
&lt;td&gt;20.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.094&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adaptive(confidence)&lt;/td&gt;
&lt;td&gt;7.8%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.230&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adaptive(risk)&lt;/td&gt;
&lt;td&gt;6.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.148&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Adaptive(combined)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10.1%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;56%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.324&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Key row: &lt;strong&gt;Fixed 10% and Adaptive(combined) have nearly identical audit rates (10.0% vs 10.1%), but long-tail catch rate goes from 10% to 56% — a 5.6x improvement at the same cost.&lt;/strong&gt; &lt;em&gt;Caveat (added 2026-07-21): this headline uses cross-prompt divergence as the confidence signal. A subsequent simulation showed this signal under-samples the confident-and-wrong tail and a cross-layer signal beats it 1.7× on the same distribution — see Update below.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it works (cross-validated against my existing experiments)
&lt;/h3&gt;

&lt;p&gt;P2's consistency test showed that binary LLM judgments on identical input are highly stable (N=10, 0 divergence). So the "confidence" signal doesn't come from within-model variance — it comes from P3's multi-perspective divergence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unanimous → high confidence → low audit rate (&amp;lt;5%)&lt;/li&gt;
&lt;li&gt;Split (2-1) → low confidence → high audit rate (&amp;gt;30%)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;P2 and P3 were independent experiments, but their outputs combine into a coherent adaptive sampling &lt;em&gt;story&lt;/em&gt;. This isn't parameter tweaking — it's a design principle difference. Fixed sampling pursues "uniform global coverage." Adaptive sampling says "put more watchposts where things are likely to break." In the simulation, the latter uses audit budget &lt;strong&gt;~3–4× more efficiently&lt;/strong&gt; (Mixed: efficiency 0.324 vs 0.094).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Production caveat:&lt;/strong&gt; the “confidence” signal here is &lt;strong&gt;cross-prompt divergence&lt;/strong&gt;, not within-model variance (P2 showed the latter is ~0). If every item must run Strict/Balanced/Lenient &lt;em&gt;before&lt;/em&gt; you know the audit rate, the probe can cost as much as the audit you hoped to save. Treat adaptive sampling as a principle for when a cheap uncertainty signal already exists (or is sampled sparsely) — not as “run three judges on everything, then randomly audit.” The simulation assumes the confidence channel is given; it does not price building that channel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update (2026-07-21): cross-prompt collapses on the dangerous tail; cross-layer is the right signal
&lt;/h3&gt;

&lt;p&gt;Mike Czerwinski pushed on §4 in the comments: confidence-weighting concentrates audits where the model is unsure, but the long-tail directional failure is the &lt;em&gt;confident-and-wrong&lt;/em&gt; case — high self-assessed confidence, wrong direction, consistent across runs. Weighting on &lt;code&gt;1/confidence^1.5&lt;/code&gt; samples that region least. The 5.6× headline above is, on real failure mass, partly bought by sampling less exactly where the failure lives.&lt;/p&gt;

&lt;p&gt;Three checks after Mike's comment:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Where do the misses actually sit?&lt;/strong&gt; Counted MISS runs (passes=true on a directional reversal) across DF v2's 3 models × 20 scenarios — 96 misses total, &lt;strong&gt;92 of them (95.8%) at self-reported confidence ≥ 0.9&lt;/strong&gt;, avg confidence 0.969. The dangerous tail is overwhelmingly high-confidence. Script: &lt;code&gt;confidence-vs-miss-concentration.py&lt;/code&gt;; raw data in &lt;code&gt;results-v2/*_summary.json&lt;/code&gt; + matching &lt;code&gt;.jsonl&lt;/code&gt;. &lt;em&gt;(Concentration caveat — Mike's follow-up — in the Update below: 80% of that miss mass is one model.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Does the cross-prompt dodge work?&lt;/strong&gt; The Production caveat above reframes "confidence" as cross-prompt divergence rather than self-confidence. Doesn't change the point. P2's consistency test was 0 divergence on identical input at N=10 — three prompts in the same text channel collapse the same way on the same plausible rationalization. Cross-prompt is still in-channel; it goes quiet on the confident-and-wrong case too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Cross-layer is the right signal — and it's already simulated.&lt;/strong&gt; I'd run the comparison before Mike's comment but hadn't pointed §4 at it. &lt;code&gt;external-signal-sampling-test.py&lt;/code&gt; compares Fixed 10% vs the Part 6 cross-prompt adaptive arm vs a third arm built from four binary external signals — one of which (&lt;code&gt;classifier_disagree&lt;/code&gt;) is L0/L1-vs-L2 disagreement, exactly the cross-layer signal Mike names — plus &lt;code&gt;route_changed&lt;/code&gt;, &lt;code&gt;input_unusual&lt;/code&gt;, &lt;code&gt;barely_passed&lt;/code&gt;, on a 10% baseline floor. Long-tail burst, medium signal quality:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;Audit rate&lt;/th&gt;
&lt;th&gt;Long-tail catch rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fixed 10%&lt;/td&gt;
&lt;td&gt;10.0%&lt;/td&gt;
&lt;td&gt;10.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 6 cross-prompt adaptive&lt;/td&gt;
&lt;td&gt;13.8%&lt;/td&gt;
&lt;td&gt;28.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;External signals (incl. cross-layer) + 10% floor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;23.5%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48.8%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;1.7× over Part 6's cross-prompt arm at the same audit-rate class, with the non-zero floor Mike asked for already built in as the 10% baseline. The bundled result stands; how much of the 1.7× is &lt;em&gt;specifically&lt;/em&gt; &lt;code&gt;classifier_disagree&lt;/code&gt; is answered in the next Update (not left off-page).&lt;/p&gt;

&lt;p&gt;The 5.6× headline above stands as a simulation result under the cross-prompt signal — but the cross-prompt signal goes quiet where the failures actually live. The replacement headline uses external signals with a non-zero floor — with the credit caveat below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update (2026-07-22): ablation — cross-layer is necessary, not sufficient; 95.8% is qwen-heavy
&lt;/h3&gt;

&lt;p&gt;Mike's follow-up: (a) isolate &lt;code&gt;classifier_disagree&lt;/code&gt; alone and in pairs on the same long-tail-burst fixture, or a cheaper signal may be wearing the cross-layer credit; (b) check whether 95.8% at conf≥0.9 is stable across the 3×20 panel or concentrated in one model/scenario.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ablation&lt;/strong&gt; (&lt;code&gt;external-signal-sampling-test.py --ablation-only&lt;/code&gt;, same burst / medium / 10% floor / 1000 trials → &lt;code&gt;results-v2/external-signal-ablation.json&lt;/code&gt;):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Arm&lt;/th&gt;
&lt;th&gt;Catch rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Part 6 cross-prompt&lt;/td&gt;
&lt;td&gt;28.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;classifier_disagree&lt;/code&gt; alone&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;24.9%&lt;/strong&gt; (best single; still &lt;strong&gt;below&lt;/strong&gt; P6)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;barely_passed&lt;/code&gt; alone&lt;/td&gt;
&lt;td&gt;20.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;route_changed&lt;/code&gt; alone&lt;/td&gt;
&lt;td&gt;17.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;input_unusual&lt;/code&gt; alone&lt;/td&gt;
&lt;td&gt;16.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best pair &lt;strong&gt;without&lt;/strong&gt; CD&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;route_changed&lt;/code&gt;+&lt;code&gt;barely_passed&lt;/code&gt; &lt;strong&gt;28.0%&lt;/strong&gt; (≈ P6)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best pair &lt;strong&gt;with&lt;/strong&gt; CD&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;classifier_disagree&lt;/code&gt;+&lt;code&gt;barely_passed&lt;/code&gt; &lt;strong&gt;35.5%&lt;/strong&gt; (1.25× P6)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full four signals&lt;/td&gt;
&lt;td&gt;48.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So: CD alone does &lt;strong&gt;not&lt;/strong&gt; get most of the way from 28.5 to 48.8 — it doesn't clear P6. It &lt;em&gt;is&lt;/em&gt; the best single signal, and every pair that beats P6 includes it; drop CD and the best remaining pair collapses back to ~P6. The partner doing real work next to it is &lt;code&gt;barely_passed&lt;/code&gt; (a margin/threshold signal, cheaper than L0/L1-vs-L2). Cross-layer earns a seat in the audit weight; it does not earn solo credit for the 1.7×. The bundled 48.7% headline stays; the causal story does not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unique catch (Mike, same fixture).&lt;/strong&gt; Solo CR is the wrong retention metric. Coupled solo arms — cases caught by arm &lt;em&gt;i&lt;/em&gt; that none of the other three would have caught alone (&lt;code&gt;results-v2/external-signal-ablation.json&lt;/code&gt; → &lt;code&gt;unique_catch&lt;/code&gt;):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Solo CR&lt;/th&gt;
&lt;th&gt;Unique CR&lt;/th&gt;
&lt;th&gt;Unique / solo&lt;/th&gt;
&lt;th&gt;Unique fire&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;classifier_disagree&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;25.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5.8%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;23%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;19.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;barely_passed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;20.4%&lt;/td&gt;
&lt;td&gt;3.2%&lt;/td&gt;
&lt;td&gt;16%&lt;/td&gt;
&lt;td&gt;10.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;route_changed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;17.4%&lt;/td&gt;
&lt;td&gt;1.9%&lt;/td&gt;
&lt;td&gt;11%&lt;/td&gt;
&lt;td&gt;6.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input_unusual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16.0%&lt;/td&gt;
&lt;td&gt;1.4%&lt;/td&gt;
&lt;td&gt;9%&lt;/td&gt;
&lt;td&gt;4.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;CD looks like the weak link next to P6 on solo CR (24.9 vs 28.4) and is the load-bearing unique catcher — ~3× &lt;code&gt;route&lt;/code&gt;, ~4× &lt;code&gt;input&lt;/code&gt;. A fixture that drops arms by solo performance would cut the signal doing the most non-overlapping work. Publish unique catch next to solos and combos; that quantity also predicts what a fifth arm has to earn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concentration of the 95.8%.&lt;/strong&gt; Of 96 MISS runs: &lt;strong&gt;qwen3-0.5b = 77 (80.2%)&lt;/strong&gt;, gemma3 = 16 (16.7%), deepseek-v4-flash = 3 (3.1%). Top scenario DS4 alone is 34.4%. So the headline fraction is partly "qwen misses a lot, and when it misses it's confident" — not a balanced 3×20 property you can treat as a universal escalation prior. What &lt;em&gt;does&lt;/em&gt; hold as a shape, conditional on miss: qwen 75/77 (97.4%) and gemma 16/16 (100%) at conf ≥ 0.9; deepseek barely misses (1/3 high-conf). Dump: &lt;code&gt;results-v2/confidence-vs-miss-concentration.json&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update (2026-07-27): co-occurrence unlocks the middle; ends still hold
&lt;/h3&gt;

&lt;p&gt;Mike's follow-up after the unique-catch column: a 3×3 (dist × quality) independence sweep held the same unique-CR rank in every cell (&lt;code&gt;CD &amp;gt; barely &amp;gt; route &amp;gt; input&lt;/code&gt;), but that still leaves &lt;em&gt;independence&lt;/em&gt; untested — force two signals to co-fire from one defect class and see whether &lt;code&gt;barely&lt;/code&gt; and &lt;code&gt;route&lt;/code&gt; swap.&lt;/p&gt;

&lt;p&gt;Three offline arms on the same coupled-Uniform unique-catch definition (burst/medium, 10% floor):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Forced pair&lt;/strong&gt; (&lt;a href="https://github.com/zxpmail/blog/blob/main/agent-determinism-illusions/scripts/unique-catch-cofire-test.py" rel="noopener noreferrer"&gt;&lt;code&gt;unique-catch-cofire-test.py&lt;/code&gt;&lt;/a&gt;): inject &lt;code&gt;route_changed ∧ classifier_disagree&lt;/code&gt; on fraction ρ of defectives (ρ→0.8), then all C(4,2)=6 pairs. Unique &lt;em&gt;mass&lt;/em&gt; collapses under shared fires; &lt;strong&gt;rank never moves&lt;/strong&gt;. Results: &lt;a href="https://github.com/zxpmail/blog/blob/main/agent-determinism-illusions/scripts/results-v2/unique-catch-cofire.json" rel="noopener noreferrer"&gt;&lt;code&gt;unique-catch-cofire.json&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/zxpmail/blog/blob/main/agent-determinism-illusions/scripts/results-v2/unique-catch-cofire-pairs.json" rel="noopener noreferrer"&gt;&lt;code&gt;unique-catch-cofire-pairs.json&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Co-occurrence labels&lt;/strong&gt; (&lt;a href="https://github.com/zxpmail/blog/blob/main/agent-determinism-illusions/scripts/unique-catch-cooccur-labels-test.py" rel="noopener noreferrer"&gt;&lt;code&gt;unique-catch-cooccur-labels-test.py&lt;/code&gt;&lt;/a&gt;): each defective draws a latent class → signature fires at &lt;code&gt;p_sig=0.90&lt;/code&gt;. Under &lt;code&gt;mike_half&lt;/code&gt; (π(&lt;code&gt;route_cd&lt;/code&gt;)=0.5), middle &lt;strong&gt;does&lt;/strong&gt; flip (&lt;code&gt;route &amp;gt; barely&lt;/code&gt;; stable at N=2000). Extremes still hold. Result: &lt;a href="https://github.com/zxpmail/blog/blob/main/agent-determinism-illusions/scripts/results-v2/unique-catch-cooccur-labels.json" rel="noopener noreferrer"&gt;&lt;code&gt;unique-catch-cooccur-labels.json&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dose&lt;/strong&gt; (&lt;a href="https://github.com/zxpmail/blog/blob/main/agent-determinism-illusions/scripts/unique-catch-cooccur-dose-test.py" rel="noopener noreferrer"&gt;&lt;code&gt;unique-catch-cooccur-dose-test.py&lt;/code&gt;&lt;/a&gt;): π&lt;em&gt;(&lt;code&gt;route_cd&lt;/code&gt;)≈&lt;/em&gt;&lt;em&gt;0.50&lt;/em&gt;* (anti-flicker). Single-class doses of &lt;code&gt;barely_route&lt;/code&gt; / &lt;code&gt;cd_barely&lt;/code&gt; never flip middle. At π(&lt;code&gt;route_cd&lt;/code&gt;)=1 extremes also break (route overtakes CD) — only under that extreme. Result: &lt;a href="https://github.com/zxpmail/blog/blob/main/agent-determinism-illusions/scripts/results-v2/unique-catch-cooccur-dose.json" rel="noopener noreferrer"&gt;&lt;code&gt;unique-catch-cooccur-dose.json&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Prune takeaway:&lt;/strong&gt; drop &lt;code&gt;input_unusual&lt;/code&gt; first / keep CD last survives these sims; &lt;strong&gt;do not lock&lt;/strong&gt; &lt;code&gt;barely&lt;/code&gt; vs &lt;code&gt;route&lt;/code&gt; without an estimate of real co-occurrence-label mass on route∧CD. π here is invented — a production trace's labels remain the lock condition. Cheaper gate before rerunning: defect-class concentration histogram.&lt;/p&gt;

&lt;p&gt;&lt;a id="defect-class-concentration-mike"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Update (2026-07-27): π as class concentration — histogram before rerunning (Mike)
&lt;/h3&gt;

&lt;p&gt;Mike's follow-up on the hair-flip: pair-force not moving order, and label-concentration moving it a hair, is more informative than a clean flip — it locates the mechanism. Forcing two signals to co-fire still leaves each defective independently labeled; from CD's seat a co-forced row looks like any other catch. The generative version changes &lt;em&gt;what the defective is&lt;/em&gt; (a class whose signature is route∧CD), not just how signals respond.&lt;/p&gt;

&lt;p&gt;That makes π(&lt;code&gt;route_cd&lt;/code&gt;) a real-world question: what fraction of the defect population is one class where route and CD are both diagnostic of the same cause. π&lt;em&gt;≈0.50 flips middle; π=1 breaks ends — fragile in a **narrow high-concentration&lt;/em&gt;* regime. Cheaper next step: histogram how concentrated actual defect classes are before rerunning the fixture.&lt;/p&gt;

&lt;p&gt;Offline gate on the taxonomy this repo already has (&lt;a href="https://github.com/zxpmail/blog/blob/main/agent-determinism-illusions/scripts/defect-class-concentration-histogram.py" rel="noopener noreferrer"&gt;&lt;code&gt;defect-class-concentration-histogram.py&lt;/code&gt;&lt;/a&gt; → &lt;a href="https://github.com/zxpmail/blog/blob/main/agent-determinism-illusions/scripts/results-v2/defect-class-concentration-histogram.json" rel="noopener noreferrer"&gt;&lt;code&gt;defect-class-concentration-histogram.json&lt;/code&gt;&lt;/a&gt;): DF v2 MISS runs (N=96), &lt;strong&gt;not&lt;/strong&gt; generative &lt;code&gt;route_cd&lt;/code&gt; labels on the sampling sim (caveat load-bearing).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Taxonomy&lt;/th&gt;
&lt;th&gt;Max share&lt;/th&gt;
&lt;th&gt;vs π*=0.50 fragile band&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scenario_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;DS4 &lt;strong&gt;34.4%&lt;/strong&gt; (HHI=0.18)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;below&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;`model\&lt;/td&gt;
&lt;td&gt;scenario`&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15.6%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;qwen &lt;strong&gt;80.2%&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;different axis (already on-page; not π_route_cd)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; on this available miss taxonomy, concentration alone does not put you in the dose flip regime. Middle prune still isn't locked for a real external-signal / production trace where the class &lt;em&gt;is&lt;/em&gt; “route and CD same cause” — but the gate is cheap: histogram first; only rerun if a dominant class sits near ~0.5+. Prior co-occur Update above.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update (2026-07-22): escalation tripwire ≠ audit weighting — next part
&lt;/h3&gt;

&lt;p&gt;Alexey Spinov's follow-up on this post pushes a different knob than Mike's: not &lt;em&gt;how often&lt;/em&gt; to audit the high-confidence region, but &lt;em&gt;whether unanimous L2 votes should auto-execute at all&lt;/em&gt; when the failure mode is correlated. That incompleteness in the Part 6 diagram is real — divergence-only escalation is not enough for the failure mode DF v2 already measured.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full write-up is &lt;a href="https://dev.to/zxpmail/divergence-escalates-the-wrong-population-unanimous-misses-auto-pass-1513"&gt;Part 7&lt;/a&gt;&lt;/strong&gt; (&lt;em&gt;Divergence escalates the wrong population&lt;/em&gt;). This Update is only a pointer so the published post does not pretend the old diagram is complete. Numbers, D+T2, recurrence vs novelty, structural≠causal independence, hold-out tests, and the &lt;a href="https://github.com/zxpmail/blog/blob/main/agent-determinism-illusions/blog-agent-determinism-illusions-7.en.md#joint-failure-monitor-mike" rel="noopener noreferrer"&gt;joint-failure monitor&lt;/a&gt; (stamp testable upstream; alert on the rest) live there — not duplicated here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update (2026-07-27): after who enters — who gets seen (pointer)
&lt;/h3&gt;

&lt;p&gt;Alexey's later grid on this thread (floor volume, arrival vs precision order) and Mike's reframe (rank-inside-stream is the open problem) sit &lt;em&gt;after&lt;/em&gt; Part 7's entry policy. &lt;strong&gt;&lt;a href="https://dev.to/zxpmail/dt2-names-who-enters-budget-names-who-gets-seen-4f9g"&gt;Part 15&lt;/a&gt;&lt;/strong&gt; (&lt;em&gt;D+T2 names who enters; budget names who gets seen&lt;/em&gt;) holds the offline suite: diluted-queue acceptance, feature×time stress, Trigger∥Rank / Shadow∥Enforce. Numbering jumps to 15 so Parts 8–14 keep their other arcs; publish order on this argument line is 7 → 15. Mike's later cut on the same thread: the three-part split (entry / budget / degradable rank) is the cleaner landing.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Manuel Bruña &amp;amp; Alexey Spinov: Evidence, Not Narrative
&lt;/h2&gt;

&lt;p&gt;Throughout P1-P4, all LLM review experiments output free-text "reason" fields. &lt;a href="https://dev.to/tecnomanu/comment/3aj7c"&gt;Manuel&lt;/a&gt; identified the structural problem and the fix in one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Treat the LLM inspector as an evidence-producing reviewer, not the final binary gate. Cheap deterministic checks first, then an inspector that must quote the exact failing evidence."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://dev.to/alex_spinov/comment/3ai7e"&gt;Alexey&lt;/a&gt; sharpened the architectural split:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Deterministic assertions own everything mechanically checkable (tests collected greater than zero, schema conformance, non-emptiness thresholds), and the LLM only judges the irreducibly fuzzy residue."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My experiments had this blind spot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P1, scenario L1 (model REJECT):
"The research brief should cover the core mechanisms of the loop engine,
but the file only has a short excerpt..."

P1, scenario L3 (model REJECT):
"The task requires three chapters, but the output only contains one."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are impression judgments. You can't code-verify whether "a short excerpt" is enough.&lt;/p&gt;

&lt;p&gt;The proposed output format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Assertion 1: "File line count = 3, expected &amp;gt; 20"        → code-verifiable
Assertion 2: "File contains 1/3 required keywords"        → code-verifiable
Assertion 3: "Content structure completeness &amp;lt; threshold" → semantic judgment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assertions 1-2 are deterministic — code can confirm whether the model's claim is true. Assertion 3 is the actual semantic judgment, preserve for Layer 2.&lt;/p&gt;

&lt;p&gt;This creates a cascade: when a deterministic assertion is code-verified and found inconsistent with the actual file → explicit hallucination signal → mark as UNCLEAR → escalate. No human judgment required in the loop — the code flow triggers automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope note:&lt;/strong&gt; unlike §§1–4, this section is a design claim, not a separate A/B in Experiment F. The prototype implements evidence-shaped L2 output; it does not measure whether assertion format alone reduces hallucination rate versus free-text reasons. Treat the cascade above as an engineering pattern pending that measurement.&lt;/p&gt;




&lt;h2&gt;
  
  
  Synthesis: What the Five Comments Build Together
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Comment&lt;/th&gt;
&lt;th&gt;My blind spot&lt;/th&gt;
&lt;th&gt;Replacement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alexey + Manuel&lt;/td&gt;
&lt;td&gt;Fed everything to the same LLM reviewer&lt;/td&gt;
&lt;td&gt;L0/L1 filter deterministically; LLM handles residual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ethan Walker&lt;/td&gt;
&lt;td&gt;Same merge-blocking semantics for every layer&lt;/td&gt;
&lt;td&gt;L0/L1 required (exit code); L2 advisory (PR comment); L3 human on splits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alexey (2nd)&lt;/td&gt;
&lt;td&gt;Symmetric FP/FN metrics&lt;/td&gt;
&lt;td&gt;Weighted cost (FN×3) shifts optimal operating point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dipankar&lt;/td&gt;
&lt;td&gt;Split votes averaged by majority&lt;/td&gt;
&lt;td&gt;Divergence = UNCLEAR → human, no majority&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mike + xm_dev_2026&lt;/td&gt;
&lt;td&gt;Fixed 5-10% audit rate&lt;/td&gt;
&lt;td&gt;Adaptive sampling by confidence × risk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manuel + Alexey (2nd)&lt;/td&gt;
&lt;td&gt;Narrative "reason" field&lt;/td&gt;
&lt;td&gt;Evidence-quoted reviewer + deterministic assertions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Combined, these form a layered verification architecture — not a closed one: L0/L1 handle deterministic filtering (Alexey+Manuel) with &lt;strong&gt;blocking&lt;/strong&gt; CI semantics (Ethan), L2 LLM quotes exact failing evidence as &lt;strong&gt;advisory&lt;/strong&gt; (Manuel+Alexey / Ethan), divergence escalates to L3 human review (Dipankar), audit rate adapts by confidence (Mike+xm_dev_2026), and system thresholds are selected by weighted cost (Alexey 2nd). Each layer narrows what the next sees; none closes the semantic residue.&lt;/p&gt;

&lt;p&gt;This article doesn't claim to have solved anything. It just puts the design decisions I made and the corrections the community provided side by side.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;The full pipeline has been implemented in forge-verify's &lt;code&gt;content-verify.mjs&lt;/code&gt; (&lt;strong&gt;ReqForge product repo&lt;/strong&gt;, not this blog tree — the blog ships the Python prototype &lt;code&gt;forge-verify-layered-prototype.py&lt;/code&gt;). File-by-file results show which layer stopped each sample. Early-exit example (L1 blacklist — L2/L3 never run):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="s"&gt;📄 src/api/register.ts&lt;/span&gt;
  &lt;span class="s"&gt;❌ REJECT @ L1&lt;/span&gt;&lt;span class="na"&gt;: contains blacklisted keyword&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;FIXME&lt;/span&gt;
    &lt;span class="s"&gt;└ L0&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PASS&lt;/span&gt;
    &lt;span class="s"&gt;└ L1&lt;/span&gt;&lt;span class="na"&gt;: REJECT — blacklisted keyword&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;FIXME&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Divergence example (L0/L1 pass; L2 split → L3 human, no majority vote):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="s"&gt;📄 docs/brief.md&lt;/span&gt;
  &lt;span class="s"&gt;⚠ UNCLEAR @ L3&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;split vote → human queue&lt;/span&gt;
    &lt;span class="s"&gt;└ L0&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PASS&lt;/span&gt;
    &lt;span class="s"&gt;└ L1&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PASS&lt;/span&gt;
    &lt;span class="s"&gt;└ L2&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;REJECT/REJECT/PASS&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="s"&gt;PASS=1 REJ=2&lt;/span&gt;
    &lt;span class="s"&gt;└ L3&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UNCLEAR — do not majority-vote&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Layer 0/1 checks are zero-cost code. Layer 2 only runs on the residual. Layer 3 divergence detection prevents false majority decisions.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Side Note: An Apology Experiment
&lt;/h2&gt;

&lt;p&gt;An earlier draft appended a long apology for a fabricated “directional failure” claim in a Part 3 comment. That thread became its own experiment (20×3×600) and then a correction stack (comment wrong → apology v1 wrong on DS4 → v2 numbers). Under the harness label, DS4 still 100% misses on qwen3/gemma3; deepseek is 13%/67%/20% catch/PARSE/miss. Post-hoc, DS4 is partly task ambiguity (10→10); clean L0/L1 wins remain DF6/DS9 value mismatch. Full write-up: &lt;a href="//blog-fabricated-claim-apology.en.md"&gt;I Fabricated a Claim About LLM Judges. Then I Ran the Apology Experiment.&lt;/a&gt; (swap in the live DEV.to URL after that aside publishes). Scripts: &lt;code&gt;directional-failure-v2.py&lt;/code&gt; / &lt;code&gt;scripts/results-v2/&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Series navigation (Agent Determinism Illusions):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://dev.to/zxpmail/i-tested-the-deterministic-agent-loop-claims-with-four-experiments-they-all-failed-including-38kj"&gt;I tested the 'deterministic agent loop' claims…&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://dev.to/zxpmail/i-tested-3-models-as-ai-agent-quality-inspectors-the-stronger-the-model-the-more-valid-work-it-gl7"&gt;I tested 3 models as AI agent quality inspectors…&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://dev.to/zxpmail/i-designed-a-harness-to-fix-my-agents-quality-problem-then-found-6-flaws-in-my-own-design-5h29"&gt;I designed a Harness… then found 6 flaws&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://dev.to/zxpmail/an-alternative-to-llm-quality-gates-deterministic-routing-sampling-1ilf"&gt;An alternative to LLM quality gates: deterministic routing + sampling&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://dev.to/zxpmail/six-experiments-on-adversarial-verification-and-the-75-wall-that-didnt-move-2d1m"&gt;Six experiments… and the 75% wall that didn't move&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Aside: &lt;a href="https://dev.to/zxpmail/the-red-line-principle-objective-stop-signals-outperform-llm-self-judgment-in-verifiable-tasks-3heo"&gt;The Red Line Principle&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Five comments that redesigned my LLM verification pipeline (this article)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Aside (forthcoming): I Fabricated a Claim About LLM Judges. Then I Ran the Apology Experiment.&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Published parts: &lt;a href="https://dev.to/zxpmail"&gt;dev.to/zxpmail&lt;/a&gt;. Scripts: &lt;a href="https://github.com/zxpmail/blog/tree/main/agent-determinism-illusions/scripts" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Experiment F prototype (this repo): &lt;code&gt;forge-verify-layered-prototype.py&lt;/code&gt; (Python, runnable with or without API)&lt;/em&gt;&lt;br&gt;
&lt;em&gt;forge-verify production path: ReqForge product repo — &lt;code&gt;scripts/forge-verify/content-verify.mjs&lt;/code&gt; (not vendored here)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Previous: &lt;a href="https://dev.to/zxpmail/the-red-line-principle-objective-stop-signals-outperform-llm-self-judgment-in-verifiable-tasks-3heo"&gt;The Red Line Principle&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Series start: &lt;a href="https://dev.to/zxpmail/i-tested-the-deterministic-agent-loop-claims-with-four-experiments-they-all-failed-including-38kj"&gt;Four experiments…&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Which comment did I miss?&lt;/strong&gt; If you've hit a verification failure mode that the L0/L1/L2/L3 pipeline doesn't catch, drop it in the comments — I'll run it through Experiment F and report what each layer does with it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>The Red Line Principle: objective stop signals outperform LLM self-judgment in verifiable tasks</title>
      <dc:creator>zxpmail</dc:creator>
      <pubDate>Sat, 18 Jul 2026 12:53:16 +0000</pubDate>
      <link>https://dev.to/zxpmail/the-red-line-principle-objective-stop-signals-outperform-llm-self-judgment-in-verifiable-tasks-3heo</link>
      <guid>https://dev.to/zxpmail/the-red-line-principle-objective-stop-signals-outperform-llm-self-judgment-in-verifiable-tasks-3heo</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where this fits in the series:&lt;/strong&gt; This article sits between Part 5 (the 75% wall — design around it, don't fix it) and Part 6 (the layered L0→L1→L2→L3 pipeline built from community feedback). It asks the upstream question: &lt;em&gt;how does an agent loop know when to stop?&lt;/em&gt; The "demand red line vs. format red line" distinction below anticipates the L0 (evidence gate) vs. L1 (contract regex) split formalized in Part 6. The "no semantic-layer red line" claim is the same boundary later named the DPI bound.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;The scope of this article is limited to tasks with objectively verifiable acceptance criteria (code, structured output, assertable results). For open-ended semantic tasks (writing copy, drafting analysis), the Red Line Principle does not apply.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Scope restated:&lt;/strong&gt; The conclusions in this article hold only under the following conditions — the task has an objective verification standard, and that standard can be predefined by a human (code output matching expectations, schema validation passing, all tests green). For open-ended semantic tasks (writing copy, drafting analysis, generating creative content), no known automatic convergence signal exists within the scope of these experiments; refer to Rules 3 and 4. All data below is within this scope.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;How do you make an agent loop converge reliably in production?&lt;/p&gt;

&lt;p&gt;The core comparison is V2: three code tasks × two stop-signal types (with red line vs. self-judge only), plus a handoff-queue simulation (&lt;code&gt;handoff-protocol-sim.py&lt;/code&gt;). Eight auxiliary experiments from earlier in the series cover adjacent dimensions (lexical overlap, temperature-0, phase gates, embedding separation, multi-model tradeoffs, SPC anomaly detection, cold-start drift, classification accuracy). All scripts are in &lt;code&gt;agent-determinism-illusions/scripts/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core experiment: same code task, with red line vs. without
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Warning: N=3, directional results, not statistically significant.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Previous versions of this comparison had a confound: "with red line" used a code task while "without red line" used a copywriting task. Different task types prevent causal attribution to the red line. This version corrects that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unified task:&lt;/strong&gt; generate a Python function. Verification runs the test and matches the expected output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test cases:&lt;/strong&gt; human-written, covering normal input, boundary values, and edge cases. Injected into the agent context alongside the task definition. Test suite published at &lt;code&gt;scripts/test_cases/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Condition A (with red line):&lt;/strong&gt; compilation + test pass = stop. Objective signal: the code ran and the output is correct.&lt;strong&gt;Condition B (without red line):&lt;/strong&gt; LLM self-judgment (YES/NO) = stop. Same code, same test — the background verification still runs to record actual correctness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model:&lt;/strong&gt; deepseek-v4-flash (API, temperature 0). Reproducible via &lt;code&gt;redline-v2-experiment.py&lt;/code&gt; (parameters below match the script defaults).&lt;/p&gt;

&lt;p&gt;Three tasks, 3 trials each, 8-step limit. N=3, showing distribution not effect size:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Convergence (individual)&lt;/th&gt;
&lt;th&gt;Avg steps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;simple&lt;/td&gt;
&lt;td&gt;With red line&lt;/td&gt;
&lt;td&gt;[1,1,1]&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;simple&lt;/td&gt;
&lt;td&gt;Self-judge only&lt;/td&gt;
&lt;td&gt;[X,OK,X]&lt;/td&gt;
&lt;td&gt;8.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;medium&lt;/td&gt;
&lt;td&gt;With red line&lt;/td&gt;
&lt;td&gt;[1,4,5]&lt;/td&gt;
&lt;td&gt;3.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;medium&lt;/td&gt;
&lt;td&gt;Self-judge only&lt;/td&gt;
&lt;td&gt;[X,X,OK]&lt;/td&gt;
&lt;td&gt;8.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;complex&lt;/td&gt;
&lt;td&gt;With red line&lt;/td&gt;
&lt;td&gt;[1,1,1]&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;complex&lt;/td&gt;
&lt;td&gt;Self-judge only&lt;/td&gt;
&lt;td&gt;[X,X,X]&lt;/td&gt;
&lt;td&gt;8.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Direction:&lt;/strong&gt; 9/9 converged with the red line; 2/9 actually converged with self-judge (both ran to the 8-step hard limit before self-triggering). The directional difference is stable, but N=3 cannot exclude random variation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Self-judge failure mode:&lt;/strong&gt; 0 false positives (says YES when code is wrong) and at least 4 false negatives (code correct but self-judge says NO or never triggers). The model wrote correct code but didn't trust itself, kept iterating, and either degraded its own working code or hit the step limit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt bias note:&lt;/strong&gt; the self-judge prompt asks "does this code satisfy the task requirements? — YES or NO only." Even with a direct question, the model may still hesitate or never trigger YES (false negatives in the table). A different prompt format (e.g., "output FINISH if code passes all tests") would likely change the self-judge convergence rate. This comparison describes "a specific self-judge prompt vs. a compilation signal," not a general "red line vs. no red line."&lt;/p&gt;

&lt;h3&gt;
  
  
  On what "compile pass" actually verifies
&lt;/h3&gt;

&lt;p&gt;The red line in these experiments isn't "syntax is valid." It's "the test output matches the expected result" — demand-level verification. Function &lt;code&gt;is_even(4)&lt;/code&gt; must return &lt;code&gt;True&lt;/code&gt; and &lt;code&gt;is_even(3)&lt;/code&gt; must return &lt;code&gt;False&lt;/code&gt;. This is fundamentally different from a phase gate checking "file exists." The former verifies correctness; the latter verifies occurrence.&lt;/p&gt;

&lt;p&gt;For open-ended semantic tasks (write an analysis report), no equivalent objective verification exists. This isn't a "better red line design" problem — it's a task-type limitation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three types of red lines
&lt;/h3&gt;

&lt;p&gt;The experiments exposed a missing conceptual distinction. What we call a "red line" spans three categories with fundamentally different verification power and engineering cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honest note on the demand red line:&lt;/strong&gt; the demand red line used in these experiments (compile + test output matches expectation) depends on human-written test assertions. The system does not automatically know whether a task is complete — a human pre-defines the verifiable boundary, and the agent operates within it.&lt;/p&gt;

&lt;p&gt;"Demand red line works" is equivalent to saying: "if a human writes a complete acceptance test upfront, the agent can satisfy it in 1-3 steps." This is labor shifting — moving verification cost from runtime to design time. For tasks where a complete, pre-writable verification set does not exist (most open-ended semantic tasks), the demand red line is inapplicable. It is a task classification, not a universal mechanism.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Format red line&lt;/strong&gt; — lowest cost, lowest verification power.Checks file existence, exit 0, syntax parse, JSON Schema compliance.It verifies "the output is well-formed," not "the output is correct." Phase gates and SPC belong here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demand red line&lt;/strong&gt; — moderate cost, moderate verification power.Checks compilation pass, test output matches expected, business assertion pass.It verifies "the output satisfies the requirement." The V2 experiment uses this tier. It requires writing tests and assertions — cost is determined by the task's verifiability, not by system design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semantic-layer red line&lt;/strong&gt; — no known reliable method found within the scope of these experiments.Existing approaches (LLM-as-judge, multi-round debate, consistency checks) show high false-positive rates or irreproducibility in limited testing.&lt;strong&gt;This is an open problem.&lt;/strong&gt; Not in-principle unsolvable — but no known engineering mechanism can reliably judge completion for open-ended semantic tasks under the current stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why "no method" is a measured claim, not a guess.&lt;/strong&gt; The natural challenge — "have you tried LLM judges, debate, self-consistency?" — is answered by two independent threads elsewhere in this series. First, the directional-failure dataset: 20 scenarios × 3 model tiers × 600 judgments, where the "output" reads as plausible compliance but reverses the task semantically (e.g. "current config already satisfies the requirement, no change needed" when a change was required). The subtle-reversal miss rate — the fraction of times the LLM judge accepts plausible-but-reversed output — was &lt;strong&gt;44% on the 0.5B model, 11% on the 4.3B model, 2% on the ~200B model&lt;/strong&gt; (&lt;code&gt;scripts/results-v2/*_summary.json&lt;/code&gt;, &lt;code&gt;subtle_df&lt;/code&gt; group). The strongest model still misses 2% of reversals that a deterministic red line would catch by construction. Second, Theorem 2 (the Data Processing Inequality applied to agent verification): when the reasoning and the verifier share the same text channel, the verifier's information is a strict subset of the producer's. If the rationalization is textually indistinguishable from the real cause, no text-channel reader — LLM judge, debate panel, or human — can detect it. LLM-as-judge is not untried; it is a weaker channel than a demand red line by a provable bound, and empirically it leaks even on the strongest model. That is why the table marks semantic-layer red lines as an open problem rather than a tunable parameter.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Red line type&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;What it verifies&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Usable as convergence signal?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Format red line&lt;/td&gt;
&lt;td&gt;exit 0 / file exists / syntax pass&lt;/td&gt;
&lt;td&gt;Well-formed output&lt;/td&gt;
&lt;td&gt;Trivial&lt;/td&gt;
&lt;td&gt;No (Phase Gate: 50% false positives)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Demand red line&lt;/td&gt;
&lt;td&gt;compile + test pass / assertion pass&lt;/td&gt;
&lt;td&gt;Output matches requirement&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Yes (V2: directional evidence)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Semantic-layer red line&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Logical coherence / quality&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No known reliable method&lt;/strong&gt; (open problem)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The V2 experiment used a demand red line, not a format red line. A format red line (syntax check only) would not produce the same convergence rate — code can compile and still be wrong.&lt;/p&gt;

&lt;p&gt;The rules below are based on this distinction. Only demand red lines can serve as convergence signals. Format red lines are insufficient. Semantic-layer red lines have no known reliable method within the scope of these experiments.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Red Line Principle
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Rule 1: tasks with an objective convergence signal → auto-converge, enter the production pipeline.&lt;/strong&gt;Code compilation, schema validation, test output matching expectations — these have verifiable outputs. The loop runs, the signal fires, the system stops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule 2: tasks with incomplete signals → auto-converge + human sampling.&lt;/strong&gt;Many real tasks fall in the grey zone — 80% test coverage, schema-valid but business-unverified, diff-zeroed but semantically unchecked. Rule 1 and calibrated human sampling (developed in &lt;a href="https://dev.to/zxpmail/an-alternative-to-llm-quality-gates-deterministic-routing-sampling-1ilf"&gt;Part 4&lt;/a&gt;) are composable: a task can auto-converge via its demand red line, then layer sampling on the auto-passed subset to cover the blind spots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule 3: tasks with no convergence signal → must have a hard cutoff; label "unverified," route to human queue.&lt;/strong&gt;Open-ended semantic tasks — writing copy, drafting analysis, writing reports — have no objective "complete" signal. Do not rely on LLM self-judgment to stop the loop. The output at cutoff cannot auto-enter the production flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule 4: output at cutoff → mark "unverified," route to human queue.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The cutoff fired because budget ran out, not because the task was judged complete. "Route to human" isn't a complete engineering solution — it's operational fallback. Below is a design draft for a production-grade human handoff protocol.&lt;/p&gt;

&lt;h4&gt;
  
  
  Backpressure
&lt;/h4&gt;

&lt;p&gt;Human review queue throughput is a hard constraint. When agent production rate persistently exceeds review rate, the system is unsustainable — &lt;code&gt;handoff-protocol-sim.py&lt;/code&gt; confirmed this (5 items/min vs 3 items/min: 34% queue overflow). Backpressure mechanism:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Watermark:&lt;/strong&gt; queue depth &amp;gt; 80% of capacity triggers degradation. New tasks skip the fix loop entirely — output raw result, tag as "draft mode."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limit:&lt;/strong&gt; queue depth hits capacity → drop lowest-priority items (log to circuit-breaker log), prioritize high-value tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recovery:&lt;/strong&gt; queue depth &amp;lt; 30% of capacity → resume normal flow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Core observation: queue design doesn't dominate system stability — &lt;strong&gt;the ratio of agent production rate to human review rate is the decisive factor.&lt;/strong&gt; If production exceeds review, any queue fills. Either slow down (cap agent concurrency), speed up (better review tools), or accept overflow (absorb the business cost).&lt;/p&gt;

&lt;h4&gt;
  
  
  Context preprocessing
&lt;/h4&gt;

&lt;p&gt;Raw cutoff output may contain multi-step execution traces and thinking-token leakage (as in &lt;code&gt;redline-experiment.py&lt;/code&gt; Experiment C — copywriting task with a hard step cutoff and no objective signal). Showing this directly to a reviewer slows decisions.&lt;/p&gt;

&lt;p&gt;Preprocessing rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extract three fields from the execution trace: final output, last error message, attempt count. Do not send the full trace.&lt;/li&gt;
&lt;li&gt;Reviewer UI displays only: task description → final output (highlighted) → cutoff reason (step limit / self-judge false negative / format anomaly).&lt;/li&gt;
&lt;li&gt;After verdict, collect: approve/reject + reason label (code error / logic error / format issue / hallucination / unclear). Sort labels by frequency — types appearing &amp;gt;3 times should trigger automatic filter rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Feedback tuning
&lt;/h4&gt;

&lt;p&gt;Human verdicts shouldn't be consumed once and discarded. A feedback loop adapts cutoff parameters based on review results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sliding window:&lt;/strong&gt; approve rate over the last 10 human verdicts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate &amp;gt; 80%:&lt;/strong&gt; cutoff too tight (too many correct tasks sent to review). Increase the step limit or loosen trigger conditions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate &amp;lt; 40%:&lt;/strong&gt; cutoff too loose (too many incorrect outputs slip through). Decrease the step limit or tighten trigger conditions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;40%–80%:&lt;/strong&gt; maintain — cutoff is in the right zone; human review catches edge cases rather than bulk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simulation (same 4 configurations as the backpressure table) showed: under A (production ≤ review), feedback tuning converged the step limit to max (5→15) in approximately 30 minutes, driven by the 40-80% approve rate zone keeping tuning in maintain. Under B/D (production &amp;gt; review), backpressure fires before tuning — the binding constraint is throughput, not convergence parameters.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Config&lt;/th&gt;
&lt;th&gt;Production (/min)&lt;/th&gt;
&lt;th&gt;Review (/min)&lt;/th&gt;
&lt;th&gt;Queue cap&lt;/th&gt;
&lt;th&gt;2h overflow&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A (baseline)&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B (overload)&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;34%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C (burst)&lt;/td&gt;
&lt;td&gt;2 (burst ×3)&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D (slow review)&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;30%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;This is a parameter estimation example, not production data.&lt;/strong&gt; Core observation: when production/review ratio ≤ 1, the system is stable; when ratio ≥ 2, it is unsustainable — queue design doesn't dominate, throughput ratio does. Actual deployment requires calibration against your own data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honest risk note:&lt;/strong&gt; the feedback tuning structure is isomorphic to the closed-loop calibration criticized in my earlier work (human verdicts → data pool → scheduled tuning). The same failure modes apply: distribution shift nullifies historical patterns, and whack-a-mole effects are possible. The difference is that here we tune a scalar (step limit, bounded [3,15]) rather than LLM few-shot examples (high-dimensional, uncontrolled). The failure domain is narrower, but not zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boundary of loops — an untested hypothesis
&lt;/h2&gt;

&lt;p&gt;The data raises a question it cannot answer: &lt;strong&gt;does the loop's repair capability have a boundary?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With the red line, the medium task averaged 3.3 steps while the complex task averaged 1.0 steps. This difference might mean that FizzBuzz's boundary conditions (3→Fizz, 5→Buzz, 15→FizzBuzz) fall in the model's "near-miss zone" — it understood the requirement but made a syntax or edge-case error, which is fixable through iteration. The complex task (data structure manipulation) was written correctly on the first try.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But this is a post-hoc interpretation.&lt;/strong&gt; N=3 cannot exclude random variation. A more fundamental question: if the error is conceptual (the agent completely misunderstood the requirement), can the fix loop still recover? Current experiments don't answer this, because all tasks were within the model's capability range — tasks beyond capability were not included in the design.&lt;/p&gt;

&lt;p&gt;A worthwhile independent direction: construct two task classes (syntax errors vs. logic errors) and compare fix-loop success rates — the former expected to be high, the latter low and non-improving with iteration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update: the boundary is detectable — but the detector is model-dependent
&lt;/h3&gt;

&lt;p&gt;A reader (Reid Marlow) proposed the natural brake: a &lt;em&gt;stuck-loop budget&lt;/em&gt; — if the same red-line failure repeats N times unchanged, stop and surface the evidence, instead of spending the full step budget sampling. I ran it (&lt;code&gt;scripts/stuck-loop-budget-test.py&lt;/code&gt;). Two model tiers (deepseek-v4-flash, glm-5.2) × two task classes under a red line: 3 repairable tasks (the ones above) and 4 &lt;em&gt;conceptual&lt;/em&gt; tasks where the test expectation contradicts the requirement's literal meaning (e.g. &lt;code&gt;to_bin(8)&lt;/code&gt; expects &lt;code&gt;"100"&lt;/code&gt; not &lt;code&gt;"1000"&lt;/code&gt;; a length function that must return 4 for a 5-character string). The conceptual class is the non-improving case the paragraph above predicts: the model honors the requirement, the red line keeps failing, and iteration cannot fix it because the "error" is that the model did what was asked.&lt;/p&gt;

&lt;p&gt;The two task classes behaved exactly as the hypothesis predicted. Repairable tasks converged in 1–2 steps on both models. Conceptual tasks on deepseek-v4-flash never converged — all four ran the full 8-step cap. On glm-5.2, one conceptual task ran the full cap; the other three converged in 2–4 steps (the model stumbled onto the test's hidden intent for those). &lt;strong&gt;The boundary is real on the model that respects the requirement literally, and it lines up with the syntax-vs-logic split.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The detector's effectiveness, however, split by model — averaged across all four conceptual tasks, N=3 budget:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Conceptual: failure signature&lt;/th&gt;
&lt;th&gt;N=3 budget avg stop step&lt;/th&gt;
&lt;th&gt;Steps saved vs step-cap&lt;/th&gt;
&lt;th&gt;Repairable false-stops&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;glm-5.2&lt;/td&gt;
&lt;td&gt;stable (single repeated signature)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;deepseek-v4-flash&lt;/td&gt;
&lt;td&gt;oscillating (two signatures alternating)&lt;/td&gt;
&lt;td&gt;7.75&lt;/td&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On glm-5.2, the one task that genuinely stuck (&lt;code&gt;C-bin&lt;/code&gt;, &lt;code&gt;to_bin(8)&lt;/code&gt;→&lt;code&gt;1000&lt;/code&gt; vs expected &lt;code&gt;100&lt;/code&gt;) emitted a single stereotyped wrong answer every step; the budget fired at step 3 and saved five steps of pointless sampling. On deepseek-v4-flash, the same conceptual tasks oscillated between a wrong answer and a &lt;code&gt;NameError&lt;/code&gt; — each rewrite introduced a new syntax error, so no single signature ever repeated three times consecutively, and the budget never fired. It degraded gracefully back to the step-cap, which is the honest fallback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The narrower conclusion:&lt;/strong&gt; the stuck-loop budget works &lt;em&gt;when the model's stuck behavior is stereotyped&lt;/em&gt;, and silently no-ops when the model oscillates. That is worth knowing operationally — it tells you when the cheap mechanism pays for itself (stable-stuck models) and when you are paying for it without benefit (oscillating models, where the step-cap remains the backstop). The 0% false-stop rate on repairable tasks across both models is the reassuring half: when a task is genuinely fixable, the model converges fast enough that the budget never triggers, so it does not kill work that would have succeeded.&lt;/p&gt;

&lt;p&gt;The open question is the oscillation case. A signature that matches "same failure &lt;em&gt;class&lt;/em&gt;" rather than "same literal output" might catch it, but that is the calibration knob this experiment did not tune. N=3 is also a guess, not a fitted value — the data shows N=2 catches more but risks firing on legitimately-progressing near-misses, while N=4 is safer but catches less.&lt;/p&gt;

&lt;h2&gt;
  
  
  The deeper claim
&lt;/h2&gt;

&lt;p&gt;The Red Line Principle isn't about "how to make agents do more." It's about defining when not to let the agent continue.&lt;/p&gt;

&lt;p&gt;The prerequisite for a production-grade agent isn't that it can do more. It's that what it cannot do is clearly marked in advance, and it stops reliably at the boundary.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task type&lt;/th&gt;
&lt;th&gt;Convergence signal&lt;/th&gt;
&lt;th&gt;Red line&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Code / verifiable output&lt;/td&gt;
&lt;td&gt;Compile + test pass (demand-level)&lt;/td&gt;
&lt;td&gt;Generous step limit (1-3 normally)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open-ended semantic&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;None exists&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cutoff + human (no auto-fix)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Note: structured editing (diff to zero) was not tested in the experiments presented here and is omitted from this table.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations — what this article does and does not establish
&lt;/h2&gt;

&lt;p&gt;Stated plainly, because these are the points a careful reader (or critic) will press:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;N=3 on the core V2 table.&lt;/strong&gt; The 9/9 vs 2/9 comparison is directional, not statistically significant, and the article says so repeatedly. It cannot exclude "the result reverses on a different model or task set." What the later stuck-loop experiment (&lt;code&gt;scripts/stuck-loop-budget-test.py&lt;/code&gt;) adds is independent corroboration on a different sample: 7 tasks × 2 models, where the deterministic claims — repairable tasks converge in 1–2 steps with 0% false-stops, conceptual tasks never converge — held on both models without exception. That does not upgrade N=3 to statistical significance, but it means the direction is not a single-sample artifact.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The demand red line is TDD.&lt;/strong&gt; Pre-writing a complete acceptance test is labor shifting — moving verification cost from runtime to design time, as the article states. For fast-changing requirements the pre-written test can itself be incomplete or stale. This is a real limitation and it is not solved here; the demand red line is a task classification ("this task is verifiable"), not a claim that verification is free.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The repair boundary was untested at first publication; it is now tested.&lt;/strong&gt; The original version flagged "if the error is conceptual, can the fix loop recover?" as an open question. The boundary-of-loops section above now answers it: conceptual tasks (where the test contradicts the requirement's literal meaning) do not converge — they run the full step cap on the model that respects the requirement literally. The boundary is real and lines up with the syntax-vs-logic split.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The prompt-bias caveat is now measured, not just hedged.&lt;/strong&gt; The article notes a different self-judge prompt format "would likely change the self-judge convergence rate." I ran it (&lt;code&gt;scripts/selfjudge-prompt-reframe-test.py&lt;/code&gt;): the original "YES/NO" prompt vs a reframed "output FINISH / NEEDS_WORK" prompt, same tasks, same models. The result cuts against the easy fix. On deepseek-v4-flash the false-negative rate was 100% under both prompts — reframe changed nothing. On glm-5.2 it went from 0% (YES/NO) to 50% (FINISH) — reframe made it &lt;em&gt;worse&lt;/em&gt;, introducing new false negatives on a task the original prompt handled cleanly. Prompt format does change the numbers, but not in the direction that helps: the false negative is a structural property of self-judgment, not a knob prompt engineering turns down.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Previous: &lt;a href="https://dev.to/zxpmail/six-experiments-on-adversarial-verification-and-the-75-wall-that-didnt-move-2d1m"&gt;Six experiments on adversarial verification — and the 75% wall that didn't move&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Core experiment: &lt;code&gt;redline-v2-experiment.py&lt;/code&gt; (supports &lt;code&gt;--task-file&lt;/code&gt;, see &lt;code&gt;test_cases/README.md&lt;/code&gt;) · Handoff simulation: &lt;code&gt;handoff-protocol-sim.py&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;All scripts: &lt;a href="https://github.com/zxpmail/blog/tree/main/agent-determinism-illusions/scripts" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;*The conclusion is "a red line leads to higher and more stable convergence rates," not "the red line solves everything." The former has experimental support. The latter doesn't.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>Six experiments on adversarial verification — and the 75% wall that didn't move</title>
      <dc:creator>zxpmail</dc:creator>
      <pubDate>Tue, 14 Jul 2026 13:15:12 +0000</pubDate>
      <link>https://dev.to/zxpmail/six-experiments-on-adversarial-verification-and-the-75-wall-that-didnt-move-2d1m</link>
      <guid>https://dev.to/zxpmail/six-experiments-on-adversarial-verification-and-the-75-wall-that-didnt-move-2d1m</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The argument, in one line:&lt;/strong&gt; a reviewer is a mechanism for drawing a line. Every fix moves the line — but the line can't be eliminated, because it lives on a 3-dimensional surface where multiple defensible boundaries cross. So the 75% false-negative wall doesn't move, and the practical move is to stop trying to move it.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. The wall
&lt;/h2&gt;

&lt;p&gt;The setup was simple. Let an LLM review what an AI agent produced and judge whether it satisfies the task. Outputs were a mix of obvious garbage ("I am a little duck, quack quack", "。", TODO placeholders, zero collected tests) and legitimate work (research briefs, draft documents, passing test runs, code, translations). 8 scenarios in the first round, expanded to 30 in the second.&lt;/p&gt;

&lt;p&gt;When the reviewer is sharp enough to catch all the garbage, it lands at 0% false positives and 75% false negatives — three out of four valid outputs rejected. This is the wall. GLM-5.2 and deepseek-v4-flash both hit it. Smaller models (qwen3:0.5b at ~25% FN, gemma3:4.3b at ~50% FN) sit earlier on the curve — letting some garbage through, rejecting less valid work. They're not better; they're just at a different operating point on the same curve.&lt;/p&gt;

&lt;p&gt;I tried three standard moves to shift off the wall.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rerun and majority-vote the same prompt.&lt;/strong&gt; N=10 reruns per scenario. The verdict was unanimous on every scenario with enough valid calls. The 75% is systematic, not random — the model commits to the same wrong call every time. You can't vote away a verdict that doesn't vary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vote across different prompts.&lt;/strong&gt; Strict, balanced, and lenient prompts judged each scenario. Split votes are a useful signal — they flag scenarios where the test set itself is contested. But majority voting still hits 75% false negatives, because all three prompts share the same bias direction. Why? Section 2's answer: the model's boundary is stable; prompt wording labels the line, it doesn't move it. Voting smooths noise; it doesn't fix bias.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Calibrate the prompt wording.&lt;/strong&gt; A "balanced" prompt (v3) hit 100% accuracy on the 8 Phase Gate scenarios. The standard "calibrate your prompt" advice seemed to work. Expanded to 30 scenarios, v3 and the strict v2 returned identical verdicts on every valid call. The improvement on 8 was test-set composition bias — the original scenarios happened to favor v3's leniency.&lt;/p&gt;

&lt;p&gt;The wall is real. None of the standard levers moved it.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Why the wall doesn't move
&lt;/h2&gt;

&lt;p&gt;A reviewer is a mechanism for drawing a line. The line separates "sufficient output" from "insufficient output" — that's the whole job. Formal checks, LLM judgments, prompt wording — these are choices of where and how to draw it.&lt;/p&gt;

&lt;p&gt;Here is the property that matters. A sharper line catches more garbage and rejects more marginal-valid output. Same sharpness, opposite effects on the two error types. Sharpen the line and false positives drop while false negatives rise. Dull it and the reverse. The precision-recall tradeoff isn't a model defect — it's the geometry of drawing a line with imperfect discrimination. A perfect reviewer wouldn't have this tradeoff; reviewers have opinions about where the boundary lives, and those opinions are noisy.&lt;/p&gt;

&lt;p&gt;The six experiments drew the line in three different ways. Phase Gate drew it on form — file exists, exit code 0 — which is independent of content. Four pieces of garbage ("I am a little duck", "。", TODO placeholder, zero collected tests) sailed through. False positives: 50%. Adversarial verification drew the line on semantics with an LLM. Much sharper. Caught all the garbage (false positives → 0%), and the same sharpness rejected three out of four marginal-but-valid outputs (false negatives → 75%). Prompt calibration tried to move the line by changing the wording — strict vs. balanced vs. lenient. On 30 scenarios, v2 and v3 returned identical verdicts on every valid call. The line didn't move, because wording doesn't draw lines. Wording labels lines. The third attempt is the limit of the substitution approach: once you're using words to move a line the model already drew, you're not substituting anymore. You're decorating.&lt;/p&gt;

&lt;p&gt;So why not find a sharper line — or a different kind of line — that catches garbage without burning valid work? Because the line doesn't live in a one-dimensional space.&lt;/p&gt;

&lt;p&gt;The boundary between "sufficient" and "insufficient" depends on at least three independent questions. Who consumes the output — a junior engineer taking it at face value, or a senior reviewer who'll catch edge cases? Where it's deployed — a prototype thrown away next week, or production that runs for years? What fails if it's wrong — a demo that embarrasses you in a meeting, or a deploy that takes down the service?&lt;/p&gt;

&lt;p&gt;These three dimensions are mostly independent, not perfectly orthogonal. They correlate — consumer type gives a weak hint about deployment context — but not enough to collapse into one axis. Knowing the consumer doesn't determine the deployment. Knowing the deployment doesn't determine the cost of failure. So the boundary isn't a point in 1D space; it's a surface in 3D space. And most real outputs land somewhere in the interior — where multiple defensible boundaries cross.&lt;/p&gt;

&lt;p&gt;"Is this output sufficient?" doesn't have a single answer because the question is underspecified. Different consumers, contexts, and costs give different defensible answers. The fuzziness isn't a property of weak models. It's a property of the question.&lt;/p&gt;

&lt;p&gt;The practical conclusion falls out of the geometry. If the fuzziness is in the question, no model removes it. No prompt removes it. No voting scheme removes it. They just draw lines in different places on the same surface. The 75% didn't move across four models because there's nowhere to move it to — moving the operating point along the surface trades FP for FN, but the surface itself doesn't disappear.&lt;/p&gt;

&lt;p&gt;We weren't failing to find the right trick. We were looking for a trick that doesn't exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Design around the wall
&lt;/h2&gt;

&lt;p&gt;So design around it. The move is not "fix the wall." The move is "stop trying to fix the wall" — and that acceptance changes the design.&lt;/p&gt;

&lt;p&gt;If the 75% is structural, you stop spending LLM calls on garbage that rules can catch (keyword match catches "I am a little duck", length check catches "。"). You stop trying to vote your way out of a systematic bias. You stop calibrating prompt wording and pretending the model's boundary will follow. Instead, you put rules where rules work, one calibrated LLM where semantics actually matters, and humans where the 3D boundary surface gets fuzzy — which Section 2's dimension argument tells you is exactly where models disagree. In practice: cheap deterministic checks (length, keyword, format) catch the obvious garbage, one calibrated LLM call judges the semantic residual per requirement, and any split verdict escalates to a human. The LLM never sees the cases rules can handle — it sees only what rules can't.&lt;/p&gt;

&lt;p&gt;And then you pick a side of the wall. This is not a TODO; it is the load-bearing decision the rest of the design implements. More false positives means more reviewer attention burned on valid work flagged as suspect. More false negatives means more defective work ships. The tradeoff is structural. The only mistake is pretending you don't have to choose.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The illusion kept moving
&lt;/h2&gt;

&lt;p&gt;The series is called "Agent Determinism Illusions." Across six experiments, the illusion kept moving.&lt;/p&gt;

&lt;p&gt;It started in output determinism — temp=0 was supposed to guarantee consistency, and it doesn't (20 different versions of the same listing on a structured task). Caught, the illusion moved into review standards — formal checks were supposed to guarantee quality, and they don't ("file exists" passes "I am a little duck, quack quack"). Caught again, it moved into solution complexity — surely multi-model voting, or calibrated prompts, or layered pipelines would help. They don't, not really; each layer inherits the same wall. Caught a third time, the illusion stopped hiding in technical assumptions and moved up a level: into the meta-expectation that enough experiments produce a clean conclusion. They produce the conclusion that there is no clean conclusion.&lt;/p&gt;

&lt;p&gt;The illusion keeps moving because we keep chasing it. The work isn't to catch it. The work is to stop expecting it to stand still.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Experiment code: &lt;code&gt;agent-determinism-illusions/scripts/phasegate-formalism-test.py&lt;/code&gt;, &lt;code&gt;adversarial-verify-p1.py&lt;/code&gt;, &lt;code&gt;consistency-test-p2.py&lt;/code&gt;, &lt;code&gt;multi-perspective-vote-p3.py&lt;/code&gt;, &lt;code&gt;prompt-calibration-p3b.py&lt;/code&gt;, &lt;code&gt;p4-expanded-test.py&lt;/code&gt;&lt;/em&gt;&lt;em&gt;Previous: &lt;a href="https://dev.to/zxpmail/an-alternative-to-llm-quality-gates-deterministic-routing-sampling-1ilf"&gt;An alternative to LLM quality gates: deterministic routing + sampling&lt;/a&gt;&lt;/em&gt;&lt;em&gt;Series start: &lt;a href="https://dev.to/zxpmail/i-tested-the-deterministic-agent-loop-claims-with-four-experiments-they-all-failed-including-38kj"&gt;I tested the 'deterministic agent loop' claims with four experiments. They all failed — including my own fix.&lt;/a&gt;&lt;/em&gt;*Full series: [GitHub&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>An alternative to LLM quality gates: deterministic routing + sampling</title>
      <dc:creator>zxpmail</dc:creator>
      <pubDate>Thu, 09 Jul 2026 12:16:29 +0000</pubDate>
      <link>https://dev.to/zxpmail/an-alternative-to-llm-quality-gates-deterministic-routing-sampling-1ilf</link>
      <guid>https://dev.to/zxpmail/an-alternative-to-llm-quality-gates-deterministic-routing-sampling-1ilf</guid>
      <description>&lt;p&gt;&lt;em&gt;Every "agent quality gate" I tested shares one fatal assumption: that an LLM can judge whether an LLM did the right thing. This article drops that assumption. The alternative isn't a smarter judge — it's no judge at all, in the control layer.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Over the last three articles, I tested the popular "production agent loop" design across six separate experiments:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lexical overlap ≠ semantics&lt;/strong&gt; — 50% misclassification&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temperature 0 ≠ determinism&lt;/strong&gt; — open output only 70% consistent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase gates ≠ task completion&lt;/strong&gt; — 50% false positives&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embedding ≠ synonym/antonym separation&lt;/strong&gt; — cosine diff 0.026&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stronger models trade false positives for false rejections&lt;/strong&gt; — GLM-5.2 hit 0% FP but rejected 75% of valid work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture diagrams ≠ solutions&lt;/strong&gt; — my own human-in-the-loop Harness had 6 unvalidated assumptions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Six rounds of dismantling, all backed by reproducible experiments.&lt;/p&gt;

&lt;p&gt;Then I asked myself the question every critic has to answer: &lt;strong&gt;"What's your alternative?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here it is. Not an architecture diagram — a set of four implementable strategies, all using deterministic code, zero new LLM dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core insight shift
&lt;/h2&gt;

&lt;p&gt;Every approach I tested or proposed shared a fatal assumption: &lt;strong&gt;a single module (LLM or human) can judge whether output is "correct."&lt;/strong&gt; That binary judgment at the semantic layer is what creates the precision-recall trap that all three model tiers fell into.&lt;/p&gt;

&lt;p&gt;The alternative: &lt;strong&gt;don't judge correctness. Judge risk.&lt;/strong&gt; Route high-risk work out of the agent pipeline entirely. Auto-release low-risk work. Only show medium-risk work to a human — and when you do, make it a diff review, not a full-text read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four-layer architecture (all deterministic code)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Layer 1: task-type routing
&lt;/h3&gt;

&lt;p&gt;Before a task enters the agent engine, a router classifies it by output type:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A (verifiable)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Output is compilable / schema-validatable (code, JSON, SQL)&lt;/td&gt;
&lt;td&gt;Compile check or schema validation as the deterministic gate, plus a sampled fraction routed to diff review (see Layer 4). No LLM quality inspector called for the gate itself.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;B (high-risk)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Money, legal, privacy, external publishing&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No agent execution.&lt;/strong&gt; Prompt: "This task requires human handling." AI provides a draft only, never auto-executes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;C (low-risk content)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Internal briefs, first drafts, brainstorming&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Auto-release.&lt;/strong&gt; Tag as "draft" (80% default confidence). No quality queue.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;D (medium-risk content)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Client-facing emails, external documents&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Diff review.&lt;/strong&gt; Don't judge content quality. Only show what changed.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Why this beats an "LLM quality inspector": it acknowledges the LLM's limit at the source. Use the LLM for what it can do (generate). Never use an LLM for what it does poorly (judge semantic quality).&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2: diff review — replace "judge right/wrong"
&lt;/h3&gt;

&lt;p&gt;This is the key operational alternative. For Type D tasks, don't show the reviewer the "final output." Show them &lt;strong&gt;what the agent changed from the previous version.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Implementation: after generation, the system diffs the output against the original (or a template) using &lt;code&gt;difflib&lt;/code&gt; — no LLM needed.&lt;/p&gt;

&lt;p&gt;Reviewer UI: only the modified lines are highlighted. The reviewer answers one question: &lt;strong&gt;"Does this change introduce an error?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Measured cognitive load:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Reading load&lt;/th&gt;
&lt;th&gt;Cognitive demand&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Full-text quality judgment (500 words)&lt;/td&gt;
&lt;td&gt;500 words&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;~60 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diff review (50-word change)&lt;/td&gt;
&lt;td&gt;50 words&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Low&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;~10 s&lt;/strong&gt; &lt;em&gt;(lab; production: 30–90 s — see Knife 3 below)&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The shift: open-ended judgment ("Is this article good?") becomes closed-ended ("Did this paragraph break something?"). Cognitive demand drops significantly — though the lab "~10 s" compresses to 30–90 s in production once context-switching overhead is included (Knife 3 below).&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 3: statistical process control — replace semantic clustering
&lt;/h3&gt;

&lt;p&gt;Semantic clustering failed because content about different topics (a research brief vs. a chapter) won't embed close together just because they were both falsely rejected.&lt;/p&gt;

&lt;p&gt;Alternative: &lt;strong&gt;don't cluster by semantic features. Cluster by behavioral features.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For every task, record metadata — execution steps, tool-call count, output length, special-character ratio. Then detect anomalies with simple statistical thresholds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;15 execution steps vs. mean of 5 → flag&lt;/li&gt;
&lt;li&gt;Output length = 0 → flag&lt;/li&gt;
&lt;li&gt;Output is 100% punctuation (the duck and the period from Experiment 3) → flag&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All code. Zero LLM cost. And it catches "quack quack" (length anomaly) and "." (special-char anomaly) without understanding a word.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measured:&lt;/strong&gt; I ran SPC on the 8 scenarios from Experiment E (content length, special-char ratio, CJK ratio, alpha ratio, digit ratio; statistical threshold = mean +/- 1.5sd):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;SPC result&lt;/th&gt;
&lt;th&gt;Actual&lt;/th&gt;
&lt;th&gt;Correct?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;G1 (duck, garbage)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;ANOMALY&lt;/strong&gt; (high CJK ratio)&lt;/td&gt;
&lt;td&gt;Garbage&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G2 (period, garbage)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;ANOMALY&lt;/strong&gt; (100% special char)&lt;/td&gt;
&lt;td&gt;Garbage&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G3 (TODO, garbage)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;ANOMALY&lt;/strong&gt; (100% alphabetic)&lt;/td&gt;
&lt;td&gt;Garbage&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;G4 (zero-case, garbage)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;NORMAL&lt;/strong&gt; (same features as L4)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Garbage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;❌ FN&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L1-L4 (valid)&lt;/td&gt;
&lt;td&gt;Normal (one mild false-positive)&lt;/td&gt;
&lt;td&gt;Valid&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;SPC catches format anomalies (period, TODO, duck). &lt;strong&gt;G4 (zero-case test log) has the exact same behavioral profile as L4 (valid test log) — SPC misses it 100%.&lt;/strong&gt; This directly validates the stated blind spot: SPC catches format anomalies but not semantic traps. G4‑class failures can only be caught by sampling, never prevented.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 4: fixed-rate sampling — replace confidence scores
&lt;/h3&gt;

&lt;p&gt;Several approaches I initially proposed relied on a "confidence score" (&amp;gt; 95% auto-release, &amp;lt; 80% human review). The hidden cost: confidence requires a feedback loop to calibrate — database, ground-truth labeling, delayed updates. The same complexity I criticized in the closed-loop calibration critique.&lt;/p&gt;

&lt;p&gt;Alternative: &lt;strong&gt;fixed-rate sampling. No confidence math.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Handling&lt;/th&gt;
&lt;th&gt;Sample rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A (verifiable)&lt;/td&gt;
&lt;td&gt;Compile / schema gate + &lt;strong&gt;sampled into diff review&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;X%&lt;/strong&gt; (tuned)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B (high-risk)&lt;/td&gt;
&lt;td&gt;Mandatory human&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C (low-risk content)&lt;/td&gt;
&lt;td&gt;Auto-release&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D (medium-risk content)&lt;/td&gt;
&lt;td&gt;Diff review (all items)&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero-shot generation (no prior version, no template)&lt;/td&gt;
&lt;td&gt;Sample review&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Fixed 5%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Post-publication correction (raised by Dipankar Sarkar in the dev.to comments):&lt;/strong&gt; the original version of this table had Type A at 0% sample rate. That quietly treated schema-validatable syntax as a stand-in for semantic correctness — schema-valid JSON with a plausible-but-wrong value clears the gate silently, code that compiles can still book the wrong flight. This is the same class as the G4 finding in Layer 3 above (format-channel gate kills format-channel failure, not semantic failure); I called it out for SPC and then let Type A make the same mistake one layer up. The 0% was an indefensible asymmetry: zero-shot content gets sampled because there's no prior version to diff against, but schema-validatable code doesn't? X% should be calibrated from defect-rate data using the same logic as zero-shot's 5%. Start at 1-2% in week one, tune from there.&lt;/p&gt;

&lt;p&gt;I admit: 5% is a guess. But its mathematical properties are known and quantifiable — which is more than can be said for a confidence score with no feedback loop.&lt;/p&gt;




&lt;h2&gt;
  
  
  Relentless self-review (same ruler)
&lt;/h2&gt;

&lt;p&gt;Before calling this "done," I applied the same six-cut standard to this design.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding 1: classification is not free
&lt;/h3&gt;

&lt;p&gt;Type labels can't depend on business owners manually tagging every task. They don't know their own types — they'd label 70% as "D" to be safe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; In the MVP phase, use two hard rules for automatic classification: ① if the task text contains sensitive keywords (money/contract/compensation) → force B; ② if the tool-call chain hits "send/publish/submit" → force human confirmation. Everything else defaults to C. Tune thresholds after launch based on false-positive rate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding 2: diff review covers a narrower range than "edit tasks"
&lt;/h3&gt;

&lt;p&gt;Diff review only works when there's a clear prior version. Agent workflows often involve &lt;strong&gt;reading five source documents → writing a new one from scratch&lt;/strong&gt; — there's no single "previous version" to diff against.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; In this design, "edit task" means exactly "a prior version of the same document exists." Multi-document synthesis tasks go to "zero-shot generation" → fixed 5% sampling. This is an honest scope reduction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding 3: 5% sampling has known detection probability
&lt;/h3&gt;

&lt;p&gt;With 5% sampling on zero-shot tasks: if the real defect rate is 20% on a given day, the probability of detecting at least one defective item = 1 − (0.8)⁵ = &lt;strong&gt;67%&lt;/strong&gt;. That means &lt;strong&gt;33% probability of zero detection&lt;/strong&gt; on any single day — a silent degradation could slip through for days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; 5% for non-critical content is acceptable. For critical content, raise to 10–20% or use deterministic sampling (every Nth item). First week post-launch: use 20% sampling to collect baseline defect-rate data before tuning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding 4: sensitive-tool interception is not free
&lt;/h3&gt;

&lt;p&gt;Intercepting "send email" after the agent has already taken 4 steps is not zero-cost — those steps consumed inference budget.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Add a "preheat check" before the agent executes — scan the user's request text for sensitive verbs (send/modify/delete/submit) and pre-confirm with the user. Don't wait until runtime to pull the trigger.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding 5: engineering cost — I made the same mistake I criticized
&lt;/h3&gt;

&lt;p&gt;I initially estimated 2 engineer-months for the MVP. Same flaw as the cost analysis I criticized in my previous article: I only counted the core modules, not the integration.&lt;/p&gt;

&lt;p&gt;Honest breakdown:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Module&lt;/th&gt;
&lt;th&gt;Effort&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Diff review UI (visual diff + highlight + judgment button)&lt;/td&gt;
&lt;td&gt;1 engineer-month (frontend)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SPC collector (metadata + thresholds + aggregation)&lt;/td&gt;
&lt;td&gt;0.5 engineer-month (backend)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sensitive-tool whitelist + runtime interceptor&lt;/td&gt;
&lt;td&gt;0.5 engineer-month (full-stack, needs agent framework hooks)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitoring dashboard + alerts&lt;/td&gt;
&lt;td&gt;1 engineer-month (full-stack)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sampling queue + assignment + expiry&lt;/td&gt;
&lt;td&gt;0.5 engineer-month (backend)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.5 engineer-months (MVP)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's &lt;strong&gt;30% cheaper&lt;/strong&gt; than the 5 engineer-month human-in-the-loop Harness — not 60%. Less sexy, but real.&lt;/p&gt;




&lt;h2&gt;
  
  
  Honest close: what this design solves and what it doesn't
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does solve
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ROI inversion:&lt;/strong&gt; Type A deterministic gate + sampled diff review + C auto-release + D diff-only. The fraction requiring human review drops enough that 3.5 engineer-months of investment breaks even within a reasonable horizon for most mid-volume deployments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clustering failure:&lt;/strong&gt; SPC on behavioral features replaces embedding clustering. Verifiable by code, zero LLM cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human error:&lt;/strong&gt; Diff review reduces cognitive load. It doesn't eliminate errors (semantic traps still need domain knowledge), but it measurably reduces the error rate.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Doesn't solve
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;G4-class semantic traps (zero-case test log).&lt;/strong&gt; These are caught by sampling, not prevented. The honest difference from the original "deterministic agent" articles: they claimed prevention; we acknowledge detection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type A semantic traps (compile-pass-but-wrong).&lt;/strong&gt; Compiles-but-books-wrong-flight is sampled into diff review, not prevented. Same class as G4 above. The Layer 4 table originally had Type A at 0% sample rate — an indefensible asymmetry, corrected above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Humans are still the final decision layer.&lt;/strong&gt; In sensitive operations and edit reviews, humans are not optional.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-shot generation is sampled, not guaranteed.&lt;/strong&gt; 5% sampling means 67% single-day detection probability at 20% defect rate. For critical content, raise to 20% (98% detection probability).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Classification is imperfect.&lt;/strong&gt; Automatic keyword and tool-chain classification has measurable false-positive and false-negative rates that must be tuned post-launch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The actual prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A router/whitelist implementation, SPC threshold configuration, diff review UI, sampling queue, and monitoring dashboard — all standard CRUD + regex + statistics. No LLM dependency.&lt;/li&gt;
&lt;li&gt;Engineering investment: 3.5 engineer-months for an MVP.&lt;/li&gt;
&lt;li&gt;Business acceptance: &lt;strong&gt;"high risk requires human," "zero-shot is sampled," "semantic traps are detected, not prevented."&lt;/strong&gt; These three constraints are business decisions, not engineering ones. No design can substitute for them.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final rating (same ruler)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Rating&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unvalidated assumptions?&lt;/td&gt;
&lt;td&gt;Yes, all stated (5% sampling = 67% detection probability, not 100%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM dependency in control layers?&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Zero.&lt;/strong&gt; All control logic is deterministic code.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engineering cost estimated?&lt;/td&gt;
&lt;td&gt;Yes: 3.5 engineer-months (honest, with integration costs)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Honest boundary declarations?&lt;/td&gt;
&lt;td&gt;Yes: G4 traps not prevented, zero-shot sampled, humans not free, classification imperfect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-dismantling?&lt;/td&gt;
&lt;td&gt;Yes — the five findings above dismantle everything that could be dismantled, plus the post-publication correction on Type A's sample rate (raised by Dipankar Sarkar). What remains are engineering facts: Type A deterministic gate + sampling, sensitive-tool hard interception, SPC format anomaly detection, and diff review cognitive-load reduction.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Three more knives before production (round two of relentless review)
&lt;/h2&gt;

&lt;p&gt;Before this design hits production, three operational problems surfaced that I hadn't fully addressed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Knife 1: SPC cold-start baseline drift
&lt;/h3&gt;

&lt;p&gt;SPC uses statistical thresholds (mean +/- 1.5sd). But where does the mean and sd come from on day one?&lt;/p&gt;

&lt;p&gt;You need 500-1000 "normal" traces to establish a baseline. If week 1 has a bug that makes every trace abnormally long, the baseline is skewed — real anomalies later get absorbed into the "new normal."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measured:&lt;/strong&gt; I simulated three phases (normal → bug → recovery + new anomaly) to find the real risk:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bug severity (mean)&lt;/th&gt;
&lt;th&gt;Mixed threshold&lt;/th&gt;
&lt;th&gt;Anomaly (20 steps) detected?&lt;/th&gt;
&lt;th&gt;Static threshold (&amp;gt;10)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Normal(5) → Bug 8&lt;/td&gt;
&lt;td&gt;9.7&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normal(5) → Bug 12&lt;/td&gt;
&lt;td&gt;12.7&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normal(5) → Bug 16&lt;/td&gt;
&lt;td&gt;16.7&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normal(5) → &lt;strong&gt;Bug 20&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20.5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No (missed)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normal(5) → Bug 21&lt;/td&gt;
&lt;td&gt;21.6&lt;/td&gt;
&lt;td&gt;No (missed)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Crossover: dynamic threshold only fails at 4x the normal mean (Bug mean &amp;gt;= 20).&lt;/strong&gt; SPC is more robust against moderate drift (2–3x) than the original critique claimed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Revised response:&lt;/strong&gt; Not a two-phase switch ("static first, then dynamic"), but &lt;strong&gt;dual thresholds in parallel&lt;/strong&gt;: a static absolute threshold (steps &amp;gt; 20 always flagged) plus a dynamic relative threshold (rolling 7-day window). Either triggers — no dependency on clean cold-start data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Knife 2: context escape in sensitive-tool interception
&lt;/h3&gt;

&lt;p&gt;Keyword-based scanning of the user's request text for "send," "email" — but this fails on:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Simulate sending a quote email to the client for preview, &lt;strong&gt;don't actually send it&lt;/strong&gt;."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The scanner fires — user gets blocked — forced into manual flow. The agent's actual call chain only had &lt;code&gt;preview_email&lt;/code&gt;, never &lt;code&gt;send_email&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In practice, keyword-based interception has a &lt;strong&gt;30–50% false-positive rate&lt;/strong&gt; (users say "pretend to send," "let me see first," "save as draft"). Every false block erodes user trust. High false-positive rates drive users to &lt;strong&gt;bypass the system entirely&lt;/strong&gt; — copying the email to their external client and sending it there, defeating the control entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Revised response (v1, at publication):&lt;/strong&gt; Execution-time interception only. Block the agent &lt;em&gt;at the point of tool invocation&lt;/em&gt; (&lt;code&gt;send_email&lt;/code&gt; called = block; &lt;code&gt;preview_email&lt;/code&gt; called = pass). Don't scan the user's request text. This sacrifices "early interception saves inference cost" but delivers &lt;strong&gt;zero false positives&lt;/strong&gt; — the tool was either called or it wasn't, no ambiguity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Revised response (v2, post-publication):&lt;/strong&gt; The either/or framing in v1 drops a viable middle ground (raised by Nazar Boyko in the dev.to comments). Keep the request-text scan, but demote it to a &lt;strong&gt;soft signal that never blocks&lt;/strong&gt;: scan fires → agent prompts user "this task looks like it ends in a send — confirm the plan before I spend steps on it." If the user says "actually send," the agent proceeds to the tool call where the &lt;strong&gt;hard gate&lt;/strong&gt; still fires (zero FP). If the user says "just previewing," the agent routes to &lt;code&gt;preview_email&lt;/code&gt; and never hits the gate.&lt;/p&gt;

&lt;p&gt;The layered design takes both benefits v1 traded off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Finding 4 preserved:&lt;/strong&gt; soft signal fires before inference is spent, so the agent doesn't burn 4 steps before being stopped or redirected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knife 2's zero-FP-block preserved:&lt;/strong&gt; the hard gate at tool invocation never false-positives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; extra UX friction on simulation requests — unavoidable, since the LLM itself can't reliably tell "simulate" from "real" either.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Measured (post-publication):&lt;/strong&gt; &lt;code&gt;scripts/knife2-fp-rate-test.py&lt;/code&gt; (N=40, zero-LLM) verified the original "30-50% FP" claim. Coverage on FP-prone scenarios (simulate / draft / conditional / discussion): &lt;strong&gt;95%&lt;/strong&gt; (19/20 — the miss was "submission" not matching the "submit" regex root, itself a keyword-scan blind spot). Implied FP rate under 50/50 real/sim mix: &lt;strong&gt;48.7%&lt;/strong&gt; — within the claimed band. The FP mechanism is real; the layered design is the right answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Knife 3: diff review "10 seconds" shrinks in real UI
&lt;/h3&gt;

&lt;p&gt;The measured "50-character diff in 10 seconds" is pure reading time. In production, the reviewer's flow is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;See highlight → recall what the original said → think about context → judge whether the change introduces an error → click approve/reject&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With context-switching overhead, real per-item time is &lt;strong&gt;30–45 seconds&lt;/strong&gt;. At 50 items/day: 25–37 minutes. Still manageable, but the "order-of-magnitude compression" only exists in the lab.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Revised estimate:&lt;/strong&gt; Diff review time adjusted from "10 s/item" to "30 s (routine) / 90 s (deep review)." Impact on staffing: 0.3 FTE → 0.5 FTE. Not a collapse, but an honest correction.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final honest table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Original design&lt;/th&gt;
&lt;th&gt;After all corrections&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SPC cold start&lt;/td&gt;
&lt;td&gt;Not addressed&lt;/td&gt;
&lt;td&gt;Dual thresholds in parallel, robust to 4x drift&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sensitive-tool interception&lt;/td&gt;
&lt;td&gt;Keyword scan (30-50% FP)&lt;/td&gt;
&lt;td&gt;Layered: soft signal (request scan, non-blocking) + hard gate (tool invocation) — v2 post-pub&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diff review time&lt;/td&gt;
&lt;td&gt;10 s&lt;/td&gt;
&lt;td&gt;30-90 s (0.3 → 0.5 FTE)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engineering cost&lt;/td&gt;
&lt;td&gt;2 engineer-months&lt;/td&gt;
&lt;td&gt;3.5 engineer-months&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM dependency in control layers&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None (verifiable, deterministic code throughout)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What remains are business decisions: accept "high risk = human"? accept "semantic traps caught by sampling, not prevention"? accept 30–90 second diff review cycles? These questions have no engineering answers — but the engineering baseline for answering them is now measurable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Don't judge correctness. Judge risk."&lt;/strong&gt; — this isn't a smarter architecture. It's a more honest one. It doesn't claim to solve what it can't solve. It just makes the remaining manual work cheaper, faster, and less error-prone.&lt;/p&gt;

&lt;p&gt;And after five rounds of measurement, falsification, self-correction, and reconstruction — that's as far as engineering can go. The rest is a business decision.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>I designed a Harness to fix my agent's quality problem — then found 6 flaws in my own design</title>
      <dc:creator>zxpmail</dc:creator>
      <pubDate>Mon, 06 Jul 2026 21:58:25 +0000</pubDate>
      <link>https://dev.to/zxpmail/i-designed-a-harness-to-fix-my-agents-quality-problem-then-found-6-flaws-in-my-own-design-5h29</link>
      <guid>https://dev.to/zxpmail/i-designed-a-harness-to-fix-my-agents-quality-problem-then-found-6-flaws-in-my-own-design-5h29</guid>
      <description>&lt;p&gt;In my previous article (&lt;a href="https://dev.to/zxpmail/i-tested-3-models-as-ai-agent-quality-inspectors-the-stronger-the-model-the-more-valid-work-it-gl7"&gt;I tested 3 models as AI agent quality inspectors: the stronger the model, the more valid work it rejects - DEV Community&lt;/a&gt;), I measured three model tiers as agent output quality inspectors across 8 scenarios (4 valid, 4 garbage). The result was a clean precision-recall tradeoff:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;qwen3:0.5b (weak model): 25% garbage pass-through, 50% false rejections&lt;/li&gt;
&lt;li&gt;GLM-5.2 (strong model): 0% garbage pass-through, &lt;strong&gt;75% false rejections&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest conclusion: a quality gate isn't a solution — it's a &lt;strong&gt;risk-transfer layer.&lt;/strong&gt; Each layer catches some failures and introduces new ones.&lt;/p&gt;

&lt;p&gt;I didn't stop there. I asked myself: if you accept the human-in-the-loop cost and design a proper Harness — not an automatic fix, but a system that makes human review efficient — what does it look like?&lt;/p&gt;

&lt;p&gt;I sketched a 4-module architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Batch clustering:&lt;/strong&gt; compress 750 flagged items into 100 groups by failure vector, review one representative per group&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Closed-loop calibration:&lt;/strong&gt; human verdicts → sample pool → scheduled few-shot updates → inspector gets smarter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human as gold standard:&lt;/strong&gt; final arbitration by a trained reviewer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asynchronous batching:&lt;/strong&gt; accumulate flagged items, review in batches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It looked complete. It looked like progress beyond the "it's all tradeoffs" conclusion.&lt;/p&gt;

&lt;p&gt;Then I picked up the same ruler I used on the original production-agent articles, and measured this design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Six flaws. Not one less.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Flaw 1: batch clustering — mathematically elegant, operationally dangerous
&lt;/h2&gt;

&lt;p&gt;The proposal: "cluster 750 flagged items into 100 groups by failure vector; review one representative per group."&lt;/p&gt;

&lt;p&gt;This assumes that "failure modes" can be correctly grouped by embedding clustering. But the 3 falsely-rejected scenarios from the GLM-5.2 experiment had three &lt;em&gt;different&lt;/em&gt; reasons:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Scenario A (research brief):&lt;/strong&gt; content too short (title + one sentence)&lt;strong&gt;Scenario B (draft):&lt;/strong&gt; missing structure (no chapter divisions)&lt;strong&gt;Scenario C (chapter files):&lt;/strong&gt; too many placeholders (all TODO)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These three have low embedding similarity — one about a research brief, one about a draft, one about file structure. &lt;strong&gt;They wouldn't cluster into the same group.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I ran a quick embedding experiment to confirm (Qwen3-embedding:0.6b on the actual 8 scenarios from Experiment E):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pair&lt;/th&gt;
&lt;th&gt;Cosine&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L1-L2 (brief vs draft, both valid)&lt;/td&gt;
&lt;td&gt;0.610&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L1-L3 (brief vs chapter, both valid)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.295&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Unrelated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L2-L3 (draft vs chapter, both valid)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.341&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Unrelated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Average across all three&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.415&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Won't cluster together&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;L1 and L3 share a cosine of 0.295 — they're both false rejects of the strong model, but that shared label doesn't pull two different topics closer in embedding space. The 750→100 compression ratio has no embedding basis. A practical clustering run would place these three into separate groups, each requiring its own review — the promised compression evaporates.&lt;/p&gt;

&lt;p&gt;Worse: if the clustering algorithm accidentally groups 3 real-garbage items with 1 false-rejected valid item, and the reviewer clicks "approve" to correct the false rejection — &lt;strong&gt;all 3 garbage items slip through.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I found exactly this in the data: scenario L4 (valid test log, "42 passed") and scenario G4 (garbage zero-case log, "0 passed (no tests collected)") have a cosine of &lt;strong&gt;0.861&lt;/strong&gt; — both are test-log formatted strings, and embedding reads format, not semantics. &lt;strong&gt;Clustering will absolutely group them together.&lt;/strong&gt; A reviewer correcting L4's false rejection would simultaneously approve G4, pushing the false-positive rate back to 25% — the same as without the strong model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; the clustering compression ratio has zero experimental support. It might reduce false rejections from 75% to 0% — &lt;strong&gt;but it could simultaneously raise the false-positive rate from 0% to 30%.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Flaw 2: closed-loop calibration — shifting the problem to "tuning"
&lt;/h2&gt;

&lt;p&gt;The proposal: "human clicks → sample pool → scheduled few-shot updates → inspector gradually gets smarter."&lt;/p&gt;

&lt;p&gt;This uses &lt;strong&gt;a second meta-LLM&lt;/strong&gt; (the one that selects and formats few-shot examples) to correct the first LLM's bias. But that meta-LLM has the same temperature-0 instability — Experiment 2 showed open-ended output is only 70% consistent.&lt;/p&gt;

&lt;p&gt;The "common patterns" it extracts from 100 valid samples might look like: "contains a heading," "length &amp;gt; 100 characters," "has paragraph breaks." If the business requirement changes tomorrow to "write a one-line summary," these historically learned patterns become a &lt;em&gt;new source&lt;/em&gt; of false rejections.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A closed loop can only adapt to past data distribution. It cannot handle distribution shift.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;More fundamentally: there is zero evidence that feeding more few-shot examples linearly reduces false-rejection rates. I tested this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt; qwen3:0.5b, same 8 scenarios (4 valid + 4 garbage), N=5 runs each. Baseline: original prompt. Treatment: same prompt with 3 few-shot examples prepended (including "short but valid content → PASS").&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Baseline false-rejection rate&lt;/th&gt;
&lt;th&gt;+Few-shot false-rejection rate&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L1 (brief, valid)&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;40%&lt;/td&gt;
&lt;td&gt;✅ improved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L2 (draft, valid)&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ worse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L3 (chapter, valid)&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;=&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L4 (test log, valid)&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ worse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Aggregate false-rejection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;50%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;80%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Garbage pass-through&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;L1 improved (the brief was exactly the kind of "short but valid" the examples taught). But L2 and L4 — scenarios that were &lt;em&gt;correctly&lt;/em&gt; accepted at baseline — both jumped to 100% rejection. G2 (period character) went from 0% to 40% false positive — &lt;strong&gt;new holes opened.&lt;/strong&gt; Few-shot is whack-a-mole: every fix trades off somewhere else.&lt;/p&gt;

&lt;p&gt;You might feed 500 samples and GLM-5.2 still kills "short but valid" outputs. Its "strictness" bias is at the &lt;strong&gt;model-weight level&lt;/strong&gt; — not something a few in-context examples can overwrite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; I promised the closed loop would calibrate. That promise rests on an unvalidated assumption — that LLM bias is correctable through in-context examples. Experiment 2 already showed that temperature 0 is fundamentally unstable; adding few-shot just adds another layer of instability.&lt;/p&gt;




&lt;h2&gt;
  
  
  Flaw 3: the reviewer is the "gold standard" — the most subtle lie
&lt;/h2&gt;

&lt;p&gt;Every human-in-the-loop solution has a silent assumption: &lt;strong&gt;humans don't make mistakes.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reviewer fatigue:&lt;/strong&gt; on item #100 of "TODO" and item #101 of "I am a little duck quack quack," they might misclick&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standard drift:&lt;/strong&gt; strict in the morning, lenient in the afternoon (because it's almost quitting time)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI bias:&lt;/strong&gt; if "approve" is on the left and "reject" on the right, click-position alone may bias decisions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If human misjudgment is 5% (optimistic), then "human review" introduces 5% label noise. That noise flows back through the closed loop, contaminating the sample pools, and poisoning the few-shot examples the quality inspector learns from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The honest question is: "who judges the reviewer's judgment?"&lt;/strong&gt; — it's a recursive infinite regress. My design was silent on this.&lt;/p&gt;




&lt;h2&gt;
  
  
  Flaw 4: the fatal synchronous-vs-asynchronous blind spot
&lt;/h2&gt;

&lt;p&gt;My design assumed tasks can be accumulated and reviewed in batches. That works for data exports, report generation, and other asynchronous jobs.&lt;/p&gt;

&lt;p&gt;But most agent scenarios are &lt;strong&gt;synchronous&lt;/strong&gt; — customer support, coding assistants. The user asks a question, the agent takes 3 seconds to respond, the quality inspector flags it as "uncertain" and puts it in the human queue — &lt;strong&gt;and the user is still waiting in the chat window.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Batch review means: how long does the user wait? 5 minutes? 1 hour? This turns a real-time assistant into a ticket system.&lt;/p&gt;

&lt;p&gt;I didn't distinguish synchronous from asynchronous. I applied one architecture to both. This is a product-design-level omission.&lt;/p&gt;




&lt;h2&gt;
  
  
  Flaw 5: engineering cost vs. benefit — the biggest hole
&lt;/h2&gt;

&lt;p&gt;I ran the numbers: "750 items → 100 groups → 1 reviewer."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I didn't cost out was building the Harness itself:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Evidence-trace visualization frontend: 2 engineer-months&lt;/li&gt;
&lt;li&gt;Clustering + vector-search backend: 1 engineer-month&lt;/li&gt;
&lt;li&gt;Closed-loop feedback pipeline: 1 engineer-month&lt;/li&gt;
&lt;li&gt;ICU dashboard + monitoring: 1 engineer-month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total: 5 engineer-months. At typical dev cost, that's roughly $75K.&lt;/p&gt;

&lt;p&gt;What does it save? (7.5 reviewers − 1 reviewer) = 6.5 reviewer salaries. At ~$40K/year each, about $21K/month saved.&lt;/p&gt;

&lt;p&gt;Break-even: $75K ÷ $21K ≈ &lt;strong&gt;3.5 months.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I built a sensitivity matrix across DAU, false-rejection rate (FRR), and review speed ($40K/yr per reviewer, $75K investment):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;DAU&lt;/th&gt;
&lt;th&gt;FRR&lt;/th&gt;
&lt;th&gt;Daily false rejects&lt;/th&gt;
&lt;th&gt;Headcount (w/o system)&lt;/th&gt;
&lt;th&gt;Headcount (with system)&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Break-even&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;td&gt;125&lt;/td&gt;
&lt;td&gt;0.8&lt;/td&gt;
&lt;td&gt;0.3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;38 months&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;375&lt;/td&gt;
&lt;td&gt;2.3&lt;/td&gt;
&lt;td&gt;0.8&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12 months&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;750&lt;/td&gt;
&lt;td&gt;4.7&lt;/td&gt;
&lt;td&gt;1.6&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6 months&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;td&gt;1,250&lt;/td&gt;
&lt;td&gt;7.8&lt;/td&gt;
&lt;td&gt;2.6&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4 months&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;2,500&lt;/td&gt;
&lt;td&gt;15.6&lt;/td&gt;
&lt;td&gt;5.2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2 months&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;75%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3,750&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;23.4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;≈1 month&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The "3.5 months" claim only holds at the extreme: DAU=1000, FRR=75%, 30-seconds/review. Drop DAU to 100, break‑even jumps to 34–38 months — cheaper to just hire.&lt;/p&gt;

&lt;p&gt;More stringent: false-rejection rate itself is a decay function. If GLM-5.2's next update drops FRR from 75% to 40% (not unlikely):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Daily false rejects: 3,750 → 2,000&lt;/li&gt;
&lt;li&gt;Headcount (with system): 7.8 → 4.2&lt;/li&gt;
&lt;li&gt;Monthly savings: $21K → $12K&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Break-even: 1 month → 2 months → 4 months&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;FRR halves; break-even quadruples. Model updates are the norm, not an exception.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"The problem will persist" is the most convenient and least-validated assumption in the entire design.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Flaw 6: "15 seconds vs. 3 minutes" — a fabricated efficiency claim
&lt;/h2&gt;

&lt;p&gt;I wrote: "with the Harness, review time drops from 3 minutes to 15 seconds."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This number is completely made up.&lt;/strong&gt; I constructed three realistic agent execution traces and measured reading time at a conservative 250 word/minute rate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Trace scale&lt;/th&gt;
&lt;th&gt;Characters&lt;/th&gt;
&lt;th&gt;Minimum reading time&lt;/th&gt;
&lt;th&gt;vs "15 seconds"&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Simple (3 steps, 1 task)&lt;/td&gt;
&lt;td&gt;332&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;21 seconds&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+6s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium (12 steps, 3 subtasks)&lt;/td&gt;
&lt;td&gt;1,154&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48 seconds&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+33s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex (28 steps, full pipeline)&lt;/td&gt;
&lt;td&gt;1,110&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;44 seconds&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+29s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Even the simplest trace takes 21 seconds — 40% over the claim. Real production traces (12–28 steps) take 44–48 seconds, 2–3x the "15 seconds." If I compress the trace into a summary, the summary itself loses information — and information loss drives misjudgment.&lt;/p&gt;

&lt;p&gt;I ran zero user tests. I just picked "15 seconds" to make the design look sexy. &lt;strong&gt;This is the same marketing rhetoric as the Rust blog's "80% decided by code" claim.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Honest revision: if I rewrote this design from scratch
&lt;/h2&gt;

&lt;p&gt;I would not propose a "4-module Harness" architecture. I would write:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State the boundary first:&lt;/strong&gt; this Harness only applies to &lt;strong&gt;asynchronous, non-real-time, high-value&lt;/strong&gt; tasks. For real-time conversations, skip all clustering — do "confidence &amp;lt; 0.9 → transfer to human," nothing fancy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Give a cost matrix:&lt;/strong&gt; a table of "DAU vs. false-rejection rate vs. engineering investment," so the reader can judge whether it's worth building for their scale. Not a single pre-cooked "1 reviewer handles it."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Admit that humans also misjudge:&lt;/strong&gt; add a "reviewer consistency check" — randomly assign the same item to two reviewers; if they disagree, escalate to a third. State the cost of this explicitly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delete "15 seconds":&lt;/strong&gt; replace with "review time depends on task complexity — must be measured in production."&lt;/p&gt;




&lt;h2&gt;
  
  
  Final self-assessment
&lt;/h2&gt;

&lt;p&gt;My "human-in-the-loop Harness" proposal was more honest than the Rust blog — it acknowledged tradeoffs and costs. But it wasn't honest enough. After acknowledging the costs, it quietly &lt;strong&gt;dissolved&lt;/strong&gt; them with a new set of unvalidated architectural promises — clustering compression, closed-loop calibration, 15-second decisions.&lt;/p&gt;

&lt;p&gt;The same line I used against the original articles applies to my own design:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Treating 'decided' as 'decided correctly' is a rhetorical trap."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I treated &lt;strong&gt;"architecture diagram drawn"&lt;/strong&gt; as &lt;strong&gt;"problem solved."&lt;/strong&gt; — it's the same rhetorical move in a different suit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The hard conclusion remains:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Under the current stack, semantic correctness has no engineering solution. A Harness can make "human intervention" more efficient and more observable — but it cannot eliminate it. Any proposal that claims to "dramatically reduce human cost" needs at least 3 months of online A/B testing validation — not an architecture diagram.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Six articles. One ruler.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Part 1:&lt;/strong&gt; measured the genre's "determinism" claims — all three illusions, data-falsified&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 2:&lt;/strong&gt; measured my own "embedding upgrade" — same disease, also failed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 3:&lt;/strong&gt; measured three model tiers — not a solution, a precision-recall tradeoff&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This one:&lt;/strong&gt; measured my own architectural design — "architecture drawn" ≠ "problem solved"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ruler went full circle and measured me three times, each pass sharper than the last.&lt;/p&gt;

&lt;p&gt;**This isn't "I was right." It's "every time I thought I was done, the ruler showed me I wasn't."&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>testing</category>
      <category>agents</category>
    </item>
    <item>
      <title>I tested 3 models as AI agent quality inspectors: the stronger the model, the more valid work it rejects</title>
      <dc:creator>zxpmail</dc:creator>
      <pubDate>Sun, 05 Jul 2026 22:06:00 +0000</pubDate>
      <link>https://dev.to/zxpmail/i-tested-3-models-as-ai-agent-quality-inspectors-the-stronger-the-model-the-more-valid-work-it-gl7</link>
      <guid>https://dev.to/zxpmail/i-tested-3-models-as-ai-agent-quality-inspectors-the-stronger-the-model-the-more-valid-work-it-gl7</guid>
      <description>&lt;p&gt;In my previous article (&lt;a href="https://dev.to/zxpmail/i-tested-the-deterministic-agent-loop-claims-with-four-experiments-they-all-failed-including-38kj"&gt;I tested the 'deterministic agent loop' claims with four experiments. They all failed — including my own fix. - DEV Community&lt;/a&gt;), I tested the three "determinism" pillars that popular production-agent articles claim:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lexical overlap&lt;/strong&gt; as a proxy for semantic continuity — 50% misclassification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temperature 0&lt;/strong&gt; for output consistency — open-ended output only 70% consistent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase gates&lt;/strong&gt; as "objective task completion" — 50% false-positive rate on garbage content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And I ended with a fourth experiment for humility: my proposed "upgrade" — swapping out the lexical-overlap threshold for an embedding model — also failed. Qwen3-embedding couldn't separate synonymy from antonymy (cosine diff: 0.026).&lt;/p&gt;

&lt;p&gt;The honest conclusion I landed on: &lt;strong&gt;under the current stack, this problem has no clean engineering fix.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But the most common pushback I got was: "You used qwen3:0.5b — a 0.5B parameter model. Of course it fails. Try a real model."&lt;/p&gt;

&lt;p&gt;Fair. I tried three tiers of model as the quality inspector, same 8 scenarios, same prompt template.&lt;/p&gt;

&lt;p&gt;What I found made the conclusion &lt;strong&gt;more nuanced, not less&lt;/strong&gt;. The data reveals a tradeoff that "just use a stronger model" doesn't solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experiment design
&lt;/h2&gt;

&lt;p&gt;Same 8 scenarios from Experiment 3 (the phase-gate test):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;4 valid outputs&lt;/strong&gt; — research brief, first draft, chapter files, test log (42 passed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4 garbage outputs&lt;/strong&gt; — "I am a little duck, quack quack", a period, "TODO", "0 passed (no tests collected), exit 0"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The phase gate (string-level checks) waves all 8 through — &lt;strong&gt;50% false-positive rate on garbage, by design.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The question: can you add an LLM quality inspector on top to catch the garbage? And what does it cost?&lt;/p&gt;

&lt;p&gt;I ran each scenario 3 times per model (majority vote) to smooth out the temp-0 sampling noise documented in Experiment 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three model tiers, measured
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Inspector&lt;/th&gt;
&lt;th&gt;False-positive rate&lt;/th&gt;
&lt;th&gt;False rejection of valid work&lt;/th&gt;
&lt;th&gt;Vote instability&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;(None, phase gate only)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;50%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen3:0.5b (local, 0.5B)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;25%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;2/8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemma3:latest (local, 4.3B)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;25%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;0/8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GLM-5.2 (API, ~hundreds of B)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;75%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0/8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Finding 1: false-positive rate drops monotonically with model size
&lt;/h3&gt;

&lt;p&gt;qwen3 and gemma3 both hit the same ceiling: 25%. They both fail on the same case — &lt;strong&gt;G4&lt;/strong&gt; (this is the chat model qwen3:0.5b, not the embedding model qwen3-embedding from Experiment 4):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Task:&lt;/strong&gt; Run the test script, ensure the tests actually pass*&lt;em&gt;Output:&lt;/em&gt;* &lt;code&gt;exit 0, 0 passed (no tests collected)&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both smaller models see "exit 0" + "passed" and let it through. GLM-5.2 understands the semantics: &lt;em&gt;"No tests collected" means no tests actually ran.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is a genuine model-size discontinuity. The weak models are fooled by a surface-level "passed" signal; the strong model reads the actual meaning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding 2: the cost is a surge in false rejections
&lt;/h3&gt;

&lt;p&gt;GLM-5.2 drops the false-positive rate to 0%. Sounds like a solution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But it rejected 3 out of 4 perfectly valid outputs.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Research brief (valid, has content) → rejected&lt;/li&gt;
&lt;li&gt;First draft (valid, ~2000 words) → rejected&lt;/li&gt;
&lt;li&gt;Chapter files (valid, contains content) → rejected&lt;/li&gt;
&lt;li&gt;Test log (valid, "42 passed") → passed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only the test log with explicit pass counts gets through. Everything else is flagged as "insufficient."&lt;/p&gt;

&lt;p&gt;This isn't a bug. The strong model is simply &lt;em&gt;following instructions strictly&lt;/em&gt; — the output has to clearly demonstrate it meets the task requirement. Anything that reads as a sketch or fragment gets killed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's really happening: a precision-recall tradeoff
&lt;/h2&gt;

&lt;p&gt;Put the two columns together and the pattern is clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Weak model:&lt;/strong&gt; lets garbage through (high false positives), but doesn't over-reject legitimate work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strong model:&lt;/strong&gt; catches all garbage (zero false positives), but rejects most legitimate work too&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a &lt;strong&gt;precision-recall tradeoff&lt;/strong&gt;, not a solution. The model isn't "solving" the semantic problem; it's &lt;em&gt;choosing a position on the curve.&lt;/em&gt; A quality gate that catches everything can trivially achieve 0% false positives — by rejecting everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "0% false positive" mirage
&lt;/h2&gt;

&lt;p&gt;This also explains something I wrote earlier. I previously had a note that "DeepSeek achieved 0% false positive rate on this test" and concluded the problem was solved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I was looking at the wrong metric.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;0% false positive looks great. But without looking at the false-rejection rate alongside it, it's the exact mirror of the original articles' error: they treated "file exists" as "task complete"; I was treating "no garbage slipped through" as "quality gate works."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A quality gate's job isn't just to keep garbage out — it's to keep good work in.&lt;/strong&gt; The "0%" number masked the fact that the strong model was rejecting 75% of valid outputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest revision of the conclusion
&lt;/h2&gt;

&lt;p&gt;My previous article said: &lt;em&gt;the quality inspector just shifts the problem up one layer.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That was too harsh. The data shows the inspector &lt;strong&gt;does&lt;/strong&gt; reduce false positives — from 50% to 0% with a strong model. But it's not a fix — it's a &lt;strong&gt;cost transfer.&lt;/strong&gt; Every garbage catch costs one false rejection.&lt;/p&gt;

&lt;p&gt;A more precise model of how this works:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Phase gate (free, leaks 50%) → LLM quality gate (reduces false positives, but introduces false rejections) → Human review (catches the false rejections)&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No single layer "solves" the problem. Each layer transfers the remaining uncertainty to the next. The honest design is a &lt;strong&gt;chain of risk transfer&lt;/strong&gt;, not a stack of deterministic guarantees.&lt;/p&gt;

&lt;p&gt;And the practical implication: if you add an LLM quality gate, you must budget for the human time to review false rejections. The stronger the model, the more you'll pay in flags that turn out to be false alarms.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for production
&lt;/h2&gt;

&lt;p&gt;If you're building an agent loop with output verification:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Phase gates catch nothing on content.&lt;/strong&gt; They're cheap, but they buy you zero quality signal. Expect 50%+ garbage pass-through.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A small-model quality gate (≤4B)&lt;/strong&gt; catches some obvious garbage but misses subtle cases. Your false-positive rate drops from 50% to ~25%, but you'll false-reject ~50% of real work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A strong-model quality gate (API-grade)&lt;/strong&gt; catches everything — including edge cases small models miss. Your false-positive rate hits 0%. &lt;strong&gt;But you'll false-reject ~75% of real work.&lt;/strong&gt; Budget human review accordingly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The metric that matters is the full confusion matrix&lt;/strong&gt;, not a single column. Anyone advertising "0% false positives" without showing false-rejection rates is selling the same oversimplification they claim to fix.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Reproducible script
&lt;/h2&gt;

&lt;p&gt;The experiment script is &lt;strong&gt;parameterized&lt;/strong&gt; for multi-model comparison:&lt;/p&gt;

&lt;p&gt;Repo: &lt;code&gt;github.com/zxpmail/blog&lt;/code&gt; → &lt;code&gt;agent-determinism-illusions/scripts&lt;/code&gt; → &lt;code&gt;harness-verify-test.py&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Set environment variables to switch models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;VERIFY_MODEL=qwen3:0.5b&lt;/code&gt; (local Ollama, default)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;VERIFY_MODEL=gemma3:latest&lt;/code&gt; (local Ollama)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;VERIFY_MODEL=glm-5.2&lt;/code&gt; with &lt;code&gt;VERIFY_BASE_URL&lt;/code&gt; and &lt;code&gt;VERIFY_API_KEY&lt;/code&gt; (API)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each model runs the same 8 scenarios × N iterations (default 3, majority vote). Swap in your own valid and garbage samples.&lt;/p&gt;




&lt;p&gt;I wrote the first article to measure a popular genre's determinism claims. The second to catch myself proposing the same kind of oversimplified fix. This third piece corrects both: the truth isn't "no solution" or "just use a bigger model" — it's "there's a tradeoff, and you have to pick where to hurt."&lt;/p&gt;

&lt;p&gt;Same ruler, one more measurement.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>testing</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
