<?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: Abhinav Pangaria</title>
    <description>The latest articles on DEV Community by Abhinav Pangaria (@abhinav_pangaria).</description>
    <link>https://dev.to/abhinav_pangaria</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%2F4135431%2F5e7fc196-6d05-4c1a-b5fa-9c7a25b64a30.png</url>
      <title>DEV Community: Abhinav Pangaria</title>
      <link>https://dev.to/abhinav_pangaria</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/abhinav_pangaria"/>
    <language>en</language>
    <item>
      <title>Making Verification Visible</title>
      <dc:creator>Abhinav Pangaria</dc:creator>
      <pubDate>Tue, 22 Sep 2026 03:34:10 +0000</pubDate>
      <link>https://dev.to/abhinav_pangaria/making-verification-visible-151l</link>
      <guid>https://dev.to/abhinav_pangaria/making-verification-visible-151l</guid>
      <description>&lt;p&gt;The hackathon gate had one output: a line in the terminal saying a task passed, failed, or needed a human. That line is honest, but it is small. It does not tell you what the agent touched, which requirements have a test behind them, which of those tests have ever passed in a browser, or why a task has been blocked since last night.&lt;/p&gt;

&lt;p&gt;The rebuild's answer was a dashboard, and it is the part people miss when they hear GuardianKane described as a Stop hook. This is a tour of it, using screenshots from the &lt;a href="https://github.com/18Abhinav07/guardiankane-ecommerce-demo" rel="noopener noreferrer"&gt;ecommerce demo&lt;/a&gt;, a real project built through the loop and committed with its unedited history. &lt;a href="https://abhinavpangaria.pages.dev/dispatches/the-second-build-was-mostly-wiring" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt; covers how the system got here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four tabs
&lt;/h2&gt;

&lt;p&gt;The dashboard runs at &lt;code&gt;localhost:4173&lt;/code&gt; and has four tabs: &lt;strong&gt;Code graph&lt;/strong&gt;, &lt;strong&gt;Memory graph&lt;/strong&gt;, &lt;strong&gt;PRD graph&lt;/strong&gt;, and &lt;strong&gt;Kane activity&lt;/strong&gt;. An "Ask GuardianKane" chat panel sits on the right of every tab. The graphs are drawn with cytoscape; the panels poll small JSON files that the hooks and the graph build write into &lt;code&gt;.testmuai/&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code graph
&lt;/h3&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%2Fsc0xpgs32sxyvz2vo1ng.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%2Fsc0xpgs32sxyvz2vo1ng.png" alt="The GuardianKane code graph for the ecommerce demo. The left sidebar lists nodes: code 42, feature 7, claim 6, external 5. Edges: imports 42, touches 41, about 13, external call 50. Phases P0 to P5, and a test-quality filter of strong, weak, and untested. On the right, the Ask GuardianKane chat shows a request to continue from T5, the agent's reply summarising that T0 to T4 are verified and T5 is claimed done, and a Stop-hook reply saying T5 failed verification at attempt 1 of 3."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The v2 design spec defines three kinds of node and four kinds of edge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;code&lt;/strong&gt; nodes are files, from an import scan with &lt;code&gt;madge&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;feature&lt;/strong&gt; nodes are tasks from the tracker&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;claim&lt;/strong&gt; nodes are Kane's use cases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;imports&lt;/strong&gt; and &lt;strong&gt;calls&lt;/strong&gt; connect code to code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;touches&lt;/strong&gt; connects a task to every file the agent edited while working on it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;about&lt;/strong&gt; connects a claim to the files it mentions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The legend on this screenshot counts 42 code nodes, 7 features, 6 claims, 42 import edges, 41 touches, and 13 about edges. The &lt;code&gt;touches&lt;/code&gt; edges are the dormant data from Part 1: the PostToolUse hook had been recording them since the hackathon version and nothing read them until this graph did. They are also the reason the graph is useful. The orange cluster on the right is what the agent actually changed per task, drawn next to what the PRD says those tasks are about.&lt;/p&gt;

&lt;p&gt;Nodes carry a phase (P0 to P5 here) and a test-quality ring: strong, weak, or untested. Clicking one or more rings isolates them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Focus and selection
&lt;/h3&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%2Fqf9iv3tit4o24zleasu5.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%2Fqf9iv3tit4o24zleasu5.png" alt="The code graph with nine nodes selected around tracker.js: agent-bridge.js, server.js, activity-feed.js, graph-build.js, kane-context.js, tracker.js, checkout.js, signup.js, and server.js. A notice reads 9 nodes selected, directly connected with 8 imports, integration test candidate, above Ask about tests and Generate tests buttons. Below are collapsible Review card, Trace, Overview, App bug sweep, and Quick generate sections."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what "pull-based" from Part 1 looks like in practice. You select nodes; the sidebar tells you how they connect (here, nine nodes joined by eight imports, flagged as an integration test candidate); and "Generate tests" asks Kane for tests scoped to that selection instead of to the whole PRD. "Ask about tests" goes to Kane's recorded explanations. The same sidebar holds the review card, the trace for a selected task, an on-request whole-app bug sweep, and quick-generate.&lt;/p&gt;

&lt;p&gt;One thing this screenshot shows that I did not intend: the selection includes &lt;code&gt;agent-bridge.js&lt;/code&gt;, &lt;code&gt;graph-build.js&lt;/code&gt;, &lt;code&gt;kane-context.js&lt;/code&gt;, and &lt;code&gt;tracker.js&lt;/code&gt;. Those are GuardianKane's own files. The installer copies them into the target project, and the graph scans the project, so the tool shows up in the graph of the app it is checking. The graph build takes an &lt;code&gt;--exclude&lt;/code&gt; flag, but nothing excludes the installed files by default. That default is the fix.&lt;/p&gt;

&lt;h3&gt;
  
  
  PRD graph
&lt;/h3&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%2Fexgzkjl0ra2z6745g21p.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%2Fexgzkjl0ra2z6745g21p.png" alt="The PRD graph tab: purple claim nodes for individual acceptance criteria, orange diamond feature nodes such as Add a product to cart, Proceed to checkout, Sign up or sign in, and Apply promo code, two blue PRD source nodes in the middle, and red triangles marking gaps numbered gap-1 to gap-13 around the checkout and promo code clusters."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The PRD graph is Kane's claim graph for the project: every acceptance criterion as a node, grouped under the feature it belongs to, and every gap as a red marker. With the Gaps and drift panel open, the same tab shows the number I think matters most in the whole dashboard:&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%2Fcb73ayddxrrhj8iuxgk1.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%2Fcb73ayddxrrhj8iuxgk1.png" alt="The PRD graph with the Gaps and drift panel open: design coverage 100 percent across 21 acceptance criteria, proven 53 percent with 10 of 21, and 14 pending gaps each listing a fix command"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design 100% (21/21 ACs). Proven 53% (10/21 ACs).&lt;/strong&gt; Every acceptance criterion has a test designed for it. Just over half have a test that has actually passed in a browser. Those two numbers are the difference this whole project is about. The gate is there to make the second one move, and the panel keeps the gap visible: 14 pending gaps, each with the Kane command that would close it. This panel exists because of Phase 5, which found that &lt;code&gt;kane-cli cover gaps --json&lt;/code&gt; already computed all of it and nothing called it.&lt;/p&gt;

&lt;p&gt;A project that reports only the first number is reporting what it intends, not what it has.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory graph
&lt;/h3&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%2Fo4x1vz3ytgcnvk4cumcv.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%2Fo4x1vz3ytgcnvk4cumcv.png" alt="The GuardianKane memory graph with a node selected: public/styles.css, 15 runs, currently fixed. A list of fails and passes from 29 August to 30 August, one of which carries a note: signed-in header assertion expects wrong email value, family automation_bug, confidence 0.96."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The memory graph renders knowledge memory: run history keyed by the set of files a verification touched, pass or fail, with timeouts and infra errors left out. This is the "memory of where" that Part 1 says the hackathon version lacked.&lt;/p&gt;

&lt;p&gt;It needs reading with care. &lt;code&gt;styles.css&lt;/code&gt; shows 15 runs, 12 fails and 3 passes, and "currently fixed". Those are not 12 failures of a stylesheet. A stylesheet is touched by nearly every verification, so its history is a record of the tasks that went through it. And the one annotated failure is &lt;code&gt;automation_bug&lt;/code&gt; at 0.96 confidence: the test expected the wrong email, not the app showing it. The family is stored, so the information to tell app bugs from test bugs is there. Nothing filters on it yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kane activity
&lt;/h3&gt;

&lt;p&gt;The Kane activity tab is &lt;code&gt;kane-activity.log&lt;/code&gt; rendered newest first, with one colour per outcome.&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%2F2nywcr3g2y0z0y48yh2q.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%2F2nywcr3g2y0z0y48yh2q.png" alt="Kane activity log, newest first. T0 sweep found an issue at attempt 1 of 3, then a secret scan failure at attempt 1 of 3 on server.js, then sweep failures at attempts 2 and 3, then BLOCKED_NEEDS_HUMAN after 3 sweep failures. About an hour and a half later T0 is swept again and reaches KANE_VERIFIED. Above that, T1's scripted batch fails three times, the first with the reason all members passed."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Its first version had the colours wrong: the classifier checked for "passed" before "failed", so a line like "FAILED ... 2/3 passed", which appears below, rendered as a pass. That was fixed in &lt;code&gt;86b3d09&lt;/code&gt;, from a dogfooding pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  The chat is wired into the live session
&lt;/h2&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%2Fzdsirq8b8t71cbn7umsf.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%2Fzdsirq8b8t71cbn7umsf.png" alt="How the dashboard talks to a live Claude Code session. Dashboard chat writes a pending-reply marker and posts through a local messaging socket, authenticated with the session token, into Claude Code, which reads it as a user turn. When Claude tries to stop, the Stop hook runs the gate and, if a marker is pending, posts the verdict text it already computed back to the dashboard."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"Ask GuardianKane" is not a separate chatbot. A message typed there lands in the Claude Code session that is building the app, as a user turn. The code that does it is about fifteen lines in &lt;code&gt;dashboard/lib/agent-bridge.js&lt;/code&gt;: connect to the Unix socket named in &lt;code&gt;CLAUDE_CODE_MESSAGING_SOCKET&lt;/code&gt;, write an auth line with the session's token, write the message. The comment above it is blunt about where that came from: the protocol was "reverse-engineered from strings embedded in the &lt;code&gt;claude&lt;/code&gt; CLI binary itself; it is not documented anywhere public". The dashboard has to be launched as a child of the session to inherit the socket and token.&lt;/p&gt;

&lt;p&gt;That makes it fragile in a way I want to be upfront about. It depends on an undocumented interface that could change in any Claude Code release, and I have no guarantee it will keep working.&lt;/p&gt;

&lt;p&gt;The first version also had a problem that was obvious once anyone used it: messages went in and nothing came back. The chat relied on the agent remembering to post a reply to the dashboard, and it did not. Commit &lt;code&gt;b2356c4&lt;/code&gt; moved the reply out of the agent's hands. Sending a message now writes a &lt;code&gt;chat-pending.json&lt;/code&gt; marker; the Stop hook, which runs at the end of every turn anyway, checks for the marker and posts back the verdict text it already computed. In the code graph screenshot above, the last chat bubble ("T-T5 failed verification (attempt 1/3) ...") is in the Stop hook's own denial format, which is the text the auto-reply posts. The agent did not write it. The gate did.&lt;/p&gt;

&lt;p&gt;It is the same move as the rest of the project. Do not rely on the agent to report on itself; have the thing that checks it do the reporting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the demo's log says about the gate
&lt;/h2&gt;

&lt;p&gt;The dashboard earned its keep by showing me bugs in GuardianKane, not just in the app. The ecommerce demo's committed &lt;code&gt;kane-activity.log&lt;/code&gt; is 103 lines, and some of its lines are wrong about themselves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"FAILED ... reason: all members passed".&lt;/strong&gt; T1's first batch, on August 29:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[T1] scripted test FAILED (attempt 1/3). summary: testrun batch: 2/3 passed. reason: all members passed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;T2 has the same shape at 3/4, and T5 at 0/3. Commit &lt;code&gt;a6a6dc1&lt;/code&gt; (August 30) traced two causes in the runner. A batch killed mid-flight fell through to a reason that claimed every member passed. And kane-cli's process exit code can be non-zero on a full pass: the commit records a live run that "exited 1 with status: 'passed'", which had produced a false &lt;code&gt;KANE_FAILED&lt;/code&gt;. The fix trusts Kane's structured verdict over the exit code. The commit is timestamped 12:16 IST; T5's contradictory line is at 14:17 IST the same day. The installer copies the hooks into the project, and I do not have data on which version the demo was running at that moment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"AC EVIDENCE CHECK FAILED" after a 3/3 pass.&lt;/strong&gt; Later on the 29th, T1's batch passed 3/3 and was still failed by the per-AC evidence check, three times in a row, with "no evidence pack path reported" and then "no matching evidence pack found". Commit &lt;code&gt;9181821&lt;/code&gt; found the gate matching evidence on the wrong field. Kane's &lt;code&gt;result.yaml&lt;/code&gt; has a &lt;code&gt;definition_id&lt;/code&gt;, the content hash in the test's frontmatter. The check looked for an &lt;code&gt;assurance_id&lt;/code&gt;, which the batch output did not contain, so it "silently failed the evidence check on every batched task". The commit message names T1's failures as the case it matches. For those three strikes, the app was fine and the checker was wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"all tasks KANE_VERIFIED. Build complete."&lt;/strong&gt; At 19:01 UTC on the 29th, after T2 was verified, the log says the build is complete. The next line starts T3's tests, and T4 and T5 follow the next day. I do not have data on what the tracker held at that moment; the log alone shows the gate declaring done while work was still being verified.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"FAILED ... reason: Objective completed".&lt;/strong&gt; After T3's tests were rewritten, its first run on the new files is logged as a failure whose summary describes a successful sign-up and whose reason is "Objective completed". I do not have data on why that run was classified as a failure; it is not one of the cases the commits above describe.&lt;/p&gt;

&lt;p&gt;The rest of the log is the loop working as designed. T3 reaches three failures, the last with &lt;code&gt;assertion_failed: @ step 3&lt;/code&gt;, escalates to a human, and after its tests were rewritten goes on to pass and verify. T4 goes 0/3, 1/3, 3/3, then verified. T5 runs into three kane-cli timeouts (exit null, state unchanged, no strike charged), then three counted failures: the first is the "all members passed" contradiction above, and the last two are on the reject-invalid-card-details test. It ends the committed log at &lt;code&gt;BLOCKED_NEEDS_HUMAN&lt;/code&gt;. The chat in the code graph screenshot, taken on August 31, shows T5 picked up again after that.&lt;/p&gt;

&lt;p&gt;So the closed loop in the demo is real, and it is not tidy. Two of the escalations in its history were at least partly the gate's own bugs. That is the lesson of &lt;a href="https://abhinavpangaria.pages.dev/dispatches/who-verifies-the-verifier" rel="noopener noreferrer"&gt;who verifies the verifier&lt;/a&gt; again, and the reason the dashboard renders the raw log instead of a summary of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dashboard had its own bugs too
&lt;/h2&gt;

&lt;p&gt;One more, because it is the kind a verification tool should be embarrassed by. Task titles, verdict summaries, AC references, and log lines were written into the page with &lt;code&gt;innerHTML&lt;/code&gt; unescaped in three renderers: the review card, the trace panel, and the stuck-tasks panel. Commit &lt;code&gt;1c9f476&lt;/code&gt; calls it what it was, a stored XSS through server-controlled fields, and escapes them. Some of those fields come from Kane's output and some come from the agent, so "server-controlled" here means "written by software I do not fully control".&lt;/p&gt;

&lt;p&gt;The same dogfooding pass (&lt;code&gt;86b3d09&lt;/code&gt;) fixed a "show N more" toggle on the drift list that never worked because of an inverted boolean, and started reaping orphaned Chrome processes that kane-cli's runs left behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  What visible buys you
&lt;/h2&gt;

&lt;p&gt;A pass or fail line tells you whether to keep going. The dashboard answers the questions that come after it: what did the agent touch, which requirements are proven and which are only designed, which failures were the app and which were the checker, and what has been stuck since yesterday. None of that makes the agent more honest on its own. It makes it cheaper for a person to check, which in my experience is what decides whether anyone checks at all.&lt;/p&gt;

&lt;p&gt;The full current build is in the &lt;a href="https://youtu.be/TfVm0yNzBJE" rel="noopener noreferrer"&gt;demo video&lt;/a&gt;, the code is at &lt;a href="https://github.com/18Abhinav07/adventures-with-kane" rel="noopener noreferrer"&gt;github.com/18Abhinav07/adventures-with-kane&lt;/a&gt;, and the ecommerce demo with its history is at &lt;a href="https://github.com/18Abhinav07/guardiankane-ecommerce-demo" rel="noopener noreferrer"&gt;github.com/18Abhinav07/guardiankane-ecommerce-demo&lt;/a&gt;. GuardianKane is built on &lt;a href="https://testmuai.com" rel="noopener noreferrer"&gt;Kane CLI&lt;/a&gt; by TestMuAI.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part 2 of 2. Previously: &lt;a href="https://abhinavpangaria.pages.dev/dispatches/the-second-build-was-mostly-wiring" rel="noopener noreferrer"&gt;the second build was mostly wiring&lt;/a&gt;. The hackathon version, and the experiments behind it, are in &lt;a href="https://abhinavpangaria.pages.dev/dispatches/series/your-agent-might-lie" rel="noopener noreferrer"&gt;Your agent might actually lie to you&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part 2 of &lt;a href="https://abhinavpangaria.pages.dev/dispatches/series/guardiankane" rel="noopener noreferrer"&gt;GuardianKane: It Will Not Let Your Agent Lie&lt;/a&gt;. The original, with the full series navigation, is &lt;a href="https://abhinavpangaria.pages.dev/dispatches/making-verification-visible" rel="noopener noreferrer"&gt;on my site&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>claudecode</category>
      <category>testing</category>
      <category>developertools</category>
    </item>
    <item>
      <title>The Second Build Was Mostly Wiring</title>
      <dc:creator>Abhinav Pangaria</dc:creator>
      <pubDate>Tue, 22 Sep 2026 03:34:09 +0000</pubDate>
      <link>https://dev.to/abhinav_pangaria/the-second-build-was-mostly-wiring-3hlm</link>
      <guid>https://dev.to/abhinav_pangaria/the-second-build-was-mostly-wiring-3hlm</guid>
      <description>&lt;p&gt;My first series, &lt;a href="https://abhinavpangaria.pages.dev/dispatches/series/your-agent-might-lie" rel="noopener noreferrer"&gt;Your agent might actually lie to you&lt;/a&gt;, is about one shape of GuardianKane: a Claude Code Stop hook that will not let the agent finish until &lt;a href="https://testmuai.com" rel="noopener noreferrer"&gt;Kane CLI&lt;/a&gt; has checked the work in a real browser. That shape placed 2nd in the TestMuAI Kane CLI hackathon, and four paired experiments stand behind it.&lt;/p&gt;

&lt;p&gt;It is not what GuardianKane is today. The hook is still there, but it is one piece of a larger system: a reviewed PRD, a phase model, a live scope guard, a file lock, a secret scan, and a dashboard with graph views and a chat panel wired into the running Claude Code session. This is the story of how it got there. Part 2 is the tour of what it looks like.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three shapes, one repo
&lt;/h2&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%2Fxhz6x9nn8mtbhxkylrmp.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%2Fxhz6x9nn8mtbhxkylrmp.png" alt="GuardianKane's three eras. Era 1 on August 20 morning: a PRD tool with no verification, commits f2a3c59, 664259f, cea4ae2, bd19e0d. Era 2 on August 20 to 21: the Stop-hook gate with bug memory, a one-command install and four A/B experiments, commits 896bc57 and b05fc4a. Era 3 from August 26: a phased, reviewed loop across phases 0 to 12." width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The repo's &lt;code&gt;JOURNEY.md&lt;/code&gt; records each shape against the commits that built it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Era 1 was a planning tool.&lt;/strong&gt; The first four commits are a design spec, an implementation plan, a task-tracker library, and a Claude Code skill with a thin &lt;code&gt;kane-cli&lt;/code&gt; wrapper. You gave it a PRD and got back a &lt;code&gt;task-tracker.md&lt;/code&gt;: tasks with ids, titles, PRD references, and dependencies. Nothing checked whether a task marked done was done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Era 2 was the gate.&lt;/strong&gt; The same afternoon, &lt;code&gt;896bc57&lt;/code&gt; added the Stop and PostToolUse hooks with the full decision table, and a day of running the loop against real apps turned it into the product the first series describes: scripted replay, a defect sweep, bug memory, an installer, and the four paired experiments. Two demo videos come from this era, a &lt;a href="https://youtu.be/efTl_ZSmXUw" rel="noopener noreferrer"&gt;14-minute walkthrough&lt;/a&gt; and a &lt;a href="https://youtu.be/-4gbIv9hv_M" rel="noopener noreferrer"&gt;3-minute cut&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Era 3 started five days later.&lt;/strong&gt; It opens with a new dashboard on August 26 and then runs through twelve numbered phases, each with its own design spec and implementation plan under &lt;code&gt;docs/superpowers/&lt;/code&gt;. The current build has its own &lt;a href="https://youtu.be/TfVm0yNzBJE" rel="noopener noreferrer"&gt;demo video&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why rebuild something that worked
&lt;/h2&gt;

&lt;p&gt;The v2 design spec opens with the problem, and it is not "the gate was broken". It is that asking a coding agent to generate tests for a system produces "on the order of hundreds of assertions, all green", that do not exercise real browser behaviour and do not map to what the PRD asked for. The spec calls these AI-slop tests and names two separate things wrong with them.&lt;/p&gt;

&lt;p&gt;The first is that they are &lt;strong&gt;push-based&lt;/strong&gt;. The agent reads the PRD once, generates everything it can think of, and stops. v2 makes verification pull-based: a person (or later the Stop hook) selects concrete nodes in a graph of the codebase, and only those nodes' claims get tested.&lt;/p&gt;

&lt;p&gt;The spec is careful about how much that buys, and I want to keep that care here. An earlier draft said v2 fixed both problems "by construction". An independent review caught it, and the spec now says pull-based targeting narrows scope, not depth. A vague instruction fed to the same browser agent can still produce a shallow pass. What v2 guarantees is that verification is aimed at something a person deliberately pointed at, not that any given run is rigorous.&lt;/p&gt;

&lt;p&gt;The second problem is &lt;strong&gt;no memory of where&lt;/strong&gt;. Era 2's bug memory could say "this looks like a bug we have seen before" by comparing failure text, but it never recorded which files were involved, so it could not say "this exact set of files was fixed on the 20th and has regressed". v2 keys its knowledge memory by the set of graph nodes under test.&lt;/p&gt;

&lt;h2&gt;
  
  
  The phase that fixed every fresh install
&lt;/h2&gt;

&lt;p&gt;Before any new feature, the plan for Era 3 audited what Kane CLI already did and what GuardianKane was duplicating. Its conclusion became the architecture:&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%2F49yrrpfr2hv20hmafvxb.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%2F49yrrpfr2hv20hmafvxb.png" alt="Who owns what after the rebuild. Kane CLI owns the claim graph, review, coverage and gaps, reconcile, and the recorded reasoning behind every generated test. GuardianKane owns tasks, phases, the Stop-hook gate, the file-touch record, and the dashboard. The twelve phases grouped as gate, knowledge, guardrails, and visibility. Unit tests: 241 after Phase 1, 387 after Phase 11, 460 of 460 today." width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GuardianKane's job narrowed to orchestration. Kane owns the claim graph, coverage, reconcile, and explanations; GuardianKane owns tasks, phases, the gate, and the dashboard.&lt;/p&gt;

&lt;p&gt;The same audit found the bug that became Phase 0. Kane CLI will not generate tests for a use case that has not been reviewed. It refuses with &lt;code&gt;uc-1 is unreviewed&lt;/code&gt;, and the fix is &lt;code&gt;kane-cli context review --approve&lt;/code&gt;. The plan records that &lt;code&gt;context review&lt;/code&gt; was "never called anywhere in the current codebase". So on any fresh install, test design failed on the first use case. I do not have data on why my Era 2 runs never tripped over it.&lt;/p&gt;

&lt;p&gt;Phase 0 (&lt;code&gt;39aaeaa&lt;/code&gt;) wires the review step in, so every grilled use case and every generated test is approved before it counts. That is also the honest version of what "PRD grilling" means in the current build: a requirement is extracted, questioned, and reviewed before anything is built against it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Twelve phases, and what most of them had in common
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;What it added&lt;/th&gt;
&lt;th&gt;Built on&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Review gate&lt;/td&gt;
&lt;td&gt;a Kane command nothing called&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Per-AC evidence and tamper detection&lt;/td&gt;
&lt;td&gt;Kane's sealed evidence packs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Phase model in the tracker&lt;/td&gt;
&lt;td&gt;the existing tracker schema&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Context injection before a task starts&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;context explain&lt;/code&gt; / &lt;code&gt;design explain&lt;/code&gt;, which make no model calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Live scope guard&lt;/td&gt;
&lt;td&gt;the PostToolUse hook's file-touch record&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Gaps panel&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;kane-cli cover gaps --json&lt;/code&gt;, which nothing called&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Review card with per-AC acknowledgment&lt;/td&gt;
&lt;td&gt;the task's diff and evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Reconcile-powered sync&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kane-cli maintain reconcile --plan&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Trace panel&lt;/td&gt;
&lt;td&gt;the activity log, never rendered&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Stuck-tasks panel&lt;/td&gt;
&lt;td&gt;attempt history already written per task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;File-level task lock&lt;/td&gt;
&lt;td&gt;the same file-touch record&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Secret scan and negative-path nudge&lt;/td&gt;
&lt;td&gt;the task's scoped diff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Browser review, PRD graph, quick-generate, evidence viewer, Playwright export&lt;/td&gt;
&lt;td&gt;the rest of Kane's surface&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read down the last column. The phase specs say it themselves, over and over: "already computes", "already fires on every Edit/Write", "nothing in the codebase calls it today". Phase 5's spec confirms the gaps command was unused with a grep that returns zero results. Phase 7 replaced a PRD diff I had written by hand with Kane's own reconcile, because mine was "reimplementing something Kane already computes".&lt;/p&gt;

&lt;p&gt;The v2 spec has a phrase for the pattern: the first place v2 "activates dormant data rather than adding new collection". It is about the &lt;code&gt;touches&lt;/code&gt; edge in the code graph. Since Era 2 the PostToolUse hook had recorded every file the agent edited, per task, and nothing downstream had ever read it. In Era 3 that one record feeds the code graph, the scope guard, the trace panel, and the file lock.&lt;/p&gt;

&lt;p&gt;That is the lesson I would carry to the next tool I build on top of an agent: before adding collection, grep for what is already collected and never read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the phases were honest about their limits
&lt;/h2&gt;

&lt;p&gt;A few specs say plainly what their phase does not do, and those are the lines I trust most.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Phase 4&lt;/strong&gt; writes scope drift to its own &lt;code&gt;scope-status.json&lt;/code&gt; instead of the existing &lt;code&gt;graph-status.json&lt;/code&gt;, because the graph rebuild would overwrite the flag "often before the dashboard's 5s poll interval catches it". The spec calls it "a real write race, not a style preference".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 8&lt;/strong&gt; found that the PostToolUse hook exits early for anything but &lt;code&gt;Edit&lt;/code&gt; and &lt;code&gt;Write&lt;/code&gt;, so "nothing is recorded for &lt;code&gt;Read&lt;/code&gt;, &lt;code&gt;Grep&lt;/code&gt;, &lt;code&gt;Glob&lt;/code&gt;, or &lt;code&gt;Bash&lt;/code&gt;". Half the trace it was asked to draw did not exist yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 10&lt;/strong&gt; only warns. There is no PreToolUse hook in the codebase, and PostToolUse fires after the edit "has already happened, so it cannot block the edit itself". The spec implements the warn half of warn-or-block and says so.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who built the second build
&lt;/h2&gt;

&lt;p&gt;This part would not be honest without it. The specs for Phases 3 to 12 carry a status line saying they were approved "under the standing full-autonomy delegation", self-reviewed or given a separate architect-style review pass, with no per-phase sign-off from me. Of the 50 most recent commits in my local clone, 29 carry a Claude Sonnet 5 co-author trailer.&lt;/p&gt;

&lt;p&gt;So the rebuild of a tool that exists because agents claim done too early was itself largely built by an agent working under a delegation. What kept it honest was the same thing GuardianKane asks of any agent: a written spec per phase, a plan, and a test suite that has to stay green. The phase specs record 241 of 241 tests passing after Phase 1 and 387 of 387 after Phase 11. I ran the suite again for this post: 36 test files, 460 of 460 passing.&lt;/p&gt;

&lt;p&gt;That count is unit tests on GuardianKane's own code. It is not evidence that any app built through the loop is correct. That evidence is the browser runs, and Part 2 shows some of them failing for reasons that had nothing to do with the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  What came after the twelfth phase
&lt;/h2&gt;

&lt;p&gt;The last days of August were not new features. They were fixes found by running the whole thing against a real project, the &lt;a href="https://github.com/18Abhinav07/guardiankane-ecommerce-demo" rel="noopener noreferrer"&gt;ecommerce demo&lt;/a&gt;: the evidence check keyed off the wrong field, a runner that trusted kane-cli's exit code over its own structured verdict, a dashboard chat that never replied, and a stored XSS in the dashboard's own rendering. Every one of those is in Part 2, with the log lines that exposed them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part 1 of 2. The hackathon version is the first series, starting with &lt;a href="https://abhinavpangaria.pages.dev/dispatches/agent-shipped-a-broken-chart" rel="noopener noreferrer"&gt;the broken chart&lt;/a&gt;. Next: &lt;a href="https://abhinavpangaria.pages.dev/dispatches/making-verification-visible" rel="noopener noreferrer"&gt;making verification visible&lt;/a&gt;, the dashboard, the graphs, and the chat bridge. The code is at &lt;a href="https://github.com/18Abhinav07/adventures-with-kane" rel="noopener noreferrer"&gt;github.com/18Abhinav07/adventures-with-kane&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part 1 of &lt;a href="https://abhinavpangaria.pages.dev/dispatches/series/guardiankane" rel="noopener noreferrer"&gt;GuardianKane: It Will Not Let Your Agent Lie&lt;/a&gt;. The original, with the full series navigation, is &lt;a href="https://abhinavpangaria.pages.dev/dispatches/the-second-build-was-mostly-wiring" rel="noopener noreferrer"&gt;on my site&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>claudecode</category>
      <category>testing</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Who Verifies the Verifier?</title>
      <dc:creator>Abhinav Pangaria</dc:creator>
      <pubDate>Tue, 22 Sep 2026 03:34:07 +0000</pubDate>
      <link>https://dev.to/abhinav_pangaria/who-verifies-the-verifier-1nff</link>
      <guid>https://dev.to/abhinav_pangaria/who-verifies-the-verifier-1nff</guid>
      <description>&lt;p&gt;The premise of this series is that you should not take an agent's word that it is done. So I built a gate that makes Claude Code prove each task in a real browser before it may stop. The browser is driven by Kane CLI, which is itself an agent.&lt;/p&gt;

&lt;p&gt;The obvious question arrives late and uncomfortably: why would I take that agent's word either?&lt;/p&gt;

&lt;p&gt;This is the part of the story where the gate turns around and looks at itself. It has three layers: the verifier's failures, my own, and the rule that decides when to stop asking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Twelve tasks, eight failures, one bug
&lt;/h2&gt;

&lt;p&gt;ORBITAL is the densest build in the project, the one with the doubled chart from Part 1. Every task went through the gate, and the Stop hook wrote each attempt to an append-only activity log. Here is the whole build, from that log.&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%2F12g0ngj1rcux88so2r51.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%2F12g0ngj1rcux88so2r51.png" alt="Grid of the thirteen ORBITAL tasks, T0 to T12, each labeled with its outcome: T0 infra errors and a stale reset then a clean sweep, T1 passed on retry, T2 T3 T4 T9 T10 T11 human overrides after automation failures, T5 T6 T8 T12 clean on the first try, and T7 caught by the sweep, fixed in 43 minutes, then clean" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;12 of 12 verified. One sweep catch. Six overrides.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Eight tasks failed a check at least once. T1 failed on an assertion timing mismatch after the navigation it was checking had already happened, and passed on retry. Six more (T2, T3, T4, T9, T10, T11) failed badly enough to need a human-reviewed override. For every one of the six, &lt;code&gt;kane-cli&lt;/code&gt;'s own triage came back &lt;code&gt;confirmed: false&lt;/code&gt;, family &lt;code&gt;automation_bug&lt;/code&gt;. The causes were specific:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;T2, the performance chart.&lt;/strong&gt; Three attempts, three different failure points. One opened the wrong page entirely. Triage pointed at flaky headless hover targeting and an ambiguous unlock-state assertion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;T3, the allocation donut.&lt;/strong&gt; The test asserted a hover effect without ever hovering the segment first. The script was missing a step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;T4, the holdings table.&lt;/strong&gt; A legitimate layout fix invalidated recorded replay baselines. Re-recorded with &lt;code&gt;--author&lt;/code&gt;, all eight test files passed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;T9, T10, T11.&lt;/strong&gt; The feature steps passed, then a redundant final re-check stalled. On T11 the browser agent oscillated between two buttons until it detected a cycle in its own plan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The eighth was T7, the sweep catch from Part 1. That was the one real problem in the app across twelve tasks.&lt;/p&gt;

&lt;p&gt;Eight failures, one bug. Read by exit code, all eight look the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule I ended up with
&lt;/h2&gt;

&lt;p&gt;Every raw &lt;code&gt;failed&lt;/code&gt; result gets read against the verifier's structured verdict: &lt;code&gt;verdict.confirmed&lt;/code&gt;, &lt;code&gt;family&lt;/code&gt;, &lt;code&gt;category&lt;/code&gt;, and confidence. Never the exit code. Then, before an override is allowed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Re-run the failing test alone, not inside a batch.&lt;/li&gt;
&lt;li&gt;Get &lt;code&gt;kane-cli&lt;/code&gt;'s triage on the repeated failure.&lt;/li&gt;
&lt;li&gt;Find direct evidence the feature works: a step in the same run that passed, a fresh recording, or a manual check in a real browser.&lt;/li&gt;
&lt;li&gt;Only then clear it, with the rationale written into the log next to the evidence.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The same held across the other experiments. On the booking studio, six toast-timing failures were Kane's agent being too slow for a four second toast. On the todo build, a persistence check looked under the storage key the other build happened to use.&lt;/p&gt;

&lt;p&gt;A slow verifier, a brittle assertion, a stale recording. None of those is the app. All of them look exactly like the app from outside.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I re-read my own write-up
&lt;/h2&gt;

&lt;p&gt;This is the part I did not expect to write.&lt;/p&gt;

&lt;p&gt;While putting this series together I went back to the repo's build log, &lt;code&gt;OBSERVATIONS-AND-REPORTINGS.md&lt;/code&gt;, and checked each ORBITAL claim against the raw activity log. My write-up had done to the verifier's failures exactly what the verifier had done to the app.&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%2Fu3iwvngoq6w6o9qte60e.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%2Fu3iwvngoq6w6o9qte60e.png" alt="Four rows comparing my build notes with the raw activity log. T2: the notes say the sweep caught a real chart lock-state defect; the log shows three scripted failures triaged as automation_bug. T3: the notes say a sweep-caught issue on the donut; the log shows the test never hovered before asserting. T4: the notes say a sweep-caught issue on the table; the log shows stale replay baselines, 8 of 8 passing once re-recorded. T9: the notes say the export raced its toast; the log shows all four export toasts confirmed and a redundant final step stalled" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What my write-up said against what the log said.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The notes say the sweep "caught a real state-tracking defect" on the chart at T2. The log shows no sweep finding on T2 at all, only three scripted failures that triage called automation. The notes credit the sweep on T3 and T4; both were test problems. The notes call T9 "a genuine race"; the log shows all four export toasts confirmed in the same run and a redundant step failing afterwards.&lt;/p&gt;

&lt;p&gt;Kane's &lt;code&gt;bug_title&lt;/code&gt; fields made this easy to get wrong. Triage titles read like bug reports: "Replay misclassifies unlocked chart state". Lift that string out of its context, forget the &lt;code&gt;confirmed: false&lt;/code&gt; next to it, and it reads as a finding. I lifted them. The first draft of Part 1 repeated them, before I checked.&lt;/p&gt;

&lt;p&gt;The repo's own evidence gif has the same problem. The README captions this one as a caught defect:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fynflb4se8yhlin2q5kib.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fynflb4se8yhlin2q5kib.gif" alt="Kane CLI driving the performance chart's lock interaction and ending the run as failed" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A failed run on the chart lock, evidence pack &lt;code&gt;31fcd34b&lt;/code&gt;. Every failure on the chart task was triaged &lt;code&gt;automation_bug&lt;/code&gt;, so I now read this as the verifier failing, not the chart.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The overrides I am least comfortable with
&lt;/h2&gt;

&lt;p&gt;Look at who cleared each override. T2 was recommended by a second Claude session and approved by me explicitly. T3 needed triage evidence and then my approval. T4, T9, T10 and T11 were cleared "under standing self-override authorization": a standing grant, not a fresh decision each time.&lt;/p&gt;

&lt;p&gt;The evidence supports each of those calls. But the process got weaker as the build went on, and the log shows it. A human in the loop who has pre-approved the loop is not really in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three strikes, then a human
&lt;/h2&gt;

&lt;p&gt;All of which forces a design question I had to answer in code before I could answer it in prose. When the gate says no, how many times do you send the agent back? An agent told "try again" will try forever. It does not get tired or embarrassed. Somebody has to decide when the loop has stopped producing information and started producing noise.&lt;/p&gt;

&lt;p&gt;GuardianKane's answer is three.&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%2F6afugqngz9v5jeel511x.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%2F6afugqngz9v5jeel511x.png" alt="The state machine the Stop hook enforces. PLANNED, IN_PROGRESS, CLAIMED_DONE, KANE_VERIFYING, KANE_VERIFIED in a row. Below: KANE_FAILED routes back to IN_PROGRESS; BLOCKED_NEEDS_HUMAN after the third failure or one case of test tampering; kane-cli exit 2 or 3 leaves state untouched." width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every gate that can fail a task follows the same shape: increment &lt;code&gt;attempts&lt;/code&gt;; under three, set &lt;code&gt;KANE_FAILED&lt;/code&gt; and deny the stop with a reason; on the third, set &lt;code&gt;BLOCKED_NEEDS_HUMAN&lt;/code&gt;. The details around the number matter more than the number:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A failure always routes back to &lt;code&gt;IN_PROGRESS&lt;/code&gt;.&lt;/strong&gt; There is no edge from a failed check back to &lt;code&gt;CLAIMED_DONE&lt;/code&gt;. The agent cannot re-assert done on the same code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalation is an allow, not a deny.&lt;/strong&gt; A deny with no retries left is an instruction to keep going with nothing new to say. The allow ends the session and leaves the task blocked where a person will see it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tampering skips the queue.&lt;/strong&gt; If a test lost an assertion for a requirement that did not change, that is a test being weakened to pass, and it escalates on the first occurrence. Retries are for honest mistakes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The verifier's failures are free.&lt;/strong&gt; When &lt;code&gt;kane-cli&lt;/code&gt; exits with an infra error or a timeout, state is left alone and no strike is charged. Given everything above, a counter that billed the agent for Kane's bad hour would escalate working code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The counter that went backwards
&lt;/h2&gt;

&lt;p&gt;The clearest log of the cap in action comes from a later project, an ecommerce demo built with the rebuilt GuardianKane and committed with its unedited history. The three-strike rule is unchanged there. Here is its first task, T0:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;20:15:30&lt;/td&gt;
&lt;td&gt;sweep FOUND ISSUE (attempt 1/3): (no summary)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20:27:58&lt;/td&gt;
&lt;td&gt;SECRET SCAN FAILED (attempt 1/3): server.js, high-entropy value assigned to "secret"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20:29:56&lt;/td&gt;
&lt;td&gt;sweep FOUND ISSUE (attempt 2/3): (no summary)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20:30:23&lt;/td&gt;
&lt;td&gt;sweep FOUND ISSUE (attempt 3/3): (no summary)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20:30:23&lt;/td&gt;
&lt;td&gt;BLOCKED_NEEDS_HUMAN after 3 sweep failures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22:11:02&lt;/td&gt;
&lt;td&gt;sweep found no issues, KANE_VERIFIED&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The escalation works. And the log taught me three things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The counter went backwards.&lt;/strong&gt; The first sweep failure is attempt 1. Twelve minutes later the secret scan failure is also attempt 1. The hook only ever increments &lt;code&gt;attempts&lt;/code&gt;; nothing in it sets the field back to zero. But the counter lives in the task tracker, a YAML block in a markdown file the agent also edits, because the agent is the one who flips tasks to &lt;code&gt;IN_PROGRESS&lt;/code&gt;. Nothing guards that field. I do not have data on what reset it in this run. The log only shows that it was reset.&lt;/p&gt;

&lt;p&gt;That is the most important finding in this series. "Only the hook writes verification states" is a rule the agent is told, not one the file system enforces, and the same is true of the retry budget. A cap the capped party can edit is a suggestion. The fix is to keep &lt;code&gt;attempts&lt;/code&gt; and the verification states in a file only the hook writes, and treat the tracker as a view.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The last two sweeps were not a browser looking at a page.&lt;/strong&gt; Attempt 3 started at 20:30:21 and reported an issue at 20:30:23. Two seconds, no summary. A sweep drives a real browser against a running app; a two-second verdict with nothing behind it is not evidence about the app. The escalation was reached mostly on verdicts like that, which is the lesson from the top of this post again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The human step is invisible.&lt;/strong&gt; T0 sits blocked for about an hour and a half, then comes back clean. Whatever happened in between, a fix, a restart, a tracker edit, is not in the log, because the log only records what the hook does. For an audit trail, the most consequential step, a person deciding to reopen a blocked task, is the one it cannot see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why three
&lt;/h2&gt;

&lt;p&gt;I do not have data that three is the right number. I did not run the loop with two or five and compare. What I am more confident about is the structure around it: honest failures retry, tampering does not, the verifier's own failures do not count, and the budget has to live somewhere the agent cannot write. The hackathon version got the first three right.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this changes about the thesis
&lt;/h2&gt;

&lt;p&gt;It does not weaken it. It sharpens it.&lt;/p&gt;

&lt;p&gt;The original claim was "do not trust the agent's self-report". The better claim is "do not trust any unverified claim, including the verifier's, including your own summary of the verifier". Each layer needs its structured evidence kept next to its conclusion, so the next reader can check the step instead of the sentence.&lt;/p&gt;

&lt;p&gt;In practice: read structured verdicts, not exit codes. Keep confidence and family next to every finding, everywhere it is quoted. And go back to the raw log when you write it up, because the summary is where the drift happens.&lt;/p&gt;

&lt;p&gt;The raw log was right the whole time. Everything that went wrong went wrong in a summary.&lt;/p&gt;

&lt;p&gt;That is also where the hackathon version runs out. A pass or fail line in a terminal cannot show you what the agent touched, which requirements are only designed and which are proven, or which failures were the checker. Answering that took a second build, and it is a different story.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part 3 of 3, and the last. Previously: &lt;a href="https://abhinavpangaria.pages.dev/dispatches/the-requirement-nobody-wrote" rel="noopener noreferrer"&gt;the requirement nobody wrote&lt;/a&gt;. The story continues in &lt;a href="https://abhinavpangaria.pages.dev/dispatches/series/guardiankane" rel="noopener noreferrer"&gt;GuardianKane: it will not let your agent lie&lt;/a&gt;, starting with &lt;a href="https://abhinavpangaria.pages.dev/dispatches/the-second-build-was-mostly-wiring" rel="noopener noreferrer"&gt;the second build was mostly wiring&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part 3 of &lt;a href="https://abhinavpangaria.pages.dev/dispatches/series/your-agent-might-lie" rel="noopener noreferrer"&gt;Your Agent Might Actually Lie to You&lt;/a&gt;. The original, with the full series navigation, is &lt;a href="https://abhinavpangaria.pages.dev/dispatches/who-verifies-the-verifier" rel="noopener noreferrer"&gt;on my site&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>testing</category>
      <category>verification</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>The Requirement Nobody Wrote</title>
      <dc:creator>Abhinav Pangaria</dc:creator>
      <pubDate>Tue, 22 Sep 2026 03:34:05 +0000</pubDate>
      <link>https://dev.to/abhinav_pangaria/the-requirement-nobody-wrote-43je</link>
      <guid>https://dev.to/abhinav_pangaria/the-requirement-nobody-wrote-43je</guid>
      <description>&lt;p&gt;The smallest PRD in my experiments has a section called "Priority badge". It asks for a badge on each task card, keeps the existing layout, and then says this, which is the whole specification of priority:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;just with a small badge showing High/
Medium/Low priority somewhere on the card, defaulting to Medium.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read it again and try to answer one question: how does a task become High?&lt;/p&gt;

&lt;p&gt;It does not say. Three values are allowed, one is the default, and there is no mechanism for reaching the other two. Nobody wrote that part down, and I did not notice until a tool asked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two builds, one gap
&lt;/h2&gt;

&lt;p&gt;This was the first of four paired experiments. Same PRD, same starting commit, same operator. &lt;code&gt;todo-kane&lt;/code&gt; went through the full GuardianKane loop. &lt;code&gt;todo-baseline&lt;/code&gt; was a fresh Claude Code agent given the PRD verbatim and told "Build this. Let me know when it's done." No hooks, no gate, no follow-up.&lt;/p&gt;

&lt;p&gt;Both builds worked. The baseline passed the add, complete, delete, and default-badge tests from the gated build's own suite. On a CRUD app this small you would expect nothing else. Then I pointed the explicit-priority test at it, and Kane's verdict came back:&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;confirmed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;major&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-priority&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;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;displayed&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;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;MEDIUM&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;badge&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;instead&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;High."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There was no way, anywhere in the baseline, to set a task to High or Low. You do not find that by reading the code, because the code is fine for what it does. You do not find it by clicking around either, because every task shows "Medium" and Medium is a valid value. The app looks correct right up until someone needs a High.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the question came from
&lt;/h2&gt;

&lt;p&gt;The gated build did not get this right because it was smarter. It got it right because, before any code existed, GuardianKane's &lt;code&gt;start&lt;/code&gt; flow ran a grilling pass over the PRD: a structured conversation whose only job is to find every place the document is ambiguous and force a decision while deciding is cheap.&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%2Frypyjkzowo2ei7yoj323.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%2Frypyjkzowo2ei7yoj323.png" alt="Diagram of the grilling step: the PRD line "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One question during grilling became a convention, a task, and a test.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The question was the obvious one, once someone asks it. The answer was deliberately small: a &lt;code&gt;!high&lt;/code&gt; or &lt;code&gt;!low&lt;/code&gt; suffix on the title sets the priority and is stripped from the displayed title. That decision became task T5, and Kane generated a real browser test around it. The step that gates the task, verbatim from the repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Assert that the task card titled {{high_priority_task_title}} shows a visible
priority badge whose text is exactly High, and that the same card still shows its
completion checkbox and delete "x" control.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;T5 could not reach &lt;code&gt;KANE_VERIFIED&lt;/code&gt; until that held in a real browser, so the gated build implemented the suffix. The unassisted build never had the question asked, and resolved the ambiguity the way ambiguity always resolves under pressure: in the direction of least effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why an agent will not ask
&lt;/h2&gt;

&lt;p&gt;I do not think this is specific to Claude. It is what one-shot building does to any gap in a spec. The instruction was "build this", and the spec was satisfiable as written: every card has a badge, the badge can show Medium, Medium is the default. An agent optimizing to finish will not invent a requirement that makes the task bigger, and it will not stop to ask, because stopping to ask looks like not finishing.&lt;/p&gt;

&lt;p&gt;A human engineer would probably have asked in standup. The grilling step moves that standup question to before the first line of code, and then turns the answer into something executable. A decision in a chat log can be forgotten. A decision in a test file cannot be skipped.&lt;/p&gt;

&lt;p&gt;One smaller finding from the same experiment keeps me honest about what that buys. Re-running the priority test against the gated build itself, Kane flagged that the badge's CSS uppercases the text, so the screen reads "HIGH" while the DOM says "High". It was &lt;code&gt;confirmed: false&lt;/code&gt;, severity minor, and it had not failed the gate originally. A verified task is verified against what was asserted at the time, not against every assertion anyone could write later. Verification is only as wide as the questions you asked before it ran.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I built a trap
&lt;/h2&gt;

&lt;p&gt;The fair criticism of the todo result was that it was too forgiving. The gap only existed because one PRD happened to leave an ambiguity. I wanted a task that would stress the gate itself.&lt;/p&gt;

&lt;p&gt;A room booking widget: pick a start and end time, create a booking, reject any booking that overlaps an existing one. The PRD is explicit about the edge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If the new range overlaps any existing booking, reject it with a visible
error and do not create it. Two bookings that merely touch (one's end
time equals the other's start time) are NOT overlapping and must be
allowed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the textbook off-by-one. Write &lt;code&gt;&amp;lt;=&lt;/code&gt; where you meant &lt;code&gt;&amp;lt;&lt;/code&gt; and the widget rejects perfectly valid back-to-back bookings. It is invisible unless a test builds two bookings that share a boundary, and it is exactly what I expected a one-shot build to get wrong.&lt;/p&gt;

&lt;p&gt;Both builds got it right. &lt;code&gt;booking-kane&lt;/code&gt; used &lt;code&gt;aStart &amp;lt; bEnd &amp;amp;&amp;amp; aEnd &amp;gt; bStart&lt;/code&gt;. &lt;code&gt;booking-baseline&lt;/code&gt; used &lt;code&gt;aStart &amp;lt; bEnd &amp;amp;&amp;amp; bStart &amp;lt; aEnd&lt;/code&gt;. Same logic, operands in a different order, both strict. The gated build ended with a verdict-confirmed record across all 12 scored tests. The baseline completed 6 of 12, including the trap, and passed every one it completed. No confirmed divergence.&lt;/p&gt;

&lt;p&gt;My trap caught nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I did not bury it
&lt;/h2&gt;

&lt;p&gt;It would have been easy to leave that experiment out. It produced two things I value more than a dramatic screenshot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It turned a claim into a fact.&lt;/strong&gt; Before the cross-test, "the baseline handles touching bookings" was the baseline agent's own self-report: the exact kind of sentence this project exists to distrust. After it, it was a verified result. The code did not change. What changed was whether anyone had to take an agent's word for it.&lt;/p&gt;

&lt;p&gt;That is the less glamorous half of what a gate does. People picture it catching bugs. Most of the time it converts "I believe this works" into "a browser watched it work", and when the answer is yes, nothing visibly happens. A good verification system spends most of its life producing null results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It kept the numbers honest.&lt;/strong&gt; Six of the baseline's tests never completed, because &lt;code&gt;kane-cli&lt;/code&gt; was flaky on this task throughout; one run hung for more than 17 minutes before I killed it. Two of the gated build's raw failures were &lt;code&gt;confirmed: false&lt;/code&gt;, &lt;code&gt;automation_bug&lt;/code&gt;: Kane's own analysis said the UI had already rejected the bad booking. Scored by exit code, this experiment would have reported bugs in the gated build that did not exist. That thread gets pulled all the way in Part 3.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I made the PRD denser
&lt;/h2&gt;

&lt;p&gt;The todo and booking PRDs were thin. Neither gave a one-shot build much room to drop anything. The third experiment kept the booking domain and loaded the PRD with detail: exact hex colors, an 8px spacing scale, corner radii, a header row with 16px padding, a 250ms shake on conflict, an auto-dismissing toast with a progress bar, a 5 second Undo that restores the exact cancelled booking, a persisted theme, and rules for what Escape closes first. Every one of those is checkable, and every one is easy to drop.&lt;/p&gt;

&lt;p&gt;The gated build reached &lt;code&gt;KANE_VERIFIED&lt;/code&gt; on 8 of 10 requirement groups. The other two, toast stacking and cancel with undo, failed six attempts between them, all six &lt;code&gt;confirmed: false&lt;/code&gt;: Kane's browser agent was too slow to act inside the few seconds the toast was on screen.&lt;/p&gt;

&lt;p&gt;A manual side-by-side found one concrete divergence, on the unassisted build. Its header rendered with the title and theme toggle flush against the viewport edge. The PRD asks for 16px of padding. The gated build had it. One missed value: real, specific, and modest.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape across four experiments
&lt;/h2&gt;

&lt;p&gt;Line the four up by how dense the PRD was, and a pattern shows up that I did not design for.&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%2Ftk7b577pneqcm0te664k.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%2Ftk7b577pneqcm0te664k.png" alt="Four cards ordered by PRD density. Todo app, low: the baseline failed only the explicit High or Low priority check nobody wrote down. Room booking, medium: no confirmed divergence. Booking studio, high: 8 of 10 requirement groups verified on the gated build, and the baseline header missing its 16px padding. ORBITAL, very high: a doubled chart, a two-column alert list, and uneven spacing on the baseline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The gap grew with the density of the PRD.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sparse PRDs produced small gaps or none. The denser the spec, the more places an unsupervised single pass had to quietly drop something. By ORBITAL, Part 1's build, the unassisted agent had a doubled chart, a wrong alerts layout, and uneven spacing.&lt;/p&gt;

&lt;p&gt;Four experiments, one operator, one pair of builds each. That is a pattern worth writing down, not a law. But it matches what I believe from building with agents: Claude is already strong at small, well-scoped builds. A gate earns its keep in the long tail of a dense spec, where "looks right at a glance" and "is actually right" start to come apart. And the most valuable thing it did in these four experiments was not the browser check. It was the question asked before the build.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you write PRDs for agents
&lt;/h2&gt;

&lt;p&gt;Read every enumerated value and ask how each one is reached. Read every default and ask what the non-default path is. Read every "can" and ask what "cannot" looks like. Those three habits would have caught the priority gap.&lt;/p&gt;

&lt;p&gt;Better, do not rely on your own reading. The gap sat in plain sight in two lines of text, and nobody saw it until something was built to look. Put a step in the loop whose only job is to ask, and make its answers executable.&lt;/p&gt;

&lt;p&gt;And design experiments that can fail. If every experiment you run says your tool is useful, you have probably only run the ones that could.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part 2 of 3. Previously: &lt;a href="https://abhinavpangaria.pages.dev/dispatches/agent-shipped-a-broken-chart" rel="noopener noreferrer"&gt;the broken chart&lt;/a&gt;. Next: &lt;a href="https://abhinavpangaria.pages.dev/dispatches/who-verifies-the-verifier" rel="noopener noreferrer"&gt;who verifies the verifier?&lt;/a&gt;, on the failures that were never the app's fault, and a retry cap the agent could edit.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part 2 of &lt;a href="https://abhinavpangaria.pages.dev/dispatches/series/your-agent-might-lie" rel="noopener noreferrer"&gt;Your Agent Might Actually Lie to You&lt;/a&gt;. The original, with the full series navigation, is &lt;a href="https://abhinavpangaria.pages.dev/dispatches/the-requirement-nobody-wrote" rel="noopener noreferrer"&gt;on my site&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>testing</category>
      <category>product</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>My Agent Shipped a Broken Chart and Told Me It Was Done</title>
      <dc:creator>Abhinav Pangaria</dc:creator>
      <pubDate>Tue, 22 Sep 2026 03:34:04 +0000</pubDate>
      <link>https://dev.to/abhinav_pangaria/my-agent-shipped-a-broken-chart-and-told-me-it-was-done-36k7</link>
      <guid>https://dev.to/abhinav_pangaria/my-agent-shipped-a-broken-chart-and-told-me-it-was-done-36k7</guid>
      <description>&lt;p&gt;The chart looked fine in the code.&lt;/p&gt;

&lt;p&gt;The data pipeline was correct. The chart library call had the right shape. Every prop was passed, every array was populated, and nothing about the component would have looked suspicious in thirty seconds of review. If you had put that diff in front of me cold, I would have approved it.&lt;/p&gt;

&lt;p&gt;Then I opened the page. The performance chart was rendering the same series twice: two overlapping traces at slightly different weights, a smeared, doubled line that no one would ship on purpose.&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%2Fi8ba9vyriufj63rkv5fr.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%2Fi8ba9vyriufj63rkv5fr.png" alt="The unassisted build's chart: the same data series drawn twice at different stroke weights, producing a visibly smeared and doubled line" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The same series, drawn twice. This build reported the task complete.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The agent that wrote it had already marked the task done.&lt;/p&gt;

&lt;p&gt;This series is about that word, "done", and what it took to stop trusting it. It starts with a tool I built for the TestMuAI Kane CLI hackathon, where it placed 2nd.&lt;/p&gt;

&lt;h2&gt;
  
  
  A planner that never checked
&lt;/h2&gt;

&lt;p&gt;GuardianKane did not start as a verifier. The first four commits in the repo are a planning tool: a design spec, an implementation plan, a task-tracker library, and a Claude Code skill with a thin &lt;code&gt;kane-cli&lt;/code&gt; wrapper. You gave it a PRD and got back a &lt;code&gt;task-tracker.md&lt;/code&gt;: tasks with ids, titles, the PRD lines each one implements, and dependencies.&lt;/p&gt;

&lt;p&gt;It was tidy, and it had one hole in the middle. The agent worked through the tracker and moved each task to done when it decided the task was done. Nothing checked. The tracker was a list of claims, and the planner's whole output depended on believing them.&lt;/p&gt;

&lt;p&gt;The same afternoon, commit &lt;code&gt;896bc57&lt;/code&gt; added two Claude Code hooks, and the planner became a gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gate, in one picture
&lt;/h2&gt;

&lt;p&gt;Claude Code has a hook called &lt;code&gt;Stop&lt;/code&gt;. It fires every time the agent finishes a turn and is about to hand control back. If the hook prints &lt;code&gt;{"decision": "block"}&lt;/code&gt; with a reason, the agent does not stop. It reads the reason and keeps working.&lt;/p&gt;

&lt;p&gt;That is the entire lever. Everything else is what I put behind it.&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%2F7p009eufmh3h358465pi.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%2F7p009eufmh3h358465pi.png" alt="Diagram of a Claude Code session: the agent claims a task done, the Stop hook fires, Kane CLI replays the task's test and runs a defect sweep in headless Chrome against the dev server, and the hook either blocks the stop with a reason or advances the task to KANE_VERIFIED" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The agent never calls Kane itself. The hook does, every time it tries to stop.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The tracker grew a state machine. The agent may move a task to &lt;code&gt;IN_PROGRESS&lt;/code&gt; and &lt;code&gt;CLAIMED_DONE&lt;/code&gt;. Only the hook is supposed to write &lt;code&gt;KANE_VERIFIED&lt;/code&gt;. The asymmetry is the point: the agent can say it is done; it cannot say it is verified.&lt;/p&gt;

&lt;p&gt;When a task is &lt;code&gt;CLAIMED_DONE&lt;/code&gt; and the agent tries to stop, the hook drives &lt;a href="https://testmuai.com" rel="noopener noreferrer"&gt;Kane CLI&lt;/a&gt;, TestMuAI's browser agent, against the running dev server in a real headless Chrome. Two checks run per task:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A scripted replay.&lt;/strong&gt; The test Kane generated for the task, run exactly as authored: &lt;code&gt;kane-cli testmd run &amp;lt;file&amp;gt; --agent --headless --variables &amp;lt;json&amp;gt;&lt;/code&gt;, with &lt;code&gt;start_url&lt;/code&gt; pointed at the local server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A defect sweep.&lt;/strong&gt; No test file at all, just a free-text objective: open the app, read this task's PRD section, and report visual defects, layout problems, missing elements, console errors, or anything that does not match the requirement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If either fails, the hook denies the stop with a reason written for the agent: the task, the attempt count, Kane's summary of what failed, and one instruction. Flip the task back to &lt;code&gt;IN_PROGRESS&lt;/code&gt;, fix the code, and claim done again. There is no path from "the browser proved this broken" back to "done" that does not go through the code.&lt;/p&gt;

&lt;p&gt;Two hook entries in &lt;code&gt;.claude/settings.json&lt;/code&gt; are the whole Claude Code side:&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;"hooks"&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;"Stop"&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;"hooks"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".claude/hooks/guardian-kane-stop.sh"&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;"PostToolUse"&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;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Edit|Write"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".claude/hooks/guardian-kane-post-tool-use.sh"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;PostToolUse&lt;/code&gt; quietly records which files each task touched. In this shape of the tool nothing reads that record. Hold on to it; it becomes the most important data in the project later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four pairs of builds
&lt;/h2&gt;

&lt;p&gt;To find out whether any of this mattered, I ran four paired experiments. Each pair is the same PRD given to two builds: one unassisted Claude Code agent told "build this", and one going through the full GuardianKane loop. The PRDs were a todo app, a room booking widget, a higher-fidelity booking studio, and ORBITAL: a dense single-page institutional portfolio dashboard with twelve or so interactive subsystems.&lt;/p&gt;

&lt;p&gt;ORBITAL is where the chart came from, because it is the only one of the four with enough visual density to have somewhere to hide.&lt;/p&gt;

&lt;p&gt;The gated build, &lt;code&gt;orbital-kane&lt;/code&gt;, rendered one clean trend line and a visually distinct dashed benchmark, which is what the PRD asked for.&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%2Fvszgepakdb5mcewlo35s.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%2Fvszgepakdb5mcewlo35s.png" alt="The gated build's portfolio performance chart: a single clean solid trend line with a separate dashed benchmark line beneath it" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;code&gt;orbital-kane&lt;/code&gt;. One series, one line.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The unassisted build, &lt;code&gt;orbital-baseline&lt;/code&gt;, drew the doubled line at the top of this post. It also drifted on the alerts panel. The PRD specifies a single-column, full-width, stacked alert list where one alert expands at a time. The baseline built a compact two-column grid instead.&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%2Fvzk0vd2voqapaukmnjhf.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%2Fvzk0vd2voqapaukmnjhf.png" alt="The gated build's alerts panel: full-width alert cards stacked vertically, one expanded with its action visible" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;code&gt;orbital-kane&lt;/code&gt;. Full width, stacked, one expanded.&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%2Fnhulbtlr2x29805kz4bm.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%2Fnhulbtlr2x29805kz4bm.png" alt="The unassisted build's alerts panel: alert cards squeezed into two columns side by side, with uneven gaps against the panels below" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;code&gt;orbital-baseline&lt;/code&gt;. Two columns, and the gaps do not match the panels beneath it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The same screenshot shows a third problem: the space around the alerts block does not match the space around the holdings and risk cards directly below it.&lt;/p&gt;

&lt;p&gt;Three defects in one build, and not one of them is visible in a diff. A grid class is not wrong-looking. A chart component that renders one series twice is well formed. The bugs only exist at the moment of render, so the only way to find them is for something to look at the rendered page.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the gate actually caught
&lt;/h2&gt;

&lt;p&gt;Going in, I assumed the scripted replay was the valuable half of the gate. It is deterministic and repeatable; it is what you would build first. I built the sweep almost as an afterthought and was mildly embarrassed by how fuzzy it was.&lt;/p&gt;

&lt;p&gt;The ORBITAL activity log says otherwise. Twelve tasks, all twelve reached &lt;code&gt;KANE_VERIFIED&lt;/code&gt;. The sweep flagged something exactly once, on T7, the alerts panel. Both scripted tests for T7 had just passed. They covered expand and switch behavior and asserted nothing about layout. The sweep failed the task anyway and sent it back to &lt;code&gt;IN_PROGRESS&lt;/code&gt;. About 43 minutes later the tests passed again and the sweep came back clean.&lt;/p&gt;

&lt;p&gt;That log line carries no summary text, so I cannot tell you what the sweep saw. The alerts panel is the same panel the unassisted build got wrong, which is a tempting story. I have no data connecting the two, so I am not telling it.&lt;/p&gt;

&lt;p&gt;What I can say is that it was the only failure in the whole build that was a real problem with the app. And the pattern underneath it is general: a scripted test can only check what you thought to assert. The defects that ship are, almost by definition, the ones nobody thought to assert. A strategy made only of assertions written in advance cannot catch the thing most likely to hurt you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things the wiring taught me
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Exit codes lie, in both directions.&lt;/strong&gt; I first mapped &lt;code&gt;kane-cli&lt;/code&gt;'s exit code straight into the gate. A replay could exit 1 while its own final &lt;code&gt;run_end&lt;/code&gt; line reported &lt;code&gt;status: 'passed'&lt;/code&gt; with every step passing. A sweep could exit 0, because the objective "completed", while its verdict reported a confirmed bug. The hook now parses the last structured line and trusts the verdict over the process status. If you wire any agentic tool into a gate, read its structured output. The exit code is a summary of a summary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Killing the process does not kill the browser.&lt;/strong&gt; &lt;code&gt;kane-cli&lt;/code&gt; spawns its own runner, and under it the headless Chrome instances. A timeout that kills the child you spawned leaves those running as orphans, still holding a debugging port. The fix is to spawn detached, so the child leads its own process group, and kill the whole group after every run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;killProcessGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGKILL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ESRCH: group already empty&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;The naive reading is "agents are unreliable, add tests." That was already the advice before I started, so it is not a finding.&lt;/p&gt;

&lt;p&gt;The more precise reading is that the failure was never in the code generation. Claude wrote a chart component that was, structurally, fine. The failure was in the self-report. An agent saying "done" is an assertion with no evidence attached, and my first tool had treated that assertion as a state transition.&lt;/p&gt;

&lt;p&gt;I have to be careful about what the gated build proves. Its chart was right, but the log does not show the gate catching a doubled chart and forcing a fix. The gated build also had its PRD grilled for ambiguity before any code existed, and twelve tasks each scoped to a PRD section. Which of those made the difference, one pair of builds cannot tell me.&lt;/p&gt;

&lt;p&gt;The narrower claim holds. In the gated build, nothing reached "done" on the agent's word. In the unassisted build, "done" was a sentence, and the sentence was wrong.&lt;/p&gt;

&lt;p&gt;There is a version of this that is just CI, and for a lot of projects CI is enough. What CI does not give you is a check inside the agent's own loop, before it stops, while it still has the context to fix what it broke. By the time CI goes red, the session is over. The gate fires while the agent is still standing there.&lt;/p&gt;

&lt;p&gt;The rest of this series is about the two surprises that followed. The first is where the gated build's advantage actually came from, and it was not the browser. The second is that of the eight ORBITAL tasks that failed a check, seven were not the app's fault at all.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part 1 of 3. Next: &lt;a href="https://abhinavpangaria.pages.dev/dispatches/the-requirement-nobody-wrote" rel="noopener noreferrer"&gt;the requirement nobody wrote&lt;/a&gt;, on the question that separated the builds and the experiment that found nothing. The code is at &lt;a href="https://github.com/18Abhinav07/adventures-with-kane" rel="noopener noreferrer"&gt;github.com/18Abhinav07/adventures-with-kane&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part 1 of &lt;a href="https://abhinavpangaria.pages.dev/dispatches/series/your-agent-might-lie" rel="noopener noreferrer"&gt;Your Agent Might Actually Lie to You&lt;/a&gt;. The original, with the full series navigation, is &lt;a href="https://abhinavpangaria.pages.dev/dispatches/agent-shipped-a-broken-chart" rel="noopener noreferrer"&gt;on my site&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>claudecode</category>
      <category>testing</category>
      <category>verification</category>
    </item>
  </channel>
</rss>
