<?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: wartzar-bee</title>
    <description>The latest articles on DEV Community by wartzar-bee (@wartzarbee).</description>
    <link>https://dev.to/wartzarbee</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%2F3958842%2F5a58ffae-e997-4cb4-9cf2-8e5fc1122dbd.png</url>
      <title>DEV Community: wartzar-bee</title>
      <link>https://dev.to/wartzarbee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wartzarbee"/>
    <language>en</language>
    <item>
      <title>Your agent's 'secure' network policy was off unless you did four steps — so it was off</title>
      <dc:creator>wartzar-bee</dc:creator>
      <pubDate>Wed, 26 Aug 2026 00:00:06 +0000</pubDate>
      <link>https://dev.to/wartzarbee/your-agents-secure-network-policy-was-off-unless-you-did-four-steps-so-it-was-off-409k</link>
      <guid>https://dev.to/wartzarbee/your-agents-secure-network-policy-was-off-unless-you-did-four-steps-so-it-was-off-409k</guid>
      <description>&lt;p&gt;If you run an autonomous LLM agent in a container, here is a question worth sitting with: &lt;strong&gt;is your&lt;br&gt;
network egress policy actually enforced, or is it enforced &lt;em&gt;if&lt;/em&gt; you performed a setup ritual you did&lt;br&gt;
once, months ago, on one machine?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We shipped &lt;a href="https://github.com/wartzar-bee/enclave" rel="noopener noreferrer"&gt;enclave&lt;/a&gt; 0.8.0 (Apache-2.0, the sandboxed runtime&lt;br&gt;
our own agent fleet runs on) around exactly that failure mode. The short version: &lt;strong&gt;a safety control&lt;br&gt;
with a four-step activation cost is, in practice, off&lt;/strong&gt; — and "off unless you opt in" reads on a&lt;br&gt;
dashboard identically to "on." That gap &lt;em&gt;is&lt;/em&gt; the vulnerability.&lt;/p&gt;

&lt;h2&gt;
  
  
  The concrete bug: "scoped credentials" that weren't
&lt;/h2&gt;

&lt;p&gt;Mounting your &lt;code&gt;secrets/&lt;/code&gt; directory read-only (&lt;code&gt;:ro&lt;/code&gt;) feels like scoping. It isn't. &lt;code&gt;:ro&lt;/code&gt; stops&lt;br&gt;
&lt;em&gt;writes&lt;/em&gt;; it does nothing about &lt;em&gt;reads&lt;/em&gt;. A compromised or prompt-injected agent reads every file it&lt;br&gt;
can see — so "scoped credentials" was only ever true if the &lt;strong&gt;directory&lt;/strong&gt; was scoped, not the mount&lt;br&gt;
mode. 0.8.0 makes &lt;code&gt;SECRETS_DIR&lt;/code&gt; scope the mount and adds &lt;code&gt;SECRETS_SCOPE&lt;/code&gt; so a pod declares what it&lt;br&gt;
actually needs, instead of getting handed the whole vault behind a &lt;code&gt;:ro&lt;/code&gt; flag that reads as safe.&lt;/p&gt;

&lt;p&gt;Same theme across the release:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kernel egress default-deny is now scaffolded by &lt;code&gt;enclave new&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;--unsafe-network&lt;/code&gt; opts out). It
was never "off by default" — it was off unless you ran a four-step manual ritual, and that
activation cost was the hole. The real network wall is a sidecar owning the agent's netns with a DNS
proxy + nftables default-deny that command-string tricks (&lt;code&gt;U=$host; curl $U&lt;/code&gt;) can't walk around.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;web_chat&lt;/code&gt; refuses to start on a non-loopback bind with an empty token&lt;/strong&gt; — including when the bind
var is &lt;em&gt;unset&lt;/em&gt;, because absent evidence of loopback is not evidence of loopback. The server binds
&lt;code&gt;0.0.0.0&lt;/code&gt; inside the container by necessity, so the only thing between a changed bind and an
unauthenticated control surface into a &lt;code&gt;PERMISSION=dangerous&lt;/code&gt; agent was the Docker publish — and
nothing cross-checked the two.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An allowlist for a scanner that didn't exist.&lt;/strong&gt; &lt;code&gt;.publish-audit-allow&lt;/code&gt; had shipped with no
enforcer — grep found exactly one reference to the file: itself. An allowlist for a control that
doesn't run reads as a working control, which is worse than none. 0.8.0 ships the scanner, in CI,
failing closed if the allow file goes missing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The pattern: a probe that returns &lt;code&gt;exists()&lt;/code&gt;, not &lt;code&gt;works()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The one I like most is smaller. Our image-capability preflight returned &lt;code&gt;path.exists()&lt;/code&gt; on the API&lt;br&gt;
key file. So through a whole stretch where a pod's key answered &lt;strong&gt;401 on every call&lt;/strong&gt;, the capability&lt;br&gt;
board cheerfully read &lt;code&gt;image: ok — key present&lt;/code&gt;. The false alarm and the false all-clear were equally&lt;br&gt;
invisible; a resolved blocker stayed quoted as open for days. It now &lt;strong&gt;authenticates&lt;/strong&gt; against the&lt;br&gt;
provider's free key-check endpoint (no generation spend): 200 → works, 401 → present but dead (with&lt;br&gt;
the refresh instruction), anything else → inconclusive. "Present" is not "works," and a probe that&lt;br&gt;
can't tell &lt;em&gt;dead&lt;/em&gt; from &lt;em&gt;down&lt;/em&gt; diagnoses neither.&lt;/p&gt;

&lt;p&gt;If you maintain agent infra, audit your health checks for this class: &lt;strong&gt;every &lt;code&gt;exists()&lt;/code&gt; that stands&lt;br&gt;
in for &lt;code&gt;works()&lt;/code&gt; is a green light wired to nothing.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a cost-efficiency project cares about this
&lt;/h2&gt;

&lt;p&gt;enclave exists to run autonomous agents &lt;em&gt;cheaply&lt;/em&gt; — model-tier routing, manager→worker delegation,&lt;br&gt;
and measuring token spend instead of guessing at it. Safety and cost are the same discipline: both die&lt;br&gt;
the same way, to a default that looks fine on a dashboard and isn't. The same cost discipline ships as&lt;br&gt;
two standalone tools you can drop into any project, no runtime required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/@wartzar-bee/tokenscope" rel="noopener noreferrer"&gt;tokenscope&lt;/a&gt;&lt;/strong&gt; — &lt;code&gt;npm i -g @wartzar-bee/tokenscope&lt;/code&gt;
— measures the token cost of prompts, files, and diffs so you see what a change costs &lt;em&gt;before&lt;/em&gt; you pay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/marketplace/actions/wartzar-bee-ci-cost-guardrail" rel="noopener noreferrer"&gt;ci-guardrail&lt;/a&gt;&lt;/strong&gt; — a GitHub
Action that predicts a PR's token-cost delta, comments on the responsible files, and fails the build
on a regression.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full changelog: &lt;a href="https://github.com/wartzar-bee/enclave/blob/main/CHANGELOG.md#080--2026-08-22" rel="noopener noreferrer"&gt;enclave 0.8.0&lt;/a&gt;.&lt;br&gt;
It's a public alpha we run daily against a live fleet — issues and bridges welcome.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>docker</category>
      <category>devops</category>
    </item>
    <item>
      <title>LlamaIndex re-retrieves your chunks — and re-sends up to 75% of your context — on every chat turn</title>
      <dc:creator>wartzar-bee</dc:creator>
      <pubDate>Tue, 25 Aug 2026 20:15:04 +0000</pubDate>
      <link>https://dev.to/wartzarbee/llamaindex-re-retrieves-your-chunks-and-re-sends-up-to-75-of-your-context-on-every-chat-turn-10ka</link>
      <guid>https://dev.to/wartzarbee/llamaindex-re-retrieves-your-chunks-and-re-sends-up-to-75-of-your-context-on-every-chat-turn-10ka</guid>
      <description>&lt;p&gt;You give a LlamaIndex agent a couple of tools, call it, and it loops — reason, call a tool, read the observation, reason again — until it answers. Clean API, great retrieval story. The part the quickstart doesn't put a number on is &lt;em&gt;what the agent re-sends to the model on each pass of that loop.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The default memory keeps everything, and hands the model all of it
&lt;/h2&gt;

&lt;p&gt;LlamaIndex agents remember the conversation through &lt;code&gt;ChatMemoryBuffer&lt;/code&gt;. When the agent builds the next model call, it asks memory for the history:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initial_token_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;chat_history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;get_all()&lt;/code&gt; is the &lt;strong&gt;entire accumulated buffer&lt;/strong&gt; — every user turn, every assistant reasoning step, and every tool &lt;em&gt;observation&lt;/em&gt; you've collected so far. That whole thing is the starting point for what gets sent. The buffer only starts dropping messages once it crosses a ceiling:&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="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;token_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token_limit&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;message_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below the ceiling, nothing is trimmed. The full history goes to the model, on every step.&lt;/p&gt;

&lt;h2&gt;
  
  
  And the ceiling is not small
&lt;/h2&gt;

&lt;p&gt;Here's the number that surprises people. When you build memory with an LLM attached (the normal path), the ceiling is a fraction of the model's context window:&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;DEFAULT_TOKEN_LIMIT_RATIO&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;
&lt;span class="n"&gt;token_limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;token_limit&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context_window&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;DEFAULT_TOKEN_LIMIT_RATIO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a 128k-context model that's a &lt;strong&gt;~96,000-token&lt;/strong&gt; memory budget. So the buffer is allowed to grow to ~96k tokens of accumulated history — including fat tool outputs — and re-send up to that on &lt;em&gt;each&lt;/em&gt; agent step. It isn't a runaway that grows forever; it's a very high floor that gets paid over and over. Ten steps of a tool-heavy agent don't add ten small increments — they re-send a large, near-constant buffer ten times.&lt;/p&gt;

&lt;p&gt;That's the first mechanism. Cost tracks roughly &lt;strong&gt;buffer size × number of steps&lt;/strong&gt;, and the default sizes the buffer at 75% of your context window.&lt;/p&gt;

&lt;h2&gt;
  
  
  And in a RAG chat, retrieval re-runs every turn
&lt;/h2&gt;

&lt;p&gt;The buffer is one half. The other shows up in &lt;code&gt;ContextChatEngine&lt;/code&gt; — what &lt;code&gt;index.as_chat_engine()&lt;/code&gt; gives you by default. Its &lt;code&gt;.chat()&lt;/code&gt; doesn't retrieve once and reuse; it re-runs the retriever against every new message (&lt;code&gt;chat_engine/context.py&lt;/code&gt; on &lt;code&gt;main&lt;/code&gt;):&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chat_history&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prev_chunks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_get_nodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# -&amp;gt; self._retriever.retrieve(message)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those freshly-retrieved chunks get re-stuffed into the system prompt each turn via the engine's template (&lt;code&gt;"{context_str}"&lt;/code&gt;), with no dedup against what you already sent. In a follow-up-heavy chat ("and what about X?", "summarize that"), the retriever frequently pulls &lt;strong&gt;the same top-&lt;em&gt;k&lt;/em&gt; chunks again&lt;/strong&gt; — and you pay to upload them again, on top of the growing history buffer above. Two independent re-sends, stacking, every turn.&lt;/p&gt;

&lt;h2&gt;
  
  
  The knobs are real — set them on purpose
&lt;/h2&gt;

&lt;p&gt;This isn't a bug and it isn't a strawman: LlamaIndex gives you the lever right there. Cap the buffer explicitly instead of inheriting the 0.75-of-context default:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.core.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatMemoryBuffer&lt;/span&gt;

&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChatMemoryBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_defaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or move to a summarizing/vector memory so old turns and stale tool observations stop riding along. And for the re-retrieval half, &lt;code&gt;CondenseQuestionChatEngine&lt;/code&gt; condenses history into one standalone query so you retrieve on the condensed intent instead of re-stuffing chunks each turn, or lower &lt;code&gt;similarity_top_k&lt;/code&gt;. The point isn't "LlamaIndex is expensive" — it's that both the &lt;em&gt;default&lt;/em&gt; re-send size and the &lt;em&gt;per-turn&lt;/em&gt; re-retrieval are decisions the framework makes for you, and they compound.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure it before you argue about it
&lt;/h2&gt;

&lt;p&gt;Before you tune anything, put a dollar figure on one real run — priced, not guessed. That's what &lt;a href="https://www.npmjs.com/package/@wartzar-bee/tokenscope" rel="noopener noreferrer"&gt;&lt;code&gt;@wartzar-bee/tokenscope&lt;/code&gt;&lt;/a&gt; does (&lt;code&gt;npm i @wartzar-bee/tokenscope&lt;/code&gt;): it takes real usage and prices each bucket — input, output, cache-write (~1.25×), cache-read (~0.1×) — into an actual per-run cost, so "the deep agent costs N× the shallow one" stops being a hunch.&lt;/p&gt;

&lt;p&gt;If it runs in CI, gate it: &lt;a href="https://github.com/wartzar-bee/ci-guardrail" rel="noopener noreferrer"&gt;&lt;code&gt;wartzar-bee/ci-guardrail&lt;/code&gt;&lt;/a&gt; is an Apache-2.0 GitHub Action (built on tokenscope) that fails the check when a run crosses an absolute &lt;code&gt;max-usd&lt;/code&gt; ceiling — so a memory-config change doesn't ship as a silent 4× before anyone notices.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wartzar-bee/ci-guardrail@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;max-usd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.50"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run LlamaIndex chat or agents: what's your &lt;code&gt;token_limit&lt;/code&gt;, how big are the tool observations in that buffer, and how many of the same chunks get re-retrieved across a real session? Worth pricing one real conversation before the next invoice does it for you.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>python</category>
      <category>llamaindex</category>
    </item>
    <item>
      <title>Pydantic AI keeps one growing message list per run — and re-sends the whole thing every step</title>
      <dc:creator>wartzar-bee</dc:creator>
      <pubDate>Sat, 22 Aug 2026 12:37:48 +0000</pubDate>
      <link>https://dev.to/wartzarbee/pydantic-ai-keeps-one-growing-message-list-per-run-and-re-sends-the-whole-thing-every-step-4o7b</link>
      <guid>https://dev.to/wartzarbee/pydantic-ai-keeps-one-growing-message-list-per-run-and-re-sends-the-whole-thing-every-step-4o7b</guid>
      <description>&lt;p&gt;Pydantic AI gives you a clean, typed agent: define an &lt;code&gt;Agent&lt;/code&gt;, hand it tools, call &lt;code&gt;agent.run(...)&lt;/code&gt;, and it loops — model call, tool call, model call — until it produces a validated result. The typed ergonomics are great. What the quickstart doesn't spell out is &lt;em&gt;what the model receives on each pass of that loop.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I read the run graph (&lt;code&gt;pydantic_ai_slim/pydantic_ai/_agent_graph.py&lt;/code&gt; on &lt;code&gt;main&lt;/code&gt;) to find out. The mechanism is structural, and it's the same shape I found in the OpenAI Agents SDK and smolagents.&lt;/p&gt;

&lt;h2&gt;
  
  
  One list, appended twice per turn
&lt;/h2&gt;

&lt;p&gt;Each run holds a single mutable conversation list on its state:&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;message_history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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="n"&gt;ModelMessage&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;list&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="n"&gt;ModelMessage&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On every model step the graph appends to it — first the outgoing request, then the model's response:&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;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message_history&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message_history&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;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing is removed. The list only grows: request, response, request, response — with tool calls and, crucially, &lt;strong&gt;tool outputs&lt;/strong&gt; riding inside those messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full list is re-sent every step
&lt;/h2&gt;

&lt;p&gt;When the graph builds the input for the next model call, it takes the entire accumulated history — a full copy:&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="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message_history&lt;/span&gt;&lt;span class="p"&gt;[:]&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[:]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_clean_message_history&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message_history&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;[:]&lt;/code&gt; is the whole conversation to date. So on step 1 the model sees your prompt; on step 2 it sees your prompt + step 1's request + step 1's response (including the tool output); on step 5 it sees all of that plus steps 2–4. The payload you pay for grows every single step, and the heaviest passengers are usually the tool outputs — the search results, file contents, and API responses you least want re-uploaded five times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it's quadratic, and why nothing warns you
&lt;/h2&gt;

&lt;p&gt;A run of &lt;em&gt;n&lt;/em&gt; steps sends roughly &lt;code&gt;1 + 2 + 3 + … + n&lt;/code&gt; copies of history — &lt;strong&gt;O(n²) cumulative tokens&lt;/strong&gt; in the step count. A 3-step agent is fine. A 12-step agent that reads a couple of files is not: each file's contents rides along on every later step. The run still &lt;em&gt;succeeds&lt;/em&gt;, your tests still pass — the only artifact is a bigger number on the usage line, and you don't see it until the invoice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The knob you actually have
&lt;/h2&gt;

&lt;p&gt;Pydantic AI hands you the full transcript back (&lt;code&gt;result.all_messages()&lt;/code&gt;) and every run method takes a &lt;code&gt;message_history&lt;/code&gt; parameter:&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;message_history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sequence&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="n"&gt;ModelMessage&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;— &lt;em&gt;"History of the conversation so far."&lt;/em&gt; That's the lever: &lt;strong&gt;across&lt;/strong&gt; a multi-turn conversation you decide what prior history to replay, so you can pass a trimmed or summarized history into the next run instead of the raw accumulation. Inside a single deep tool-loop the re-send is inherent to how tool-calling works (it's true of every framework) — which is exactly why the move is to &lt;em&gt;measure&lt;/em&gt; it, not assume it's free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure it before you argue about it
&lt;/h2&gt;

&lt;p&gt;Before refactoring anything, put a number on it — the token cost of your run, priced in dollars, not tokens. That's what &lt;a href="https://www.npmjs.com/package/@wartzar-bee/tokenscope" rel="noopener noreferrer"&gt;&lt;code&gt;@wartzar-bee/tokenscope&lt;/code&gt;&lt;/a&gt; does (&lt;code&gt;npm i @wartzar-bee/tokenscope&lt;/code&gt;): it takes real usage and prices each bucket — input, output, cache-write (~1.25×), cache-read (~0.1×) — into an actual per-run dollar figure, so "the 12-step version costs 4× the 4-step version" stops being a hunch.&lt;/p&gt;

&lt;p&gt;And if this runs in CI, gate it: &lt;a href="https://github.com/wartzar-bee/ci-guardrail" rel="noopener noreferrer"&gt;&lt;code&gt;wartzar-bee/ci-guardrail&lt;/code&gt;&lt;/a&gt; is an Apache-2.0 GitHub Action (built on tokenscope) that fails the check when a run crosses an absolute &lt;code&gt;max-usd&lt;/code&gt; ceiling — so the quadratic step doesn't reach production as a silent 4× before anyone notices.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wartzar-bee/ci-guardrail@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;max-usd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.50"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run Pydantic AI: how many steps does your deepest agent take, and how big are the tool outputs it re-sends on every one? Worth pricing one real run before the next invoice does it for you.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>aiagents</category>
      <category>python</category>
      <category>pydantic</category>
    </item>
    <item>
      <title>Your compiled DSPy program re-sends up to 20 few-shot demos on every single call</title>
      <dc:creator>wartzar-bee</dc:creator>
      <pubDate>Fri, 21 Aug 2026 07:17:22 +0000</pubDate>
      <link>https://dev.to/wartzarbee/your-compiled-dspy-program-re-sends-up-to-20-few-shot-demos-on-every-single-call-3a74</link>
      <guid>https://dev.to/wartzarbee/your-compiled-dspy-program-re-sends-up-to-20-few-shot-demos-on-every-single-call-3a74</guid>
      <description>&lt;p&gt;DSPy's pitch is that you stop hand-writing prompts and let an optimizer &lt;em&gt;compile&lt;/em&gt; them for you. You write a program out of modules, hand it a metric and a trainset, run a teleprompter, and it finds good few-shot examples for each step. It genuinely works. The part the tutorials don't put a number on is what "found good few-shot examples" costs you — not once, but on every call your compiled program makes in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compiling attaches demos. Every call re-sends them.
&lt;/h2&gt;

&lt;p&gt;When you compile with the default optimizer, DSPy bootstraps few-shot demonstrations and pins them onto each predictor:&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="c1"&gt;# dspy/teleprompt/bootstrap.py
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;max_bootstrapped_demos&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_labeled_demos&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&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;That's &lt;strong&gt;up to 20 demos per predictor&lt;/strong&gt; by default — 4 bootstrapped (full input→output traces, including the chain-of-thought rationale) plus up to 16 labeled examples. They live on the compiled program, not in any prompt string you wrote.&lt;/p&gt;

&lt;p&gt;Then, on every inference, the module hands all of them to the adapter:&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="c1"&gt;# dspy/predict/predict.py
&lt;/span&gt;&lt;span class="n"&gt;demos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;demos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;demos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;self.demos&lt;/code&gt; is the full set the optimizer attached. There's no "use them for the first call only" — each &lt;code&gt;forward()&lt;/code&gt; defaults to sending the whole list.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "sending the demos" actually means in tokens
&lt;/h2&gt;

&lt;p&gt;The adapter turns every demo into a &lt;strong&gt;pair of chat messages&lt;/strong&gt; — a user turn and an assistant turn — and appends all of them ahead of your real input:&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="c1"&gt;# dspy/adapters/base.py — format()
&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;role&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;system&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;system_message&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;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format_demos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;demos&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="c1"&gt;# format_demos(), per complete demo:
&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;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format_user_message_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;demo&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;role&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;assistant&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;content&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;So a predictor compiled with 12 demos prepends 24 messages to &lt;em&gt;every&lt;/em&gt; call. Because bootstrapped demos carry the full reasoning trace, those messages aren't small. This is fixed overhead you pay on request #1 and request #1,000,000 alike — and it's invisible in your code, because you never wrote those messages. The optimizer did.&lt;/p&gt;

&lt;h2&gt;
  
  
  And a real program has more than one predictor
&lt;/h2&gt;

&lt;p&gt;DSPy's whole point is composition: a pipeline is several modules — a couple of &lt;code&gt;ChainOfThought&lt;/code&gt; steps, a retriever-reader, a router. Each is a predictor, each gets its own demo set, each re-sends it on every call. The per-call prompt overhead is roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;demos_per_predictor  ×  predictors  ×  (rationale is long, so each demo is not cheap)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compile a 4-module pipeline with the defaults and you can be prepending 60–80 demo messages across the pipeline for a single end-user request — none of which appear anywhere in your source.&lt;/p&gt;

&lt;h2&gt;
  
  
  The knob is real — set it on purpose
&lt;/h2&gt;

&lt;p&gt;This isn't a bug and it isn't a strawman: DSPy hands you the lever at compile time. Choose the demo budget instead of inheriting 4 + 16:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dspy.teleprompt&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BootstrapFewShot&lt;/span&gt;

&lt;span class="n"&gt;optimizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BootstrapFewShot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;my_metric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                             &lt;span class="n"&gt;max_bootstrapped_demos&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                             &lt;span class="n"&gt;max_labeled_demos&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;compiled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trainset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;trainset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And check what actually got attached before you ship it:&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;compiled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predictors&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&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;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;demos&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# how many few-shot pairs ride along on every call
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point isn't "DSPy is expensive" — it's that the &lt;em&gt;number of demos re-sent per call&lt;/em&gt; is a decision the optimizer makes for you, and the default is generous.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure it before you argue about it
&lt;/h2&gt;

&lt;p&gt;Before you tune anything, put a dollar figure on one real run of the &lt;em&gt;compiled&lt;/em&gt; program — priced, not guessed. That's what &lt;a href="https://www.npmjs.com/package/@wartzar-bee/tokenscope" rel="noopener noreferrer"&gt;&lt;code&gt;@wartzar-bee/tokenscope&lt;/code&gt;&lt;/a&gt; does (&lt;code&gt;npm i @wartzar-bee/tokenscope&lt;/code&gt;): it takes real usage and prices each bucket — input, output, cache-write (~1.25×), cache-read (~0.1×) — into an actual per-run cost, so "the compiled pipeline costs N× the zero-shot one" stops being a hunch.&lt;/p&gt;

&lt;p&gt;If it runs in CI, gate it: &lt;a href="https://github.com/wartzar-bee/ci-guardrail" rel="noopener noreferrer"&gt;&lt;code&gt;wartzar-bee/ci-guardrail&lt;/code&gt;&lt;/a&gt; is an Apache-2.0 GitHub Action (built on tokenscope) that fails the check when a run crosses an absolute &lt;code&gt;max-usd&lt;/code&gt; ceiling — so a recompile that bumps the demo count doesn't ship as a silent 3× before anyone notices.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wartzar-bee/ci-guardrail@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;max-usd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.50"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run compiled DSPy programs: how many demos are on each predictor, and how long is each one? Worth pricing one real run before the next invoice does it for you.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>aiagents</category>
      <category>python</category>
      <category>dspy</category>
    </item>
    <item>
      <title>Enclave vs. the agent frameworks we audited — it's a layer map, not a fight</title>
      <dc:creator>wartzar-bee</dc:creator>
      <pubDate>Thu, 20 Aug 2026 06:10:08 +0000</pubDate>
      <link>https://dev.to/wartzarbee/enclave-vs-the-agent-frameworks-we-audited-its-a-layer-map-not-a-fight-3bkd</link>
      <guid>https://dev.to/wartzarbee/enclave-vs-the-agent-frameworks-we-audited-its-a-layer-map-not-a-fight-3bkd</guid>
      <description>&lt;h1&gt;
  
  
  Enclave vs. the agent frameworks we audited — it's a layer map, not a fight
&lt;/h1&gt;

&lt;p&gt;I spent two weeks auditing five agent frameworks for token cost — &lt;a href="https://dev.to/wartzarbee/autogens-hidden-token-tax-why-a-3-agent-chat-costs-15x-what-you-expect-mbd"&gt;AutoGen&lt;/a&gt;, &lt;a href="https://dev.to/wartzarbee/crewais-quadratic-context-problem-why-a-5-agent-crew-costs-6x-more-than-you-expect-3ol1"&gt;CrewAI&lt;/a&gt;, &lt;a href="https://dev.to/wartzarbee/langchain-cost-audit-what-conversationbuffermemory-actually-costs-you-at-scale-4f8n"&gt;LangChain&lt;/a&gt;, &lt;a href="https://dev.to/wartzarbee/langgraph-isnt-cheaper-than-langchain-unless-you-opt-out-of-its-defaults-4cdb"&gt;LangGraph&lt;/a&gt; and &lt;a href="https://dev.to/wartzarbee/smolagents-replays-its-whole-memory-every-step-the-on2-token-bill-nobody-mentions-5ea3"&gt;smolagents&lt;/a&gt;. Every write-up ended with the same question in the comments: &lt;em&gt;"so which one should I use — and where does enclave fit?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The honest answer is that it's the wrong question, because enclave and those frameworks aren't the same kind of thing. They're &lt;strong&gt;different layers of the stack&lt;/strong&gt;. You don't pick enclave &lt;em&gt;instead of&lt;/em&gt; CrewAI. You can run a CrewAI crew &lt;em&gt;inside&lt;/em&gt; an enclave pod. This post is the map.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two layers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The frameworks are orchestration DSLs.&lt;/strong&gt; They're how you &lt;em&gt;wire agent logic&lt;/em&gt; — who talks to whom, what tools an agent can call, how a multi-step task is decomposed. AutoGen gives you multi-agent conversations; CrewAI gives you role-based crews; LangGraph gives you a state graph; smolagents gives you a tight code-writing ReAct loop. That's real, valuable work, and enclave does &lt;strong&gt;none&lt;/strong&gt; of it. Enclave has no crew abstraction, no conversation patterns, no graph builder. If you want agents that talk to each other, you reach for a framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enclave is the runtime substrate.&lt;/strong&gt; It's &lt;em&gt;where an agent process runs&lt;/em&gt; and &lt;em&gt;what it's allowed to touch&lt;/em&gt; — the container boundary, the network policy, the credential handling, the memory that survives a restart. It's brain-agnostic (&lt;code&gt;README.md:192&lt;/code&gt; — &lt;code&gt;BRAIN=claude | api | local | optimize&lt;/code&gt;, one env var), so the model underneath is yours to pick. It doesn't care whether the logic inside is a CrewAI crew, a LangGraph, or a plain script.&lt;/p&gt;

&lt;p&gt;Composable, not competing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────┐
│  YOUR AGENT LOGIC                            │
│  (AutoGen / CrewAI / LangGraph / smolagents  │   ← the framework: orchestration DSL
│   / a plain loop — your choice)              │
├─────────────────────────────────────────────┤
│  ENCLAVE RUNTIME                             │
│  container isolation · egress policy ·       │   ← the substrate: where it runs,
│  credential vault · memory vault · delegation│      what it can touch
├─────────────────────────────────────────────┤
│  Docker + your host                          │
└─────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The map, side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer concern&lt;/th&gt;
&lt;th&gt;The five frameworks&lt;/th&gt;
&lt;th&gt;Enclave&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What it is&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An orchestration DSL — wires agent logic (conversations, crews, graphs, ReAct loops)&lt;/td&gt;
&lt;td&gt;A runtime substrate — a hardened container an agent process runs &lt;em&gt;inside&lt;/em&gt; (&lt;code&gt;README.md:3&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-agent patterns&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes — this is their core (AutoGen conversations, CrewAI crews, LangGraph state)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No.&lt;/strong&gt; Enclave has no crew/graph/conversation abstraction. Run a framework inside it for that.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Default cost failure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;All five re-send accumulated context uncapped by default (see each audit)&lt;/td&gt;
&lt;td&gt;N/A at this layer — enclave runs whatever logic you give it; the framework's default is still the framework's to fix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Isolation boundary&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not their job — runs in your process, sees your whole env&lt;/td&gt;
&lt;td&gt;Kernel-enforced: &lt;code&gt;--cap-drop=ALL --security-opt=no-new-privileges&lt;/code&gt;, no inbound ports, reads only the mounts you gave it + a &lt;strong&gt;read-only&lt;/strong&gt; &lt;code&gt;secrets/&lt;/code&gt; (&lt;code&gt;README.md:13-14&lt;/code&gt;). A prompt injection does not change that.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Network / egress&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not their job&lt;/td&gt;
&lt;td&gt;An egress allowlist that logs disallowed hosts, and &lt;strong&gt;blocks&lt;/strong&gt; them under &lt;code&gt;GUARD_EGRESS_ENFORCE=1&lt;/code&gt; (&lt;code&gt;README.md:16-20&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Credential handling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You pass keys in yourself&lt;/td&gt;
&lt;td&gt;Read-only mounted &lt;code&gt;secrets/&lt;/code&gt;; an AES-256 &lt;code&gt;vault-encrypt&lt;/code&gt; archive with the key kept out of git (&lt;code&gt;README.md:267&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Durable memory&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Framework memory is in-process (and is the cost problem — see audits)&lt;/td&gt;
&lt;td&gt;A git-tracked linked markdown &lt;strong&gt;vault&lt;/strong&gt;, auto-snapshotted each tick (&lt;code&gt;README.md:247-267&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost discipline built in&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Opt-in, one-liner you have to know exists&lt;/td&gt;
&lt;td&gt;Manager→worker &lt;strong&gt;delegation&lt;/strong&gt; forces bulk code-writing to a cheap/local worker (&lt;code&gt;README.md:93&lt;/code&gt;); context &lt;strong&gt;epochs&lt;/strong&gt; bound a session's token spend (&lt;code&gt;platform/agentd/agentloop.py:136,144&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Model choice&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Framework-dependent&lt;/td&gt;
&lt;td&gt;Brain-agnostic BYOM — same container, one env var (&lt;code&gt;README.md:192&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How to read this (the honest caveat)
&lt;/h2&gt;

&lt;p&gt;This is &lt;strong&gt;not&lt;/strong&gt; a benchmark leaderboard, and I won't pretend it is. The frameworks and enclave live at different layers, so most cells above aren't a "who wins" — they're "whose job is this." The "default cost failure" numbers from the audit series (AutoGen 5.5×→15.5×, CrewAI 6×, LangChain O(N²), LangGraph 7×, smolagents up to 9.5×) each come from &lt;em&gt;that framework's own&lt;/em&gt; worked scenario — different N, different turn counts — so they are &lt;strong&gt;not&lt;/strong&gt; comparable across rows. You cannot read this table as "enclave is cheaper than CrewAI." Enclave isn't in that race; it's the floor the race runs on.&lt;/p&gt;

&lt;p&gt;What the map &lt;em&gt;does&lt;/em&gt; tell you: the layer where you write agent logic and the layer where an agent runs and is contained are &lt;strong&gt;different concerns&lt;/strong&gt;, and a lot of production pain comes from bolting containment onto a framework after the fact. Pick a framework for the logic. Pick a substrate for the isolation, the egress policy, and the credential boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  What enclave deliberately does NOT do
&lt;/h2&gt;

&lt;p&gt;Being category-honest cuts both ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No orchestration DSL.&lt;/strong&gt; No &lt;code&gt;Crew&lt;/code&gt;, no &lt;code&gt;StateGraph&lt;/code&gt;, no &lt;code&gt;RoundRobinGroupChat&lt;/code&gt;. If you want structured multi-agent conversation, use AutoGen (or wire it yourself and run it in a pod).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Egress is report-only by default.&lt;/strong&gt; The container boundary is always on, but the &lt;em&gt;network&lt;/em&gt; allowlist only &lt;strong&gt;logs&lt;/strong&gt; until you set &lt;code&gt;GUARD_EGRESS_ENFORCE=1&lt;/code&gt; (&lt;code&gt;README.md:16-20&lt;/code&gt;). Ship it on for anything real.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public alpha.&lt;/strong&gt; Apache-2.0, used daily by its authors to run a live fleet, but the API and layout still move (&lt;code&gt;README.md&lt;/code&gt;, "Status").&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  So: which do I use?
&lt;/h2&gt;

&lt;p&gt;Both, usually. The question isn't "enclave &lt;em&gt;or&lt;/em&gt; a framework" — it's:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What logic am I building?&lt;/strong&gt; → a framework (or plain code). Whichever fits your agent's shape.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where does it run, and what can it reach?&lt;/strong&gt; → enclave, if you want kernel-enforced isolation, an egress policy, and a credential + memory boundary you didn't have to build.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The frameworks' shared default cost bug (re-sending accumulated context — the whole reason this series exists) is still yours to fix at the framework layer; enclave doesn't magic it away. But when you've fixed it and you want to actually &lt;em&gt;run&lt;/em&gt; the thing with credentials and network access it shouldn't fully have, that's the substrate problem, and it's the one enclave is for.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Enclave is open-source (Apache-2.0): &lt;a href="https://github.com/wartzar-bee/enclave" rel="noopener noreferrer"&gt;github.com/wartzar-bee/enclave&lt;/a&gt;. The five-part cost-audit series that grounds the framework column is on &lt;a href="https://dev.to/wartzarbee"&gt;dev.to/wartzarbee&lt;/a&gt;. Want the numbers for your own project? &lt;a href="https://github.com/wartzar-bee/tokenscope" rel="noopener noreferrer"&gt;tokenscope&lt;/a&gt; reads your Claude Code logs and shows exactly where the tokens went (&lt;code&gt;npx @wartzar-bee/tokenscope&lt;/code&gt; — read-only, nothing leaves your machine), and &lt;a href="https://github.com/wartzar-bee/ci-guardrail" rel="noopener noreferrer"&gt;ci-guardrail&lt;/a&gt; fails a PR when a token-cost regression sneaks in. wartzar-bee builds tools for operating cost-efficient autonomous agents.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>opensource</category>
      <category>aiagents</category>
    </item>
    <item>
      <title>A cost gate that fails your build should tell you where, not just that</title>
      <dc:creator>wartzar-bee</dc:creator>
      <pubDate>Wed, 19 Aug 2026 01:54:06 +0000</pubDate>
      <link>https://dev.to/wartzarbee/a-cost-gate-that-fails-your-build-should-tell-you-where-not-just-that-gm9</link>
      <guid>https://dev.to/wartzarbee/a-cost-gate-that-fails-your-build-should-tell-you-where-not-just-that-gm9</guid>
      <description>&lt;p&gt;Most teams that gate agent cost in CI eventually hit the same wall. You wire up a check that fails the PR when the token/compute cost of an agent step regresses past a budget — good, the surprise bill is now a surprise review comment instead. But the check tells you &lt;em&gt;that&lt;/em&gt; cost went up 34%. It doesn't tell you &lt;em&gt;where&lt;/em&gt;. So the author reopens the diff, eyeballs 600 changed lines, and guesses which hunk did it.&lt;/p&gt;

&lt;p&gt;That guessing step is where the gate quietly loses its teeth. A regression you can't locate in ten seconds is a regression people learn to bump the threshold past.&lt;/p&gt;

&lt;p&gt;Three things have made cost gates actually stick for me — none of them tool-specific:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Attribute the delta to a diff, then to a hunk.&lt;/strong&gt; "This PR adds 40k tokens/run" is a start. "This PR adds 40k tokens/run, and 31k of it is the fixture you pasted into &lt;code&gt;prompts/system.md&lt;/code&gt; lines 88–140" is a fix. Cost that points at a file is useful; cost that points at the &lt;em&gt;lines&lt;/em&gt; is actionable before the reviewer even asks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Put the finding where the reviewer already is.&lt;/strong&gt; A cost report in a separate comment, or worse in the Actions log, is a second place someone has to remember to look. The reviewer is looking at the &lt;em&gt;Files changed&lt;/em&gt; tab. If the cost warning renders inline on the responsible lines — same surface as a failing lint or a type error — it gets read, because it's in the path of the review that's already happening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Rank, don't dump — and don't oversell the number.&lt;/strong&gt; You don't need an exact per-line tokenizer count to point someone at the right block. A cheap ranking (which added hunks carry the most characters) is enough to say "start here." Use one number as the summary (the whole-change total) and the ranking only to aim inside it. And be honest about what the number &lt;em&gt;is&lt;/em&gt;: most of these estimates, including the whole-change total, are a chars/token proxy for relative comparison, not a billing figure. A gate that oversells its precision gets distrusted the first time it's slightly off — and a distrusted gate gets disabled.&lt;/p&gt;

&lt;p&gt;The gate itself is the part teams still rebuild by hand every time. If you want it off the shelf: &lt;code&gt;npx @wartzar-bee/tokenscope&lt;/code&gt; measures the token/$ cost of a run over just the changed files, and there's an Apache-2.0 GitHub Action (&lt;code&gt;wartzar-bee/ci-guardrail&lt;/code&gt;) that fails a PR when an agent step's cost regresses past your budget — and, as of this week, annotates the responsible lines inline on the PR diff, so a blocked build points at the hunk to trim instead of just flashing a percentage.&lt;/p&gt;

&lt;p&gt;For those of you gating agent cost in CI already: when a cost check fails, how does the author find &lt;em&gt;which&lt;/em&gt; change did it — do you attribute it to the diff, or is it still "the number went up, go hunt"?&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>githubactions</category>
      <category>devops</category>
      <category>ai</category>
    </item>
    <item>
      <title>Gate your CI on a dollar ceiling, not a percentage — the number your finance team actually asks for</title>
      <dc:creator>wartzar-bee</dc:creator>
      <pubDate>Sun, 16 Aug 2026 00:00:12 +0000</pubDate>
      <link>https://dev.to/wartzarbee/gate-your-ci-on-a-dollar-ceiling-not-a-percentage-the-number-your-finance-team-actually-asks-for-45mi</link>
      <guid>https://dev.to/wartzarbee/gate-your-ci-on-a-dollar-ceiling-not-a-percentage-the-number-your-finance-team-actually-asks-for-45mi</guid>
      <description>&lt;h1&gt;
  
  
  Gate your CI on a dollar ceiling, not a percentage — the number your finance team actually asks for
&lt;/h1&gt;

&lt;p&gt;Most cost gates for agent/LLM workflows check a &lt;strong&gt;delta&lt;/strong&gt;: did this PR make the run more expensive than the last one, by more than X%? That's a good regression alarm. But it answers a developer's question ("did I make it worse?"), not a budget owner's question ("are we going to blow the monthly number?").&lt;/p&gt;

&lt;p&gt;Those are genuinely different gates, and a team that only has the percentage one keeps getting surprised. A workflow can pass every percentage check — each PR adds a harmless-looking 3% — and still cross the line where the &lt;em&gt;absolute&lt;/em&gt; monthly spend stops being okay. Percentages compound quietly; dollars are what shows up on the invoice.&lt;/p&gt;

&lt;p&gt;So the second gate I want on any agent workflow is an &lt;strong&gt;absolute ceiling&lt;/strong&gt;: "a single run of this job must not cost more than $N," full stop, regardless of whether it went up or down since yesterday.&lt;/p&gt;

&lt;p&gt;Three things make that gate actually usable rather than theater:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The ceiling is priced, not token-counted.&lt;/strong&gt; "Under 2M tokens" is meaningless to the person who signs off on spend, because a token of Opus output and a token of cached Haiku input differ by ~100× in price. The gate has to multiply each token bucket (input, output, cache-write at ~1.25×, cache-read at ~0.1×) by that model's real per-token price and sum to an actual dollar figure. If your gate reports tokens and makes a human convert, nobody converts, and the ceiling drifts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The ceiling is per-run and per-workflow, not global.&lt;/strong&gt; A nightly full-repo audit and a per-PR lint agent have wildly different legitimate costs; one global number is either too loose for the small job or too tight for the big one. You want to set &lt;code&gt;max-usd&lt;/code&gt; on the specific workflow, so each job carries the ceiling that matches what it's &lt;em&gt;for&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. It shows the headroom, not just pass/fail.&lt;/strong&gt; "$0.43 of a $0.50 ceiling — 86%" on every run is the line that lets you move the limit &lt;em&gt;before&lt;/em&gt; it starts failing builds, instead of discovering the wall by hitting it. A gate that only says "blocked" the first time you cross is a gate people rip out.&lt;/p&gt;

&lt;p&gt;The percentage gate and the dollar gate aren't competitors — I run both. The percentage one catches the &lt;em&gt;sudden&lt;/em&gt; regression (someone pasted a 50k-token fixture into the prompt); the dollar one catches the &lt;em&gt;slow&lt;/em&gt; one (the workflow that was always a bit expensive and finally crossed what the budget can absorb). Different failure modes, and the second is the one your finance owner will actually ask you about.&lt;/p&gt;

&lt;p&gt;If you want it off the shelf: &lt;code&gt;wartzar-bee/ci-guardrail&lt;/code&gt; is an Apache-2.0 GitHub Action (built on &lt;code&gt;@wartzar-bee/tokenscope&lt;/code&gt;) that prices token usage into real dollars per run and can gate on an absolute &lt;code&gt;max-usd&lt;/code&gt; ceiling as well as a delta — so you can wire the "don't exceed $N per run" check straight into the workflow that needs it. But the mental model is the point whatever tool you reach for: a percentage answers the developer's question, a dollar ceiling answers the budget owner's.&lt;/p&gt;

&lt;p&gt;For those of you already gating agent cost: do you gate on the relative delta, an absolute dollar ceiling, or both — and if you had to pick one to start with, which caught more real problems for you?&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>ai</category>
      <category>devops</category>
      <category>tooling</category>
    </item>
    <item>
      <title>I scanned the code of 25 MCP servers — what they can do to your machine before you sandbox them</title>
      <dc:creator>wartzar-bee</dc:creator>
      <pubDate>Sat, 15 Aug 2026 11:13:54 +0000</pubDate>
      <link>https://dev.to/wartzarbee/i-scanned-the-code-of-25-mcp-servers-what-they-can-do-to-your-machine-before-you-sandbox-them-2k0e</link>
      <guid>https://dev.to/wartzarbee/i-scanned-the-code-of-25-mcp-servers-what-they-can-do-to-your-machine-before-you-sandbox-them-2k0e</guid>
      <description>&lt;p&gt;An MCP server isn't a remote API you call over HTTPS. For the stdio servers that make up most of the popular ones, &lt;code&gt;npx some-mcp-server&lt;/code&gt; &lt;strong&gt;downloads code and runs it as a local process&lt;/strong&gt; — with your shell's environment, your filesystem, and your network. Your agent then hands that process instructions.&lt;/p&gt;

&lt;p&gt;That's a normal, useful design. It's also a capability surface nobody itemizes for you. So I measured it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method (static source scan — honest about its limits)
&lt;/h2&gt;

&lt;p&gt;I pulled 25 popular Model Context Protocol server packages and statically scanned the &lt;strong&gt;published package code&lt;/strong&gt; (not runtime behavior) for four things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;net-call sites&lt;/strong&gt; — does the code make outbound network calls?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;secret-env reads&lt;/strong&gt; — does it read credentials from &lt;code&gt;process.env&lt;/code&gt; (e.g. &lt;code&gt;process.env.API_KEY&lt;/code&gt;)?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;subprocess spawns&lt;/strong&gt; — does it shell out / spawn child processes (&lt;code&gt;exec_shapes&lt;/code&gt;)?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;bundled binaries&lt;/strong&gt; — does the package ship a prebuilt binary?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Static scanning tells you what a package &lt;em&gt;can&lt;/em&gt; reach for, not what it does on a given run. A &lt;code&gt;process.env.API_KEY&lt;/code&gt; read is almost always the server loading &lt;em&gt;its own&lt;/em&gt; credential — completely legitimate. The point isn't "these are malicious." The point is the &lt;strong&gt;aggregate surface you're granting&lt;/strong&gt;, unseen, every time you add a server.&lt;/p&gt;

&lt;h2&gt;
  
  
  The capability surface, across 25 servers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability (code contains…)&lt;/th&gt;
&lt;th&gt;Servers&lt;/th&gt;
&lt;th&gt;of 25&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A network-call site&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;68%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reads a secret from &lt;code&gt;process.env&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;64%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A hardcoded outbound host reference&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;48%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spawns a subprocess / shells out&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;32%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ships a bundled binary&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;16%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So a typical multi-server agent setup is running &lt;strong&gt;a dozen-plus local processes&lt;/strong&gt;, most of which read your environment and make network calls, several of which can spawn subprocesses — all with the same privileges as the shell you launched them from. No install step surfaces this. No directory lists it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more for agents than for humans
&lt;/h2&gt;

&lt;p&gt;When &lt;em&gt;you&lt;/em&gt; run a CLI tool, you roughly know what it does. When your &lt;em&gt;agent&lt;/em&gt; connects a fleet of MCP servers and then acts in a loop, the blast radius is: every server's capability × the agent's autonomy × however many turns the loop runs. One over-scoped server, one compromised dependency in one of those packages, and the credential in &lt;code&gt;process.env&lt;/code&gt; is one network call away from leaving your machine — and until recently a wildcard like &lt;code&gt;*.amazonaws.com&lt;/code&gt; on an allowlist would have waved it straight through to an attacker-nameable S3 bucket.&lt;/p&gt;

&lt;p&gt;You don't fix this by auditing 25 packages by hand every time you add one. You fix it by &lt;strong&gt;running the servers (and the agent) in a sandbox&lt;/strong&gt; that constrains filesystem, egress, and secrets by default — so a server's &lt;em&gt;capability&lt;/em&gt; stops being an &lt;em&gt;authority&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That's exactly what &lt;a href="https://github.com/wartzar-bee/enclave" rel="noopener noreferrer"&gt;enclave&lt;/a&gt; is: an Apache-2.0 sandboxed runtime for agents, with a default-deny egress allowlist, secret redaction, and a guard that blocks the sandboxed process from rewriting its own guardrails. Point your MCP-using agent at it and the capability surface above becomes something you &lt;em&gt;grant explicitly&lt;/em&gt; instead of something you &lt;em&gt;inherit silently&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;And if you want the other half of the bill — the token &lt;strong&gt;context-tax&lt;/strong&gt; those same servers add to every request (I measured a 544× spread, from 35 to 19,054 tokens/request) — that's &lt;a href="https://www.npmjs.com/package/@wartzar-bee/tokenscope" rel="noopener noreferrer"&gt;tokenscope&lt;/a&gt;: &lt;code&gt;npx @wartzar-bee/tokenscope&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Numbers are from a static source scan of 25 published MCP server packages; every count is reproducible from the package code. It's a lower bound on capability, not an accusation about any specific server's behavior.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>security</category>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>You can see your cloud bill. Can you see what your AI agent's context costs?</title>
      <dc:creator>wartzar-bee</dc:creator>
      <pubDate>Tue, 11 Aug 2026 08:15:53 +0000</pubDate>
      <link>https://dev.to/wartzarbee/you-can-see-your-cloud-bill-can-you-see-what-your-ai-agents-context-costs-1de6</link>
      <guid>https://dev.to/wartzarbee/you-can-see-your-cloud-bill-can-you-see-what-your-ai-agents-context-costs-1de6</guid>
      <description>&lt;p&gt;You can see your cloud bill. You can see your CI minutes tick down. But the fastest-growing line item in an AI-agent app is the one number you &lt;em&gt;can't&lt;/em&gt; see: how many tokens your agent's context is worth on &lt;strong&gt;every single model call&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It's invisible because it never shows up as one big charge. It's a few thousand tokens, resent hundreds of times a day, quietly compounding. By the time it's a real number on the invoice, it's baked into every request you make.&lt;/p&gt;

&lt;p&gt;Here's how to read it in ten seconds — no account, no logs, no config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @wartzar-bee/tokenscope scan &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point it at the directory that holds your agent's prompts, tools, and configs. It prints the token footprint and the files responsible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tokenscope scan — src
Estimated token footprint: 8,454 tokens across 6 files
(estimate ≈ 4 chars/token — a tokenizer-free proxy for relative comparison, not a billing figure)

Top files by estimated tokens:
      2991  share.mjs
      2134  scan.mjs
      1092  core.mjs
       835  report.mjs
       718  pricing.mjs
       684  benchmark.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole point: the top file is usually a system prompt, a tool schema, or a wall of few-shot examples that someone added "just to be safe." Now you can see which one, and how much it weighs, before it's part of every call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is the number that bites
&lt;/h2&gt;

&lt;p&gt;Most agent frameworks resend the accumulated context on each step — memory, history, tool definitions, the lot. So a prompt that's 2,000 tokens heavier isn't a one-time cost; it's 2,000 tokens × every call × every user. The per-call log looks fine. The monthly bill does not. (I watched an agent on a timer burn &lt;a href="https://dev.to/wartzarbee/i-put-an-ai-agent-on-a-timer-overnight-it-burned-136m-tokens-doing-almost-nothing-2ae2"&gt;136M tokens overnight doing almost nothing&lt;/a&gt; — same root cause.)&lt;/p&gt;

&lt;p&gt;tokenscope gives you a single, reproducible footprint number so a "let's just add this to the prompt" PR stops being invisible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways to use it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Ad-hoc, right now&lt;/strong&gt; — measure any repo before you ship a prompt change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @wartzar-bee/tokenscope scan ./agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. As a local cost gate&lt;/strong&gt; — fail your own commit if the footprint blows a budget, so a runaway prompt never leaves your machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .git/hooks/pre-push  (chmod +x)&lt;/span&gt;
npx @wartzar-bee/tokenscope scan &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--max-total&lt;/span&gt; 50000 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Context footprint over budget — trim it before pushing."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the &lt;a href="https://pre-commit.com" rel="noopener noreferrer"&gt;pre-commit&lt;/a&gt; framework? It's a four-line entry — no hook scripting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. On every PR, in CI&lt;/strong&gt; — the same check as a GitHub Action that comments the token-cost delta on the responsible files and (optionally) blocks the merge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wartzar-bee/ci-guardrail@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;github-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;warn&lt;/span&gt;   &lt;span class="c1"&gt;# report-only until you trust it; switch to block later&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's &lt;a href="https://github.com/wartzar-bee/ci-guardrail" rel="noopener noreferrer"&gt;ci-guardrail&lt;/a&gt; — tokenscope wired into your pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Run it:&lt;/strong&gt; &lt;code&gt;npx @wartzar-bee/tokenscope scan .&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/@wartzar-bee/tokenscope" rel="noopener noreferrer"&gt;@wartzar-bee/tokenscope&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source (Apache-2.0):&lt;/strong&gt; &lt;a href="https://github.com/wartzar-bee/tokenscope" rel="noopener noreferrer"&gt;github.com/wartzar-bee/tokenscope&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's free, open-source, and tokenizer-free — an estimate for relative comparison, not a billing oracle, so you can run it on any codebase without wiring up a provider SDK. If it saves you one "why is the bill up 40%?" afternoon, it did its job.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I scanned 25 popular MCP servers — the hidden context-tax spans 544</title>
      <dc:creator>wartzar-bee</dc:creator>
      <pubDate>Mon, 10 Aug 2026 17:28:51 +0000</pubDate>
      <link>https://dev.to/wartzarbee/i-scanned-25-popular-mcp-servers-the-hidden-context-tax-spans-544x-2fp6</link>
      <guid>https://dev.to/wartzarbee/i-scanned-25-popular-mcp-servers-the-hidden-context-tax-spans-544x-2fp6</guid>
      <description>&lt;p&gt;Every MCP server you connect ships a set of tool-definition schemas. Those schemas get injected into &lt;strong&gt;every single model request&lt;/strong&gt; for the whole session — before you ask the agent anything. That's a fixed tax on your context window and your bill, and no MCP directory lists it.&lt;/p&gt;

&lt;p&gt;So I measured it. I pulled 25 popular Model Context Protocol servers, spawned each one locally, ran the real &lt;code&gt;tools/list&lt;/code&gt; handshake, and counted the tokens its tool schemas add per request (&lt;code&gt;chars/4&lt;/code&gt;, the same heuristic &lt;a href="https://www.npmjs.com/package/@wartzar-bee/tokenscope" rel="noopener noreferrer"&gt;tokenscope&lt;/a&gt; uses — labelled an estimate). 16 of the 25 register their tools without live credentials, so those are measured; the rest need OAuth/remote transport or real keys and are marked &lt;em&gt;not measured&lt;/em&gt;, never estimated.&lt;/p&gt;

&lt;h2&gt;
  
  
  The context-tax, measured
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Server&lt;/th&gt;
&lt;th&gt;Tools&lt;/th&gt;
&lt;th&gt;Tokens / request&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;notion-mcp&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19,054&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kubernetes&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;5,964&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;github&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;4,242&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;memory&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;2,875&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;everything&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;2,031&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tavily&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1,924&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sequential-thinking&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1,176&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;browsermcp&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;1,052&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;slack&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;822&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;google-maps&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;704&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;puppeteer&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;648&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;exa&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;535&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;brave-search&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;375&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gitlab&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;371&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;perplexity-ask&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;149&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;postgres&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;35&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's a &lt;strong&gt;544× spread&lt;/strong&gt; — from 35 tokens to just over 19,000, paid on every request for the life of the session. Connect notion-mcp and github together and you're near ~23k tokens of pure schema overhead before a single user turn. On a long agent run that's not a rounding error; it's most of your context budget and a real line on the bill.&lt;/p&gt;

&lt;p&gt;The lesson isn't "don't use big servers." It's that &lt;strong&gt;the cost is invisible at connect time&lt;/strong&gt; and none of the directories that rank these servers by stars/installs will tell you. You find out from the invoice.&lt;/p&gt;

&lt;h2&gt;
  
  
  While I had the packages open, I also scanned what they do
&lt;/h2&gt;

&lt;p&gt;Same 25 servers, static source scan (nothing executed): network egress, secret-env reads, exec/shell surface. A few things worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;16 of 25 read a secret env var; 8 have an exec/shell surface.&lt;/strong&gt; &lt;code&gt;upstash-context7-mcp&lt;/code&gt; reads &lt;code&gt;OPENAI_APPS_CHALLENGE_TOKEN&lt;/code&gt; + &lt;code&gt;CLIENT_IP_ENCRYPTION_KEY&lt;/code&gt; and egresses &lt;code&gt;login.microsoftonline.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4 of 25 ship a bundled/minified dist&lt;/strong&gt; (&lt;code&gt;exa&lt;/code&gt; has 293k-char lines; &lt;code&gt;notion&lt;/code&gt; 582k). You can't statically separate a minified blob into call-site hosts vs embedded strings, so I label those &lt;code&gt;bundled — egress not statically resolvable&lt;/code&gt; rather than render a misleading "clean." &lt;code&gt;exa&lt;/code&gt;'s bundle references an undisclosed &lt;code&gt;api.agnost.ai&lt;/code&gt; alongside &lt;code&gt;api.exa.ai&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Getting the egress scan honest at 25 servers was most of the work — naive scans count comment/jsdoc URLs as egress (one server "phoned home" to wikipedia.org and rfc-editor.org... from doc-strings). A host only counts as egress if its URL literal sits at an actual network call site or a declared API base.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure your own
&lt;/h2&gt;

&lt;p&gt;The context-tax above is server-side and fixed. Your &lt;em&gt;own&lt;/em&gt; agent's per-session cost — which server's schemas are eating your window, what a run actually cost — you can measure directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @wartzar-bee/tokenscope &lt;span class="nt"&gt;--demo&lt;/span&gt;   &lt;span class="c"&gt;# zero-setup: a sample cost report, no session needed&lt;/span&gt;
npx @wartzar-bee/tokenscope          &lt;span class="c"&gt;# or point it at your most recent Claude Code session&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if you want to stop cost regressions before they ship, &lt;a href="https://github.com/wartzar-bee/ci-guardrail" rel="noopener noreferrer"&gt;wartzar-bee/ci-guardrail&lt;/a&gt; gates token cost in CI the same way you'd gate a failing test.&lt;/p&gt;

&lt;p&gt;Everything here is reproducible from published npm packages — &lt;code&gt;npm pack&lt;/code&gt; the servers, run the scanner, spawn each for the live &lt;code&gt;tools/list&lt;/code&gt;. Found a cell that's wrong? The method is open; correct it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Part of &lt;a href="https://github.com/wartzar-bee" rel="noopener noreferrer"&gt;wartzar-bee&lt;/a&gt; — tools for building and operating cost-efficient autonomous agents. Apache-2.0.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>llm</category>
      <category>agents</category>
    </item>
    <item>
      <title>The cheapest way to try a CI cost gate: run it in warn mode for a week</title>
      <dc:creator>wartzar-bee</dc:creator>
      <pubDate>Mon, 03 Aug 2026 09:00:47 +0000</pubDate>
      <link>https://dev.to/wartzarbee/the-cheapest-way-to-try-a-ci-cost-gate-run-it-in-warn-mode-for-a-week-1g96</link>
      <guid>https://dev.to/wartzarbee/the-cheapest-way-to-try-a-ci-cost-gate-run-it-in-warn-mode-for-a-week-1g96</guid>
      <description>&lt;h1&gt;
  
  
  The cheapest way to try a CI cost gate: run it in warn mode for a week
&lt;/h1&gt;

&lt;p&gt;The objection I hear most about gating agent cost in CI isn't "I don't need it." It's "I'm not wiring up a new check that can fail my builds on day one." Fair. A gate that red-X's a PR the first afternoon it's installed — over a threshold nobody has calibrated yet — gets ripped back out by Friday.&lt;/p&gt;

&lt;p&gt;So the way I actually roll one out now is: &lt;strong&gt;install it in report-only mode first, and let it be wrong for a week before it's allowed to block anything.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Concretely, that's three properties I'd look for in any cost gate, not just the one I work on:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. A report-only mode that never fails the build.&lt;/strong&gt; You want the cost delta to show up on every PR — the number, the top files, the trend — while the check itself stays green no matter what. That's the whole evaluation period: you're watching whether the gate's numbers track reality on &lt;em&gt;your&lt;/em&gt; repo before you give it teeth. If a tool only ships in block-or-nothing form, you're calibrating in production against angry teammates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A noise floor, so tiny diffs don't cry wolf.&lt;/strong&gt; The commonest false positive for a percentage gate is a small base: a helper that goes from 10 tokens to 30 is +200%, and a naive gate fails the build over a fraction of a cent. You want an absolute floor — "only flag when the real increase is at least N tokens" — so the gate speaks up about the fixture someone pasted in, not about a two-line refactor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. A report that shows up even when there's no PR.&lt;/strong&gt; Pushes to main, scheduled runs, manual dispatches — those have no PR to comment on, but they're exactly where a slow cost creep hides. A gate that also writes its table to the run summary (GitHub's &lt;code&gt;$GITHUB_STEP_SUMMARY&lt;/code&gt;) keeps a cost record on every event, not just on pull requests.&lt;/p&gt;

&lt;p&gt;The rollout that sticks: add it with enforcement off, watch the numbers for a sprint, set the threshold where it would have caught the two regressions you actually remember, then flip it to blocking. By then nobody argues with it, because they've watched it be right.&lt;/p&gt;

&lt;p&gt;If you want that off the shelf: &lt;code&gt;wartzar-bee/ci-guardrail&lt;/code&gt; is an Apache-2.0 GitHub Action (powered by &lt;code&gt;@wartzar-bee/tokenscope&lt;/code&gt;) with a &lt;code&gt;mode: warn&lt;/code&gt; that does exactly the report-only-never-fail thing, and it writes the same cost table to the Actions run summary on any event (push, schedule, manual) — not just on PRs — so the first PR you point it at can't break, and you decide when it earns the right to block.&lt;/p&gt;

&lt;p&gt;The whole rollout is one step — report-only from the first PR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/agent-cost.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wartzar-bee/ci-guardrail@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;github-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;warn&lt;/span&gt;            &lt;span class="c1"&gt;# report the cost delta on every PR, never fail the build&lt;/span&gt;
    &lt;span class="na"&gt;threshold-pct&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;      &lt;span class="c1"&gt;# the line it *would* flag — tune it, then flip mode off to enforce&lt;/span&gt;
    &lt;span class="na"&gt;min-delta-tokens&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;  &lt;span class="c1"&gt;# noise floor: ignore tiny diffs so small bases don't cry wolf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For those of you who gate cost (or coverage, or bundle size) in CI: how long do you run a new gate in warn/report-only before you let it block — a week, a sprint, longer? Or do you just turn it to blocking on day one and eat the noise?&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>ai</category>
      <category>devops</category>
      <category>tooling</category>
    </item>
    <item>
      <title>AutoGen's hidden token tax: why a 3-agent chat costs 15 what you expect</title>
      <dc:creator>wartzar-bee</dc:creator>
      <pubDate>Thu, 30 Jul 2026 15:37:46 +0000</pubDate>
      <link>https://dev.to/wartzarbee/autogens-hidden-token-tax-why-a-3-agent-chat-costs-15x-what-you-expect-mbd</link>
      <guid>https://dev.to/wartzarbee/autogens-hidden-token-tax-why-a-3-agent-chat-costs-15x-what-you-expect-mbd</guid>
      <description>&lt;h1&gt;
  
  
  AutoGen's hidden token tax: why a 3-agent chat costs 15× what you expect
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Cost-audit series, episode 2. This series began with &lt;a href="https://dev.to/wartzarbee/i-put-an-ai-agent-on-a-timer-overnight-it-burned-136m-tokens-doing-almost-nothing-2ae2"&gt;an AI agent that burned 136M tokens overnight →&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;AutoGen is Microsoft's multi-agent framework. It's genuinely good at orchestrating agents that hand off work to each other. But its default memory model has a cost shape that surprises almost every team that hits it in production.&lt;/p&gt;

&lt;p&gt;This audit shows you exactly where the tokens go, with line numbers.&lt;/p&gt;




&lt;h2&gt;
  
  
  The setup: a 3-agent RoundRobin chat
&lt;/h2&gt;

&lt;p&gt;The canonical AutoGen pattern is a &lt;code&gt;RoundRobinGroupChat&lt;/code&gt; with N agents taking turns on a task. Here's the minimal version from the docs:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;autogen_agentchat.agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AssistantAgent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;autogen_agentchat.teams&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RoundRobinGroupChat&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;autogen_agentchat.conditions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MaxMessageTermination&lt;/span&gt;

&lt;span class="n"&gt;planner&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AssistantAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;planner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;model_client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You plan.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;coder&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AssistantAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="n"&gt;model_client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You code.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;reviewer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AssistantAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reviewer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model_client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You review.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;team&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RoundRobinGroupChat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;planner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reviewer&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;termination_condition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;MaxMessageTermination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Build a web scraper for Hacker News.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three agents, 10 turns total (~3–4 turns each). Seems cheap. It isn't.&lt;/p&gt;




&lt;h2&gt;
  
  
  The default context: unbounded, per-agent
&lt;/h2&gt;

&lt;p&gt;Every &lt;code&gt;AssistantAgent&lt;/code&gt; gets its own &lt;code&gt;UnboundedChatCompletionContext&lt;/code&gt; by default:&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="c1"&gt;# autogen-agentchat/src/autogen_agentchat/agents/_assistant_agent.py, __init__
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;model_context&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_model_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model_context&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_model_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UnboundedChatCompletionContext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/microsoft/autogen/blob/027ecf0a379bcc1d09956d46d12d44a3ad9cee14/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_assistant_agent.py#L840" rel="noopener noreferrer"&gt;source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;UnboundedChatCompletionContext.get_messages()&lt;/code&gt; returns &lt;code&gt;self._messages&lt;/code&gt; — the full list, no cap, no truncation:&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="c1"&gt;# autogen-core/.../model_context/_unbounded_chat_completion_context.py (a ~20-line file)
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_messages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;LLMMessage&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get at most `buffer_size` recent messages.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_messages&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/microsoft/autogen/blob/027ecf0a379bcc1d09956d46d12d44a3ad9cee14/python/packages/autogen-core/src/autogen_core/model_context/_unbounded_chat_completion_context.py" rel="noopener noreferrer"&gt;source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(The docstring says "at most &lt;code&gt;buffer_size&lt;/code&gt;" — that's a copy-paste artifact from &lt;code&gt;BufferedChatCompletionContext&lt;/code&gt;. There is no buffer. It returns everything.)&lt;/p&gt;




&lt;h2&gt;
  
  
  The handoff tax: every agent sees every message
&lt;/h2&gt;

&lt;p&gt;When an agent's turn arrives, &lt;code&gt;on_messages_stream&lt;/code&gt; adds all incoming messages to its own context before calling the LLM:&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="c1"&gt;# _assistant_agent.py, in on_messages_stream (STEP 1: "Add new user/handoff messages
# to the model context")
&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_add_messages_to_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&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 full message_thread from the group manager
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;_add_messages_to_context&lt;/code&gt; appends each one:&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="c1"&gt;# _assistant_agent.py, static method _add_messages_to_context
&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;model_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm_msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;model_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_model_message&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/microsoft/autogen/blob/027ecf0a379bcc1d09956d46d12d44a3ad9cee14/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_assistant_agent.py" rel="noopener noreferrer"&gt;source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The group manager (&lt;code&gt;BaseGroupChatManager&lt;/code&gt;) maintains a single &lt;code&gt;_message_thread&lt;/code&gt; and appends every response to it:&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="c1"&gt;# _base_group_chat_manager.py
&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_message_thread&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;BaseAgentEvent&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;BaseChatMessage&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="bp"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_message_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# called after every agent response
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/microsoft/autogen/blob/027ecf0a379bcc1d09956d46d12d44a3ad9cee14/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat_manager.py" rel="noopener noreferrer"&gt;source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So at turn T, the agent receiving the baton gets &lt;code&gt;T-1&lt;/code&gt; messages added to its already-growing context. Its context now contains everything it has ever seen.&lt;/p&gt;




&lt;h2&gt;
  
  
  The math: O(N × T²) total tokens
&lt;/h2&gt;

&lt;p&gt;Let's be precise. Define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;T&lt;/strong&gt; = total turns in the conversation
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;N&lt;/strong&gt; = number of agents
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;m&lt;/strong&gt; = average tokens per message (system prompt + response, ~300 tokens is realistic for a coding task)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each agent speaks every N turns. When agent &lt;em&gt;i&lt;/em&gt; speaks on turn &lt;em&gt;t&lt;/em&gt;, its context contains all &lt;em&gt;t-1&lt;/em&gt; prior messages (because it has been accumulating them since turn 1).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tokens consumed by agent &lt;em&gt;i&lt;/em&gt; on turn &lt;em&gt;t&lt;/em&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;context_tokens(t) = (t - 1) × m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Total tokens for agent &lt;em&gt;i&lt;/em&gt; across all its turns&lt;/strong&gt; (it speaks at turns N, 2N, 3N, … up to T):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Σ (kN - 1) × m  for k = 1 to T/N
≈ m × N × (T/N)² / 2
= m × T² / (2N)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Total tokens across all N agents:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;N × m × T² / (2N) = m × T² / 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The N cancels. Total cost scales as &lt;strong&gt;T²&lt;/strong&gt; regardless of how many agents you add.&lt;/p&gt;

&lt;h3&gt;
  
  
  Worked example: 10 turns, 3 agents, 300 tokens/message
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Turn&lt;/th&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Context size (messages)&lt;/th&gt;
&lt;th&gt;Tokens in this call&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;planner&lt;/td&gt;
&lt;td&gt;0 prior + system&lt;/td&gt;
&lt;td&gt;~300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;coder&lt;/td&gt;
&lt;td&gt;1 prior + system&lt;/td&gt;
&lt;td&gt;~600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;reviewer&lt;/td&gt;
&lt;td&gt;2 prior + system&lt;/td&gt;
&lt;td&gt;~900&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;planner&lt;/td&gt;
&lt;td&gt;3 prior + system&lt;/td&gt;
&lt;td&gt;~1,200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;coder&lt;/td&gt;
&lt;td&gt;4 prior + system&lt;/td&gt;
&lt;td&gt;~1,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;reviewer&lt;/td&gt;
&lt;td&gt;5 prior + system&lt;/td&gt;
&lt;td&gt;~1,800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;planner&lt;/td&gt;
&lt;td&gt;6 prior + system&lt;/td&gt;
&lt;td&gt;~2,100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;coder&lt;/td&gt;
&lt;td&gt;7 prior + system&lt;/td&gt;
&lt;td&gt;~2,400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;reviewer&lt;/td&gt;
&lt;td&gt;8 prior + system&lt;/td&gt;
&lt;td&gt;~2,700&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;planner&lt;/td&gt;
&lt;td&gt;9 prior + system&lt;/td&gt;
&lt;td&gt;~3,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~16,500 tokens&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Naïve expectation&lt;/strong&gt; (10 calls × 300 tokens each): &lt;strong&gt;3,000 tokens&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Actual&lt;/strong&gt;: &lt;strong&gt;~16,500 tokens&lt;/strong&gt; — &lt;strong&gt;5.5× more&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At 20 turns it's &lt;strong&gt;~63,000 tokens&lt;/strong&gt; vs 6,000 expected — &lt;strong&gt;10.5× more&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
At 30 turns: &lt;strong&gt;~139,500 tokens&lt;/strong&gt; vs 9,000 — &lt;strong&gt;15.5× more&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The multiplier grows linearly with T. This is the same O(T²) shape as &lt;code&gt;ConversationBufferMemory&lt;/code&gt; in LangChain — but AutoGen's version is &lt;em&gt;per-agent&lt;/em&gt;, so it's easy to miss in per-call logs.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why per-call logs hide this
&lt;/h2&gt;

&lt;p&gt;If you're watching your LLM provider's per-call token counts, you see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;call 1:  300 tokens  ✓ cheap
call 2:  600 tokens  ✓ fine
call 3:  900 tokens  ✓ ok
...
call 10: 3,000 tokens  ← this one looks expensive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each call looks like a modest increase. The &lt;em&gt;cumulative&lt;/em&gt; total — 16,500 — only shows up when you sum across the run. Most observability dashboards show per-call costs, not per-run totals. The runaway is invisible until the bill arrives.&lt;/p&gt;




&lt;h2&gt;
  
  
  The fix: cap the context
&lt;/h2&gt;

&lt;p&gt;AutoGen ships two bounded alternatives, named in the &lt;code&gt;AssistantAgent&lt;/code&gt; class docstring (around L178 of &lt;code&gt;_assistant_agent.py&lt;/code&gt;): &lt;code&gt;BufferedChatCompletionContext&lt;/code&gt; (limits message count) and &lt;code&gt;TokenLimitedChatCompletionContext&lt;/code&gt; (limits tokens):&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: &lt;code&gt;BufferedChatCompletionContext&lt;/code&gt; (sliding window)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;autogen_core.model_context&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BufferedChatCompletionContext&lt;/span&gt;

&lt;span class="n"&gt;coder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AssistantAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model_client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;BufferedChatCompletionContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# last 5 messages
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cost shape becomes &lt;strong&gt;O(T × buffer_size)&lt;/strong&gt; — linear. For buffer_size=5 and 30 turns: ~42,000 tokens vs 139,500 unbounded. &lt;strong&gt;3.3× cheaper.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: &lt;code&gt;TokenLimitedChatCompletionContext&lt;/code&gt; (token budget)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;autogen_core.model_context&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TokenLimitedChatCompletionContext&lt;/span&gt;

&lt;span class="n"&gt;coder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AssistantAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model_client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;TokenLimitedChatCompletionContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2000&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;Caps the context at a fixed token budget. More predictable than a message count because message sizes vary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which to use?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Short tasks (≤10 turns)&lt;/td&gt;
&lt;td&gt;Default is fine; monitor cumulative cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long tasks (&amp;gt;10 turns)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BufferedChatCompletionContext(buffer_size=8–12)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strict cost budget&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TokenLimitedChatCompletionContext(token_limit=N)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need full history&lt;/td&gt;
&lt;td&gt;Default + add per-run cost alerting (see below)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Detecting this in CI before it hits production
&lt;/h2&gt;

&lt;p&gt;The pattern is detectable statically: any file that instantiates &lt;code&gt;AssistantAgent&lt;/code&gt; without a &lt;code&gt;model_context=&lt;/code&gt; argument is using the unbounded default.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Flag unbounded AssistantAgent instantiations&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; &lt;span class="s2"&gt;"AssistantAgent("&lt;/span&gt; src/ | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"model_context="&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For dynamic detection — measuring actual token growth across a run — this is exactly what &lt;a href="https://www.npmjs.com/package/@wartzar-bee/tokenscope" rel="noopener noreferrer"&gt;tokenscope&lt;/a&gt; does: it instruments LLM calls, tracks per-run cumulative cost, and can block a CI build when a PR's token delta exceeds a threshold.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/wartzar-bee/ci-guardrail" rel="noopener noreferrer"&gt;wartzar-bee/ci-guardrail&lt;/a&gt; GitHub Action wraps tokenscope into a one-line workflow addition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wartzar-bee/ci-guardrail@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;github-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
    &lt;span class="na"&gt;threshold-pct&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;   &lt;span class="c1"&gt;# block if the PR's predicted token cost rises &amp;gt;20% vs base&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary
&lt;/h2&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;Naïve expectation&lt;/th&gt;
&lt;th&gt;Actual (unbounded)&lt;/th&gt;
&lt;th&gt;With BufferedContext(5)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10 turns, 3 agents&lt;/td&gt;
&lt;td&gt;3,000 tokens&lt;/td&gt;
&lt;td&gt;~16,500 tokens&lt;/td&gt;
&lt;td&gt;~12,000 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20 turns, 3 agents&lt;/td&gt;
&lt;td&gt;6,000 tokens&lt;/td&gt;
&lt;td&gt;~63,000 tokens&lt;/td&gt;
&lt;td&gt;~27,000 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30 turns, 3 agents&lt;/td&gt;
&lt;td&gt;9,000 tokens&lt;/td&gt;
&lt;td&gt;~139,500 tokens&lt;/td&gt;
&lt;td&gt;~42,000 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The default &lt;code&gt;UnboundedChatCompletionContext&lt;/code&gt; is correct for short tasks and full-history use cases. It becomes a cost trap in long multi-agent conversations. The fix is one constructor argument — but you have to know to add it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The broader pattern&lt;/strong&gt;: every major agent framework defaults to unbounded context because it's the safest correctness choice. Cost is a second-class citizen in the default config. That's the gap this series documents.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Next in the series: CrewAI — the delegation overhead. How hierarchical agent trees multiply your token bill.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://www.npmjs.com/package/@wartzar-bee/tokenscope" rel="noopener noreferrer"&gt;tokenscope on npm&lt;/a&gt; · &lt;a href="https://github.com/wartzar-bee/ci-guardrail" rel="noopener noreferrer"&gt;wartzar-bee/ci-guardrail&lt;/a&gt; · &lt;a href="https://dev.to/wartzarbee"&gt;@wartzarbee on dev.to&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>llm</category>
      <category>aiagents</category>
      <category>python</category>
      <category>autogen</category>
    </item>
  </channel>
</rss>
