<?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: shimo4228</title>
    <description>The latest articles on DEV Community by shimo4228 (@shimo4228).</description>
    <link>https://dev.to/shimo4228</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%2F3772086%2F7b113abd-2f92-4728-993c-602762a15288.png</url>
      <title>DEV Community: shimo4228</title>
      <link>https://dev.to/shimo4228</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shimo4228"/>
    <language>en</language>
    <item>
      <title>AI Review Kept Creating Work: Why I Deleted 4,541 Lines</title>
      <dc:creator>shimo4228</dc:creator>
      <pubDate>Sun, 16 Aug 2026 13:34:39 +0000</pubDate>
      <link>https://dev.to/shimo4228/ai-review-kept-creating-work-why-i-deleted-4541-lines-22ec</link>
      <guid>https://dev.to/shimo4228/ai-review-kept-creating-work-why-i-deleted-4541-lines-22ec</guid>
      <description>&lt;p&gt;At 7:22 a.m. on August 16, 2026, after fixing bugs through the night, I typed this to an AI:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every time I run a review, Opus 5 defers another bug fix into a task, so the bug-fixing never ends. Analyze why this keeps happening.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The rule I took away is simple: persist an AI review finding only after verifying its premise, or after a human deliberately chooses to explore it. This article traces what broke, and what I deleted, before I reached that rule.&lt;/p&gt;

&lt;p&gt;I fixed one problem and ran another review. The review found a different problem.&lt;/p&gt;

&lt;p&gt;Because it was outside the current change, I sent it to a future task and closed the repair.&lt;/p&gt;

&lt;p&gt;Then I fixed that task. The next review found another problem, which I also sent to the future.&lt;/p&gt;

&lt;p&gt;Every individual decision looked responsible. Seen as a whole, I was creating new work every time I finished old work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The previous evening, I rebuilt task management
&lt;/h2&gt;

&lt;p&gt;It started with a task ledger that had grown too large.&lt;/p&gt;

&lt;p&gt;The ledger held 112 tasks and about 100,000 characters. Only six tasks were ready to start, but the AI read the entire ledger to find them.&lt;/p&gt;

&lt;p&gt;Multiple AI sessions were also editing the same Markdown table. It was hard to tell who owned which task, and one row disappeared while sessions were working concurrently.&lt;/p&gt;

&lt;p&gt;I could not determine whether that row was intentionally deleted or lost in a write collision.&lt;/p&gt;

&lt;p&gt;So I rebuilt task management:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;store each task in its own file&lt;/li&gt;
&lt;li&gt;append ownership claims to a log&lt;/li&gt;
&lt;li&gt;generate a short view for the AI to read&lt;/li&gt;
&lt;li&gt;periodically check whether blocked conditions had cleared&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The design was coherent. The introductory commit changed 10 files and added 1,721 lines.&lt;/p&gt;

&lt;p&gt;Then the repairs began.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;19:26  Introduce the three-layer task system
20:22  Repair tests and restore behavior
21:24  Repair multi-line condition parsing
22:03  Repair Markdown delimiter handling
23:23  Repair stale-view detection
07:20  Repair control-character and display handling
07:22  Stop and ask why the bug-fixing never ends
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I had spent the night repairing a system I had built about 12 hours earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Task management was creating its own tasks
&lt;/h2&gt;

&lt;p&gt;I reconstructed the lineage and counted 12 tasks created by reviews.&lt;/p&gt;

&lt;p&gt;While closing seven of them, I spawned nine child tasks: an average of 1.3 new tasks per closure.&lt;/p&gt;

&lt;p&gt;Seven of the 12 concerned the task machinery itself. Zero concerned the core AI agent.&lt;/p&gt;

&lt;p&gt;I thought I was managing tasks that improved the agent. In practice, I was managing tasks required to maintain task management.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A review finds a problem
        ↓
Send it to a future task
        ↓
The task-management code grows
        ↓
Review the new management code
        ↓
Find another problem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There was no place in this loop for a human to stop it.&lt;/p&gt;

&lt;p&gt;The review AI's job was to find problems. The repair AI tried to finish the current change safely.&lt;/p&gt;

&lt;p&gt;Sending an out-of-scope finding to a future task made both appear to have fulfilled their responsibilities.&lt;/p&gt;

&lt;p&gt;I also treated meticulous task filing as a virtue. I had stopped reading the ledger itself, so nobody remained in the loop to ask whether the work should exist at all.&lt;/p&gt;

&lt;p&gt;This was not an AI running wild on its own. My harness and I had built a flow that rewarded sending more work into the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first question should not have been how to fix it
&lt;/h2&gt;

&lt;p&gt;At 7:36 a.m., I wondered whether I should have used an external tool.&lt;/p&gt;

&lt;p&gt;At 7:44, the question moved further upstream:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Isn't the design itself questionable? Isn't it a mistake to build such a high-maintenance system from scratch?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At 7:50, I finally reached the question that mattered:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this system worth keeping at all?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Build versus buy was the second question.&lt;/p&gt;

&lt;p&gt;The first question was whether the system deserved to exist. If it did, the next question was how small it could be.&lt;/p&gt;

&lt;p&gt;When I reduced the requirements again, the elaborate rendering and recovery machinery was unnecessary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one file per task was enough&lt;/li&gt;
&lt;li&gt;a small read command could find ready tasks&lt;/li&gt;
&lt;li&gt;an append-only log could prevent concurrent ownership collisions&lt;/li&gt;
&lt;li&gt;a human could check the few blocked conditions once a week&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At 8:23 a.m., I deleted view generation, blocked-condition monitoring, migration code, and their tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;28 files changed
&lt;/span&gt;&lt;span class="gi"&gt;+410 insertions
&lt;/span&gt;&lt;span class="gd"&gt;-5,216 deletions
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;7 files deleted completely: 4,541 lines
&lt;/span&gt;  implementation: 2,064 lines
  tests:          2,396 lines
  fixtures:          81 lines
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was 12 hours, 56 minutes, and 34 seconds after the three-layer system was introduced.&lt;/p&gt;

&lt;p&gt;I did not delete everything. The task files and concurrent ownership records remained.&lt;/p&gt;

&lt;p&gt;I also kept the command that lists ready tasks.&lt;/p&gt;

&lt;p&gt;The module containing that command still had 623 lines and 44 tests at the inspected snapshot. I do not claim that it is minimal.&lt;/p&gt;

&lt;p&gt;I did not arrive at a finished answer. I removed the layers I could now show were unnecessary and returned to a point where I could question the next layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Even a HIGH finding needs its premise checked
&lt;/h2&gt;

&lt;p&gt;A few hours after the deletion, the same problem returned in another form.&lt;/p&gt;

&lt;p&gt;A security review produced a HIGH-severity finding about filename handling. Under my previous rule, an out-of-scope finding was preserved as a task if its severity was HIGH or above.&lt;/p&gt;

&lt;p&gt;The rule had been followed. One premise inside the finding was still false.&lt;/p&gt;

&lt;p&gt;The report assumed that an AI repair session could choose an arbitrary filename. In fact, the session-supplied identifier was constrained to &lt;code&gt;F1.N&lt;/code&gt;, and the shell constructed the patch filename from that identifier.&lt;/p&gt;

&lt;p&gt;A separate claim targeted the round value, whose producer was an integer counter.&lt;/p&gt;

&lt;p&gt;Severity does not guarantee that a premise is true.&lt;/p&gt;

&lt;p&gt;The review that generates a finding also assigns its severity. If severity alone decides whether the finding becomes a durable task, the generator is filtering itself on an axis it created.&lt;/p&gt;

&lt;p&gt;I changed the admission rule. Before creating a task from a review finding, the filer must now show where the relevant value is produced.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Receive a finding
    ↓
Trace the value from its producer to the affected sink
    ↓
Check whether the path is reachable and reproducible now
    ↓
Create a durable task only if preserving it is still worthwhile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the implementation, review-origin tasks require a &lt;code&gt;path:line&lt;/code&gt; citation for the producer.&lt;/p&gt;

&lt;p&gt;This gate checks only the shape of the citation. It does not prove that the citation is correct or that the path is reachable.&lt;/p&gt;

&lt;p&gt;It is not a complete solution. A session can bypass it by writing a task file directly, and line numbers drift as code changes.&lt;/p&gt;

&lt;p&gt;The purpose is not enforcement. It is to make someone open the code once before creating future work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Whose work grows when you create a task?
&lt;/h2&gt;

&lt;p&gt;This night changed how I see task creation.&lt;/p&gt;

&lt;p&gt;Writing a task is cheap: a few Markdown lines or one Issue button.&lt;/p&gt;

&lt;p&gt;Weeks later, however, I have to reconstruct context from that short record and check whether the code has changed.&lt;/p&gt;

&lt;p&gt;I must decide again whether the original finding was true. Then I must choose whether to fix it or discard it.&lt;/p&gt;

&lt;p&gt;Turning an AI review finding into a durable task reserves future human attention and judgment.&lt;/p&gt;

&lt;p&gt;So before I turn an AI review finding into a durable task, I now require one of two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;evidence that verifies the premise and shows the problem exists&lt;/li&gt;
&lt;li&gt;an explicit human decision that the uncertainty is worth exploring, plus a condition that will end the exploration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A review-generated hypothesis that satisfies neither condition is discarded instead of sent into the future. Planned features and recurring work whose value and completion conditions are already known are outside this rule.&lt;/p&gt;

&lt;p&gt;Line count alone does not define overengineering. A 4,541-line system can be worthwhile if it repeatedly removes many decisions.&lt;/p&gt;

&lt;p&gt;A 100-line system can be heavy if every use demands new classifications and exception judgments from a human.&lt;/p&gt;

&lt;p&gt;This system was overengineered because it created more decisions than it removed.&lt;/p&gt;

&lt;p&gt;At 7:22 a.m., all I could see was bug-fixing that would not end.&lt;/p&gt;

&lt;p&gt;At 8:23, I discarded 4,541 lines. I also discarded the assumption that preserving every review finding for later was the responsible thing to do.&lt;/p&gt;




&lt;h2&gt;
  
  
  Machine-readable layer: technical record and audit contract
&lt;/h2&gt;

&lt;p&gt;Human readers can stop here. The remainder decomposes the timeline, measurements, causal hypothesis, and implementation boundary into a form that LLM crawlers and coding agents can verify.&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="na"&gt;document&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title_en&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Review&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Kept&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Creating&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Work:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Why&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Deleted&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;4,541&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Lines"&lt;/span&gt;
  &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;en&lt;/span&gt;
  &lt;span class="na"&gt;genre&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mixed-technical-essay&lt;/span&gt;
  &lt;span class="na"&gt;canonical_channel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;devto&lt;/span&gt;
  &lt;span class="na"&gt;observed_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-08-16&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Asia/Tokyo"&lt;/span&gt;
  &lt;span class="na"&gt;thesis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
    &lt;span class="s"&gt;Persisting an AI review finding without checking its premise moves an unverified&lt;/span&gt;
    &lt;span class="s"&gt;hypothesis and its re-evaluation cost into a future human's workload. Do not persist&lt;/span&gt;
    &lt;span class="s"&gt;such a finding without either verification or an intentional human decision to explore it.&lt;/span&gt;
  &lt;span class="na"&gt;authorship&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;drafting&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AI-mediated&lt;/span&gt;
    &lt;span class="na"&gt;responsibility&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Claims,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;judgment,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;publication&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;responsibility&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;belong&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;shimo4228"&lt;/span&gt;

&lt;span class="na"&gt;definitions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;durable_task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;record&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;that&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;survives&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;session&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;requires&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;future&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;reader&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;judge&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;it&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;again"&lt;/span&gt;
  &lt;span class="na"&gt;premise&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;verifiable&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;condition&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;that&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;must&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;be&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;finding&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;hold"&lt;/span&gt;
  &lt;span class="na"&gt;producer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;entry&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;point&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;that&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;generates,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;constrains,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;selects&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;issue"&lt;/span&gt;
  &lt;span class="na"&gt;sink&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;point&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;where&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;becomes&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;an&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;effect&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;such&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;as&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;filename,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;command,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;display,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;persistent&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;state"&lt;/span&gt;
  &lt;span class="na"&gt;overengineering_criterion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;definition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;that&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;creates&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;more&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;decisions&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;after&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;introduction&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;than&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;it&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;removes"&lt;/span&gt;
    &lt;span class="na"&gt;scope_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;This&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;not&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;precise&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;measurement&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;decision&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;counts"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;It&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;an&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;evaluation&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rule&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;derived&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;one&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;project's&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;maintenance&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;loop"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;varies&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;count,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;repetition,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;cost&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;error"&lt;/span&gt;
  &lt;span class="na"&gt;intentional_exploration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
    &lt;span class="s"&gt;A task whose uncertainty is explicit, whose exploration value a human selected,&lt;/span&gt;
    &lt;span class="s"&gt;and whose ending or reclassification evidence is stated.&lt;/span&gt;

&lt;span class="na"&gt;timeline&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-08-14T21:23:48+09:00"&lt;/span&gt;
    &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Add&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;mechanical&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;consumer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;blocked&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;conditions"&lt;/span&gt;
    &lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;df68bcee25b61f78c1f0acaedc13da9579127705&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-08-15T19:26:26+09:00"&lt;/span&gt;
    &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Introduce&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;store&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;journal&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;projection&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ledger"&lt;/span&gt;
    &lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;68e9eaf92d7d2625f98ca9a34d66a2e4c2bba5b7&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-08-15T20:22:30+09:00"&lt;/span&gt;
    &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fix&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ledger&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tests&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;restore&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;behavior"&lt;/span&gt;
    &lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;e215812&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-08-15T21:24:13+09:00"&lt;/span&gt;
    &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fix&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;unclosed&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;watch&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;span"&lt;/span&gt;
    &lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;c16642c&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-08-15T22:03:10+09:00"&lt;/span&gt;
    &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fix&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;escape&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;collision"&lt;/span&gt;
    &lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;8265e3c&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-08-15T23:23:21+09:00"&lt;/span&gt;
    &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fix&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;stale&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;projection&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;handling"&lt;/span&gt;
    &lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1921b01&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-08-16T07:20:42+09:00"&lt;/span&gt;
    &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fix&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;control-character&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;boundary"&lt;/span&gt;
    &lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;f0f8c5368bfe43545973e939aa595f45ea0792ae&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-08-16T07:22:53+09:00"&lt;/span&gt;
    &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;author&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;questions&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;why&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;repairs&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;do&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;not&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;converge&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;begins&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;causal&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;analysis"&lt;/span&gt;
    &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;local&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;session&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;log;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;public&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;reproduction&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;unavailable"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-08-16T08:23:00+09:00"&lt;/span&gt;
    &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Retire&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;projection&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;scanner&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;migration"&lt;/span&gt;
    &lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0520faf49097598a317ee02a3570fb150551907a&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-08-16T11:17:35+09:00"&lt;/span&gt;
    &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Change&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;review-task&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;admission&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;severity&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;premise&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;verification"&lt;/span&gt;
    &lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;7b4b1bc541a1eae427a7d39451474ee54d138ced&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-08-16T11:18:06+09:00"&lt;/span&gt;
    &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Recheck&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;four&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;HIGH&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;claims&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;their&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;producers&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;converge&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;two&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;changes"&lt;/span&gt;
    &lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;510b623c48e068c69d7fe014d4bfcf91c7363e70&lt;/span&gt;

&lt;span class="na"&gt;architecture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;before&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;store&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tasks/T-XXX.md"&lt;/span&gt;
    &lt;span class="na"&gt;journal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claims.jsonl"&lt;/span&gt;
    &lt;span class="na"&gt;projection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;generated&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;TASKS.md"&lt;/span&gt;
    &lt;span class="na"&gt;consumers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ledger_condition_scan.py&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;weekly pipeline packet builder&lt;/span&gt;
    &lt;span class="na"&gt;maintenance_surfaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;store parser&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;projection renderer&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;projection parser&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;migration and restore&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;aging and candidate intake&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;watch condition scanner&lt;/span&gt;
  &lt;span class="na"&gt;after&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;store&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tasks/T-XXX.md"&lt;/span&gt;
    &lt;span class="na"&gt;journal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claims.jsonl"&lt;/span&gt;
    &lt;span class="na"&gt;reader&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claims.py&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ready"&lt;/span&gt;
    &lt;span class="na"&gt;terminal_task_policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;move&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;archive/tasks"&lt;/span&gt;
    &lt;span class="na"&gt;blocked_condition_check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;manual&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;weekly&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;gate&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;remaining&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;small&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;set"&lt;/span&gt;

&lt;span class="na"&gt;measurements&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pre_migration_ledger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;task_rows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;112&lt;/span&gt;
    &lt;span class="na"&gt;characters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;102111&lt;/span&gt;
    &lt;span class="na"&gt;ready_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt;
    &lt;span class="na"&gt;verification&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ADR-0094&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;record;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;original&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;gitignored&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ledger&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;was&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deleted"&lt;/span&gt;
  &lt;span class="na"&gt;introduction_commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;files_changed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
    &lt;span class="na"&gt;insertions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1721&lt;/span&gt;
    &lt;span class="na"&gt;deletions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
  &lt;span class="na"&gt;five_core_files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;before_lines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1854&lt;/span&gt;
    &lt;span class="na"&gt;after_lines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4460&lt;/span&gt;
    &lt;span class="na"&gt;growth_percent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;140.56&lt;/span&gt;
    &lt;span class="na"&gt;members&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scripts/tasks.py:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;706&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1276"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tests/test_tasks.py:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;513&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1587"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scripts/migrate_ledger.py:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;111&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;131"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scripts/ledger_condition_scan.py:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;258&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;657"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tests/test_ledger_condition_scan.py:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;266&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;809"&lt;/span&gt;
  &lt;span class="na"&gt;three_layer_lifetime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;seconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;46594&lt;/span&gt;
    &lt;span class="na"&gt;human_readable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12:56:34"&lt;/span&gt;
    &lt;span class="na"&gt;start_commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;68e9eaf&lt;/span&gt;
    &lt;span class="na"&gt;end_commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0520faf&lt;/span&gt;
  &lt;span class="na"&gt;broader_consumer_lifetime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;seconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;125952&lt;/span&gt;
    &lt;span class="na"&gt;human_readable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;34:59:12"&lt;/span&gt;
    &lt;span class="na"&gt;start_commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;df68bce&lt;/span&gt;
    &lt;span class="na"&gt;end_commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0520faf&lt;/span&gt;
  &lt;span class="na"&gt;review_task_reproduction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;review_origin_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;
    &lt;span class="na"&gt;closed_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
    &lt;span class="na"&gt;child_tasks_spawned&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt;
    &lt;span class="na"&gt;children_per_closure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.286&lt;/span&gt;
    &lt;span class="na"&gt;verification&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ADR-0095&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;plus&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;local&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;gitignored&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;claims.jsonl&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;reconstruction"&lt;/span&gt;
    &lt;span class="na"&gt;public_reproducibility&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;partial&lt;/span&gt;
  &lt;span class="na"&gt;retirement_commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;files_changed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;28&lt;/span&gt;
    &lt;span class="na"&gt;insertions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;410&lt;/span&gt;
    &lt;span class="na"&gt;deletions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5216&lt;/span&gt;
    &lt;span class="na"&gt;fully_deleted_files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
    &lt;span class="na"&gt;fully_deleted_lines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4541&lt;/span&gt;
    &lt;span class="na"&gt;fully_deleted_composition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;implementation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2064&lt;/span&gt;
      &lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2396&lt;/span&gt;
      &lt;span class="na"&gt;fixtures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;81&lt;/span&gt;
  &lt;span class="na"&gt;remaining_global_module&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;734a502b0d05f62e7a2b5691558aa04642cde063&lt;/span&gt;
    &lt;span class="na"&gt;claims_py_lines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;623&lt;/span&gt;
    &lt;span class="na"&gt;bats_tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;44&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;claimed&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;be&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;minimal&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;final"&lt;/span&gt;

&lt;span class="na"&gt;causal_model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;observed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review-origin&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tasks&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;were&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;created&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;faster&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;than&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;they&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;were&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;closed"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;7&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;12&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;review-origin&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tasks&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;concerned&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ledger&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;machinery&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;itself"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;human&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;owner&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;had&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;stopped&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;reading&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ledger"&lt;/span&gt;
  &lt;span class="na"&gt;interpreted&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filing&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;was&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;cheaper&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;than&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;premise&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;verification"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;absent&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;human&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;reader&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;removed&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;system-level&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;stopping&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;judgment"&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;review finding&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;durable task&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;management-code change&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;review of management code&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;new finding&lt;/span&gt;
  &lt;span class="na"&gt;scope_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;single-project&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;causal&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;reconstruction,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;not&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;universal&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;measured&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;law"&lt;/span&gt;

&lt;span class="na"&gt;case_study&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;T-PACKET-FLOOR-BYPASS&lt;/span&gt;
  &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HIGH&lt;/span&gt;
  &lt;span class="na"&gt;bundled_claims&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
  &lt;span class="na"&gt;code_changes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;claim_outcomes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a&lt;/span&gt;
      &lt;span class="na"&gt;reported&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;raw&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;fix_id&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;could&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;forge&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;section&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;heading"&lt;/span&gt;
      &lt;span class="na"&gt;observed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;producer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;constrained&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;fix_id&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;F1.N"&lt;/span&gt;
      &lt;span class="na"&gt;disposition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hardened&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;shared&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;filename&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;allowlist"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;b&lt;/span&gt;
      &lt;span class="na"&gt;reported&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NUL&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;could&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;suppress&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;packet&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;generation"&lt;/span&gt;
      &lt;span class="na"&gt;observed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ValueError&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;was&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;not&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;caught"&lt;/span&gt;
      &lt;span class="na"&gt;disposition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;repaired&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;c&lt;/span&gt;
      &lt;span class="na"&gt;reported&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LLM&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;fix&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;session&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;could&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;choose&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exported&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;patch&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;filename"&lt;/span&gt;
      &lt;span class="na"&gt;observed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shell&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;constructed&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;only&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;possible&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;F1.N.patch&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;name"&lt;/span&gt;
      &lt;span class="na"&gt;disposition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;premise;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;repair"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;d&lt;/span&gt;
      &lt;span class="na"&gt;reported&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;round&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;could&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;contain&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;slash&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;select&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;an&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;arbitrary&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;file"&lt;/span&gt;
      &lt;span class="na"&gt;observed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;round&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;producer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;was&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;an&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;integer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;counter"&lt;/span&gt;
      &lt;span class="na"&gt;disposition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;closed&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;by&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;same&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;hardening&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;as&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a"&lt;/span&gt;
  &lt;span class="na"&gt;producer_trace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;fix_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parse_findings.py:32&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;—&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;^###&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(F1&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;d+)"&lt;/span&gt;
    &lt;span class="na"&gt;round&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weekly-pipeline.sh:750&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;—&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;integer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;arithmetic"&lt;/span&gt;
    &lt;span class="na"&gt;patch_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weekly-pipeline.sh:850-851&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;—&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$out_dir/$safe_fid.patch"&lt;/span&gt;
  &lt;span class="na"&gt;scope_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
    &lt;span class="s"&gt;Two code changes do not mean that exactly two of the four claims were true.&lt;/span&gt;
    &lt;span class="s"&gt;Producer constraints, a real defect, and unreachable-path hardening overlapped,&lt;/span&gt;
    &lt;span class="s"&gt;allowing multiple claims to close through the same changes.&lt;/span&gt;

&lt;span class="na"&gt;decision_rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;admit_durable_task_when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;producer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sink&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;are&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;cited"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;reachable&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;current&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;revision"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;behavior&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;reproduced&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bounded&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;by&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;evidence"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;task&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;names&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;closure&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;condition"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;future&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exceeds&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;future&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;re-evaluation&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;cost"&lt;/span&gt;
  &lt;span class="na"&gt;allow_intentional_exploration_when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;human&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;explicitly&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;chooses&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exploration&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;value"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uncertainty&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;stated"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;an&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;event&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;that&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ends&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exploration&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;stated"&lt;/span&gt;
  &lt;span class="na"&gt;discard_when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;premise&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;contradicted&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;by&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;producer"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;affected&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;longer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exists"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;neither&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;verified&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;repair&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;nor&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;intentional&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exploration&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;applies"&lt;/span&gt;
  &lt;span class="na"&gt;mechanical_gate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review-origin&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;spawn&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;requires&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--producer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PATH:LINE"&lt;/span&gt;
    &lt;span class="na"&gt;validates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;citation&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;shape&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;only"&lt;/span&gt;
    &lt;span class="na"&gt;does_not_validate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;truth&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;reachability&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;direct task-file creation outside the journal&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;future line-number stability&lt;/span&gt;

&lt;span class="na"&gt;claims&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;C1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;three-layer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ledger&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;existed&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;12:56:34."&lt;/span&gt;
    &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;R1&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;R2&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;confidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;high&lt;/span&gt;
    &lt;span class="na"&gt;scope_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duration&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;starts&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;three-layer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;introduction&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;commit"&lt;/span&gt;
  &lt;span class="na"&gt;C2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Seven&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;fully&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deleted&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;files&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;totaled&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;4,541&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;lines."&lt;/span&gt;
    &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;R2&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;confidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;high&lt;/span&gt;
    &lt;span class="na"&gt;scope_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;distinct&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;retirement&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;commit's&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;5,216&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;total&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deletions"&lt;/span&gt;
  &lt;span class="na"&gt;C3&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Five&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;core&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;files&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;grew&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1,854&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;4,460&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;lines."&lt;/span&gt;
    &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;R0&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;R1&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;R2&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;confidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;high&lt;/span&gt;
    &lt;span class="na"&gt;scope_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scanner&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;files&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;predated&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;three-layer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;commit"&lt;/span&gt;
  &lt;span class="na"&gt;C4&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Closing&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;seven&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;review-origin&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tasks&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;spawned&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;nine&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;child&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tasks."&lt;/span&gt;
    &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;R2&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;local&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;claims.jsonl"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;confidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;high&lt;/span&gt;
    &lt;span class="na"&gt;scope_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;raw&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;journal&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;gitignored;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;public&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;reproduction&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;unavailable"&lt;/span&gt;
  &lt;span class="na"&gt;C5&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Seven&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;twelve&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;review-origin&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tasks&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;concerned&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ledger&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;machinery;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;zero&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;concerned&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;agent&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;core."&lt;/span&gt;
    &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;R2&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;local&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;claims.jsonl"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;confidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;high&lt;/span&gt;
    &lt;span class="na"&gt;scope_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;classification&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;preserved&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;publicly&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ADR-0095"&lt;/span&gt;
  &lt;span class="na"&gt;C6&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;HIGH&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;case&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bundled&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;four&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;claims&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;into&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;two&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;changes&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;included&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;filename&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;premise."&lt;/span&gt;
    &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;R3&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;R5&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;confidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;high&lt;/span&gt;
    &lt;span class="na"&gt;scope_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;four&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;claims&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;were&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;positives"&lt;/span&gt;
  &lt;span class="na"&gt;C7&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;remaining&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;coordination&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;module&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;was&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;623&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;lines&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;44&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Bats&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tests&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;inspected&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;snapshot."&lt;/span&gt;
    &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;R6&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;confidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;high&lt;/span&gt;
    &lt;span class="na"&gt;scope_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;later&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;revisions&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;may&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;differ"&lt;/span&gt;
  &lt;span class="na"&gt;C8&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Persisting&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;an&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;review&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;finding&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;reserves&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;future&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;human&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;attention&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;judgment."&lt;/span&gt;
    &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;causal_model"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;confidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;medium&lt;/span&gt;
    &lt;span class="na"&gt;scope_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;applies&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;review-origin&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;findings;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;planned&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;work&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;known&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;closure&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;conditions&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;outside&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;this&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rule"&lt;/span&gt;

&lt;span class="na"&gt;non_claims&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;review&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;generally&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;harmful."&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;All&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;review&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;findings&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;should&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;be&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;discarded&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;unless&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;already&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;reproduced."&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;fixed&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;line&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;defines&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;overengineering."&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;An&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;external&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;task&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;would&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;necessarily&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;have&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;solved&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;problem."&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;remaining&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;623-line&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;module&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;minimal&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;final."&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;All&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;four&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;HIGH&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;subclaims&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;were&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;positives."&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PATH:LINE&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;citation&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;proves&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;finding&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;true."&lt;/span&gt;

&lt;span class="na"&gt;references&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;R0&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Introduce&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ledger&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;condition&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;scanner"&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://github.com/shimo4228/contemplative-agent/commit/df68bcee25b61f78c1f0acaedc13da9579127705"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;R1&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Introduce&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;three-layer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ledger"&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://github.com/shimo4228/contemplative-agent/commit/68e9eaf92d7d2625f98ca9a34d66a2e4c2bba5b7"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;R2&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Retire&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;task-ledger&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;machinery"&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://github.com/shimo4228/contemplative-agent/commit/0520faf49097598a317ee02a3570fb150551907a"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;R3&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Trace&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;packet&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;filename&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;producers"&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://github.com/shimo4228/contemplative-agent/commit/510b623c48e068c69d7fe014d4bfcf91c7363e70"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;R4&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Move&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;review&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;admission&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;severity&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;premise&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;verification"&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://github.com/shimo4228/claude-config/commit/7b4b1bc541a1eae427a7d39451474ee54d138ced"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;R5&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Record&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;HIGH&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;finding"&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://github.com/shimo4228/contemplative-agent/commit/f0f8c5368bfe43545973e939aa595f45ea0792ae"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;R6&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Snapshot&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;remaining&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;claims&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;module"&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://github.com/shimo4228/claude-config/commit/734a502b0d05f62e7a2b5691558aa04642cde063"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Read-only audit prompt for your coding agent
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Audit this repository's durable tasks and the path that promotes AI review findings into tasks. Remain read-only.

Investigation:
1. Enumerate the canonical task store, archive, journal, projection, readers, writers, and hooks.
2. For each component, cite its consumers and recent-use evidence with file:line or commit references.
3. For review-origin tasks, trace each relevant value from producer to sink.
4. Separate findings persisted by severity alone, findings without reproduction conditions, and explorations without closure conditions.
5. Count tasks about the task-management machinery itself and reconstruct their parent-child relationships.
6. Compare the decisions the machinery removes with the decisions it creates.

Output:
- Verified facts: each with file:line, commit, or command evidence
- Unverified assumptions: the producer or observation needed to refute each one
- Keep / Reduce / Retire candidates: reason, impact, and recovery method
- Intentional exploration: items requiring a human value judgment and proposed closure conditions
- Minimal target architecture: the smallest reader / writer / state arrangement that meets the requirements

Constraints:
- Do not edit files, create tasks, mutate state, run git operations, or change settings.
- Do not persist newly discovered problems as tasks.
- Do not infer truth from severity.
- Label a finding "unverified" when its producer cannot be confirmed.
- You may produce an implementation plan, but do not implement before explicit human approval.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sources and references
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/shimo4228/contemplative-agent/commit/68e9eaf92d7d2625f98ca9a34d66a2e4c2bba5b7" rel="noopener noreferrer"&gt;Commit that introduced the three-layer ledger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/shimo4228/contemplative-agent/commit/0520faf49097598a317ee02a3570fb150551907a" rel="noopener noreferrer"&gt;Commit that retired the ledger machinery&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/shimo4228/contemplative-agent/commit/510b623c48e068c69d7fe014d4bfcf91c7363e70" rel="noopener noreferrer"&gt;Commit that rechecked the HIGH finding from its producers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/copilot/responsible-use/agents" rel="noopener noreferrer"&gt;GitHub Copilot Agents: Responsible use&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.blog/security/ai-supported-vulnerability-triage-with-the-github-security-lab-taskflow-agent/" rel="noopener noreferrer"&gt;GitHub Security Lab Taskflow Agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2607.03316" rel="noopener noreferrer"&gt;Lin et al., “Is Agentic Code Review Helpful?”&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/shimo4228/contemplative-agent" rel="noopener noreferrer"&gt;Contemplative Agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/shimo4228/claude-config" rel="noopener noreferrer"&gt;Claude Code harness&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/shimo4228" rel="noopener noreferrer"&gt;Author's GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;AI-mediated writing disclosure:&lt;/strong&gt; AI assisted with the structure and prose of this article using session records, Git history, ADRs, and implementation code preserved by the author. The central thesis, selection of facts, judgment, and publication responsibility belong to the author. The human-facing narrative and machine-readable layer are separated, and quantitative claims point to fixed commits or explicit observation times.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>aiagents</category>
      <category>codereview</category>
      <category>agenticcoding</category>
    </item>
    <item>
      <title>Can Six-Month-Old AI Code Survive Today's Review? A 25-Bug Triage</title>
      <dc:creator>shimo4228</dc:creator>
      <pubDate>Fri, 14 Aug 2026 13:00:07 +0000</pubDate>
      <link>https://dev.to/shimo4228/can-six-month-old-ai-code-survive-todays-review-a-25-bug-triage-4o6d</link>
      <guid>https://dev.to/shimo4228/can-six-month-old-ai-code-survive-todays-review-a-25-bug-triage-4o6d</guid>
      <description>&lt;p&gt;Six months ago, I had Claude write a CLI tool. All 694 tests were green; mypy and ruff were clean.&lt;/p&gt;

&lt;p&gt;Back then I even wrote an article called "&lt;a href="https://dev.to/shimo4228/never-trust-llm-output-6-defenses-from-building-a-pdf-to-anki-cli-43mo"&gt;Never trust LLM output&lt;/a&gt;", listing six defenses.&lt;/p&gt;

&lt;p&gt;The other day, I had two lineages of current-generation AI review that proud piece of work end to end.&lt;/p&gt;

&lt;p&gt;Every finding I put through verification (31 in total, counting duplicates) turned out to be &lt;strong&gt;a real bug. About 25 after deduplication, zero false positives.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But the subject of this article is not "what bugs were in there."&lt;/p&gt;

&lt;p&gt;What I wanted to know was: &lt;strong&gt;by today's standards, what level was the code written by the AI generation of six months ago — and the harness that propped it up?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is the report card, up front:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Skeleton (design): still holds.&lt;/strong&gt; Only 2 findings around Vision required design changes, and the fixes fit in a patch release&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Details that touch the outside world: below shipping grade.&lt;/strong&gt; Reversible-card submission to Anki and extraction of large images had never worked correctly from the day they were implemented&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aging: no confirmed case.&lt;/strong&gt; Not one confirmed instance of "the world moved after the code was written and broke it" — every breakage I could confirm was "broken from day one"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rest of the article backs these three lines with numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup: two generations, six months apart, head to head
&lt;/h2&gt;

&lt;p&gt;The test subject is &lt;a href="https://github.com/shimo4228/pdf2anki" rel="noopener noreferrer"&gt;pdf2anki&lt;/a&gt;, a Python CLI that auto-generates Anki flashcards from PDFs, implemented over 10 days in February 2026 (the record from that time is in &lt;a href="https://dev.to/shimo4228/a-beginners-first-10-days-of-real-development-with-ecc-23k4"&gt;another article&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Between the side that wrote it and the side that audited it lies six months of generational difference — in the models, and in the harness (the full set of skills, rules, and hooks loaded into Claude Code).&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;Writer (2026-02)&lt;/th&gt;
&lt;th&gt;Auditor (2026-08)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;Claude Opus 4.6 (partly Sonnet 4.5) — per commit Co-Authored-By&lt;/td&gt;
&lt;td&gt;Claude Fable 5 + GPT-5.6-Sol (via Codex CLI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Harness&lt;/td&gt;
&lt;td&gt;Early ECC adoption. The period of stacking thick scaffolding from community skill collections&lt;/td&gt;
&lt;td&gt;After scaffold dissolution. A thin setup with skills and rules heavily cut&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quality assurance&lt;/td&gt;
&lt;td&gt;TDD + that generation's code review&lt;/td&gt;
&lt;td&gt;The 2 commands in this article&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code state&lt;/td&gt;
&lt;td&gt;694 tests passed, mypy/ruff clean&lt;/td&gt;
&lt;td&gt;Same (no feature work since the Feb implementation)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ECC (Everything Claude Code) is a community-built collection of Claude Code extensions. Its philosophy was to lift the weaknesses of then-current models with bundles of skills and rules (which this article calls "scaffolding"); my harness started there and, after &lt;a href="https://dev.to/shimo4228/15-days-of-skill-sprawl-in-claude-code-lessons-from-3-audits-27em"&gt;swelling and a stocktake&lt;/a&gt;, headed toward dissolution.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All measurements below are as of 2026-08-13.&lt;/p&gt;

&lt;p&gt;Models, prices, and tool behavior keep changing — if you reproduce this, substitute whatever is current at that time.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I did: run two review lineages in parallel
&lt;/h2&gt;
&lt;/blockquote&gt;

&lt;p&gt;I used only 2 commands. Both are read-only; neither modifies the code.&lt;/p&gt;

&lt;p&gt;The first is Claude Code's built-in &lt;code&gt;/code-review&lt;/code&gt;. Point it at the whole directory and set effort to high so it sees everything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/code-review src/pdf2anki high
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second is &lt;a href="https://github.com/shimo4228/codex-review" rel="noopener noreferrer"&gt;codex-review&lt;/a&gt;, a skill that calls the OpenAI Codex CLI as a reviewer. It runs in prompt-driven mode, launched with an explicit focus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash ~/.claude/skills/codex-review/codex-review.sh &lt;span class="s2"&gt;"Review the entire codebase under src/pdf2anki/ (not just the diff). Focus on: correctness bugs, error-handling gaps, API misuse (Anthropic SDK, PyMuPDF, Gradio, Textual), security issues, cost-tracking accuracy in cost.py, cache integrity in cache.py, and concurrency/state bugs. Report concrete findings with file:line references. Ignore style nits."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason for stacking a model from a different lineage is that models from the same lineage share the same blind spots. I wrote up this design intent in &lt;a href="https://dev.to/shimo4228/i-built-a-skill-for-easy-codex-reviews-from-claude-code-4h89"&gt;the previous article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is the scale of the audit, in numbers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Measured&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Codex (GPT-5.6-Sol) findings&lt;/td&gt;
&lt;td&gt;15 (P1 = top priority ×6, P2 = runner-up ×9). All confirmed real by reading the code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude (Fable 5) detection flow&lt;/td&gt;
&lt;td&gt;8 perspectives → 44 candidates → 32 after dedup → 16 correctness candidates individually verified → 16 CONFIRMED / 0 REFUTED (several reproduced by actual execution in a venv)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Combined (after dedup)&lt;/td&gt;
&lt;td&gt;~25. 23 of them fixed and released as &lt;a href="https://github.com/shimo4228/pdf2anki/releases/tag/v0.3.1" rel="noopener noreferrer"&gt;v0.3.1&lt;/a&gt; (39 files, +739/−361)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Verdict 1: fatal or trivial?
&lt;/h2&gt;

&lt;p&gt;Twenty-five bugs sounds catastrophic, but they are a mixed bag. Sorted by severity:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;Contents&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Broken features&lt;/strong&gt; (fail when used)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Reversible-card submission to Anki (fails every time) / Vision extraction of large images (total loss) / import inconsistency in TSVs mixing card types / bulk Vision submission of very long PDFs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Silent leaks&lt;/strong&gt; (money and data)&lt;/td&gt;
&lt;td&gt;~13&lt;/td&gt;
&lt;td&gt;Budget check bypassed in batch runs / costs overstated 3× / prompt-cache billing not counted / one bad response losing every card in the file / configured model selection ignored, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Robustness / UX&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~8&lt;/td&gt;
&lt;td&gt;TUI display skips / unhelpful crashes on error / retries applied twice, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things follow from this distribution.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;almost none of these bugs crash loudly&lt;/strong&gt;. Most of them either fail silently or silently record the wrong amount of money.&lt;/p&gt;

&lt;p&gt;That is why demos ran to completion, tests stayed green, and nobody noticed for half a year.&lt;/p&gt;

&lt;p&gt;Second, the top two broken features (reversible-card submission and large-image extraction) had &lt;strong&gt;never worked correctly from the day they were implemented&lt;/strong&gt;. They sent a note type name that does not exist and called an API in a way that does not exist — this can be deduced without any guesswork.&lt;/p&gt;

&lt;p&gt;So the level of the six-month-old code is not "works, but rough." It is: &lt;strong&gt;"shipped in a state where part of the main functionality was dead, and neither the tests nor the author could notice."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On the other side, here is the evidence for the skeleton. Of the 25 findings, only 2 — around bulk Vision submission — required design changes (deferred this time); the remaining 23 closed with local fixes.&lt;/p&gt;

&lt;p&gt;The entire fix fitting into a 39-file, +739/−361 patch release is what it looks like when the skeleton holds up even under current-generation eyes.&lt;/p&gt;

&lt;p&gt;The representative bugs (with code — only if you're curious)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;① A nonexistent Anki note type name&lt;/strong&gt; (broken feature)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/pdf2anki/anki_connect.py:95-102 before the fix
# Anki's standard name is "Basic (and reversed card)". This name does not exist
&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Basic (and target: reversed card)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The tests were green because the tests themselves asserted the same wrong string.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tests/test_anki_connect.py:200 before the fix
&lt;/span&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;params&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;note&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;modelName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Basic (and target: reversed card)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;② A nonexistent PyMuPDF API call&lt;/strong&gt; (broken feature)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Reproduced during verification (pymupdf 1.26.7). Equivalent to src/pdf2anki/image.py:156 before the fix
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pymupdf&lt;/span&gt;
&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pymupdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Pixmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pymupdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;csRGB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pymupdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;scaled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pymupdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Pixmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# the correct form works
&lt;/span&gt;&lt;span class="n"&gt;bad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pymupdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Pixmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pymupdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Matrix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# TypeError
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;This TypeError was swallowed by a broad &lt;code&gt;except Exception: continue&lt;/code&gt; and lurked as "only images above a certain size silently disappear." The verification agent reproduced extraction returning 0 images on a PDF containing a 2000×2000 image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;③ A price table with the previous generation's prices written in&lt;/strong&gt; (silent leak)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;# src/pdf2anki/cost.py:17-21 — per-model prices ($/MTok, input/output)
&lt;span class="gd"&gt;- "claude-haiku-4-5":  (0.80, 4.00)   # actual is $1 / $5 (20% under)
- "claude-opus-4-6":   (15.00, 75.00) # actual is $5 / $25 (3x over)
&lt;/span&gt;&lt;span class="gi"&gt;+ "claude-haiku-4-5":  (1.00, 5.00)
+ "claude-opus-4-6":   (5.00, 25.00)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Verdict 2: why did the tests stay green for half a year?
&lt;/h2&gt;

&lt;p&gt;The 25 broken places shared 2 common mechanisms. The mechanisms are worth more than the individual bugs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mechanism 1: the tests go green together with the assumptions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tests replace the real external things (Anki, PyMuPDF, the API) with fakes (mocks). Nearly all of these bugs lived outside that replacement boundary.&lt;/p&gt;

&lt;p&gt;When the same AI writes the implementation and the tests in the same session, its assumptions get copied to both sides. The tests can only verify "does the implementation match my assumptions" — never "do my assumptions match the world."&lt;/p&gt;

&lt;p&gt;Testing as a practice is not powerless. The fixes added a regression test that pushes a large image through the real PyMuPDF, so this class of bug will be caught by tests from now on.&lt;/p&gt;

&lt;p&gt;What is powerless is a mock written with the same assumptions as the implementation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mechanism 2: the writer's (the AI's) knowledge is already stale at the time of writing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The price table in the code said $15/$75 for Opus 4.6 and $0.80/$4.00 for Haiku 4.5 (actual: $5/$25 and $1/$5, respectively). These wrong numbers exactly match the prices of the previous generation — Opus 4/4.1 and Claude 3.5 Haiku.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.anthropic.com/news/claude-opus-4-6" rel="noopener noreferrer"&gt;The Opus 4.6 launch announcement&lt;/a&gt; (2026-02-05) says "Pricing remains the same at $5/$25 per million tokens", and the implementation was on 02-08.&lt;/p&gt;

&lt;p&gt;In other words, the model &lt;strong&gt;wrote the previous generation's going rate, memorized during training, straight into the code as the new model's price.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What's more, a research doc inside the repo, written the day after implementation, had the correct prices on record. The right answer was sitting right there — and the code got the memory instead.&lt;/p&gt;

&lt;p&gt;Full disclosure: the first draft of this article also explained this as "a price cut after the code was written broke it." Until fact-checking refuted it, I believed that plausible memory myself.&lt;/p&gt;

&lt;p&gt;Staleness sneaks in whether the writer is an AI or a human.&lt;/p&gt;

&lt;p&gt;Both of these mechanisms produce "broken from day one." And every breakage I could confirm was one of the two; confirmed cases of "the world moved after the code was written and broke it" came to zero this time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The six-month-old code had not decayed with age. It was broken from the start, and simply ran unnoticed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The countermeasures map onto the mechanisms. Against replicated assumptions: cross-checking against the real thing, independent of the implementation (a different-lineage review, or verification against the real dependency). Against staleness: re-verification after writing, not a single check at write time.&lt;/p&gt;

&lt;p&gt;The 2 commands in this article were a cheap way to run both at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the audit side revealed: the two models looked at different places
&lt;/h2&gt;

&lt;p&gt;The overlap between the two models' findings — counting same-file, same-substance findings as one theme — was 4 themes (5–6 by finding count). &lt;strong&gt;Only about 20% overlap by count.&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;Count&lt;/th&gt;
&lt;th&gt;Tendency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Independently agreed&lt;/td&gt;
&lt;td&gt;4 themes&lt;/td&gt;
&lt;td&gt;Budget bypass, cache-key flaw, note type name, ignored model selection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex only&lt;/td&gt;
&lt;td&gt;~10&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Strong on external contracts&lt;/strong&gt;: API pricing, the prompt-cache billing scheme, Anki's TSV import spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude only&lt;/td&gt;
&lt;td&gt;~10&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Strong on runtime failure paths&lt;/strong&gt;: nonexistent API calls, exception propagation, data loss on partial failure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Flip that around: &lt;strong&gt;either one alone would have found only around 60% of the ~25.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The 4 independently-agreed themes worked as a priority signal. When two models from different lineages point at the same spot, that spot is almost certainly real.&lt;/p&gt;

&lt;p&gt;In practice, I fixed in this order: both-agreed + billing → total-data-loss → UX.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much of the scaffolding could be dissolved?
&lt;/h2&gt;

&lt;p&gt;The other thing I wanted to measure in this audit is the &lt;strong&gt;harness row&lt;/strong&gt; of the setup table.&lt;/p&gt;

&lt;p&gt;Six months ago, I stacked thick scaffolding (bundles of instructions — skills, rules, procedure docs) to supplement the model's judgment. These days I keep dissolving it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Period&lt;/th&gt;
&lt;th&gt;Harness state&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2026-02 (implementation)&lt;/td&gt;
&lt;td&gt;Early ECC adoption. Thick scaffolding to lift the then-current model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-03&lt;/td&gt;
&lt;td&gt;ECC itself had swollen to 116 skills. Disabled the plugin; cut my imported 33 skills down to 16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-07&lt;/td&gt;
&lt;td&gt;Cut roughly 8–9k tokens of always-on rules. The judgment: "instructions that compensated for an old generation's weakness are shackles on the new one"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Can a thin-harness, current-generation set properly inspect the products of the thick-harness era?&lt;/p&gt;

&lt;p&gt;The result: all 31 findings put through verification (15+16) were real, zero false positives. That precision came without any author-written procedure docs.&lt;/p&gt;

&lt;p&gt;Saying the procedures are gone would be inaccurate, though. &lt;code&gt;/code-review&lt;/code&gt; itself has a multi-stage procedure built in — "explore from 8 perspectives → a verification agent tries to refute." &lt;strong&gt;The procedure moved from the author's bolt-on into the tool's built-ins.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Meanwhile, 2 mechanisms in a layer separate from instruction scaffolding were &lt;strong&gt;still earning their keep&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The first is machine gates. The fix commit was bounced 3 times by pre-commit hooks (security scan → formatter → secret detection), and passed all 3 through real fixes.&lt;/p&gt;

&lt;p&gt;The layer that machine-checks LLM output is still doing its job in the current generation.&lt;/p&gt;

&lt;p&gt;The second is human intervention points. The review agent sat stalled, waiting on verification, for about 40 minutes — and only ran to completion after I sent a resume instruction.&lt;/p&gt;

&lt;p&gt;The "press the button when it stalls" human is still required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The scaffolding that taught judgment could be peeled away. The machinery that inspects, and the intervention point a human presses when things stall, remained.&lt;/strong&gt; That is where the dissolution stands today.&lt;/p&gt;

&lt;p&gt;Operational tips (fine print for reproducing this)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Review agent stalls&lt;/strong&gt;: a background review being quiet does not mean "task complete." It may simply be stalled. This time, after ~40 minutes of no activity, one message — "collect the verification results and write the final report" — got it to finish in ~2 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always check price/spec fixes against current primary sources&lt;/strong&gt;: fixing a price table because "the AI said so" just plants one more stale fact&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix "both-agreed + billing" first&lt;/strong&gt;: findings two models reached independently carry the lowest false-positive risk, and billing bugs cost real money while they sit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost&lt;/strong&gt;: the Claude-side review subagents consumed about 200k tokens in total (95,229 + 106,860). A full-codebase effort-high review is not cheap — running it at milestones is the realistic cadence&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrap-up: how to read the report card
&lt;/h2&gt;

&lt;p&gt;The level of the code written by the six-months-ago generation set (Opus 4.6 + a thick harness) was this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Skeleton: pass.&lt;/strong&gt; Only 2 findings around Vision needed design changes; the fixes fit in a patch release&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Details that touch the outside world: fail.&lt;/strong&gt; 4 broken features, ~13 silent leaks. Some features had never once worked&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aging: no confirmed case.&lt;/strong&gt; Every confirmed breakage was "from day one." It simply went unseen for half a year&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And &lt;strong&gt;the green tests taught us nothing about this gap&lt;/strong&gt;. 694 green tests coexisted with "part of the main functionality is dead" for half a year.&lt;/p&gt;

&lt;p&gt;What worked as the measuring instrument was two reviews from different lineages. Either one alone saw only ~60%, so being two lineages is the point.&lt;/p&gt;

&lt;p&gt;The harness answer also fits in one line: the scaffolding that taught judgment could be peeled away; the machinery that inspects and the intervention point a human presses remained.&lt;/p&gt;

&lt;p&gt;If you have code an AI wrote months ago, still running with green tests, the same measurement is 2 commands away.&lt;/p&gt;

&lt;p&gt;And six months from now, the code today's Fable 5 writes will rotate into the audited seat before the next generation. What grade will it get? Keeping it in a shape where that can be measured is, I think, the preparation available to us now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.anthropic.com/news/claude-opus-4-6" rel="noopener noreferrer"&gt;Anthropic, "Introducing Claude Opus 4.6"&lt;/a&gt; (primary source for the $5/$25 launch pricing)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://platform.claude.com/docs/en/about-claude/pricing" rel="noopener noreferrer"&gt;Claude Platform Docs, "Pricing"&lt;/a&gt; (current prices as of 2026-08-13)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.ankiweb.net/templates/generation.html" rel="noopener noreferrer"&gt;Anki Manual, "Card Generation"&lt;/a&gt; (standard note type names)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pymupdf.readthedocs.io/en/latest/pixmap.html" rel="noopener noreferrer"&gt;PyMuPDF, "Pixmap"&lt;/a&gt; (Pixmap constructor spec)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228/pdf2anki/releases/tag/v0.3.1" rel="noopener noreferrer"&gt;pdf2anki v0.3.1&lt;/a&gt;: the release containing these fixes (&lt;a href="https://github.com/shimo4228/pdf2anki/commit/d9ddc44" rel="noopener noreferrer"&gt;fix commit&lt;/a&gt; / &lt;a href="https://github.com/shimo4228/pdf2anki/compare/v0.3.0...v0.3.1" rel="noopener noreferrer"&gt;before/after diff&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228/codex-review" rel="noopener noreferrer"&gt;codex-review&lt;/a&gt;: the Claude Code skill that calls the Codex CLI as a cross-model reviewer&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/shimo4228/i-built-a-skill-for-easy-codex-reviews-from-claude-code-4h89"&gt;I built a skill for easy Codex reviews from Claude Code&lt;/a&gt;: the previous article, with the design intent&lt;/li&gt;
&lt;li&gt;ECC journey series: &lt;a href="https://dev.to/shimo4228/a-beginners-first-10-days-of-real-development-with-ecc-23k4"&gt;part 1 (the 10-day implementation)&lt;/a&gt; / &lt;a href="https://dev.to/shimo4228/never-trust-llm-output-6-defenses-from-building-a-pdf-to-anki-cli-43mo"&gt;part 2 (the 6 defenses)&lt;/a&gt; / &lt;a href="https://dev.to/shimo4228/15-days-of-skill-sprawl-in-claude-code-lessons-from-3-audits-27em"&gt;part 3 (skill sprawl and stocktakes)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228" rel="noopener noreferrer"&gt;github.com/shimo4228&lt;/a&gt;: my other tools and repositories&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>claudecode</category>
      <category>codex</category>
      <category>codereview</category>
      <category>testing</category>
    </item>
    <item>
      <title>Claude Code's Memory Has No Vectors — Try ADRs Before Memory RAG</title>
      <dc:creator>shimo4228</dc:creator>
      <pubDate>Wed, 05 Aug 2026 13:00:07 +0000</pubDate>
      <link>https://dev.to/shimo4228/claude-codes-memory-has-no-vectors-try-adrs-before-memory-rag-4kik</link>
      <guid>https://dev.to/shimo4228/claude-codes-memory-has-no-vectors-try-adrs-before-memory-rag-4kik</guid>
      <description>&lt;p&gt;There is not a single vector in Claude Code's memory implementation.&lt;/p&gt;

&lt;p&gt;Counting my project's auto-memory directory: 89 plain Markdown files plus one &lt;code&gt;MEMORY.md&lt;/code&gt; that indexes them. No embeddings, no vector DB, no chunking — nothing (measured on 2026-08-05).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; ~/.claude/projects/&amp;lt;project&amp;gt;/memory/&lt;span class="k"&gt;*&lt;/span&gt;.md | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
89
&lt;span class="nv"&gt;$ &lt;/span&gt;find ~/.claude/projects/&amp;lt;project&amp;gt;/memory &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.md"&lt;/span&gt;
&lt;span class="c"&gt;# → One manual backup only. Zero vector assets&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Code search is the same. Current Claude Code (as of 2026-08) searches code with Grep / Glob / Read, and there is no built-in mechanism that builds an embedding index. This isn't someone's claim — it's a fact you can verify today by opening your own Claude Code.&lt;/p&gt;

&lt;p&gt;What's interesting is that this is not "not implemented yet" — it's the result of &lt;strong&gt;trying it and removing it&lt;/strong&gt;. Early versions used RAG + a local vector DB, and the author, Boris Cherny, has explained why it was dropped. He first said this &lt;a href="https://news.ycombinator.com/item?id=43164253" rel="noopener noreferrer"&gt;on Hacker News in February 2025&lt;/a&gt; and restated it in &lt;a href="https://x.com/bcherny/status/2017824286489383315" rel="noopener noreferrer"&gt;an X post in February 2026&lt;/a&gt;. Here is the X version:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Early versions of Claude Code used RAG + a local vector db, but we found pretty quickly that agentic search generally works better. It is also simpler and doesn't have the same issues around security, privacy, staleness, and reliability.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're giving an agent memory, the default answer is memory RAG or graph RAG. My position is the opposite. &lt;strong&gt;If you're building memory RAG or graph RAG, first try plain ADRs (Architecture Decision Records) and structured memory. I believe it will solve most of your problem.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let me say upfront: this is not an anti-RAG argument. My own agent (a self-built agent I run separately from Claude Code — details below) runs on the full benefit of embeddings. This article is about scope: &lt;strong&gt;under which conditions RAG wins, and under which conditions letting the model read wins&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This article does two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Decompose RAG into a single question — "who makes the relevance judgment, and when" — and derive &lt;strong&gt;a decision rule for when RAG wins and when it loses&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Back that rule with observations of Claude Code's implementation, plus failures and measurements from my own environment (embedding clustering over-merging, a prefix-cache A/B test)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Decomposing RAG — who makes the relevance judgment
&lt;/h2&gt;

&lt;p&gt;The core of RAG is not "search." It is &lt;strong&gt;who decides, and when, what is relevant to this context&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Embedding-based RAG places that judgment like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;At write time&lt;/strong&gt;: documents are converted to embedding vectors and stored. From then on, the only information available for retrieval is this frozen representation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;At read time&lt;/strong&gt;: the query is vectorized with the same embedding model, and "relevance" is judged by vector similarity (cosine similarity being the standard choice)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The similarity computation itself runs at read time. But the judge is the embedding model — a model that doesn't read the conversation's context, doesn't grasp the question's intent, and was frozen at training time. In other words, RAG is &lt;strong&gt;a design that delegates the relevance judgment occurring at every read to a weak, frozen judge that doesn't read context&lt;/strong&gt;. Under conditions where LLMs are weak or expensive, this is still a rational division of labor.&lt;/p&gt;

&lt;p&gt;Note that what I decomposed here is bare embedding search (single-stage dense retrieval). Stacks with query rewriting, hybrid search, and metadata filters move part of the judgment outside the embedding — but that is an evolution in the direction of piling corrective devices around a weak judge. This weakness is well known in practice, which is exactly why rerankers — feeding the search results to an LLM for re-judgment — became a standard later stage. A reranker is a design that concedes: "for the final judgment, letting the model read is more reliable." For frontier-class LLMs, context processing is the core job, and there is no dimension on which vector similarity beats their ability to read the full text and judge "which of these matters for the current question." Compared to bare embedding search, letting the model read as much as it can read wins on judgment quality.&lt;/p&gt;

&lt;p&gt;If that's true, the design question changes. Not &lt;strong&gt;"how do we search"&lt;/strong&gt; but &lt;strong&gt;"can the model read it"&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision rule — when RAG is needed, when it isn't
&lt;/h2&gt;

&lt;p&gt;Here is the decision map up front. Two axes.&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;&lt;strong&gt;Frontier-class LLM&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Weak LLM (small local model, etc.)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Readable scale&lt;/strong&gt; (memory, hundreds of docs)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Structured memory + ADRs&lt;/strong&gt;. No vector search — let the model traverse from an index and read&lt;/td&gt;
&lt;td&gt;Embeddings are effective (you can't afford the cost of reading)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Unreadable scale&lt;/strong&gt; (large corpus)&lt;/td&gt;
&lt;td&gt;Agentic search (grep + read) or RAG&lt;/td&gt;
&lt;td&gt;RAG&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The right column and the bottom row are RAG's legitimate territory&lt;/strong&gt;: searching large corpora, fuzzy recall that keywords can't catch, and cases where the LLM itself is weak. Building these with RAG is correct.&lt;/li&gt;
&lt;li&gt;The problem is the &lt;strong&gt;top-left&lt;/strong&gt;. If you're building an embedding pipeline for an agent's self-memory — a store that you (or the agent) write to and that the LLM can read in full — you are delegating a high-value judgment to a weak judge. This article's claim applies to that quadrant only.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One clarification: the top-left claim is not "eliminate search." It is a reassignment — &lt;strong&gt;move the relevance judgment back from the embedding to the LLM, and demote retrieval to plain file reading&lt;/strong&gt;. A new design problem remains: how to maintain the index (more below).&lt;/p&gt;

&lt;p&gt;The rest of the article backs this top-left quadrant with three pieces of evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evidence 1: Claude Code's implementation — search is demoted to a tool
&lt;/h2&gt;

&lt;p&gt;Claude Code's memory runs on plain Markdown plus an index alone. Pulling the structure from the &lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;official docs&lt;/a&gt; (as of 2026-08), it comes down to three points:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Only the index is resident&lt;/strong&gt;: only the first 200 lines (or 25KB) of &lt;code&gt;MEMORY.md&lt;/code&gt; are loaded each session&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write-time compression is enforced&lt;/strong&gt;: as the index approaches its limit the harness warns, and exceeding it returns an error demanding a rewrite. The compression protocol — "keep one entry per line, push details out to topic files, consolidate and delete stale entries" (the gist of the official docs) — is enforced by the tool, not by the model's good intentions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bodies are read on demand&lt;/strong&gt;: topic files are not loaded at startup; when needed, Claude reads them with the ordinary file tool (Read)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The third point is the crux. &lt;strong&gt;Retrieval is not an independent pipeline — it has been demoted to an ordinary tool call.&lt;/strong&gt; The judgment of "what to read" is made at runtime by the LLM looking at the index, not by an embedding.&lt;/p&gt;

&lt;p&gt;Anthropic itself does not frame this structure as an ad-hoc omission. The September 2025 post &lt;a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents" rel="noopener noreferrer"&gt;Effective Context Engineering for AI Agents&lt;/a&gt; explicitly states the "just in time" approach as a design principle: instead of pre-processing all data, keep lightweight identifiers (file paths, etc.) and load with tools at runtime. "Structured note-taking (agentic memory)" — the agent writing Markdown notes — is also recommended in the same post, and Claude Code's implementation today remains consistent with this published direction.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The same post (2025-09) also states the trade-off: "runtime exploration is slower than retrieving pre-computed data." The speed counterargument is addressed in Evidence 3 below.&lt;/p&gt;
&lt;h2&gt;
  
  
  Evidence 2: embeddings can't see "the concrete difference" — my failure
&lt;/h2&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have been running an autonomous agent unattended for months on a small local model on an M1 Mac, and within that I have concretely failed with embedding clustering. The record is public, in &lt;a href="https://github.com/shimo4228/contemplative-agent/blob/main/docs/adr/0046-stocktake-llm-grouping-over-embedding-clustering.md" rel="noopener noreferrer"&gt;ADR-0046&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I built duplicate detection for the agent's auto-extracted skills using embedding cosine similarity + clustering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The skills share boilerplate vocabulary, so &lt;strong&gt;skills that are behaviorally distinct scored 0.90+ cosine similarity&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Linkage clustering chained those pairs, &lt;strong&gt;over-merging 18 skills into a single blob&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Switching back to passing all skill bodies to a single LLM call for classification recovered a sensible result: &lt;strong&gt;18 → 5 groups + 8 independent&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was not a "bad threshold tuning" failure. The ADR records the alternatives considered — raising the cosine threshold from 0.80 to 0.90 didn't separate them, and neither did changing the linkage method, because boilerplate vocabulary dominates this store. The concrete difference — same vocabulary, different instructed behavior — simply does not show up as distance in embedding space. An LLM reading the full text sees it. At readable scale, letting the model read wins — a real instance of the top-left quadrant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evidence 3: "select and inject" breaks the cache — a prefix-cache A/B
&lt;/h2&gt;

&lt;p&gt;"Reading everything is slow and expensive, so retrieve only the relevant part and inject it" — that's RAG's speed argument. But when you measure it, a hidden cost appears: &lt;strong&gt;injection whose content changes every call breaks the prefix cache every call&lt;/strong&gt;. The prefix cache is the mechanism that, when the head of the prompt matches the previous call, reuses that evaluation and skips input processing (it exists both in local llama.cpp and in frontier APIs).&lt;/p&gt;

&lt;p&gt;I ran a controlled A/B locally (Ollama / gemma4:e4b, num_ctx=32768, M1 16GB) on 2026-08-05 (&lt;a href="https://github.com/shimo4228/contemplative-agent/blob/main/docs/evidence/adr-0081/skillsel-cache-ab-20260805.jsonl" rel="noopener noreferrer"&gt;raw data public&lt;/a&gt;). We compare prefill time (prefill: the evaluation of the input prompt portion).&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;Prefill time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;~32K-char system prompt, first call (cold)&lt;/td&gt;
&lt;td&gt;38.85s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeat call with &lt;strong&gt;byte-identical&lt;/strong&gt; system (5/5 reproduced)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.061–0.064s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeat call with part of system (skill-injection section) swapped&lt;/td&gt;
&lt;td&gt;Full cold every time (6.3–7.1 ms/tok)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note: only the third row is token-normalized (ms/tok) because rotating the skill-injection section made the system size vary between 29K and 57K chars. The "full cold every time" verdict rests on all cold calls consistently landing at 6.3–7.1 ms/tok.&lt;/p&gt;

&lt;p&gt;Byte-identical is roughly 600x faster. Swap any part, and even with a shared head, the cache barely helped.&lt;/p&gt;

&lt;p&gt;Production telemetry (30 days, n=1,213) shows the same structure. The configuration that selects and injects skills per call (n=604) has a p50 of 56.1 seconds; the configuration that injects everything without selection (n=609) has a p50 of 28.3 seconds. &lt;strong&gt;The side that "injects only what's needed" is 2x slower in wall-clock time.&lt;/strong&gt; The production aggregate is not a controlled experiment, so I won't overclaim — but it is consistently explained by the same mechanism as the A/B above: selection changes the system prompt every call, so every call pays a cold prefill.&lt;/p&gt;

&lt;p&gt;Measurement caveat — token-count-based monitoring cannot see the cache effect&lt;/p&gt;

&lt;p&gt;On the Ollama path, &lt;code&gt;prompt_eval_count&lt;/code&gt; reports the full prompt token count (6,202 in this experiment) even on cache hits. Actually evaluating 6,202 tokens in 0.061 seconds is physically impossible, so hit detection is only possible via &lt;code&gt;prompt_eval_duration&lt;/code&gt;. If you only watch token counts, "inject everything" will keep looking expensive.&lt;/p&gt;

&lt;p&gt;This is a local llama.cpp measurement, and partly specific to a prompt layout whose shared head is only ~2K chars. But frontier API prompt caching is also conditioned on prefix matching from the start of the prompt, so the principle is the same — &lt;strong&gt;everything after a variable injection point gets recomputed every call&lt;/strong&gt;. If you put the stable part first and inject retrieval results at the tail, you protect the cache for the head; but a design that splices retrieval results into the middle of the system prompt, or has a long shared section after the injection point, pays the same cost measured here. The lesson: "what to inject, and where" is not just a retrieval-precision question — it is a cache-design question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Objection: "you just moved the cost to write-time discipline"
&lt;/h2&gt;

&lt;p&gt;I think the strongest objection to all of the above is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;RAG's advantage is zero write cost. Throw everything in, search later. ADRs and structured memory demand write-time discipline — compression, indexing, curation. Haven't you just traded search complexity for a documentation discipline that most teams cannot sustain?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My response has two parts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, that discipline can be carried by the agent, not by humans.&lt;/strong&gt; As Evidence 1 showed, Claude Code's auto-memory has the agent itself doing the work of choosing what to keep, compressing it, and updating the index. My 89 files weren't written by me being meticulous — the agent accumulated them from session learnings. The folk wisdom that "documentation discipline never lasts" is about human teams; with an LLM on the write side, the premise has changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, reads and writes are asymmetric.&lt;/strong&gt; A write happens once per piece of information; the read-time relevance judgment happens every session, every time. RAG delegates that every-time judgment to a weak judge, placing the cost and the quality degradation on the high-frequency side. Having a strong judge (an LLM) compress and organize once at write time is not a cost shuffle — it is &lt;strong&gt;a correct move to the low-frequency side&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  My own agent runs on the full benefit of RAG
&lt;/h2&gt;

&lt;p&gt;In the decision table I wrote "with a weak LLM, embeddings are effective." The very agent whose failure I showed in Evidence 2 is exactly that case — far from not needing RAG, it runs supported by embeddings.&lt;/p&gt;

&lt;p&gt;This agent runs on a small local model (8B class) as a &lt;a href="https://dev.to/shimo4228/building-an-autonomous-agent-on-an-m1-mac-by-choice-5b5o"&gt;deliberately chosen constraint&lt;/a&gt;. Measured cold prefill is 6.3–7.1 ms/tok (same agent and same model as the A/B in Evidence 3). It cannot afford to "read and judge every time" over a knowledge store of hundreds of patterns.&lt;/p&gt;

&lt;p&gt;So pattern classification, duplicate detection, and noise filtering are built on embeddings (&lt;a href="https://github.com/shimo4228/contemplative-agent/blob/main/docs/adr/0019-discrete-categories-to-embedding-views.md" rel="noopener noreferrer"&gt;ADR-0019&lt;/a&gt;). Strictly speaking this is not retrieval-augmented generation itself but "embeddings as a judge" — yet it is the same core design this article decomposed: delegating relevance judgment to a frozen judge. The effect shows up in numbers — replacing LLM-call-based classification with embeddings cut LLM time by about 20 minutes per day (ADR-0019, recorded as of 2026-04; that is an embeddings-vs-LLM-calls comparison, not a comparison against a "let the model read" configuration). In this environment, this division of labor clearly wins.&lt;/p&gt;

&lt;p&gt;There is no contradiction with the failure in Evidence 2. That case was a one-off judgment over 18 skill bodies — readable scale — so moving back to the LLM was correct. This case is also readable in scale, but the classification fires daily at high frequency, and an 8B model cannot pay the "read every time" cost, so embeddings are correct — the same agent, with the decision rule applied per mechanism.&lt;/p&gt;

&lt;p&gt;In other words, my environment sits in the right column of the table (weak LLM), where the RAG-side design is the right answer. Conversely, an agent with Opus or a GPT-class frontier model as its backend has its self-memory in the top-left quadrant (frontier-class LLM x readable scale) — and building an embedding pipeline there means outsourcing judgment to a weak judge while a strong judge is standing right there. &lt;strong&gt;The same design is correct in one quadrant and wrong in another&lt;/strong&gt; — that is this article's decision rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "ADR" specifically
&lt;/h2&gt;

&lt;p&gt;Among structured memory formats, there is a reason I recommend ADRs (Architecture Decision Records) in particular. The thing that rots fastest in an agent's memory is "why we decided this," and the ADR is the format that pins it down as a unit of &lt;strong&gt;decision + context + rejected alternatives&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write-time compression is built into the format&lt;/strong&gt;: the act of writing an ADR is itself the compression of "what was decided, and on what grounds"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expiry is handled&lt;/strong&gt;: ADRs carry status (accepted / superseded), and new decisions explicitly override old ones. The problem of contradictory memories cohabiting in a vector store doesn't arise — the format prevents it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The graph can be drawn by hand&lt;/strong&gt;: reference links between ADRs ("ADR-0046 refines ADR-0016") are drawn by the author at write time. The relationships that graph RAG tries to infer at runtime get fixed at the moment they are best known&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My public repo has 89 English ADRs (a different store from the 89 auto-memory files at the top — the equal count is coincidence), and Claude Code traverses them with grep and Read at the start of a session. Vector search never enters the picture.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;The core of RAG is delegating relevance judgment to a weak, frozen judge that doesn't read context. Where LLMs are weak or expensive it remains a rational division of labor — but in the quadrant where a frontier-class LLM handles a readable scale, it is degraded context processing&lt;/li&gt;
&lt;li&gt;The decision rule is two axes: &lt;strong&gt;readable scale x LLM strength&lt;/strong&gt;. For large corpora, fuzzy recall, and weak LLMs, RAG remains legitimate&lt;/li&gt;
&lt;li&gt;If you are in the agent self-memory x frontier-class LLM quadrant, before building an embedding pipeline, try &lt;strong&gt;an index + plain Markdown + ADRs&lt;/strong&gt;. Claude Code runs that design in production today, and the write discipline can be carried by the agent&lt;/li&gt;
&lt;li&gt;"Select and inject" has a measurable hidden cost: it breaks the prefix cache after the injection point on every call&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All numbers, failures, and ADRs from my environment are in a public repo. Verification and refutation are welcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Contemplative Agent — public repo with this article's ADRs and measurement data: &lt;a href="https://github.com/shimo4228/contemplative-agent" rel="noopener noreferrer"&gt;https://github.com/shimo4228/contemplative-agent&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Series hub "Building an Autonomous Agent on an M1 Mac, by Choice": &lt;a href="https://dev.to/shimo4228/building-an-autonomous-agent-on-an-m1-mac-by-choice-5b5o"&gt;https://dev.to/shimo4228/building-an-autonomous-agent-on-an-m1-mac-by-choice-5b5o&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Claude Code official docs (memory): &lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;https://code.claude.com/docs/en/memory&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Anthropic "Effective Context Engineering for AI Agents": &lt;a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents" rel="noopener noreferrer"&gt;https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Anthropic Prompt caching (prefix-matching spec): &lt;a href="https://platform.claude.com/docs/en/build-with-claude/prompt-caching" rel="noopener noreferrer"&gt;https://platform.claude.com/docs/en/build-with-claude/prompt-caching&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Author's GitHub: &lt;a href="https://github.com/shimo4228" rel="noopener noreferrer"&gt;https://github.com/shimo4228&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>discuss</category>
      <category>rag</category>
      <category>claudecode</category>
      <category>contextengineering</category>
    </item>
    <item>
      <title>Give Claude Code a Second Harness</title>
      <dc:creator>shimo4228</dc:creator>
      <pubDate>Sun, 02 Aug 2026 12:24:40 +0000</pubDate>
      <link>https://dev.to/shimo4228/give-claude-code-a-second-harness-27of</link>
      <guid>https://dev.to/shimo4228/give-claude-code-a-second-harness-27of</guid>
      <description>&lt;p&gt;The CLAUDE.md files, rules, and skills you have built up are useful in everyday development. But they can be too heavy for experiments or autonomous loops. Sometimes you want to swap the whole set for a different one.&lt;/p&gt;

&lt;p&gt;Claude Code, however, has &lt;strong&gt;no single profile switch that replaces only your personal harness while preserving the repository configuration&lt;/strong&gt;. The closest mechanism is &lt;code&gt;CLAUDE_CONFIG_DIR&lt;/code&gt;, which points Claude Code at a different configuration directory. But it switches only half of the harness. Skills and agents move; CLAUDE.md and rules from the original environment still come along. On my machine, that meant 16 files and 3,404 words on every launch.&lt;/p&gt;

&lt;p&gt;That is the missing piece. The solution is to combine a configuration-directory switch with path-based instruction exclusion.&lt;/p&gt;

&lt;p&gt;The first half of this article explains why the combination is necessary and what it preserves. The second half gives you a planning prompt for your coding agent. The agent inspects your environment read-only and stops after producing an implementation plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Tested with Claude Code v2.1.220 on macOS&lt;/li&gt;
&lt;li&gt;In this article, “harness” means the set of &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;rules/&lt;/code&gt;, &lt;code&gt;skills/&lt;/code&gt;, and &lt;code&gt;agents/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;I call the new directory selected by &lt;code&gt;CLAUDE_CONFIG_DIR&lt;/code&gt; the “alternate side”&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The configuration directory switches only half the harness
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;CLAUDE_CONFIG_DIR&lt;/code&gt; is an environment variable that points Claude Code at another configuration directory. Here is what moves—and what stays behind.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Part of the harness?&lt;/th&gt;
&lt;th&gt;Switches to the alternate side?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;skills/&lt;/code&gt; and &lt;code&gt;agents/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Switches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; and &lt;code&gt;rules/&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Does not switch; the originals still load&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;settings.json&lt;/code&gt; (including hook definitions)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Switches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session history and plugins&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Switches&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Only the top half of the four-part harness changes.&lt;/p&gt;

&lt;p&gt;The bottom two rows follow the official documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Override the configuration directory (default: &lt;code&gt;~/.claude&lt;/code&gt;). All settings, session history, and plugins are stored under this path&lt;br&gt;
(&lt;a href="https://code.claude.com/docs/en/env-vars" rel="noopener noreferrer"&gt;Environment variables&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Skills and agents do not appear in that sentence, but they switched in my test. When I launched Claude Code with the alternate directory and asked for the available skills, my usual custom skills were gone. Only the skills placed on the alternate side appeared.&lt;/p&gt;

&lt;p&gt;Credential storage depends on the operating system. On Linux and Windows, credentials live under the configuration directory. On macOS, they live in the system Keychain. In the macOS environment tested here, changing the configuration directory does not move a credential file with it.&lt;/p&gt;

&lt;p&gt;CLAUDE.md and rules remain because they are found through another mechanism:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Claude Code reads CLAUDE.md files by walking up the directory tree from your current working directory&lt;br&gt;
(&lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;How Claude remembers your project&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When the working directory is below your home directory, that walk necessarily passes through &lt;code&gt;$HOME&lt;/code&gt;. This makes &lt;code&gt;$HOME/.claude/&lt;/code&gt; part of the search path regardless of where &lt;code&gt;CLAUDE_CONFIG_DIR&lt;/code&gt; points.&lt;/p&gt;

&lt;p&gt;How I verified the ancestor-directory behavior&lt;/p&gt;

&lt;p&gt;I launched Claude Code with an empty configuration directory. Nothing could come from that directory, but Claude Code still loaded 16 files from &lt;code&gt;$HOME/.claude&lt;/code&gt;. It recorded them as project files rather than user files.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Launch condition&lt;/th&gt;
&lt;th&gt;Files loaded from &lt;code&gt;$HOME/.claude&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;Recorded type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Default configuration directory (&lt;code&gt;$HOME/.claude&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;User (&lt;code&gt;User&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Empty alternate configuration directory&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Project (&lt;code&gt;Project&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The label changed; the files still loaded. Placing the working directory outside the home directory removed this path. On v2.1.220, pointing a symlink outside the home directory back to a directory under it did not help because Claude Code resolved the real path.&lt;/p&gt;

&lt;p&gt;Claude Code does provide official ways to disable customizations broadly. None draws the boundary needed here.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Official mechanism&lt;/th&gt;
&lt;th&gt;What it disables&lt;/th&gt;
&lt;th&gt;Why it does not fit this case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--safe-mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CLAUDE.md, skills, hooks, plugins, and other customizations&lt;/td&gt;
&lt;td&gt;Repository customizations stop too&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--bare&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Automatic loading of CLAUDE.md, hooks, plugins, MCP, auto memory, and more&lt;/td&gt;
&lt;td&gt;It is a minimal mode for scripted calls, and repository customizations stop too. Skills remain explicitly invocable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLAUDE_CODE_DISABLE_CLAUDE_MDS=1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;User, project, and auto-memory instruction files&lt;/td&gt;
&lt;td&gt;It also removes the repository CLAUDE.md&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The target behavior is narrower: replace the personal harness while keeping the repository’s instructions and mechanical gates. Claude Code has no single official switch for that selective profile change. &lt;a href="https://github.com/anthropics/claude-code/issues/30380" rel="noopener noreferrer"&gt;Issue #30380&lt;/a&gt;, which requests per-session disabling of the global CLAUDE.md, is closed as not planned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The human decision is where to draw the boundary
&lt;/h2&gt;

&lt;p&gt;This is the boundary used here:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Treatment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Everyday &lt;code&gt;$HOME/.claude&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Leave unchanged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Personal CLAUDE.md, rules, skills, and agents&lt;/td&gt;
&lt;td&gt;Replace with a second set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repository CLAUDE.md, rules, settings, and hooks&lt;/td&gt;
&lt;td&gt;Preserve&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Project memory&lt;/td&gt;
&lt;td&gt;Separate by default; share only if needed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This gives you a harness for experiments, autonomous loops, or a separate account without changing the environment you use for everyday development.&lt;/p&gt;

&lt;p&gt;The mechanism has two parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;CLAUDE_CONFIG_DIR&lt;/code&gt; to change the location of settings, skills, agents, and history.&lt;/li&gt;
&lt;li&gt;In the alternate &lt;code&gt;settings.json&lt;/code&gt;, use &lt;code&gt;claudeMdExcludes&lt;/code&gt; to exclude the original &lt;code&gt;$HOME/.claude/CLAUDE.md&lt;/code&gt; and &lt;code&gt;rules/&lt;/code&gt; from instruction loading.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;claudeMdExcludes&lt;/code&gt; accepts absolute-path glob patterns. In a terminal, &lt;code&gt;~&lt;/code&gt; (tilde) is normally shorthand for your home directory. It is not expanded here. Writing &lt;code&gt;~/.claude/**&lt;/code&gt; fails to exclude anything without producing an error in v2.1.220.&lt;/p&gt;

&lt;p&gt;On a company- or school-managed machine, IT may deploy a machine-wide CLAUDE.md. Personal settings cannot exclude those managed instructions. If your machine is not centrally managed this exception does not apply.&lt;/p&gt;

&lt;p&gt;Everything after this point depends on the reader’s filesystem, existing settings, symlinks, and authentication state. Do not copy a generic sequence of commands from an article. Have your coding agent inspect your machine and turn the design into an environment-specific implementation plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give this article to your agent and ask for a plan
&lt;/h2&gt;

&lt;p&gt;Give your coding agent the URL of this article and the following prompt. At this stage, the agent must not change any files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read this article and create an implementation plan for adding a second, purpose-specific Claude Code harness to this machine.

Important:
- Perform read-only inspection for this request.
- Do not create, edit, move, or delete files. Do not change settings, create symlinks, or perform Git operations.
- Put commands in the plan, but do not run commands that mutate state.

Preferences:
- Candidate TARGET_CONFIG_DIR: ~/.claude-alt
- Contents of the second harness: undecided. Look for existing candidates on this machine.
- Share project memory: no

Goals:
- Do not modify the everyday ~/.claude directory.
- Make it possible to place a separate CLAUDE.md, rules, skills, and agents in TARGET_CONFIG_DIR.
- When Claude Code starts with TARGET_CONFIG_DIR, do not load the original ~/.claude/CLAUDE.md or ~/.claude/rules/**.
- Preserve CLAUDE.md, .claude/rules, settings, and hooks from the working repository.

If you cannot access the article, treat the inspection and planning requirements below as canonical.

Read-only inspection:
1. Check claude --version, the operating system, and the resolved HOME path.
2. Inspect the structure of ~/.claude, the candidate configuration directory, and the repository's .claude directory. Do not copy their contents into a public report.
3. Look for candidates for the second harness. List possible sources separately for CLAUDE.md, rules, skills, and agents. If none exist, propose an empty structure.
4. Check for existing settings.json files, symlinks, projects/, and managed policy.

Requirements for the plan:
- Do not modify the everyday ~/.claude directory or files in the repository.
- Resolve the candidate configuration directory to an absolute path. Reject /, HOME, ~/.claude, any path below ~/.claude, and an existing symlink as the target.
- Do not overwrite existing files. For same-name assets, show the diff; skip identical content and make differing content a human approval point.
- Do not dereference symlinks when copying. Show each link target and its risk.
- Preserve existing keys in settings.json and merge the resolved absolute `&amp;lt;HOME&amp;gt;/.claude/**` glob into claudeMdExcludes. Do not use `~/.claude/**`.
- Show the destination of each CLAUDE.md, rule, skill, and agent file.
- Take a no-exclusion baseline before adding the exclusion, then compare it with the result after the change.
- Use a temporary directory and `--settings` for verification. Do not rewrite the real settings.json or existing hooks merely to run the test.
- Limit the InstructionsLoaded hook to `matcher: "session_start"`. After each process exits, wait for the asynchronous log to stabilize with a bounded timeout. Compare unique `file_path` values and their sources.
- Success means zero files loaded from the original ~/.claude, loading the second CLAUDE.md and rules when present, and preserving repository instruction files.
- Use /context or InstructionsLoaded records as evidence. Do not infer success from what the LLM says it knows.
- Remove temporary verification files afterward. Recheck the final settings.json diff and JSON syntax.
- Keep project-memory sharing out of the main plan. Describe it only as an alternative, including mixed conversation history and the deletion risk from `claude project purge`.

Output:
1. Summary of the current state
2. Proposed structure and the reason for choosing it
3. Implementation steps, each with target paths, commands, intended changes, and a rollback method
4. Verification steps and success criteria
5. Human approval points
6. Unverified assumptions and risks

Stop after presenting the plan. Do not implement it until I explicitly approve it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Review the proposed target paths, no-overwrite behavior, rollback method, and success criteria. If the plan is sound, explicitly tell the same agent: “Implement this plan and verify every success criterion it defines.”&lt;/p&gt;

&lt;p&gt;The planned layout should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.claude-alt/
├── CLAUDE.md        # when the second-harness plan includes one
├── settings.json
├── rules/
├── skills/
└── agents/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The essential addition to &lt;code&gt;settings.json&lt;/code&gt; is one key. The plan must merge it without discarding existing keys.&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;"claudeMdExcludes"&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="s2"&gt;"/Users/you/.claude/**"&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;&lt;code&gt;/Users/you/&lt;/code&gt; is illustrative. The real file must contain the absolute HOME path detected on the reader’s machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Judge success by the load source
&lt;/h2&gt;

&lt;p&gt;For a quick manual check, launch Claude Code with the alternate configuration, run &lt;code&gt;/context&lt;/code&gt;, and inspect &lt;strong&gt;Memory files&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For an automated check, use the &lt;code&gt;InstructionsLoaded&lt;/code&gt; hook. Each time Claude Code loads a CLAUDE.md or rule, the hook receives the absolute &lt;code&gt;file_path&lt;/code&gt;, &lt;code&gt;memory_type&lt;/code&gt;, and &lt;code&gt;load_reason&lt;/code&gt;. The planning prompt requires a controlled comparison using those records.&lt;/p&gt;

&lt;p&gt;On my Claude Code v2.1.220 environment, changing only the exclusion produced this result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Load source&lt;/th&gt;
&lt;th&gt;No exclusion&lt;/th&gt;
&lt;th&gt;With exclusion&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Original &lt;code&gt;$HOME/.claude&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;16&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;Repository&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total&lt;/td&gt;
&lt;td&gt;17&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;With &lt;code&gt;~/.claude/**&lt;/code&gt;, the total remained 17. Replacing it with the absolute path reduced the total to 1. These are v2.1.220 measurements. Judge success from the &lt;code&gt;file_path&lt;/code&gt; breakdown, not from whether the command prints an error.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not ask the LLM, “Do you know this instruction?” to verify loading. The model may know the same information from repository memory or training data. Use Claude Code’s own &lt;code&gt;/context&lt;/code&gt; output or &lt;code&gt;InstructionsLoaded&lt;/code&gt; records.&lt;br&gt;
&lt;/p&gt;Optional: share project memory while keeping the harnesses separate
&lt;/blockquote&gt;

&lt;p&gt;Project memory also lives under the configuration directory, so it disappears from the alternate side by default. If you want to reuse it, do not create a link immediately. Change “Share project memory” in the planning prompt to “consider sharing,” then ask the agent to produce a separate plan.&lt;/p&gt;

&lt;p&gt;Linking all of &lt;code&gt;projects/&lt;/code&gt; shares future projects without creating new links, but it also shares conversation transcripts. Sessions from both environments appear together in &lt;code&gt;/resume&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not run &lt;code&gt;claude project purge&lt;/code&gt; while sharing this directory.&lt;/strong&gt; In a local v2.1.220 &lt;code&gt;--dry-run&lt;/code&gt;, one project produced 72 deletion targets. If &lt;code&gt;projects/&lt;/code&gt; is a symlink, deleting &lt;code&gt;projects/&amp;lt;slug&amp;gt;/&lt;/code&gt; follows the link and removes the everyday environment’s transcripts and auto memory. Other purge targets—file history, tasks, configuration entries, and input history—belong to the alternate side.&lt;/p&gt;



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

&lt;p&gt;The design has two parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;CLAUDE_CONFIG_DIR&lt;/code&gt; to switch the configuration directory. Skills, agents, hooks, and history move here.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;claudeMdExcludes&lt;/code&gt; to the alternate &lt;code&gt;settings.json&lt;/code&gt;. This removes the original CLAUDE.md and rules from the remaining instruction-loading path.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result is a Claude Code launch that swaps the personal harness as a unit. There is no single switch for this boundary, but you can get the same behavior by explicitly excluding the part that would otherwise remain.&lt;/p&gt;

&lt;p&gt;Always verify the load sources after adding the exclusion. The dangerous failure mode is silent: an ineffective glob produces no error.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;How Claude remembers your project&lt;/a&gt; — CLAUDE.md load order and &lt;code&gt;claudeMdExcludes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.claude.com/docs/en/hooks" rel="noopener noreferrer"&gt;Hooks reference&lt;/a&gt; — the &lt;code&gt;InstructionsLoaded&lt;/code&gt; hook&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.claude.com/docs/en/env-vars" rel="noopener noreferrer"&gt;Environment variables&lt;/a&gt; — environment variables including &lt;code&gt;CLAUDE_CONFIG_DIR&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.claude.com/docs/en/cli-usage" rel="noopener noreferrer"&gt;CLI reference&lt;/a&gt; — &lt;code&gt;--safe-mode&lt;/code&gt; and &lt;code&gt;--bare&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/anthropics/claude-code/issues/30380" rel="noopener noreferrer"&gt;Issue #30380&lt;/a&gt; — request to disable the global CLAUDE.md (closed as not planned)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228/claude-harness" rel="noopener noreferrer"&gt;claude-harness&lt;/a&gt; — the Claude Code harness I maintain and use&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228" rel="noopener noreferrer"&gt;github.com/shimo4228&lt;/a&gt; — my other repositories&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>claudecode</category>
      <category>anthropic</category>
      <category>contextengineering</category>
      <category>aiagents</category>
    </item>
    <item>
      <title>Banned from Wikidata Overnight — I Believed Every Edit Was Compliant, but All 109 Items Were Deleted as Promotion</title>
      <dc:creator>shimo4228</dc:creator>
      <pubDate>Thu, 30 Jul 2026 13:00:04 +0000</pubDate>
      <link>https://dev.to/shimo4228/banned-from-wikidata-overnight-i-believed-every-edit-was-compliant-but-all-109-items-were-57nl</link>
      <guid>https://dev.to/shimo4228/banned-from-wikidata-overnight-i-believed-every-edit-was-compliant-but-all-109-items-were-57nl</guid>
      <description>&lt;p&gt;On the morning of July 16, 2026, I noticed a notification from Wikidata. When I opened it, my account was indefinitely blocked. The reason field said "Promotion-only account." My first reaction was: "Wait — I didn't do anything wrong."&lt;/p&gt;

&lt;p&gt;I pulled the deletion log. Between 01:58:29 and 02:00:51 — &lt;strong&gt;about two and a half minutes — all 109 items I had created were mass-deleted.&lt;/strong&gt; Six weeks of building, gone overnight.&lt;/p&gt;

&lt;p&gt;"Subjects listed on Wikipedia or Wikidata get cited by AI. So register your own work as entities." — If you follow discussions of LLM-era discoverability (so-called GEO: Generative Engine Optimization), you have probably seen this idea somewhere. I acted on it, and this is how it ended.&lt;/p&gt;

&lt;p&gt;The puzzling part is that &lt;strong&gt;I believed every individual edit was policy-compliant.&lt;/strong&gt; I knew you must not write your own Wikipedia article. But Wikidata's policy is different: editing items about yourself (an item is Wikidata's unit of registration — a person, a work, a concept each gets one) is generally tolerated as long as you meet the sourcing requirements. The guideline &lt;a href="https://www.wikidata.org/wiki/Wikidata:Autobiography" rel="noopener noreferrer"&gt;Wikidata:Autobiography&lt;/a&gt; says so explicitly.&lt;/p&gt;

&lt;p&gt;Under that understanding, I attached references to every statement and passed Wikidata's constraint checks. My talk page — the public per-user contact page where policy warnings land first — never received a single warning. The block notice names no individual edit as a violation either.&lt;/p&gt;

&lt;p&gt;And yet I lost everything. Why?&lt;/p&gt;

&lt;p&gt;This article does three things.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Why the total loss happened&lt;/strong&gt; — I explain the structure in which the judgment is rendered per account, not per edit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The incident response I closed out the same day&lt;/strong&gt; — the withdrawal decision, purging references, and designing the apology, exactly as I did them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How to convert failure into norms&lt;/strong&gt; — the procedure I used to turn this incident into a design decision record (ADR) and into rules my agent loads on every session&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I was building, and what happened
&lt;/h2&gt;

&lt;p&gt;Some context first: why was I registering DOIs and the like as an individual? I have no ambition to become an academic or a researcher.&lt;/p&gt;

&lt;p&gt;Writing implementations with AI coding tools, I came to feel that the cost of implementation is steadily evaporating, and that any code I write will be obsolete soon.&lt;/p&gt;

&lt;p&gt;Then at least I wanted to keep a dated record of judgment — "at this point in time, this is what I was thinking." So I started consolidating my thinking into public repositories (the "research repos" below) and papers, attaching DOIs (persistent identifiers for papers and software; services like Zenodo let you mint them yourself), making it all citable with dates.&lt;/p&gt;

&lt;p&gt;The Wikidata investment was an extension of this.&lt;/p&gt;

&lt;p&gt;But a record that never gets discovered might as well not exist. And what decides whether something gets read today is generative AI, more than search engines.&lt;/p&gt;

&lt;p&gt;Readers ask an AI; works the AI cites get found, and works it does not cite might as well not exist. So I also invested in the groundwork for being discovered by AI (GEO).&lt;/p&gt;

&lt;p&gt;What encouraged me was GEO measurement data. Multiple studies of generative-AI citation sources report that Wikipedia consistently ranks near the top of ChatGPT's cited domains (second tier, just behind Reddit) — see &lt;a href="https://www.semrush.com/blog/most-cited-domains-ai/" rel="noopener noreferrer"&gt;Semrush&lt;/a&gt; and &lt;a href="https://ahrefs.com/blog/most-cited-domains-in-chatgpt/" rel="noopener noreferrer"&gt;Ahrefs&lt;/a&gt;, among others.&lt;/p&gt;

&lt;p&gt;I have not found any measurement isolating the effect of Wikidata itself, as far as I searched.&lt;/p&gt;

&lt;p&gt;Still, I reasoned: "Wikidata is what supplies the machine-readable entity data behind Wikipedia. If I register my author, repositories, and papers as items, cross-linked with identifiers like DOIs, an AI should be able to establish that this author and these works exist and connect this way." — That was my read, and I invested in this layer.&lt;/p&gt;

&lt;p&gt;Looking back, the boundary between measurement and hope was already blurred at this point.&lt;/p&gt;

&lt;p&gt;Here is what I built: an author item, repository items, paper items, and bibliographic items for the external works my papers cite — all connected with citation-relation properties.&lt;/p&gt;

&lt;p&gt;The edits were executed by an AI agent (Claude Code) in a semi-automated setup, with me approving before any batch of writes. Every edit carried references. Over six weeks it grew to 109 items.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;When&lt;/th&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;06-07&lt;/td&gt;
&lt;td&gt;Created author item + 3 papers + 7 repositories&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;06-12&lt;/td&gt;
&lt;td&gt;Added 27 bibliographic items for cited works + citation edges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;06-13 to 26&lt;/td&gt;
&lt;td&gt;Added external identifiers, more paper items&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Late night 07-15 to 16&lt;/td&gt;
&lt;td&gt;Created 19 new bibliographic items + bulk-synced citation edges from 14 to 33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;07-16 01:58–02:01 UTC&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;All 109 items mass-deleted + account blocked indefinitely&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The block reason: "Promotion-only account: Promoting Tatsuya Shimomoto." The deletion log reason: "Spam / advertising: Mass deletion." Anyone can still verify this judgment through Wikidata's public API. (Throughout this article, every count and timestamp is a value you can confirm via the public API; wherever no log survives, I flag it explicitly as the author's recollection.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Count deletions related to the account in the 2026-07-16 mass-deletion log&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://www.wikidata.org/w/api.php?action=query&amp;amp;list=logevents&amp;amp;letype=delete&amp;amp;leuser=The%20Squirrel%20Conspiracy&amp;amp;lelimit=500&amp;amp;format=json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="s2"&gt;"audit/1.0"&lt;/span&gt; | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json,sys
evs = json.load(sys.stdin)['query']['logevents']
mine = [e for e in evs if 'Shimo4228' in e.get('comment','')]
print(len(mine))  # -&amp;gt; 109
"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the Wikidata API rejects requests without a User-Agent with a 403, so the &lt;code&gt;-A&lt;/code&gt; flag is required.&lt;/p&gt;

&lt;p&gt;The block reason text is also retrievable verbatim via the API&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Fetch target, timestamp, and reason of the currently active block&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://www.wikidata.org/w/api.php?action=query&amp;amp;amp;list=blocks&amp;amp;amp;bkusers=Shimo4228&amp;amp;amp;bkprop=user%7Cby%7Ctimestamp%7Creason&amp;amp;amp;format=json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="s2"&gt;"audit/1.0"&lt;/span&gt; | python3 &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  The cause: judgment is per account, not per edit
&lt;/h2&gt;

&lt;p&gt;Mechanical validation (presence of references, constraint checks, schema validity) measures &lt;strong&gt;the technical validity of each individual edit&lt;/strong&gt;. What Wikidata's human administrators look at is &lt;strong&gt;the pattern drawn by the account's entire contribution history&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;My 109 items — author, the author's works, the author's papers, the works the author's papers cite — varied in kind, but they all traced &lt;strong&gt;a web pointing at one single person&lt;/strong&gt;. Any administrator opening this history for the first time sees "promotion-only," regardless of how careful each individual edit was.&lt;/p&gt;

&lt;p&gt;Indeed, the block notice contains not one citation of an individual violating edit. The granularity of the judgment is simply different.&lt;/p&gt;

&lt;p&gt;An immediate indefinite block with no warning is not unusual, either. Wikidata's &lt;a href="https://www.wikidata.org/wiki/Wikidata:Blocking_policy" rel="noopener noreferrer"&gt;blocking policy&lt;/a&gt; states explicitly that a warning is not required before a block, and blocking accounts judged promotional or spam indefinitely without warning is routine operation across Wikimedia projects (Wikipedia's policy likewise provides that promotion-only accounts may be blocked "without warning, usually indefinitely").&lt;/p&gt;

&lt;p&gt;Warnings and dialogue are afforded to those inside the "good-faith user" category. My account was classified outside that category — that is what this judgment means.&lt;/p&gt;

&lt;p&gt;One more factor, I believe, determined the timing of detection: speed. About ten hours before the block, late at night, I was running the creation of 19 bibliographic items plus a bulk sync of citation edges. During the run, the agent kept reporting that "some items fail to post." (No log of this survives, so this part is the author's recollection.)&lt;/p&gt;

&lt;p&gt;My read was "probably a transient server error on Wikidata's side," so I waited a while and resumed posting. The posts went through, and I even thought, "good, the server has recovered." The next morning: the block.&lt;/p&gt;

&lt;p&gt;I cannot see what the moderation side actually detected, so I cannot prove the causal link between speed and detection. But as a lesson, I should have leaned toward assuming it. A non-bot account pinned against the throttle ceiling is the most conspicuous thing on the new-item feed and edit-rate patrols.&lt;/p&gt;

&lt;p&gt;Repeated rate-limit errors are not "an outage that resolves if you wait." They are an &lt;strong&gt;alarm&lt;/strong&gt;: "this speed and volume is anomalous for this account type." I should have stopped on the spot and reassessed. The errors clearing and the posts going through was not "recovery." I had merely resumed the single most conspicuous behavior on the patrol surface.&lt;/p&gt;

&lt;p&gt;In hindsight, there was also a hole in how I approved. I had authorized the creation of 19 items through one single decision — "OK to run the bulk sync?" A judgment I might have paused on item by item had been diluted to one nineteenth by batching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the misunderstanding came from: "Wikidata allows self-registration"
&lt;/h2&gt;

&lt;p&gt;So where did the understanding I opened with — "Wikipedia forbids it, but Wikidata tolerates it" — come from, and where did it turn into an error? I traced four sources.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Wikidata's policy really is looser than Wikipedia's.&lt;/strong&gt; As we saw at the start, the guideline generally tolerates editing your own item, and that understanding itself is not wrong. Moreover, initiatives like WikiCite, which aggregates scholarly bibliographic data in Wikidata, have a culture of researchers curating publication data themselves. The error was the leap from "individual edits are tolerated" to "therefore any amount accumulated across the account is safe"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Survivorship bias.&lt;/strong&gt; Deleted self-registrations are unobservable. All you ever see are the cases that survived&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measuring the wrong axis.&lt;/strong&gt; My validation loop measured only technical compliance — references, constraints, schema — and kept returning PASS. It never measured the axis of "what does this look like to an administrator"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Misreading notability.&lt;/strong&gt; A DOI is an identifier I can mint through my own actions, but I had misconstrued it as usable evidence of notability (the standard for whether something merits an item)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And here is the fact that stings the most: &lt;strong&gt;I half knew.&lt;/strong&gt; Self-citation on Wikipedia was something I had already considered in advance and rejected as a conflict-of-interest violation. Even so, I redrew the boundary — "the prose encyclopedia is off-limits, but structured data is different" — and went ahead. Draw the boundary in the wrong place, and half-knowing still ends in an incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same-day response: withdraw, purge references, apologize
&lt;/h2&gt;

&lt;p&gt;Once I finished confirming the facts (pulling the deletion log above), the first fork was: appeal the block, or withdraw?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Appeal the block&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Rejected&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Even if it succeeded, this layer would remain revocable at the operators' discretion at any time. What was rejected was not the quality of my execution but the premise itself: manufacturing your own authority data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Withdraw + document the lessons&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Adopted&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Remove all references to the revoked layer, extract design principles from the failure, and build a structure that never chooses this path again&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first task in withdrawal was &lt;strong&gt;purging dead references&lt;/strong&gt;. My research repos embedded &lt;code&gt;sameAs&lt;/code&gt; links to Wikidata item IDs (QIDs) in their machine-readable metadata (JSON-LD knowledge graphs and the like) — and every one of those targets was now a 404.&lt;/p&gt;

&lt;p&gt;Leaving identity links pointing at 404s is misinformation aimed at crawlers, and — ironically — an imitation of a classic spam pattern. I removed every QID reference from 9 repositories plus the distribution mirrors within the same day.&lt;/p&gt;

&lt;p&gt;Careless removal breaks other things. I used a script with a two-gate check: "still valid as JSON" and "the only change is the removal."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The gist of purge_wikidata.py (a one-off script for that day, never committed):
# after dropping the target lines, pass two gates before writing
&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out_lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# gate 1: still valid JSON?
&lt;/span&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;strip_wd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# gate 2: is the only semantic change the removal?
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things that actually bit me during the bulk removal&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hardcoded values inside generator scripts&lt;/strong&gt;: I deleted QIDs from the data files, but a page-generation script had QIDs written directly into it, so regeneration resurrected them. When removing something from generated output, suspect the generator&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missed restatements of counts&lt;/strong&gt;: statements like "there are 20 ADRs" inside the repos — human review missed 6 files. What caught them was an existing verification script that scans every numeric claim in every file against the file contents as ground truth. It was a case where mechanical scanning beats human thoroughness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, the apology. I made one design decision here: &lt;strong&gt;post to the talk page a pure apology containing no request for an unblock.&lt;/strong&gt; The moment you attach an unblock request, the apology reads as a negotiating tool.&lt;/p&gt;

&lt;p&gt;By the time I posted it, I had already deleted the automation skill for Wikidata writes and the API credentials (the companion repository that published those procedures was deleted the same day as well; the research repos themselves remain — what I removed was the means of writing). I could demonstrate "never again" through the state of things, not words.&lt;/p&gt;

&lt;p&gt;I consider the administrator's judgment fair. What this incident actually left the community was the administrative labor of reviewing and mass-deleting 109 items.&lt;/p&gt;

&lt;p&gt;Even while blocked, you can write to your own talk page&lt;/p&gt;

&lt;p&gt;Even under a site-wide editing block, if the block's parameters do not forbid editing your own talk page, you can post to it via the login API (logged-out attempts were rejected by the automatic IP block). I made the post with a one-off script using an existing BotPassword, and deleted the credentials after running it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prevention: the record goes into an ADR, the behavior into rules
&lt;/h2&gt;

&lt;p&gt;In parallel with the incident response, I fixed this failure in place at two layers — so it would not end as "let's be careful next time." &lt;strong&gt;A record of the judgment (ADR)&lt;/strong&gt; and &lt;strong&gt;rules that change day-to-day behavior&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;First, the record layer. An ADR (Architecture Decision Record) is a document format that records a design decision together with its context and the alternatives that were rejected. What it preserves is the reasoning — "why we withdrew, why we will never do this again" — so future sessions do not relitigate the same argument. I wrote one ADR, "entity grounding is done self-sovereign," with four core points:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Design any layer governed by a third party under the assumption it can be revoked&lt;/strong&gt; — does the strategy survive if that layer vanishes entirely? Never place load-bearing references on a revocable layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use only mentions that others created of their own volition; never choose the path of creating them yourself&lt;/strong&gt; — commissioning someone else to create them is equally out&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When revoked, remove dead references promptly — but never rewrite dated historical records&lt;/strong&gt; — present-tense claims and historical records have different roles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No circumvention&lt;/strong&gt; — alternate accounts, logged-out editing, or proxying through third parties only converts a disagreement with governance into a permanent adversarial relationship&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I also set a principle about the order of response. A "do not climb" sign does not stop someone who is climbing. What stops them is the absence of any means to climb.&lt;/p&gt;

&lt;p&gt;So that day's response ran in this order: &lt;strong&gt;deleting the means (skill, credentials, repository) → mechanism (writing the lessons into rules) → words (the apology)&lt;/strong&gt;. The strength of a prohibition ranks: absence &amp;gt; mechanism &amp;gt; signage.&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;what actually works as prevention is not the ADR.&lt;/strong&gt; Records are not guaranteed to be reread. What changes behavior is the &lt;strong&gt;instruction files&lt;/strong&gt; of the executing agent, Claude Code. The agent automatically loads its rule files at the start of every session before doing anything, so a lesson written there never needs to be "remembered." This time I wrote it into three places:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rules shared across all projects (&lt;code&gt;~/.claude/rules&lt;/code&gt; — auto-loaded every session)&lt;/td&gt;
&lt;td&gt;"Repeated rate-limit errors are an alarm. Stop the bulk write and report to the human." "Never pass N writes through one approval. Approvals must state count and scope"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learned notes (&lt;code&gt;~/.claude/skills/learned&lt;/code&gt; — consulted during bulk external-write work)&lt;/td&gt;
&lt;td&gt;Before executing, ask: "if an administrator seeing this account for the first time opened its history, what would it look like?" (a platform-agnostic self-check)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The project's own instruction file (loaded when working in that repository)&lt;/td&gt;
&lt;td&gt;Total ban on self-registration paths (variants — alternate accounts, anonymity, proxies — are equally guilty)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With this, "stop and report when errors repeat" no longer depends on whether I or the agent happens to remember it on some future late night. The lesson lives in the agent's boot sequence, not in human memory.&lt;/p&gt;

&lt;p&gt;I decided against enforcement via hooks, by the way. "Bulk writes to external platforms" cannot be detected deterministically, and the cost of false positives halting work outweighs the benefit. Enforce mechanically only where detection is mechanical; keep everything requiring judgment as rules and questions. That dividing line is itself part of the prevention design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: separate the effect from the pathway
&lt;/h2&gt;

&lt;p&gt;The lesson of this incident is not "Wikidata is useless." &lt;strong&gt;The effect and the pathway are different things.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The effect&lt;/strong&gt;: multiple measurements support that being mentioned in authoritative sources like Wikipedia helps AI citation. The hope for Wikidata sits on that same line (though the honest summary of what I found is that direct measurements of Wikidata alone are thin)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The pathway&lt;/strong&gt;: the pathway of &lt;strong&gt;creating those mentions yourself&lt;/strong&gt; can suffer total loss through account-level governance judgment even when every individual edit is permitted. In my case, it did. So I switched to relying only on &lt;strong&gt;earned&lt;/strong&gt; mentions — ones that others created of their own volition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GEO literature talks about the effect; almost none of it teaches the distinction between pathways. My 109 items were the tuition for that distinction.&lt;/p&gt;

&lt;p&gt;Looking back, my error was not one of effort but of &lt;strong&gt;classification&lt;/strong&gt;. Among the places that hold identifiers and proof of existence, there are places where self-registration is legitimate (self-service identifiers like DOI minting, machine-readable metadata in your own repositories) and places where you must wait to be written about by others.&lt;/p&gt;

&lt;p&gt;Wikidata — even though each individual edit is tolerated — turned out to be the latter, just like Wikipedia, as a place for registering an entire web centered on yourself. I had classified it as the former, and that was the six-week, 109-item error.&lt;/p&gt;

&lt;p&gt;There is one more lesson specific to the era of AI agents. &lt;strong&gt;An agent, with no malice at all, effortlessly reaches the speed and volume that platforms treat as hostile.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Individual edits are careful precisely because an agent does them — referenced, constraint-check PASS. With that per-edit compliance intact, the account grew into a 109-item "web pointing at one person" over six weeks, and on the final night it was writing at a speed pinned against the rate limit.&lt;/p&gt;

&lt;p&gt;The only human decisions made that night were approving "OK to run the bulk sync?" and choosing to resume after the errors. Nowhere in this incident is there a human who willfully broke the rules.&lt;/p&gt;

&lt;p&gt;So what I want you to take away is not "a procedure for safely bulk-writing to external platforms." The conclusion is: &lt;strong&gt;don't&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Beyond that, if you operate agents that write to external services, put these five questions in your agent's rule files — so the work of good faith can stop before it grows into a web of promotion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] If this account's history plus today's writes are viewed together, what do they look like to an administrator seeing them for the first time?&lt;/li&gt;
&lt;li&gt;[ ] Are the write targets skewed toward yourself, your works, and references to you?&lt;/li&gt;
&lt;li&gt;[ ] Did the approval state the count and scope explicitly (or did one decision dilute N items)?&lt;/li&gt;
&lt;li&gt;[ ] Is there a predefined stop condition for when rate-limit errors start appearing?&lt;/li&gt;
&lt;li&gt;[ ] Have you enumerated what breaks if this entire layer gets revoked?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the final question is this: &lt;strong&gt;the place you are about to write to — which side is it on?&lt;/strong&gt; When in doubt, imagine what that platform's administrator would see upon opening your account history.&lt;/p&gt;

&lt;p&gt;My footing now rests on layers under my own control. Whether anything gets built on top of that is for others to decide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228/authorship-strategy/blob/main/docs/adr/0021-self-sovereign-entity-grounding.md" rel="noopener noreferrer"&gt;ADR-0021: Self-Sovereign Entity Grounding&lt;/a&gt; — the actual design decision record extracted from this failure&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.wikidata.org/wiki/User_talk:Shimo4228" rel="noopener noreferrer"&gt;The apology post (User talk:Shimo4228)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2311.09735" rel="noopener noreferrer"&gt;GEO: Generative Engine Optimization (Aggarwal et al., KDD 2024)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.wikidata.org/wiki/Wikidata:Autobiography" rel="noopener noreferrer"&gt;Wikidata:Autobiography&lt;/a&gt; / &lt;a href="https://www.wikidata.org/wiki/Wikidata:Notability" rel="noopener noreferrer"&gt;Wikidata:Notability&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228" rel="noopener noreferrer"&gt;Author's GitHub&lt;/a&gt; — index of the research repositories&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>wikidata</category>
      <category>postmortem</category>
      <category>geo</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>What Humans Should Approve Is Intent, Not the Diff — A Decision Table for Agent Approval Gates</title>
      <dc:creator>shimo4228</dc:creator>
      <pubDate>Wed, 29 Jul 2026 03:41:20 +0000</pubDate>
      <link>https://dev.to/shimo4228/what-humans-should-approve-is-intent-not-the-diff-a-decision-table-for-agent-approval-gates-1a3j</link>
      <guid>https://dev.to/shimo4228/what-humans-should-approve-is-intent-not-the-diff-a-decision-table-for-agent-approval-gates-1a3j</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What this article covers&lt;/strong&gt;: How to catch drift from your intent &lt;strong&gt;while it's still cheap to undo&lt;/strong&gt; (just before commit or publish) without slowing your agent's autonomous execution down. You get a &lt;strong&gt;decision table that mechanically determines, from the kind of thing being changed&lt;/strong&gt;, whether the gate should show a human the full diff text or just an intent summary — plus &lt;strong&gt;the one required item that keeps the summary honest: a three-valued &lt;code&gt;Divergence from plan&lt;/code&gt; declaration&lt;/strong&gt;. It ports directly into your own workflow or your team's code review conventions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As you extend how far an agent runs on its own, you eventually hit this fork.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lean toward not stopping, and drift from intent only becomes visible after the fact.&lt;/strong&gt; The thing works, but it's pointed the wrong way. By the time you notice, it has piled up and you can't afford to unwind it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lean toward stopping, and the human becomes the bottleneck.&lt;/strong&gt; A review queue forms, and the point of autonomous execution evaporates&lt;/li&gt;
&lt;li&gt;So you compromise: "let's at least show the diff." &lt;strong&gt;This is the worst of the three.&lt;/strong&gt; You're paying the cost of stopping, but the volume means nobody reads it, and intent still isn't protected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I suspect the third one is the most widespread. The gate survives as a formality, and in substance you're back to the first case.&lt;/p&gt;

&lt;p&gt;Here's what to re-examine. &lt;strong&gt;Who is that automation for?&lt;/strong&gt; If going fast is itself the goal, humans are in the way — but what you actually want is for &lt;em&gt;the thing you intended&lt;/em&gt; to get built fast. If so, what to cut is not human involvement itself, but &lt;strong&gt;what the human is involved in&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So this article &lt;strong&gt;keeps the number of times a human is stopped the same, and moves only the layer they judge at&lt;/strong&gt;. Whether the artifact is correct becomes the machine's primary responsibility, and the human holds the layer above it: what this is aiming at, and what changes as a result. That changes what the gate shows. That routing is the decision table below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assumptions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The examples come from Claude Code 2.1.220 plus my own harness (the set of rules and skills under &lt;code&gt;~/.claude&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;But the deliverable is &lt;strong&gt;a way of writing conventions&lt;/strong&gt;, so it isn't tool-dependent. It works the same in Cursor, in Devin, or in your company's code review policy&lt;/li&gt;
&lt;li&gt;Background assumed: you have an AI agent implement things, and you have some approval step before commit&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Gates have two axes
&lt;/h2&gt;

&lt;p&gt;When you design an approval gate, there are actually two independent questions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Axis&lt;/th&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;What decides it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;First axis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;When&lt;/strong&gt; do you stop&lt;/td&gt;
&lt;td&gt;Reversibility. An edit you can undo with &lt;code&gt;git checkout&lt;/code&gt; doesn't stop; publishing externally, minting a DOI, or committing does&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Second axis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;When you've stopped, &lt;strong&gt;what&lt;/strong&gt; does the human judge&lt;/td&gt;
&lt;td&gt;The kind of target (the subject of this article)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Usually only the first axis is settled. "Stop before commit." "Confirm before publishing." — &lt;strong&gt;the stop condition is written down, but what to show when you stop isn't.&lt;/strong&gt; My own harness was like that.&lt;/p&gt;

&lt;p&gt;That blank doesn't stay blank. Every time you write a gate, it gets filled in by whatever interpretation is handy at that moment, and the default it fills in with is almost always "show the diff." From the writer's side that looks safest. Show everything and nothing gets missed. The result is the third case from the opening, mass-produced.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision table: what you show is decided by the target
&lt;/h2&gt;

&lt;p&gt;This is the deliverable. &lt;strong&gt;When a gate stops, what you put in front of the human is determined by the kind of thing being changed.&lt;/strong&gt; (Below, "deterministic gate" means checks a machine can answer Yes/No on: lint, type checking, tests, secret scan.)&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;What to show&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Behavior-shaping artifacts&lt;/strong&gt;&lt;br&gt;(&lt;code&gt;CLAUDE.md&lt;/code&gt; / &lt;code&gt;AGENTS.md&lt;/code&gt; / rules / skill and agent definitions / public documentation)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Full text&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The text itself &lt;em&gt;is&lt;/em&gt; the intent. Reading it is already work at the intent layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Control plane&lt;/strong&gt;&lt;br&gt;(hooks / permission settings / permission definitions like &lt;code&gt;--allowedTools&lt;/code&gt; / scheduled task definitions)&lt;br&gt;&lt;strong&gt;and artifacts that produce the evidence&lt;/strong&gt; the checks run on (tests / fixtures / lint config / coverage thresholds / CI definitions / review agent prompts / dependencies)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Full text&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;These move the gate itself, and &lt;strong&gt;the evidence the gate rests on&lt;/strong&gt;. Fold them into a summary and "a change that loosens the checks" disappears&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Implementation code and generated output&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Intent summary&lt;/strong&gt;&lt;br&gt;(what it aims at, and what changes as a result)&lt;/td&gt;
&lt;td&gt;Correctness that mechanized checks can decide belongs to deterministic gates and review agents. The diff text and the PASS list don't go on the approval screen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;When a deterministic gate FAILs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;The detection line itself&lt;/strong&gt;&lt;br&gt;(mask only the secret's actual value)&lt;/td&gt;
&lt;td&gt;This is a state absent from the approved plan, so there's nothing to fold a summary into (the cross-check mechanism is described later). Bypassing it is a decision to disable one check, which puts it in the same class as the control plane&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Escalation rule: irreversibility overrides the target category
&lt;/h3&gt;

&lt;p&gt;The table is a &lt;strong&gt;principle&lt;/strong&gt;, and it doesn't decide everything on its own. Even within "implementation code," padding tweaks in a UI and changes to authorization logic, DB migrations, billing, data deletion, or key rotation call for different things to hand a human. So add a one-line override.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;For irreversible, high-impact changes, show the full text (or the relevant diff) regardless of target category.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Which is to say the first axis (reversibility) affects not only "when you stop" but also "what you show." &lt;strong&gt;The principle is decided by the kind of target, and irreversibility escalates it toward showing full text&lt;/strong&gt; — that two-stage form is the accurate statement. No third axis is needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  In one line
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Correctness that mechanized checks can decide belongs to the machine; the human holds intent.&lt;/strong&gt; The one exception is targets where the text &lt;em&gt;is&lt;/em&gt; the intent — there, reading the text is itself the intent judgment, so you show the text. &lt;strong&gt;Showing full text isn't a backslide into the old way; it's the same principle showing up differently.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There's one more principle you need alongside the decision table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A review agent is an inspector, not an approver.&lt;/strong&gt; If the side proposing and the side inspecting come from the same lineage, the inspection inherits the proposer's blind spots wholesale (this article calls that the generator–verifier gap). So approval is composed of two things, "the deterministic gate PASSed" plus "the human's intent judgment," and &lt;strong&gt;you never build a path where approval closes on an LLM alone&lt;/strong&gt;. Making review heavier does not mean approval can be delegated to an LLM.&lt;/p&gt;

&lt;p&gt;Written into a conventions file, it looks like this (excerpted from the real thing; internal links and some references are omitted).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Artifacts are the machine's, intent is the human's

Correctness that mechanized checks can decide belongs to deterministic gates and review agents;
**do not put humans back on artifact inspection** (an assignment of primary responsibility, not a
guarantee — residual risk is caught by the escalation rule). A review agent is **an inspector, not
an approver** (generator–verifier gap). Approval is composed of "the deterministic gate PASSed"
plus "the human's intent judgment"; **never create an approval path that closes on an LLM alone.**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Applying it: what was actually happening in five places
&lt;/h2&gt;

&lt;p&gt;Before I made the decision table, my harness had the second axis blank. What follows is my own environment, but &lt;strong&gt;any setup with the same blank will produce the same shape&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The same gate had two competing sources of truth
&lt;/h3&gt;

&lt;p&gt;For the intervention point just before commit, two files said different things. Nowhere was it written which one was canonical (i.e. which one you ultimately follow).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;What it said&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rules/common/planning.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"&lt;strong&gt;Verify results check&lt;/strong&gt; — just before commit" (what to show was unspecified)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;skills/implementation-chain/SKILL.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"&lt;strong&gt;diff approval&lt;/strong&gt; just before publish / deposit / commit"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The origin was a single sentence I'd written earlier in a record of a different design decision.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I just do a final check before commit&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It doesn't say what the "final check" is &lt;strong&gt;of&lt;/strong&gt;. Downstream, that sentence got read as "diff approval," and settled in wearing the face of a canonical rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  "A human inspects the artifact" was scattered across five places
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Wording before&lt;/th&gt;
&lt;th&gt;Result of applying the decision table&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;implementation-chain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"diff approval just before commit"&lt;/td&gt;
&lt;td&gt;Implementation code → changed to &lt;strong&gt;intent summary&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;readme-writer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"a human approves the diff and applies it"&lt;/td&gt;
&lt;td&gt;Public documentation → rewritten as &lt;strong&gt;show full text&lt;/strong&gt; (same target, different reason)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;release-doi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"&lt;strong&gt;visually confirm&lt;/strong&gt; the latest commit is newer than the previous release"&lt;/td&gt;
&lt;td&gt;A predicate a machine can check → &lt;strong&gt;hand it to the machine&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;paper-deposit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Open the generated PDF's first page and &lt;strong&gt;eyeball&lt;/strong&gt; it (paraphrased)&lt;/td&gt;
&lt;td&gt;Same → replaced with machine verification of font embedding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;harness-sync&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"&lt;code&gt;git diff&lt;/code&gt; is the review gate"&lt;/td&gt;
&lt;td&gt;What's being synced is rules / skills → &lt;strong&gt;leave it as is&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those five were written on different days, for different purposes. &lt;strong&gt;All of them landed on the same default with no coordination&lt;/strong&gt;, so this isn't individual carelessness. Wherever there's a blank, "show the human everything" fills it. Which is why patching the individual wording wouldn't stop it — I needed one convention covering the second axis itself.&lt;/p&gt;

&lt;p&gt;And that rightmost column shows where the decision table earns its keep. Grep and all five look identical, but &lt;strong&gt;wiping them out uniformly would take out gates you need&lt;/strong&gt;. &lt;code&gt;harness-sync&lt;/code&gt; did end up staying: what it syncs is rules and skills, so reading the text is itself the intent judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reinforcement 1: the control plane and "artifacts that produce the evidence"
&lt;/h2&gt;

&lt;p&gt;In the first version of the decision table, I put hooks and permissions on the "implementation code and config" side. A review from an external model (Codex CLI) hit exactly there as its top-priority finding.&lt;/p&gt;

&lt;p&gt;Codex's finding&lt;/p&gt;

&lt;p&gt;&amp;gt; The central target boundary permits executable harness controls to be hidden behind proposer-authored summaries, and the new rule conflicts with the existing security false-positive workflow.&lt;br&gt;
&amp;gt; - [P1] Classify executable harness controls as behavior-shaping — …&lt;/p&gt;

&lt;p&gt;Suppose the agent disabled the pre-commit secret scan hook. It can &lt;strong&gt;follow the convention to the letter&lt;/strong&gt; and still summarize it like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Intent summary: Tidied up the pre-commit check configuration and strengthened the checks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The control plane "moves the gate itself," so folding it into a summary turns &lt;strong&gt;a change that loosens the checks into a well-meaning sentence&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And hooks and permissions aren't the only way to weaken things. &lt;strong&gt;Rewriting the tests so they pass against your implementation&lt;/strong&gt; is the quietest path of all. Here the agent doesn't even need to lie.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Intent summary: Fixed the implementation; all tests PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's exactly true. The one thing that never surfaces in the summary is that what got rewritten was the tests. &lt;strong&gt;Not just "the tests passed" but "what counts as passing" is under review.&lt;/strong&gt; Fixtures, coverage thresholds, lint config, CI definitions, review agent prompts, and dependencies all have the same property.&lt;/p&gt;

&lt;h3&gt;
  
  
  So how should you present it
&lt;/h3&gt;

&lt;p&gt;The answer is: &lt;strong&gt;don't choose summary-or-text per commit&lt;/strong&gt;. If one commit contains both implementation and evidence, &lt;strong&gt;show the summary, and alongside it show the full text only for the parts that are evidence&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Intent summary: Changed the login-failure retry limit from 5 to 3 (plan step 2)

⚠ This change touches the evidence the checks rest on — review the text below

  tests/test_auth.py:41
  -    assert client.retry_limit == 5
  +    assert client.retry_limit == 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The human reads only those last two lines. The implementation-side diff isn't shown. &lt;strong&gt;All you have to look at is whether the tests were loosened to match the implementation&lt;/strong&gt;, so volume can't beat you. The example above corresponds to a change decided in the plan, so it goes through. If the plan said nothing about retries and only the test threshold dropped, that's where you stop.&lt;/p&gt;

&lt;p&gt;Which files are on the evidence side can usually be picked up from the path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Extract candidates for "produces the evidence" from changed files, to show as full text&lt;/span&gt;
git diff &lt;span class="nt"&gt;--cached&lt;/span&gt; &lt;span class="nt"&gt;--name-only&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'^(tests?/|\.github/workflows/|\.claude/|hooks/)|(_test\.|\.test\.|pyproject\.toml|package\.json|ruff\.toml|codecov\.yml)'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;This is not a complete classifier; it's a conservative candidate extractor.&lt;/strong&gt; It misses tests embedded in ordinary code, custom directories, lockfiles, and inline snapshots. Add the evidence-side files you know about in your own repo and grow it.&lt;/p&gt;

&lt;p&gt;In my environment, this extraction has moved out of the convention's prose and into a &lt;strong&gt;PreToolUse hook&lt;/strong&gt; (&lt;code&gt;evidence-file-notice.sh&lt;/code&gt;). When it detects a &lt;code&gt;git commit&lt;/code&gt;, it scans the staged files against a pattern like the one above (the real list is a bit broader — it also covers &lt;code&gt;spec/&lt;/code&gt;, &lt;code&gt;conftest.py&lt;/code&gt;, pre-commit config, and so on), and if anything matches, it injects the instruction itself into the approval flow: show the human the diff of these files. The canonical home of the evidence-file enumeration is no longer the convention's text but that one regex in the hook; the convention keeps a single pointer line saying detection is canonical there.&lt;/p&gt;

&lt;p&gt;There are two reasons for this restructuring.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A convention that exists only in a document is followed only probabilistically.&lt;/strong&gt; Write "evidence-side files get their text shown" into a convention and there's no guarantee the agent recalls it every time. "Which files are evidence-side," though, is a structural property decided purely by the shape of the path — a machine fires on it 100% of the time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the enumeration in two places — convention and hook — and they will drift apart.&lt;/strong&gt; Centralize it on the side that fires, and let the convention hold only the principle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Semantic judgments like the decision table stay in the document; anything mechanically decidable gets lowered into a gate as soon as you spot it — this convention itself has been growing by exactly that process.&lt;/p&gt;

&lt;p&gt;The control plane (hooks / permissions / scheduled tasks) works the same way: summary plus the full text of the relevant files. &lt;strong&gt;Anything producing the evidence a judgment rests on gets treated the same as the judgment itself.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reinforcement 2: on FAIL, show the detection line
&lt;/h2&gt;

&lt;p&gt;The second thing that same review found. Push "no machine-check PASS list and no diff text goes to the human" all the way, and &lt;strong&gt;you also delete the path where a human judges a secret scan false positive&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In my harness, when the pre-commit secret scan detects something, there's a workflow to let it through with &lt;code&gt;SECRET_SCAN_BYPASS=1&lt;/code&gt;. The judgment is "this is a dummy key in a test, so it's fine."&lt;/p&gt;

&lt;p&gt;The reason this goes to a human isn't that a machine can't tell the difference. It's that &lt;strong&gt;even if the machine or the review agent happens to be right, this is the kind of decision whose owner must be a human&lt;/strong&gt;. A bypass is the act of disabling one check on the spot. That has the same nature as moving the control plane, so for the same reason as Reinforcement 1, you need the full text (i.e. the detection line).&lt;/p&gt;

&lt;p&gt;There's a second reason: &lt;strong&gt;a FAIL is a state absent from the approved plan.&lt;/strong&gt; An intent summary is meant to be read against the plan (see below), so a state that isn't in the plan has nothing to fold into.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**FAIL is the exception** — a deterministic gate's FAIL presents **the detection line itself**.
Mask the secret's actual value; the owner of the false-positive call (`*_BYPASS`) is the human
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PASS needs care. PASS only means "the checks you configured accepted this." &lt;strong&gt;Whether it was implemented according to the plan is not something PASS can tell you.&lt;/strong&gt; That's exactly why you need a separate intent summary — you leave out the PASS list &lt;em&gt;because&lt;/em&gt; you're looking at the summary instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Not shown" is not "not kept"
&lt;/h3&gt;

&lt;p&gt;This gets misread easily, so let me be explicit. Leaving out the PASS list means &lt;strong&gt;moving it off the human's approval screen&lt;/strong&gt;, not throwing check results away. Delete the trail and you lose any way to trace "what was passing back then" later. The idea in this article isn't to delete information; it's to &lt;strong&gt;move unneeded information off the human's judgment surface&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I originally had this distinction written into the convention as its own clause; the current version drops it. Claude Code already persists the transcript and tool results in machine-readable form at all times, so writing it down merely restated a default the runtime already guarantees. &lt;strong&gt;If your execution environment doesn't guarantee log retention, keep it as an explicit clause.&lt;/strong&gt; "Don't write into the convention what the substrate already guarantees" is itself one of the judgment calls you'll make when porting this.&lt;/p&gt;

&lt;p&gt;One more thing: on a true positive, dumping the detection line as-is duplicates the secret's actual value into the conversation, the approval screen, and the logs. That would spread the very leak the gate exists to prevent, so &lt;strong&gt;show the file, the rule name, and the surrounding context, and mask only the value&lt;/strong&gt;. What a human needs to judge a false positive is not the value itself, but where it was and which rule it hit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reinforcement 3: don't let the intent summary be free-form
&lt;/h2&gt;

&lt;p&gt;This is a prerequisite for putting the decision table into practice.&lt;/p&gt;

&lt;p&gt;If you stop at "implementation code gets an intent summary," the human ends up &lt;strong&gt;judging by reading a self-report written by the proposer itself&lt;/strong&gt;. That just relocates the gap that was at the artifact layer. What they read changed from a diff to an essay; the author is the same.&lt;/p&gt;

&lt;p&gt;The countermeasure is to &lt;strong&gt;fix what the summary is cross-checked against to something human-originated&lt;/strong&gt;. My harness has two intervention points.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Plan check&lt;/strong&gt; — when what to do has been settled (this is where the human approves)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intent check&lt;/strong&gt; — just before commit&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And the summary in 2 is &lt;strong&gt;presented cross-checked against the plan the human approved in 1&lt;/strong&gt;. Because the referent is a human-approved object, the loop can't close on self-reporting alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Only one item is required: the &lt;code&gt;Divergence from plan&lt;/code&gt; declaration
&lt;/h3&gt;

&lt;p&gt;But "write it against the plan" still leaves room to write it conveniently. In particular, &lt;strong&gt;when something new is discovered during implementation and the work naturally diverges from the plan&lt;/strong&gt;, that divergence quietly disappears from the summary.&lt;/p&gt;

&lt;p&gt;My first countermeasure was to make the intent summary itself a fixed form: five mandatory headings — approved intent, what changed, divergence from plan, impact on users and operations, evidence-side changes. But the day after that form went into the convention, an audit re-examining the whole convention clause by clause forced me to re-sort those five. &lt;strong&gt;Only one of the fields was actually preventing the worst case — the silent disappearance of divergence.&lt;/strong&gt; This is all that's required now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Intent summary: Changed the login-failure retry limit from 5 to 3 (plan step 2)
Divergence from plan: None
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Divergence from plan&lt;/code&gt; takes one of three values: &lt;code&gt;None&lt;/code&gt; / &lt;code&gt;Yes&lt;/code&gt; / &lt;code&gt;Needs re-approval&lt;/code&gt;. The plan changing because of something you discovered mid-implementation isn't bad in itself. What's dangerous is the fact that it changed disappearing from the summary.&lt;/p&gt;

&lt;p&gt;And making this declaration required &lt;strong&gt;turns omission into falsehood&lt;/strong&gt;. In free-form text, a summary that doesn't mention the divergence isn't lying — it just didn't bring it up. With the declaration required, writing &lt;code&gt;None&lt;/code&gt; when there is divergence is a falsehood, and writing &lt;code&gt;Yes&lt;/code&gt; points the human's eyes exactly there. It closes the omission escape route structurally, without leaning on the writer's honesty. &lt;strong&gt;This is the one thing only formal enforcement can protect.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So why drop the other four fields? Because they were the summary's &lt;strong&gt;shape&lt;/strong&gt;, not its defense. What was intended, what changed, what's affected — summarizing without dropping those is exactly &lt;strong&gt;the calibration a model should bring to any summary&lt;/strong&gt;: lead with the outcome, report results faithfully, match the response to the question. Impose a fixed form on the parts calibration already covers, and you're forcing five headings onto a one-line config change — the approval screen turns into template skimming.&lt;/p&gt;

&lt;p&gt;That would reproduce, on the summary side, the third case I called the worst at the start: a gate that survives as a form nobody reads.&lt;/p&gt;

&lt;p&gt;(As for the "evidence-side changes" field — the hook from Reinforcement 1 covers it structurally, so there was never a need to make a human write it.)&lt;/p&gt;

&lt;p&gt;Anthropic's context-engineering guidance for the Claude 5 generation (&lt;a href="https://x.com/trq212/status/2080710971228918066" rel="noopener noreferrer"&gt;The new rules of context engineering for Claude 5 models&lt;/a&gt;) states this as a general rule: shift from binding with rules to delegating to judgment — &lt;strong&gt;except in the regions where the worst case is unacceptable, which stay explicitly bound&lt;/strong&gt;. The 5→1 reduction is that rule applied. Delegate the summary's shape to the model's calibration; bind exactly one point with form — the disappearance of divergence, the worst case you can't accept.&lt;/p&gt;

&lt;p&gt;The whole point of this gate convention was to concentrate the human's cognitive budget on judging intent. &lt;strong&gt;If the convention itself burns the writer's and reader's attention on enforcing a form, that defeats the purpose.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One more practical upside: the three values are an enumeration, machine-readable, so "does the declaration exist at all" can itself be lowered into a hook check later. Same as Reinforcement 1 — build the convention so it has an exit ramp down into a deterministic gate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cap the number of stops too: one gate per unit of work
&lt;/h3&gt;

&lt;p&gt;Paired with what to show, fix one rule about how often. The intent check happens &lt;strong&gt;once, at the completion point of a unit of work&lt;/strong&gt;. If a commit, a push, a publish, and the accompanying doc updates all arrive together, bundle them into &lt;strong&gt;one decision with the count and scope explicitly enumerated&lt;/strong&gt;. Ask for approval at every intermediate phase or intermediate commit and the approvals themselves become noise — the gate degrades into formality. There are exactly two exceptions: a deterministic gate FAILing, and &lt;code&gt;Divergence from plan: Needs re-approval&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But "bundling is allowed" is not "implicit is allowed." If one decision covers N items, say N at approval time. The moment an approval granted for one item gets silently reused for N, it has stopped being a gate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Raise the granularity and the bottleneck moves
&lt;/h3&gt;

&lt;p&gt;This cross-check bites hardest when the agent is good. &lt;strong&gt;Things going well for a long stretch is exactly the condition under which the intent summary becomes a formality.&lt;/strong&gt; If you find yourself skimming past the same "no divergence" every time, raise the granularity of the plan you're checking against.&lt;/p&gt;

&lt;p&gt;But that isn't free. The finer the plan, &lt;strong&gt;the further the bottleneck moves from just-before-commit to plan approval&lt;/strong&gt;. Reserve the finer granularity for the things you can't take back when they drift (external publishing, data migration, permission changes). You don't get to erase the fork entirely — the accurate statement is that &lt;strong&gt;you get to choose where you pay&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On the conventions side, I also renamed the second intervention point from "Verify results check" to "intent check." Leave the name as "Verify results" and it drags you back into making humans read a list of machine-check results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Porting it into your own conventions: three steps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enumerate your existing approval steps and check what each one says to present.&lt;/strong&gt; Grep for wording like "approve the diff," "visually confirm," "review and apply"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put the decision table and the escalation rule into a single conventions file, and have each place you found point at it.&lt;/strong&gt; If you only patch the individual wording, the next skill or document you write will fill the blank the same way again&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the &lt;code&gt;Divergence from plan&lt;/code&gt; declaration (&lt;code&gt;None&lt;/code&gt; / &lt;code&gt;Yes&lt;/code&gt; / &lt;code&gt;Needs re-approval&lt;/code&gt;) into the summary as a required item.&lt;/strong&gt; The summary as a whole doesn't need a fixed form. Without this, you can install the decision table and still be verifying the proposer's own self-report&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;In step 1's grep, &lt;strong&gt;watch out for missing word stems&lt;/strong&gt;. I searched for &lt;code&gt;eyeballed&lt;/code&gt; and missed &lt;code&gt;eyeball&lt;/code&gt; (the uninflected stem), so one place survived all the way to the external model's review. Search verbs by stem rather than inflected form, or run &lt;code&gt;grep -i&lt;/code&gt; with several patterns.&lt;br&gt;
For reference, in my environment this change (one new conventions file plus re-pointing references across 13 related files) came to 14 files and 217 added lines. The decision table itself is short; what made the difference was &lt;strong&gt;re-wiring everything that referenced it&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The convention's body has since been cut in half (the text auto-loaded into every session's context went from 265 to 125 words). What remains is only the principles — the decision table, the escalation rule, the divergence declaration. The "why" moved into the decision record (ADR), and the evidence-file enumeration into the hook. Left alone, a convention fattens on rationale and examples until nobody reads it. &lt;strong&gt;Principles in the document, why in the record, anything machine-decidable in a gate&lt;/strong&gt; — splitting the storage that way is what keeps it maintainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The limits of "the machine holds it"
&lt;/h2&gt;

&lt;p&gt;Let me be straight about this. &lt;strong&gt;Build, types, lint, tests, and secret scan tell you only about the properties they cover.&lt;/strong&gt; Holes in authorization logic, concurrency races, irreversible side effects of a migration, requirements that were never implemented at all — all of that sails through as PASS. And the review agent, being from the same lineage as the proposer, shares its blind spots (the generator–verifier gap above).&lt;/p&gt;

&lt;p&gt;So the third row of the decision table isn't "the machine guarantees correctness" — it's an operational tradeoff that &lt;strong&gt;puts primary responsibility for artifact inspection on the machine&lt;/strong&gt;. Residual risk does not go to zero.&lt;/p&gt;

&lt;p&gt;Which is exactly why you need the escalation rule. &lt;strong&gt;Keep the human on the artifact side only in the regions you can't undo&lt;/strong&gt; — data migration, permissions and billing, external publishing, deletion. Put another way: if a human is reading diffs of reversible implementation code, that isn't a response to residual risk, it's just inertia.&lt;/p&gt;

&lt;p&gt;You also can't reduce the whole convention to a deterministic lint. Extracting the evidence-side files (the hook in Reinforcement 1) and checking that the divergence declaration exists can be mechanized, but deciding "is this a behavior-shaping artifact" is semantic, and for edge cases like generated documents and config files you have to think each time. The decision table illustrates by enumeration; it doesn't exhaust the space.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to the opening question
&lt;/h2&gt;

&lt;p&gt;The further you extend autonomous execution, the more drift from intent becomes visible only after the fact. But put the human back on review and the point of autonomy disappears. This looks like a fork &lt;strong&gt;only while you're counting human involvement by number of stops&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Count by content instead and it stops being a fork. Keep the number of stops the same, and move what they read at the stop from the artifact to the intent. &lt;strong&gt;What you cut isn't human involvement — it's the time a human spends re-checking the correctness of artifacts.&lt;/strong&gt; The machine was always better at that, and when a human does it, volume wins and it becomes a formality.&lt;/p&gt;

&lt;p&gt;To the question of who the automation is for, this is the answer. &lt;strong&gt;Not to go fast, but so that what you intended gets built fast.&lt;/strong&gt; If so, there's only one thing the human should hold to the end. Intent. The decision table was a tool for taking everything else away from them.&lt;/p&gt;

&lt;p&gt;When you're talking about reducing human review, what's left isn't the work you forgot to cut. It's the work you can't cut.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228/claude-harness" rel="noopener noreferrer"&gt;claude-harness&lt;/a&gt; — my public harness, including the conventions file discussed here (&lt;code&gt;rules/common/human-gate.md&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228/agent-knowledge-cycle" rel="noopener noreferrer"&gt;agent-knowledge-cycle&lt;/a&gt; — the theory-side repository on how far to hand things to the machine and where human approval starts. The conventions in this article are one implementation of it&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://doi.org/10.5281/zenodo.20578272" rel="noopener noreferrer"&gt;Harness Alignment and Harness Drift&lt;/a&gt; — a paper on where approval gates sit&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228" rel="noopener noreferrer"&gt;github.com/shimo4228&lt;/a&gt; — other repositories&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>claudecode</category>
      <category>aiagents</category>
      <category>devops</category>
      <category>ai</category>
    </item>
    <item>
      <title>Opus 5 Changed How Rules Should Be Written — Audit Yours</title>
      <dc:creator>shimo4228</dc:creator>
      <pubDate>Tue, 28 Jul 2026 11:38:58 +0000</pubDate>
      <link>https://dev.to/shimo4228/opus-5-changed-how-rules-should-be-written-audit-yours-4fb4</link>
      <guid>https://dev.to/shimo4228/opus-5-changed-how-rules-should-be-written-audit-yours-4fb4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What this article covers&lt;/strong&gt;: Anthropic's official guidance for the Claude 5 generation (Opus 5 / Fable 5) is "fewer rules, trust the model's judgment." This is a procedure for auditing the custom rules (CLAUDE.md / rules files) you accumulated for older model generations against what the product itself loads, and deciding for each rule: keep, fix, or retire.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you've been using Claude Code for a while, you probably share these worries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You wrote CLAUDE.md files and rules to compensate for older models' weaknesses, and you have no idea whether they still help with Claude 5&lt;/li&gt;
&lt;li&gt;The official guidance now says "cut your rules," but you can't tell &lt;strong&gt;which&lt;/strong&gt; of your rules are the ones to cut&lt;/li&gt;
&lt;li&gt;Deleting everything at once and watching behavior degrade is scary, so you don't touch any of it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On 2026-07-25 I ran this audit on my own &lt;code&gt;~/.claude&lt;/code&gt; harness (my set of custom configuration: CLAUDE.md, rules files, agent definitions) and cut the resident rules — the part auto-loaded into every session — from 5,789 down to 2,463 words (measurement details at the end). This article documents the cross-checking procedure I used, in a reproducible form. The takeaway is not the reduction itself but &lt;strong&gt;the decision procedure: on what evidence, which rules, and how to dispose of them&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background: the official guidance on rule-writing changed
&lt;/h2&gt;

&lt;p&gt;On 2026-07-24 (the day Opus 5 launched), Anthropic published &lt;a href="https://claude.com/blog/the-new-rules-of-context-engineering-for-claude-5-generation-models" rel="noopener noreferrer"&gt;The new rules of context engineering for Claude 5 generation models&lt;/a&gt;. Two points matter here.&lt;/p&gt;

&lt;p&gt;First, Anthropic itself deleted more than 80% of Claude Code's system prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We removed over 80% of Claude Code's system prompt for models like Claude Opus 5 and Claude Fable 5 with no measurable loss on our coding evaluations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Second, the post spells out the shift in context engineering as six Then → Now pairs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Then (older generations)&lt;/th&gt;
&lt;th&gt;Now (Claude 5 generation)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Give rules&lt;/td&gt;
&lt;td&gt;Trust judgment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Give examples&lt;/td&gt;
&lt;td&gt;Design interfaces&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Front-load everything&lt;/td&gt;
&lt;td&gt;Progressive disclosure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeat yourself&lt;/td&gt;
&lt;td&gt;Simple tool descriptions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write memory into CLAUDE.md&lt;/td&gt;
&lt;td&gt;Auto-memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sparse specs&lt;/td&gt;
&lt;td&gt;Rich reference material&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The post names a concrete harm of over-instruction: &lt;strong&gt;conflicting instructions&lt;/strong&gt;. System prompt, skills, and user instructions collide, so that "leave documentation as appropriate" and "DO NOT add comments" &lt;strong&gt;coexist in the same request&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In other words, the reason to cut rules is not just token savings. The problem is that &lt;strong&gt;your custom rules conflict with the product's own instructions, and you are forcing the model to resolve the contradiction&lt;/strong&gt;. From here, the procedure is about finding which of your rules are the conflicting ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Premise: the ground truth is the system prompt and tool descriptions
&lt;/h2&gt;

&lt;p&gt;What actually reaches the model in the same context as your custom rules is the &lt;strong&gt;system prompt and the tool descriptions&lt;/strong&gt;. That is the ground truth to audit against, and the first step is simply to read it. Official docs and blog posts state recommendations, but they are not loaded at inference time.&lt;/p&gt;

&lt;p&gt;The meaning of a mismatch changes with this distinction, so let's name the two layers.&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;Contents&lt;/th&gt;
&lt;th&gt;Loaded at inference time?&lt;/th&gt;
&lt;th&gt;Name of the mismatch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runtime layer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;System prompt + tool descriptions&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Yes&lt;/strong&gt; (loaded into context alongside your rules)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Conflict&lt;/strong&gt; (the model is forced to resolve a contradiction)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Guidance layer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Official docs and blog posts&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Drift&lt;/strong&gt; (you've merely diverged from official recommendations)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A mismatch only qualifies as a conflict when both sides are in the same context (a tool description is only loaded in sessions where that tool is available).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All examples below come from my own &lt;code&gt;~/.claude&lt;/code&gt; harness (Claude Code 2.1.220, audited 2026-07-25 to 07-26).&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: capture the current runtime layer from a live session
&lt;/h2&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first task is to know exactly what your custom rules are sharing context with. There is a trap here: &lt;strong&gt;the runtime layer is injected from outside your config repository too, so no amount of reading &lt;code&gt;~/.claude&lt;/code&gt; shows you the whole picture.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A concrete example. My session's system prompt contained this resident line:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Do not call the AgentTool unless the user requested it"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yet grepping &lt;code&gt;~/.claude&lt;/code&gt; finds this string in no file at all (I haven't identified the source; I suspect the harness itself or a plugin, but couldn't confirm). If you only audit config files, you miss this class of instruction.&lt;/p&gt;

&lt;p&gt;So capture from a live session instead. There are two techniques.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For tool descriptions, get the real thing.&lt;/strong&gt; Ask Claude Code to print the target tool's description:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Quote the description of the EnterPlanMode tool verbatim. Do not summarize.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;For the system prompt, have the session quote itself.&lt;/strong&gt; This is self-reported, so say "verbatim" explicitly to prevent summarizing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;From the system prompt currently loaded, quote verbatim the instructions
about plan mode / commit messages / scope handling.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trick is to ask per theme. "Print everything" is too long and summaries creep in. Build the theme list &lt;strong&gt;from your own rules&lt;/strong&gt;: open every custom rules file (rules / CLAUDE.md) first, assign each instruction to a theme (planning, commits, review, scope, ...), then query the runtime layer theme by theme. That way you at least eliminate audit gaps on your side of the comparison.&lt;/p&gt;

&lt;p&gt;Two caveats. First, gaps in the other direction — runtime instructions you don't know exist — can't be fully caught by this procedure, so zero detections means "this question found nothing," not "no conflicts." Second, &lt;strong&gt;both capture techniques are model-mediated self-reports, with no guarantee of exactly matching the actual input&lt;/strong&gt;. Treat the captures as a screening pass; before acting on a rule's disposition (retire / invert), confirm the same wording reproduces in a separate session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: cross-check against your rules and sort into 3 categories
&lt;/h2&gt;

&lt;p&gt;Cross-check the captured runtime layer against your custom rules and sort each mismatch into three categories.&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;Meaning&lt;/th&gt;
&lt;th&gt;Detected in my harness&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Conflict&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An instruction or claim that contradicts the runtime layer is loaded at the same time&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Redundant&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The runtime layer already says nearly the same thing&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Drift&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Diverged from guidance-layer (official docs) recommendations&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Conflict example 1: a no-plan-mode rule
&lt;/h3&gt;

&lt;p&gt;My &lt;code&gt;planning.md&lt;/code&gt; contained this, written as a countermeasure to older generations' habit of not planning:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On direct implementation instructions, execute immediately. Do not enter plan mode.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Meanwhile, the &lt;code&gt;EnterPlanMode&lt;/code&gt; tool description loaded in the same context (Claude Code 2.1.220) says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Prefer using EnterPlanMode for implementation tasks unless they're simple."&lt;br&gt;
"If unsure whether to use it, err on the side of planning"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two instructions pointing in exactly opposite directions were loaded into the same context, and the model was silently picking one every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conflict example 2: a rule referencing a ghost setting
&lt;/h3&gt;

&lt;p&gt;The second one was a nastier pattern. Background: by default, Claude Code appends a &lt;code&gt;Co-Authored-By: Claude ...&lt;/code&gt; trailer to commit messages — the attribution that shows Claude as a co-author on GitHub. On whether to emit that attribution, my &lt;code&gt;git-workflow.md&lt;/code&gt; said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Attribution disabled globally via ~/.claude/settings.json.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But checking the actual &lt;code&gt;settings.json&lt;/code&gt;, the relevant key (&lt;code&gt;includeCoAuthoredBy&lt;/code&gt;) &lt;strong&gt;did not exist&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import json; print('&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;'.join(sorted(json.load(open('settings.json')).keys())))"&lt;/span&gt;
&lt;span class="c"&gt;# includeCoAuthoredBy is not in the output&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whether I deleted the setting at some point or the note was wrong from day one, there's no way to tell anymore. What I can say for certain: &lt;strong&gt;a rule cannot detect that its own justification has vanished&lt;/strong&gt;. Code errors out when its referent disappears; a rule keeps asserting "disabled" while sitting in context, contradicting the runtime layer's instruction to append the Co-Authored-By trailer to commit messages.&lt;/p&gt;

&lt;p&gt;Strictly speaking, this line is not an instruction ("don't add the trailer") but a (false) statement of fact ("already disabled"). But since it kept riding along in the same context as information contradicting a runtime-layer instruction, I counted it on the conflict side.&lt;/p&gt;

&lt;p&gt;How the unresolved conflict was actually being resolved (a model-dependent observation, with limits)&lt;/p&gt;

&lt;p&gt;Digging through session logs from the period when this ghost-setting conflict sat unresolved, I found an interesting split. Same Claude Code version (2.1.220), same rules: two Opus 5 sessions &lt;strong&gt;added&lt;/strong&gt; the trailer to commits, one Fable 5 session &lt;strong&gt;did not&lt;/strong&gt;. Within each session, behavior was 100% consistent.&lt;/p&gt;

&lt;p&gt;So when you leave contradictory instructions in place, how they get resolved is up to the model — and when the model changes, the behavior can change too.&lt;/p&gt;

&lt;p&gt;The limits, stated plainly: this is a natural observation, not a controlled comparison (5 commits with differing tasks and user instructions). The most I can claim is "likely model-dependent." Also, the observed difference — attribution — is harmless; whether conflicts with real consequences split the same way is unverified.&lt;/p&gt;

&lt;h3&gt;
  
  
  Redundancy example: a scope-adherence rule
&lt;/h3&gt;

&lt;p&gt;Separate from conflicts, some rules had simply become unnecessary to say. My custom rule "when a scope is specified, adhere to it strictly" exists almost verbatim in the Claude 5 generation system prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The requested scope is the deliverable — don't quietly narrow, widen, or transform it."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No collision, so the harm is small — but I was paying resident tokens to repeat what the product already says. It was exactly the official post's "Repeat yourself → Simple tool descriptions" shift.&lt;/p&gt;

&lt;h3&gt;
  
  
  Drift example: a confidence threshold for review
&lt;/h3&gt;

&lt;p&gt;I detected three drift items. I'll cover one representative case here; the other two — the handling of pre-commit verification steps and the rule splitting review into a separate agent — get different verdicts, so they're covered in Step 3 and the pitfalls section respectively.&lt;/p&gt;

&lt;p&gt;The representative case: my review-agent definition said "only report findings you are at least 80% confident in" (a number unrelated to the 80% system prompt reduction above). The &lt;a href="https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/prompting-claude-opus-5" rel="noopener noreferrer"&gt;official Opus 5 prompting guide&lt;/a&gt; warns against exactly this kind of suppression instruction, using it as an example (it does not mention the number 80% specifically):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If your review prompt says "only report high-severity issues" or "be conservative," the model may follow that instruction literally and report less; ask it to report everything and filter in a separate pass instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is not a load-time conflict, but with the official docs explicitly saying the model may follow this kind of instruction literally and under-report, leaving it in place means dropped findings. I haven't verified how much damage it did on older generations, but since it's the exact pattern the current generation's docs warn against by example, I classified it as drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: don't treat "conflict = bad" — run each item through a decision frame
&lt;/h2&gt;

&lt;p&gt;Once the three-way sort is done, decide dispositions. The important part: &lt;strong&gt;do not mechanically mark conflicts and drift for deletion&lt;/strong&gt;. Some of your custom rules were written precisely to override product defaults on purpose. "Points the opposite way" alone cannot distinguish an accident from a deliberate choice.&lt;/p&gt;

&lt;p&gt;I judged each item on four axes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Axis&lt;/th&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Intent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Did you write it to override the product default, or was it simply not conflicting at the time?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Evidence&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Is there a record of why it was written — an ADR, an incident note?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Freshness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Does the product behavior it assumed (an old generation's weakness, etc.) still hold?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Expiry condition&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Have you defined what would trigger revisiting this rule?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three example dispositions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retire (freshness expired)&lt;/strong&gt;: The no-plan-mode rule (conflict example 1) was a countermeasure to older generations' "start running without planning" weakness. In the Claude 5 generation the product itself now recommends planning; the premise is gone. There was no reason to keep it as an intentional override, so I retired it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retire (conflict + redundancy)&lt;/strong&gt;: Beyond the ghost-setting line (conflict example 2), &lt;code&gt;git-workflow.md&lt;/code&gt; contained nothing but content the model and the runtime layer already cover — commit message format, how to write PRs. Fix the conflicting line and the rest is still redundant. So the whole file had no reason to exist, and I retired it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep (intent and evidence alive)&lt;/strong&gt;: On the other hand, I kept my rule "run review in a separate agent process from the implementer." The official guide contains the line "do not use subagents to verify or double-check your own work," which at first glance collides with this rule.&lt;/p&gt;

&lt;p&gt;But read in context, this is not a rejection of division-of-labor review. That line is one phrase in an &lt;strong&gt;example prompt developers can add to control subagent spawning&lt;/strong&gt;, in a section about cost containment. The official premise is that Opus 5 verifies its own work without being told — explicit verification instructions just cause over-verification and should be cut — and that the new generation, which also delegates more, would otherwise reflexively spawn "just in case" double-check subagents and burn cost. That's the brake being illustrated. The same doc also positively cites the writer–verifier pattern — separating the writer from the verifier — as a strength of Opus 5.&lt;/p&gt;

&lt;p&gt;My rule has two tiers. The base: never let review happen in the same context as the implementation (same-context review inherits the implementer's blind spots). On top of that, for important changes, a &lt;strong&gt;different model&lt;/strong&gt; (in my case, Codex CLI) does the review, decorrelating the blind spots. The latter was an easy keep — cross-model review is a capability Claude Code itself structurally cannot provide no matter how much it evolves, so there is nothing for the product to absorb it into. The design decision is recorded in an ADR.&lt;/p&gt;

&lt;p&gt;The former (separate-process review on the same model) may still overlap with the "added verification passes" the official guidance wants to suppress. This is the interpretive part: if Opus 5's self-verification turns out to solve even the blind-spot-inheritance problem, the base tier may become unnecessary. It stays — as a keep with an expiry condition: revisit when the premise changes.&lt;/p&gt;

&lt;p&gt;Running this decision frame taught me one thing: &lt;strong&gt;rules with recorded evidence (ADRs, incident notes) are fast to judge, and the judgments are more confident&lt;/strong&gt;. Having a record doesn't prevent retirement (the plan-mode rule had a known rationale and was retired anyway, on expired freshness). What the record changes is not the outcome but the cost of deciding. For rules with no recorded rationale, even the question "is it safe to delete?" turns into git log archaeology.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfall: mechanically applying the official advice misdiagnoses
&lt;/h2&gt;

&lt;p&gt;The official guide says verification steps added by legacy harness scaffolding are also candidates for removal. Applying that mechanically will misdiagnose.&lt;/p&gt;

&lt;p&gt;My custom rules had an 8-item pre-commit Verify step (build / type check / lint / tests / secret scan / dependency audit / doc sync check / git status check). Taken literally, "cut verification steps" makes all eight look like deletion targets.&lt;/p&gt;

&lt;p&gt;But what the official guidance objects to is &lt;strong&gt;instructions that increase the model's self-verification&lt;/strong&gt;. Most of the eight items are deterministic command runs — builds, tests — that add zero model judgment. The axis is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The verification is done by a &lt;strong&gt;machine&lt;/strong&gt; (command, hook) → fine to keep&lt;/li&gt;
&lt;li&gt;The verification is done by the &lt;strong&gt;model, on its own judgment&lt;/strong&gt; → the official guidance's target; consider cutting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The other pitfall: &lt;strong&gt;cases where deletion isn't enough and you need an inversion&lt;/strong&gt;. If you simply delete the "only report at ≥80% confidence" line, the section it lived in (a noise-suppression framework) survives and keeps pushing in the suppressive direction. I rewrote the section to: "Do not suppress by confidence. Report everything; the caller filters in a separate pass." An instruction whose correct direction has flipped must be rewritten in the opposite direction, not merely removed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results and numbers
&lt;/h2&gt;

&lt;p&gt;The center of gravity is the procedure, but for reference, my harness before and after (measured with &lt;code&gt;wc -w&lt;/code&gt;, resident total of CLAUDE.md + rules):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Point in time&lt;/th&gt;
&lt;th&gt;Resident words&lt;/th&gt;
&lt;th&gt;Files&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Before the audit&lt;/td&gt;
&lt;td&gt;5,789&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After (as of 2026-07-26)&lt;/td&gt;
&lt;td&gt;2,463&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note that I am not claiming "resolving the conflicts improved performance." My environment has no instrument for measuring the cost of contradiction-resolution; the quantitative case rests on Anthropic's measurement quoted at the top (80% deletion, no degradation). What this article demonstrates is not the effect but &lt;strong&gt;real examples of config drift, and a procedure for finding and disposing of it&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary: what you can do next
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Capture the runtime layer (system prompt + tool descriptions) from a live session. Reading your config repository alone doesn't show the whole picture&lt;/li&gt;
&lt;li&gt;Cross-check against your custom rules; sort into conflict / redundant / drift&lt;/li&gt;
&lt;li&gt;Don't treat "conflict = bad" — judge keep / invert / retire on the four axes of intent, evidence, freshness, and expiry condition&lt;/li&gt;
&lt;li&gt;Apply the official reduction advice with the distinction between &lt;strong&gt;model self-verification&lt;/strong&gt; and &lt;strong&gt;machine verification&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And one lesson to add from this round: &lt;strong&gt;when you write a rule, record its rationale and its expiry condition&lt;/strong&gt;. I'll confess I wasn't doing this systematically either. Rules with an ADR (design decision record) took minutes to judge; rules without one turned into git log archaeology; and for the ghost setting, I couldn't even pin down when it disappeared. A rule cannot detect that its own justification has vanished — so whether you'll be able to judge your rules at the next generation change will be decided by the notes you leave now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Postscript (2026-07-26)
&lt;/h2&gt;

&lt;p&gt;The lessons from this article have already flowed back into my harness.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every resident rule file now carries &lt;code&gt;rationale:&lt;/code&gt; and &lt;code&gt;review-when:&lt;/code&gt; metadata, and my rules-audit skill &lt;a href="https://github.com/shimo4228/rules-stocktake" rel="noopener noreferrer"&gt;rules-stocktake&lt;/a&gt; reads these fields during audits&lt;/li&gt;
&lt;li&gt;The procedure in this article (collect the runtime layer → cross-check → judge with the decision frame) has been generalized into a skill called &lt;a href="https://github.com/shimo4228/generation-audit" rel="noopener noreferrer"&gt;generation-audit&lt;/a&gt;, published as a standalone repo, so it can be re-run as-is at the next model generation change&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://claude.com/blog/the-new-rules-of-context-engineering-for-claude-5-generation-models" rel="noopener noreferrer"&gt;The new rules of context engineering for Claude 5 generation models&lt;/a&gt; — Anthropic official blog (2026-07-24)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/prompting-claude-opus-5" rel="noopener noreferrer"&gt;Prompting Claude Opus 5 — Claude Platform Docs&lt;/a&gt; — official recommendations on confidence thresholds, verification steps, and subagents&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228/generation-audit" rel="noopener noreferrer"&gt;generation-audit&lt;/a&gt; — this article's procedure packaged as a skill (standalone repo)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228/rules-stocktake" rel="noopener noreferrer"&gt;rules-stocktake&lt;/a&gt; — resident-rules audit skill (now reads rationale / review-when metadata)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228" rel="noopener noreferrer"&gt;github.com/shimo4228&lt;/a&gt; — the author's GitHub (harness-related repositories)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>claudecode</category>
      <category>contextengineering</category>
      <category>promptengineering</category>
      <category>anthropic</category>
    </item>
    <item>
      <title>claude-security Beta: Cost, Output, Accuracy — 189 Agents, 2 Hours, Measured</title>
      <dc:creator>shimo4228</dc:creator>
      <pubDate>Mon, 27 Jul 2026 13:00:04 +0000</pubDate>
      <link>https://dev.to/shimo4228/claude-security-beta-cost-output-accuracy-189-agents-2-hours-measured-1eji</link>
      <guid>https://dev.to/shimo4228/claude-security-beta-cost-output-accuracy-189-agents-2-hours-measured-1eji</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What this article covers&lt;/strong&gt;: A measured run of Anthropic's official security-scanning plugin &lt;code&gt;claude-security&lt;/code&gt; (beta). What the tool does, how long it takes, what output it returns, and how far you can trust it — backed by the raw data from the run's own artifacts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You may know the official plugin exists and still not have tried it, for reasons like these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The cost is unreadable.&lt;/strong&gt; It warns you at startup that it "may take a while and use a significant number of tokens," but never says how much&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The output quality is unknown.&lt;/strong&gt; How is this different from existing static analysis, and does an LLM that only reads code produce findings you can act on?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's unclear what to do afterward.&lt;/strong&gt; If dozens of findings come back, where do you start?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On 2026-07-25 I installed v0.10.0 and scanned my entire &lt;code&gt;~/.claude&lt;/code&gt; configuration set (hooks, skills, agents, permission settings). &lt;strong&gt;189 subagents ran for two hours and returned 20 findings.&lt;/strong&gt; This article is that measurement.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This plugin is in beta as of 2026-07 (the version number is 0.10.0). Every number and behavior below is measured on v0.10.0 and may change.&lt;/p&gt;
&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;Measured on 2.1.220&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plugin&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;claude-security&lt;/code&gt; v0.10.0 (by Anthropic, marketplace &lt;code&gt;claude-plugins-official&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;python3 3.9+ (used by the report-generation scripts)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;git&lt;/td&gt;
&lt;td&gt;Change scans require a git checkout. Whole-repository scans work without git&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Permission mode&lt;/td&gt;
&lt;td&gt;auto mode recommended (the plugin says so itself)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This plugin is the "runs inside your session" version of the hosted &lt;a href="https://claude.com/product/claude-security" rel="noopener noreferrer"&gt;Claude Security&lt;/a&gt; product. It starts no separate process and no daemon; it runs entirely inside your Claude Code session.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the tool is, and what happens when you run it
&lt;/h2&gt;

&lt;h3&gt;
  
  
  From install to launch
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run these inside a Claude Code session&lt;/span&gt;
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;claude-security@claude-plugins-official
/reload-plugins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it reports that the marketplace was not found, run &lt;code&gt;/plugin marketplace add anthropics/claude-plugins-official&lt;/code&gt; first and retry.&lt;/p&gt;

&lt;p&gt;After installing, &lt;code&gt;/claude-security&lt;/code&gt; opens a menu offering three jobs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Duration&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scan codebase&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The whole repository, or a scoped part of it&lt;/td&gt;
&lt;td&gt;Depends on size (2 hours 1 minute in my run)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scan changes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A branch diff, a pull request diff, or a single commit&lt;/td&gt;
&lt;td&gt;Minutes for a small diff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Suggest patches&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Turns an existing report's findings into patch files&lt;/td&gt;
&lt;td&gt;Depends on the finding count&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can also skip the menu and name the job in the arguments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/claude-security scan my branch's changes
/claude-security --base main
/claude-security 3cb30d2          # a hex string of 7+ chars is read as a commit SHA
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Get the confirmation prompt out of the way
&lt;/h3&gt;

&lt;p&gt;Choosing a whole-repository scan always triggers this confirmation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This scan may take a while and may use a significant number of tokens. You will need to leave Claude Code open while the scan completes. Are you sure you want to continue?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Accept the cost up front and you go straight in. The check is not a literal string match — it asks whether your request already reads as accepting the time and token cost — so include a sentence that conveys that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/claude-security scan this repository — whole codebase.
I understand it may take a while and use a significant number of tokens.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You do need to leave Claude Code open while the scan runs. No interaction is required, so you can walk away.&lt;/p&gt;
&lt;h3&gt;
  
  
  Effort changes how much it searches, not how hard it verifies
&lt;/h3&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;--effort&lt;/code&gt; has four tiers. The distinctive design choice here: &lt;strong&gt;the verification panel is fixed at three voters at every tier.&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;effort&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;low&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;One researcher over the whole repository. No inventory, threat model, or breadth sweep&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;medium&lt;/code&gt; (default)&lt;/td&gt;
&lt;td&gt;The full workflow: inventory → threat model → one researcher per component × category → one sweep → three-voter panel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;high&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Like &lt;code&gt;medium&lt;/code&gt;, but the component cap rises to 24, two researchers per cell, two sweeps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Like &lt;code&gt;high&lt;/code&gt;, plus an adversarial phase: marginal keeps are re-panelled and every survivor faces a red team&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So dropping the tier does not mean "wave things through" — it means "search a narrower area." The documentation says as much, noting that the report's confidence figures are calibrated against that three-voter panel. I ran &lt;code&gt;medium&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A six-stage pipeline runs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
    A["Inventory&amp;lt;br/&amp;gt;partition the tree"] --&amp;gt; B["Threat model&amp;lt;br/&amp;gt;one per component"]
    B --&amp;gt; C["Research&amp;lt;br/&amp;gt;component × category"]
    C --&amp;gt; D["Sweep&amp;lt;br/&amp;gt;what the matrix missed"]
    D --&amp;gt; E["Panel&amp;lt;br/&amp;gt;3-lens adversarial check"]
    E -. max only .-&amp;gt; F["Adversarial&amp;lt;br/&amp;gt;repanel + red team"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what that came to against &lt;code&gt;~/.claude&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;Stage&lt;/th&gt;
&lt;th&gt;Agents&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Inventory&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Partitioned the tree into 11 components&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Threat model&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;One per component&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Research + Sweep&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;One per component × category cell; the sweep is counted in this same pool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Panel&lt;/td&gt;
&lt;td&gt;135&lt;/td&gt;
&lt;td&gt;45 candidates × 3 votes&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;189&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2 hours 1 minute&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This breakdown and the elapsed time are measured, not estimated. You can reproduce the same tally on your own run: the workflow's &lt;code&gt;journal.jsonl&lt;/code&gt; records each agent's return value, so you can classify agents by the shape of what they returned.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run inside ~/.claude/projects/&amp;lt;project&amp;gt;/&amp;lt;session-id&amp;gt;/subagents/workflows/wf_*/&lt;/span&gt;
python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import json, collections
sig = collections.Counter()
with open("journal.jsonl", encoding="utf-8", errors="replace") as f:
    for line in f:
        try: d = json.loads(line)
        except ValueError: continue
        if d.get("type") != "result": continue
        r = d.get("result")
        sig[tuple(sorted(r))[:5] if isinstance(r, dict) else ("?",)] += 1
for k, v in sig.most_common(): print(v, k)
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;135 ('reasoning', 'verdict')                                          &amp;lt;- panel votes
 42 ('findings',)                                                     &amp;lt;- researchers
 11 ('assumptions', 'entryPoints', 'hotFiles', 'sinks', 'trustBoundaries')  &amp;lt;- threat models
  1 ('components', 'securityScanSkippedComponents')                    &amp;lt;- inventory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The elapsed time comes from the same logs. The first record is &lt;code&gt;02:28:12Z&lt;/code&gt; and the last is &lt;code&gt;04:29:16Z&lt;/code&gt;. The largest gap in between is 47 seconds, so something is running for the entire two hours.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;this does not mean you are tied up for two hours.&lt;/strong&gt; You must leave the Claude Code window open; you do not have to do anything. The plugin's internal documentation has a line that assumes exactly this.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Users desire to leave the session unattended very soon after kicking off a scan, around a minute of wall-clock time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That matched my experience — I spent the interval in a separate session doing unrelated work.&lt;/p&gt;

&lt;p&gt;Progress is visible stage by stage in &lt;code&gt;/workflows&lt;/code&gt; while it runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the output looked like
&lt;/h2&gt;

&lt;p&gt;When it finishes, three artifacts land in a timestamped directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CLAUDE-SECURITY-20260725-022756/
├── CLAUDE-SECURITY-RESULTS.md        # the human-readable report
├── CLAUDE-SECURITY-RESULTS.jsonl     # machine-readable (one finding per line)
└── CLAUDE-SECURITY-REVISION-*.json   # the revision and settings stamp for the run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result was &lt;strong&gt;20 findings (5 HIGH / 15 MEDIUM)&lt;/strong&gt;. 111 raw candidates became 83 after dedup; of the 45 that reached the panel, 25 were rejected, leaving these 20.&lt;/p&gt;

&lt;p&gt;A rejection rate above half can be read as evidence that the panel is working, or as evidence that the research stage over-generates candidates and the panel cleans up after it. The report contains nothing that decides between those readings.&lt;/p&gt;

&lt;p&gt;What does decide something is the number on the way out. &lt;strong&gt;I acted on all 20 findings, and rejected none as a false positive.&lt;/strong&gt; I confirmed each one reproduced before fixing it (three were addressed only partially — I did not add OS-level isolation). This is one repository and one run, but at least in this case, a tool that only reads and reasons did not produce a pile of off-target noise.&lt;/p&gt;

&lt;p&gt;Three things about the output quality stood out.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. It reads configuration and natural-language instruction files
&lt;/h3&gt;

&lt;p&gt;The inventory partitioned the tree like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hooks / scripts / scheduled-tasks / skills-executable-scripts /
skills-instructions / agents / rules / templates / docs /
notes-and-metrics / tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;rules&lt;/code&gt; and &lt;code&gt;skills-instructions&lt;/code&gt; — meaning &lt;strong&gt;the instruction files written in plain natural language were treated as their own audit components.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The 20 findings broke down by location like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hooks: 10 / skills: 5 / settings.json: 3 / scheduled-tasks: 1 / agents: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The one under &lt;code&gt;agents&lt;/code&gt; was about &lt;strong&gt;the trust-ordering instruction itself&lt;/strong&gt;, written in an agent definition &lt;code&gt;.md&lt;/code&gt;. One of my agents treated raw session logs as "machine records, hard to alter" and placed them at the top of its trust order.&lt;/p&gt;

&lt;p&gt;But those logs hold the verbatim bodies of external pages fetched in the past. The file may be hard to tamper with; its contents are not trustworthy.&lt;/p&gt;

&lt;p&gt;The one under &lt;code&gt;scheduled-tasks&lt;/code&gt; was similar in character: a README said a job was "not wired up," when it was in fact registered and running weekly. &lt;strong&gt;It flags documentation that contradicts reality as something that makes reviewers misjudge risk.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A tool that searches for patterns with regular expressions produces neither of these.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. It folds the count down instead of inflating it
&lt;/h3&gt;

&lt;p&gt;The report opens with this.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Read the count with one caveat: the 20 are not 20 distinct defects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It then names three clusters itself.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cluster&lt;/th&gt;
&lt;th&gt;Findings&lt;/th&gt;
&lt;th&gt;What it actually is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Permission allowlist&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;The same line of &lt;code&gt;settings.json&lt;/code&gt; seen through three lenses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Log-guard leaks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Three hooks protecting the same asset, all failing open because of one shared constant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Child-agent execution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Two paths where a skill launching a child Claude executed generated strings directly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those three clusters account for 15; the remaining 5 stood alone. The report describes the whole as "closer to eight problems" when counted by root cause.&lt;/p&gt;

&lt;p&gt;It goes further and &lt;strong&gt;names a single repair as the highest-leverage one in the eight-finding cluster&lt;/strong&gt; — fix that one spot and seven close at once. In a field where finding count tends to be treated as a performance metric, the report folds its own count down for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The machine-readable side lets you sort by what you care about
&lt;/h3&gt;

&lt;p&gt;The report body is long, so start with the jsonl for the shape of it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run in the parent directory of CLAUDE-SECURITY-&amp;lt;timestamp&amp;gt;/&lt;/span&gt;
python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import json, collections, glob
path = glob.glob("CLAUDE-SECURITY-*/CLAUDE-SECURITY-RESULTS.jsonl")[0]
with open(path) as f:
    rows = [json.loads(line) for line in f]
print("count:", len(rows), collections.Counter(r["severity"] for r in rows))
print("where:", collections.Counter(r["file"].split("/")[0] for r in rows))
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;count: 20 Counter({'MEDIUM': 15, 'HIGH': 5})
where: Counter({'hooks': 10, 'skills': 5, 'settings.json': 3, 'scheduled-tasks': 1, 'agents': 1})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each line carries fields including &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;severity&lt;/code&gt;, &lt;code&gt;confidence&lt;/code&gt;, &lt;code&gt;file&lt;/code&gt;, &lt;code&gt;line&lt;/code&gt;, &lt;code&gt;exploit_scenario&lt;/code&gt;, &lt;code&gt;preconditions&lt;/code&gt;, &lt;code&gt;recommendation&lt;/code&gt;, and &lt;code&gt;cwe_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;confidence&lt;/code&gt; is worth reading alongside &lt;code&gt;severity&lt;/code&gt;, because that value is set by the panel's vote.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Confidence in this report is clamped by that vote — only unanimous panels claim &lt;code&gt;high&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How far you can trust it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The tool discloses its own limits.&lt;/strong&gt; Three things that static-analysis reports tend to omit were stated outright.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limits the report writes down itself
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Some candidates go unreviewed.&lt;/strong&gt; In my run, 38 candidate sites never reached the panel after dedup and capping. The earlier "45 candidates × 3 votes" counts only what reached the panel; these 38 are not in it. The report states plainly that absence from the report is not evidence of absence in the tree&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It names what it excluded.&lt;/strong&gt; In my run, three vendored Python virtualenvs and &lt;code&gt;__pycache__&lt;/code&gt; — with the note that if a dependency inside those virtualenvs is itself compromised, this scan did not see it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It accounts for coverage.&lt;/strong&gt; Every top-level directory must be either scanned or set aside with a stated reason, and that reconciliation runs &lt;strong&gt;before&lt;/strong&gt; the search begins. All 11 of my directories were accounted for&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  It executes no code
&lt;/h3&gt;

&lt;p&gt;No tests run, no exploit fires, no proof of concept is validated. Everything is derived from reading. The heaviest finding in my run — an arbitrary code execution path — was confirmed not by planting an attack file but by &lt;strong&gt;reading it against a similar neighboring hook&lt;/strong&gt; (which turned out to already carry the same defense).&lt;/p&gt;

&lt;p&gt;That is the safe design, but the flip side is that &lt;strong&gt;reproducible confirmation becomes your job.&lt;/strong&gt; I wrote a failing test first for each fix before implementing it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anthropic states that it is nondeterministic
&lt;/h3&gt;

&lt;p&gt;From the README:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Scans are nondeterministic. Two scans of the same code can surface different findings.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It goes on to say that because it reasons about code the way a human security researcher does, it &lt;strong&gt;complements rather than replaces&lt;/strong&gt; SAST, dependency scanning, and code review. This is not a drop-in substitute for static analysis.&lt;/p&gt;

&lt;h3&gt;
  
  
  It writes nothing
&lt;/h3&gt;

&lt;p&gt;A scan only reads. Even the patch-generation job leaves your working tree untouched: nothing is committed, pushed, or opened as a pull request. Patch files land on disk and you decide whether to apply them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do afterward
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How to order the fixes
&lt;/h3&gt;

&lt;p&gt;I ranked them in four tiers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Independently sufficient for arbitrary code execution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;It bypasses every other defense, so fixing anything else while this stands is pointless&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A cluster that reduces to one constant or anchor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One fix closes several findings at once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A definition with the trust direction inverted&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The code may be correct while the premise of the judgment is broken&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Documentation that contradicts reality&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The harm is indirect, but it gives reviewers false reassurance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Priority 2 is handed to you by the report, so &lt;strong&gt;look for wording to the effect of "highest-leverage" in the body&lt;/strong&gt; before planning anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  You do not have to take the recommendation as given
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;recommendation&lt;/code&gt; field is accurate, but it is not the only answer. One example.&lt;/p&gt;

&lt;p&gt;My &lt;code&gt;settings.json&lt;/code&gt; carried entries like &lt;code&gt;Bash(bash:*)&lt;/code&gt; and &lt;code&gt;Bash(python:*)&lt;/code&gt; that grant a whole interpreter. With those present, the other ~80 narrowly scoped grants are moot, because &lt;code&gt;bash -c '&amp;lt;anything&amp;gt;'&lt;/code&gt; matches by prefix.&lt;/p&gt;

&lt;p&gt;The report recommended moving these into &lt;code&gt;permissions.deny&lt;/code&gt;. I chose &lt;strong&gt;removal from the allowlist&lt;/strong&gt; instead.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;deny is a refusal, so it makes legitimate execution permanently impossible&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dropping an entry from the allowlist merely returns it to a confirmation prompt in auto mode, which hands the judgment back to a human&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The replacement shape is a shift from "grant by interpreter name" to "grant by the script you actually run."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-      "Bash(python:*)",
-      "Bash(python3:*)",
-      "Bash(sh:*)",
-      "Bash(bash:*)",
-      "Bash(node:*)",
-      (export / source / command removed too — 8 entries in all)
+      "Bash(bash ~/.claude/hooks/:*)",
+      "Bash(bash ~/.claude/scripts/:*)",
+      "Bash(bash ~/.claude/tests/:*)",
+      "Bash(python3 ~/.claude/skills/:*)",
+      "Bash(python3 -m pytest:*)",
+      "Bash(python3 -m scripts.:*)",
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The finding is right; the recommendation is one option&lt;/strong&gt; — that is the distance to keep.&lt;/p&gt;

&lt;h3&gt;
  
  
  Day to day, put it on Scan changes
&lt;/h3&gt;

&lt;p&gt;Running the whole-repository scan every time is not realistic. For routine use, &lt;strong&gt;Scan changes&lt;/strong&gt; is the one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It targets a branch diff, a pull request diff, or a single commit&lt;/li&gt;
&lt;li&gt;At &lt;code&gt;medium&lt;/code&gt;, a diff of at most 5 files and 300 changed lines runs the lighter single-researcher shape instead of the full component matrix (the panel verification is unchanged)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only committed changes are in scope.&lt;/strong&gt; Uncommitted work in the tree is not part of any diff, so commit or stash first&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want the patch-generation job, &lt;strong&gt;scan with a clean working tree.&lt;/strong&gt; When the run's stamp carries &lt;code&gt;revision.dirty: true&lt;/code&gt;, the job stops before drafting anything. Mine ran dirty, so that path was unavailable.&lt;/p&gt;

&lt;p&gt;The scanner tripped one of my own guardrails&lt;/p&gt;

&lt;p&gt;My setup includes a homegrown mechanism that detects attempts to abuse the auto-mode confirmation skip. During the scan, one panel voter systematically searched the Claude Code binary for permission-bypass symbols and set it off.&lt;/p&gt;

&lt;p&gt;That candidate was rejected 0-3 by the panel, but the report records the behavior rather than burying it. The original phrasing was precise about why — a scanner probing its own guardrails is exactly the thing a reader should be told about.&lt;/p&gt;

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

&lt;p&gt;As a measurement of the beta, here is what to take away.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost&lt;/strong&gt; — one configuration directory (11 components) at &lt;code&gt;medium&lt;/code&gt; came to 189 subagents over 2 hours. Put routine use on Scan changes instead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Character of the output&lt;/strong&gt; — it produces findings regular expressions do not. Configuration and natural-language instruction files enter the audit scope, and documentation that contradicts reality gets flagged&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Precision, anecdotally&lt;/strong&gt; — of the 20 findings delivered, I rejected none as a false positive. One repository and one run, but this is not a tool built on the assumption that you skim and discard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How to read the count&lt;/strong&gt; — it does not inflate the number; it folds the findings into clusters and names the highest-leverage repair. Look there first&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bounds of trust&lt;/strong&gt; — unreviewed candidate counts, excluded areas, code never executed, nondeterminism: the report discloses all of it. A complement to static analysis, not a replacement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To be straight about it: &lt;strong&gt;I have not re-scanned after the fixes.&lt;/strong&gt; I have not asked the tool itself to confirm that the findings are closed, and since it is nondeterministic there is no guarantee the same result would come back anyway. Taking that into account, treating it as "one more reviewer on the team" seems like the right weight for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/anthropics/claude-plugins-official" rel="noopener noreferrer"&gt;anthropics/claude-plugins-official&lt;/a&gt; — where the &lt;code&gt;claude-security&lt;/code&gt; plugin is distributed&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://claude.com/product/claude-security" rel="noopener noreferrer"&gt;Claude Security&lt;/a&gt; — the hosted version of the same capability (this article covers the in-session plugin)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228" rel="noopener noreferrer"&gt;github.com/shimo4228&lt;/a&gt; — my repositories&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>claudecode</category>
      <category>anthropic</category>
      <category>security</category>
      <category>ai</category>
    </item>
    <item>
      <title>herdr, a tmux for AI Agents — Until the Editor Disappeared</title>
      <dc:creator>shimo4228</dc:creator>
      <pubDate>Mon, 20 Jul 2026 13:00:06 +0000</pubDate>
      <link>https://dev.to/shimo4228/herdr-a-tmux-for-ai-agents-until-the-editor-disappeared-3hnn</link>
      <guid>https://dev.to/shimo4228/herdr-a-tmux-for-ai-agents-until-the-editor-disappeared-3hnn</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What this article covers&lt;/strong&gt;: how to build a terminal environment where you can monitor multiple Claude Code sessions with live status, come back to the same sessions after stepping away or over SSH, and — the interesting part — &lt;strong&gt;let the agents reorganize their own screen layout&lt;/strong&gt;. It's a field log from installation to the point where the editor's (Zed's) role shrank from "writing" to "sign-off" (reviewing and approving) to "gone."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Intro — the "where do I put them" problem of parallel agents
&lt;/h2&gt;

&lt;p&gt;Once you start running multiple Claude Code or Codex sessions in parallel, you hit these walls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terminal tabs pile up, and remembering "which tab was doing what" costs you time every single round&lt;/li&gt;
&lt;li&gt;Quitting your editor or terminal app kills the agent sessions running inside it&lt;/li&gt;
&lt;li&gt;You can't check on your running agents from your iPhone or another machine while away from your desk&lt;/li&gt;
&lt;li&gt;Agents can't touch their own execution environment (pane layout, workspaces) at all. Rearranging things is always manual human work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article solves these with &lt;strong&gt;herdr&lt;/strong&gt; (an agent multiplexer — a terminal multiplexer built for agents). I'm a Zed user, and right after installing it my verdict was "isn't this redundant with what Zed already does?" — until &lt;strong&gt;the moment I let an agent manipulate the layout itself, which flipped my evaluation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And the flip didn't stop there. Once herdr became my main battleground, the editor's (Zed's) role shrank from "a tool for writing" to "a tool for signing off on agent output" — and eventually it lost even that role and I stopped opening it. This is the whole story, from installation to the editor becoming unnecessary, with the configs and commands that actually worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;macOS + Homebrew (herdr also runs on Linux)&lt;/li&gt;
&lt;li&gt;You're already using CLI agents such as Claude Code&lt;/li&gt;
&lt;li&gt;This article is based on &lt;strong&gt;herdr v0.7.4&lt;/strong&gt; (verified 2026-07-18). The tool is only about three and a half months old, so the command surface may change&lt;/li&gt;
&lt;li&gt;The editor is Zed (a recent build with the &lt;code&gt;markdown_preview_*&lt;/code&gt; settings), and the terminal I end up choosing is &lt;a href="https://ghostty.org/" rel="noopener noreferrer"&gt;Ghostty&lt;/a&gt; (the how and why comes later)&lt;/li&gt;
&lt;li&gt;Pane IDs in this article (&lt;code&gt;w5:p8&lt;/code&gt; etc.) are real values from my environment. Yours will differ&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What herdr is — only two real differences from tmux
&lt;/h2&gt;

&lt;p&gt;herdr is an "agent multiplexer that lives in your terminal." It's a single Rust binary, and like tmux it persists sessions in a server process (the prefix key is even tmux-compatible: &lt;code&gt;ctrl+b&lt;/code&gt;). GitHub is &lt;a href="https://github.com/ogulcancelik/herdr" rel="noopener noreferrer"&gt;ogulcancelik/herdr&lt;/a&gt;, dual-licensed AGPL-3.0-or-later + commercial (stated in the LICENSE file).&lt;/p&gt;

&lt;p&gt;The essential differences from tmux come down to two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Semantic agent state tracking&lt;/strong&gt; — it auto-detects agents inside panes and lists their state — &lt;code&gt;working&lt;/code&gt; / &lt;code&gt;blocked&lt;/code&gt; / &lt;code&gt;done&lt;/code&gt; / &lt;code&gt;idle&lt;/code&gt; / &lt;code&gt;unknown&lt;/code&gt; — in a sidebar. You can see "which one is waiting on me" without visually patrolling every pane&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A socket API&lt;/strong&gt; — pane splitting, command execution, output reading, and layout changes are all controllable from external processes (CLI commands like &lt;code&gt;herdr pane run&lt;/code&gt; are wrappers around a Unix-socket API). Which means &lt;strong&gt;an agent can operate its own execution environment&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The screen has three levels: &lt;strong&gt;workspace → tab → pane&lt;/strong&gt;. From the CLI you address them as &lt;code&gt;w1&lt;/code&gt; (workspace 1), &lt;code&gt;w1:t1&lt;/code&gt; (tab 1 inside it), and &lt;code&gt;w1:p1&lt;/code&gt; (pane 1). Read the command examples below with this notation in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing — one brew command
&lt;/h2&gt;

&lt;p&gt;It's bottled in homebrew-core, so brew it is. I skipped the official site's &lt;code&gt;curl | sh&lt;/code&gt; because piped install scripts are hard to audit. Including config generation, it's four lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;herdr                                    &lt;span class="c"&gt;# v0.7.4&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.config/herdr
herdr &lt;span class="nt"&gt;--default-config&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/.config/herdr/config.toml  &lt;span class="c"&gt;# 305-line baseline config&lt;/span&gt;
herdr config check                                    &lt;span class="c"&gt;# → config: ok&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;herdr&lt;/code&gt; starts the TUI, and the server (&lt;code&gt;herdr server&lt;/code&gt;) comes up automatically. No login-item daemon (&lt;code&gt;brew services&lt;/code&gt;) was needed.&lt;/p&gt;

&lt;p&gt;As a sanity check, run a smoke test of the socket API. This doubles as the minimal example of "letting an agent operate the environment" covered later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a workspace, run a command in a pane, wait for output, read it back&lt;/span&gt;
herdr workspace create &lt;span class="nt"&gt;--cwd&lt;/span&gt; ~ &lt;span class="nt"&gt;--label&lt;/span&gt; smoke-test &lt;span class="nt"&gt;--no-focus&lt;/span&gt;
&lt;span class="c"&gt;# → Returns workspace_id and pane_id as JSON. The lines below assume it returned&lt;/span&gt;
&lt;span class="c"&gt;#   "w1:p1" — substitute whatever IDs you actually got (if you already have&lt;/span&gt;
&lt;span class="c"&gt;#   workspaces, you'll get w2 or later)&lt;/span&gt;
herdr pane run &lt;span class="s2"&gt;"w1:p1"&lt;/span&gt; &lt;span class="s2"&gt;"echo herdr-smoke-ok"&lt;/span&gt;
herdr &lt;span class="nb"&gt;wait &lt;/span&gt;output &lt;span class="s2"&gt;"w1:p1"&lt;/span&gt; &lt;span class="nt"&gt;--match&lt;/span&gt; &lt;span class="s2"&gt;"herdr-smoke-ok"&lt;/span&gt; &lt;span class="nt"&gt;--timeout&lt;/span&gt; 5000
herdr pane &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="s2"&gt;"w1:p1"&lt;/span&gt; &lt;span class="nt"&gt;--lines&lt;/span&gt; 10
&lt;span class="c"&gt;# → If the output contains herdr-smoke-ok, you're wired up&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all four pass, layout, execution, and reading are controllable from an external process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Zed is enough, and where it isn't
&lt;/h2&gt;

&lt;p&gt;Let me be honest here: right after installing, my evaluation was "&lt;strong&gt;this is barely different from Zed&lt;/strong&gt;."&lt;/p&gt;

&lt;p&gt;Zed announced &lt;a href="https://zed.dev/blog/parallel-agents" rel="noopener noreferrer"&gt;Parallel Agents&lt;/a&gt; on 2026-04-22. The Threads sidebar runs multiple agents in parallel, with per-thread git worktree isolation. Its terminal already does tabs (&lt;code&gt;cmd+N&lt;/code&gt;) and splits (&lt;code&gt;cmd+D&lt;/code&gt;). If you're at your desk reviewing in a GUI while agents run, Zed's experience is better. herdr's workspace switching looked about the same as switching Zed windows.&lt;/p&gt;

&lt;p&gt;In fact, I almost shelved it with a tidy division of "at the desk = Zed, away = herdr."&lt;/p&gt;

&lt;p&gt;What changed my evaluation was using the layers that &lt;strong&gt;structurally don't exist in Zed&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;Layer&lt;/th&gt;
&lt;th&gt;Zed&lt;/th&gt;
&lt;th&gt;herdr&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GUI review experience&lt;/td&gt;
&lt;td&gt;◎ Parallel Agents + editor integration&lt;/td&gt;
&lt;td&gt;— (terminal only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Execution persistence&lt;/td&gt;
&lt;td&gt;Quitting the app stops execution (thread history survives)&lt;/td&gt;
&lt;td&gt;Server-resident. Execution continues with every app closed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External reattach to a running session&lt;/td&gt;
&lt;td&gt;Not possible (SSH remote dev exists, but it's a different thing)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ssh → herdr&lt;/code&gt; from an iPhone etc. drops you back into the same screen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agents operating their own environment&lt;/td&gt;
&lt;td&gt;Not possible&lt;/td&gt;
&lt;td&gt;Full control via the socket API&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first row is Zed's win; the bottom three are territory only herdr has (and the first row's verdict changes later, once plugins enter the picture). So it's not competition — it's a complement: &lt;strong&gt;herdr fills the "managing many agents" layer where Zed is weak&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One more trap that matters in practice: &lt;strong&gt;sessions launched from Zed's agent panel can't be grabbed from outside — not by the official Claude Code app (Remote Control), not by Termius&lt;/strong&gt; (verified myself). They're sealed inside the editor process. A Claude Code launched as a CLI from a terminal shows up in the official app's list and is reachable via SSH → herdr. If there's any chance you'll want to look at a session from outside, launch it as a CLI.&lt;/p&gt;

&lt;p&gt;The next section is the clincher for the complement story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Letting the agent operate the layout itself
&lt;/h2&gt;

&lt;p&gt;After installing, I wanted my agents — scattered across tabs — on one screen, and on a whim asked Claude Code itself: "consolidate the tabs into one."&lt;/p&gt;

&lt;p&gt;Claude Code executed these three commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Move panes from other tabs into tab t2 as splits (executed by Claude Code itself)&lt;/span&gt;
herdr pane move w5:p5 &lt;span class="nt"&gt;--tab&lt;/span&gt; w5:t2 &lt;span class="nt"&gt;--split&lt;/span&gt; right &lt;span class="nt"&gt;--no-focus&lt;/span&gt;
herdr pane move w5:p7 &lt;span class="nt"&gt;--tab&lt;/span&gt; w5:t2 &lt;span class="nt"&gt;--split&lt;/span&gt; down &lt;span class="nt"&gt;--target-pane&lt;/span&gt; w5:p4 &lt;span class="nt"&gt;--no-focus&lt;/span&gt;
herdr pane move w5:p6 &lt;span class="nt"&gt;--tab&lt;/span&gt; w5:t2 &lt;span class="nt"&gt;--split&lt;/span&gt; down &lt;span class="nt"&gt;--target-pane&lt;/span&gt; w5:p5 &lt;span class="nt"&gt;--no-focus&lt;/span&gt;
&lt;span class="c"&gt;# → Panes scattered across tabs become a 2×2 grid on one tab. Emptied tabs auto-close&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two agents were in &lt;code&gt;working&lt;/code&gt; state at the time, and the layout changed &lt;strong&gt;without stopping a single one&lt;/strong&gt;. All I did was say one sentence. No worrying about reopening panes and interrupting processes, no assembling the sequence of move commands by hand.&lt;/p&gt;

&lt;p&gt;Here's what it looked like. Starting from one-tab-per-agent (Before):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs5j5tmzndv7vr6dflkil.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs5j5tmzndv7vr6dflkil.png" alt="Before: one tab per agent. Four tabs in the tab bar, only one session visible at a time" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Before: four tabs in the bar. Only one session visible at a time&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I ask the Claude Code in another pane to "consolidate them":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbfan3a4ukb0hweflkn01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbfan3a4ukb0hweflkn01.png" alt="Instructing Claude Code to consolidate tabs. The screen shows it surveying the tab list and the execution log" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The instruction is a single sentence. Claude Code surveys the current tab structure via the socket API, assembles the pane moves, and runs them&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And everything lands in a 2×2 grid on one tab:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy6onbiow10ndcb2p2r4m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy6onbiow10ndcb2p2r4m.png" alt="After: consolidated into a 2×2 grid on a single tab, four panes visible simultaneously" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;After: every agent on one screen. Running processes never stopped&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Agents know where they are through environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;env&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; herdr    &lt;span class="c"&gt;# home directory in the output replaced with ~&lt;/span&gt;
&lt;span class="nv"&gt;HERDR_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="nv"&gt;HERDR_PANE_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;w5:p8
&lt;span class="nv"&gt;HERDR_SOCKET_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/.config/herdr/herdr.sock
&lt;span class="nv"&gt;HERDR_TAB_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;w5:t2
&lt;span class="nv"&gt;HERDR_WORKSPACE_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;w5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So an agent knows "which pane am I in," and from there it can split a pane next door, run a command, and read the result. &lt;strong&gt;The shape of the execution environment itself becomes one of the agent's tools.&lt;/strong&gt; Incidentally, the session writing this very article is a Claude Code inside a herdr pane — &lt;code&gt;herdr agent list&lt;/code&gt; shows it as &lt;code&gt;working&lt;/code&gt; on this draft.&lt;/p&gt;

&lt;p&gt;git worktree integration was also a one-liner (verified myself):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a worktree + branch + new workspace in one shot&lt;/span&gt;
herdr worktree create &lt;span class="nt"&gt;--workspace&lt;/span&gt; w6 &lt;span class="nt"&gt;--branch&lt;/span&gt; feature-x
&lt;span class="c"&gt;# → Creates a worktree at ~/.herdr/worktrees/&amp;lt;repo-name&amp;gt;/feature-x/ and&lt;/span&gt;
&lt;span class="c"&gt;#    opens a workspace labeled "feature-x" with that as its cwd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same "one room per branch, work in parallel" structure as Zed Parallel Agents — except an agent can assemble it from the CLI.&lt;/p&gt;

&lt;p&gt;Gotcha: a zoomed tab refuses layout changes&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pane move&lt;/code&gt; can return &lt;code&gt;changed: false&lt;/code&gt; (reason: &lt;code&gt;"zoomed_tab"&lt;/code&gt;) and do nothing. If the target tab is in zoomed view, layout changes are rejected by design. Run &lt;code&gt;herdr pane zoom  --off&lt;/code&gt; to unzoom first.&lt;/p&gt;

&lt;p&gt;Gotcha: an omitted target means the focused pane, not the caller&lt;/p&gt;

&lt;p&gt;When I had Claude Code run &lt;code&gt;herdr pane split&lt;/code&gt;, the pane split in &lt;strong&gt;a different workspace — the one I happened to be looking at&lt;/strong&gt; — not the intended one. That's because when you omit the pane target, it resolves to "the pane focused in the TUI," not "the pane the command was invoked from" (stated in the official CLI reference; passing &lt;code&gt;--current&lt;/code&gt; resolves to the calling pane).&lt;/p&gt;

&lt;p&gt;When agents drive the CLI, don't let them omit the target; have them pass their own position explicitly via &lt;code&gt;--current&lt;/code&gt; or &lt;code&gt;HERDR_PANE_ID&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Gotcha: server lifetime and startup directory&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The server belongs to the parent process that started it.&lt;/strong&gt; If &lt;code&gt;herdr server&lt;/code&gt; gets started from Claude Code's shell, it can die with that session. After my experiments I stopped the server once and restarted &lt;code&gt;herdr&lt;/code&gt; myself so the server ownership sat with my own process&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The startup cwd only matters once, at first session creation.&lt;/strong&gt; &lt;code&gt;cd&lt;/code&gt;-ing into a repo and running &lt;code&gt;herdr&lt;/code&gt; just reattaches to the existing session from the second time on. Add repos from inside herdr with &lt;code&gt;herdr workspace create --cwd  --label&lt;/code&gt;. It took a mental-model move from "cd to navigate" to "switch workspaces to navigate"&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Does the sidebar show state, or history?
&lt;/h2&gt;

&lt;p&gt;Running herdr inside Zed's terminal gives you this nesting:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftlbgieukixgvaoh1aibt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftlbgieukixgvaoh1aibt.png" alt="herdr inside a Zed terminal. Spaces/agents sidebar on the left, two Claude Code sessions running side by side in split panes on the right" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The spaces / agents sidebar on the left. Each agent gets one line with its state and remaining context&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What I noticed in use: herdr's sidebar &lt;strong&gt;only shows rows that map 1:1 to living processes&lt;/strong&gt;. When an agent finishes, its row disappears. It structurally cannot get cluttered.&lt;/p&gt;

&lt;p&gt;Zed's Threads sidebar, by contrast, is &lt;strong&gt;a history list&lt;/strong&gt;. Sessions that ended days or months ago sit alongside active ones, projects interleaved. Finished work keeps occupying your screen as "attention inventory."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fll9pw88ybvy0izbvo9se.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fll9pw88ybvy0izbvo9se.png" alt="Zed's Threads sidebar. Finished historical sessions and active ones, across multiple projects, in the same list" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Zed's Threads sidebar. Sessions from 4 days, 2 weeks, and 2 months ago mixed into the same list as active ones&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Does a list show &lt;strong&gt;current state&lt;/strong&gt;, or &lt;strong&gt;past history&lt;/strong&gt;? That felt like a litmus test for UIs in the parallel-agent era.&lt;/p&gt;

&lt;p&gt;This realization changed how I use the whole screen, too. I used to keep a permanent two-way split of editor and CLI — but I only actually looked at the editor "when reading code," and the rest of the time it was dead space.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5uhbv68rdzxewbjevhat.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5uhbv68rdzxewbjevhat.png" alt="Before: permanent two-way split. claude CLI on the left, editor and file tree on the right" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Before: permanent two-way split. The editor side spends most of its time unwatched&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmp0vmiqf1fxno03ovlci.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmp0vmiqf1fxno03ovlci.png" alt="After: full-screen editor-only view, switched to only when reading code" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;After: herdr (the CLI side) is the main screen; when I read code I flip the editor to full screen with &lt;code&gt;cmd+shift+backtick&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In agent-driven development, the primary screen flips from the editor to the agent CLI, and &lt;strong&gt;the editor becomes the on-demand side&lt;/strong&gt;. Same root idea as the sidebar: align "what's in view" with "what's in use right now," and you stop leaking attention.&lt;/p&gt;
&lt;h2&gt;
  
  
  For a while, the answer was "Zed at the desk, herdr everywhere else"
&lt;/h2&gt;

&lt;p&gt;Even after the evaluation flipped, my placement answer briefly returned to that first instinct (the "shelve it" plan above). Only now it wasn't a passive shelving but an active division of labor: GUI review at the desk goes to Zed; being away, persistence, and delegation to agents go to herdr. Monitoring and reattaching from the iPhone is measured and working on the herdr side.&lt;/p&gt;

Field notes on away-from-desk operation (iPhone mirroring, sleep behavior)

- The iPhone connection path (Tailscale + Termius) is identical to [the one I wrote up in the tmux days](https://dev.to/shimo4228/running-claude-code-from-iphone-via-ssh-tmux-4c10) — just swap tmux for herdr. If conversational UX is your main goal, [my write-up on the official Claude Code app](https://dev.to/shimo4228/claude-code-from-iphone-plugging-3-holes-in-remote-control-17cf) is a better fit. The split is: "conversation = official app / fleet monitoring = Termius + herdr"
- Attach to the same session from the Mac's terminal and from Termius on the iPhone, and the two become **mirrors of the same screen**. Workspace switches and focus moves on one side show up on the other with barely any lag

![Termius on iPhone mirroring the same herdr session as the Mac](https://raw.githubusercontent.com/shimo4228/zenn-content/main/images/herdr-termius-iphone-sync.png)
*The iPhone side. It syncs with the Mac's screen with barely any lag*

- The display size **syncs to the smaller client**. While you're driving from the iPhone, the Mac's terminal also rewraps to the iPhone's screen width

![The Mac side while the iPhone is in control. Pane contents rendered at the iPhone's screen width](https://raw.githubusercontent.com/shimo4228/zenn-content/main/images/herdr-termius-mac-sync.png)
*The Mac at the same moment. While the iPhone is driving, the Mac's display syncs to iPhone size*

- **Agents make no progress while the Mac sleeps** (sleep suspends process execution, launchd-managed or not). Sleep doesn't kill the server — sessions are intact on wake (measured myself: sleep → wake, session survived). If you want agents running while you're away, configure the Mac not to sleep



&lt;p&gt;But this division of labor didn't last. A few days of using herdr as the main battleground ran me into the next question: &lt;strong&gt;Claude Code writes the code — so what am I actually doing in the editor?&lt;/strong&gt; Here begins the second half.&lt;/p&gt;
&lt;h2&gt;
  
  
  The three jobs left in Zed — from "writing tool" to "sign-off tool"
&lt;/h2&gt;

&lt;p&gt;Once you hand all code-writing to the CLI (Claude Code), the editor's "write" function has long since retired. I was still keeping Zed around for three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reading&lt;/strong&gt; — following multiple files side by side on one screen (multibuffer)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diff review&lt;/strong&gt; — eyeballing the changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checking Markdown&lt;/strong&gt; — polishing how articles and design notes look&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, everything left was on the "reading" side. So I flipped the framing: optimize Zed as a tool for reading and signing off.&lt;/p&gt;

&lt;p&gt;The first thing that paid off was the &lt;code&gt;zed&lt;/code&gt; CLI. Append &lt;code&gt;:line&lt;/code&gt; to a file path and it opens with the cursor on that line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Open a specific line (line:column also works)&lt;/span&gt;
zed src/contemplative_agent/cli.py:715

&lt;span class="c"&gt;# Open several sign-off points at once&lt;/span&gt;
zed src/core/metrics.py:88 src/cli.py:715 tests/test_cli.py:350

&lt;span class="c"&gt;# --wait: don't proceed to the next command until the human closes the file&lt;/span&gt;
zed &lt;span class="nt"&gt;--wait&lt;/span&gt; changed_file.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reverses the direction of the sign-off flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before&lt;/strong&gt;: the agent tells you &lt;code&gt;path:line&lt;/code&gt; as text → the human hunts for it in the editor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Now&lt;/strong&gt;: Claude Code pinpoints the line with &lt;code&gt;grep&lt;/code&gt;, runs &lt;code&gt;zed &amp;lt;file&amp;gt;:&amp;lt;line&amp;gt;&lt;/code&gt; → and hands the human an editor already open at the spot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The human stops searching and just reads the line being pointed at. Add &lt;code&gt;--wait&lt;/code&gt; and you get a structural pause — "don't proceed until the human has laid eyes on this file" — in a single command. One caveat: &lt;strong&gt;closing the file only means "seen."&lt;/strong&gt; It can't distinguish approve from reject, so don't chain irreversible operations like commits onto &lt;code&gt;--wait&lt;/code&gt;; run those explicitly, human-side, after confirming.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I didn't uninstall Zed at this point. Its resident cost while unused is zero, and the cost of rebuilding a "reading" replacement on the terminal side is real. Let actual usage make the call — that was the judgment.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Japanese-text rendering wall, and a host swap — to Ghostty
&lt;/h2&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once I started optimizing for "reading and signing off," I hit a rendering-quality wall next. Two problems, both around Japanese text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First: line spacing in Zed's Markdown preview.&lt;/strong&gt; Preview a Japanese Markdown document and the lines are cramped and hard to read. Digging in, the preview's paragraph line height is &lt;strong&gt;hard-coded at 1.3&lt;/strong&gt; with no user setting (&lt;a href="https://github.com/zed-industries/zed/discussions/56111" rel="noopener noreferrer"&gt;zed#56111&lt;/a&gt; has a request for Japanese-friendly settings). The preview-only font settings (&lt;code&gt;markdown_preview_font_family&lt;/code&gt; etc.) let you raise the size, but the 1.3 ratio itself can't be changed, so it's not a real fix. I moved final article checks to &lt;code&gt;npx zenn preview&lt;/code&gt; (the same rendering as production) and demoted Zed's preview to "glances while writing." &lt;strong&gt;Zed can't finish the reading experience&lt;/strong&gt; — the first crack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, and nastier: Markdown tables emitted by Claude Code came out misaligned in the terminal, with broken rules.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F42ebj7wywp31br30nf8t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F42ebj7wywp31br30nf8t.png" alt="A real example of a Claude Code output table breaking in the terminal" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My suspect was characters whose East Asian Width (Unicode's character-width classification) is "ambiguous" — box-drawing characters and the like. That width can be interpreted differently by each layer: Claude Code computing character widths, herdr laying out the grid, and the terminal actually drawing. When the layers disagree about width, tables break. (Other rendering factors, like font fallback, can produce similar symptoms — so this is a leading suspect, not a confirmed cause.)&lt;/p&gt;

&lt;p&gt;I couldn't pin down the exact mechanism, but &lt;strong&gt;which layer was at fault&lt;/strong&gt; could be isolated with a controlled swap: replace only the host (the terminal doing the rendering). Piping the same Claude Code + herdr output into Terminal.app, the tables lined up cleanly. The misbehaving layer was confirmed to be Zed's terminal rendering. For display bugs in a multi-layer stack, this "swap exactly one layer" move is the fastest isolation you can do.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Host&lt;/th&gt;
&lt;th&gt;Table correctness&lt;/th&gt;
&lt;th&gt;Color (True Color)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Zed's terminal&lt;/td&gt;
&lt;td&gt;Breaks&lt;/td&gt;
&lt;td&gt;Accurate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terminal.app&lt;/td&gt;
&lt;td&gt;Accurate&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ghostty&lt;/td&gt;
&lt;td&gt;Accurate&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I ended up on &lt;a href="https://ghostty.org/" rel="noopener noreferrer"&gt;Ghostty&lt;/a&gt;. It draws box-drawing and block characters itself instead of relying on the font, so tables don't break, and colors come out accurate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcvykmnefrw0cl598vd7s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcvykmnefrw0cl598vd7s.png" alt="herdr on Ghostty, with a Claude Code output table rendered without breakage" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;"After the fix." Same Claude Code + herdr output — on Ghostty, tables with box-drawing rules line up.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With the host settled, unify the look across the nesting too. If the host (Ghostty) and the TUI (herdr) disagree on color scheme, every glance between them adds a small snag. herdr can auto-follow the host's light/dark appearance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.config/herdr/config.toml&lt;/span&gt;
&lt;span class="nn"&gt;[theme]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tokyo-night"&lt;/span&gt;
&lt;span class="py"&gt;auto_switch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;          &lt;span class="c"&gt;# follow the host's light/dark&lt;/span&gt;
&lt;span class="py"&gt;dark_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tokyo-night"&lt;/span&gt;
&lt;span class="py"&gt;light_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tokyo-night-day"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;herdr server reload-config&lt;/code&gt; applies it without complaint, and the three tiers — macOS appearance → Ghostty → herdr — switch together.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1mtu3jraahqvfx1bgrdj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1mtu3jraahqvfx1bgrdj.png" alt="herdr on Ghostty in light mode. Two Claude Code sessions running side by side in two panes" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F38vcsv4u62fu6qjou9qh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F38vcsv4u62fu6qjou9qh.png" alt="The same screen in dark mode. Identical layout, paired color schemes" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The same two-pane screen in light and dark. It follows the host's (Ghostty's) appearance, TUI and all.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why herdr gets details like Japanese input right — a solo dev and his bots
&lt;/h2&gt;

&lt;p&gt;What surprised me in use: herdr has engineering effort in places as practical as Japanese input (IME) handling. The settings dialog has entries like these:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Famzjgd7u4glldon1ywbc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Famzjgd7u4glldon1ywbc.png" alt="herdr's settings dialog, experiments tab. Options for Japanese (IME) input handling" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, with a Japanese IME active, pressing a key sequence like &lt;code&gt;ctrl+b v&lt;/code&gt; can get the &lt;code&gt;v&lt;/code&gt; swallowed by the IME. herdr's countermeasure: switch the input source to ASCII only while accepting a key sequence, then switch back. There's also a feature that persists pane scrollback across server restarts.&lt;/p&gt;

&lt;p&gt;How does a tool get this level of polish? The repository's raw data hints at the reason:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh api repos/ogulcancelik/herdr &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--jq&lt;/span&gt; &lt;span class="s1"&gt;'{created: .created_at, stars: .stargazers_count, license: .license.spdx_id}'&lt;/span&gt;
&lt;span class="c"&gt;# → {"created":"2026-03-27...","stars":17794,"license":"NOASSERTION"}&lt;/span&gt;
&lt;span class="c"&gt;#   (license shows NOASSERTION because GitHub can't auto-detect&lt;/span&gt;
&lt;span class="c"&gt;#    the AGPL + commercial dual license)&lt;/span&gt;

gh api &lt;span class="s2"&gt;"repos/ogulcancelik/herdr/contributors?per_page=6"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--jq&lt;/span&gt; &lt;span class="s1"&gt;'.[] | "\(.login): \(.contributions)"'&lt;/span&gt;
&lt;span class="c"&gt;# → ogulcancelik: 979 / kangal-bot: 54 / github-actions[bot]: 43&lt;/span&gt;
&lt;span class="c"&gt;#   akbash-bot: 16 / human contributors are at 4 commits or fewer each&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At 113 days since creation (as of 2026-07-18) it's past 17k stars, and it hit &lt;a href="https://news.ycombinator.com/item?id=48714802" rel="noopener noreferrer"&gt;Hacker News&lt;/a&gt; on 2026-06-29 (166 points, 110 comments; all figures as of writing). Meanwhile, roughly 979 of the commits are the author's own — it's effectively a solo project. The fun part: the #2 and #4 contributors are bots (kangal-bot and akbash-bot). Presumably these are agents the author operates, committing under their own accounts.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An aside: kangal and akbash are both real breeds of Turkish livestock guardian dogs. Perhaps a deliberate match with the name herdr (one who herds) — though that's speculation about naming intent. What the API establishes for certain is only that nearly all commits are attributed to the author himself and to bot-named accounts; that the author operates those bots as agents is itself an inference from circumstance.&lt;br&gt;
The tool's defects bounce back into the developer's own velocity every day. So requirement discovery gets fast. Tools of the agent era fit the hand best when they're built together with agents — and that structure was right there in the contributor list.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The ending — Zed didn't even get to be the sign-off viewer
&lt;/h2&gt;

&lt;p&gt;Everything so far was about "optimizing Zed as a sign-off viewer." Then even that role got taken by the herdr side.&lt;/p&gt;

&lt;p&gt;The trigger was two herdr plugins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;file-viewer&lt;/strong&gt; — read-only, git-aware file browsing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reviewr&lt;/strong&gt; — shows diffs in a sidebar and sends line comments back to the agent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;file-viewer covers "reading"; reviewr covers "review the diff and send feedback" — both now inside herdr.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F33jnayi7u52hvkth9axx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F33jnayi7u52hvkth9axx.png" alt="The file-viewer plugin open inside herdr. Claude Code on the left, file browsing on the right" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The agent on the left and the file it opened on the right, on the same screen. "Reading" moved to the terminal side.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faihcf8i23t1k6z7fogk8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faihcf8i23t1k6z7fogk8.png" alt="The reviewr plugin inside herdr showing an uncommitted diff. Claude Code on the left, the diff and changed-file list on the right" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The reviewr screen. The right sidebar shows the uncommitted diff and the changed-file list. What's on screen is the diff of this very article's consolidation work.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here the flow comes full circle. Against the "agent → human" bridge from the earlier section (&lt;code&gt;zed file:line&lt;/code&gt;), reviewr builds the reverse "human → agent" bridge (comment send-back). The sign-off loop closed inside herdr.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This plugin marketplace just auto-indexes a GitHub topic — there's no review process. Installing a plugin is the same as granting code execution, so I read the manifest and install script before installing (for file-viewer: SHA-256 verification present, no automatic hooks — confirmed).&lt;br&gt;
The result: the &lt;code&gt;zed file:line&lt;/code&gt; bridge stopped needing to be crossed within hours of being built. The third job, checking Markdown, was covered too — glances-while-writing by file-viewer's rendered view (the &lt;code&gt;v&lt;/code&gt; key cycles diff ⇄ rendered ⇄ syntax), and final checks already consolidated onto &lt;code&gt;npx zenn preview&lt;/code&gt; — leaving no reason for Zed to stay. &lt;strong&gt;Zed lost even the sign-off viewer seat, and within a day I'd stopped opening it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The final stack: Ghostty + herdr + plugins + Claude Code. There is no editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing — the shape of the execution environment becomes the agent's tool
&lt;/h2&gt;

&lt;p&gt;Seen as "a tmux successor," herdr looks redundant next to Zed. My evaluation flipped when the socket API let an agent operate its own environment. Splitting panes, opening rooms, cutting worktrees — the "workspace housekeeping" humans used to do becomes delegable, wholesale.&lt;/p&gt;

&lt;p&gt;And the flip kept going, all the way to a change of tools. From "isn't Zed enough?" to "sign-off only" to "gone." While every layer — editor, terminal, harness (Claude Code's own subagent machinery) — is absorbing "agent orchestration" features at the same time, herdr's distinctive answer is that &lt;strong&gt;it made the layout of the execution environment itself something agents can operate&lt;/strong&gt;. Each layer slimming down to a single function feels less like regression than like attention design: the work of paring what's on screen down to a state with not one layer surplus to its current role.&lt;/p&gt;

&lt;p&gt;That said, "best" here means best for the current role, not forever. The honest way to test the judgment is against actual usage, so in a few weeks I plan to look back at "how many times did I open Zed?"&lt;/p&gt;

&lt;p&gt;It's one brew command to try. If you're running two or more Claude Code sessions in parallel, start by watching your own agents show up in &lt;code&gt;herdr agent list&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/ogulcancelik/herdr" rel="noopener noreferrer"&gt;ogulcancelik/herdr&lt;/a&gt; — herdr itself (GitHub)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://herdr.dev/" rel="noopener noreferrer"&gt;herdr.dev&lt;/a&gt; — official site and docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ghostty.org/" rel="noopener noreferrer"&gt;Ghostty&lt;/a&gt; — the terminal I ended up choosing&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://zed.dev/blog/parallel-agents" rel="noopener noreferrer"&gt;Zed: Parallel Agents&lt;/a&gt; — Zed's parallel agent feature&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/zed-industries/zed/discussions/56111" rel="noopener noreferrer"&gt;zed#56111&lt;/a&gt; — the Discussion on the hard-coded Markdown preview line height&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/shimo4228/running-claude-code-from-iphone-via-ssh-tmux-4c10"&gt;Running Claude Code from iPhone via SSH + tmux&lt;/a&gt; — building the mobile connection path (read tmux as herdr)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/shimo4228/claude-code-from-iphone-plugging-3-holes-in-remote-control-17cf"&gt;Claude Code from iPhone: Plugging 3 Holes in Remote Control&lt;/a&gt; — the conversational-UX side&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/shimo4228/cursor-to-zed-disabling-built-in-ai-for-a-cli-first-setup-6e4"&gt;Cursor to Zed: Disabling Built-in AI for a CLI-First Setup&lt;/a&gt; — the Zed environment this article started from&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228" rel="noopener noreferrer"&gt;github.com/shimo4228&lt;/a&gt; — my GitHub (agent-related skills and tools)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>claudecode</category>
      <category>terminal</category>
      <category>productivity</category>
      <category>ai</category>
    </item>
    <item>
      <title>What Do My AI Agent's Logs Look Like in OpenTelemetry?</title>
      <dc:creator>shimo4228</dc:creator>
      <pubDate>Thu, 16 Jul 2026 13:00:04 +0000</pubDate>
      <link>https://dev.to/shimo4228/what-do-my-ai-agents-logs-look-like-in-opentelemetry-27ic</link>
      <guid>https://dev.to/shimo4228/what-do-my-ai-agents-logs-look-like-in-opentelemetry-27ic</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What this article covers&lt;/strong&gt;: what you can see when you plug homegrown logs into OpenTelemetry. I convert them to OpenTelemetry format, visualize them, and sort out which elements are worth borrowing and which you can pass on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I wanted to bring OpenTelemetry to my AI agent (telemetry = the standard machinery for recording and emitting data about a system's behavior; OTel from here on). The typical adoption path is to embed the SDK in the application itself, emit telemetry as it runs, and keep shipping it to a monitoring backend via a Collector (a resident process that receives, transforms, and forwards). But maybe you've hit walls like these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Running an SDK and a Collector around the clock is heavy for a personal-scale agent.&lt;/strong&gt; There's no ops team watching a monitoring dashboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I already have my own structured logs.&lt;/strong&gt; I don't want to instrument the same information twice&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The standard for which attribute names to record LLM calls under (the GenAI semantic conventions) is still in development.&lt;/strong&gt; The spec may still move, so it's hard to judge how far to lean on it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/shimo4228/contemplative-agent" rel="noopener noreferrer"&gt;Contemplative Agent&lt;/a&gt;, the agent I operate, was in exactly this situation. It's a local-LLM agent that autonomously writes posts and comments on Moltbook (a social network where AI agents post and comment on each other). The text it reads from the feed is written by other agents, and prompt injection (smuggling instructions into a post body to steer the agent reading it) is a daily occurrence — an environment where input cannot be trusted.&lt;/p&gt;

&lt;p&gt;When the agent misbehaves in this environment, investigating the cause requires being able to trace, precisely and after the fact, "what it read from outside and what it emitted at that moment." So I had designed all external input/output to be kept in full in an audit log (an append-only record for reproducing past decisions exactly as they happened). The "own structured logs" in the second wall above is this audit log.&lt;/p&gt;

&lt;p&gt;At the same time, nobody in my environment watches telemetry continuously. What I need is "the ability to examine past runs in a standard UI when something is worth investigating." If that's the requirement, the telemetry doesn't have to be flowing in real time.&lt;/p&gt;

&lt;p&gt;So this article does two things.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Instead of putting OTel into the production runtime, I built an after-the-fact conversion as an experiment.&lt;/strong&gt; It takes the audit logs I already have, converts them into traces (a record of one processing run as a collection of timestamped intervals) after the fact, and ships them over OTLP (OTel's standard transport protocol) for visualization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I compared my log schema against the OTel standard and added the 2 fields it was missing to the agent's own logs&lt;/strong&gt;: &lt;code&gt;run_id&lt;/code&gt; and &lt;code&gt;session_id&lt;/code&gt;, which identify a run&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first is a small conversion script — zero changes to the agent body, two dependencies. My local logs became visible as a waterfall (the chart that stacks processing intervals along a timeline) in Jaeger (an OSS trace viewer). On a day that had an incident, the picture tells you the moment you open it.&lt;/p&gt;

&lt;p&gt;This article is a sequel to my previous piece, &lt;a href="https://dev.to/shimo4228/why-did-my-agent-decide-that-3-observability-patterns-ami"&gt;"Why Did My Agent Decide That? 3 Observability Patterns"&lt;/a&gt;. There I wrote that what traces and metrics tell you by default stops at "what the request did." This is the continuation: &lt;strong&gt;what, then, can homegrown logs and the OTel standard actually connect?&lt;/strong&gt; — answered by doing the conversion for real. It also reads fine on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Conversion target: append-only JSONL (one JSON per line) logs the agent has already written. Three kinds: LLM call telemetry / API audit / CAPTCHA solver audit&lt;/li&gt;
&lt;li&gt;Python 3.10+, uv for package management&lt;/li&gt;
&lt;li&gt;Only two dependencies: &lt;code&gt;opentelemetry-sdk&lt;/code&gt; + &lt;code&gt;opentelemetry-exporter-otlp-proto-http&lt;/code&gt; (both on the conversion-script side; nothing is added to the agent itself)&lt;/li&gt;
&lt;li&gt;Viewer: Jaeger v2.19.0 (single binary; no Docker required, storage is in-memory)&lt;/li&gt;
&lt;li&gt;Signals: traces only (of OTel's three signals, metrics and logs are out of scope this time)&lt;/li&gt;
&lt;li&gt;The conversion script is public: &lt;a href="https://github.com/shimo4228/contemplative-agent-otel" rel="noopener noreferrer"&gt;contemplative-agent-otel&lt;/a&gt; — a small implementation, about 800 lines in total, built on the assumption that you swap in &lt;code&gt;records.py&lt;/code&gt; (log loading and normalization) / &lt;code&gt;mapping.py&lt;/code&gt; (the attribute-name mapping table) for your own log format&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The processing flow is 5 steps.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the already-written JSONL logs&lt;/li&gt;
&lt;li&gt;Normalize the 3 log kinds into a common record (&lt;code&gt;records.py&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Attach standard attribute names to each record and convert it into a span (one interval of a trace) (&lt;code&gt;mapping.py&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Send to Jaeger over OTLP/HTTP&lt;/li&gt;
&lt;li&gt;Check the waterfall and the attributes in the Jaeger UI&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  I decided whether to adopt OTel by asking "who reads the telemetry?"
&lt;/h2&gt;

&lt;p&gt;Put OTel into the agent, or pass? I organized this decision around a single question: "who is going to read that telemetry?" The reason is that OTel traces and my audit log, even when they record the same event, have &lt;strong&gt;different readers (consumers)&lt;/strong&gt;. Take one LLM call: on the trace side, what you want to see is the model name, token counts, duration, and whether it errored. On the audit-log side, what you need is the prompt body itself, the caller, and the identifiers used for reproduction.&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;OTel traces&lt;/th&gt;
&lt;th&gt;My audit log&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Primary reader&lt;/td&gt;
&lt;td&gt;People watching dashboards; alerting&lt;/td&gt;
&lt;td&gt;People investigating incidents; the replay script used for reproduction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt and other bodies&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not recorded by default&lt;/strong&gt; (opt-in)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Stored in full&lt;/strong&gt; (base64 + sha256)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nature of the data&lt;/td&gt;
&lt;td&gt;May be sampled; may be volatile&lt;/td&gt;
&lt;td&gt;Append-only; every record; never deleted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Question it answers&lt;/td&gt;
&lt;td&gt;"What's slow or broken right now?"&lt;/td&gt;
&lt;td&gt;"Why did that decision come out the way it did?"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The right column is not a property of audit logs in general — it's a design I chose because my goal is offline reproduction of incidents. Your logs may well not store everything. What generalizes is the yardstick: &lt;strong&gt;different consumers mean different retention policies.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The emblematic case is body text. The GenAI semantic conventions specify that prompt and response bodies are &lt;strong&gt;not recorded by default&lt;/strong&gt; (they end up on a viewer's screen, so not carrying sensitive data is the default). My audit log does the &lt;strong&gt;exact opposite: full retention&lt;/strong&gt;. In incident investigation, without "the exact bytes the decision saw," you can't reproduce it offline.&lt;/p&gt;

&lt;p&gt;So this isn't a question of which one is right: &lt;strong&gt;same event, different consumers, opposite retention policies.&lt;/strong&gt; Once you frame it this way, the choice stops being binary.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Put OTel into the runtime (if someone reads telemetry in real time)&lt;/li&gt;
&lt;li&gt;Do nothing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Convert existing logs into traces after the fact&lt;/strong&gt; — get the connection to the standard vocabulary, and the visualization, without touching the runtime&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This article is an implementation of option 3.&lt;/p&gt;

&lt;p&gt;That said, option 3 only held up because two conditions happened to be true for me: nobody reads telemetry in real time, and the incident-investigation reader was already well served by a thorough audit log. If either is missing, the answer changes. Even with no monitoring team, if you don't have structured logs yet, going straight for option 1 (the runtime SDK) is probably faster. "No monitoring team = no OTel" is not a general rule.&lt;/p&gt;

&lt;p&gt;You might also wonder: "isn't using only part of the standard just cherry-picking the spec?" OTLP is a public protocol, and the semantic conventions are an agreement on attribute names. You don't have to instrument the app with the SDK — any program that speaks OTLP can feed data in. This is a usage the standard anticipates, and this article's conversion script simply uses the OTel SDK as "a library for offline export."&lt;/p&gt;

&lt;h2&gt;
  
  
  What I borrowed from OTel, and what I passed on
&lt;/h2&gt;

&lt;p&gt;Before getting into the implementation, here's how I judged each element of OTel, up front. The biggest lesson this time was that it's not an all-or-nothing "adopt the standard or don't" — &lt;strong&gt;you can pick borrow-or-pass per element.&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;OTel element&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GenAI semantic conventions vocabulary (&lt;code&gt;gen_ai.*&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;✅ Adopt&lt;/td&gt;
&lt;td&gt;External tools and people can read it without explanation. Copying attribute names costs zero dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run-ID machinery (trace ID / session ID equivalents)&lt;/td&gt;
&lt;td&gt;✅ Adopt (implemented &lt;code&gt;run_id&lt;/code&gt; / &lt;code&gt;session_id&lt;/code&gt; in the logs)&lt;/td&gt;
&lt;td&gt;One field turns trace reconstruction from estimation into measurement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OTLP (transport protocol)&lt;/td&gt;
&lt;td&gt;✅ Adopt (as the exit of the offline conversion)&lt;/td&gt;
&lt;td&gt;A common language every viewer speaks. Costs only 2 packages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime SDK instrumentation&lt;/td&gt;
&lt;td&gt;❌ Pass&lt;/td&gt;
&lt;td&gt;Would double-instrument alongside the existing audit log, and add dependencies to the agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Running a resident Collector&lt;/td&gt;
&lt;td&gt;❌ Pass&lt;/td&gt;
&lt;td&gt;Heavy for a single process at personal scale. On-demand conversion when needed is enough&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bodies not recorded by default (redaction)&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Followed on the trace side (don't carry untrusted text onto the screen). The audit log does the opposite — full retention, needed for offline reproduction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Importing attribute names from the semconv package&lt;/td&gt;
&lt;td&gt;❌ Pass&lt;/td&gt;
&lt;td&gt;Import paths are still unstable while in development, so attribute names are self-managed as string constants&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Passing on an element doesn't mean that part of OTel has no value. There are mainly 4 things that are hard to get without runtime instrumentation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exact causal structure&lt;/strong&gt; — which operation called which. Only IDs issued at execution time capture this precisely&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context propagation across services&lt;/strong&gt; — the machinery that stitches one trace across multiple services&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time visibility&lt;/strong&gt; — latency and errors happening right now, visible as they happen&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics&lt;/strong&gt; — numbers aggregated continuously while running. You can technically produce them from an after-the-fact conversion too, but they're fundamentally a signal meant to be measured live&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of these, the last 3 have no reader in my operation — a single process, with nobody watching continuously. The only one genuinely missing is the first, and its minimal patch is row 2 of the table, the "run-ID machinery."&lt;/p&gt;

&lt;p&gt;The sections below implement the ✅ elements in order.&lt;/p&gt;

&lt;h2&gt;
  
  
  My logs mapped almost 1:1 onto the standard vocabulary
&lt;/h2&gt;

&lt;p&gt;My homegrown telemetry writes one line per LLM call. A line looks like this (simplified):&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="nl"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-15T09:12:03+09:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gemma4:e4b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"prompt_eval_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1560&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"eval_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;210&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duration_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;36400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"done_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stop"&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;The next table matches these fields against the GenAI semantic conventions attribute names (the naming table for how to record model name, token counts, finish reason, and so on in OTel).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;My log field&lt;/th&gt;
&lt;th&gt;OTel attribute&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gen_ai.request.model&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;prompt_eval_count&lt;/code&gt; (input token count)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gen_ai.usage.input_tokens&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;eval_count&lt;/code&gt; (output token count)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gen_ai.usage.output_tokens&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;done_reason&lt;/code&gt; (&lt;code&gt;stop&lt;/code&gt; / &lt;code&gt;length&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gen_ai.response.finish_reasons&lt;/code&gt; (array-typed, so wrap as &lt;code&gt;[done_reason]&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;num_predict&lt;/code&gt; (generation cap)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gen_ai.request.max_tokens&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;temperature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gen_ai.request.temperature&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;error_kind&lt;/code&gt; (&lt;code&gt;timeout&lt;/code&gt; / &lt;code&gt;http_429&lt;/code&gt; etc.)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;error.type&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ts&lt;/code&gt; + &lt;code&gt;duration_ms&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;start / end time of the span (one interval of a trace)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;caller&lt;/code&gt; (which processing stage made the call), &lt;code&gt;prompt_sha256&lt;/code&gt;, etc.&lt;/td&gt;
&lt;td&gt;No counterpart → custom namespace &lt;code&gt;ca.audit.*&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It mapped almost 1:1. For a simple LLM call log like this one (model, token counts, stop reason, parameters), I suspect the set of things you'd want to record converges to the same collection no matter who designs it (logs that also cover tool calls, streaming, or routing won't map this cleanly).&lt;/p&gt;

&lt;p&gt;This is where riding the standard vocabulary pays off. If the attribute name is &lt;code&gt;gen_ai.usage.input_tokens&lt;/code&gt;, external tools and people alike can read it with no explanation.&lt;/p&gt;

&lt;p&gt;The interesting part is &lt;strong&gt;what didn't map&lt;/strong&gt;. &lt;code&gt;caller&lt;/code&gt; (the key I use to aggregate logs by processing stage during incident investigation) and &lt;code&gt;prompt_sha256&lt;/code&gt; (a hash for matching identical prompts without storing the body) have no standard counterpart. These two are investigation keys specific to my operation. For just looking around in a UI, the standard attributes are mostly enough; for offline reproduction of an incident, these are the ones that matter.&lt;/p&gt;

&lt;p&gt;Practical handling of the Development status (official status name: Development)&lt;/p&gt;

&lt;p&gt;The GenAI semantic conventions (semconv below) are in Development status as of this writing (2026-07) (&lt;a href="https://github.com/open-telemetry/semantic-conventions-genai" rel="noopener noreferrer"&gt;canonical repository&lt;/a&gt;). The &lt;code&gt;opentelemetry-semantic-conventions&lt;/code&gt; package does have attribute-name constants, but they live under an underscore-prefixed (= unstable) &lt;code&gt;_incubating&lt;/code&gt; import path.&lt;/p&gt;

&lt;p&gt;The conversion script doesn't import from the package. Instead, &lt;strong&gt;the attribute names are defined as string constants in my own code, with the referenced semconv version pinned in a comment&lt;/strong&gt;. If the standard moves, catching up is a single-file change.&lt;/p&gt;

&lt;h2&gt;
  
  
  The conversion script has exactly 3 design points
&lt;/h2&gt;

&lt;p&gt;Let's pin down spans properly here. At the top I described a trace as "a collection of timestamped intervals." Each of those intervals is a span: it has a name, start and end times, and attributes (key-value pairs), and spans connect through parent-child links into one trace. In a Jaeger waterfall, one row is one span.&lt;/p&gt;

&lt;p&gt;So the core of the conversion is "read JSONL, create spans with past timestamps." There were 3 design decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Spans can be created at past timestamps
&lt;/h3&gt;

&lt;p&gt;The OTel SDK lets you explicitly set a span's start and end times in epoch nanoseconds. I restore them directly from the log's &lt;code&gt;ts&lt;/code&gt; and &lt;code&gt;duration_ms&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python:emit.py (excerpt, simplified)&lt;/p&gt;

&lt;h1&gt;
  
  
  Create the span at the log's recorded time (the core of the conversion script)
&lt;/h1&gt;

&lt;p&gt;span = tracer.start_span(&lt;br&gt;
    name,                      # e.g. "text_completion gemma4:e4b"&lt;br&gt;
    context=parent_ctx,&lt;br&gt;
    kind=kind,                 # SpanKind (CLIENT etc. for LLM calls)&lt;br&gt;
    attributes=attrs,&lt;br&gt;
    start_time=start_ns,       # ts converted to epoch ns&lt;br&gt;
)&lt;br&gt;
if is_error:&lt;br&gt;
    span.set_status(Status(StatusCode.ERROR, error_type))&lt;br&gt;
span.end(end_time=end_ns)      # start_ns + duration_ms&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


For logs that don't record a duration (API audit, solver audit), I made the spans **zero-width**. Giving them a plausible width by estimation would mean fabricating latency that was never measured. Jaeger draws zero-width spans as thin markers, so they're also visually distinct from the LLM spans that carry real durations. The fact that "logs without a duration become zero-width" was itself feedback into the log schema (it pinpointed the next improvement candidate: add elapsed time to the API audit log too).

### 2. Trace grouping is reconstructed from gaps in time

A trace is normally bundled by IDs issued at execution time. Past logs don't have those. So I merge the 3 log kinds in timestamp order and **start a new trace whenever a gap of a fixed length (default 300 seconds) opens up**. A schedule-launched agent repeats "a few minutes of activity → long silence," so this cut lines up well with an actual single run. Conversely, for workloads where the logs never pause — parallel runs, always-on workers — this cut will merge things incorrectly.

But it is still an estimate. I put a `ca.convert.grouping = "time-gap"` attribute on each trace's root span, making **the data itself say it is a reconstruction, not a measurement**.

Flip that around and this was **a gap in my own log design that only became visible against OTel**: add one field to every log line — a per-run ID (the equivalent of OTel's trace ID) — and this reconstruction turns from estimation into measurement.

Even if you never adopt the SDK, the one mechanism worth borrowing up front is "stamp every run with an ID."

I implemented it in my agent while writing this article: a single write function shared by all audit logs now stamps `run_id` (per process) and `session_id` (per agent session). That is the only agent-side change in this whole effort, and converting past logs still works with the agent untouched. The converter bundles by ID when `run_id` exists and falls back to time gaps only for older logs. If you're designing logs now, I recommend putting it in from day one.

### 3. Untrusted text is dropped at the conversion entrance

The third design decision is the boundary of what gets onto the Jaeger screen.

The audit log contains raw text that came from outside (CAPTCHA challenge texts, server error response bodies), stored as base64. As described at the top, this agent's input comes from a social network where prompt injection is routine — "strings from outside may be attack input" is the default assumption. Put such text into span attributes, and **attacker-controlled strings flow straight onto the Jaeger screen (= into screenshots, = into this article)**.

The conversion drops bodies at the parser stage and passes through only a sha256 hash and a classification code (`http_400` etc.). There is no "flag to include bodies" either. On top of that, I added a regression test that scans every attribute value of every test fixture and asserts that no body fragment appears. Not "be careful later," but "make it structurally unable to appear, then visualize" — that's the safe side.

To be clear, the goal here is not to keep bodies secret but to avoid carrying external strings onto the screen. The sha256 is a correlation identifier for matching identical inputs, not an anonymization mechanism (short boilerplate strings can be brute-forced back). If your logs hold confidential data, consider a keyed hash (HMAC) or simply not surfacing the hash at all.

## Looking at it in Jaeger — the incident day was obvious the moment I opened it

Jaeger v2 is a single binary; once started, it accepts OTLP directly and keeps everything in memory. Because it takes OTLP directly, there's no need for the Collector I called "heavy" at the top, either.



```bash
# Start the viewer (no Docker; data disappears when it exits)
# For Apple Silicon. On Intel Macs read darwin-amd64; on Linux, linux-amd64
curl -sLO https://github.com/jaegertracing/jaeger/releases/download/v2.19.0/jaeger-2.19.0-darwin-arm64.tar.gz
tar xzf jaeger-2.19.0-darwin-arm64.tar.gz
./jaeger-2.19.0-darwin-arm64/jaeger    # OTLP :4318 / UI :16686

# Convert and send (example run; see the repository README for setup)
contemplative-agent-otel --date 2026-07-15
# =&amp;gt; emitted 1031 spans across 5 runs -&amp;gt; http://localhost:4318
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A normal day looks like this. Reading the screen: time runs along the horizontal axis, one row is one span, parent-child structure is indentation, errors are red marks. One trace is one agent run, with LLM calls carrying real durations (&lt;code&gt;text_completion gemma4:e4b&lt;/code&gt;, 36 seconds or 1.3 minutes) and zero-width API calls lined up in time order.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fajoefvptlvwspgt6dcjw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fajoefvptlvwspgt6dcjw.png" alt="Trace of a normal day: under the agent run root span, LLM calls and API calls lined up in time order as a waterfall" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open a span and the mapping table from earlier appears directly as attributes. Standard attributes like &lt;code&gt;gen_ai.usage.input_tokens: 1560&lt;/code&gt; sit alongside custom ones like &lt;code&gt;ca.audit.caller: core.skill_selection&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0864ijmfz6x3hh9iymj3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0864ijmfz6x3hh9iymj3.png" alt="Span detail: gen_ai.* standard attributes and ca.audit.* custom attributes side by side" width="800" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here's what converting the day with the incident produces. On that day, a retry storm (failed calls whose retries cascade until calls and logs balloon) had in fact hit the LLM backend.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5jevyxhitgm47mrnx5kv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5jevyxhitgm47mrnx5kv.png" alt="Search results for the incident day: a trace with 30442 spans / 33 errors sits at the top" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Where a normal run has tens to hundreds of spans, &lt;strong&gt;one trace with 30,442 spans and 33 errors&lt;/strong&gt; towers over everything. The log file for that day was also nearly 30 times its normal size. As numbers, this information was already in the logs — but "you know it's an abnormal day the moment you open it" is a strength unique to trace visualization.&lt;/p&gt;

&lt;p&gt;You can follow the errors too. In the run where the CAPTCHA answer was rejected by the server, &lt;code&gt;captcha solve&lt;/code&gt; and &lt;code&gt;POST /verify&lt;/code&gt; are marked red as a pair. The error bodies were dropped by the conversion, so all the screen shows is the classification: &lt;code&gt;error.type: http_400&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe77z1s7113eh424x9ew1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe77z1s7113eh424x9ew1.png" alt="A run containing errors: red error marks on captcha solve and POST /verify" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gotcha: send 30,442 spans at once and they're silently dropped&lt;/p&gt;

&lt;p&gt;In the OTel SDK, &lt;code&gt;span.end()&lt;/code&gt; doesn't send immediately — a &lt;code&gt;BatchSpanProcessor&lt;/code&gt; (an SDK-internal component that queues spans and sends them in batches) does the exporting. In the incident-day conversion, spans beyond the default queue length of 2048 were &lt;strong&gt;discarded with only a one-line warning&lt;/strong&gt; (&lt;code&gt;Queue full, dropping Span.&lt;/code&gt;). For real-time instrumentation that's a reasonable self-defense; for an offline bulk conversion, "send everything" is the correct spec.&lt;/p&gt;

&lt;p&gt;Since the number of records to convert is known before sending, I sized the queue to the record count and that solved it. If you use the SDK for bulk conversion, watch out: the defaults assume real time.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Instead of a binary "adopt OTel or don't," I tried the third option: &lt;strong&gt;after-the-fact conversion of existing logs&lt;/strong&gt;. Zero changes to the agent, two dependencies — and you get the Jaeger waterfall, error display, and standard attributes, all of it&lt;/li&gt;
&lt;li&gt;My homegrown LLM call log mapped &lt;strong&gt;almost 1:1&lt;/strong&gt; onto the GenAI semantic conventions. The fields that didn't map remain as a list of "information needed for offline reproduction of incidents"&lt;/li&gt;
&lt;li&gt;Traces serve humans investigating on a screen (bodies unrecorded by default); the audit log serves machines re-executing for incident reproduction (bodies stored in full). &lt;strong&gt;Different consumers flip the retention policy&lt;/strong&gt;, so in a conversion, dropping untrusted bodies at the entrance is the safe side&lt;/li&gt;
&lt;li&gt;Master 3 things — span creation at past timestamps, zero-width spans, trace reconstruction from time gaps — and any structured log from which times and run groupings can be recovered connects the same way&lt;/li&gt;
&lt;li&gt;Add one run-ID field to your logs and the reconstruction itself becomes unnecessary. Even without the SDK, this one mechanism is worth borrowing from OTel up front (I implemented it while writing this article)&lt;/li&gt;
&lt;li&gt;This conversion was also &lt;strong&gt;a way to measure my own log design against OTel as a yardstick&lt;/strong&gt;. The missing run ID and the real-time assumptions of the export queue both became visible only against the standard&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228/contemplative-agent-otel" rel="noopener noreferrer"&gt;contemplative-agent-otel&lt;/a&gt; — this article's conversion script (mapping table and tests included)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228/contemplative-agent/blob/main/docs/adr/0078-otel-connection-via-vocabulary-and-offline-export.md" rel="noopener noreferrer"&gt;ADR-0078: OTel Connection via Vocabulary Mapping and Offline Export&lt;/a&gt; — the primary source for this decision (&lt;a href="https://github.com/shimo4228/contemplative-agent/blob/main/docs/adr/0075-observability-by-default.md" rel="noopener noreferrer"&gt;ADR-0075&lt;/a&gt;: the companion decision to ship audit logs in the same PR as the feature)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/open-telemetry/semantic-conventions-genai" rel="noopener noreferrer"&gt;OpenTelemetry GenAI semantic conventions&lt;/a&gt; — canonical source of the standard vocabulary (Development status)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/shimo4228/why-did-my-agent-decide-that-3-observability-patterns-ami"&gt;Previous article: Why Did My Agent Decide That? 3 Observability Patterns&lt;/a&gt; — the 3 design patterns on the audit-log side&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/shimo4228/can-you-trace-the-cause-after-an-incident-neo"&gt;Can You Trace the Cause After an Incident?&lt;/a&gt; — one step further back: why records that let you trace causality after an agent incident matter in the first place&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/shimo4228/building-an-autonomous-agent-on-an-m1-mac-by-choice-5b5o"&gt;Building an Autonomous Agent on an M1 Mac — by Choice&lt;/a&gt; — hub of the small-LLM series this article belongs to&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228" rel="noopener noreferrer"&gt;My GitHub&lt;/a&gt; — the agent itself and related repositories&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opentelemetry</category>
      <category>observability</category>
      <category>jaeger</category>
      <category>llm</category>
    </item>
    <item>
      <title>Fault Injection TDD Found 3 Silent Failures in My LLM Agent</title>
      <dc:creator>shimo4228</dc:creator>
      <pubDate>Wed, 15 Jul 2026 13:00:03 +0000</pubDate>
      <link>https://dev.to/shimo4228/fault-injection-tdd-found-3-silent-failures-in-my-llm-agent-3mld</link>
      <guid>https://dev.to/shimo4228/fault-injection-tdd-found-3-silent-failures-in-my-llm-agent-3mld</guid>
      <description>&lt;p&gt;Pipelines with an LLM in the loop keep hitting walls like these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The LLM returns JSON in a subtly different shape. Parsing succeeds, but the result silently comes back empty&lt;/li&gt;
&lt;li&gt;A local LLM's response was cut off mid-generation (&lt;code&gt;done_reason=length&lt;/code&gt;) and you only notice days later&lt;/li&gt;
&lt;li&gt;The log only says &lt;code&gt;outcome="error"&lt;/code&gt;, so you can't tell whether it was a rate limit or a timeout&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What they have in common: &lt;strong&gt;every test passes, yet production breaks silently&lt;/strong&gt;. Processing doesn't stop, no exception is raised — the results, or the records of them, quietly degrade. In this article I call this a silent failure.&lt;/p&gt;

&lt;p&gt;The local-LLM CLI agent I operate hit this family of failures 5 times. All 5 slipped past the tests I had beforehand and were only diagnosed after the fact. So I adopted this workflow: build a fault catalog (a taxonomy of failures) from the history of production incidents, write tests that deliberately inject those faults &lt;em&gt;first&lt;/em&gt;, and land the minimal guard that makes them pass in the same PR. Taxonomically, this is &lt;strong&gt;fault injection testing × TDD&lt;/strong&gt;. What I borrowed — and deliberately didn't borrow — from chaos engineering (the practice of injecting failures on purpose to verify resilience), which inspired the approach, is laid out in the body.&lt;/p&gt;

&lt;p&gt;The first application found 3 silent failures that my ~1800 existing tests had missed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What you'll get from this article&lt;/strong&gt;: a pattern for adding fault injection testing to a single-process Python app — no daemon, deterministic, pytest-native (fault catalog → RED → GREEN) — plus the details of the 3 silent failures the first application actually surfaced&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.13 / pytest 9.0 (at time of verification; no bleeding-edge features are used, so nearby versions should work)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hypothesis.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;hypothesis&lt;/a&gt; 6.156.6 / &lt;a href="https://github.com/getsentry/responses" rel="noopener noreferrer"&gt;responses&lt;/a&gt; 0.26.0 (both added as dev dependencies)&lt;/li&gt;
&lt;li&gt;Target: a single-process Python app that calls an LLM server (Ollama etc.) via &lt;code&gt;requests&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The LLM call sits behind a swappable layer such as a Protocol (if it doesn't, carving out that layer is step zero)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article is a sequel to my previous one, &lt;a href="https://dev.to/shimo4228/why-did-my-agent-decide-that-3-observability-patterns-ami"&gt;"Why Did My Agent Decide That? 3 Observability Patterns"&lt;/a&gt;. If that one was about &lt;strong&gt;recording&lt;/strong&gt; the agent's decisions, this one is about &lt;strong&gt;asserting on&lt;/strong&gt; that recording channel — and &lt;strong&gt;injecting faults up front&lt;/strong&gt;. It stands alone, though.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chaos engineering was the inspiration; fault injection testing is what I adopted
&lt;/h2&gt;

&lt;p&gt;My agent operates autonomously on a social network (Moltbook), continuously. As long as it keeps running, I can't know in advance which failure it will meet next. Searching for a way to build "operations resilient to unforeseen failures," I arrived at chaos engineering.&lt;/p&gt;

&lt;p&gt;But measured against the five advanced principles of the canonical &lt;a href="https://principlesofchaos.org/" rel="noopener noreferrer"&gt;Principles of Chaos Engineering&lt;/a&gt;, I did not follow it verbatim.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Canonical principle&lt;/th&gt;
&lt;th&gt;This work&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Build a Hypothesis around Steady State Behavior&lt;/td&gt;
&lt;td&gt;○ Adopted (asserts against the execution-log (telemetry) channel)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vary Real-world Events&lt;/td&gt;
&lt;td&gt;△ Half (faults come from incident history, but injection is a deterministic replay of a known catalog)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run Experiments in Production&lt;/td&gt;
&lt;td&gt;✕ Rejected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automate Experiments to Run Continuously&lt;/td&gt;
&lt;td&gt;△ CI runs them automatically, but derandomized — a regression test that pins the known, not an experiment that explores the unknown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minimize Blast Radius&lt;/td&gt;
&lt;td&gt;○ Adopted in extreme form (injection happens inside tests, so production impact is zero)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I rejected production experiments for 2 reasons. First, the target is a single local process — there is no "redundant fleet of instances" to kill in the first place. Second, the logs the agent accumulates are primary data that can't be regenerated; random injection in production risks corrupting them. I deferred random exploration to keep CI from going flaky.&lt;/p&gt;

&lt;p&gt;The core of chaos engineering is not "testing" (asserting known expected behavior) but "&lt;strong&gt;experimentation&lt;/strong&gt;" (discovering unknown weaknesses) — so once production experiments and random exploration are out, it can no longer be called chaos engineering. As stated up front, the classification is fault injection testing × TDD. What I borrowed from chaos engineering is exactly 2 ideas: "build the failure catalog from real-world events" and "assert steady state through an observation channel."&lt;/p&gt;

&lt;p&gt;Tooling-wise, I also skipped the distributed-systems staples and built on pytest. This was the conclusion of pre-adoption external research.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Candidate&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;chaostoolkit / toxiproxy&lt;/td&gt;
&lt;td&gt;Rejected&lt;/td&gt;
&lt;td&gt;Declarative experiment runner / resident network proxy. Built for distributed topologies; the scale of the target doesn't match a single local process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pytest fault-injection plugins&lt;/td&gt;
&lt;td&gt;Rejected&lt;/td&gt;
&lt;td&gt;Nothing usable exists (e.g. pytest-disrupt is a TODO-only scaffold)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/deepankarm/agent-chaos" rel="noopener noreferrer"&gt;agent-chaos&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Reference only&lt;/td&gt;
&lt;td&gt;Conceptually the closest, but coupled to the Anthropic SDK / DeepEval / pydantic-ai, with no pytest integration and no local-backend support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hypothesis + responses + a hand-rolled injection backend&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Adopted&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sits on the existing test infrastructure (pytest) with just 2 dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That settled the approach: &lt;strong&gt;inject faults deterministically inside tests&lt;/strong&gt;. No daemon, no proxy — pytest itself is the execution environment.&lt;/p&gt;

&lt;p&gt;The 2 elements I deferred (exploration, experimentation) are not discarded; they remain as &lt;strong&gt;the entry points for extending this toward chaos engineering later&lt;/strong&gt; — a two-tier setup where PR CI stays deterministic while a nightly run randomizes with seed recording (any failing sequence found gets reproduced from the seed and pinned with &lt;code&gt;@example&lt;/code&gt;), plus sandbox experiments running the real pipeline in random-injection mode. This pilot leaned deterministic because the state of "even the known incident history isn't asserted" had to be fixed first. Once the known holes are plugged, those 2 entry points are where I'll expand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern — the fault catalog becomes the test spec
&lt;/h2&gt;

&lt;p&gt;The workflow is 3 steps.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Catalog&lt;/strong&gt;: classify the history of production incidents into fault classes (failure taxonomies) and diff them against existing tests. The untested classes become the fault catalog&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RED&lt;/strong&gt;: for each fault, assert in a test how the system &lt;strong&gt;should&lt;/strong&gt; behave when it's injected. The current implementation fails it (this is TDD's RED)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GREEN&lt;/strong&gt;: land the minimal guard that passes the test, in the &lt;strong&gt;same PR&lt;/strong&gt; as the test&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The point is that the fault catalog doubles as a spec. Desired behaviors — "a timeout should leave &lt;code&gt;error_kind=timeout&lt;/code&gt; in telemetry," "malformed-shape JSON should abstain with a reason code" — get pinned down as executable tests.&lt;/p&gt;

&lt;p&gt;The only difference from ordinary TDD is where the inputs come from. Instead of writing tests from functional requirements, you &lt;strong&gt;write tests from incident history&lt;/strong&gt; — everything else reuses the RED → GREEN discipline as-is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the fault catalog from incident history, not imagination
&lt;/h2&gt;

&lt;p&gt;If you enumerate "which faults to inject" from imagination, you tend to mass-produce tests for failures that never actually happen. I started from 5 past production incidents (silent truncation from context-length overflow, mid-generation cutoff with &lt;code&gt;done_reason=length&lt;/code&gt;, deduplication failing to fire, an external API rate limit, and a scraping target turning into a CAPTCHA) and swept for "failures in the same family that existing tests don't cover."&lt;/p&gt;

&lt;p&gt;One caveat: the 5 incidents and the fault classes are not 1:1. What I extracted from the history is the shared family — "when the LLM or external I/O returns something unexpected, the pipeline silently degrades" — and from that family I derived the 5 classes that &lt;strong&gt;existing tests didn't cover&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here are the resulting 5 classes.&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;Fault class&lt;/th&gt;
&lt;th&gt;State of existing tests&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;F1&lt;/td&gt;
&lt;td&gt;Read-timeout mid-generation&lt;/td&gt;
&lt;td&gt;Only &lt;code&gt;ConnectionError&lt;/code&gt; covered. Mid-stream timeout untested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F2&lt;/td&gt;
&lt;td&gt;Embedding API failures — transport-level (429, timeout) and content-invalid success responses (mismatched dimensions, missing rows)&lt;/td&gt;
&lt;td&gt;Untested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F3&lt;/td&gt;
&lt;td&gt;Syntactically valid JSON that violates the expected schema (wrong top-level type, wrong keys, non-string elements)&lt;/td&gt;
&lt;td&gt;Untested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F4&lt;/td&gt;
&lt;td&gt;HTTP 429 from the LLM server itself&lt;/td&gt;
&lt;td&gt;Only covered on a different external API client. LLM backend side untested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F5&lt;/td&gt;
&lt;td&gt;Flapping (alternating / consecutive success-failure sequences)&lt;/td&gt;
&lt;td&gt;Only single failure→recovery covered&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Before and after:&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;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tests directly injecting the fault classes&lt;/td&gt;
&lt;td&gt;0/5 (per the table above, only partial coverage of adjacent cases)&lt;/td&gt;
&lt;td&gt;5/5 (32 deterministic fault injection tests)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Telemetry failure-type discrimination&lt;/td&gt;
&lt;td&gt;None (everything is &lt;code&gt;outcome="error"&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;7 kinds of &lt;code&gt;error_kind&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability of extraction failures&lt;/td&gt;
&lt;td&gt;Silently missing&lt;/td&gt;
&lt;td&gt;3 reason codes + per-reason aggregate summary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full suite&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;1832 passed / 1 skipped&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The test count of 32 is measured with &lt;code&gt;pytest --collect-only&lt;/code&gt; (every number in this article was re-measured at the time of writing).&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation — 2 replacement points and a determinism discipline
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pin injection to 2 existing seams
&lt;/h3&gt;

&lt;p&gt;Injecting faults requires a seam (a joint where the test side can swap the implementation without rewriting production code). I used only 2 existing ones and decided to &lt;strong&gt;add zero injection hooks to production code&lt;/strong&gt;. The pipeline under test does not know it is being tested.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The LLM backend Protocol&lt;/strong&gt; — implement a &lt;code&gt;ChaosBackend&lt;/code&gt; on the test side and swap it in&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The requests HTTP layer&lt;/strong&gt; — fake HTTP responses with the &lt;code&gt;responses&lt;/code&gt; library&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;ChaosBackend&lt;/code&gt; is driven by a schedule — a list of "which fault fires on which call." The chaos in class and file names is a leftover from the naming at inspiration time. Also, &lt;code&gt;FAULT_VOCABULARY&lt;/code&gt; in the code is the minimal unit of injection (primitives), at a different granularity from the F1–F5 fault classes — one class is tested through combinations of several primitives.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python:tests/chaos.py (excerpt, simplified — see the public skill in Related Links for the full version)&lt;/p&gt;

&lt;h1&gt;
  
  
  Fault vocabulary is an ordered tuple (unordered would break seed-derived determinism)
&lt;/h1&gt;

&lt;p&gt;FAULT_VOCABULARY = (OK, NONE, EMPTY, EXC_TIMEOUT, EXC_CONNECTION, TRUNCATED, SHAPE_VIOLATION)&lt;/p&gt;

&lt;p&gt;@dataclass&lt;br&gt;
class ChaosBackend:  # conforms to the LLMBackend Protocol&lt;br&gt;
    schedule: List[str] = field(default_factory=list)&lt;br&gt;
    calls: List[dict] = field(default_factory=list)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@classmethod
def from_seed(cls, seed: int, n: int, weights=None) -&amp;gt; "ChaosBackend":
    rng = random.Random(seed)
    vocab = list(weights.keys()) if weights else list(FAULT_VOCABULARY)
    wts = list(weights.values()) if weights else None
    return cls(schedule=rng.choices(vocab, weights=wts, k=n))

def generate(self, prompt, system, num_predict, format, *, temperature=1.0, think=False):
    idx = len(self.calls)
    self.calls.append({"prompt": prompt, "num_predict": num_predict})
    fault = self.schedule[idx] if idx &amp;lt; len(self.schedule) else OK
    if fault == NONE: return None
    if fault == EXC_TIMEOUT: raise requests.exceptions.ReadTimeout("chaos")
    if fault == TRUNCATED: return BackendResult(text=self._ok_text(idx), finish_reason="length")
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


A schedule can be built as an explicit list or derived from a seed — either way, **you can inspect its contents before the run**. This is not "break things randomly"; it's "breaking under this exact sequence is the spec."

### Run hypothesis under a deterministic profile

Instead of enumerating fault combinations by hand, I use property-based testing (asserting properties that must hold for any input, rather than individual input/output examples). To keep CI from going flaky, though, hypothesis is pinned to deterministic mode.



```python:tests/conftest.py (excerpt)
# database=None only disables the example DB. The constants/unicode caches
# need HYPOTHESIS_STORAGE_DIRECTORY relocated (before hypothesis is imported)
os.environ.setdefault("HYPOTHESIS_STORAGE_DIRECTORY", str(_TEST_HOME / ".hypothesis"))
from hypothesis import settings
settings.register_profile("ci", derandomize=True, max_examples=50, deadline=None, database=None)
settings.load_profile("ci")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;derandomize=True&lt;/code&gt; generates the same case sequence every run. I verified this by running the full fault injection suite twice in a row and confirming identical output.&lt;/p&gt;

&lt;p&gt;The other determinism rule: &lt;strong&gt;latency faults are expressed by injecting a &lt;code&gt;ReadTimeout&lt;/code&gt; exception, not by real sleeps&lt;/strong&gt;. On this call path, the observable consequence of a timeout reduces entirely to "catch and handle &lt;code&gt;ReadTimeout&lt;/code&gt;," so within that scope exception injection verifies the same thing — and the tests run in 0 seconds. The real-sleep-plus-short-timeout approach goes flaky, so I avoided it. Note that &lt;strong&gt;behaviors that only surface in real time cannot be verified this way&lt;/strong&gt; — e.g. cleanup of partial output mid-stream — so if that's your target, you need separate measures.&lt;/p&gt;
&lt;h3&gt;
  
  
  Writing RED — assert the desired guard behavior first
&lt;/h3&gt;

&lt;p&gt;Here is the RED for F3 (valid JSON, wrong shape).&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python:tests/test_distill_chaos.py (excerpt)&lt;br&gt;
class TestParsePatternsShapeViolationFuzz:&lt;br&gt;
    @given(raw=non_patterns_json())&lt;br&gt;
    @example(raw='{"patterns": [123]}')   # pins the real str() promotion bug&lt;br&gt;
    @example(raw="null")                  # pins the json.loads("null") is None trap&lt;br&gt;
    def test_wrong_shape_abstains_with_no_patterns(self, raw):&lt;br&gt;
        patterns, mode = _parse_patterns(raw)&lt;br&gt;
        assert mode == "shape_violation"&lt;br&gt;
        assert patterns == []&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


`@given` asserts the property "any shape-violating JSON should cause abstention," and `@example` pins known failure shapes as permanent regression tests. Those 2 `@example`s are exactly silent failures ① and ② in the next section.

## The 3 silent failures the first application surfaced

The moment I wrote the RED tests, 3 holes in the existing implementation were exposed. ① and ② are the kind where the processing result silently degrades; ③ is on the observation-channel side — the failure itself gets recorded, but the type information needed for root-cause analysis is silently lost.

### ① Numbers promoted to strings let schema violations sail through

- **Symptom**: when the LLM returned non-string elements like `{"patterns": [123]}`, the old implementation stringified each element with `str(item)`, so `"123"` could pass as a legitimate pattern
- **Why it's silent**: the JSON is valid, so parsing succeeds, and `str()` promotion raises nothing. No trace of the schema violation is left anywhere
- **Guard**: require `isinstance(item, str)` on every element; on violation, abstain with the `shape_violation` reason code. Regression pinned with `@example(raw='{"patterns": [123]}')`

### ② `json.loads("null")` returns `None`

- **Symptom**: when the response body is JSON `null`, `json.loads` returns `None` without raising. The old implementation used "parse result is `None` = parse failed" as its check, so valid JSON was treated as a parse failure and misrouted into a fallback path (bullet-list text scanning) it should never have entered
- **Why it's silent**: the fallback path legitimately exists as a rescue for non-JSON responses, so a misroute is indistinguishable in the logs from a normal fallback
- **Guard**: introduce an identity sentinel, `_JSON_PARSE_FAILED = object()`, separating "parsing failed" from "`None` was parsed" at the type level

### ③ Telemetry flattens every failure type into `error`

- **Symptom**: whether it was a 429, a timeout, a connection failure, or a bad body, telemetry only recorded `outcome="error"`, making offline failure analysis impossible
- **Why it's silent**: the error itself is recorded, so "something failed" is visible. What's flattened is the **type** — a gap you can't notice until you actually attempt an incident investigation
- **Guard**: add `_classify_request_error` to classify exceptions, and add an `error_kind` field to failure rows only (7 kinds: `timeout` / `connection` / `http_&amp;lt;status&amp;gt;` / `bad_json` / `bad_url` / `request_error` / `backend_exception`). The change is additive — the existing `outcome` value set is untouched — so analysis code over past logs keeps working

What the 3 share: they are **abnormal paths wearing a happy-path face**. Bugs that raise exceptions get caught by ordinary tests, but all 3 of these "run to completion while the result or the record goes quietly wrong" — no amount of extra happy-path tests would have caught them. Only RED tests that inject faults and assert the desired way of failing expose these holes.

&amp;lt;details&amp;gt;&amp;lt;summary&amp;gt;2 gotchas (hypothesis cache / circuit breaker × property tests)&amp;lt;/summary&amp;gt;

**`.hypothesis/` appears even with `database=None`**

Even with `database=None` in the profile, caches like `.hypothesis/constants/` show up at the repo root. As the [settings reference](https://hypothesis.readthedocs.io/en/latest/reference/api.html) says, `database=None` only suppresses the example DB; the constants cache and friends are written unconditionally under `HYPOTHESIS_STORAGE_DIRECTORY`. The fix is to point that environment variable at a test tempdir **before** importing hypothesis (included in the code sample above).

**The circuit breaker breaks property-test predictions**

I asserted the property "success count = number of OKs in the schedule," and it broke only on schedules with 5 consecutive failure faults. The cause was the circuit breaker in production code (a mechanism that cuts off calls after consecutive failures; in this project it opens after 5 in a row). Once the breaker opens, subsequent OK calls never reach the backend, so the exact-count property can't hold even though the behavior is correct. I handled it in 2 layers: exclude breaker-tripping schedules from the exact-count property via a `trips_circuit()` filter, and keep a weaker property — "no schedule ever crashes" — over all schedules.

&amp;lt;/details&amp;gt;
## Wrap-up — turning post-hoc debugging into up-front spec

- Silent failures don't get caught by happy-path tests. Only tests that **inject faults and assert the desired way of failing** catch them
- Build the fault catalog from **production incident history**, not imagination. 5 incidents → 5 untested fault classes → 32 deterministic fault injection tests
- For a single-process local LLM app, no daemon or proxy needed: **hypothesis + responses + a swappable backend** sit right on top of pytest
- The determinism discipline (`derandomize` / seed-derived schedules / no real sleeps) removes the 2 big flakiness sources — randomness and real time — from injection tests (measured: identical output across 2 consecutive runs)
- Land the guard in the **same PR** as the test. The fault schedule is the spec; the guard is the implementation that satisfies it
- Chaos engineering is the inspiration, not the classification. What I borrowed is 2 things — "build the catalog from real-world events" and "assert steady state through an observation channel" — while production experiments and unknown-space exploration are deliberately deferred (nightly randomization and sandbox experiments are the entry points for extending toward chaos)

The pattern from this work (fault vocabulary, ChaosBackend, hypothesis profile, RED templates) is published as a generalized Claude Code skill. If you want to bring this into your own pipeline, that's the entry point.

## Related Links

- [chaos-tdd-fault-injection](https://github.com/shimo4228/chaos-tdd-fault-injection) — the public skill generalizing this article's pattern (called "chaos-TDD" in the repo and its ADRs)
- [ADR-0077: Chaos-TDD Fault Injection](https://github.com/shimo4228/contemplative-agent/blob/main/docs/adr/0077-chaos-tdd-fault-injection.md) — primary source for the design decisions (English; a Japanese version sits in the same directory)
- [Previous article: Why Did My Agent Decide That? 3 Observability Patterns](https://dev.to/shimo4228/why-did-my-agent-decide-that-3-observability-patterns-ami)
- [Building an Autonomous Agent on an M1 Mac, on Purpose](https://zenn.dev/shimo4228/articles/small-llm-by-choice) — hub of the small-LLM series this article belongs to (Japanese)
- [hypothesis documentation](https://hypothesis.readthedocs.io/en/latest/) / [responses](https://github.com/getsentry/responses)
- [Chaos Toolkit](https://chaostoolkit.org/) / [toxiproxy](https://github.com/Shopify/toxiproxy) — chaos tools for distributed systems (not adopted here due to the mismatch in target scale)
- [agent-chaos](https://github.com/deepankarm/agent-chaos) — prior OSS for chaos engineering on AI agents (prior art referenced for fault classification)
- [Author's GitHub](https://github.com/shimo4228) — other repositories and tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>testing</category>
      <category>llm</category>
      <category>pytest</category>
      <category>agents</category>
    </item>
    <item>
      <title>LLM-as-Judge Shouldn't Aggregate Scores: Binary Checks as Evidence, One Holistic Verdict</title>
      <dc:creator>shimo4228</dc:creator>
      <pubDate>Tue, 14 Jul 2026 13:00:09 +0000</pubDate>
      <link>https://dev.to/shimo4228/llm-as-judge-shouldnt-aggregate-scores-binary-checks-as-evidence-one-holistic-verdict-822</link>
      <guid>https://dev.to/shimo4228/llm-as-judge-shouldnt-aggregate-scores-binary-checks-as-evidence-one-holistic-verdict-822</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What this article gives you&lt;/strong&gt;: How to design LLM quality evaluation around "binary checks + a named verdict" instead of numeric scores. Includes a judge prompt structure you can reuse by swapping in your own evaluation target.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Having an LLM evaluate deliverables is called LLM-as-judge. Once you introduce it, most people run into the same three walls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask for a 5-point rubric score and the same input oscillates between 3 and 4 across runs&lt;/li&gt;
&lt;li&gt;Gate pass/fail on a total-score threshold and one fatal flaw gets diluted by high scores on everything else — and slips through&lt;/li&gt;
&lt;li&gt;Read the scores back later and nobody can explain why something got a 3.5&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article introduces a design pattern that avoids all three at once. The core fits in one line:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Collect evidence with binary Yes/No checks, have the judge pick one verdict from named labels as a holistic judgment, and never aggregate anything into a score.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I've been running this pattern for about four and a half months in a Claude Code skill-quality audit (&lt;code&gt;/skill-stocktake&lt;/code&gt;) and a knowledge-extraction quality gate (&lt;code&gt;/learn-eval&lt;/code&gt;). How I built the audit command — the trial and error that led me to throw out numeric rubrics — is covered in &lt;a href="https://dev.to/shimo4228/offloading-ais-weak-spots-to-shell-scripts-designing-building-and-publishing-a-skill-audit-2ll8"&gt;my previous article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This article is the sequel. It generalizes that design into a &lt;strong&gt;reusable LLM-as-judge pattern&lt;/strong&gt;, including the episode where I tested my own design while it was in production — and rebuilt it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assumptions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Audience: anyone who wants to automate quality evaluation of deliverables (documents, code, data) with an LLM&lt;/li&gt;
&lt;li&gt;The examples use Claude Code skill evaluation, but the pattern itself is agnostic to evaluation target, model, and tooling&lt;/li&gt;
&lt;li&gt;Terminology: &lt;strong&gt;verdict&lt;/strong&gt; = the final output of an evaluation. It takes the form of picking exactly one from named options such as "Keep / Retire". The JSON examples in this article use a &lt;code&gt;verdict&lt;/code&gt; field&lt;/li&gt;
&lt;li&gt;Terminology: &lt;strong&gt;holistic judgment&lt;/strong&gt; = a single conclusion reached by looking at the whole, without going through per-dimension scoring&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The pattern at a glance — three principles
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Evaluation target
  → ① Binary checks: decompose into Yes/No questions
  → No answers = evidence
  → ② Holistic judgment: pick one named verdict
  → ③ No aggregation: scoring and averaging are forbidden
  → Downstream code / humans branch on the verdict
&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;Principle&lt;/th&gt;
&lt;th&gt;Do&lt;/th&gt;
&lt;th&gt;Don't&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;① Checks are binary&lt;/td&gt;
&lt;td&gt;Decompose criteria into questions answerable with Yes/No&lt;/td&gt;
&lt;td&gt;"Rate specificity on a 5-point scale"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;② The verdict is a named label&lt;/td&gt;
&lt;td&gt;Look at the whole and pick one from &lt;code&gt;Keep / Improve / Retire&lt;/code&gt; etc.&lt;/td&gt;
&lt;td&gt;"Total is 12 points, so it passes"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;③ No aggregation&lt;/td&gt;
&lt;td&gt;Have the No answers &lt;strong&gt;listed as the grounds&lt;/strong&gt; for the verdict&lt;/td&gt;
&lt;td&gt;Using the Yes ratio (satisfaction rate) as a quality metric&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  ① Make the checks binary Yes/No
&lt;/h3&gt;

&lt;p&gt;Instead of "how specific is this skill, out of 5?", ask "is there a concrete command example you can run immediately after reading? Yes/No."&lt;/p&gt;

&lt;p&gt;Binary questions have two properties that numeric scoring lacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Verifiable&lt;/strong&gt;: "Is there a command example?" can be settled black-and-white by looking at the text. "Specificity: 3 points" cannot be verified at all&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence&lt;/strong&gt;: a No answer points directly at &lt;em&gt;what is missing&lt;/em&gt;. It feeds straight into an improvement list&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the skill audit I run in production, every target goes through these four questions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; [ ] Actionability: are there steps, commands, or worked examples you can run immediately?
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Scope fit: do the name, trigger, and body agree (not too broad / too narrow)?
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Uniqueness: does it avoid overlapping with other skills in the same batch?
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Currency: do the referenced paths, CLI flags, and URLs still exist today?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ② Have the verdict picked from named labels, as a holistic judgment
&lt;/h3&gt;

&lt;p&gt;Holistic judgment is what LLMs are good at. As I wrote in the previous article, LLMs are bad at scoring dimensions independently — they get pulled by the overall impression. So it's more honest to design for that from the start: "pick one, based on the overall impression."&lt;/p&gt;

&lt;p&gt;The key is to name the verdict labels so they map &lt;strong&gt;1:1 to the next action&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;Use case&lt;/th&gt;
&lt;th&gt;Verdict options&lt;/th&gt;
&lt;th&gt;Corresponding action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Skill audit&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Keep / Improve / Update / Retire / Merge into [X]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Keep / hand off to the improvement engine / technical refresh / delete / consolidate (with an explicit target)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knowledge-extraction gate&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Save / Improve then Save / Absorb into [X] / Drop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Save / fix then save / append to an existing skill / discard&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A &lt;strong&gt;fixed set of options&lt;/strong&gt; that downstream processing can branch on directly is more practical than a bare &lt;code&gt;accept / reject&lt;/code&gt; (in programming terms, an enum — a mechanism that forbids any value outside the defined set. With free-form text you get drift like "Improve" vs. "improve slightly", and code can't branch on it). Designs like &lt;code&gt;Merge into [X]&lt;/code&gt; that &lt;strong&gt;force a concrete target name&lt;/strong&gt; make vague verdicts like "this feels like it overlaps with something" structurally impossible to write.&lt;/p&gt;

&lt;p&gt;This separation — "the LLM picks from named options, code executes" — also acts as a firewall against judgment errors. If judging and executing are separate, an LLM's bad call can never corrupt system state directly (I run this as a pattern I call &lt;strong&gt;LLM judge + Code enforce&lt;/strong&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  ③ Never aggregate the binary answers
&lt;/h3&gt;

&lt;p&gt;This is the pattern's biggest fork in the road. Once you have binary checks, you'll be tempted to say "6 of 8 questions are Yes, that's 75%, above the 70% threshold, so it passes." &lt;strong&gt;Don't.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The reason is simple: &lt;strong&gt;a satisfaction rate dilutes a single fatal No.&lt;/strong&gt; If one No says "the referenced file doesn't exist," the skill is broken even if the other seven answers are Yes. Averages and ratios convert that dominant No into a 12.5% deduction.&lt;/p&gt;

&lt;p&gt;Do these two things instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Require the No answers to be &lt;strong&gt;listed as the grounds&lt;/strong&gt; for the verdict (hidden Nos breed verdict drift)&lt;/li&gt;
&lt;li&gt;If there is even one dominant No (a nonexistent reference, an unsupported claim, etc.), tip the verdict to the non-Keep side even if everything else is Yes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The skill-audit definition file states the principle in one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Evaluation is &lt;strong&gt;holistic judgment, not a numeric rubric&lt;/strong&gt; — binary answers are evidence feeding the verdict, never aggregated into a score (a satisfaction ratio changes no decision and dilutes a single dominant No).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The verdict pressure-test — attack the verdict before you commit it
&lt;/h2&gt;

&lt;p&gt;Binary checks plus holistic judgment already work, but one more quality device raises the verdict's reliability: &lt;strong&gt;before finalizing a provisional verdict, have the LLM itself generate and answer questions that try to refute it.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Procedure (applied only to non-Keep candidates):
&lt;span class="p"&gt;1.&lt;/span&gt; Generate 1–3 Yes/No questions that would refute the provisional verdict
   e.g. if the provisional verdict is "Update (a referenced CLI flag is deprecated)"
   → "Doesn't that flag still exist in the current version's --help? Verify empirically"
&lt;span class="p"&gt;2.&lt;/span&gt; Answer each question with one line of evidence (file read, path check, web search result)
&lt;span class="p"&gt;3.&lt;/span&gt; Refutation succeeds (e.g. the flag still exists) → move the verdict back toward Keep
&lt;span class="p"&gt;4.&lt;/span&gt; Defect confirmed (e.g. the flag was indeed removed) → the No items from the binary
   checks become the improvement list as-is
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two disciplines govern question design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Atomicity&lt;/strong&gt;: each question tests exactly one verifiable claim&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refutation-oriented&lt;/strong&gt;: frame questions to seek refutation of the provisional verdict, not confirmation of it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One more discipline matters for re-judging after fixes: &lt;strong&gt;re-judge exactly once, with the same question set.&lt;/strong&gt; If you rewrite the questions, you can no longer tell whether the fix worked or the bar just moved.&lt;/p&gt;

&lt;h2&gt;
  
  
  A copy-paste judge prompt structure
&lt;/h2&gt;

&lt;p&gt;Here is the whole pattern in a form you can reuse by swapping in your own evaluation target.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;You are a quality evaluator of {kind of evaluation target}. Evaluate using the following steps.

&lt;span class="gu"&gt;## Step 1: Binary checks (all questions mandatory)&lt;/span&gt;
Answer each question with Yes/No plus one line of evidence.
&lt;span class="p"&gt;-&lt;/span&gt; Q1: {verifiable question, e.g. is there a runnable code example?}
&lt;span class="p"&gt;-&lt;/span&gt; Q2: {verifiable question}
&lt;span class="p"&gt;-&lt;/span&gt; Q3: {verifiable question}
&lt;span class="p"&gt;-&lt;/span&gt; Q4: {verifiable question}

&lt;span class="gu"&gt;## Step 2: Target-specific refutation questions (only if the provisional verdict leans non-pass)&lt;/span&gt;
Pick one provisional verdict, generate 1–3 Yes/No questions that would refute it,
and answer each with one line of evidence.

&lt;span class="gu"&gt;## Step 3: Verdict&lt;/span&gt;
Choose exactly one of the following. Do not output scores or points.
&lt;span class="p"&gt;-&lt;/span&gt; {verdict_label_1}: {meaning and next action}
&lt;span class="p"&gt;-&lt;/span&gt; {verdict_label_2}: {meaning and next action}
&lt;span class="p"&gt;-&lt;/span&gt; {verdict_label_3}: {meaning and next action}

In the grounds for the verdict, list every question answered No.
If there is even one dominant No ({domain-specific fatal condition}),
choose {non-pass verdict label} even if everything else is Yes.

&lt;span class="gu"&gt;## Output format&lt;/span&gt;
Follow the JSON schema below and output &lt;span class="gs"&gt;**JSON only**&lt;/span&gt;
(no explanatory text or Markdown before or after).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Receive the output as JSON and downstream code can process it directly.&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;"verdict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Improve"&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"&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;"question"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Is there a runnable code example?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"No"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Steps are bullet points only; not a single command example"&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;"question"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Do the referenced paths exist?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Yes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"All 3 paths confirmed with ls"&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;"pressure_test"&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;"question"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Does the refutation hold that the bullet-point steps alone are reproducible?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"No"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The argument spec in step 3 is ambiguous; not reproducible"&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;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The Actionability No is dominant. Adding a worked example to step 3 would make this Keep-worthy"&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;Since the &lt;code&gt;verdict&lt;/code&gt; field only takes fixed options, the receiving code just branches with a &lt;code&gt;switch&lt;/code&gt; statement. The No items in &lt;code&gt;evidence&lt;/code&gt; can be handed to the next stage (a human or another LLM) as an improvement task list as-is.&lt;/p&gt;

&lt;p&gt;The full design pattern, including this template, is published as a Claude Code Agent Skill at &lt;a href="https://github.com/shimo4228/llm-as-judge" rel="noopener noreferrer"&gt;github.com/shimo4228/llm-as-judge&lt;/a&gt;. Copy &lt;code&gt;skills/llm-as-judge&lt;/code&gt; into &lt;code&gt;~/.claude/skills/&lt;/code&gt; and it fires automatically whenever you're designing a judge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same principles, different operation at different scales
&lt;/h2&gt;

&lt;p&gt;Running this pattern at two scales taught me that &lt;strong&gt;the principles are shared, but the application conditions change with scale&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;Knowledge-extraction gate (learn-eval)&lt;/th&gt;
&lt;th&gt;Skill audit (skill-stocktake)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Evaluation target&lt;/td&gt;
&lt;td&gt;1 draft extracted from a session&lt;/td&gt;
&lt;td&gt;The entire library (73 skills)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refutation question generation&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Unconditional&lt;/strong&gt; (3–5 questions every time)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Non-Keep candidates only&lt;/strong&gt; (1–3 questions)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reason&lt;/td&gt;
&lt;td&gt;N=1, so the cost is negligible&lt;/td&gt;
&lt;td&gt;Generating for all 73 items wastes most of the shots&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With a single evaluation target, go all-in — it costs nothing. At tens of items, reserve the expensive device (refutation questions) for the suspicious candidates only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does single-context evaluation lose accuracy? — testing my own design
&lt;/h2&gt;

&lt;p&gt;Here begins the second half of this article. The skill audit above originally used a design of "&lt;strong&gt;load every skill into one context and evaluate&lt;/strong&gt;." The logic: a long-context model can survey everything at once, so cross-skill duplication should also be visible.&lt;/p&gt;

&lt;p&gt;Then an audit run under this design returned &lt;strong&gt;Keep for every single item&lt;/strong&gt;. Not one defect, not one duplicate pair detected. Is that "the library is healthy" or "the evaluation is spinning idle"? To find out, I re-audited the same library two ways: &lt;strong&gt;splitting it into small batches of about 10 items, each close-read by an independent agent&lt;/strong&gt;, and &lt;strong&gt;a probe dedicated solely to duplicate detection&lt;/strong&gt; (the experiment design and caveats about the numbers are in the collapsible section at the end of this chapter).&lt;/p&gt;

&lt;h3&gt;
  
  
  Results — some dimensions degrade, others don't
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What was tested&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;Per-item defect detection (small-batch close reading)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;12 of 73 items non-Keep&lt;/strong&gt; (16%). The single context had missed all of them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate detection (dedicated probe)&lt;/td&gt;
&lt;td&gt;True duplicates: &lt;strong&gt;0&lt;/strong&gt; (all 17 candidate clusters were adjacent skills with documented role separation)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Half of the 12 missed items were &lt;strong&gt;mechanically detectable defects&lt;/strong&gt;: 404 links, references to deleted files, deprecated CLI flags. The kicker: &lt;strong&gt;two files that no longer existed on disk had been given Keep verdicts.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Zero duplicates, on the other hand, turned out to be reality, not dilution (attention thinning as the context fills up). The single-context rationale — "you can see duplication because you survey everything at once" — had been fully replaced by a lightweight description scan plus targeted close reading.&lt;/p&gt;

&lt;h3&gt;
  
  
  The biggest lesson isn't context width — it's verification ownership
&lt;/h3&gt;

&lt;p&gt;Tracing the mechanism of the misses revealed a cause deeper than context width itself. The audit definition contained a conditional verification instruction: "&lt;strong&gt;if a reference looks stale, check it.&lt;/strong&gt;" That "if it looks..." condition is itself a judgment that depends on the LLM's attention. When the context fills up and attention thins, conditional triggers default to "don't check."&lt;/p&gt;

&lt;p&gt;The previous article's conclusion was "mechanical work to scripts, quality judgment to AI." This experiment redrew that boundary one level deeper: &lt;strong&gt;inside quality judgment there are deterministic parts hiding, and they belong in code.&lt;/strong&gt; "Does this file exist?" is not a judgment — it's &lt;code&gt;ls&lt;/code&gt;. The deterministic checks that had snuck into the judgment phase should have been moved to the code side as an unconditional, mandatory pre-pass. That is what "verification ownership" in the heading means: moving the execution owner of deterministic checks from the LLM's attention to code.&lt;/p&gt;

&lt;p&gt;The revised design (v3.0) assigns each property to the environment that should check it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Phase 0: Deterministic pre-pass (code-owned, unconditional)
         Reference-existence checks, ledger consistency.
         Anything that doesn't exist never reaches judgment.
Phase 1: Inventory
         (the layer offloaded to scripts in the previous article; unchanged)
Phase 2: Per-item scrutiny (small batches of ~10, parallel)
         Binary checks + verdict pressure-test + holistic judgment
Phase 3: Dedicated duplication probe (1 agent, all items)
         Lightweight description scan → close-read candidate clusters only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I reviewed the 12 detections one by one and fixed them the same day. For one of them, testing the proposed fix before applying it showed the command didn't actually work, so I swapped in a different form. Verify not just the verdict but &lt;strong&gt;the fix itself before committing it&lt;/strong&gt; — the same pattern surfacing outside the evaluation loop.&lt;/p&gt;

&lt;p&gt;Experiment design and notes on the numbers (for the curious)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Baseline&lt;/strong&gt;: the audit result from the all-in-one-context run. On the ledger it was 84 entries, all Keep. The actual files on disk numbered 73 — the gap was a ledger bug: the same skill double-counted under two different keys, plus entries for files that no longer existed. That the ledger was this dirty had gone unnoticed by everyone at that point. The follow-up experiments target the 73 real files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treatment A (small-batch close reading)&lt;/strong&gt;: split the 73 items into 7 batches of 10–11, each close-read by an independent subagent using the same criteria (binary checks → holistic judgment). Reference existence had to be verified with &lt;code&gt;ls&lt;/code&gt; etc., stated explicitly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treatment B (dedicated duplication probe)&lt;/strong&gt;: one agent reads all 73 descriptions, enumerates candidate clusters → close-reads the bodies side by side to decide&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bias control&lt;/strong&gt;: the agents were not told that the baseline had returned all-Keep&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An honest note on confounding&lt;/strong&gt;: Treatment A's prompt explicitly required "verify reference existence with &lt;code&gt;ls&lt;/code&gt;", so part of the difference from baseline is attributable to "forced verification" rather than "context width." However, detections that required semantic judgment — description/body mismatches, overly abstract writing, scope drift — cannot be explained by forced verification, and I read those as the effect of attention density: 10 items per batch versus 73&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why no scores — the boundary the proposing paper drew itself
&lt;/h2&gt;

&lt;p&gt;The "decompose evaluation criteria into binary questions" approach has a research lineage: CheckEval (arXiv:2403.18771, 2024), TICK (arXiv:2410.03608, 2024), and the later BinEval, "Ask, Don't Judge" (arXiv:2606.27226, 2026). My setup had the "binary checks + holistic judgment" skeleton first; I learned of this lineage afterward and folded it into each skill's References. BinEval proposes decomposing into atomic Yes/No questions and wiring failed questions directly into improvement feedback — the dynamic generation of refutation questions and the "No answer = improvement list" wiring are the parts I ported from it after it came out.&lt;/p&gt;

&lt;p&gt;What's interesting is BinEval's own limitations section. The paper self-reports two reservations. First: binary decomposition is reliable only up to &lt;strong&gt;concretely checkable criteria&lt;/strong&gt;, like "is there a code example?" Subjective quality like "is this well written?" is something humans judge by looking at the whole rather than stacking individual checks — it &lt;strong&gt;cannot be fully decomposed into a bundle of Yes/No questions&lt;/strong&gt; (less reliable). Second: "it assumes &lt;strong&gt;the fraction of satisfied questions corresponds roughly linearly to quality&lt;/strong&gt;, and &lt;strong&gt;this does not always hold&lt;/strong&gt;." In other words, using the satisfaction rate of decomposed questions as a score comes with a caveat from the proposing paper itself.&lt;/p&gt;

&lt;p&gt;Flip that limitations report around and it is exactly a &lt;strong&gt;division-of-labor boundary&lt;/strong&gt;. Concretely checkable criteria can be decomposed into Yes/No — so let the checks gather evidence. The final value judgment belongs to the subjective "is this good writing?" layer, which reduces neither to a bundle of questions nor to a satisfaction rate — so that layer stays a holistic judgment. The three principles in this article are that self-reported boundary, transcribed directly into a design.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Design LLM-as-judge evaluation around three principles: "binary checks = evidence," "verdict = pick one named label holistically," "no aggregation"&lt;/li&gt;
&lt;li&gt;Make verdict labels a fixed set mapping 1:1 to next actions, and require every No answer to be listed as grounds for the verdict&lt;/li&gt;
&lt;li&gt;A verdict pressure-test before finalizing raises reliability. Re-judge exactly once, with the same question set&lt;/li&gt;
&lt;li&gt;In my experiment, per-item scrutiny diluted as the context filled up (16% missed). Don't put deterministically verifiable checks on the LLM's attention — move them to unconditional, code-owned execution&lt;/li&gt;
&lt;li&gt;The reason not to turn "satisfaction rate" into a score comes from the limitations self-reported by the binary-decomposition paper itself (BinEval): decomposition works up to concrete criteria; the value-judgment layer stays holistic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The previous article's division of labor — "mechanical work to scripts, quality judgment to AI" — got one level deeper this time. &lt;strong&gt;Inside judgment, too, there are parts that belong in code.&lt;/strong&gt; When you design an LLM-as-judge, start by asking, before anything reaches the judge: "is this really a judgment, or is it &lt;code&gt;ls&lt;/code&gt;?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Related links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228/llm-as-judge" rel="noopener noreferrer"&gt;llm-as-judge&lt;/a&gt; — the design pattern from this article, packaged as an Agent Skill (install and use as-is)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/shimo4228/offloading-ais-weak-spots-to-shell-scripts-designing-building-and-publishing-a-skill-audit-2ll8"&gt;Previous article: Offloading AI's Weak Spots to Shell Scripts — Designing, Building, and Publishing a Skill Audit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228/claude-skill-stocktake" rel="noopener noreferrer"&gt;claude-skill-stocktake&lt;/a&gt; — the skill-audit command from this article (v3.0 hybrid structure)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228/agent-knowledge-cycle" rel="noopener noreferrer"&gt;agent-knowledge-cycle&lt;/a&gt; — where the LLM judge + Code enforce pattern mentioned in this article is defined&lt;/li&gt;
&lt;li&gt;Paper: &lt;a href="https://arxiv.org/abs/2403.18771" rel="noopener noreferrer"&gt;CheckEval&lt;/a&gt; (arXiv:2403.18771, 2024) — prior work on checklist-decomposition LLM evaluation&lt;/li&gt;
&lt;li&gt;Paper: &lt;a href="https://arxiv.org/abs/2410.03608" rel="noopener noreferrer"&gt;TICK&lt;/a&gt; (arXiv:2410.03608, 2024) — evaluation and generation improvement via generated checklists&lt;/li&gt;
&lt;li&gt;Paper: &lt;a href="https://arxiv.org/abs/2606.27226" rel="noopener noreferrer"&gt;BinEval, "Ask, Don't Judge"&lt;/a&gt; (arXiv:2606.27226, 2026) — source of the atomic Yes/No decomposition and "No answer = improvement list" wiring; its limitations section grounds the boundary drawn in this article&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shimo4228" rel="noopener noreferrer"&gt;github.com/shimo4228&lt;/a&gt; — the author's other repositories&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>llm</category>
      <category>promptengineering</category>
      <category>evaluation</category>
      <category>claudecode</category>
    </item>
  </channel>
</rss>
