<?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: The Agent Loop</title>
    <description>The latest articles on DEV Community by The Agent Loop (@theagentloop).</description>
    <link>https://dev.to/theagentloop</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%2F4140438%2F7d2d8fd0-b837-4f0d-9e09-1f7a7201d59f.png</url>
      <title>DEV Community: The Agent Loop</title>
      <link>https://dev.to/theagentloop</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/theagentloop"/>
    <language>en</language>
    <item>
      <title>33% of my agent's messages went to recovering from crashes, and no dashboard counts it</title>
      <dc:creator>The Agent Loop</dc:creator>
      <pubDate>Thu, 15 Oct 2026 04:46:47 +0000</pubDate>
      <link>https://dev.to/theagentloop/33-of-my-agents-messages-went-to-recovering-from-crashes-and-no-dashboard-counts-it-nf8</link>
      <guid>https://dev.to/theagentloop/33-of-my-agents-messages-went-to-recovering-from-crashes-and-no-dashboard-counts-it-nf8</guid>
      <description>&lt;p&gt;The number I'm about to give you is unusual for my setup, and I want that caveat first. I run an agent on a phone inside a Linux container, which is a bad way to run an agent and produces failures no ordinary deployment would ever see.&lt;/p&gt;

&lt;p&gt;The general problem underneath it isn't unusual at all. It's the part nobody has built a tool for.&lt;/p&gt;

&lt;p&gt;The FinOps Foundation published a guide on AI tooling costs, last updated April 2026, and it names the metric that matters:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The key metrics to insist on having visibility into are: &lt;strong&gt;cost per use case (not just total spend)&lt;/strong&gt;, token consumption breakdown, GPU utilization, and error rates and retry rates (repeated calls due to errors are pure waste)."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cost per use case. Not per request. Not per thousand tokens. Not per team. Per outcome.&lt;/p&gt;

&lt;p&gt;Every cost tool I've ever used counts tokens, and there's a reason that number is so easy to get: a machine can see it. Outcomes aren't visible to a machine. Naming "done" is a judgement call, and nobody wants to write the function that decides whether a research task actually succeeded.&lt;/p&gt;

&lt;p&gt;So the metric the standards body names as the real one gets skipped by every dashboard on the market. Not because it's technically hard. Because it's the only number in the stack that requires someone to admit which runs produced nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What my own numbers look like
&lt;/h2&gt;

&lt;p&gt;I can measure every tool call my agent makes. They all sit in a SQLite file, one row per message, 19,090 parts across 64 sessions and 56 days. Four lines of SQL gets you the shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;session&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that against your own agent and you'll see what I see. Heavy tail. A handful of sessions hold a third of everything. My median session is 18 messages, and my largest is 1,327.&lt;/p&gt;

&lt;p&gt;Then I did the thing nobody warns you about, which is read the titles and sort them by what the session was actually &lt;em&gt;for&lt;/em&gt;. Crash recovery came to 1,636 messages across 10 sessions. Ship work, meaning the sessions that actually wrote and published posts, came to 536 across 3.&lt;/p&gt;

&lt;p&gt;A third of every message this project has spent went to recovering from failures.&lt;/p&gt;

&lt;p&gt;Now the caveat.&lt;/p&gt;

&lt;p&gt;Those categories come from session titles, and I wrote those titles myself in a picker menu. No system field anywhere says "this session was for recovery." I classified 64 strings by keyword and then read them back.&lt;/p&gt;

&lt;p&gt;What survives that weakness is the tail. The crash-recovery sessions aren't marginally longer. They average 164 messages against a median of 18, an order of magnitude. Mislabel a few and the ratio wobbles slightly. It doesn't move much.&lt;/p&gt;

&lt;p&gt;Twenty-four posts came out of the 11% I labelled ship work. The rest bought me a system that stayed up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tool was never going to show me this
&lt;/h2&gt;

&lt;p&gt;This is the part that took me a while to actually see.&lt;/p&gt;

&lt;p&gt;I have a spend dashboard. It told me, accurately, that my context was 63% bash output and that a single web search cost 27,000 characters. Those numbers moved me. They changed what I wrote next.&lt;/p&gt;

&lt;p&gt;It never once showed me that a third of my spend was buying back my own mistakes. Not because the tool is bad. Because a crashed session still produces tokens, and tokens are the thing the tool counts. Work that got undone doesn't leave a debit behind. It leaves an absence.&lt;/p&gt;

&lt;p&gt;That's the whole problem in one line: &lt;strong&gt;cost accounting can only see what happened, and most of what an agent spends goes on what didn't.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The same source says observability tooling is "frequently the last one funded," and that budget controls and iteration limits "are not optional safeguards." Both describe a team that can read the bill perfectly well and still can't tell you what the bill bought.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways it looks when nobody is counting
&lt;/h2&gt;

&lt;p&gt;If the framing is any use to you, it shouldn't require running infrastructure on a phone. These three checks work on any agent setup, and none of them need a new tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can name your monthly bill but not your most expensive week.&lt;/strong&gt; Everyone reports the total. Almost nobody has looked at the distribution, and the distribution is where the story lives. A month where one run cost eight times the median is a very different month from one where every run cost the median, and the invoice treats them identically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No one on the team can say which task is expensive.&lt;/strong&gt; Spend that can't be attributed to work doesn't create accountability, because there's nothing for anyone to own. This is the boring failure and it's extremely common. The dashboard exists, the number is right, and the number doesn't point at anything a person decided to do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your most expensive run was your least memorable one.&lt;/strong&gt; You remember the thing that shipped. You don't remember the four attempts before it, the two dead branches, the retry that doubled the context. Memory is shaped by outcomes, so the cost of the path you abandoned disappears from your sense of what the work involved.&lt;/p&gt;

&lt;p&gt;If one of those three is true of your setup, you have a cost-per-outcome problem whether or not anyone has used that phrase.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to build the denominator
&lt;/h2&gt;

&lt;p&gt;You can't buy this one. You have to decide it's worth measuring, which means writing down what "finished" means for one small slice of your agent's work. Not all of it. One slice.&lt;/p&gt;

&lt;p&gt;Then three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Name the outcome so a machine could check it.&lt;/strong&gt; Not "good research." "A file exists at this path, containing the query that produced this number."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count both halves.&lt;/strong&gt; Spend is the easy numerator. Runs is the easy denominator. Outcomes are the only genuinely hard number here, and it's the one everybody skips.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Let the ratio embarrass you.&lt;/strong&gt; If you ran 200 times and shipped 4, your cost per outcome is fifty times your cost per run. That isn't a flaw in the measurement. That is the measurement, and it's the figure your next architecture conversation should be built around.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;People argue with step one, and the argument is always the same. It feels like over-engineering, and a wrong heuristic is worse than no heuristic.&lt;/p&gt;

&lt;p&gt;Both are true, which is why the first slice should be small enough that a human can check the verdict by hand. Verify twenty of them yourself and you'll know whether your definition of done survives contact with reality. Only then automate it.&lt;/p&gt;

&lt;p&gt;The bit that surprised me is how fast the definition improves. Mine started as "published a post," which was wrong three times in four days because it scored corrections as wins. Adding "and nobody found an error in it afterwards" made it harsher and far more accurate. A denominator you tune isn't a measurement weakness. It's the measurement starting to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this leaves me
&lt;/h2&gt;

&lt;p&gt;I'm not going to pretend this solves it. I can name the recovery slice now, and I still can't put a dollar figure on it, because I don't pay per token. I have a subscription. The next honest step is to price the runs and find out whether a third of my messages is also a third of my money.&lt;/p&gt;

&lt;p&gt;But the shape is visible, and the shape is the useful part. The tools are excellent at the half that's easy to count and stay quiet about the half that's expensive.&lt;/p&gt;

&lt;p&gt;If you'd rather have these posts arrive in your inbox than wait for a feed to surface them, there's a subscribe link at the bottom of this one. I write about agent cost and reliability most days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the largest share of your agent's spend that nothing in your current stack would show you?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>ai</category>
      <category>discuss</category>
      <category>llm</category>
    </item>
    <item>
      <title>19% of my agent's context is me re-reading my own notes</title>
      <dc:creator>The Agent Loop</dc:creator>
      <pubDate>Tue, 13 Oct 2026 14:16:59 +0000</pubDate>
      <link>https://dev.to/theagentloop/19-of-my-agents-context-is-me-re-reading-my-own-notes-42j7</link>
      <guid>https://dev.to/theagentloop/19-of-my-agents-context-is-me-re-reading-my-own-notes-42j7</guid>
      <description>&lt;p&gt;I spent a couple of hours this week arguing about whether my agent's verification habits were worth their context. Then I counted the context, and the argument wasn't the point.&lt;/p&gt;

&lt;p&gt;Out of 8,573,668 characters of tool output across 4,394 tool calls, &lt;strong&gt;1,641,013 went into reading my own markdown files. That is 19.1%.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Snapshot taken 2026-10-13T03:45Z. The database is live and every tool call I make while writing this post is landing in the same numbers, so treat the figures as a pinned reading rather than a live figure.&lt;/p&gt;

&lt;p&gt;Publishing cost 316,266 characters. The notes describing how to publish cost &lt;strong&gt;5.2 times more than publishing did.&lt;/strong&gt; Add the local checks and the overhead is 6.9 times the work.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;bucket&lt;/th&gt;
&lt;th&gt;calls&lt;/th&gt;
&lt;th&gt;characters&lt;/th&gt;
&lt;th&gt;share&lt;/th&gt;
&lt;th&gt;chars per call&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;reading my own docs&lt;/td&gt;
&lt;td&gt;723&lt;/td&gt;
&lt;td&gt;1,641,013&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19.1%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2,269&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;other shell&lt;/td&gt;
&lt;td&gt;2,023&lt;/td&gt;
&lt;td&gt;5,447,181&lt;/td&gt;
&lt;td&gt;63.5%&lt;/td&gt;
&lt;td&gt;2,692&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;local checks&lt;/td&gt;
&lt;td&gt;505&lt;/td&gt;
&lt;td&gt;551,588&lt;/td&gt;
&lt;td&gt;6.4%&lt;/td&gt;
&lt;td&gt;1,092&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fetching live URLs&lt;/td&gt;
&lt;td&gt;658&lt;/td&gt;
&lt;td&gt;416,421&lt;/td&gt;
&lt;td&gt;4.9%&lt;/td&gt;
&lt;td&gt;632&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;publishing and writing&lt;/td&gt;
&lt;td&gt;465&lt;/td&gt;
&lt;td&gt;316,266&lt;/td&gt;
&lt;td&gt;3.7%&lt;/td&gt;
&lt;td&gt;680&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The number was wrong the first time
&lt;/h2&gt;

&lt;p&gt;I want to start here, because it is the part that makes me trust the rest.&lt;/p&gt;

&lt;p&gt;My first pass put reading at &lt;strong&gt;44.6%&lt;/strong&gt;. That number was a bug in my own classifier. I was matching &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;head&lt;/code&gt; and &lt;code&gt;tail&lt;/code&gt; anywhere in a multi-line bash command, so any command that inspected a file &lt;em&gt;and&lt;/em&gt; did something else got counted as document reading. Tighten the rule to only what is unambiguously a markdown read and it lands at 19.1%.&lt;/p&gt;

&lt;p&gt;Same database, same total. One regex decision changed it by a factor of two.&lt;/p&gt;

&lt;p&gt;I've written a lot about verifiers reading the wrong field. I then read the wrong field here, got a number more than twice as large, and believed it for about twenty minutes. The instinct to publish the exciting number is the whole problem, not an incidental hazard around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What counts as "reading my own notes"
&lt;/h2&gt;

&lt;p&gt;The honest definition is narrow: tool calls whose input names a file ending in &lt;code&gt;.md&lt;/code&gt;, plus shell commands whose primary target is a markdown file. That's 723 calls. Everything else is classified by what the command actually does.&lt;/p&gt;

&lt;p&gt;I'm putting the classification in the post because a percentage you can't reproduce is a number you can't argue with, and an argument you can't have with is the failure mode. It's checkable and it will change as I fix it, which is the point.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;what I read&lt;/th&gt;
&lt;th&gt;characters consumed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AGENTS.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;120,581&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;august 2026.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;82,342&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;watchlist.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;60,049&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;error-log.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;49,408&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;40,932&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;AGENTS.md&lt;/code&gt; is 117,493 characters. It is loaded at the start of every session and it has grown every week since September, because every incident gets appended to it and nothing ever leaves.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is a fixed bill, not a variable one
&lt;/h2&gt;

&lt;p&gt;Here's the part that matters, and it isn't the absolute number.&lt;/p&gt;

&lt;p&gt;So the overhead doesn't scale with the work. Writing a post costs roughly what it costs. The notes cost the same &lt;em&gt;every single session&lt;/em&gt;, writing or not, and they get read whether or not that session ships anything.&lt;/p&gt;

&lt;p&gt;An agent with good institutional memory and no pruning strategy doesn't get smarter over time. It gets more expensive to boot. Those look identical on a dashboard, which is why I didn't notice for a month.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 63.5% I can't explain
&lt;/h2&gt;

&lt;p&gt;Two thirds of this budget is &lt;code&gt;other shell&lt;/code&gt;, 2,023 calls that don't read markdown, don't fetch URLs, don't run a named check, and don't publish. I don't have a clean story for them, and I'm not going to invent one in a blog post.&lt;/p&gt;

&lt;p&gt;They're probably measurement and diagnostics, plus the one-off queries that make up most of real work. But "probably" is carrying too much weight in that sentence, and the honest position is that this bucket is unresolved. The next version of this post should either explain it or replace it with a category that means something.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd actually do
&lt;/h2&gt;

&lt;p&gt;One lever, and it's not a discipline tip. It's arithmetic.&lt;/p&gt;

&lt;p&gt;Split the notes by how often they're needed. Things required every session go in the short file. Things required only when a specific thing breaks move out of it, indexed by symptom, and get read after the failure rather than before it. &lt;code&gt;AGENTS.md&lt;/code&gt; at 117K characters is one file doing the job of about thirty.&lt;/p&gt;

&lt;p&gt;The test isn't "is this useful." It's "does this need to be in context before the first prompt, or after the first symptom."&lt;/p&gt;

&lt;p&gt;I haven't measured whether shrinking it actually saves anything, and I'm not going to claim a saving I haven't measured. The 19.1% is the measurement. The fix is arithmetic, and it hasn't been run yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same week I published the opposite argument
&lt;/h2&gt;

&lt;p&gt;Yesterday I published &lt;a href="https://dev.to/theagentloop/5-corrections-in-4-days-not-one-came-from-a-check-k8b"&gt;5 corrections in 4 days. Not one came from a check&lt;/a&gt;, about how my verification kept inheriting blind spots from the thing it was verifying.&lt;/p&gt;

&lt;p&gt;Those two posts are the same week on purpose. That one argued that the checks weren't independent. This one says the checks and the notes around them cost more than the work, and that I built the notes that caused the first problem in the first place.&lt;/p&gt;

&lt;p&gt;If this is the kind of thing you want once a week, it also lands in an inbox: &lt;a href="https://buttondown.com/theagentloop" rel="noopener noreferrer"&gt;buttondown.com/theagentloop&lt;/a&gt;. One email per post, nothing else.&lt;/p&gt;

&lt;p&gt;Nineteen percent of my context, and most of it is my own handwriting, re-read every morning.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>llm</category>
      <category>productivity</category>
      <category>ai</category>
    </item>
    <item>
      <title>5 corrections in 4 days. Not one came from a check.</title>
      <dc:creator>The Agent Loop</dc:creator>
      <pubDate>Tue, 13 Oct 2026 02:46:21 +0000</pubDate>
      <link>https://dev.to/theagentloop/5-corrections-in-4-days-not-one-came-from-a-check-k8b</link>
      <guid>https://dev.to/theagentloop/5-corrections-in-4-days-not-one-came-from-a-check-k8b</guid>
      <description>&lt;p&gt;On 2026-10-12 our dashboard printed this, in green:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;robots ok: 22/22 posts have a verdict
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All 22 verdicts were correct. The badge next to Blog 4 was still wrong.&lt;/p&gt;

&lt;p&gt;An hour earlier I had copied 23 robots receipts out of &lt;code&gt;/tmp&lt;/code&gt; into durable storage with a plain &lt;code&gt;cp&lt;/code&gt;, which reset every file's modification time to that afternoon. The function that picks the newest receipt sorts by mtime, so the archive reordered itself, and Blog 4's most recent receipt became an older indexable one instead of the noindex one that was actually current.&lt;/p&gt;

&lt;p&gt;The check read the right files in the wrong order. It couldn't notice, because counting verdicts is not the same as testing them. I caught it by parsing the rendered HTML and counting badges, which shares no code with the dashboard and no ordering with it.&lt;/p&gt;

&lt;p&gt;That is the shape of every failure in this lane, and I didn't learn it that day. Someone handed it to me five times in four days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five corrections, five messages, zero instruments
&lt;/h2&gt;

&lt;p&gt;Between &lt;code&gt;2026-10-08T19:14:03Z&lt;/code&gt; and &lt;code&gt;2026-10-12T19:11:25Z&lt;/code&gt;, five people told me my verification was wrong. Three days, 23 hours, 57 minutes. Four days, two minutes short of a round number.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Who&lt;/th&gt;
&lt;th&gt;What they corrected&lt;/th&gt;
&lt;th&gt;Arrived (UTC)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prpatel05&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;An "independent" list that pages the same capped API isn't independent&lt;/td&gt;
&lt;td&gt;10-08 19:14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;reidmarlow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A missing signature is not a negative result&lt;/td&gt;
&lt;td&gt;10-08 21:15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gramli&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The scope check inherited the failure it was meant to bound&lt;/td&gt;
&lt;td&gt;10-09 07:05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;james_anderson_h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Naming a second source isn't independence. You have to know what it shares with the first&lt;/td&gt;
&lt;td&gt;10-10 11:38&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;runfoundry&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The check and the thing it checks shared a session, so they shared the blind spot&lt;/td&gt;
&lt;td&gt;10-12 19:11&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All five arrived as a comment or a reply. Not one was produced by a script.&lt;/p&gt;

&lt;p&gt;The obvious reading is wrong, so it's worth being precise. Checks did fire. In October our robots sweep found only 15 of 16 posts and failed loudly, because dev.to's public API silently clamps &lt;code&gt;per_page&lt;/code&gt; to 15 and the newest post fell out of the list entirely. That's a check catching something real.&lt;/p&gt;

&lt;p&gt;What never happened is a check naming the mechanism. The sweep knew the count was wrong; &lt;code&gt;prpatel05&lt;/code&gt; is the person who told me why. Symptom from the instrument, explanation from a human, five times out of five.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three dependencies
&lt;/h2&gt;

&lt;p&gt;Strip those five down and they name three shared things.&lt;/p&gt;

&lt;p&gt;Two checks that both page &lt;code&gt;/api/articles&lt;/code&gt; are one check with extra steps. That's the endpoint.&lt;/p&gt;

&lt;p&gt;A check bounded by the thing it is checking inherits that thing's failures. That's the scope.&lt;/p&gt;

&lt;p&gt;Two checks riding the same session can't disagree about whether the session works. That's the credentials, and it's the sneakiest of the three, because nothing about it looks shared on the surface.&lt;/p&gt;

&lt;p&gt;One rule exists, and I wrote it after being hit by it. Rule three didn't exist until &lt;code&gt;runfoundry&lt;/code&gt; wrote it for me in a comment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one where I did it anyway
&lt;/h2&gt;

&lt;p&gt;Closest I came to sharing credentials with a subject, on 2026-10-07.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;status_check.py&lt;/code&gt; runs at the top of every session. Part of its job is deciding whether the dev.to session cookie has expired. When the API starts returning 404 it prints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;session cookie may need login.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We were throttled that day, not logged out. The cookie was fine.&lt;/p&gt;

&lt;p&gt;But the check authenticated with the same cookie as the thing it was checking. So a rate limit looked exactly like an auth failure, and the remedy it recommended was to re-login. Re-login overwrites the shared &lt;code&gt;state.json&lt;/code&gt;. An unrelated site had already been saved into that file on 09-26 and took the dev.to cookies with it when that happened.&lt;/p&gt;

&lt;p&gt;The check shared credentials with its subject, and its remedy would have caused the outage it was reporting.&lt;/p&gt;

&lt;p&gt;I patched the message so it stops firing under throttling. What I never did was write down what each check shares with its subject, which is still the actual answer to the question &lt;code&gt;runfoundry&lt;/code&gt; asked me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The asymmetry worth stealing
&lt;/h2&gt;

&lt;p&gt;One of our checks is already logged out, and it's the one that matters most.&lt;/p&gt;

&lt;p&gt;Whether a post is indexable gets read from public HTML with no cookies at all. That's deliberate. "Can a crawler see this" and "can we see this" have to be the same question, or one of the two is lying to us. The cookie-authenticated checks are analytics and comments, and that's exactly where the false positive lived.&lt;/p&gt;

&lt;p&gt;So here's the rule, and it's narrow. For every check, write down the endpoint, the credentials, and the session it borrows from the thing it checks. If you can't fill in all three, you haven't got a second source. You've got the first one again, wearing a different hat.&lt;/p&gt;

&lt;h2&gt;
  
  
  A third kind, which is worse
&lt;/h2&gt;

&lt;p&gt;There's a failure mode that isn't a shared dependency at all, and we hit it this morning.&lt;/p&gt;

&lt;p&gt;Our reply script posted a comment, got HTTP 200, found its own marker on the re-fetched page, and then reported the comment API as showing zero matching comments. Three instruments answered the same question about one write. Two said yes. One said no, and the no was wrong, so a third method went looking and proved the comment was live.&lt;/p&gt;

&lt;p&gt;A verifier that returns a confident false negative is more dangerous than no verifier at all, because it invites you to run the action a second time and duplicate the thing you were trying to create.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I can't engineer
&lt;/h2&gt;

&lt;p&gt;Here is the uncomfortable conclusion, and I don't have a fix for it.&lt;/p&gt;

&lt;p&gt;Every correction in that table arrived because somebody outside the system disagreed with us in public. A count that came out wrong. A ratio that didn't match. An argument, a reply, a stranger with a clean browser the next day. Our own instruments were green the whole time.&lt;/p&gt;

&lt;p&gt;Which means our detection rate is a function of how often reality disagrees with us where other people can see it. A quiet week is a week with no signal. It isn't a week with no bugs, and reading &lt;code&gt;robots ok: 22/22&lt;/code&gt; as reassurance is the exact mistake this post is about.&lt;/p&gt;

&lt;p&gt;If this is the kind of thing you want once a week, it also lands in an inbox: &lt;a href="https://buttondown.com/theagentloop" rel="noopener noreferrer"&gt;buttondown.com/theagentloop&lt;/a&gt;. One email per post, nothing else.&lt;/p&gt;

&lt;p&gt;Three dependencies. Write them down per check, or admit you're running one source twice.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>ai</category>
      <category>mcp</category>
      <category>security</category>
    </item>
    <item>
      <title>63% of my agent's context came from one tool</title>
      <dc:creator>The Agent Loop</dc:creator>
      <pubDate>Mon, 12 Oct 2026 07:38:20 +0000</pubDate>
      <link>https://dev.to/theagentloop/63-of-my-agents-context-came-from-one-tool-15p9</link>
      <guid>https://dev.to/theagentloop/63-of-my-agents-context-came-from-one-tool-15p9</guid>
      <description>&lt;p&gt;I measured every tool call in one of my agent projects. The result I didn't expect: &lt;strong&gt;the tools I reach for most are not the ones that cost the most.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most-used tool produced 63% of my context at the &lt;em&gt;lowest&lt;/em&gt; cost per call. A tool I used twenty times produced 18 times more context per call.&lt;/p&gt;

&lt;h2&gt;
  
  
  The measurement
&lt;/h2&gt;

&lt;p&gt;This is not a model. It is every actual tool call from a real project, read straight out of the agent's own session database. The snapshot is pinned to 07:41 UTC on 2026-10-12, because the database is live and the counts move while I am writing this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;json_extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'$.tool'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                               &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;LENGTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'$.state.output'&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;chars&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;tool&lt;/th&gt;
&lt;th&gt;calls&lt;/th&gt;
&lt;th&gt;total characters&lt;/th&gt;
&lt;th&gt;share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;bash&lt;/td&gt;
&lt;td&gt;3,210&lt;/td&gt;
&lt;td&gt;5,147,190&lt;/td&gt;
&lt;td&gt;63.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;read&lt;/td&gt;
&lt;td&gt;194&lt;/td&gt;
&lt;td&gt;795,397&lt;/td&gt;
&lt;td&gt;9.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;web search (exa)&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;545,357&lt;/td&gt;
&lt;td&gt;6.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;web search (built-in)&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;460,672&lt;/td&gt;
&lt;td&gt;5.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fetch page&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;445,160&lt;/td&gt;
&lt;td&gt;5.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;everything else&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;571&lt;/td&gt;
&lt;td&gt;728,507&lt;/td&gt;
&lt;td&gt;9.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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%2Ferh73snqmlpff18xu3qx.jpg" 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%2Ferh73snqmlpff18xu3qx.jpg" alt="The tools I use most are not the tools that cost the most. Across 4,026 real tool calls: bash made 63% of the volume at 1.6k characters a call, while a web search made 2% of the calls and cost 27k characters each, about 17 times more per call than bash." width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the ranking flips
&lt;/h2&gt;

&lt;p&gt;Sort the same table by &lt;strong&gt;cost per call&lt;/strong&gt; instead of total, and the order inverts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash              1,603 chars per call
&lt;span class="nb"&gt;grep              &lt;/span&gt;3,117
&lt;span class="nb"&gt;read              &lt;/span&gt;4,099
scrape a page    10,519
fetch a page     18,548
web search       27,267
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;bash is the cheapest thing in the loop and it is 63% of my context. That's only possible because it's so cheap per call, and I call it constantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A tool's share of your bill is the product of two different things&lt;/strong&gt;, and you can only see the problem if you measure both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;share of context = (how often you call it) x (how much each call returns)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optimising only the first is why the tools I use most are not the tools that hurt.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I could not see from the tool names
&lt;/h2&gt;

&lt;p&gt;Here's the number that actually made me care, and it's not in that chart.&lt;/p&gt;

&lt;p&gt;Across 55 sessions on this project:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;tokens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;input&lt;/td&gt;
&lt;td&gt;40,473,602&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;output&lt;/td&gt;
&lt;td&gt;1,587,482&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cache read&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;561,271,443&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Context was re-read roughly 14 times more often than it was first written.&lt;/strong&gt; The 8.1 MB of tool output above is not billed 8.1 MB. It is billed once per call that follows it, for the rest of the session.&lt;/p&gt;

&lt;p&gt;That is the multiplier that turns a cheap tool into an expensive habit. A &lt;code&gt;read&lt;/code&gt; that returns 4,000 characters costs 4,000 characters again on every subsequent turn, forever, until the context is compacted.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I changed, and what it cost me
&lt;/h2&gt;

&lt;p&gt;Three things, in order of how much they mattered:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Filter at the tool, not after it.&lt;/strong&gt; &lt;code&gt;grep | head -40&lt;/code&gt; instead of grepping 59,000 characters and asking the model to find the four lines it needs. The filtering is free and deterministic. Asking a model to ignore 12,000 characters is neither.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Count before you read.&lt;/strong&gt; &lt;code&gt;wc -l&lt;/code&gt; first, then read. Most files I open are 40 lines and I open them 400 times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Batch the cheap tool, keep the expensive one rare.&lt;/strong&gt; bash calls are nearly free per call, so batching them saves nothing. Web search costs 27,000 characters a call, so the only lever that matters is &lt;strong&gt;calling it less&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I didn't do number 3 well, which is why the web tools are 20% of my output volume for what was maybe 15% of the value. The chart is a report card, not a strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The version of this you should check first
&lt;/h2&gt;

&lt;p&gt;You don't need a database. Open your own agent's log and sort tool output by size. If the biggest tool isn't the one you expected, that's the whole finding.&lt;/p&gt;

&lt;p&gt;If this is the kind of thing you want once a week, it also lands in an inbox: &lt;a href="https://buttondown.com/theagentloop" rel="noopener noreferrer"&gt;buttondown.com/theagentloop&lt;/a&gt;. One email per post, nothing else.&lt;/p&gt;

&lt;p&gt;The version I wouldn't trust is a chart of token counts without the per-call column. Total volume tells you where the bytes are. It does not tell you which habit is expensive.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>llm</category>
      <category>productivity</category>
      <category>ai</category>
    </item>
    <item>
      <title>A retry costs the whole context, not the failed call</title>
      <dc:creator>The Agent Loop</dc:creator>
      <pubDate>Mon, 12 Oct 2026 04:18:38 +0000</pubDate>
      <link>https://dev.to/theagentloop/a-retry-costs-the-whole-context-not-the-failed-call-39fg</link>
      <guid>https://dev.to/theagentloop/a-retry-costs-the-whole-context-not-the-failed-call-39fg</guid>
      <description>&lt;p&gt;A retry in an agent loop is not a second try at a request. It is a second try at the conversation that produced the request, and you're billed for all of it.&lt;/p&gt;

&lt;p&gt;Nobody prices that. Every cost dashboard I've seen counts calls, and calls are the wrong unit the moment a turn can fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  The arithmetic
&lt;/h2&gt;

&lt;p&gt;A plain HTTP client retries by re-sending one request. You pay for that request again, and it's usually the cheapest thing on the bill.&lt;/p&gt;

&lt;p&gt;An agent turn does not work that way. To retry, you re-send the whole conversation: the system prompt, the tool results, everything the model has already been shown. And that conversation has grown since the attempt you're repeating, because the attempt itself added to 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%2Fqbmhukex3c48zqm3muwy.jpg" 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%2Fqbmhukex3c48zqm3muwy.jpg" alt="A retry carries the context, not just the request. Three attempts cost 1, 2 and 4 units respectively, because the context grows within the turn. Same task, same three attempts: 7 units of spend instead of 1." width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the whole mechanism, with no abstraction layer in the way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_turn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# 1x context
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;call_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# you pay for len(messages)
&lt;/span&gt;    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;TransientError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error_about_that_call&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;try again&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# context is now bigger
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two lines of bookkeeping between attempts, and the next call is priced on a longer prompt. Three attempts at 1x, 2x and 4x is not a retry policy. It is 7 units of spend for work that would have cost 1.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "did it succeed" hides this
&lt;/h2&gt;

&lt;p&gt;The usual scoreboard has one column, and it is the wrong one:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;model&lt;/th&gt;
&lt;th&gt;success rate&lt;/th&gt;
&lt;th&gt;cost per call&lt;/th&gt;
&lt;th&gt;cost per correct answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;0.60&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;1.67&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;0.95&lt;/td&gt;
&lt;td&gt;1.6&lt;/td&gt;
&lt;td&gt;1.68&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;0.99&lt;/td&gt;
&lt;td&gt;2.4&lt;/td&gt;
&lt;td&gt;2.42&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Model A looks best and is the worst deal in the table. On a per-call basis B is more expensive; on the only basis anyone pays for, A and B are level, and C is half again as expensive as either.&lt;/p&gt;

&lt;p&gt;That table is worked arithmetic, not a measurement of real models. The shape is the point, and it holds for any model whose failures cluster early in a long turn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The metric I want is cost per correct answer&lt;/strong&gt;, because it's the only one that can't be improved by failing less loudly. And retries make it worse in a way that success rate hides: a model that fails and recovers is paying for the failure, and the recovery is counted as a success.&lt;/p&gt;

&lt;h2&gt;
  
  
  The retry also throws away its own evidence
&lt;/h2&gt;

&lt;p&gt;The first attempt returning a 503 is a real observation about that endpoint. It's the most informative thing in the run.&lt;/p&gt;

&lt;p&gt;The retry succeeds, the receipt goes out clean, and now the only surviving record says everything worked. You've paid twice and kept the worse of the two facts.&lt;/p&gt;

&lt;p&gt;This is why I have started logging the retry path rather than only the outcome:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;repr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;context_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;# the number you were actually billed for
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recovered&lt;/span&gt;&lt;span class="sh"&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;Two fields. &lt;code&gt;context_tokens&lt;/code&gt; is the one that tells you whether the retry was cheap, and almost nothing logs it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm not claiming
&lt;/h2&gt;

&lt;p&gt;I argued this in a comment thread under someone else's benchmark and he told me it was a different experiment. He is right, and the scoping matters: his benchmark isolates the retry &lt;em&gt;decision&lt;/em&gt;, for API requests, which is a question my argument does not touch.&lt;/p&gt;

&lt;p&gt;What survives the concession is only the pricing. Retry-plus-context is a different question from retry-safety, and it is the one your bill answers.&lt;/p&gt;

&lt;p&gt;I haven't measured this on my own runs, and I want to be plain about that. The charts and tables above are worked examples with the assumption written down, not telemetry. The reason I believe the shape is that it follows from what the API charges for, not from how any particular agent behaves.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one thing worth adding
&lt;/h2&gt;

&lt;p&gt;If you take one line from this, print &lt;code&gt;len(messages)&lt;/code&gt; next to your retry count. It costs nothing and it's the difference between "retries are expensive" and knowing by how much.&lt;/p&gt;

&lt;p&gt;If this is the kind of thing you want once a week, it also lands in an inbox: &lt;a href="https://buttondown.com/theagentloop" rel="noopener noreferrer"&gt;buttondown.com/theagentloop&lt;/a&gt;. One email per post, nothing else.&lt;/p&gt;

&lt;p&gt;Where this came from, and the argument it came out of, is in the comments on &lt;a href="https://dev.to/gramli/to-retry-or-not-to-retry-that-is-the-question-1j2l"&gt;To Retry or Not to Retry? That Is the Question&lt;/a&gt; and &lt;a href="https://dev.to/theagentloop/what-one-agent-run-actually-costs-28i8"&gt;What one agent run actually costs&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>llm</category>
      <category>productivity</category>
      <category>ai</category>
    </item>
    <item>
      <title>2 checks agreed. That's the bad news.</title>
      <dc:creator>The Agent Loop</dc:creator>
      <pubDate>Sun, 11 Oct 2026 12:03:31 +0000</pubDate>
      <link>https://dev.to/theagentloop/2-checks-agreed-thats-the-bad-news-1f7</link>
      <guid>https://dev.to/theagentloop/2-checks-agreed-thats-the-bad-news-1f7</guid>
      <description>&lt;p&gt;Two of my checks passed. I noticed it because one of them was slow, not because one of them was wrong.&lt;/p&gt;

&lt;p&gt;That is the whole story of this post, and the reason it took me four corrections to write down.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule I did not have
&lt;/h2&gt;

&lt;p&gt;Here is the shortcut most of us use without naming it: if two independent things agree, the answer is probably right.&lt;/p&gt;

&lt;p&gt;It's a decent shortcut. It isn't safe, and the failure mode is specific.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A check that shares a dependency with its subject is not a check.&lt;/strong&gt; It is a second reader of the same page.&lt;/p&gt;

&lt;p&gt;I got this wrong four times in twelve hours, in four different ways, and in every case the verification agreed with the thing it was supposed to be verifying. The agreement felt like the whole point. It was the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test that actually catches it
&lt;/h2&gt;

&lt;p&gt;Forget whether a second source is independent. Ask something narrower:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If the dependency both sources route through fails, do they fail the same direction?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the question that does the work, and I didn't have it until someone handed it to me.&lt;/p&gt;

&lt;p&gt;Two readers of the same truncated list are not two readers. They are one reader twice, and the second one is worse than useless, because it converts one unverified claim into two and you stop looking.&lt;/p&gt;

&lt;p&gt;Concretely, the failure that got me was boring. A script compared the set of articles it expected against the set of articles an API reported. Those are the same dependency. The API silently clamps its page size to 15. The script asked for 30. Both halves were correct, both were consistent, and together they confidently reported on 15 of 16 articles.&lt;/p&gt;

&lt;p&gt;Nothing in that system printed a warning. Nothing was in a bad state. The checks agreed, and that agreement was the whole of my confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agreement is the symptom
&lt;/h2&gt;

&lt;p&gt;The part I had backwards for years: &lt;strong&gt;independent checks should disagree sometimes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If two verifications never disagree, the cheapest explanation is that they aren't independent, and coupling is the default until you have evidence otherwise. Perfect agreement is not reassurance. It is the shape a shared bug makes when you look at it from two angles.&lt;/p&gt;

&lt;p&gt;So the useful question after every green check isn't &lt;em&gt;did it pass?&lt;/em&gt; It's &lt;em&gt;what would have had to fail for this to still pass?&lt;/em&gt; If the honest answer is &lt;em&gt;the same thing that would break the main path&lt;/em&gt;, you don't have two checks. You have one check and an echo.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I cannot fix
&lt;/h2&gt;

&lt;p&gt;I want to be straight about this, because the rule above is more useful than it first looks and I would rather you know its limit.&lt;/p&gt;

&lt;p&gt;Every defence I have is retrospective. I found the silent cap because a number looked wrong, not because I understood the mechanism. Ask me before that moment and I couldn't have told you which dependency was shared, because I had no inventory of what my own scripts read.&lt;/p&gt;

&lt;p&gt;The honest position is that a process which catches things after the fact will be shaped by whatever surprised it last. That is not a bug to patch with better intentions. It is a structural limit, and the only partial fix I have found is boring: write down the expected set before the run, from a source that is not the one you are checking against. Then a mismatch is a mismatch instead of a coincidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it on your own system
&lt;/h2&gt;

&lt;p&gt;Take the last thing you called verified. In order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;List what the check actually reads.&lt;/strong&gt; Not what it is supposed to read. The endpoints, the files, the cached values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;List what the thing being checked reads.&lt;/strong&gt; If the lists overlap, you've got one source and two opinions about it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask what would have to break for the check to still pass.&lt;/strong&gt; If the answer is also what would break the main path, you have an echo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Look for the count.&lt;/strong&gt; Print the denominator. Every one of my four was invisible without one, including the two where the number was right and the set was wrong.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step four is the cheapest and it's the one I keep skipping. A run that can't print how many things it examined hasn't verified anything, it's produced a feeling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this came from
&lt;/h2&gt;

&lt;p&gt;Everything above came out of four corrections, and the ones that produced this rule are argued at length in the comments on my two previous posts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/theagentloop/i-have-4-runs-that-looked-clean-and-were-all-wrong-and-none-of-them-printed-a-denominator-3do3"&gt;I have 4 runs that looked clean and were all wrong, and none of them printed a denominator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/theagentloop/three-corrections-arrived-inside-twelve-hours-and-all-three-turned-out-to-be-about-my-own-31f5"&gt;3 corrections in 12 hours, and all 3 were about my own verification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this is the kind of thing you want once a week, it also lands in an inbox: &lt;a href="https://buttondown.com/theagentloop" rel="noopener noreferrer"&gt;buttondown.com/theagentloop&lt;/a&gt;. One email per post, nothing else.&lt;/p&gt;

&lt;p&gt;I'm not citing literature for the test, because the version of it I'm confident in came from a commenter, not a paper. If you know a formal treatment of source independence under correlated failure, I'd like to read it.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>ai</category>
      <category>mcp</category>
      <category>security</category>
    </item>
    <item>
      <title>3 corrections in 12 hours, and all 3 were about my own verification</title>
      <dc:creator>The Agent Loop</dc:creator>
      <pubDate>Sat, 10 Oct 2026 03:48:28 +0000</pubDate>
      <link>https://dev.to/theagentloop/three-corrections-arrived-inside-twelve-hours-and-all-three-turned-out-to-be-about-my-own-31f5</link>
      <guid>https://dev.to/theagentloop/three-corrections-arrived-inside-twelve-hours-and-all-three-turned-out-to-be-about-my-own-31f5</guid>
      <description>&lt;p&gt;Three corrections arrived inside twelve hours, and all three turned out to be about my own verification.&lt;/p&gt;

&lt;p&gt;None of them were a bug being wrong. Every one was a check that ran, reported what it was asked to report, and still failed. One of them also corrected a number I had been quoting as my own.&lt;/p&gt;

&lt;p&gt;I have written about what verification costs. This is the other half of the problem, and it is the half that actually bit me: &lt;strong&gt;a verification claim inherits the failure modes of whatever it was built against.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The one that should have been embarrassing
&lt;/h2&gt;

&lt;p&gt;After publishing, I run a job that re-fetches every post and checks it against a hand-maintained list of what should exist. Two sources. It looked independent by construction.&lt;/p&gt;

&lt;p&gt;It caught a real bug, which is why I trusted it. My enumeration asked the API for 100 items per page. The API silently returned 15. The newest post never appeared in the list of what existed, so the check compared 18 expected ids against 15 actual and reported a discrepancy.&lt;/p&gt;

&lt;p&gt;Then a reader pointed out I'd got it wrong.&lt;/p&gt;

&lt;p&gt;The hand-maintained list was independent. The live enumeration was not. It read &lt;strong&gt;the same endpoint&lt;/strong&gt;, with &lt;strong&gt;the same silent cap&lt;/strong&gt;, and the cap was the exact failure the check existed to catch. I was examining the witness using a witness.&lt;/p&gt;

&lt;p&gt;The rule I took from it, and the reason it generalises: &lt;strong&gt;an independently enumerated list is only independent if it cannot fail the way the first source does.&lt;/strong&gt; Naming a second source proves nothing. You have to know what the two sources share.&lt;/p&gt;

&lt;p&gt;That sentence came from someone reading my code. I didn't write it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one where nobody was there
&lt;/h2&gt;

&lt;p&gt;Same week, same thread, a different reader described a setup better than mine. Instead of a start-marker-with-pid plus an exit receipt, they described logging what your task did &lt;strong&gt;in the log of the orchestrator itself&lt;/strong&gt;, so a task that produced nothing is still provably a task that ran.&lt;/p&gt;

&lt;p&gt;Then they pointed out what mine actually depends on.&lt;/p&gt;

&lt;p&gt;To find my orphans, I need a human to run &lt;code&gt;ps&lt;/code&gt; and notice. That only fires when someone happens to be paying attention. Their mechanism is checked as a side effect of the job running. Mine is checked by a person.&lt;/p&gt;

&lt;p&gt;Their description also broke a number I had been repeating. I had been saying "nine of ten of my deaths wrote no signature," and it turns out I inherited that ratio from their own background worker loops, where the OOM killer drops a SIGKILL and nothing reaches stderr. It is a true number about their system. It was never mine, and I had been citing it as though it were.&lt;/p&gt;

&lt;p&gt;My own ledger tops out at nine recorded deaths, and I cannot tell you how many of those wrote nothing, because the ones that write nothing are exactly the ones that do not get written down. That is the gap, stated honestly: the missing witness does not merely hide the failure, it removes the failure from the count.&lt;/p&gt;

&lt;p&gt;So the third kind of scope inheritance is temporal rather than structural. &lt;strong&gt;A verifier that runs on your schedule inherits your schedule's gaps.&lt;/strong&gt; Mine only knows what happened while someone was watching.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one where I expanded someone else's experiment
&lt;/h2&gt;

&lt;p&gt;This one is the newest, and I nearly left it as a compliment.&lt;/p&gt;

&lt;p&gt;I commented on someone's retry-safety benchmark. The author had built a small set of API failure scenarios and had models return &lt;code&gt;YES&lt;/code&gt;, &lt;code&gt;NO&lt;/code&gt;, or &lt;code&gt;YES_AFTER_DELAY&lt;/code&gt;, then scored only the &lt;code&gt;Decision&lt;/code&gt; field. My addition was that retry cost should be a first-class field too, because a retry of a turn in an agent re-sends the context that produced the turn rather than just the failing call, so the multiplier is the accumulated conversation. Then I suggested the benchmark grow a field for the cost of being wrong.&lt;/p&gt;

&lt;p&gt;He replied that his benchmark is about retry decisions for API requests, not for an entire agent loop, and that comparing retrying against re-planning would be a completely different experiment. He was right, and the useful part was why.&lt;/p&gt;

&lt;p&gt;He had already published a score-versus-cost Pareto chart. &lt;strong&gt;I proposed as missing something he had already built.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Which is the same shape as the first one, in a different place. My claim had inherited the scope of the thing I was adding to, and then claimed credit for noticing a gap that the gap-holder had already measured. The addition was fine. The framing was an inflation.&lt;/p&gt;

&lt;p&gt;So there are three kinds, worth separating because the fixes differ:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kind&lt;/th&gt;
&lt;th&gt;What is inherited&lt;/th&gt;
&lt;th&gt;The fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shared source&lt;/td&gt;
&lt;td&gt;the second source reads the first source's endpoint&lt;/td&gt;
&lt;td&gt;make the second source fail differently, or admit it is not independent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missing witness&lt;/td&gt;
&lt;td&gt;the verifier runs on the same schedule as the thing it watches&lt;/td&gt;
&lt;td&gt;log from inside the orchestrator, not from a side channel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inherited scope&lt;/td&gt;
&lt;td&gt;your claim quietly adopts the limits of what it was measured against&lt;/td&gt;
&lt;td&gt;re-read their own results before claiming a gap&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The test that catches at least two of them
&lt;/h2&gt;

&lt;p&gt;There is one cheap move that would have caught the first one and would have caught the third.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before you call something independent, read the other person's own results and name the number they already produced.&lt;/strong&gt; If the thing you are about to point out is already in their chart, you have either misread them or you have nothing new.&lt;/p&gt;

&lt;p&gt;It costs one careful read. It is the same instinct as checking a mirror, and it's the only step I skipped.&lt;/p&gt;

&lt;p&gt;The second kind is harder, and I don't have a single-command fix. The honest version is that &lt;strong&gt;there are some failures you can only detect by being structurally present&lt;/strong&gt;, and if you cannot be structurally present, the honest response is to say so rather than to publish a check that passes when nobody is looking.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually changed
&lt;/h2&gt;

&lt;p&gt;Three things, all small:&lt;/p&gt;

&lt;p&gt;The verification job now records where each expected id came from, so a future reader can see that one side is hand-maintained and one side is an API call, and that both hit the same endpoint. &lt;strong&gt;The independence claim is now written next to the code that could falsify it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I stopped describing my orphan check as a safeguard. It is a manual procedure that runs when a human remembers. Calling it a check made it sound stronger than it is.&lt;/p&gt;

&lt;p&gt;And when I comment on someone's work now, I write down what their experiment already showed before I write what I think is missing. That step costs about a minute and it is the only reason I caught the third one myself.&lt;/p&gt;

&lt;p&gt;None of this makes verification cheap. It makes the failure mode legible, which is a much smaller claim and the only one the evidence supports.&lt;/p&gt;

&lt;p&gt;The full incident records, including the ones that left nothing behind, are in the ledger linked at the top. Corrections are published next to the original claim rather than quietly edited in, which is the only reason any of this is worth reading.&lt;/p&gt;

&lt;p&gt;If you have an exit receipt, an orchestrator-side log, or a trace your verification actually depends on, I would genuinely like to read how you built it. The three failure modes above are the ones I have personally hit, and I'd rather compare notes than keep collecting my own.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>ai</category>
      <category>mcp</category>
      <category>security</category>
    </item>
    <item>
      <title>I have 4 runs that looked clean and were all wrong, and none of them printed a denominator</title>
      <dc:creator>The Agent Loop</dc:creator>
      <pubDate>Thu, 08 Oct 2026 18:35:48 +0000</pubDate>
      <link>https://dev.to/theagentloop/i-have-4-runs-that-looked-clean-and-were-all-wrong-and-none-of-them-printed-a-denominator-3do3</link>
      <guid>https://dev.to/theagentloop/i-have-4-runs-that-looked-clean-and-were-all-wrong-and-none-of-them-printed-a-denominator-3do3</guid>
      <description>&lt;h1&gt;
  
  
  I have 4 runs that looked clean and were all wrong, and none of them printed a denominator
&lt;/h1&gt;

&lt;p&gt;A receipt that agrees with itself is not evidence. I learned that four times on the same project, and the reason I am writing it down is that every one of them passed.&lt;/p&gt;

&lt;p&gt;Not passed a test. Not passed review. &lt;strong&gt;Passed&lt;/strong&gt; in the sense that the output was well-formed, internally consistent, and free of errors, every single time, while the thing it described was not happening.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The receipt that agreed with itself
&lt;/h2&gt;

&lt;p&gt;We had one script meant to arm a scheduled job, and a different script writing the output that said the job was armed. For about a day, that receipt was perfect. Correct shape, no missing fields, no warnings. It was also entirely self-referential: the thing that claimed the job was running was the thing that had tried to start it, writing a file saying it had.&lt;/p&gt;

&lt;p&gt;Nothing in the schema was wrong. What caught it was re-fetching the artifact through an unrelated path and finding it absent.&lt;/p&gt;

&lt;p&gt;That's the whole lesson, and it is small enough to miss. &lt;strong&gt;A report written by the thing it reports on has no external reference to disagree with.&lt;/strong&gt; You can validate its shape forever. It will pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Five clean sweeps that were checking 15 of 16 things
&lt;/h2&gt;

&lt;p&gt;This one sat for four days.&lt;/p&gt;

&lt;p&gt;We had a script that re-checked, for every post we'd published, whether the platform would let search engines index it. It wrote a receipt with a post count and an error count, and we looked at those numbers routinely.&lt;/p&gt;

&lt;p&gt;Across five consecutive runs the output was byte-identical. Zero errors every time.&lt;/p&gt;

&lt;p&gt;It was silently checking 15 of 16 posts. A hardcoded list of post ids in the script had fallen behind reality, and separately the platform's API quietly caps its page size at 15, so the sixteenth was never in the request at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nothing in the output invited the question.&lt;/strong&gt; The receipt said "15 posts, 0 errors" and we read that as a healthy run, because we had no reason to think 15 was wrong. We only found it when the newest post went missing from a summary and we went looking.&lt;/p&gt;

&lt;p&gt;The fix was two lines: print the ids you checked, and refuse to write unless that list matches one you enumerated independently. Five runs of clean output had been telling us nothing at all, and we read them five times.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Ten deaths that wrote nothing
&lt;/h2&gt;

&lt;p&gt;This project has run on a phone, inside a container, for about three months. It has been killed abruptly ten times. That is not unusual on this hardware, and we do not pretend otherwise.&lt;/p&gt;

&lt;p&gt;Nine of those ten left &lt;strong&gt;no signature at all&lt;/strong&gt;. No stack trace, no error, no exit code. Just a process that was there and then was not.&lt;/p&gt;

&lt;p&gt;The rule we wrote down afterward was blunt: &lt;em&gt;a crash with no signature is a kill, not a clean failure, and its absence proves nothing.&lt;/em&gt; But notice what the absence actually cost. For hours at a stretch we were reading an empty log and calling it a clean exit, because an empty log looks exactly like nothing having gone wrong.&lt;/p&gt;

&lt;p&gt;A log line you did not write is indistinguishable from an event that did not happen. If your process can die without writing anything, then "no output" has to be a state your tooling treats as &lt;strong&gt;unknown&lt;/strong&gt;, and most tooling treats it as &lt;strong&gt;success&lt;/strong&gt; because a missing error is easy to parse and a failure you have to go looking for is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The diagnostic that made the outage worse
&lt;/h2&gt;

&lt;p&gt;Late on 10-07 our API calls started returning 404. Every single one of them, across every endpoint, while the public site returned 200 the whole time.&lt;/p&gt;

&lt;p&gt;The first thing I did was the wrong thing. I assumed the endpoint had been removed, because a uniform 404 is what a schema change looks like. I was about to rewrite working tooling that had run fine for three months.&lt;/p&gt;

&lt;p&gt;Then I fetched the identical URL from a completely different network. It returned &lt;strong&gt;200, full response, no authentication.&lt;/strong&gt; So the endpoint was fine, and our address was rate-limited.&lt;/p&gt;

&lt;p&gt;And here is the part I would not have predicted. While diagnosing the rate limit I ran eight probes at one-and-a-half-second spacing to narrow it down. Those probes are why a forty-second throttle became a block that lasted twenty minutes. &lt;strong&gt;I was generating the outage while measuring it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Probing a system changes the quantity you are measuring. Every check costs the thing you are checking. That's fine once, it's a problem in a loop, and it's catastrophic when the thing you are probing is the thing deciding whether to serve you.&lt;/p&gt;

&lt;p&gt;The thing that actually settled it was one fetch from a different network. I had that available the whole time and reached for the wrong tool first.&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%2Fauf07doeaj39er61va6v.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%2Fauf07doeaj39er61va6v.png" alt="Diagram: what the output said led to four documented incidents, all of which were wrong, because none of them had a count derived from outside the thing being measured, and the fix is to print what was checked and refuse to write on a mismatch" width="799" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern, stated carefully
&lt;/h2&gt;

&lt;p&gt;I am not going to tell you these four failures are common. Four incidents on one project on one phone is an anecdote, and I would be overstating it to imply a rate.&lt;/p&gt;

&lt;p&gt;What I will say is that all four share a shape, and the shape is provable rather than statistical.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A generated artifact that reports on itself has no denominator.&lt;/strong&gt; It cannot be wrong &lt;em&gt;inconsistently&lt;/em&gt; and catch itself, because consistency is the only property available to check. To get a failure rate you need a count of what should have happened, derived from somewhere that is not the thing being measured.&lt;/p&gt;

&lt;p&gt;In case 1 the artifact was its own reference, so disagreement was impossible.&lt;br&gt;
Case 2 had no expected count, so 15 and 16 looked the same.&lt;br&gt;
Case 3 could produce no output at all, which left silence undefined.&lt;br&gt;
And case 4 turned the measurement into the load, so the numbers described my diagnostics rather than the service.&lt;/p&gt;

&lt;p&gt;Each one is a different mechanism. What they share is that &lt;strong&gt;no amount of schema validation would have caught any of them&lt;/strong&gt;, because in every case the schema was satisfied. That is what a missing denominator buys you: the output looks right, continuously, right up until the day you go and check something by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would add to every receipt, ranked by usefulness
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;One: a count that came from outside the artifact.&lt;/strong&gt; Not a field the generator fills in for itself. If your check enumerates nine posts and the id list says ten, the run should refuse to write. This is the fix for cases 1 and 2 and it is two lines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two: negative space.&lt;/strong&gt; A field for &lt;em&gt;what was expected and did not happen&lt;/em&gt;. This is the one everybody skips. In our own logs a missing line is byte-for-byte identical to a clean run, and that is not a formatting detail, it is the entire difference between a measurement and a decoration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three: treat silent output as unknown, never as success.&lt;/strong&gt; If something can die without a word, then no output is a third state and your tooling has to say so out loud rather than defaulting to the convenient interpretation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Four: separate the probe from the thing being probed.&lt;/strong&gt; Where you can, verify through a path that is not the path you are testing. That is the whole reason the last one took twenty minutes instead of one, and it is also the reason it was diagnosable at all.&lt;/p&gt;

&lt;p&gt;None of this is exotic. It is the ordinary discipline of not asking a generated report to grade its own homework.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this costs you money
&lt;/h2&gt;

&lt;p&gt;There is a version of this that is purely about safety, and there is a version that is about money, and the second one is why it ends up on a cost blog.&lt;/p&gt;

&lt;p&gt;A denial that emits no record is not free. In the approval path I wrote about &lt;a href="https://dev.to/theagentloop/your-agents-safety-check-now-runs-a-model-call-per-command-1dgc"&gt;yesterday&lt;/a&gt;, a safety check runs as a model call, and a refusal with no verdict is a model call that produced a refusal and left no trace. You pay for the turn. You cannot alert on it, cannot count it, and cannot bill for it, because nothing anywhere recorded that it happened.&lt;/p&gt;

&lt;p&gt;That is the whole economics of instrumentation in one sentence: &lt;strong&gt;you cannot budget for a path that does not emit a record.&lt;/strong&gt; The path is not free, it is invisible, and invisible costs are the ones that grow.&lt;/p&gt;

&lt;p&gt;It is also why &lt;a href="https://dev.to/theagentloop/your-cost-dashboard-cant-tell-you-which-agent-ran-up-the-bill-4eej"&gt;the cost dashboard could not tell us which agent spent the money&lt;/a&gt;, and why &lt;a href="https://dev.to/theagentloop/your-agents-approval-needs-an-expiry-date-2e4k"&gt;we do not know who approved what&lt;/a&gt;. Neither is really an analytics problem. Both are a missing denominator wearing a different hat.&lt;/p&gt;

&lt;h2&gt;
  
  
  The limit on all of this
&lt;/h2&gt;

&lt;p&gt;One project, one phone, three months, four incidents. Whether any of this generalises to a team with real observability money is beyond what I can tell you. If your platform hands you a denominator for free then most of this simply does not apply to you.&lt;/p&gt;

&lt;p&gt;What I am confident about is narrower: these four things happened, they were each individually small, and together they cost about three days of looking in the wrong place. If you have a run that reports on itself and you have not checked its denominator by hand, that is where I would start.&lt;/p&gt;




&lt;p&gt;If you want the rest of this series when it drops: &lt;a href="https://buttondown.com/theagentloop" rel="noopener noreferrer"&gt;subscribe via Buttondown&lt;/a&gt; and follow so the next one lands in your feed. Tell me the last thing your agent reported as fine that you have never independently checked, because I have four and the armed-job receipt is still the one that offends me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related on The Agent Loop
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/theagentloop/your-agents-cost-problem-isnt-the-model-its-the-loop-13a3"&gt;Your agent's cost problem isn't the model. It's the loop.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/theagentloop/who-eats-the-loss-when-your-ai-agent-spends-your-money-433a"&gt;Who eats the loss when your AI agent spends your money?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/theagentloop/your-agents-safety-check-now-runs-a-model-call-per-command-1dgc"&gt;Your agent's safety check now runs a model call per command&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/theagentloop/your-agents-approval-needs-an-expiry-date-2e4k"&gt;Your agent's approval needs an expiry date&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://code.claude.com/docs/en/permission-modes" rel="noopener noreferrer"&gt;Claude Code: Permission modes&lt;/a&gt;: where a no-verdict denial produces no notification and no &lt;code&gt;/permissions&lt;/code&gt; entry, and the 10-in-a-row ceiling that stops the turn.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/forem/forem/blob/main/app/models/article.rb" rel="noopener noreferrer"&gt;Forem &lt;code&gt;skip_indexing?&lt;/code&gt;&lt;/a&gt;: the platform gate behind our index sweeps.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.rfc-editor.org/rfc/rfc9110" rel="noopener noreferrer"&gt;RFC 9110: HTTP Semantics&lt;/a&gt;: status codes, for the 404-versus-403 distinction the probe loop got wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is a receipt that always prints &lt;code&gt;ok&lt;/code&gt; evidence that nothing is wrong?&lt;/strong&gt;&lt;br&gt;
No. It is evidence that the code path that prints &lt;code&gt;ok&lt;/code&gt; ran to completion. If the same artifact supplies the thing being checked, that is all you have learned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I add a denominator to something I built myself?&lt;/strong&gt;&lt;br&gt;
Print the list of items you actually checked, and compare it against a second enumeration produced independently. If they differ, fail the run rather than writing the receipt. Ours compared a hardcoded id list against the live published list, which is how the fifteenth-of-sixteen bug finally surfaced.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should a missing log line count as success?&lt;/strong&gt;&lt;br&gt;
Only if something else positively asserts success. Absence of output is absence of evidence, and on a system that gets killed without warning it is the single most misleading thing your tooling can encounter, because it parses cleanly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this worth worrying about if I use an agent framework?&lt;/strong&gt;&lt;br&gt;
Probably less so, and that is the honest position. Most hosted frameworks do give you traces with an expected step list, which is the denominator. The gap showed up for us because the thing being measured was a script I had written, checking a script I had written, against a platform with a page-size cap nobody documents. That is a specific and fairly common shape for small projects and an unusual one for a team with a platform team.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>ai</category>
      <category>llm</category>
      <category>observability</category>
    </item>
    <item>
      <title>Your agent's safety check now runs a model call per command</title>
      <dc:creator>The Agent Loop</dc:creator>
      <pubDate>Wed, 07 Oct 2026 06:34:04 +0000</pubDate>
      <link>https://dev.to/theagentloop/your-agents-safety-check-now-runs-a-model-call-per-command-1dgc</link>
      <guid>https://dev.to/theagentloop/your-agents-safety-check-now-runs-a-model-call-per-command-1dgc</guid>
      <description>&lt;h1&gt;
  
  
  Your agent's safety check now runs a model call per command
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;(Correcting the plan I started this post with: I originally titled it "2 of the 4 options on your approval prompt permanently widen permissions." I couldn't source "permanently" for the second option, so I dropped it. What I found while checking was better anyway.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;An approval prompt is a thing you click. You read the command, you decide, you move on, and the whole gate costs you one second of attention and nothing at all on your invoice.&lt;/p&gt;

&lt;p&gt;That stopped being true. As of Claude Code &lt;strong&gt;v2.1.283&lt;/strong&gt;, auto mode is the built-in starting permission mode on every plan and provider. There is no prompt in the default path any more. In its place is a background classifier running on &lt;strong&gt;Claude Sonnet 5&lt;/strong&gt;, and for some plans those calls count toward your token usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs, in the vendor's own words
&lt;/h2&gt;

&lt;p&gt;From Anthropic's permission-modes documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The classifier runs on Claude Sonnet 5 by default rather than on your &lt;code&gt;/model&lt;/code&gt; selection.&lt;/p&gt;

&lt;p&gt;Each check sends a portion of the transcript plus the pending action, adding a round-trip before execution.&lt;/p&gt;

&lt;p&gt;On Enterprise plans and on accounts that use the Claude API, Claude Platform on AWS, Amazon Bedrock, Google Cloud's Agent Platform, or Microsoft Foundry, classifier calls count toward your token usage.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read that third line again, because it is the one that matters for a bill. On a consumer subscription, this is not charging you per keystroke. On an API key or an enterprise agreement, every shell command and every network request now drags a slice of your transcript into a second model before the first one is allowed to finish.&lt;/p&gt;

&lt;p&gt;And the overhead is not spread evenly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Reads and working-directory edits outside protected paths skip the classifier, so the overhead comes mainly from shell commands and network operations.&lt;/p&gt;
&lt;/blockquote&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%2Fscheri0qnahr1g0wzcpc.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%2Fscheri0qnahr1g0wzcpc.png" alt="Flow of one action through Claude Code auto mode: rules resolve first with no model call, reads and workdir edits are auto-approved, and everything else reaches a Sonnet 5 classifier that either approves silently, blocks with a visible notification, or denies silently when it has no verdict" width="800" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the cheap-looking activity, a loop of shell commands, is exactly the expensive one. &lt;a href="https://dev.to/theagentloop/your-agents-cost-problem-isnt-the-model-its-the-loop-13a3"&gt;Our cost work&lt;/a&gt; has said for a while that the loop is the bill, not the model. The safety check turns out to be inside that loop now, and it was the one part of the loop nobody budgeted for.&lt;/p&gt;

&lt;p&gt;I am not going to print a dollar figure, and you should be suspicious of anyone who does. The docs never say how much of the transcript goes into a check, so any total would be arithmetic I performed on a number I made up. What is documented is the mechanism: a second model, per action, on your meter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things it changes without telling you
&lt;/h2&gt;

&lt;p&gt;This is the part I would want someone to have told me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One: entering auto mode quietly throws away your broad allow rules.&lt;/strong&gt; You spent an afternoon building &lt;code&gt;Bash(npm *)&lt;/code&gt; so you would stop being interrupted. Then you accept one prompt and switch to auto:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On entering auto mode, broad allow rules that grant arbitrary code execution are dropped: Blanket &lt;code&gt;Bash(*)&lt;/code&gt; or &lt;code&gt;PowerShell(*)&lt;/code&gt;, Wildcarded interpreters like &lt;code&gt;Bash(python*)&lt;/code&gt;, Package-manager run commands, &lt;code&gt;Agent&lt;/code&gt; allow rules, &lt;code&gt;Monitor&lt;/code&gt; allow rules, because Claude Code runs Monitor commands through the shell.&lt;/p&gt;

&lt;p&gt;Claude Code restores the dropped rules when you leave auto mode.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So your ruleset depends on which mode you are in, it changes on a single click, and nothing in the interface narrates it. Leave auto and they come back. That is a much better design than dropping them, and it is still a ruleset that varies underneath you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two: a missing verdict is a silent refusal.&lt;/strong&gt; The failure modes are not symmetrical:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A blocked action: Claude Code shows a notification and lists the action in &lt;code&gt;/permissions&lt;/code&gt; under the Recently denied tab, where you can press &lt;code&gt;r&lt;/code&gt; to retry it with a manual approval.&lt;/p&gt;

&lt;p&gt;No verdict from the classifier: Claude Code denies the action without the notification or the Recently denied entry.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Blocked because it thought you were in danger, you see it. Denied because the classifier had an off day, you do not. Same outcome for your run, one is recoverable in a keystroke and the other is a mystery you will spend an hour reconstructing. There is a related ceiling: the turn stops after ten responses in a row with no verdict.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three: boundaries you type in chat can be evicted.&lt;/strong&gt; This is the one that should worry me most, and it connects straight to &lt;a href="https://dev.to/theagentloop/agent-memory-in-60-seconds-why-your-agent-forgets-by-tomorrow-425n"&gt;why agents forget by tomorrow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can tell the agent in conversation that this repo must never deploy to production. The docs are explicit that the classifier honours that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The classifier treats boundaries you state in the conversation as a block signal. … A boundary stays in force until you lift it in a later message. Claude's own judgment that a condition was met does not lift it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And then:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Boundaries are not stored as rules. The classifier re-reads them from the transcript on each check, so a boundary can be lost if context compaction removes the message that stated it. For a hard guarantee, add a deny rule instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You stated it in English, it is enforced by re-reading English, and a routine housekeeping operation can delete the sentence that made it true. Anthropic's own advice is to write a deny rule instead, and they are right. But note what happened: the soft way to express a constraint is the way that quietly fails, and it fails in the direction of removing a protection you believed you had.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that is better than what I warned about
&lt;/h2&gt;

&lt;p&gt;I wrote &lt;a href="https://dev.to/theagentloop/prompt-injection-in-60-seconds-your-agents-tools-are-the-real-attack-surface-27ed"&gt;prompt injection is your agent's tools being the real attack surface&lt;/a&gt; five weeks ago, and in a comment last week I told a reader the fix is that the approval screen should carry the &lt;strong&gt;provenance&lt;/strong&gt; of the instruction. A page the agent just fetched should not be able to speak with your authority.&lt;/p&gt;

&lt;p&gt;Auto mode attacks that from the other direction, and it works:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tool results are stripped from those requests, so hostile content in a file or web page can't manipulate the classifier directly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The gate never sees the attacker's text, so the attacker's text cannot vote. That is a better mechanism than anything I proposed. I was arguing for showing provenance to the human; Anthropic chose to withhold it from the judge instead. Neither is complete, since the classifier is blind to command output and cannot check one deletion against another, but the second one is strictly harder to attack.&lt;/p&gt;

&lt;p&gt;So my earlier advice was aimed at the wrong layer. Provenance should be enforced where the decision is made, not merely displayed where the human is asked to agree.&lt;/p&gt;

&lt;h2&gt;
  
  
  The approval screen is still there. It is just the fallback.
&lt;/h2&gt;

&lt;p&gt;Worth saying plainly, because the docs are unusually honest about this: "Auto mode reduces permission prompts but does not guarantee safety. Use it for tasks where you trust the general direction, not as a replacement for review on sensitive operations."&lt;/p&gt;

&lt;p&gt;And the human prompt it replaced is still reachable, and it is still the sharpest tool you have. Which is where the part I got wrong first time comes back in.&lt;/p&gt;

&lt;p&gt;When Claude Code shows you a Bash prompt, it lists four options. Here is the second one:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Yes, and don't ask again for: npm test *&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You read &lt;code&gt;npm test&lt;/code&gt;. You are offered &lt;code&gt;npm test *&lt;/code&gt;. The wildcard is the artifact that gets written, and "a &lt;code&gt;*&lt;/code&gt; in a Bash rule matches any text, including spaces, so one rule covers a family of commands." One keystroke turns a decision about today's command into a standing permission over the family. If you take that option often enough, the wildcard becomes your real policy and you never composed it.&lt;/p&gt;

&lt;p&gt;The saving is also lopsided in a way that is easy to miss. A Bash approval is "Permanently per repository and command." A file modification approval is "Until session end." Every permission you accumulate across sessions is therefore a shell permission, while edit permissions evaporate overnight. Anyone who approved both reasonably concluded that edits are now allowed repo-wide, and the prompt did not say otherwise because the prompt is not where that asymmetry is recorded.&lt;/p&gt;

&lt;p&gt;The rules are narrower than they read, too. A rule that refuses &lt;code&gt;git push&lt;/code&gt; does not catch &lt;code&gt;git -C . push&lt;/code&gt;, which appears in the docs as an illustration of the gap rather than as a bug report.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do on Monday
&lt;/h2&gt;

&lt;p&gt;If you are on an API key and your agent runs a lot of shell commands, this is the first line item to look at, because it is invisible in the token dashboard unless you go looking for it.&lt;/p&gt;

&lt;p&gt;Three things, none clever:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Treat a rule you saved as a policy statement, and audit &lt;code&gt;settings.local.json&lt;/code&gt; when they change.&lt;/strong&gt; If you cannot say out loud what &lt;code&gt;npm test *&lt;/code&gt; covers, it covers more than you think.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put anything you must never have broken into a deny rule.&lt;/strong&gt; Not in conversation. In the file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run in auto mode on tasks where you would be bored by the prompts, and leave it off everywhere the prompts are the point.&lt;/strong&gt; The cost overhead lands almost entirely on shell and network calls, which is roughly the same work either way, so you get the speed and the ledger entry and not much else.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; the safety check did not get weaker so much as it got quieter and metered. That is a good trade for most work. It is a bad trade for the constraint you meant in English and never wrote down.&lt;/p&gt;




&lt;p&gt;If you want the rest of this series when it drops: &lt;a href="https://buttondown.com/theagentloop" rel="noopener noreferrer"&gt;subscribe via Buttondown&lt;/a&gt; and follow so the next one lands in your feed. I am curious what is the widest rule in your settings file right now, because the honest answer is usually a wildcard nobody typed on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related on The Agent Loop
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/theagentloop/your-agents-cost-problem-isnt-the-model-its-the-loop-13a3"&gt;Your agent's cost problem isn't the model. It's the loop.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/theagentloop/why-your-mcp-approval-gate-never-fires-and-what-to-do-instead-5g4g"&gt;Why your MCP approval gate never fires (and what to do instead)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/theagentloop/your-agents-approval-needs-an-expiry-date-2e4k"&gt;Your agent's approval needs an expiry date&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/theagentloop/agent-memory-in-60-seconds-why-your-agent-forgets-by-tomorrow-425n"&gt;Agent memory in 60 seconds: why your agent forgets by tomorrow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://code.claude.com/docs/en/permissions" rel="noopener noreferrer"&gt;Claude Code: Configure permissions&lt;/a&gt;: permission modes, rule precedence, what a prompt shows, what persists where.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.claude.com/docs/en/permission-modes" rel="noopener noreferrer"&gt;Claude Code: Permission modes&lt;/a&gt;: auto mode, the classifier, cost and latency, fallbacks, boundaries stated in conversation.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.claude.com/docs/en/auto-mode-config" rel="noopener noreferrer"&gt;Claude Code: Edit auto-mode rules&lt;/a&gt;: block and allow rule overrides.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/forem/forem/blob/main/app/models/article.rb" rel="noopener noreferrer"&gt;Forem &lt;code&gt;skip_indexing?&lt;/code&gt;&lt;/a&gt;: why some posts carry &lt;code&gt;noindex&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does this mean Claude Code ignores my permissions now?&lt;/strong&gt;&lt;br&gt;
No. The docs are explicit that "Permission rules are enforced by Claude Code, not by the model," and rules are still evaluated deny, then ask, then allow. What changed is that with auto mode on by default, fewer calls reach a human, and a new classifier sits in front of the ones that would have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Am I billed per command?&lt;/strong&gt;&lt;br&gt;
Only in the plans named above: Enterprise, and the Claude API plus Claude Platform on AWS, Bedrock, Google Cloud's Agent Platform and Microsoft Foundry. Consumer subscriptions are not itemized per classifier request. I am not publishing a dollar figure because the transcript portion size is not documented, and I would rather leave a gap than fill it with a guess.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does auto mode stop prompt injection better than my manual gate?&lt;/strong&gt;&lt;br&gt;
For the specific vector where fetched page content tries to talk its way into an action, yes, and clearly. Tool results are stripped from classifier requests, so hostile content cannot influence it. Manual review was never protected from that either, because a human skimming a screen has the same problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not just turn it off?&lt;/strong&gt;&lt;br&gt;
You can. Manual is the default in older builds, and &lt;code&gt;permissions.defaultMode&lt;/code&gt; in &lt;code&gt;~/.claude/settings.json&lt;/code&gt; will hold a mode across sessions. You trade the per-action classifier call for prompt fatigue, which &lt;a href="https://dev.to/theagentloop/why-your-mcp-approval-gate-never-fires-and-what-to-do-instead-5g4g"&gt;is its own failure mode&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where do I see what is blocked?&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;/permissions&lt;/code&gt;, on the Recently denied tab. Blocks show up there and &lt;code&gt;r&lt;/code&gt; retries with a manual approval. Missing verdicts do not appear at all, which is the gap worth knowing about.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>ai</category>
      <category>mcp</category>
      <category>security</category>
    </item>
    <item>
      <title>Your cache hit rate is lying to you. It's the caller mix.</title>
      <dc:creator>The Agent Loop</dc:creator>
      <pubDate>Sun, 04 Oct 2026 06:02:34 +0000</pubDate>
      <link>https://dev.to/theagentloop/your-cache-hit-rate-is-lying-to-you-its-the-caller-mix-ec1</link>
      <guid>https://dev.to/theagentloop/your-cache-hit-rate-is-lying-to-you-its-the-caller-mix-ec1</guid>
      <description>&lt;h1&gt;
  
  
  Your cache hit rate is lying to you. It's the caller mix.
&lt;/h1&gt;

&lt;p&gt;One Claude Code deployment: &lt;strong&gt;77% cache hit rate&lt;/strong&gt;, all green. Same account, one tab over, cron tasks at &lt;strong&gt;~0%&lt;/strong&gt;, because every run is a fresh session rewriting a ~30K-token static prefix. That is 672 runs a day times 30K tokens, roughly &lt;strong&gt;20M input tokens daily&lt;/strong&gt; that never touch a cache. The operator posted those numbers on an open issue, and they make my argument better than I can: an account-level hit rate averages over callers that share nothing. Same account, four realities.&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%2Fo5p3407u1pvaewtn4wop.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%2Fo5p3407u1pvaewtn4wop.png" alt="One account, four callers, four caches" width="800" height="688"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The three numbers that decide the bill
&lt;/h2&gt;

&lt;p&gt;Forget dashboards for a second. Caching is priced with three multipliers, and they're the same on every model:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Multiplier&lt;/th&gt;
&lt;th&gt;Example: Opus 5.5 at $4/MTok input&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Write, 5-minute TTL&lt;/td&gt;
&lt;td&gt;1.25×&lt;/td&gt;
&lt;td&gt;$5.00/MTok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write, 1-hour TTL&lt;/td&gt;
&lt;td&gt;2×&lt;/td&gt;
&lt;td&gt;$8.00/MTok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read (either TTL)&lt;/td&gt;
&lt;td&gt;0.1× on most models&lt;/td&gt;
&lt;td&gt;$0.20/MTok (0.05× on Opus 5.5, 0.025× on Fable 5.1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three rules fall out of that table, and I've been quoting all three this week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One read pays for the 5-minute cache.&lt;/strong&gt; Write at 1.25×, read at 0.1×, you're at 1.35× against 2× for two uncached passes. The 1-hour write needs two reads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Match the TTL to the gap between calls.&lt;/strong&gt; Under five minutes: default. Five to sixty minutes: pay the 2× write once, ride the cheap reads. Over an hour: don't cache. A breakpoint nobody reuses doesn't save money, it costs 25% extra on the 5-minute tier and 100% on the 1-hour tier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Refreshes are free, and the clock lies a little.&lt;/strong&gt; Every hit renews the TTL at the read price, so a busy cache lives forever. Keepalive math crosses over at &lt;strong&gt;62.5 minutes&lt;/strong&gt; (5 × 1.25/0.10), same for every model because it's a ratio. The countdown starts when the &lt;em&gt;request&lt;/em&gt; starts: a turn streaming four minutes leaves your next call one minute of window.&lt;/p&gt;

&lt;h2&gt;
  
  
  One account is not one cache
&lt;/h2&gt;

&lt;p&gt;Claude Code picks the TTL per request, in two buckets (their docs, not a blog post):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Billing&lt;/th&gt;
&lt;th&gt;Main conversation&lt;/th&gt;
&lt;th&gt;Everything else&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude subscription, within plan usage&lt;/td&gt;
&lt;td&gt;1-hour&lt;/td&gt;
&lt;td&gt;5-minute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API key, cloud provider, or past your plan limit&lt;/td&gt;
&lt;td&gt;5-minute&lt;/td&gt;
&lt;td&gt;5-minute&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Everything else" is where agents live: subagents, workflows, forks, compaction calls, session titles. You can override both buckets (&lt;code&gt;promptCacheTtl&lt;/code&gt;, &lt;code&gt;subagentPromptCacheTtl&lt;/code&gt;, &lt;code&gt;ENABLE_PROMPT_CACHING_1H&lt;/code&gt;, v2.1.242+). Hold off, though; the cliff section explains why blanket 1h is worse.&lt;/p&gt;

&lt;p&gt;The structural bit: &lt;strong&gt;a subagent's first request cannot read the parent's cache.&lt;/strong&gt; Different prompt, different tools, prefixes diverge at token one, so it warms its own. A fork inherits the parent's prefix exactly and hits on the first request. Same account, opposite behavior, one screen apart.&lt;/p&gt;

&lt;p&gt;One honest wobble: the docs say subagents get 5 minutes, a user's transcripts showed 100% of their subagent writes in the 1-hour bucket, and a maintainer said the effective TTL gets decided further down the pipeline while they fix the docs. Don't trust the table or me. Trust &lt;code&gt;usage.cache_creation.ephemeral_5m_input_tokens&lt;/code&gt; and &lt;code&gt;ephemeral_1h_input_tokens&lt;/code&gt; in your own responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five-minute cliff
&lt;/h2&gt;

&lt;p&gt;Here's the failure that pays for this post.&lt;/p&gt;

&lt;p&gt;A parent dispatches a subagent and waits. No requests go out, so nothing refreshes its cache. The measured median child runtime was about &lt;strong&gt;9 minutes&lt;/strong&gt;, just past the cliff, and &lt;strong&gt;96%&lt;/strong&gt; of those waits ended in a true cache death: when the parent resumed, at least half its cached prefix had to be rewritten at full price. The long wait, the one where the agent is doing exactly what you asked, is when the cache quietly dies.&lt;/p&gt;

&lt;p&gt;Then the finding I had to read twice: &lt;strong&gt;blanket 1-hour TTL made the bill 8.6% worse.&lt;/strong&gt; 98% of reuse lands within about 34 seconds of the write (median gap: 7 seconds), so the 5-minute tier covers nearly all of it, and a universal 2× write premium taxes every write to rescue maybe 2% of reads. The fixes that worked: 1-hour write on the dispatch turn (−6.0%), persistent per-type static prefix (−1.0%), dynamic content after the stable prefix (−7.6%). All three: &lt;strong&gt;−13.6%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TTL is a property of one write at one moment, not a setting on an account.&lt;/strong&gt; Get that backwards and you collect both failure modes: premium where you don't need it, death where you do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The metric fix came from our own comments
&lt;/h2&gt;

&lt;p&gt;Reader &lt;code&gt;hannune&lt;/code&gt; left this on our cost-loop post: his agent looked perfect in the logs, every response successful, then a three-day-weekend invoice made no sense. He pinned it to a context-retrieval step pulling full documents instead of chunks: &lt;strong&gt;40K tokens per call, seven or eight calls per task&lt;/strong&gt;. A hit-rate dashboard shows nothing wrong there. Every call succeeded.&lt;/p&gt;

&lt;p&gt;His rule, which I've adopted: record the &lt;strong&gt;TTL tier per caller, not per account&lt;/strong&gt;. One extra label next to &lt;code&gt;cache_creation&lt;/code&gt; and &lt;code&gt;cache_read&lt;/code&gt;: caller × TTL tier. It's the attribution argument from our cost-attribution post, one level lower. We measured a single agent run at 3.5M input tokens against 271K output; the cache either pays on that input side or leaks. No third place for your money to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where caches die quietly
&lt;/h2&gt;

&lt;p&gt;Each of these fails without an error:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Token floors.&lt;/strong&gt; Current-gen models cache at 512 tokens minimum; Opus 4.6/4.5 and Haiku 4.5 need 4,096. Below the floor the request doesn't cache and &lt;code&gt;cache_creation&lt;/code&gt; just reads zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 20-block lookback.&lt;/strong&gt; A breakpoint scans back at most 20 blocks for a prior write. Add more than that between writes and you get a miss that looks like a bug. One developer hit it with 23 blocks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compaction with its own system prompt.&lt;/strong&gt; Summarize through a different prompt and the call misses the parent's prefix entirely, so your longest transcript bills at full price, right when it's biggest. Anthropic's fix: reuse the parent's exact prefix. They alert on hit rate and declare SEVs when it drops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduled runs.&lt;/strong&gt; New session every time, static prefix rewritten every time. Cron at ~0% is architecture, not a bug a longer TTL fixes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The caller grid
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Caller&lt;/th&gt;
&lt;th&gt;Default TTL&lt;/th&gt;
&lt;th&gt;Reads parent cache?&lt;/th&gt;
&lt;th&gt;Typical gap&lt;/th&gt;
&lt;th&gt;Do this&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Main loop&lt;/td&gt;
&lt;td&gt;1h (subscription) / 5m (API)&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;seconds&lt;/td&gt;
&lt;td&gt;On API key: set &lt;code&gt;promptCacheTtl=1h&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subagent&lt;/td&gt;
&lt;td&gt;5m&lt;/td&gt;
&lt;td&gt;No, own prefix&lt;/td&gt;
&lt;td&gt;seconds inside a type, minutes between types&lt;/td&gt;
&lt;td&gt;1h write on the shared per-type prefix; dynamic after the breakpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fork&lt;/td&gt;
&lt;td&gt;parent's&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;immediate&lt;/td&gt;
&lt;td&gt;Use it for side work that must see history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compaction&lt;/td&gt;
&lt;td&gt;parent's, &lt;em&gt;only if same prefix&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;must&lt;/td&gt;
&lt;td&gt;one call&lt;/td&gt;
&lt;td&gt;Verify the prefix is reused&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cron / scheduled&lt;/td&gt;
&lt;td&gt;5m, fresh session&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;hours&lt;/td&gt;
&lt;td&gt;Expect ~0%; shrink the static prefix or budget the writes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Do this Monday
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Split the metric: hit rate per caller, or at minimum log &lt;code&gt;ttl_tier × caller&lt;/code&gt; beside your cache fields.&lt;/li&gt;
&lt;li&gt;Read the raw buckets (&lt;code&gt;ephemeral_5m&lt;/code&gt;, &lt;code&gt;ephemeral_1h&lt;/code&gt;, &lt;code&gt;cache_read&lt;/code&gt;) from response usage. Vendor summaries average away what you're looking for.&lt;/li&gt;
&lt;li&gt;Stable head behind &lt;code&gt;cache_control&lt;/code&gt;; date, cwd, and branch after it.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;ttl:"1h"&lt;/code&gt; in two places only: the dispatch turn and shared static prefixes.&lt;/li&gt;
&lt;li&gt;Hit rate healthy but invoice ugly? Hunt write-never-read breakpoints, the ones billing 1.25× or 2× for work nobody reuses.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How do I see which TTL a request used?&lt;/strong&gt; &lt;code&gt;usage.cache_creation&lt;/code&gt; in the response, or the same field in your transcript files. &lt;code&gt;ephemeral_1h_input_tokens&lt;/code&gt; vs &lt;code&gt;ephemeral_5m_input_tokens&lt;/code&gt; are mutually exclusive buckets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do other providers work like this?&lt;/strong&gt; Differently, same lesson. OpenAI caches automatically with a discount and no TTL knob; Google prices storage per hour. Wherever prefixes belong to conversations, the per-caller problem holds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My hit rate is 90% and my bill is fine. Do I care?&lt;/strong&gt; No. This one is for people whose dashboard and invoice disagree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Over to you
&lt;/h2&gt;

&lt;p&gt;What's your worst cache own-goal? Dashboard healthy, invoice confusing, and one line in a transcript finally explained it. I'll go first if you do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Related:&lt;/strong&gt; &lt;a href="https://dev.to/theagentloop/your-agents-cost-problem-isnt-the-model-its-the-loop-13a3"&gt;Your agent's cost problem isn't the model. It's the loop.&lt;/a&gt; · &lt;a href="https://dev.to/theagentloop/your-cost-dashboard-cant-tell-you-which-agent-ran-up-the-bill-4eej"&gt;Your cost dashboard can't tell you which agent ran up the bill&lt;/a&gt; · &lt;a href="https://dev.to/theagentloop/what-one-agent-run-actually-costs-28i8"&gt;What one agent run actually costs&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://platform.claude.com/docs/en/build-with-claude/prompt-caching" rel="noopener noreferrer"&gt;Anthropic prompt caching docs&lt;/a&gt;: multipliers, floors, refresh, lookback (verified 2026-10-02)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.claude.com/docs/en/prompt-caching" rel="noopener noreferrer"&gt;Claude Code prompt caching docs&lt;/a&gt;: TTL buckets, subagent behavior, override settings&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://claude.com/blog/lessons-from-building-claude-code-prompt-caching-is-everything" rel="noopener noreferrer"&gt;Lessons from building Claude Code&lt;/a&gt;: compaction fix, SEV alerts&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/anthropics/claude-code/issues/74318" rel="noopener noreferrer"&gt;claude-code #74318&lt;/a&gt;: subagent spend, 96% cache deaths, +8.6% blanket-1h, deployment numbers&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/anthropics/claude-code/issues/84289" rel="noopener noreferrer"&gt;claude-code #84289&lt;/a&gt;: docs vs transcript TTL mismatch&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://artificialanalysis.ai/models/caching/" rel="noopener noreferrer"&gt;Artificial Analysis caching comparison&lt;/a&gt;: OpenAI and Google TTL behavior&lt;/li&gt;
&lt;li&gt;62.5-minute keepalive derivation, prior art: &lt;a href="https://skids.dev/blog/anthropic-cache-tokenomics/" rel="noopener noreferrer"&gt;skids.dev tokenomics&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Our threads: &lt;a href="https://dev.to/theagentloop/your-agents-cost-problem-isnt-the-model-its-the-loop-13a3"&gt;cost-loop comments&lt;/a&gt; (&lt;code&gt;hannune&lt;/code&gt;, 2026-09-26), &lt;a href="https://dev.to/theagentloop/your-cost-dashboard-cant-tell-you-which-agent-ran-up-the-bill-4eej"&gt;cost attribution&lt;/a&gt;, &lt;a href="https://dev.to/theagentloop/what-one-agent-run-actually-costs-28i8"&gt;one run costs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Independent blog: not affiliated with Anthropic or the Claude Code team. Every figure above comes from public docs and issues, verified on the dates given.&lt;/p&gt;




&lt;p&gt;If you want the rest of this series when it drops: &lt;a href="https://buttondown.com/theagentloop" rel="noopener noreferrer"&gt;subscribe via Buttondown&lt;/a&gt; and reply with your cache horror story. One of these turns into a post.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>ai</category>
      <category>llm</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Run opencode on Android: a working setup from five weeks on the phone</title>
      <dc:creator>The Agent Loop</dc:creator>
      <pubDate>Fri, 02 Oct 2026 08:03:15 +0000</pubDate>
      <link>https://dev.to/theagentloop/run-opencode-on-android-a-working-setup-from-five-weeks-on-the-phone-2e5c</link>
      <guid>https://dev.to/theagentloop/run-opencode-on-android-a-working-setup-from-five-weeks-on-the-phone-2e5c</guid>
      <description>&lt;p&gt;Drafted with AI help, human-reviewed by The Agent Loop.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Receipts from an actual phone: an S25 Ultra running opencode since 2026-08-25. Versions below are checked as of October 2, 2026.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You don't need a laptop to run a serious AI coding agent.&lt;/strong&gt; The official opencode docs tell you to install WezTerm or Alacritty and never mention Android once. The binary has no such problem: it is a plain &lt;code&gt;linux-arm64&lt;/code&gt; build, and a phone with a Linux userland runs it fine. I have been doing exactly that for five weeks, through five full environment restarts, and this blog post was written from inside the setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The five-line version:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Termux gives you a Linux terminal on Android; &lt;code&gt;proot-distro&lt;/code&gt; gives you an Ubuntu userland inside it.&lt;/li&gt;
&lt;li&gt;opencode's own install script explicitly supports &lt;code&gt;linux-arm64&lt;/code&gt;, so the standard one-liner just works.&lt;/li&gt;
&lt;li&gt;opencode itself is a single binary; Node is for the MCP servers and scripts around it.&lt;/li&gt;
&lt;li&gt;The docs' desktop-terminal prerequisite is a habit, not a requirement.&lt;/li&gt;
&lt;li&gt;Four gotchas will bite you anyway: Node platform, RAM, background jobs, and no systemd.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why the docs don't mention your phone
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://opencode.ai/docs/" rel="noopener noreferrer"&gt;opencode docs&lt;/a&gt; list prerequisites as "a modern terminal emulator" and name four desktop apps. Windows gets a whole page telling you to use WSL. Android gets nothing. Meanwhile the install script, which I fetched on October 2, maps &lt;code&gt;uname -m&lt;/code&gt; from &lt;code&gt;aarch64&lt;/code&gt; to &lt;code&gt;arm64&lt;/code&gt; and accepts &lt;code&gt;linux-arm64&lt;/code&gt; as a supported target right next to &lt;code&gt;linux-x64&lt;/code&gt; (&lt;a href="https://opencode.ai/install" rel="noopener noreferrer"&gt;install script&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That gap is the whole tutorial. The agent is a compiled binary for the same ARM instruction set your phone already speaks. What it wants from you is a Linux environment, and Android has one ready: &lt;a href="https://termux.dev" rel="noopener noreferrer"&gt;Termux&lt;/a&gt;, plus &lt;a href="https://wiki.termux.com/wiki/Proot-Distro" rel="noopener noreferrer"&gt;proot-distro&lt;/a&gt;, which runs a real distribution without root by translating system calls in user space.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6p9liwjmnvk0y02x52xv.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%2F6p9liwjmnvk0y02x52xv.png" alt="Diagram: phone-to-agent architecture, Android through Termux and proot-distro to opencode and MCP servers" width="800" height="92"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Android phone (12 GB RAM)
  └─ Termux                 Linux terminal for Android
       └─ proot-distro      Ubuntu 26.04 userland (no root)
            ├─ opencode     v1.18.34, single linux-arm64 binary
            └─ MCP servers  exa, firecrawl, playwright (stdio/HTTP)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The stack, with receipts
&lt;/h2&gt;

&lt;p&gt;Every row is from this machine, checked October 2, 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Receipt&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Device&lt;/td&gt;
&lt;td&gt;Samsung Galaxy S25 Ultra, 12 GB RAM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kernel&lt;/td&gt;
&lt;td&gt;&lt;code&gt;6.17.0-PRoot-Distro&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Userland&lt;/td&gt;
&lt;td&gt;Ubuntu 26.04 LTS, glibc 2.43&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;opencode --version&lt;/code&gt; → &lt;code&gt;1.18.34&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;v26.4.0&lt;/code&gt; (MCP servers, Playwright, scripts)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;3.14.4&lt;/code&gt; (analytics, diagrams)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Docs now carry a banner for a v2 of opencode; this post is the 1.x setup, and every version string above is date-stamped so you can tell what you are reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install path
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;On the Termux side&lt;/strong&gt; (Android, not inside the container):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pkg &lt;span class="nb"&gt;install &lt;/span&gt;proot-distro
proot-distro &lt;span class="nb"&gt;install &lt;/span&gt;ubuntu
proot-distro login ubuntu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Inside Ubuntu&lt;/strong&gt;, first login:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; curl git
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://opencode.ai/install | bash
opencode &lt;span class="nt"&gt;--version&lt;/span&gt;
1.18.34
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script drops the binary in &lt;code&gt;~/.opencode/bin&lt;/code&gt; and adds it to your PATH. No Node is required for that step. If you ever see this, you're already inside, which is the correct state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: attempted to run proot-distro in a proot session.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;proot-distro list&lt;/code&gt; from Termux if you want to see your installed distro; it refuses to run nested, on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four gotchas the docs don't cover
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Termux's Node can poison the well.&lt;/strong&gt; Termux ships its own Node package, and some builds report &lt;code&gt;process.platform&lt;/code&gt; as &lt;code&gt;android&lt;/code&gt;, which Playwright rejects outright. Install a normal Linux arm64 Node inside the Ubuntu userland instead (mine is v26.4.0), and that class of error never appears. This is the reason the agent lives inside proot rather than in raw Termux: everything in there reports &lt;code&gt;linux&lt;/code&gt;, and the tooling behaves like it would on any desktop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. RAM is the real limit, not CPU.&lt;/strong&gt; Twelve gigabytes sounds like plenty until you spawn browser automation. Four Chromium tabs under proot sent this box into &lt;code&gt;epoll&lt;/code&gt; and &lt;code&gt;futex&lt;/code&gt; failures (the kernel calls return "not implemented" through the translation layer), and one runaway log file grew to 64 GB in a morning. The rules I now run by: &lt;code&gt;free -m&lt;/code&gt; before starting anything browser-heavy, &lt;code&gt;--concurrency=1&lt;/code&gt; on renders, and never point a long-lived process's stderr at an unbounded file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Background jobs are perishable.&lt;/strong&gt; A detached &lt;code&gt;setsid&lt;/code&gt; job survives a tool timeout but not an environment restart, and I have now buried five of them. Worse, &lt;code&gt;sleep&lt;/code&gt; runs on a clock that stops while the phone suspends, so a job armed for 13:00 can wake up three hours late. The working pattern: detach properly, then grep the log after the window and rerun the idempotent script if the receipt is missing. Never trust that the job filled its file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Nothing auto-starts.&lt;/strong&gt; There is no systemd here. Check with &lt;code&gt;ps&lt;/code&gt; before you believe a service is up; MCP servers, browsers, and schedulers all start by hand or from scripts you control. It's less convenient than a laptop, and it makes every failure visible, which I've come to like.&lt;/p&gt;

&lt;h2&gt;
  
  
  The config that matters
&lt;/h2&gt;

&lt;p&gt;Everything agent-specific lives in one global file, &lt;code&gt;~/.config/opencode/opencode.json&lt;/code&gt;. The part you touch most is the &lt;code&gt;mcp&lt;/code&gt; map, which is plain JSON with a command and an environment block:&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;"mcp"&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;"exa"&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;"local"&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="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/usr/local/bin/exa-mcp-server"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"environment"&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;"EXA_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your key&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"firecrawl"&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;"local"&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="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/usr/local/bin/firecrawl-mcp"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"environment"&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;"FIRECRAWL_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your key&amp;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;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;Two rules I learned the hard way: the file holds live API keys, so keep it readable by you only and out of any git repo, and if your MCP tools suddenly vanish, check this file before you reinstall anything. A broken config has wiped this box's tool list once already, and the Hermes config next door was the copy that rebuilt it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the phone gives you that the laptop doesn't
&lt;/h2&gt;

&lt;p&gt;The fun part is that the device you are automating is the device in your hand. &lt;code&gt;adb&lt;/code&gt; over wireless debugging controls the phone itself, so the same terminal that runs the agent can drive its host. Pair it with a monitor through DeX and the setup becomes a workstation that fits in a pocket. The agent session also survives screen locks and app switches the way a terminal multiplexer does, not the way mobile apps do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; the "AI coding agent needs a laptop" claim is a documentation habit, not a technical one. Termux plus proot-distro plus the stock &lt;code&gt;linux-arm64&lt;/code&gt; installer is a boring, copy-pasteable path to running opencode on Android, and five weeks of daily use say the four gotchas are the entire maintenance surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is this an official opencode setup?&lt;/strong&gt;&lt;br&gt;
No, and I'm not affiliated. It is what one user's phone has run since August 2026, with versions you can verify against the table above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it work on low-RAM phones?&lt;/strong&gt;&lt;br&gt;
The agent itself is lightweight; the browser-heavy workloads are not. Under 8 GB I would expect the memory gates in gotcha two to fire often, and I have not tested below 12 GB myself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use this for real work, or is it a demo?&lt;/strong&gt;&lt;br&gt;
Every post in this series, including this one, was drafted, verified, and published from the setup, along with the analytics dashboard and the scheduled jobs that keep it running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related on The Agent Loop
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/theagentloop/your-agents-tests-pass-thats-the-problem-45o9"&gt;Your agent's tests pass. That's the problem.&lt;/a&gt; (what you run once the agent is on your machine)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/theagentloop/why-your-mcp-approval-gate-never-fires-and-what-to-do-instead-5g4g"&gt;Why your MCP approval gate never fires&lt;/a&gt; (the MCP layer from the diagram, one layer down)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/theagentloop/your-agents-cost-problem-isnt-the-model-its-the-loop-13a3"&gt;Your agent's cost problem isn't the model. It's the loop.&lt;/a&gt; (what the loop costs when it runs on your hardware)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://opencode.ai/docs/" rel="noopener noreferrer"&gt;opencode docs: Intro and prerequisites&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://opencode.ai/install" rel="noopener noreferrer"&gt;opencode install script&lt;/a&gt; (fetched 2026-10-02; &lt;code&gt;linux-arm64&lt;/code&gt; support at script lines 102-110)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://termux.dev" rel="noopener noreferrer"&gt;Termux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.termux.com/wiki/Proot-Distro" rel="noopener noreferrer"&gt;proot-distro (Termux wiki)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opencode.ai/docs/mcp-servers/" rel="noopener noreferrer"&gt;opencode MCP server docs&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If this saved you a laptop, tap the &lt;strong&gt;unicorn&lt;/strong&gt; below; one click, and it is the only metric Dev.to shows me. &lt;strong&gt;Follow &lt;a href="https://dev.to/theagentloop"&gt;The Agent Loop&lt;/a&gt;&lt;/strong&gt; for the rest of this series.&lt;/p&gt;

&lt;p&gt;Every post also lands in an inbox: &lt;a href="https://buttondown.com/theagentloop" rel="noopener noreferrer"&gt;subscribe by email&lt;/a&gt;, one email per post and nothing else.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What would you run first if your entire dev environment fit in your pocket?&lt;/strong&gt; A CI script, a bot, the blog itself? Reply below.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>android</category>
      <category>terminal</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Who eats the loss when your AI agent spends your money?</title>
      <dc:creator>The Agent Loop</dc:creator>
      <pubDate>Fri, 02 Oct 2026 03:51:07 +0000</pubDate>
      <link>https://dev.to/theagentloop/who-eats-the-loss-when-your-ai-agent-spends-your-money-433a</link>
      <guid>https://dev.to/theagentloop/who-eats-the-loss-when-your-ai-agent-spends-your-money-433a</guid>
      <description>&lt;p&gt;Drafted with AI help, human-reviewed by The Agent Loop.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;How the rails read as of October 2026. Not legal advice; your issuer agreement and your jurisdiction beat anything here.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When your AI agent buys something with your card details, the loss lands on you.&lt;/strong&gt; Every liability rule on these rails turns on a single question: was the transaction &lt;em&gt;unauthorized&lt;/em&gt;? Hand an agent your credentials and, by the text of those rules, the transaction was authorized. That is the whole trick.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The five-line version:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;US Regulation E excludes transfers by anyone you "furnished the access device", and the official commentary says you are &lt;strong&gt;fully liable even when they exceed the authority you gave&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Visa's zero-liability policy "solely covers unauthorized payments". A purchase your agent made with your blessing isn't one.&lt;/li&gt;
&lt;li&gt;We could find &lt;strong&gt;no rule, issuer policy, or court decision that treats "an AI did it" as a defense&lt;/strong&gt; (checked October 2026). The industry calls it an open question.&lt;/li&gt;
&lt;li&gt;The one bound that works today is set before the damage: hard limits on a pre-funded card.&lt;/li&gt;
&lt;li&gt;The lever during the damage: tell the bank that person's transfers are no longer authorized, and the protections re-engage.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The three ceilings
&lt;/h2&gt;

&lt;p&gt;Two readers of our bank-accounts post drew the frame I wish I had used. Ownership is one ceiling (can an agent &lt;em&gt;hold&lt;/em&gt; the account). Authentication is a lower one (did &lt;em&gt;this&lt;/em&gt; transaction get strong customer authentication). The third ceiling is the one this post is about: &lt;strong&gt;liability, who eats the loss when it goes wrong&lt;/strong&gt; (&lt;a href="https://dev.to/theagentloop/can-an-ai-agent-have-a-bank-account-in-2026-5h7m#3fj85"&gt;mickyarun's comment&lt;/a&gt;). Ownership debates take years. A disputed charge lands this month.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the rules actually say
&lt;/h2&gt;

&lt;p&gt;Regulation E defines an "unauthorized electronic fund transfer" as one initiated by a person other than you "without actual authority to initiate the transfer" (&lt;a href="https://www.consumerfinance.gov/rules-policy/regulations/1005/2/" rel="noopener noreferrer"&gt;12 CFR 1005.2(m)&lt;/a&gt;). The same paragraph excludes transfers initiated "by a person who was furnished the access device to the consumer's account by the consumer". You gave your agent the card. It is that person.&lt;/p&gt;

&lt;p&gt;The official staff commentary closes the escape hatch: if you grant authority to a person, "such as a family member or co-worker", and they &lt;strong&gt;exceed the authority given&lt;/strong&gt;, "the consumer is fully liable for the transfers" until you tell the bank otherwise (Official Staff Commentary 2(m)-2, via &lt;a href="https://www.consumerfinance.gov/compliance/compliance-resources/deposit-accounts-resources/electronic-fund-transfers/electronic-fund-transfers-faqs/" rel="noopener noreferrer"&gt;CFPB's EFT FAQs&lt;/a&gt;). Banking-industry commentary puts it as a sandwich-versus-Tesla story: tell someone to buy a sandwich, they buy a Tesla, that's not a bank problem (&lt;a href="https://www.backbase.com/blog/ai-agent-payment-liability" rel="noopener noreferrer"&gt;Backbase, 2026&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The caps in &lt;a href="https://www.consumerfinance.gov/rules-policy/regulations/1005/6/" rel="noopener noreferrer"&gt;1005.6&lt;/a&gt; ($50 in two days, $500 within sixty, unlimited after) only attach to &lt;em&gt;unauthorized&lt;/em&gt; transfers. Your agent's purchase never enters that machinery.&lt;/p&gt;

&lt;p&gt;Visa's side matches: the fraud-risk FAQ says zero-liability "solely covers unauthorized payments", excludes transactions "initiated by the consumer", and doesn't apply to certain commercial cards (&lt;a href="https://corporate.visa.com/content/dam/VCOM/global/products/documents/visa-direct-fraud-risk-faqs.pdf" rel="noopener noreferrer"&gt;Visa Direct Fraud Risk FAQ&lt;/a&gt;). Issuer cardholder agreements go further and may exclude any transaction made "by a person authorized to transact business on the account" or one that "exceeds the authority given by the account owner" (&lt;a href="https://www.cuofco.org/support/visar-zero-liability-benefit" rel="noopener noreferrer"&gt;example issuer terms&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The EU has the same shape from a different direction. PSD2 Article 74 puts unauthorised-transaction losses on the payer up to €50, and removes even that when strong customer authentication was not used (&lt;a href="https://www.eba.europa.eu/regulation-and-policy/single-rulebook/interactive-single-rulebook/16228" rel="noopener noreferrer"&gt;EBA, Article 74&lt;/a&gt;). But the shield is built for &lt;em&gt;stolen instruments&lt;/em&gt;, not delegated ones. Whether an AI agent you authorized counts as an authorized delegate remains an open question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who eats it: four setups
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;Who eats the bad purchase&lt;/th&gt;
&lt;th&gt;The bound&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Personal card, agent has your credentials&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;You&lt;/strong&gt;, fully&lt;/td&gt;
&lt;td&gt;Whatever the agent can reach&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same, after you revoke with the bank&lt;/td&gt;
&lt;td&gt;Back into the normal dispute process&lt;/td&gt;
&lt;td&gt;Regulation E caps re-attach&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pre-funded card, hard daily limit&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;You&lt;/strong&gt;, up to the limit&lt;/td&gt;
&lt;td&gt;The limit, per day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Registered agent on Amex's network&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Amex&lt;/strong&gt;, per its published commitment&lt;/td&gt;
&lt;td&gt;Their terms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Rows three and four are the whole policy debate compressed. &lt;code&gt;hannune&lt;/code&gt;, the second commenter, wrote what I now consider the design rule: a pre-funded card with a hard daily limit "makes the card the unit of audit instead of the agent" (&lt;a href="https://dev.to/theagentloop/can-an-ai-agent-have-a-bank-account-in-2026-5h7m#3fjb6"&gt;thread&lt;/a&gt;). Open-ended liability plus an autonomous spender is the default row, and the one nobody should be in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is actually moving in 2026
&lt;/h2&gt;

&lt;p&gt;Honesty requires the parts that cut against us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Merchants got a new weapon first.&lt;/strong&gt; Visa's compelling-evidence 3.0 rule (April 2023) invalidates a fraud dispute when evidence shows the cardholder &lt;em&gt;or an authorized person&lt;/em&gt; participated in the transaction. "My agent did it" can therefore lose at the merchant fight-back stage too, on the device, IP, and login history the agent itself generated (&lt;a href="https://usa.visa.com/content/dam/VCOM/regional/na/us/support-legal/documents/evolution-of-compelling-evidence-merchant-faqs-mar2023.pdf" rel="noopener noreferrer"&gt;Visa merchant FAQ&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The networks shipped the front end before the back end.&lt;/strong&gt; Visa expanded its Agentic Ready program globally in late April 2026; Mastercard and Santander ran Europe's first live regulated agent payment in March 2026; Amex published an agentic commerce kit and a commitment to cover erroneous purchases by registered agents (&lt;a href="https://www.finopotamus.com/post/chargebacks911-warns-ai-agents-are-creating-a-new-era-of-dispute-risk-for-merchants-and-banks" rel="noopener noreferrer"&gt;Chargebacks911 via Finopotamus, May 2026&lt;/a&gt;). The same piece warns the dispute infrastructure behind those launches "remains almost entirely unaddressed", against a Mastercard forecast: chargebacks growing 24% to 324 million a year by 2028, before agent impact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The protocol layer is where the fix lives.&lt;/strong&gt; Visa's Trusted Agent Protocol, Mastercard Agent Pay, and Google's AP2 all aim at the real problem: cryptographically proving &lt;em&gt;what&lt;/em&gt; the consumer authorized the agent to do at the moment of delegation, not reconstructed after a dispute (&lt;a href="https://www.chargeflow.io/blog/ai-agent-chargeback-liability" rel="noopener noreferrer"&gt;Chargeflow, July 2026&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regulators stepped aside on purpose.&lt;/strong&gt; The Fed, OCC, and FDIC's SR 26-2 (April 2026) explicitly placed generative and agentic AI outside its updated model-risk guidance as "novel and rapidly evolving". No country has agentic-commerce liability law; PSD3 is still being negotiated (&lt;a href="https://www.chargeflow.io/blog/ai-agent-chargeback-liability" rel="noopener noreferrer"&gt;Chargeflow&lt;/a&gt;, &lt;a href="https://www.centschat.com/blog/agentic-payments-are-coming-accountability-is-not-optional" rel="noopener noreferrer"&gt;CentsChat&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My own limit, checked twice while writing: I looked for one published rule, decision, or issuer policy that treats an agent as a special case, and found none. If you know one, the comments are for exactly that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three questions before you hand over a card
&lt;/h2&gt;

&lt;p&gt;From mickyarun's test, made operational:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Who is the accountable party on this rail&lt;/strong&gt;, and does zero-liability even apply to this card type (commercial cards are the known carve-out)?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What reverses this transaction, on what clock, and whose logs count as evidence&lt;/strong&gt;, mine or only the bank's?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What is the worst day?&lt;/strong&gt; If the answer is open-ended, the daily limit comes before the agent, not after.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; liability is the ceiling that falls first. Until a rule says otherwise, on the payment rails your agent is you: it spends under your authority, it loses under your name. Keep the bound small, know your revocation line, and ask the three questions first.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is my AI agent's purchase "unauthorized" under Regulation E?&lt;/strong&gt;&lt;br&gt;
Almost certainly not, if you gave the agent your credentials. The definition excludes transfers by a person you furnished with the access device; the staff commentary holds you fully liable even when they exceed the authority you granted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Visa zero-liability save me from my agent's mistake?&lt;/strong&gt;&lt;br&gt;
No. The policy covers unauthorized payments only. Purchases you initiated or delegated don't qualify, and issuer terms may add explicit carve-outs for authorized persons and commercial cards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the AI company liable instead?&lt;/strong&gt;&lt;br&gt;
Not under anything we could source as of October 2026. Industry reviews call the question open, and Amex's registered-agent commitment is the one published exception that shifts loss from you to a network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should I do before giving an agent spending access?&lt;/strong&gt;&lt;br&gt;
Run the three questions above, put the agent on a pre-funded card with a hard daily limit, and write down the revocation path (who to call to make their transfers unauthorized again) &lt;em&gt;before&lt;/em&gt; the first transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related on The Agent Loop
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/theagentloop/can-an-ai-agent-have-a-bank-account-in-2026-5h7m"&gt;Can an AI agent have a bank account in 2026?&lt;/a&gt; (the ownership ceiling; source of both comments)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/theagentloop/where-does-the-budget-check-go-1bj3"&gt;Where does the budget check go?&lt;/a&gt; (loss &lt;em&gt;prevention&lt;/em&gt;, this post is loss &lt;em&gt;allocation&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/theagentloop/what-one-agent-run-actually-costs-28i8"&gt;What one agent run actually costs&lt;/a&gt; (the cost side of the same ledger)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://www.consumerfinance.gov/rules-policy/regulations/1005/2/" rel="noopener noreferrer"&gt;12 CFR 1005.2: Definitions, including "unauthorized electronic fund transfer"&lt;/a&gt; (CFPB)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.consumerfinance.gov/compliance/compliance-resources/deposit-accounts-resources/electronic-fund-transfers/electronic-fund-transfers-faqs/" rel="noopener noreferrer"&gt;CFPB Electronic Fund Transfers FAQs, incl. Staff Commentary 2(m)-2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.consumerfinance.gov/rules-policy/regulations/1005/6/" rel="noopener noreferrer"&gt;12 CFR 1005.6: Liability of consumer for unauthorized transfers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://corporate.visa.com/content/dam/VCOM/global/products/documents/visa-direct-fraud-risk-faqs.pdf" rel="noopener noreferrer"&gt;Visa Direct Risk and Compliance FAQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://usa.visa.com/content/dam/VCOM/regional/na/us/support-legal/documents/evolution-of-compelling-evidence-merchant-faqs-mar2023.pdf" rel="noopener noreferrer"&gt;Visa Evolution of Compelling Evidence, Merchant FAQs (March 2023)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.eba.europa.eu/regulation-and-policy/single-rulebook/interactive-single-rulebook/16228" rel="noopener noreferrer"&gt;PSD2 Article 74, EBA Single Rulebook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cuofco.org/support/visar-zero-liability-benefit" rel="noopener noreferrer"&gt;Issuer zero-liability exclusion example, Credit Union of Colorado&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.backbase.com/blog/ai-agent-payment-liability" rel="noopener noreferrer"&gt;Who's liable when a customer's AI agent authorizes the wrong payment&lt;/a&gt; (Backbase)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.chargeflow.io/blog/ai-agent-chargeback-liability" rel="noopener noreferrer"&gt;AI Agent Chargeback Liability&lt;/a&gt; (Chargeflow, July 2026)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.finopotamus.com/post/chargebacks911-warns-ai-agents-are-creating-a-new-era-of-dispute-risk-for-merchants-and-banks" rel="noopener noreferrer"&gt;Chargebacks911 warns AI agents are creating a new era of dispute risk&lt;/a&gt; (Finopotamus, May 2026)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.centschat.com/blog/agentic-payments-are-coming-accountability-is-not-optional" rel="noopener noreferrer"&gt;Agentic payments are coming. Accountability is not optional.&lt;/a&gt; (CentsChat, June 2026)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/theagentloop/can-an-ai-agent-have-a-bank-account-in-2026-5h7m#3fj85"&gt;mickyarun, comment on Can an AI agent have a bank account in 2026?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/theagentloop/can-an-ai-agent-have-a-bank-account-in-2026-5h7m#3fjb6"&gt;hannune, same thread&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If this post changed how you read your own exposure, tap the &lt;strong&gt;unicorn&lt;/strong&gt; below; one click, and it is the only metric Dev.to shows me. &lt;strong&gt;Follow &lt;a href="https://dev.to/theagentloop"&gt;The Agent Loop&lt;/a&gt;&lt;/strong&gt; for the rest of this series on where agent money actually goes.&lt;/p&gt;

&lt;p&gt;Every post also lands in an inbox: &lt;a href="https://buttondown.com/theagentloop" rel="noopener noreferrer"&gt;subscribe by email&lt;/a&gt;, one email per post and nothing else.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Has your agent ever spent money you didn't expect?&lt;/strong&gt; What did the bank or card issuer say the moment you told them an agent did it? Reply below.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>aiagents</category>
      <category>fintech</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
