<?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: Faizan Raza</title>
    <description>The latest articles on DEV Community by Faizan Raza (@faizan_raza_8f0a1d3e9428e).</description>
    <link>https://dev.to/faizan_raza_8f0a1d3e9428e</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%2F4050311%2Ff872b964-7c15-4d11-9e36-dd48f797305c.png</url>
      <title>DEV Community: Faizan Raza</title>
      <link>https://dev.to/faizan_raza_8f0a1d3e9428e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/faizan_raza_8f0a1d3e9428e"/>
    <language>en</language>
    <item>
      <title>Catching the AI Agent Failure Mode Your Loop Guard Can't See</title>
      <dc:creator>Faizan Raza</dc:creator>
      <pubDate>Tue, 28 Jul 2026 01:11:27 +0000</pubDate>
      <link>https://dev.to/faizan_raza_8f0a1d3e9428e/catching-the-ai-agent-failure-mode-your-loop-guard-cant-see-1c92</link>
      <guid>https://dev.to/faizan_raza_8f0a1d3e9428e/catching-the-ai-agent-failure-mode-your-loop-guard-cant-see-1c92</guid>
      <description>&lt;h2&gt;
  
  
  Your agent's loop guard almost certainly has a blind spot
&lt;/h2&gt;

&lt;p&gt;Most agent frameworks that bother to guard against runaway loops do it the same&lt;br&gt;
way: hash each tool call's name and arguments, keep a sliding window of recent&lt;br&gt;
hashes, and flag it if the same hash shows up twice. It's cheap, it's simple,&lt;br&gt;
and it catches the most obvious case.&lt;/p&gt;

&lt;p&gt;It also has three specific blind spots that let an agent burn real money while&lt;br&gt;
looking perfectly fine on a dashboard that only checks for exact duplicates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fuzzy loops.&lt;/strong&gt; An agent retrying the same failing action with a slightly&lt;br&gt;
different argument each time (an incrementing attempt counter, a fresh&lt;br&gt;
timestamp) never hashes equal to its own prior attempt, even though it's the&lt;br&gt;
same unproductive retry every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semantic oscillation.&lt;/strong&gt; An agent alternating between two strategies,&lt;br&gt;
&lt;code&gt;check_status&lt;/code&gt; → &lt;code&gt;retry_action&lt;/code&gt; → &lt;code&gt;check_status&lt;/code&gt; → &lt;code&gt;retry_action&lt;/code&gt;, with each&lt;br&gt;
individual call looking "new" relative to the one immediately before it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Productive-looking stalls.&lt;/strong&gt; An agent whose every tool call is technically&lt;br&gt;
unique (different tool, different arguments, every single time) but whose&lt;br&gt;
actual results are boilerplate and whose context keeps growing regardless. An&lt;br&gt;
exact-match detector sees zero duplicates and concludes everything is fine.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://github.com/faizannraza/wattage" rel="noopener noreferrer"&gt;Wattage&lt;/a&gt;, an open-source&lt;br&gt;
token-spend profiler and cost-regression gate for AI agents, because I kept&lt;br&gt;
hitting all three of these in practice, and none of them showed up until the&lt;br&gt;
bill did.&lt;/p&gt;
&lt;h2&gt;
  
  
  Measuring progress instead of counting duplicates
&lt;/h2&gt;

&lt;p&gt;Rather than asking "have I seen this exact call before," Wattage's&lt;br&gt;
&lt;code&gt;nonconvergence&lt;/code&gt; detector measures &lt;strong&gt;progress&lt;/strong&gt; directly, per iteration, using&lt;br&gt;
five signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Evidence gain (E)&lt;/strong&gt;: how novel this iteration's tool/retrieval results
are against everything seen so far in the loop (cosine novelty over a
lightweight embedding).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State delta (S)&lt;/strong&gt;: how much the agent's chosen action actually changed
from just the &lt;em&gt;immediately prior&lt;/em&gt; iteration (a local, not cumulative,
comparison, which is what keeps this a distinct signal from E).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Oscillation (O)&lt;/strong&gt;: periodic repetition in the sequence of actions taken
(period 2 or more only), robust to argument noise because it keys on tool
names, not exact arguments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Growth-vs-information (G)&lt;/strong&gt;: how many new context tokens this iteration
cost relative to how much genuinely new evidence arrived, so a loop can have
low evidence gain yet still be judged on whether that repetition was &lt;em&gt;cheap&lt;/em&gt;
or &lt;em&gt;expensive&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Goal proximity (P)&lt;/strong&gt;: only meaningful when an explicit goal signal is
supplied; defaults to a neutral value otherwise, never a guess.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These combine into a per-iteration progress score, and a loop gets classified&lt;br&gt;
as &lt;strong&gt;productive&lt;/strong&gt;, &lt;strong&gt;thrashing&lt;/strong&gt; (the same unproductive action repeated),&lt;br&gt;
&lt;strong&gt;oscillating&lt;/strong&gt; (a detected cycle), or &lt;strong&gt;stalled&lt;/strong&gt; (context keeps growing,&lt;br&gt;
evidence and state both stay flat: the exact-match blind spot). A loop that&lt;br&gt;
genuinely recovers only gets credit for that if there's real, substantive,&lt;br&gt;
different content in its later iterations, ending isn't the same as&lt;br&gt;
succeeding.&lt;/p&gt;
&lt;h2&gt;
  
  
  The evidence, not a marketing claim
&lt;/h2&gt;

&lt;p&gt;I didn't want to just assert this works better. So I built a hand-reviewed set&lt;br&gt;
of 10 labeled synthetic loops (three productive, three thrashing, two&lt;br&gt;
oscillating, two stalled), each specifically constructed so its label is&lt;br&gt;
defensible on inspection, and benchmarked Wattage's classifier against a real&lt;br&gt;
reference implementation of the SHA-256 exact-match baseline:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Classifier&lt;/th&gt;
&lt;th&gt;Precision&lt;/th&gt;
&lt;th&gt;Recall&lt;/th&gt;
&lt;th&gt;F1&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Wattage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.00&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-256 exact-match&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;0.14&lt;/td&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The baseline catches exactly one of the seven genuinely non-convergent cases:&lt;br&gt;
the one where the arguments happened to be byte-identical every time. The&lt;br&gt;
concrete case it misses entirely: a loop where every tool call is technically&lt;br&gt;
unique, yet Wattage correctly flags it as stalled because the context kept&lt;br&gt;
growing while the results stayed boilerplate.&lt;/p&gt;

&lt;p&gt;Reproduce it yourself, no cherry-picking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run python &lt;span class="nt"&gt;-m&lt;/span&gt; benchmarks.harness
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  It's not just loop detection, it prices everything
&lt;/h2&gt;

&lt;p&gt;The convergence engine is the part I'm proudest of, but it's one of eight&lt;br&gt;
detectors. Point Wattage at an OpenTelemetry GenAI trace (OTLP JSON, no&lt;br&gt;
vendor lock-in, no proprietary SDK) and it prices every call against a real,&lt;br&gt;
dated pricing snapshot and runs detectors for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;prefix_churn&lt;/code&gt;: a stable prompt re-sent instead of cached&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cache_gap&lt;/code&gt;: caching attempted but under-redeemed by later reads&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;verbosity&lt;/code&gt;: output far beyond what the step needed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;redundant_tool_calls&lt;/code&gt;: the same tool call repeated, exact or fuzzy&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nonconvergence&lt;/code&gt;: the loop detector above&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;retrieval_thrash&lt;/code&gt;: repeated retrieval that never yields relevant results&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;model_mismatch&lt;/code&gt;: a pricier model doing work a cheaper one could handle&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reasoning_overspend&lt;/code&gt;: heavy reasoning-token spend on a simple step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On a real captured agent trace (not synthetic), Wattage's &lt;code&gt;prefix_churn&lt;/code&gt; fix&lt;br&gt;
simulation shows a &lt;strong&gt;44.7% cost reduction&lt;/strong&gt; ($0.000199 to $0.000110) from&lt;br&gt;
enabling prompt caching on a stable prefix, a small dollar figure because&lt;br&gt;
it's a 3-turn demo trace, but the mechanism is identical at production scale.&lt;/p&gt;

&lt;p&gt;Wattage never fabricates a number: an unpriced model leaves that call's cost&lt;br&gt;
at zero and fails loudly rather than guessing, and an unmeasured quality&lt;br&gt;
signal is reported as &lt;code&gt;unmeasured&lt;/code&gt;, never assumed fine.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Fully offline, no API key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uvx wattage report trace.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if you want this enforced automatically rather than checked by hand,&lt;br&gt;
&lt;code&gt;wattage ci&lt;/code&gt; is a cost-regression gate. It fails your build when a change&lt;br&gt;
makes your agent measurably more expensive, the same idea as a&lt;br&gt;
perf-regression gate, with a per-detector delta posted as a PR comment.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/faizannraza/wattage" rel="noopener noreferrer"&gt;https://github.com/faizannraza/wattage&lt;/a&gt;&lt;br&gt;
Docs: &lt;a href="https://faizannraza.github.io/wattage/" rel="noopener noreferrer"&gt;https://faizannraza.github.io/wattage/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Would love feedback, especially from anyone whose agent traces trip up the&lt;br&gt;
loop detector in a way that points at a case I haven't handled.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>llm</category>
      <category>opentelemetry</category>
    </item>
  </channel>
</rss>
