<?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: Little shorts</title>
    <description>The latest articles on DEV Community by Little shorts (@avgdev7001).</description>
    <link>https://dev.to/avgdev7001</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%2F4234044%2F2d8a3d1c-9d06-4533-a6dd-8fac68a0d178.png</url>
      <title>DEV Community: Little shorts</title>
      <link>https://dev.to/avgdev7001</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/avgdev7001"/>
    <language>en</language>
    <item>
      <title>My coding agent burned tokens in a loop. So I built it a brake.</title>
      <dc:creator>Little shorts</dc:creator>
      <pubDate>Sun, 08 Nov 2026 19:36:50 +0000</pubDate>
      <link>https://dev.to/avgdev7001/my-coding-agent-burned-tokens-in-a-loop-so-i-built-it-a-brake-32nj</link>
      <guid>https://dev.to/avgdev7001/my-coding-agent-burned-tokens-in-a-loop-so-i-built-it-a-brake-32nj</guid>
      <description>&lt;h1&gt;
  
  
  My coding agent burned tokens in a loop. So I built it a brake.
&lt;/h1&gt;

&lt;p&gt;Here's a failure mode every heavy user of coding agents has seen: the agent gets stuck. It re-reads the same file. It retries the same failing test with cosmetically different commands. It makes call after call, each one plausible, none of them progress — and every call costs tokens.&lt;/p&gt;

&lt;p&gt;When I went looking for tooling, I found plenty of cost dashboards: ccusage, CodeBurn, and friends. They're good tools. But they all share one property — they tell you what a session cost &lt;strong&gt;after it ended&lt;/strong&gt;. That's an autopsy, not a safety system.&lt;/p&gt;

&lt;p&gt;I wanted something that intervenes &lt;strong&gt;live&lt;/strong&gt;, inside the session, while there's still something to save. So I built DejaLoop, an open-source Claude Code plugin. This post is about the interesting engineering problem underneath it: &lt;em&gt;how do you tell a stuck agent from a busy one, in real time, cheaply enough to run on every session?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why fingerprints aren't enough
&lt;/h2&gt;

&lt;p&gt;The naive approach is fingerprinting: hash each tool call (tool name + inputs), and flag repeats. It works — for the dumbest loops. An agent reading the same file six times in a row is trivially detectable.&lt;/p&gt;

&lt;p&gt;But the expensive loops are smarter than that. Consider this trajectory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pytest -q            → FAILED
pytest tests/        → FAILED
python -m pytest     → FAILED
pytest -q -x         → FAILED
pytest tests/ -q     → FAILED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six calls, six &lt;em&gt;different&lt;/em&gt; fingerprints, zero edits in between. A fingerprint detector sees variety and concludes the agent is exploring. A human sees an agent that has retried the same failing strategy six ways and changed nothing. The difference between those two readings is &lt;strong&gt;semantics&lt;/strong&gt;: you have to judge whether the trajectory constitutes progress, not whether the calls are identical.&lt;/p&gt;

&lt;p&gt;The opposite failure matters just as much. A TDD session — edit, test fails, edit, test fails, edit, test passes — &lt;em&gt;looks&lt;/em&gt; like a failing loop to naive detection. Flag that, and your guard becomes the most annoying tool on someone's machine. Any detector in this space lives or dies on its false-alarm rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The design: three layers
&lt;/h2&gt;

&lt;p&gt;DejaLoop runs inside Claude Code via its hooks system (&lt;code&gt;PreToolUse&lt;/code&gt;, &lt;code&gt;PostToolUse&lt;/code&gt;, &lt;code&gt;Stop&lt;/code&gt;). Every tool call flows through three layers:&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1 — Deterministic checks (free, instant)
&lt;/h3&gt;

&lt;p&gt;Fingerprint every call. Same call 3× consecutively, or Bash failing 3× in a row, is a strong local signal that costs nothing to compute. This layer also maintains the trajectory features the judge needs: repetition streaks, failure streaks, call-similarity, steps since the last edit, whether any verification (test/build/lint) has run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2 — A semantic judge
&lt;/h3&gt;

&lt;p&gt;Every few tool calls — or immediately when layer 1 smells trouble — the recent trajectory is scored by a judge answering typed questions: &lt;em&gt;Is the agent stuck? Is it repeating a failed strategy? How would you rate its progress?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The judge is pluggable, and there are two real options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A zero-cost local judge&lt;/strong&gt; that ships built in. No API key, no network, works offline. It's a purpose-built scorer for this one narrow judgment — not a general model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeSafe Jev&lt;/strong&gt; (bring your own key). Jev is a dedicated decision model that returns probabilities for typed questions, which is exactly the shape this problem wants. It costs about $0.0002 per typical session at current pricing — a $5 credit covers tens of thousands of sessions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A general lesson here: I did &lt;strong&gt;not&lt;/strong&gt; need a general-purpose decision model. I needed one judgment, made cheaply and often. Narrow judges are dramatically easier to build, test, and afford.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 3 — Policy: escalate, don't amputate
&lt;/h3&gt;

&lt;p&gt;A "chance it's stuck" score isn't an action. The policy layer maps it to escalating interventions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt; 0.50&lt;/td&gt;
&lt;td&gt;Continue silently&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.50–0.75&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Warn&lt;/strong&gt; — inject a message into the session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75–0.90&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Replan&lt;/strong&gt; — deny the next tool call, feed the reason back to the agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;gt; 0.90&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Halt &amp;amp; ask&lt;/strong&gt; — stop and let the human decide&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There's also a separate check on the &lt;code&gt;Stop&lt;/code&gt; event: if the agent edited files but never ran a test, build, or lint, its "done" claim gets blocked and it's sent back to verify. Anyone who has reviewed agent output knows why that one exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two safety properties that are not optional
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Fail-open.&lt;/strong&gt; If the judge times out, if the plugin throws, if anything at all goes wrong in the guard — the session proceeds, untouched. A safety tool that can brick your session is a liability, not a safety tool. Every exception path in the hook handler resolves to "allow."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shadow mode by default.&lt;/strong&gt; New installs only &lt;em&gt;log&lt;/em&gt; what the guard would have done. Blocking is opt-in, after you've read the logs and trust it. This is how security tooling earns adoption, and it's also how I get real-world false-alarm data without hurting anyone's work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The benchmark (and its limits)
&lt;/h2&gt;

&lt;p&gt;I built 8 labeled scenarios — 4 stuck, 4 healthy — with the healthy set deliberately including the hard negatives: a TDD red-green cycle, varied exploration across files, and legitimate repeated test runs. A scenario counts as "caught" if the guard intervenes; a healthy scenario counts as a false alarm if it intervenes at all.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Judge&lt;/th&gt;
&lt;th&gt;Stuck caught&lt;/th&gt;
&lt;th&gt;False alarms&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deterministic rules only&lt;/td&gt;
&lt;td&gt;1/4&lt;/td&gt;
&lt;td&gt;0/4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local judge (zero-cost)&lt;/td&gt;
&lt;td&gt;4/4&lt;/td&gt;
&lt;td&gt;0/4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Rules alone miss exactly the cases I built this for — the varied-command failing loop, the alternating two-strategy loop, the near-identical search thrash. The local judge caught all of them and left every healthy pattern alone.&lt;/p&gt;

&lt;p&gt;Now the honesty paragraph, because this audience deserves it: &lt;strong&gt;8 scenarios is a small set, and they're simulated.&lt;/strong&gt; Claude Code hooks are just JSON on stdin, which makes the whole pipeline testable offline with scripted payloads — that's how these numbers were produced — but real sessions are longer, messier, and weirder than anything I scripted. The benchmark code is in the repo; the real validation will be shadow-mode logs from actual use, and I'd rather tell you that up front than dress up a small number as a big one.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Calibrate against real Jev.&lt;/strong&gt; The Jev client is implemented against the documented API, but its thresholds (0.50 / 0.75 / 0.90) are uncalibrated against real Jev probabilities. That's the first job once it's running with a live key on real sessions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More agents.&lt;/strong&gt; v1 is Claude Code only, because that's where the hooks are documented and where I work. A Codex adapter is the obvious next step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learn from the ledger.&lt;/strong&gt; Every intervention is logged locally (&lt;code&gt;~/.dejaloop/ledger.jsonl&lt;/code&gt; in the current build). Aggregate patterns — with consent, never by default — are how the judge gets smarter than my 8 scenarios.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;DejaLoop is MIT-licensed, Python standard library only, nothing hosted, no telemetry, bring-your-own-key only if you opt into Jev.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/Syed-Amaan-ullah/dejaloop" rel="noopener noreferrer"&gt;https://github.com/Syed-Amaan-ullah/dejaloop&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've watched an agent loop your money away, I want to hear the story — and if you run it in shadow mode, I want to hear what it &lt;em&gt;would&lt;/em&gt; have caught. That's the data that makes v2 real.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>programming</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
