<?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>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__ (L840)
&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>
    <item>
      <title>Gate your AI agents' token cost in CI — before the bill, not after</title>
      <dc:creator>wartzar-bee</dc:creator>
      <pubDate>Thu, 30 Jul 2026 14:59:43 +0000</pubDate>
      <link>https://dev.to/wartzarbee/gate-your-ai-agents-token-cost-in-ci-before-the-bill-not-after-67g</link>
      <guid>https://dev.to/wartzarbee/gate-your-ai-agents-token-cost-in-ci-before-the-bill-not-after-67g</guid>
      <description>&lt;p&gt;A multi-agent run doesn't fail loudly when it gets expensive. It just quietly costs more.&lt;/p&gt;

&lt;p&gt;One extra delegation loop, a tool that dumps a big blob into shared context, an agent that re-reads the whole history every turn — and the pipeline step that cost a few cents last week costs a few dollars this week. Nothing broke, so nothing warned you. You find out &lt;em&gt;after&lt;/em&gt; the invoice, not before the run.&lt;/p&gt;

&lt;p&gt;We already gate build time, bundle size, and test coverage in CI, and we fail the PR when they regress. The token/compute cost of the LLM steps themselves is the one budget we still tend to discover on the monthly bill. Here are three habits that have actually helped, then the concrete CI piece I was least sure how to do — and now do on every repo that runs agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Budget the prompt before the run, not after
&lt;/h2&gt;

&lt;p&gt;Before kicking off an agent, look at what it actually carries into context: system prompt, tool schemas, accumulated task output. If a single agent walks in with 15k tokens of boilerplate it never uses, that's a fixed tax on &lt;em&gt;every&lt;/em&gt; turn it takes. Trim it once, save it every run.&lt;/p&gt;

&lt;p&gt;A zero-setup way to see the shape of a real session's cost — output vs cached re-reads vs cache writes — without wiring anything up:&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;# sample cost report, no logs needed&lt;/span&gt;
npx @wartzar-bee/tokenscope               &lt;span class="c"&gt;# your most recent Claude Code session&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Attribute tokens per &lt;em&gt;source&lt;/em&gt;, not per message
&lt;/h2&gt;

&lt;p&gt;"This run used 400k tokens" tells you nothing actionable. "This run used 400k tokens and 60% of them are the same three prior task outputs re-sent on every agent handoff" tells you exactly what to fix — cache it, summarize it, or stop broadcasting it. Attribution is what turns a scary number into a diff.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Gate the cost in CI the way you'd gate build time
&lt;/h2&gt;

&lt;p&gt;This is the one that changed how I think about it. A run that suddenly costs 3x more is a regression, the same as a build that suddenly takes 3x longer — it should be able to fail a check, not surprise you in prod.&lt;/p&gt;

&lt;p&gt;The tricky part is estimating a PR's cost &lt;em&gt;statically&lt;/em&gt;, before you spend it. &lt;code&gt;tokenscope scan&lt;/code&gt; does a pre-run token estimate over your agent code:&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="nt"&gt;--dir&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;   &lt;span class="c"&gt;# static, pre-run token estimate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and there's a GitHub Action that wraps it, comments the delta on the files responsible, and can block the PR on a regression. The honest way to adopt it is &lt;strong&gt;&lt;code&gt;mode: warn&lt;/code&gt; first&lt;/strong&gt; — it posts the same verdict and flags the breach in the Actions log, but never fails the build, so you can watch what it &lt;em&gt;would&lt;/em&gt; block for a couple of weeks before turning enforcement on:&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="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Agent cost guardrail&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pull_request&lt;/span&gt;
&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
  &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;        &lt;span class="c1"&gt;# so it can comment the delta on the PR&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cost&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&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;actions/checkout@v4&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;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;       &lt;span class="c1"&gt;# needs the base branch to diff against&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;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;# flag if predicted tokens grow &amp;gt;20% vs base&lt;/span&gt;
          &lt;span class="na"&gt;max-usd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.50&lt;/span&gt;        &lt;span class="c1"&gt;# hard dollar ceiling per run (0 = off)&lt;/span&gt;
          &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sonnet&lt;/span&gt;        &lt;span class="c1"&gt;# price the delta at the right $/1M&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; switch to 'block' when you trust it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you're ready to enforce, drop &lt;code&gt;mode: warn&lt;/code&gt; (it defaults to &lt;code&gt;block&lt;/code&gt;) and the PR can't merge until the regression is addressed. A couple of inputs worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;min-delta-tokens&lt;/code&gt; (default &lt;code&gt;100&lt;/code&gt;) is a noise floor so a tiny base — 10 → 30 tokens, +200% — doesn't fail a build over a fraction of a cent. That's the commonest cost-guardrail false positive, off by default-sane.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;max-tokens&lt;/code&gt; / &lt;code&gt;max-usd&lt;/code&gt; are absolute ceilings that catch a codebase that's &lt;em&gt;already&lt;/em&gt; over budget and creeping up by a small percentage each PR — something a percentage gate alone can never flag.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both the CLI (&lt;code&gt;@wartzar-bee/tokenscope&lt;/code&gt;) and the Action (&lt;code&gt;wartzar-bee/ci-guardrail&lt;/code&gt;) are Apache-2.0 and run locally / in your own runner — nothing phones home, it's reading your session logs and code. They're early and I'm actively working on them, so if you gate agent cost some other way, or the estimate is off for your setup, I genuinely want to hear it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For those of you running agents in a real pipeline — do you gate the cost anywhere, or watch a dashboard and hope? What's caught you out?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cicd</category>
      <category>githubactions</category>
      <category>devops</category>
    </item>
    <item>
      <title>smolagents replays its whole memory every step: the O(n ) token bill nobody mentions</title>
      <dc:creator>wartzar-bee</dc:creator>
      <pubDate>Wed, 29 Jul 2026 04:37:00 +0000</pubDate>
      <link>https://dev.to/wartzarbee/smolagents-replays-its-whole-memory-every-step-the-on2-token-bill-nobody-mentions-5ea3</link>
      <guid>https://dev.to/wartzarbee/smolagents-replays-its-whole-memory-every-step-the-on2-token-bill-nobody-mentions-5ea3</guid>
      <description>&lt;h1&gt;
  
  
  smolagents replays its whole memory every step: the O(n²) token bill nobody mentions
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Cost-audit series, episode 5. 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;smolagents is Hugging Face's deliberately small agent framework — a few thousand lines, "no abstraction on top of abstraction," and 28k+ GitHub stars. Its &lt;code&gt;CodeAgent&lt;/code&gt; is genuinely elegant: the model writes Python, the runtime executes it, the result comes back, repeat until a final answer.&lt;/p&gt;

&lt;p&gt;The elegance hides a cost curve. A smolagents run that takes &lt;em&gt;n&lt;/em&gt; reasoning steps does &lt;strong&gt;not&lt;/strong&gt; cost &lt;em&gt;n&lt;/em&gt; times a single step. On input tokens it costs closer to &lt;em&gt;n²/2&lt;/em&gt;, because &lt;strong&gt;every step re-sends the entire accumulated memory of every previous step&lt;/strong&gt;. The default step budget is 20. A task that genuinely needs a dozen tool calls quietly sends the model its own transcript a dozen times over.&lt;/p&gt;

&lt;p&gt;This audit shows the exact lines, gives you a formula you can evaluate on your own workload, and shows the one-hook fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the tokens go
&lt;/h2&gt;

&lt;p&gt;Every step, the agent rebuilds the full message list it sends to the model. Here is the method that does it, verbatim (&lt;a href="https://github.com/huggingface/smolagents/blob/v1.26.0/src/smolagents/agents.py#L758-L770" rel="noopener noreferrer"&gt;&lt;code&gt;agents.py:758-770&lt;/code&gt;, v1.26.0&lt;/a&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;write_memory_to_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="n"&gt;summary_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Reads past llm_outputs, actions, and observations or errors from the memory into a series of messages
    that can be used as input to the LLM. Adds a number of keywords (such as PLAN, error, etc) to help
    the LLM.
    &lt;/span&gt;&lt;span class="sh"&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_messages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;summary_mode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;memory_step&lt;/span&gt; &lt;span class="ow"&gt;in&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;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;steps&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;memory_step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_messages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;summary_mode&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the loop: it walks &lt;strong&gt;every&lt;/strong&gt; entry in &lt;code&gt;self.memory.steps&lt;/code&gt; and appends its messages. &lt;code&gt;memory.steps&lt;/code&gt; only ever grows — it is a plain list initialised empty and appended to, never trimmed, except by an explicit &lt;code&gt;reset()&lt;/code&gt; between runs (&lt;a href="https://github.com/huggingface/smolagents/blob/v1.26.0/src/smolagents/memory.py#L230" rel="noopener noreferrer"&gt;&lt;code&gt;memory.py:230&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/huggingface/smolagents/blob/v1.26.0/src/smolagents/memory.py#L232-L234" rel="noopener noreferrer"&gt;&lt;code&gt;memory.py:232-234&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That method is called at the top of &lt;strong&gt;every action step&lt;/strong&gt;, with no &lt;code&gt;summary_mode&lt;/code&gt;, so the full history is replayed each time (&lt;a href="https://github.com/huggingface/smolagents/blob/v1.26.0/src/smolagents/agents.py#L1284-L1286" rel="noopener noreferrer"&gt;&lt;code&gt;agents.py:1284-1286&lt;/code&gt;&lt;/a&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="n"&gt;memory_messages&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;write_memory_to_messages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;input_messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;memory_messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default ceiling on how many times this can happen is 20 (&lt;a href="https://github.com/huggingface/smolagents/blob/v1.26.0/src/smolagents/agents.py#L300" rel="noopener noreferrer"&gt;&lt;code&gt;agents.py:300&lt;/code&gt;, &lt;code&gt;max_steps: int = 20&lt;/code&gt;&lt;/a&gt;). Nothing in the default path caps or summarises the growing memory — &lt;code&gt;summary_mode=True&lt;/code&gt; is used only for &lt;em&gt;planning&lt;/em&gt; messages (&lt;a href="https://github.com/huggingface/smolagents/blob/v1.26.0/src/smolagents/agents.py#L684" rel="noopener noreferrer"&gt;&lt;code&gt;agents.py:684&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/huggingface/smolagents/blob/v1.26.0/src/smolagents/agents.py#L886" rel="noopener noreferrer"&gt;&lt;code&gt;agents.py:886&lt;/code&gt;&lt;/a&gt;), not for the main action loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The math (evaluate it on your own numbers)
&lt;/h2&gt;

&lt;p&gt;Let:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;P&lt;/code&gt; = tokens in the system prompt (fixed, sent every step)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;s&lt;/code&gt; = tokens each completed step &lt;em&gt;adds&lt;/em&gt; to memory — the model's code/thought plus the tool observation it produced&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At step &lt;em&gt;k&lt;/em&gt; (1-indexed) the input the model receives is &lt;code&gt;P + (k-1)·s&lt;/code&gt; — the prompt plus everything the previous &lt;em&gt;k-1&lt;/em&gt; steps left behind. Summed over an &lt;em&gt;n&lt;/em&gt;-step run, cumulative &lt;strong&gt;input&lt;/strong&gt; tokens are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Σ (k=1..n) [ P + (k-1)·s ]  =  n·P  +  s · n(n-1)/2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;s · n(n-1)/2&lt;/code&gt; term is quadratic in &lt;em&gt;n&lt;/em&gt;. Compare it to the intuition most people price with — "&lt;em&gt;n&lt;/em&gt; steps ≈ &lt;em&gt;n&lt;/em&gt; × one step" = &lt;code&gt;n·(P + s)&lt;/code&gt;. The history you re-pay for is &lt;code&gt;n(n-1)/2 · s&lt;/code&gt; instead of &lt;code&gt;n · s&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Steps &lt;em&gt;n&lt;/em&gt;
&lt;/th&gt;
&lt;th&gt;History replays (× &lt;code&gt;s&lt;/code&gt;), naive&lt;/th&gt;
&lt;th&gt;History replays (× &lt;code&gt;s&lt;/code&gt;), actual&lt;/th&gt;
&lt;th&gt;Overpay factor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;1.5×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;3.5×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;td&gt;5.5×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20 (max)&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;190&lt;/td&gt;
&lt;td&gt;9.5×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;(Table is illustrative of the formula above — it is the closed form &lt;code&gt;n(n-1)/2&lt;/code&gt; vs &lt;code&gt;n&lt;/code&gt;, not a measured run. Plug in your own &lt;code&gt;P&lt;/code&gt; and &lt;code&gt;s&lt;/code&gt; to get dollars.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The observation size &lt;code&gt;s&lt;/code&gt; is where it bites hardest. If a tool returns a chunk of a web page, a file, or a dataframe, that payload is now re-sent on &lt;strong&gt;every subsequent step&lt;/strong&gt; for the rest of the run. Long, tool-heavy tasks are exactly the ones that hit &lt;code&gt;max_steps&lt;/code&gt;, so the worst tasks pay the worst multiplier.&lt;/p&gt;

&lt;p&gt;Good news: smolagents already &lt;strong&gt;measures&lt;/strong&gt; this for you. Every &lt;code&gt;ActionStep&lt;/code&gt; carries a &lt;code&gt;token_usage&lt;/code&gt; field with input/output token counts (&lt;a href="https://github.com/huggingface/smolagents/blob/v1.26.0/src/smolagents/memory.py#L63" rel="noopener noreferrer"&gt;&lt;code&gt;memory.py:63&lt;/code&gt;&lt;/a&gt;). After a run, sum &lt;code&gt;step.token_usage.input_tokens&lt;/code&gt; across &lt;code&gt;agent.memory.steps&lt;/code&gt; and you will see the curve directly. The problem is that by the time you read it, you have already paid.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: prune the replayed history with a step callback
&lt;/h2&gt;

&lt;p&gt;smolagents gives you the exact hook you need. The agent accepts &lt;code&gt;step_callbacks&lt;/code&gt; — callables invoked at the end of each step, and you can register them per step-type (&lt;a href="https://github.com/huggingface/smolagents/blob/v1.26.0/src/smolagents/agents.py#L282" rel="noopener noreferrer"&gt;&lt;code&gt;agents.py:282&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/huggingface/smolagents/blob/v1.26.0/src/smolagents/agents.py#L304" rel="noopener noreferrer"&gt;&lt;code&gt;agents.py:304&lt;/code&gt;&lt;/a&gt;, wired in &lt;a href="https://github.com/huggingface/smolagents/blob/v1.26.0/src/smolagents/agents.py#L416-L425" rel="noopener noreferrer"&gt;&lt;code&gt;_setup_step_callbacks&lt;/code&gt;, &lt;code&gt;agents.py:416-425&lt;/code&gt;&lt;/a&gt;). Because &lt;code&gt;memory.steps&lt;/code&gt; is just a list you own, a callback can cap how much history survives into the next &lt;code&gt;write_memory_to_messages&lt;/code&gt; call:&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;smolagents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CodeAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ActionStep&lt;/span&gt;

&lt;span class="n"&gt;KEEP_LAST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;  &lt;span class="c1"&gt;# replay only the most recent N action steps
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;trim_memory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;action_steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ActionStep&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;stale&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;action_steps&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;KEEP_LAST&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="c1"&gt;# collapse the bulky observation; keep a short marker so the model
&lt;/span&gt;        &lt;span class="c1"&gt;# still knows the step happened
&lt;/span&gt;        &lt;span class="n"&gt;stale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[trimmed to control context cost]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CodeAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[...],&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;step_callbacks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;trim_memory&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Illustrative usage of the real &lt;code&gt;step_callbacks&lt;/code&gt; API — tune &lt;code&gt;KEEP_LAST&lt;/code&gt; and what you collapse to your task. The point is that the hook is first-class, not that these exact lines ship in the library.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This turns the input curve from quadratic back toward linear: a fixed window of history instead of an ever-growing one. You trade some long-range recall for a bounded bill — for most tool-loop tasks that is the right trade, and you make it deliberately instead of discovering it on an invoice.&lt;/p&gt;

&lt;p&gt;Other levers, in order of bluntness: lower &lt;code&gt;max_steps&lt;/code&gt; from the default 20 so a wandering run can't rack up 190× history replays; truncate large tool return values &lt;em&gt;before&lt;/em&gt; they enter memory; and use the planning/summary path smolagents already has for long-horizon tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  See the bill before you run it
&lt;/h2&gt;

&lt;p&gt;The pattern in this series is always the same: the framework is fine, the &lt;strong&gt;default&lt;/strong&gt; is expensive, and the cost is invisible until it shows up on the invoice. smolagents is the most honest case yet — it even hands you &lt;code&gt;token_usage&lt;/code&gt; — but you still have to run the task, at full quadratic cost, to see it.&lt;/p&gt;

&lt;p&gt;That is the gap &lt;a href="https://www.npmjs.com/package/@wartzar-bee/tokenscope" rel="noopener noreferrer"&gt;&lt;strong&gt;tokenscope&lt;/strong&gt;&lt;/a&gt; closes — it shows what a run actually cost, and estimates a source tree's token footprint &lt;em&gt;before&lt;/em&gt; you spend it. See a real cost breakdown in five seconds, no setup or logs required:&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run it on your own most-recent Claude Code session (just &lt;code&gt;npx @wartzar-bee/tokenscope&lt;/code&gt;), or estimate a directory's token cost &lt;em&gt;before&lt;/em&gt; a run — the static check that powers the guardrail:&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="nt"&gt;--dir&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want that check enforced automatically — a bot that comments the predicted token-cost delta on the responsible files in every pull request and can block a regression — that is what we build the &lt;a href="https://github.com/wartzar-bee/ci-guardrail" rel="noopener noreferrer"&gt;ci-guardrail GitHub Action&lt;/a&gt; for. It's one copy-paste step — no account, no proxy:&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;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;# fail the PR if token cost grows &amp;gt;20% vs the base branch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;Next in the series: we turn the audits into a checklist — the five context-cost anti-patterns that show up in almost every agent framework, and the one-line review question that catches each. Follow &lt;a href="https://dev.to/wartzarbee"&gt;@wartzarbee&lt;/a&gt; so you don't miss it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Found an error in this audit? The whole point is that every number is reproducible — reply with the line and I'll fix it in public.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>llm</category>
      <category>aiagents</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>LangGraph isn't cheaper than LangChain — unless you opt out of its defaults</title>
      <dc:creator>wartzar-bee</dc:creator>
      <pubDate>Tue, 28 Jul 2026 09:56:41 +0000</pubDate>
      <link>https://dev.to/wartzarbee/langgraph-isnt-cheaper-than-langchain-unless-you-opt-out-of-its-defaults-4cdb</link>
      <guid>https://dev.to/wartzarbee/langgraph-isnt-cheaper-than-langchain-unless-you-opt-out-of-its-defaults-4cdb</guid>
      <description>&lt;h1&gt;
  
  
  LangGraph isn't cheaper than LangChain — unless you opt out of its defaults
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Cost-audit series, episode 4. 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;When LangChain deprecated &lt;code&gt;ConversationBufferMemory&lt;/code&gt; (the subject of episode 1 in this series), the official migration path was LangGraph. The pitch: explicit state management, you control exactly what flows where. More expressive, more controllable.&lt;/p&gt;

&lt;p&gt;It is — but only if you reach for the controls. &lt;strong&gt;The default state model in LangGraph has the same unbounded-growth problem as the memory it replaced.&lt;/strong&gt; Teams migrating to escape ConversationBufferMemory's cost curve often land on an identical curve, with new graph complexity on top.&lt;/p&gt;

&lt;p&gt;This audit shows exactly where the default grows, what it costs, and what opt-outs exist.&lt;/p&gt;




&lt;h2&gt;
  
  
  The default: &lt;code&gt;MessagesState&lt;/code&gt; + &lt;code&gt;add_messages&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The quickstart in LangGraph's own docs uses this pattern:&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;langgraph.graph&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StateGraph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MessagesState&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_node&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;MessagesState&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;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# sends ALL messages to the LLM
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&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;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;

&lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StateGraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MessagesState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_node&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;MessagesState&lt;/code&gt; is a &lt;code&gt;TypedDict&lt;/code&gt; with a single key, &lt;code&gt;messages&lt;/code&gt;, backed by the &lt;code&gt;add_messages&lt;/code&gt; reducer. Here's what that reducer does:&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;# langgraph/graph/message.py — add_messages (def at line 18; merge loop below)
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_messages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&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;right&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="o"&gt;-&amp;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;# ... (coerces left/right to lists of BaseMessage) ...
&lt;/span&gt;    &lt;span class="n"&gt;left_idx_by_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="n"&gt;merged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;ids_to_remove&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existing_idx&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;left_idx_by_id&lt;/span&gt;&lt;span class="p"&gt;.&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;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&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="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RemoveMessage&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;ids_to_remove&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&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;merged&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;existing_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;      &lt;span class="c1"&gt;# same id → update in place
&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;merged&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;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                  &lt;span class="c1"&gt;# new id → APPEND (the list grows)
&lt;/span&gt;    &lt;span class="n"&gt;merged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;merged&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ids_to_remove&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;merged&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source: &lt;a href="https://github.com/langchain-ai/langgraph/blob/0.2.60/libs/langgraph/langgraph/graph/message.py" rel="noopener noreferrer"&gt;&lt;code&gt;langgraph/graph/message.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is not a summarizer, not a window, not a trimmer. It is an append-only list. Every message ever added to state stays in state — and every node that reads &lt;code&gt;state["messages"]&lt;/code&gt; sees the full list.&lt;/p&gt;

&lt;p&gt;This is &lt;code&gt;ConversationBufferMemory&lt;/code&gt; with a graph wrapper.&lt;/p&gt;




&lt;h2&gt;
  
  
  The cost math
&lt;/h2&gt;

&lt;p&gt;Assume a conversational agent: 10 turns, 150 tokens per user message, 200 tokens per assistant reply (modest — a short answer each time).&lt;/p&gt;

&lt;p&gt;After 10 turns, &lt;code&gt;state["messages"]&lt;/code&gt; contains 20 messages = (10 × 150) + (10 × 200) = &lt;strong&gt;3,500 tokens&lt;/strong&gt; of accumulated history.&lt;/p&gt;

&lt;p&gt;For the &lt;em&gt;11th&lt;/em&gt; call, the node sends all 3,500 tokens of prior history as context, then generates a new reply. Each further turn adds another 350 tokens (150 user + 200 assistant), so the &lt;em&gt;12th&lt;/em&gt; call sends 3,850, the &lt;em&gt;13th&lt;/em&gt; 4,200, and so on.&lt;/p&gt;

&lt;p&gt;Total input tokens for a 20-turn conversation:&lt;/p&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;Messages in state&lt;/th&gt;
&lt;th&gt;Input tokens (messages + system)&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;0 prior&lt;/td&gt;
&lt;td&gt;150 + 400 (system)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4 prior turns&lt;/td&gt;
&lt;td&gt;1,550 + 400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;9 prior turns&lt;/td&gt;
&lt;td&gt;3,300 + 400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;14 prior turns&lt;/td&gt;
&lt;td&gt;5,050 + 400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;19 prior turns&lt;/td&gt;
&lt;td&gt;6,800 + 400&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;strong&gt;~77,500 tokens input&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;(Each call = 400 system + 150 current user + (turn−1) × 350 accumulated history.)&lt;/p&gt;

&lt;p&gt;A naive estimate (flat 550 tokens/call × 20 calls) = 11,000 tokens.&lt;br&gt;&lt;br&gt;
Actual with &lt;code&gt;add_messages&lt;/code&gt; default = ~77,500 tokens. &lt;strong&gt;7× over.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With claude-haiku-4-5 ($0.80/M input, $4/M output) for a chatbot doing 500 conversations/day:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Naive estimate:&lt;/strong&gt; 11,000 × 500 × 30 × $0.80/M = &lt;strong&gt;$132/month&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Actual:&lt;/strong&gt; 77,500 × 500 × 30 × $0.80/M = &lt;strong&gt;$930/month&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's $798/month of silent overspend on input tokens alone, just from the default accumulation — before you add nodes, tools, or memory.&lt;/p&gt;


&lt;h2&gt;
  
  
  Multiplier 1: multi-node graphs (each node pays the full state)
&lt;/h2&gt;

&lt;p&gt;LangGraph's value over a simple chat loop is composing multiple nodes — a router, a tool-caller, a summarizer, a responder. Each node that reads &lt;code&gt;state["messages"]&lt;/code&gt; pays the full token cost of the accumulated message list.&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;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StateGraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MessagesState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;router&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;route_node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# reads state["messages"]
&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_caller&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# reads state["messages"]
&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;responder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;respond_node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# reads state["messages"]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a 3-node graph where each node reads messages, a single user turn that passes through all three nodes costs &lt;strong&gt;3×&lt;/strong&gt; the message-list tokens. After 10 turns with 3,500 accumulated tokens, one user message costs: 3 × 3,500 = 10,500 tokens just for message history, before any node-specific prompts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Multiplier 2: &lt;code&gt;interrupt_before&lt;/code&gt; / &lt;code&gt;interrupt_after&lt;/code&gt; (human-in-the-loop)
&lt;/h2&gt;

&lt;p&gt;LangGraph's human-in-the-loop feature pauses graph execution at a node boundary. When the graph resumes, it deserializes the full checkpointed state and re-injects it into the next node:&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;# langgraph/pregel/__init__.py — Pregel.astream (v0.2.60), the entrypoint
# that drives interruptible execution. Verbatim signature:
&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;astream&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="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;RunnableConfig&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="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;stream_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;StreamMode&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;StreamMode&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="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;output_keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&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="nb"&gt;str&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="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;interrupt_before&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;All&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="nb"&gt;str&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="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;interrupt_after&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;All&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="nb"&gt;str&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="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bool&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="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;subgraphs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&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;AsyncIterator&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;Any&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;Source: &lt;a href="https://github.com/langchain-ai/langgraph/blob/0.2.60/libs/langgraph/langgraph/pregel/__init__.py" rel="noopener noreferrer"&gt;&lt;code&gt;langgraph/pregel/__init__.py&lt;/code&gt;&lt;/a&gt; — search the file for &lt;code&gt;async def astream&lt;/code&gt; (defined ~line 1683; &lt;code&gt;interrupt_before&lt;/code&gt;/&lt;code&gt;interrupt_after&lt;/code&gt; are the pause controls). When a run resumes after an interrupt, Pregel reloads the pending state from the checkpointer (the &lt;code&gt;aget_tuple&lt;/code&gt;/&lt;code&gt;aget_state&lt;/code&gt; path returns the full checkpoint blob — every message included) before continuing at the next node.&lt;/p&gt;

&lt;p&gt;The cost: full state deserialization on every resume. If a workflow interrupts 3 times before completion (a common approval flow), and the state has 5,000 tokens of messages, the resumption overhead alone is 3 × 5,000 = 15,000 extra tokens — paid every time, even if the approval is just a "yes."&lt;/p&gt;




&lt;h2&gt;
  
  
  Multiplier 3: parallel fan-out (&lt;code&gt;Send&lt;/code&gt; API)
&lt;/h2&gt;

&lt;p&gt;LangGraph's &lt;code&gt;Send&lt;/code&gt; API dispatches parallel subgraph invocations, each receiving a copy of 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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langgraph.types&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Send&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fanout_node&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;MessagesState&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;Send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;worker_a&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;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&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;task&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;summarize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="nc"&gt;Send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;worker_b&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;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&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;task&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;critique&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="nc"&gt;Send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;worker_c&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;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&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;task&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;expand&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;Source: &lt;a href="https://github.com/langchain-ai/langgraph/blob/0.2.60/libs/langgraph/langgraph/types.py" rel="noopener noreferrer"&gt;&lt;code&gt;langgraph/types.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each &lt;code&gt;Send&lt;/code&gt; carries the full &lt;code&gt;state["messages"]&lt;/code&gt; to the worker node. With 3 workers and 5,000 tokens of history: &lt;strong&gt;15,000 tokens dispatched&lt;/strong&gt; in the fan-out alone. If those workers themselves call an LLM, each call pays the full 5,000-token history again. Compare to the CrewAI quadratic problem from episode 3 — this is the same failure mode, different API.&lt;/p&gt;




&lt;h2&gt;
  
  
  The opt-outs (LangGraph actually provides them)
&lt;/h2&gt;

&lt;p&gt;Unlike ConversationBufferMemory (which had no good trim story), LangGraph ships built-in tools to fix this. Teams just don't use them by default.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trim messages before every LLM call
&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;langchain_core.messages&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;trim_messages&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_node&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;MessagesState&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;trimmed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;trim_messages&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;max_tokens&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="c1"&gt;# hard cap
&lt;/span&gt;        &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;last&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;# keep the most recent
&lt;/span&gt;        &lt;span class="n"&gt;token_counter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;# use the model's tokenizer
&lt;/span&gt;        &lt;span class="n"&gt;include_system&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;# always keep the system message
&lt;/span&gt;        &lt;span class="n"&gt;allow_partial&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# sends trimmed history, not full list
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&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;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;Source: &lt;a href="https://github.com/langchain-ai/langchain/blob/langchain-core==0.3.28/libs/core/langchain_core/messages/utils.py" rel="noopener noreferrer"&gt;&lt;code&gt;langchain_core/messages/utils.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This keeps the full history in state (for checkpointing, human inspection) while capping what the LLM actually sees. Applying a 2,000-token cap on a 20-turn conversation reduces input tokens from ~77,500 to ~40,000 (2,000 tokens × 20 calls). &lt;strong&gt;~48% cost reduction, one line change.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pass only what the node needs
&lt;/h3&gt;

&lt;p&gt;Instead of giving every node the full &lt;code&gt;state["messages"]&lt;/code&gt;, scope what each node receives:&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;class&lt;/span&gt; &lt;span class="nc"&gt;MyState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TypedDict&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;Annotated&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;BaseMessage&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;add_messages&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;last_tool_result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;        &lt;span class="c1"&gt;# structured, compact
&lt;/span&gt;    &lt;span class="n"&gt;task_description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;        &lt;span class="c1"&gt;# set once, doesn't grow
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tool_caller_node&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;MyState&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# This node only needs the task + last result — not the full conversation
&lt;/span&gt;    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Task: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;task_description&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Context: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;last_tool_result&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;last_tool_result&lt;/span&gt;&lt;span class="sh"&gt;"&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;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;tool_caller_node&lt;/code&gt; never touches &lt;code&gt;state["messages"]&lt;/code&gt; — it pays zero for message accumulation. Only the nodes that genuinely need conversational context receive it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summarize periodically (the right way)
&lt;/h3&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;maybe_summarize&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;MessagesState&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;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                &lt;span class="c1"&gt;# threshold: tune to your cost tolerance
&lt;/span&gt;        &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="nc"&gt;SystemMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summarize this conversation in 3 sentences.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&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="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&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;span class="nc"&gt;SystemMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Conversation summary: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&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="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="c1"&gt;# keep the last human message
&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;    &lt;span class="c1"&gt;# keep the last assistant message
&lt;/span&gt;            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;   &lt;span class="c1"&gt;# no change needed
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This collapses the accumulated history into a single system message at the summarization trigger. After summarization, the effective context is ~200 tokens (summary + last exchange) instead of 3,500+. Insert &lt;code&gt;maybe_summarize&lt;/code&gt; as an always-on node before expensive LLM calls.&lt;/p&gt;




&lt;h2&gt;
  
  
  Measuring your own graph
&lt;/h2&gt;

&lt;p&gt;LangGraph's built-in tracing (via LangSmith) shows per-node token usage, but it's behind a paid tier for production volumes. For a free alternative that works with any JSONL export:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @wartzar-bee/tokenscope
npx @wartzar-bee/tokenscope &lt;span class="nt"&gt;--demo&lt;/span&gt;   &lt;span class="c"&gt;# instant sample, no setup&lt;/span&gt;
npx @wartzar-bee/tokenscope langgraph-session.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output shows the session total, how much of each call is re-sent accumulated context versus new work, and where the growth is steepest — the same view that surfaced the 136M-token burn in episode 1.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;LangGraph's &lt;code&gt;MessagesState&lt;/code&gt; + &lt;code&gt;add_messages&lt;/code&gt; default is ConversationBufferMemory under a new name. The graph model gives you explicit controls that the old memory API lacked — but the controls are opt-in. Without &lt;code&gt;trim_messages&lt;/code&gt;, selective state reads, or periodic summarization, migration from LangChain to LangGraph buys you graph expressiveness at the same (or higher, for multi-node graphs) token cost.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Cost impact&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;MessagesState&lt;/code&gt; default&lt;/td&gt;
&lt;td&gt;7× over naive estimate at 20 turns&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;trim_messages&lt;/code&gt; before every LLM call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-node graph, all nodes read messages&lt;/td&gt;
&lt;td&gt;N× multiplier (N = node count)&lt;/td&gt;
&lt;td&gt;Scope state: only pass what each node needs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;interrupt&lt;/code&gt;/resume&lt;/td&gt;
&lt;td&gt;Full state re-injected on every resume&lt;/td&gt;
&lt;td&gt;Summarize before checkpointing at long sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Send&lt;/code&gt; fan-out&lt;/td&gt;
&lt;td&gt;Parallel full-state copies&lt;/td&gt;
&lt;td&gt;Pass minimal substate to each worker&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The migration from LangChain to LangGraph is worth it — but only once you understand and explicitly opt out of these defaults. Otherwise you're paying for the complexity without the savings.&lt;/p&gt;

&lt;p&gt;Once you've opted out of the expensive defaults, the way to keep them out is to measure cost on every change. &lt;a href="https://www.npmjs.com/package/@wartzar-bee/tokenscope" rel="noopener noreferrer"&gt;tokenscope&lt;/a&gt; gives you the per-run number locally (&lt;code&gt;npx @wartzar-bee/tokenscope --demo&lt;/code&gt;), and our &lt;a href="https://github.com/wartzar-bee/ci-guardrail" rel="noopener noreferrer"&gt;ci-guardrail&lt;/a&gt; GitHub Action fails a pull request when a change pushes token cost past a budget you set — so a silent 7× regression like this one gets caught in review, not on the invoice.&lt;/p&gt;

&lt;p&gt;It's one copy-paste step in a workflow — no account, no proxy:&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;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;# fail the PR if token cost grows &amp;gt;20% vs the base branch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;wartzar-bee builds tools for operating cost-efficient autonomous agents: &lt;a href="https://www.npmjs.com/package/@wartzar-bee/tokenscope" rel="noopener noreferrer"&gt;tokenscope&lt;/a&gt; for token-cost measurement, &lt;a href="https://github.com/wartzar-bee/ci-guardrail" rel="noopener noreferrer"&gt;ci-guardrail&lt;/a&gt; to gate cost regressions in CI, and &lt;a href="https://github.com/wartzar-bee/enclave" rel="noopener noreferrer"&gt;enclave&lt;/a&gt; — the open-source sandboxed runtime we run our own agent fleet on. All free and open-source. &lt;a href="https://dev.to/wartzarbee"&gt;Follow on dev.to →&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>llm</category>
      <category>aiagents</category>
      <category>python</category>
      <category>langgraph</category>
    </item>
    <item>
      <title>CrewAI's quadratic context problem: why a 5-agent crew costs 6 more than you expect</title>
      <dc:creator>wartzar-bee</dc:creator>
      <pubDate>Sun, 26 Jul 2026 03:55:49 +0000</pubDate>
      <link>https://dev.to/wartzarbee/crewais-quadratic-context-problem-why-a-5-agent-crew-costs-6x-more-than-you-expect-3ol1</link>
      <guid>https://dev.to/wartzarbee/crewais-quadratic-context-problem-why-a-5-agent-crew-costs-6x-more-than-you-expect-3ol1</guid>
      <description>&lt;h1&gt;
  
  
  CrewAI's quadratic context problem: why a 5-agent crew costs 6× more than you expect
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Cost-audit series, episode 3. 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;CrewAI is one of the most-starred agent orchestration frameworks on GitHub. Its pitch is intuitive: define a crew of role-playing agents, assign tasks, watch them collaborate. What the README doesn't tell you is that the default context-passing model has a &lt;strong&gt;quadratic token cost curve&lt;/strong&gt;. A 5-agent crew doesn't cost 5× a solo agent — on input tokens it costs closer to 6×, and once you stack memory, delegation, and verbose mode it climbs to 8–15×. The multiplier grows with every agent or task you add.&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 architecture in one paragraph
&lt;/h2&gt;

&lt;p&gt;CrewAI executes tasks sequentially (by default). Each task has an optional &lt;code&gt;context&lt;/code&gt; list — a list of other tasks whose outputs should be available to the executing agent. When you don't specify &lt;code&gt;context&lt;/code&gt; explicitly, CrewAI's default behavior is to make all &lt;em&gt;previously completed tasks&lt;/em&gt; available to each subsequent agent. The output of Task 1 goes into Task 2's prompt. Task 1 + Task 2 outputs go into Task 3's prompt. And so on.&lt;/p&gt;

&lt;p&gt;This is linear accumulation — and it produces a quadratic total token count.&lt;/p&gt;




&lt;h2&gt;
  
  
  The code that does it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;src/crewai/crew.py&lt;/code&gt;&lt;/strong&gt; — the &lt;code&gt;Crew._get_context&lt;/code&gt; method (line 792 at tag 0.80.0):&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_context&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;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task_outputs&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;TaskOutput&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;aggregate_raw_outputs_from_tasks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;aggregate_raw_outputs_from_task_outputs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task_outputs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source: &lt;a href="https://github.com/crewAIInc/crewAI/blob/0.80.0/src/crewai/crew.py#L792" rel="noopener noreferrer"&gt;&lt;code&gt;src/crewai/crew.py#L792&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The decisive branch is the &lt;code&gt;else&lt;/code&gt;: when a task sets &lt;strong&gt;no explicit &lt;code&gt;context&lt;/code&gt;&lt;/strong&gt;, CrewAI falls back to &lt;code&gt;aggregate_raw_outputs_from_task_outputs(task_outputs)&lt;/code&gt; — the outputs of &lt;em&gt;all&lt;/em&gt; prior tasks. That aggregation helper (in &lt;code&gt;crewai/utilities&lt;/code&gt;) serializes each prior task's full raw output into the string that gets injected into the current task's prompt. There is no summarization, no truncation, no deduplication. The string grows with every task.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;src/crewai/crew.py&lt;/code&gt;&lt;/strong&gt; — the task execution loop lives in &lt;code&gt;Crew._execute_tasks&lt;/code&gt; (defined at line 635 at tag 0.80.0; the context-injection call is at line 696). Simplified to the two lines that matter:&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;# inside _execute_tasks, iterating over the crew's tasks:
&lt;/span&gt;&lt;span class="n"&gt;context&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_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task_outputs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# task_outputs = every prior task's output
&lt;/span&gt;&lt;span class="n"&gt;task_output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agent_to_use&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agent_to_use&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source: &lt;a href="https://github.com/crewAIInc/crewAI/blob/0.80.0/src/crewai/crew.py" rel="noopener noreferrer"&gt;&lt;code&gt;src/crewai/crew.py&lt;/code&gt;&lt;/a&gt; (method &lt;code&gt;_execute_tasks&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;task_outputs&lt;/code&gt; is the running list of every prior task's output, so it grows as the crew progresses. Each call to &lt;code&gt;execute_sync&lt;/code&gt; constructs a full prompt that includes that accumulated context string, and it is sent with &lt;strong&gt;every&lt;/strong&gt; LLM API request — not cached between tasks (CrewAI doesn't use prompt caching by default).&lt;/p&gt;




&lt;h2&gt;
  
  
  The math
&lt;/h2&gt;

&lt;p&gt;Assume:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;N = 5 tasks&lt;/strong&gt; (a typical research + writing crew)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;T_task = 500 tokens&lt;/strong&gt; per task output (modest: a paragraph or a JSON object)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;T_system = 800 tokens&lt;/strong&gt; per agent's system prompt + task description (role, goal, backstory, task instructions)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Token count for Task k = T_system + (k-1) × T_task (accumulated context from all prior tasks)&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Context tokens&lt;/th&gt;
&lt;th&gt;System + task&lt;/th&gt;
&lt;th&gt;Total input tokens&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;0&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;1,300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;1,800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1,500&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;2,300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2,000&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;2,800&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;strong&gt;5,000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4,000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9,000&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A naive model would predict 5 × 800 = 4,000 input tokens. The actual bill is &lt;strong&gt;9,000&lt;/strong&gt; — 2.25× for just five tasks with modest outputs. Now scale up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;T_task = 2,000 tokens&lt;/strong&gt; (a realistic research output, a code block, a structured list):&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Context tokens&lt;/th&gt;
&lt;th&gt;System + task&lt;/th&gt;
&lt;th&gt;Total input tokens&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;0&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2,000&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;2,800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4,000&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;4,800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;6,000&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;6,800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;8,000&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;8,800&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;strong&gt;20,000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4,000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;24,000&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now you're at &lt;strong&gt;6×&lt;/strong&gt; the naive estimate — and that's &lt;em&gt;input tokens only&lt;/em&gt;. Add output tokens from each task (another ~2,000 × 5 = 10,000) and the total API cost for one crew run is 34,000 tokens instead of the ~14,000 you'd expect.&lt;/p&gt;

&lt;p&gt;With claude-sonnet-4-6 ($3/M input, $15/M output):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Naive estimate:&lt;/strong&gt; (4,000 × $3 + 10,000 × $15) / 1,000,000 = $0.162&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Actual:&lt;/strong&gt; (24,000 × $3 + 10,000 × $15) / 1,000,000 = &lt;strong&gt;$0.222&lt;/strong&gt; — 37% over&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now run 50 crew executions per day:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Naive:&lt;/strong&gt; $0.162 × 50 × 30 = $243/month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Actual:&lt;/strong&gt; $0.222 × 50 × 30 = &lt;strong&gt;$333/month&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a SaaS product with hundreds of daily crew runs, this gap becomes tens of thousands of dollars per month — and it gets worse as you add agents or as task outputs grow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three multipliers on top of the base cost
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Memory layers (each one adds tokens)
&lt;/h3&gt;

&lt;p&gt;CrewAI's &lt;code&gt;memory=True&lt;/code&gt; flag (off by default, but heavily promoted) activates four memory systems:&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;# src/crewai/memory/short_term/short_term_memory.py
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShortTermMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search&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;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score_threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.35&lt;/span&gt;&lt;span class="p"&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;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score_threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;score_threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source: &lt;a href="https://github.com/crewAIInc/crewAI/blob/0.80.0/src/crewai/memory/short_term/short_term_memory.py" rel="noopener noreferrer"&gt;&lt;code&gt;src/crewai/memory/short_term/short_term_memory.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Short-term memory retrieves every stored item scoring above a similarity threshold (default &lt;code&gt;0.35&lt;/code&gt;) and appends them to the prompt — the count is unbounded, so a longer run history injects more retrieved context per task. Long-term memory hits an SQLite database. Entity memory maintains structured entity descriptions. Semantic memory uses ChromaDB embeddings — each retrieval costs an embedding API call &lt;em&gt;plus&lt;/em&gt; the tokens from the retrieved chunks injected into the prompt.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;memory=True&lt;/code&gt; on a 10-run crew history, expect &lt;strong&gt;+1,500–3,000 tokens per task&lt;/strong&gt; in retrieval overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;verbose=True&lt;/code&gt; (the default in most tutorials)
&lt;/h3&gt;

&lt;p&gt;Most CrewAI tutorials set &lt;code&gt;verbose=True&lt;/code&gt; or &lt;code&gt;verbose=2&lt;/code&gt;. This surfaces the agent's intermediate reasoning — but the real cost driver isn't the logging, it's the ReAct loop underneath it. CrewAI runs its own &lt;code&gt;CrewAgentExecutor&lt;/code&gt; (not LangChain's), and each agent step accumulates the full "Thought / Action / Observation" trace back into the LLM prompt for the next iteration. Each tool call adds another round of Thought + Action + Observation tokens before the final answer. For an agent that calls 3 tools, this can add &lt;strong&gt;800–2,000 tokens&lt;/strong&gt; per task.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Human delegation (&lt;code&gt;allow_delegation=True&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;When an agent can delegate to another, it can route subtasks to specialist agents mid-task. This is CrewAI's "hierarchical" feature. The cost: each delegation creates &lt;strong&gt;a new complete LLM call&lt;/strong&gt; with the delegating agent's accumulated context inherited into the delegatee's prompt. A single task can spawn 2–3 delegation chains, each carrying the full context blob from above.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a real run looks like
&lt;/h2&gt;

&lt;p&gt;Let's measure a concrete 3-agent research crew using &lt;code&gt;@wartzar-bee/tokenscope&lt;/code&gt;:&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;# instant sample, no setup&lt;/span&gt;
npx @wartzar-bee/tokenscope crew-session.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a crew with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Researcher agent: finds + summarizes 3 sources (outputs ~1,800 tokens)&lt;/li&gt;
&lt;li&gt;Analyst agent: interprets the research (outputs ~1,200 tokens)
&lt;/li&gt;
&lt;li&gt;Writer agent: produces a structured report (outputs ~2,000 tokens)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The per-agent context accumulation (illustrative — tokenscope reports the session total you can check this against, not a per-agent split):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Per-agent context accumulation:
  researcher  →  input:  1,100  output:  1,800   (system + task only)
  analyst     →  input:  2,900  output:  1,200   (+1,800 context from researcher)
  writer      →  input:  4,100  output:  2,000   (+1,800 + 1,200 context from prior two)

  Total input:    8,100
  Total output:   5,000
  Session total: 13,100 tokens

  Naive estimate (no context accumulation): 8,300 tokens
  Actual multiplier: 1.58×
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a &lt;em&gt;modest&lt;/em&gt; crew. Bump to 5 agents with research-heavy outputs and the multiplier reaches 4–6×. Add memory and delegation: 8–15×.&lt;/p&gt;




&lt;h2&gt;
  
  
  The fix: explicit context scoping
&lt;/h2&gt;

&lt;p&gt;CrewAI lets you control which tasks feed context to which. Use the &lt;code&gt;context&lt;/code&gt; parameter explicitly:&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;crewai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;

&lt;span class="n"&gt;research_task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Find the top 3 cloud cost optimization techniques.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;researcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# no context — this is the first task
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;analysis_task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Analyze the techniques and rank by ROI.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;analyst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;research_task&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# only research output, not all prior tasks
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;writing_task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write a 500-word summary of the #1 technique.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;analysis_task&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# only the analysis — researcher output not needed here
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: the writer agent sees only the analyst's output (~1,200 tokens), not the researcher's raw output + the analyst's output (3,000 tokens). Context tokens halved on the most expensive task.&lt;/p&gt;

&lt;p&gt;For longer pipelines, consider a &lt;strong&gt;summary task&lt;/strong&gt;: a cheap, short-output task that condenses prior results, and only &lt;em&gt;its&lt;/em&gt; output flows forward. The cost of the summarization step is far less than sending raw outputs through N subsequent agents.&lt;/p&gt;




&lt;h2&gt;
  
  
  Measuring your own crew
&lt;/h2&gt;

&lt;p&gt;If you're running CrewAI in production, the default LLM logging doesn't surface how much of each call is re-sent accumulated context. Point tokenscope at a session transcript to see it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @wartzar-bee/tokenscope
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Export your run as a session JSONL (a Claude Code session under &lt;code&gt;~/.claude/projects&lt;/code&gt;, or any transcript in that format), then:&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 crew-session.jsonl
&lt;span class="c"&gt;# machine-readable:&lt;/span&gt;
npx @wartzar-bee/tokenscope crew-session.jsonl &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see the session total, how much is new work versus re-sent accumulated context, and where the growth is steepest — the accumulation this whole post is about.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;CrewAI's default context model accumulates all prior task outputs into every subsequent agent's prompt. This produces a quadratic total token count as N grows — not linear. The practical impact at modest scale (5 agents, 2,000-token outputs): &lt;strong&gt;4–6× the token count you'd expect&lt;/strong&gt;. Add memory layers, delegation, and verbose mode: &lt;strong&gt;8–15×&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The fix is explicit context scoping: pass only the task outputs that each agent actually needs. It's a one-line change per task, and it can halve your API bill immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next in the series:&lt;/strong&gt; we'll look at LangGraph's token footprint — the stateful graph model has a different cost shape, and it's worth understanding before you migrate from LangChain to LangGraph chasing efficiency gains.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;wartzar-bee builds tools for operating cost-efficient autonomous agents. &lt;a href="https://www.npmjs.com/package/@wartzar-bee/tokenscope" rel="noopener noreferrer"&gt;tokenscope&lt;/a&gt; is free and open-source. &lt;a href="https://dev.to/wartzarbee"&gt;Follow on dev.to →&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>llm</category>
      <category>aiagents</category>
      <category>python</category>
      <category>crewai</category>
    </item>
    <item>
      <title>Put a token-cost gate on your AI-agent PRs in 5 minutes</title>
      <dc:creator>wartzar-bee</dc:creator>
      <pubDate>Sat, 25 Jul 2026 08:41:46 +0000</pubDate>
      <link>https://dev.to/wartzarbee/put-a-token-cost-gate-on-your-ai-agent-prs-in-5-minutes-4g7g</link>
      <guid>https://dev.to/wartzarbee/put-a-token-cost-gate-on-your-ai-agent-prs-in-5-minutes-4g7g</guid>
      <description>&lt;p&gt;If you run an AI agent, your token bill lives in your source code — in prompts, tool&lt;br&gt;
descriptions, and how much context you re-send every turn. And source code changes in pull&lt;br&gt;
requests. So the natural place to catch a cost regression is the same place you catch a bug:&lt;br&gt;
in CI, on the PR, before it merges.&lt;/p&gt;

&lt;p&gt;I learned this the expensive way. We once&lt;br&gt;
&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;put an agent on a timer and it burned 136M tokens overnight doing almost nothing&lt;/a&gt;.&lt;br&gt;
That was the dramatic end. The everyday end is quieter: a system prompt that grew by 200&lt;br&gt;
tokens, a context window that stopped being trimmed, a new tool whose description is 800 words&lt;br&gt;
long. None of it shows up in a code review — the diff looks fine. The tokens are invisible.&lt;/p&gt;

&lt;p&gt;This is a 5-minute walkthrough to make them visible: a GitHub Action that estimates the&lt;br&gt;
token-cost delta of a PR, comments the responsible files on the PR, and can fail the build if&lt;br&gt;
the cost jumps past a threshold you set.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one file you add
&lt;/h2&gt;

&lt;p&gt;Drop this into &lt;code&gt;.github/workflows/cost-guardrail.yml&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Cost Guardrail
on: [pull_request]

jobs:
  cost-check:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write   # needed to post the comment
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0       # the action compares HEAD vs the base branch

      - uses: wartzar-bee/ci-guardrail@v1
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          threshold-pct: 20        # block if tokens grow &amp;gt;20% vs base
          working-directory: .     # where your agent code lives
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That's the whole setup. No account, no API key beyond the &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; your repo already&lt;br&gt;
has, no service to sign up for. &lt;code&gt;fetch-depth: 0&lt;/code&gt; matters: the action checks out both branches&lt;br&gt;
to diff them, so it needs the git history, not just the tip commit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get on the next PR
&lt;/h2&gt;

&lt;p&gt;Here's a comment from a &lt;strong&gt;real run&lt;/strong&gt; of the action — a contributor added a few-shot block to an&lt;br&gt;
agent's &lt;code&gt;system_prompt.txt&lt;/code&gt;, which re-sends on every turn. These are genuine &lt;code&gt;tokenscope&lt;/code&gt;&lt;br&gt;
numbers, not a mock-up; the action updates the &lt;em&gt;same&lt;/em&gt; comment on each push, so it never spams&lt;br&gt;
the thread:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## 🚨 wartzar-bee Cost Guardrail

| Metric             | Value      |
|--------------------|------------|
| Base branch tokens | 978        |
| This PR tokens     | 1,512      |
| Delta              | **54.6%** (**+$0.0016**) |
| Threshold          | 20%        |

💵 Cost estimated at $3.00/1M tokens — set `price-per-1m-tokens` to your model's price.

### Biggest cost increases (responsible files)
| File                     | Base | Head  | Δ        |
|--------------------------|-----:|------:|---------:|
| agent/system_prompt.txt  | 962  | 1,496 | **+534** |

&amp;gt; ⛔ Build blocked — cost regression exceeds the 20% threshold.
&amp;gt; Reduce prompt size, add caching, or raise the threshold if intentional.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The value isn't the top number — it's the &lt;strong&gt;responsible-files&lt;/strong&gt; table. It turns "the bill went&lt;br&gt;
up" into "the bill went up &lt;em&gt;because &lt;code&gt;system_prompt.txt&lt;/code&gt; grew 534 tokens&lt;/em&gt;." That's a comment a&lt;br&gt;
reviewer can act on in the PR, not a surprise on the invoice. (The per-PR dollar figure is&lt;br&gt;
small; the point is it re-sends every turn, every day, across your fleet — the percentage is&lt;br&gt;
what gates the build.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Start in report-only mode
&lt;/h2&gt;

&lt;p&gt;Failing builds on day one is a good way to get an Action deleted. Start by measuring, not&lt;br&gt;
blocking. Set the threshold to &lt;code&gt;0&lt;/code&gt; and it always comments, never fails:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- uses: wartzar-bee/ci-guardrail@v1
  with:
    github-token: ${{ secrets.GITHUB_TOKEN }}
    threshold-pct: 0   # report only, never block
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let it run on real PRs for a week. You'll see what a "normal" delta looks like for your repo —&lt;br&gt;
maybe +5% is routine and +40% is the one worth stopping. &lt;em&gt;Then&lt;/em&gt; set &lt;code&gt;threshold-pct&lt;/code&gt; to a number&lt;br&gt;
that reflects that, and turn on blocking with intent instead of guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the dollar figure yours
&lt;/h2&gt;

&lt;p&gt;The comment shows an estimated dollar delta so a non-engineer reading the PR gets it. By default&lt;br&gt;
it uses an approximate blended input-token price of &lt;code&gt;$3.00&lt;/code&gt; per 1M tokens. That's a placeholder —&lt;br&gt;
override it to your model so the number is real:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- uses: wartzar-bee/ci-guardrail@v1
  with:
    github-token: ${{ secrets.GITHUB_TOKEN }}
    threshold-pct: 20
    price-per-1m-tokens: 0.80   # e.g. a cheaper model's input price
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The percentage is what gates the build; the dollar figure is there to make the percentage land&lt;br&gt;
with whoever approves the merge.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it's actually doing
&lt;/h2&gt;

&lt;p&gt;No magic, and nothing sent anywhere:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Runs &lt;a href="https://www.npmjs.com/package/@wartzar-bee/tokenscope" rel="noopener noreferrer"&gt;&lt;code&gt;tokenscope&lt;/code&gt;&lt;/a&gt; on your HEAD branch
to estimate its total token footprint.&lt;/li&gt;
&lt;li&gt;Fetches the base branch and runs the same scan.&lt;/li&gt;
&lt;li&gt;Computes the delta as a percentage.&lt;/li&gt;
&lt;li&gt;Posts (or updates) the PR comment with the per-file breakdown — and writes the same table to
the Actions run summary, so you also see it on &lt;code&gt;push&lt;/code&gt; or scheduled runs where there's no PR to
comment on.&lt;/li&gt;
&lt;li&gt;Exits non-zero if the delta exceeds your &lt;code&gt;threshold-pct&lt;/code&gt; (and the threshold is above 0).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The engine is &lt;code&gt;tokenscope&lt;/code&gt;, an open-source token-cost analyzer; the Action is a thin, auditable&lt;br&gt;
composite wrapper around it. Both are MIT.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The Action: &lt;a href="https://github.com/wartzar-bee/ci-guardrail" rel="noopener noreferrer"&gt;github.com/wartzar-bee/ci-guardrail&lt;/a&gt;
(&lt;code&gt;uses: wartzar-bee/ci-guardrail@v1&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;On the Marketplace: &lt;a href="https://github.com/marketplace/actions/wartzar-bee-ci-cost-guardrail" rel="noopener noreferrer"&gt;CI Cost Guardrail&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The engine, standalone: &lt;code&gt;npx @wartzar-bee/tokenscope &amp;lt;your-session.jsonl&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add the one file, open a PR, and watch the token delta show up next to the diff. If it catches&lt;br&gt;
even one 800-word tool description before it merges, it's paid for itself.&lt;/p&gt;

</description>
      <category>github</category>
      <category>devops</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>We open-sourced the runtime our agent fleet runs on: Enclave</title>
      <dc:creator>wartzar-bee</dc:creator>
      <pubDate>Thu, 23 Jul 2026 17:23:48 +0000</pubDate>
      <link>https://dev.to/wartzarbee/we-open-sourced-the-runtime-our-agent-fleet-runs-on-enclave-4m67</link>
      <guid>https://dev.to/wartzarbee/we-open-sourced-the-runtime-our-agent-fleet-runs-on-enclave-4m67</guid>
      <description>&lt;h1&gt;
  
  
  We open-sourced the runtime our agent fleet runs on
&lt;/h1&gt;

&lt;p&gt;We run a small fleet of autonomous agents. Not demos — long-running operators that wake up on a timer, read their own memory, pick the next step, and do it, tick after tick, without a human in the loop. Doing that safely and without setting fire to your model bill turns out to be most of the work.&lt;/p&gt;

&lt;p&gt;The agent logic is the easy part. The runtime around it — the sandbox, the credential scoping, the memory that survives a restart, the cost governor that keeps a frontier model from burning your whole cap on a heartbeat — is where the weeks go.&lt;/p&gt;

&lt;p&gt;Today we're open-sourcing that runtime. It's called &lt;strong&gt;Enclave&lt;/strong&gt;, it's &lt;strong&gt;Apache-2.0&lt;/strong&gt;, and it's the same code our own fleet runs on right now.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;github.com/wartzar-bee/enclave&lt;/code&gt; — public alpha, Apache-2.0.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/wartzar-bee/enclave.git enclave &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;enclave
./bin/enclave init      &lt;span class="c"&gt;# wizard: name, brain, model, port, paste your credential&lt;/span&gt;
./bin/enclave run       &lt;span class="c"&gt;# build + start, opens a browser chat at 127.0.0.1:8888&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What it is
&lt;/h2&gt;

&lt;p&gt;Enclave runs one autonomous agent in a &lt;strong&gt;hardened container&lt;/strong&gt; with &lt;strong&gt;scoped credentials&lt;/strong&gt; and a &lt;strong&gt;local web chat&lt;/strong&gt;. &lt;code&gt;docker compose up&lt;/code&gt;, and you talk to your agent in the browser — a real Claude-Code conversation, resumable, multi-thread, at the agent's own model.&lt;/p&gt;

&lt;p&gt;It's &lt;strong&gt;brain-agnostic&lt;/strong&gt;: one env var, &lt;code&gt;BRAIN=claude | api | local | optimize&lt;/code&gt;. Same container, same security guard, whether you run it on a Claude subscription, any OpenAI-compatible key, or a local model server. Switching brains keeps the agent's memory intact.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "constrained" actually means (read this before you trust it)
&lt;/h2&gt;

&lt;p&gt;We're allergic to security theatre, so here's the precise boundary — the part most "agent framework" READMEs hand-wave:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The container boundary is enforced by the kernel, always.&lt;/strong&gt; The agent runs &lt;code&gt;--cap-drop=ALL --security-opt=no-new-privileges&lt;/code&gt; with no inbound ports. It sees exactly the mounts you gave it and a &lt;strong&gt;read-only&lt;/strong&gt; &lt;code&gt;secrets/&lt;/code&gt; — nothing else on your disk. A prompt injection does not change that; it's architectural, not a policy the model can be talked out of.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The network boundary is enforced only when you turn it on.&lt;/strong&gt; The egress allowlist ships in &lt;strong&gt;report-only mode by default&lt;/strong&gt; — it &lt;em&gt;logs&lt;/em&gt; disallowed hosts rather than blocking them, so a first run doesn't fail in a way you can't diagnose. For anything real, set &lt;code&gt;GUARD_EGRESS_ENFORCE=1&lt;/code&gt;. We'd rather tell you that up front than have you discover it in an audit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A &lt;code&gt;PreToolUse&lt;/code&gt; guard (&lt;code&gt;platform/agentd/hooks/guard.py&lt;/code&gt;) fires even under &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt; and blocks &lt;code&gt;git&lt;/code&gt;, foreign-secret reads, and — via opt-in profiles — cloud writes and production mutations. You can verify all of this by reading the code, which is the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part we care about most: cost discipline
&lt;/h2&gt;

&lt;p&gt;A persistent fleet on a frontier model burns your subscription or API cap fast. At fleet scale, that's &lt;em&gt;the&lt;/em&gt; binding constraint — not latency, not quality. Enclave ships two layers to keep judgment quality while cutting spend, both toggled by an env flag:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model-tier routing&lt;/strong&gt; (&lt;code&gt;ROUTER=on&lt;/code&gt;) — routine heartbeats and mechanical directives (post / measure / narrate / commit) run on a cheaper model, reserving the top model for actual judgment (decide / design / review). It's safe-by-default: anything ambiguous, or any upstream error, resolves &lt;em&gt;up&lt;/em&gt; to the top model. You never silently downgrade a decision that mattered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manager→worker delegation&lt;/strong&gt; — when &lt;code&gt;BRAIN=claude&lt;/code&gt;, a capable manager is &lt;em&gt;forced&lt;/em&gt; to hand bulk code-writing to a cheap or local worker instead of spending frontier tokens on keystrokes. The manager plans and reviews; the worker does the labor under a verify-gate. The guard self-disables for &lt;code&gt;api&lt;/code&gt;/&lt;code&gt;local&lt;/code&gt; brains, which already &lt;em&gt;are&lt;/em&gt; the cheap worker.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the same thesis behind our other tools — &lt;a href="https://www.npmjs.com/package/@wartzar-bee/tokenscope" rel="noopener noreferrer"&gt;tokenscope&lt;/a&gt; (measure where the tokens actually go) and our &lt;a href="https://github.com/wartzar-bee/ci-guardrail" rel="noopener noreferrer"&gt;CI cost-regression guardrail&lt;/a&gt; (block a PR that spikes token cost). Enclave is where those patterns run in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory that survives a machine wipe
&lt;/h2&gt;

&lt;p&gt;The agent's memory is &lt;strong&gt;one linked, markdown, git-trackable vault&lt;/strong&gt; — an LLM-maintained wiki plus operational memory and skills, navigable as a graph, no DB or GPU required. The runtime auto-snapshots after every tick, and every snapshot is &lt;strong&gt;scan-gated and fail-closed&lt;/strong&gt;: a credential pasted into memory &lt;em&gt;blocks the commit&lt;/em&gt;, because git history is forever. Opt-in &lt;code&gt;qmd&lt;/code&gt; semantic search and a &lt;code&gt;codegraph&lt;/code&gt; symbol/call graph layer on top when you want them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it deliberately isn't (the honest gaps)
&lt;/h2&gt;

&lt;p&gt;Open-sourcing a thing you use daily means publishing its rough edges too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS + Linux only.&lt;/strong&gt; Developed on Apple silicon and Linux; Windows is untested (WSL2 is the likely path, unverified). If you run it elsewhere, reports are genuinely welcome — that's a big reason to open it up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Host capabilities (bridges) aren't bundled.&lt;/strong&gt; Browser automation, transcription, TTS live &lt;em&gt;outside&lt;/em&gt; the container as host services. Enclave ships the &lt;strong&gt;pattern&lt;/strong&gt; (&lt;code&gt;docs/BRIDGES.md&lt;/code&gt; + a working &lt;code&gt;tools/bridge-template/&lt;/code&gt;), not the services. Out of the box an agent can think, read/write files, and call APIs — it can't drive a browser until you stand a bridge up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public alpha.&lt;/strong&gt; The API and layout still move. It runs a live fleet daily, but pin your version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most useful contribution is a &lt;strong&gt;bridge&lt;/strong&gt; — a new host capability behind a narrow, audited surface. If you've wanted an agent runtime you can actually read end-to-end before you trust it with a credential, this is that.&lt;/p&gt;

&lt;p&gt;⭐ &lt;strong&gt;&lt;code&gt;github.com/wartzar-bee/enclave&lt;/code&gt;&lt;/strong&gt; (Apache-2.0). Kick the tyres, file an issue with your &lt;code&gt;enclave status&lt;/code&gt; output, and tell us what breaks on your OS.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>aiagents</category>
      <category>security</category>
      <category>docker</category>
    </item>
  </channel>
</rss>
