<?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: Suleiman Ribeiro</title>
    <description>The latest articles on DEV Community by Suleiman Ribeiro (@suleiman_ribeiro_372f2314).</description>
    <link>https://dev.to/suleiman_ribeiro_372f2314</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%2F4126665%2Fec72318a-c675-47c4-9936-0d3d954d254a.png</url>
      <title>DEV Community: Suleiman Ribeiro</title>
      <link>https://dev.to/suleiman_ribeiro_372f2314</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/suleiman_ribeiro_372f2314"/>
    <language>en</language>
    <item>
      <title>My context optimizer was what broke my prompt cache</title>
      <dc:creator>Suleiman Ribeiro</dc:creator>
      <pubDate>Tue, 15 Sep 2026 17:31:13 +0000</pubDate>
      <link>https://dev.to/suleiman_ribeiro_372f2314/my-context-optimizer-was-what-broke-my-prompt-cache-37h0</link>
      <guid>https://dev.to/suleiman_ribeiro_372f2314/my-context-optimizer-was-what-broke-my-prompt-cache-37h0</guid>
      <description>&lt;p&gt;I spent weeks chasing the wrong theory about why my agent was expensive.&lt;/p&gt;

&lt;p&gt;The theory was the obvious one: expensive context means large context. So I built what seemed like the right fix — a routine that re-scored the tool set on every turn and sent only the tools relevant to that particular message. Fewer tokens out, smaller bill.&lt;/p&gt;

&lt;p&gt;Here is what it actually did, measured in production on three trivial turns of the same conversation:&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;before&lt;/th&gt;
&lt;th&gt;after&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;tools, turns 1→3&lt;/td&gt;
&lt;td&gt;52 → 54 → 61&lt;/td&gt;
&lt;td&gt;59 · 59 · 59&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cache hit rate&lt;/td&gt;
&lt;td&gt;9.6%&lt;/td&gt;
&lt;td&gt;80–88%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cost per turn&lt;/td&gt;
&lt;td&gt;$0.0168&lt;/td&gt;
&lt;td&gt;$0.0036–0.0046&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same model. Same conversation. A 75% reduction on the cached turn, and I got it by &lt;em&gt;removing&lt;/em&gt; the optimization.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mechanism
&lt;/h2&gt;

&lt;p&gt;Provider prompt caching matches the prefix byte for byte, up to the first difference. Your tool schema is part of that prefix.&lt;/p&gt;

&lt;p&gt;My routine re-scored tools by embedding the user's message. Different message, different relevance ranking, different tool set: 52, then 54, then 61 tools in a single conversation. Three turns, three prefixes, three full-price cache writes.&lt;/p&gt;

&lt;p&gt;I was paying full input price on every turn in order to save the handful of tokens I had trimmed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The arithmetic that settles it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pruning 1,000 tokens saves roughly $0.0006.&lt;/li&gt;
&lt;li&gt;A cache hit saves roughly $0.014 on the same turn.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pruning is worth about 4% of what the cache is worth. And in my case, pruning was the thing destroying the cache. The optimization was negative-value by more than an order of magnitude, and it looked like good engineering the whole time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The root cause was worse than the symptom
&lt;/h2&gt;

&lt;p&gt;The tool-count ceiling was sized by a learned factor — a running average calibrated from real usage. That same factor also fed a budget guard.&lt;/p&gt;

&lt;p&gt;Those two consumers want opposite things. The budget guard wants &lt;strong&gt;accuracy&lt;/strong&gt;: it should track reality as closely as possible. The tool ceiling wants &lt;strong&gt;stability&lt;/strong&gt;: it should not move, because every move rewrites the prefix. One number cannot serve both. Worse, the learned factor was not persisted, so every deploy restarted the drift from scratch.&lt;/p&gt;

&lt;p&gt;The fix was to split it: a fixed factor sizes the ceiling, the learned factor stays in the budget guard only, and trivial turns use a stable tool set with no per-message embedding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is not on the anti-pattern lists
&lt;/h2&gt;

&lt;p&gt;Every guide to prompt caching lists the same culprits: a timestamp in the system prompt, user-specific content in the cached prefix, inconsistent whitespace, dumping raw files into context. All of those are carelessness. You read the list, you check your prompt, you move on.&lt;/p&gt;

&lt;p&gt;Mine passed every item on that list. The thing invalidating my prefix was a deliberate, well-engineered optimization whose entire purpose was to reduce cost. That is a different failure class, and it is harder to find, because you are not looking for a bug inside the code you wrote to fix the problem.&lt;/p&gt;

&lt;p&gt;There is prior work on the general shape of this. &lt;em&gt;Don't Break the Cache&lt;/em&gt; (arXiv 2601.06007, Lumer et al.) evaluated caching strategies across 500 agent sessions with 10,000-token system prompts and found 41–80% cost reduction from controlling the cache boundary, plus the counterintuitive result that naive full-context caching can increase latency. Their framing is about where to put the boundary. Mine is one layer up: what in your own system is moving the boundary without telling you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule I kept
&lt;/h2&gt;

&lt;p&gt;Before asking how much context you send, ask &lt;strong&gt;what changes between turns&lt;/strong&gt;. The system prompt, the tool schema and the tool order are a stable contract, not space to squeeze. Cost work starts by measuring cache hit rate, not size.&lt;/p&gt;

&lt;p&gt;And a corollary that generalizes past caching: when one value serves two consumers with opposite needs, it is two values. Ask each consumer whether it wants accuracy or stability. If they disagree, split it.&lt;/p&gt;

&lt;p&gt;This regression produces no error, never turns a test red, and is invisible in the UI. It shows up on the invoice, weeks later. So the check that catches it is not a behavioral test — it is a test that asserts the ceiling is a constant, and fails if anyone makes it learned again.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;These numbers come from a multi-tenant agent system I have been building solo since June, logged in a dated engineering diary. Every figure above is reproducible from a command recorded alongside it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
