<?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: Ahmad ammar</title>
    <description>The latest articles on DEV Community by Ahmad ammar (@ahmadammar).</description>
    <link>https://dev.to/ahmadammar</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%2F4008655%2F02a38ff7-7a96-4951-ace5-49b80fbf78ab.jpeg</url>
      <title>DEV Community: Ahmad ammar</title>
      <link>https://dev.to/ahmadammar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ahmadammar"/>
    <language>en</language>
    <item>
      <title>Small local models: what actually holds up when you re-run the measurement</title>
      <dc:creator>Ahmad ammar</dc:creator>
      <pubDate>Fri, 21 Aug 2026 18:18:08 +0000</pubDate>
      <link>https://dev.to/ahmadammar/small-local-models-what-actually-holds-up-when-you-re-run-the-measurement-5590</link>
      <guid>https://dev.to/ahmadammar/small-local-models-what-actually-holds-up-when-you-re-run-the-measurement-5590</guid>
      <description>&lt;p&gt;I run a small local model — &lt;strong&gt;Qwen3.5 4B via Ollama&lt;/strong&gt; — as one deterministic-checked step inside some tooling. This is what still reproduces today, on named versions, with the scripts to check it yourself. Where a result did &lt;em&gt;not&lt;/em&gt; survive re-running, that is here too, because that turned out to be the most useful part.&lt;/p&gt;

&lt;p&gt;Versions, so you can pin them: &lt;strong&gt;Ollama 0.32.6&lt;/strong&gt;, model &lt;code&gt;qwen3.5:4b&lt;/code&gt;, digest &lt;code&gt;2a654d98e6fb&lt;/code&gt;, Q4_K_M.&lt;/p&gt;

&lt;p&gt;None of this is a new technique. The underlying pattern is &lt;strong&gt;Best-of-N with verifiers&lt;/strong&gt;, which is well established. These are measurements inside a known pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;think: true&lt;/code&gt; can hand you an empty answer
&lt;/h2&gt;

&lt;p&gt;With thinking enabled, the chain-of-thought and the final answer share one token budget. When the thinking consumes it, &lt;code&gt;response&lt;/code&gt; comes back empty while &lt;code&gt;thinking&lt;/code&gt; holds the content. Code that reads only &lt;code&gt;response&lt;/code&gt; sees &lt;code&gt;""&lt;/code&gt; and concludes the call failed. It didn't.&lt;/p&gt;

&lt;p&gt;Measured today, one-line question, &lt;code&gt;num_predict: 128&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;think:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;response=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;thinking=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;think:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;response=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;thinking=&lt;/span&gt;&lt;span class="mi"&gt;483&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fix: for extraction and structured tasks, read both fields, or turn thinking off. Whether it triggers depends on your budget — tighten &lt;code&gt;num_predict&lt;/code&gt; to make it likely.&lt;/p&gt;

&lt;p&gt;(Separately: there is evidence that pushing long chain-of-thought onto small models can &lt;em&gt;hurt&lt;/em&gt; accuracy — Luo et al., &lt;em&gt;"Through the Valley: Path to Effective Long CoT Training for Small Language Models,"&lt;/em&gt; EMNLP 2025. That is a training-time effect, not this API field-routing quirk. Different problem; noting it so the two don't get conflated.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-critique made the 4B worse
&lt;/h2&gt;

&lt;p&gt;I asked it to critique its own answer. It verbally retracted the wrong fix — "Remove the check proposed above" — and then re-emitted that same wrong fix in code, in the same reply.&lt;/p&gt;

&lt;p&gt;One anecdotal example of what &lt;strong&gt;Huang et al.&lt;/strong&gt; measure at scale in &lt;em&gt;"Large Language Models Cannot Self-Correct Reasoning Yet"&lt;/em&gt; (arXiv:2310.01798, ICLR 2024): on GSM8K, GPT-4's own accuracy drops &lt;strong&gt;95.5% → 91.5% → 89.0%&lt;/strong&gt; under intrinsic self-critique.&lt;/p&gt;

&lt;p&gt;Self-critique is not a free reliability layer. You need an external judge, not the model grading itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Request precision beat model depth
&lt;/h2&gt;

&lt;p&gt;Same input, two prompts. Asking for output keys that match the source's naming &lt;em&gt;literally&lt;/em&gt; moved accuracy &lt;strong&gt;7/10 → 10/10&lt;/strong&gt;. A numeric spec — "each row has exactly 3 cells and 4 dashes" — fixed a table that a descriptive spec — "a well-formed separator row" — kept producing broken.&lt;/p&gt;

&lt;p&gt;Well-worn prompt-engineering advice. Included only because I have the before/after on the same input.&lt;/p&gt;

&lt;h2&gt;
  
  
  The external judge needs its own negative control
&lt;/h2&gt;

&lt;p&gt;Here is the part I got wrong for a while, and it is the expensive one.&lt;/p&gt;

&lt;p&gt;"Put a deterministic external check on the output" is half an instruction. I had that check. It passed a fabrication.&lt;/p&gt;

&lt;p&gt;A merge task, two columns into one, 81 rows. The model invented 5 values that were never in the input. The check returned &lt;code&gt;PASS&lt;/code&gt;, &lt;code&gt;autoVerified: true&lt;/code&gt;, and &lt;strong&gt;exit code 0&lt;/strong&gt;. It was not bypassed and it did not crash. Every invented value was drawn from a closed vocabulary — every value that &lt;em&gt;could&lt;/em&gt; appear did appear somewhere in the input — so every token-level assertion the check ran was satisfied. The judge was asked a question that could not tell the two cases apart.&lt;/p&gt;

&lt;p&gt;So the rule I would give someone starting this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A judge that has never been made to fail has not been shown to pass.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Build the check, then feed it material it &lt;em&gt;must&lt;/em&gt; reject. If it stays green, you don't have a judge — you have a second thing that agrees with the model. And when the output is a pure function of the input, compare it &lt;em&gt;to the input&lt;/em&gt; mechanically before accepting it. An exit code is not evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  And one result that did not survive
&lt;/h2&gt;

&lt;p&gt;An earlier note of mine reported that this model drops the space at a Latin↔Arabic boundary — ask for &lt;code&gt;hello مرحبا&lt;/code&gt;, get back &lt;code&gt;helloمرحبا&lt;/code&gt;. I had it in 2 of 2 runs and treated it as a real defect.&lt;/p&gt;

&lt;p&gt;Re-running it today: &lt;strong&gt;53 generations. Zero reproductions.&lt;/strong&gt; Seven prompt shapes on the pinned model, six of those shapes repeated on two other local 4B variants, plus the original echo prompt.&lt;/p&gt;

&lt;p&gt;I can't tell you it was fixed, and I'm not going to pretend otherwise — the model file on disk is the same one that was there when I first measured it. Prompt shape doesn't explain it either; I tried the transform-style tasks that were closer to the original context and they came back clean too.&lt;/p&gt;

&lt;p&gt;What I can tell you is that a result I would have published as a measured defect was, six weeks later, unreproducible — and I only know that because re-running it was one command. Both scripts are in the repo; the boundary one is currently a null result, and it stays in, because a null result you can run is worth more than a claim you can't.&lt;/p&gt;

&lt;p&gt;The check itself is four lines and still worth having if you ship bilingual text — English-centric output assertions never look at the script boundary, so nothing else in your test suite is watching that seam.&lt;/p&gt;

&lt;h2&gt;
  
  
  The through-line
&lt;/h2&gt;

&lt;p&gt;A small model doesn't get better when you ask it to think more. It gets better when you &lt;strong&gt;reduce what it has to decide&lt;/strong&gt;, put a &lt;strong&gt;deterministic external judge&lt;/strong&gt; on its output — and then &lt;strong&gt;prove the judge can fail&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That framing isn't mine. DSPy's &lt;code&gt;BestOfN&lt;/code&gt; with a custom &lt;code&gt;reward_fn&lt;/code&gt; reproduces most of this shape; CodeT and AlphaCode filter samples by executing tests; Guardrails' &lt;code&gt;on_fail=reask&lt;/code&gt; and Instructor drive retry-on-failure. Even graduated exit codes for agent steps are the &lt;strong&gt;Nagios&lt;/strong&gt; convention (&lt;code&gt;0&lt;/code&gt; OK / &lt;code&gt;1&lt;/code&gt; WARNING / &lt;code&gt;2&lt;/code&gt; CRITICAL), about 25 years old.&lt;/p&gt;

&lt;p&gt;Reproduction scripts, with versions pinned: &lt;a href="https://github.com/ahmadyaseen35-coder/local-model-field-notes-repro" rel="noopener noreferrer"&gt;https://github.com/ahmadyaseen35-coder/local-model-field-notes-repro&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>localai</category>
      <category>agents</category>
    </item>
    <item>
      <title>How parallel AI agents should talk to each other (and the bug that proved it)</title>
      <dc:creator>Ahmad ammar</dc:creator>
      <pubDate>Fri, 21 Aug 2026 17:21:19 +0000</pubDate>
      <link>https://dev.to/ahmadammar/how-parallel-ai-agents-should-talk-to-each-other-and-the-bug-that-proved-it-2mh1</link>
      <guid>https://dev.to/ahmadammar/how-parallel-ai-agents-should-talk-to-each-other-and-the-bug-that-proved-it-2mh1</guid>
      <description>&lt;p&gt;If you run more than one coding agent at a time, you hit a problem nobody has a settled answer for: &lt;strong&gt;how do two agent sessions message each other?&lt;/strong&gt; Not the model talking to a tool — two independent sessions, running in parallel, that need to hand off a decision or a result.&lt;/p&gt;

&lt;p&gt;The obvious channel is a human relaying copy-paste. I spent a day watching that fail in two specific ways, then replaced it, then found a bug in the replacement that is the best argument for the whole approach. Here's the pattern and the evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the human relay fails
&lt;/h2&gt;

&lt;p&gt;It fails for two reasons that have nothing to do with typos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. It loses provenance.&lt;/strong&gt; When a message arrives as pasted text, the receiver cannot prove who wrote it. That matters more than it sounds. Two messages reached a session this way in one day: one &lt;strong&gt;asserted a state that had never happened&lt;/strong&gt; ("you enabled X" — to a session that had done no such thing), and one reported &lt;strong&gt;CI green on a commit that was already two commits stale&lt;/strong&gt;. Both were caught. They were caught &lt;em&gt;only because the receiver independently checked&lt;/em&gt; — the message itself carried zero evidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. It looks exactly like a prompt injection.&lt;/strong&gt; "Read this and do it" is the shape of an attack. A well-behaved agent should be suspicious of instructions with no verifiable origin — which means the safe agent and the useful relay are in direct conflict.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern: a committed file + a pointer + a provenance check
&lt;/h2&gt;

&lt;p&gt;The fix is to stop sending &lt;em&gt;content&lt;/em&gt; and start sending a &lt;em&gt;reference to a committed artifact&lt;/em&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The sender writes the message to a file in a path it owns and &lt;strong&gt;commits it&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The sender passes one plain-text line: &lt;code&gt;Read &amp;lt;path&amp;gt; and follow it.&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Before acting, the receiver checks the origin:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'%an %ci'&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; path/to/message.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the message has an author, a timestamp, and an immutable history. "Read this and do it" stops being injection-shaped because the receiver can verify the sender before it acts — and can refuse anything whose provenance it doesn't like. Refusal becomes a &lt;em&gt;feature&lt;/em&gt;, not a failure.&lt;/p&gt;

&lt;p&gt;Add a naming convention so a session can find its own mailbox — &lt;code&gt;message-to-&amp;lt;recipient&amp;gt;-&amp;lt;topic&amp;gt;-&amp;lt;date&amp;gt;.md&lt;/code&gt; — and a tiny script that lists the open ones at session start. Round-trip on this channel that day: a message sent, read, acted on, and replied to in &lt;strong&gt;nine minutes&lt;/strong&gt;, with a verifiable trail at every step.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that proves the point
&lt;/h2&gt;

&lt;p&gt;Here's the part I didn't plan. I wrote the "list my open messages" tool. It ran, and it reported &lt;strong&gt;&lt;code&gt;0 open&lt;/code&gt;&lt;/strong&gt; — while two real messages sat unread. The tool hid its own inbox.&lt;/p&gt;

&lt;p&gt;The cause was one line. Each message carries a status, and the closer looks like this:&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="c1"&gt;// BROKEN: "done" appears in the boilerplate of every OPEN message too&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isClosed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;statusLine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;done&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;statusLine&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;p&gt;Every &lt;em&gt;open&lt;/em&gt; message's status line reads: &lt;code&gt;STATUS: open · Set to "done" after you consume it.&lt;/code&gt; The word &lt;code&gt;done&lt;/code&gt; is right there in the instructions — so the check marked open messages closed and filtered them out. The fix reads the &lt;strong&gt;value&lt;/strong&gt;, not the line:&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="c1"&gt;// FIXED: read only the token right after STATUS:&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isClosed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;statusLine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;statusLine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/STATUS:&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\*&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;([&lt;/span&gt;&lt;span class="sr"&gt;a-z-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/i&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="nx"&gt;m&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;done&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lesson isn't "parse carefully." It's this: &lt;strong&gt;a green result reads as "safe" when it often means "the check can't see."&lt;/strong&gt; Before you trust any all-clear — a gate, a linter, an inbox that says empty — prove the red can appear. One test would have caught it:&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="c1"&gt;// negative control: an OPEN message whose text also contains "done" must still surface&lt;/span&gt;
&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isClosed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;STATUS: open · Set to "done" after you consume&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My synthetic test fixture used a clean status line, so it never contained the trap. The real message did. That is the whole case for dogfooding on real inputs, and for writing the test that tries to make green turn red &lt;em&gt;before&lt;/em&gt; you rely on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things to take
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Provenance beats convenience.&lt;/strong&gt; A message you can't attribute is a message you can't trust — build the channel so every hop is verifiable, and let the receiver refuse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never trust a green you haven't tried to turn red.&lt;/strong&gt; The most dangerous failure isn't the check that fails loudly; it's the one that passes while blind. My inbox tool passed while blind to its own inbox.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Your AI agent's best-practices doc is a wish. So was the number I used to prove it.</title>
      <dc:creator>Ahmad ammar</dc:creator>
      <pubDate>Fri, 21 Aug 2026 15:04:33 +0000</pubDate>
      <link>https://dev.to/ahmadammar/your-ai-agents-best-practices-doc-is-a-wish-so-was-the-number-i-used-to-prove-it-p5o</link>
      <guid>https://dev.to/ahmadammar/your-ai-agents-best-practices-doc-is-a-wish-so-was-the-number-i-used-to-prove-it-p5o</guid>
      <description>&lt;p&gt;You wrote the &lt;code&gt;AGENTS.md&lt;/code&gt;. Or the &lt;code&gt;CLAUDE.md&lt;/code&gt;, or the team best-practices doc. A month later, how many of those practices does any agent actually follow? If you can't answer with a number, you don't have practices — you have wishes.&lt;/p&gt;

&lt;p&gt;I learned that twice. The second time was worse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Documented ≠ adopted
&lt;/h2&gt;

&lt;p&gt;I run three production projects on Claude Code, and I'd written a model-routing rule into my shared config: mechanical work — inventory, renames, boilerplate — goes to the cheap model; hard reasoning — architecture, security, migrations — goes to the expensive one. I wrote it, promoted it to an auto-adopted practice, felt good about it.&lt;/p&gt;

&lt;p&gt;Then I built a small usage analyzer to see where the tokens actually went. Zero dependencies — it just reads the session transcripts the tool already writes to disk. The cheap model came back at &lt;strong&gt;0.03% of output&lt;/strong&gt;: 18k tokens against tens of millions. Not "underused." A rounding error.&lt;/p&gt;

&lt;p&gt;Documentation is not a control surface. The doc had not routed a single task.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix that doesn’t work
&lt;/h2&gt;

&lt;p&gt;The reflex is to write it again, bigger. Add a reminder. Write a third markdown file about the two markdown files nobody is applying.&lt;/p&gt;

&lt;p&gt;This is the same mistake as putting a budget in a prompt. "Be mindful of the model tier" is a wish, exactly like "max 8 searches" is a wish — a model sails past both the moment the task still feels unfinished. If your practice lives only in a doc the session may never open, you do not have a practice. You have a hope with good formatting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure, then enforce below the decision
&lt;/h2&gt;

&lt;p&gt;A practice that lives only in a doc fails for the same structural reason a budget in a prompt fails: nothing forces it into the path where the decision is made. So move it below that path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measure adoption from data you already generate.&lt;/strong&gt; You cannot enforce what you cannot see. The transcripts already record which model ran, which tools fired, which skills were invoked. A zero-dependency reader turns that into an adoption number per practice. That is how I learned the routing rule was near zero — by counting, not guessing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enforce at the boundary the model cannot skip.&lt;/strong&gt; Every session hits session-start. That is the injection surface. Instead of a doc the session may never load, the practice gets printed into the session-start context deterministically: static strings, no extra generation cost, fails open. Same principle as a budget belonging in the harness rather than the prompt — put the practice in the layer the model passes through by construction, not the one it has to choose to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I audited the counter
&lt;/h2&gt;

&lt;p&gt;A month later I went to re-measure, and found the reader had been lying to me the entire time.&lt;/p&gt;

&lt;p&gt;Subagent work is written to a separate sidechain file under the session directory. My reader was not recursive. It never opened those files. Every token a subagent spent was invisible to it.&lt;/p&gt;

&lt;p&gt;That matters because of &lt;em&gt;where the cheap model actually runs&lt;/em&gt;. Measured properly: &lt;strong&gt;100% of the cheap model's output was subagent work.&lt;/strong&gt; Zero on the main thread. The one surface the rule governs was the exact surface the counter could not see.&lt;/p&gt;

&lt;p&gt;So 0.03% was never an adoption number. It was an artifact of a blind spot. The commit that fixed the reader is titled, accurately, &lt;code&gt;read subagent sidechains — "Haiku=0" was a measurement artifact&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is the part that rescues the story: the corrected figure is &lt;strong&gt;0.75%&lt;/strong&gt; — 370k tokens out of 49.4M. Still terrible. The conclusion survived. The evidence for it did not.&lt;/p&gt;

&lt;p&gt;That is the real lesson, and it is less comfortable than the first one: &lt;strong&gt;a measurement you never audit is also a wish.&lt;/strong&gt; It just wears a number, which makes it harder to doubt. I spent a month more confident than my instrument had earned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest caveat
&lt;/h2&gt;

&lt;p&gt;Injection is not adoption either. Firing the reminder every session is necessary, not sufficient. The arbiter is the next measurement cycle — if the cheap-model share climbs, the injection worked; if it does not, the problem is deeper than reminding, and I will know, because I am measuring. A practice you inject but never re-measure is just a wish that loads faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  The moves, in order
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Measure adoption&lt;/strong&gt; from data you already generate. The transcript is a telemetry stream you are not reading.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit the instrument.&lt;/strong&gt; Do not ask whether it looks right — feed it a case whose answer you already know and check that the number comes back. A counter that has never been made to fail has never really passed. Mine had no such test, and when the missing surface finally showed up it moved the figure by more than twenty times.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inject the practice&lt;/strong&gt; where the model passes through by construction, not into a doc it may never open.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-measure.&lt;/strong&gt; The doc was never the mechanism. The number is — once you have earned the right to trust it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 1 usually tells you your existing docs are decorative. That is not a reason to write better docs; it is the reason to stop treating docs as controls. Step 2 occasionally tells you step 1 was decorative too.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Multi-agent fleets burn ~15x the tokens — here's the budget layer the playbooks skip</title>
      <dc:creator>Ahmad ammar</dc:creator>
      <pubDate>Mon, 29 Jun 2026 19:41:26 +0000</pubDate>
      <link>https://dev.to/ahmadammar/multi-agent-fleets-burn-15x-the-tokens-heres-the-budget-layer-the-playbooks-skip-4i9e</link>
      <guid>https://dev.to/ahmadammar/multi-agent-fleets-burn-15x-the-tokens-heres-the-budget-layer-the-playbooks-skip-4i9e</guid>
      <description>&lt;p&gt;Give ten agents a shared, metered tool — a paid search or research API where every call is real money — and you've handed ten of them the same company credit card. Each one reasons "I'll just run a quick search." You find out the total on the invoice.&lt;/p&gt;

&lt;p&gt;Anthropic's own multi-agent write-up clocks a fleet at roughly 15x the tokens of a single chat. That's the token pool. The paid external tools are the line item the orchestration playbooks skip — and it's the one that shows up in actual dollars. I run four production projects on Claude Code with deterministic budget enforcement built into the harness, and this is the pattern that keeps that line item from surprising me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix that doesn't work
&lt;/h2&gt;

&lt;p&gt;You can't solve this by asking agents to be frugal. "Be mindful of the budget" in a prompt is a wish, and an LLM will sail past "max 8 searches" the moment the task still feels unfinished. If your budget lives in the prompt, you don't have a budget — you have a hope.&lt;/p&gt;

&lt;h2&gt;
  
  
  The split most write-ups collapse
&lt;/h2&gt;

&lt;p&gt;Cost governance is two layers, and conflating them is why prompt-level "budgets" fail:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Enforcement is deterministic and lives below the model.&lt;/strong&gt; A PreToolUse hook in the harness sits in the tool-call path. When an agent tries to call a metered API, the hook checks the per-session counter and the account-wide daily budget before the call reaches the network. If either ceiling is crossed, the call is denied — the harness refuses it, and the model literally cannot proceed. The model can't argue with it, jailbreak it, or sneak "just one more search" past it. This is what actually bounds the dollars — plain middleware that touches no credential, because it works by blocking the call before it gets there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Judgment is the half a gate can't do.&lt;/strong&gt; The hook can cap spend, but it can't decide whether &lt;em&gt;this task deserves to spend at all&lt;/em&gt;. That decision is model-shaped, and it's where the agent earns its place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A novelty-gate.&lt;/strong&gt; Most tasks don't qualify to spend: CRUD, mechanical edits, known facts → zero research. The biggest budget win isn't a smaller cap — it's that the majority of work never reaches the paid tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A tier + cap per qualifying call&lt;/strong&gt; (quick-fact vs deep-dive), declared as policy that the harness then enforces mechanically — the agent proposes the tier, the counter imposes the limit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honest degradation.&lt;/strong&gt; When the paid source fails, the agent falls back to a free one and flags the reliability downgrade in its output.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the division is clean: &lt;strong&gt;the gate enforces, the agent judges.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The novel part isn't "centralize the credential" — that's least-privilege, decades old. The leverage is pairing a deterministic hook that the model can't escape with model-shaped judgment about &lt;em&gt;whether to spend at all&lt;/em&gt; — with the gate defaulting to "no."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why two ceilings, not one
&lt;/h2&gt;

&lt;p&gt;This is the detail that blew up the budget in production until I separated them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-session cap on research calls&lt;/strong&gt; (free or paid — just a count). This is the runaway catch: a single agent spinning in a loop, making 40 searches when 4 would do. Easy to detect, hard to reason about in the prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Account-wide daily budget on the PAID API only&lt;/strong&gt;. This is the actual money. Free research (web search) gets a generous ceiling — enough to do real work. Paid research (EXA, specialized APIs) gets a strict daily spend cap in dollars or calls.&lt;/p&gt;

&lt;p&gt;If you cap both the same, you either throttle good work (the paid API becomes useless when free search hits its ceiling) or leak money (free and paid burn the same quota, paid spending happens before you notice). Separate them, and the paid tool stays protected while research quality stays real.&lt;/p&gt;

&lt;h2&gt;
  
  
  The distributed-counter problem, solved
&lt;/h2&gt;

&lt;p&gt;If the budget is a single shared number across parallel agents and workflows, you're holding a classic distributed-counter race: two agents can both read "budget remaining," both decide they're clear, and both spend — blowing the cap. This isn't theory. It happens.&lt;/p&gt;

&lt;p&gt;I built the account-wide budget as a single shared file written by N parallel sessions. Access goes through an atomic mutex — an exclusive create-lock that a session holds while it reads and updates the count, then releases. A crashed holder can't wedge it shut: a lock older than a few seconds is reclaimed as stale. But a live lock is never stolen — if a waiter can't acquire it in time, it fails OPEN, refusing the call rather than corrupting the count. Losing one uncounted call is safe. Corrupting a shared budget is not.&lt;/p&gt;

&lt;p&gt;This was tested under 12 concurrent paid calls. Zero lost updates. That's not magic — it's a metered gate built right.&lt;/p&gt;

&lt;h2&gt;
  
  
  A surprise about fan-out
&lt;/h2&gt;

&lt;p&gt;Subagents spawned from a main session, and workflow-launched agents, inherit the parent session's identity. That means they all count against the same per-session ceiling for research calls. A main agent + 3 subagents + 3 workflow calls = one shared pool. That's the runaway catch working exactly as designed.&lt;/p&gt;

&lt;p&gt;But parallel SESSIONS (run in different harness processes or separate user sessions) each get their own per-session counter. Which is exactly why the account-wide daily budget — the paid-API cap — has to sit at the account level, not per-session. One session doesn't see another session's spending. The shared daily budget is the only thing that keeps total paid spend honest across parallel work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this bounds — and what it doesn't
&lt;/h2&gt;

&lt;p&gt;Be precise: this harness layer controls (a) the count of external-research calls per session and (b) total daily spend on paid APIs. It is NOT a fix for general token-quota blowups from heavy agents doing non-search work. If an agent runs 12 parallel research workflows, each doing deep reasoning, the token pool scales badly — that's a different control: don't parallelize research at all, or schedule it across sessions.&lt;/p&gt;

&lt;p&gt;This is about external tool spend and runaway calls. It's not a token limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Most "agent budget" advice optimizes the cap size. The leverage is one level up: a deterministic gate that sits below the model, makes most work never spend at all, and owns no credential because it works by refusing the call. Add a second ceiling for the paid API at the account level, not per-agent. Stop asking your fleet to be frugal. Build a harness hook that can't be escaped — and let the agent's judgment about when to spend do the rest.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
