<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Neha's Substack]]></title><description><![CDATA[My personal Substack]]></description><link>https://blog.polos.dev</link><image><url>https://substackcdn.com/image/fetch/$s_!5Npj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2752c1b-fbc2-4835-aa11-dfa5acbe0b47_574x574.jpeg</url><title>Neha&apos;s Substack</title><link>https://blog.polos.dev</link></image><generator>Substack</generator><lastBuildDate>Tue, 07 Apr 2026 05:28:10 GMT</lastBuildDate><atom:link href="https://blog.polos.dev/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Neha Deodhar]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[ndeodhar@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[ndeodhar@substack.com]]></itunes:email><itunes:name><![CDATA[Neha Deodhar]]></itunes:name></itunes:owner><itunes:author><![CDATA[Neha Deodhar]]></itunes:author><googleplay:owner><![CDATA[ndeodhar@substack.com]]></googleplay:owner><googleplay:email><![CDATA[ndeodhar@substack.com]]></googleplay:email><googleplay:author><![CDATA[Neha Deodhar]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[I Built a Coding Agent That Fixes GitHub Issues - in just a few lines of code]]></title><description><![CDATA[I built a coding agent that picks up GitHub issues, writes the fix in a sandbox, and pings me on Slack when the PR is ready for approval - without writing a single line of Docker, Slack or state management code.]]></description><link>https://blog.polos.dev/p/i-built-a-coding-agent-that-fixes</link><guid isPermaLink="false">https://blog.polos.dev/p/i-built-a-coding-agent-that-fixes</guid><dc:creator><![CDATA[Neha Deodhar]]></dc:creator><pubDate>Wed, 25 Feb 2026 00:23:52 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/KYVBpdZ_5eM" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I built a coding agent that picks up GitHub issues, writes the fix in a sandbox, and pings me on Slack when the PR is ready for approval - without writing a single line of Docker, Slack or state management code. Here&#8217;s how.</p><h2><strong>The Workflow</strong></h2><p>A new GitHub issue triggers the workflow. The agent comments on the issue, clones the repo into a sandboxed Docker container.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.polos.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Neha's Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Two agents take over - planner and coder. Both get built-in sandbox tools automatically: shell execution, file read/write, edit, glob, grep, web search. I just defined the agent goals. Polos handled the sandbox lifecycle, tool wiring, and coordination.</p><p>The coder finishes. The workflow pauses for human review. I get a Slack notification, review the diff, approve, PR is live.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">const sandbox = sandboxTools({
  env: 'docker',
  scope: 'session',
  docker: { image: 'node:20-slim', memory: '2g' },
});

const planner = defineAgent({
  id: 'planner',
  model: anthropic('claude-sonnet-4-5'),
  systemPrompt: 'Analyze the issue and create an execution plan.',
  tools: [...sandbox],
});

const coder = defineAgent({
  id: 'coder',
  model: anthropic('claude-sonnet-4-5'),
  systemPrompt: 'Implement the plan. Read, write, and test code.',
  tools: [...sandbox],
});</code></pre></div><p>I didn&#8217;t have to figure out how to create the Docker container, execute commands inside it, manage file system access, or keep the same sandbox alive across multiple tool calls within a session. Polos manages the full sandbox lifecycle - creation, tool execution, persistence across calls, and cleanup.</p><p>Full working example: <strong><a href="https://github.com/polos-dev/polos-examples/tree/main/github-issue-fixer/typescript">Typescript</a></strong> | <strong><a href="https://github.com/polos-dev/polos-examples/tree/main/github-issue-fixer/python">Python</a></strong></p><h3><strong>Demo</strong></h3><p>3-minute video: </p><div id="youtube2-KYVBpdZ_5eM" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;KYVBpdZ_5eM&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/KYVBpdZ_5eM?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>For this demo, I used a fork of the Zod repo and gave the agent an existing issue. Within seconds it commented on the issue, started working in the sandbox. A few minutes later, Slack notification - coder finished, ready for review. Approved from my phone. PR was live.</p><h2><strong>What I Used</strong></h2><p>I built this with<a href="https://github.com/polos-dev/polos"> Polos</a>, an open-source runtime for AI agents. What I got out of the box:</p><ul><li><p><strong>Sandboxed execution</strong> - agents run inside managed Docker containers with built-in tools for shell, files, and web search</p></li><li><p><strong>Slack integration</strong> - @mention agents, get responses in thread, receive notifications when agents need input</p></li><li><p><strong>Durable workflows</strong> - agent fails at step 47 of 50, resumes from 47</p></li><li><p><strong>Observability</strong> - OpenTelemetry tracing for every tool call and decision</p></li><li><p><strong>LLM agnostic</strong> - any provider via Vercel AI SDK and LiteLLM</p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">curl -fsSL https://install.polos.dev/install.sh | bash
npx create-polos
cd my-project &amp;&amp; polos dev</code></pre></div><p>Github repo: https://github.com/polos-dev/polos</p><p>100% open source. Python and TypeScript.</p><p>If you&#8217;re building agents that do real work, run commands, touch real systems - give it a try. I&#8217;d love to hear what you build.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.polos.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Neha's Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Why I Built Polos: Durable Execution for AI Agents]]></title><description><![CDATA[When I started building AI agents, getting a demo working was easy.]]></description><link>https://blog.polos.dev/p/why-i-built-polos-durable-execution</link><guid isPermaLink="false">https://blog.polos.dev/p/why-i-built-polos-durable-execution</guid><dc:creator><![CDATA[Neha Deodhar]]></dc:creator><pubDate>Mon, 09 Feb 2026 16:00:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!n5v4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e7a29c4-a7ca-4ea3-963e-44c50f2df74a_1318x1422.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When I started building AI agents, getting a demo working was easy. But once we put them in production, things got complicated fast.</p><p>We needed a bunch of infra:</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.polos.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Neha's Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><ul><li><p><strong>Kafka</strong> to pass events between agents</p></li><li><p><strong>Retry logic</strong> - but if an agent fails halfway through, you can&#8217;t just restart it. It may have already charged the customer or sent an email.</p></li><li><p><strong>Concurrency control</strong> so we didn&#8217;t blow through our OpenAI quota</p></li><li><p><strong>Observability</strong> to actually see what the agents were doing</p></li></ul><p>We ended up bolting together Kafka, durable execution frameworks, a bunch of heavyweight infrastructure - just to run agents reliably. And then we were stuck operating all of it. Time we should&#8217;ve spent building the actual product!</p><p>I realized every team building agents hits the same wall. We&#8217;re missing an AI-native platform that handles this out of the box.</p><p>That&#8217;s why I built Polos.</p><h2><strong>What is Polos?</strong></h2><p>Polos is a durable execution platform for AI agents. It gives you stateful infrastructure to run long-running, autonomous agents reliably at scale - with a built-in event system, so you don&#8217;t need to bolt on Kafka or RabbitMQ.</p><p>You write plain Python or TypeScript. No DAGs, no graph syntax. Polos handles the durability, the retries, the coordination.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!n5v4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e7a29c4-a7ca-4ea3-963e-44c50f2df74a_1318x1422.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!n5v4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e7a29c4-a7ca-4ea3-963e-44c50f2df74a_1318x1422.png 424w, https://substackcdn.com/image/fetch/$s_!n5v4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e7a29c4-a7ca-4ea3-963e-44c50f2df74a_1318x1422.png 848w, https://substackcdn.com/image/fetch/$s_!n5v4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e7a29c4-a7ca-4ea3-963e-44c50f2df74a_1318x1422.png 1272w, https://substackcdn.com/image/fetch/$s_!n5v4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e7a29c4-a7ca-4ea3-963e-44c50f2df74a_1318x1422.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!n5v4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e7a29c4-a7ca-4ea3-963e-44c50f2df74a_1318x1422.png" width="1318" height="1422" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4e7a29c4-a7ca-4ea3-963e-44c50f2df74a_1318x1422.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1422,&quot;width&quot;:1318,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:317602,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ndeodhar.substack.com/i/187253934?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e7a29c4-a7ca-4ea3-963e-44c50f2df74a_1318x1422.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!n5v4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e7a29c4-a7ca-4ea3-963e-44c50f2df74a_1318x1422.png 424w, https://substackcdn.com/image/fetch/$s_!n5v4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e7a29c4-a7ca-4ea3-963e-44c50f2df74a_1318x1422.png 848w, https://substackcdn.com/image/fetch/$s_!n5v4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e7a29c4-a7ca-4ea3-963e-44c50f2df74a_1318x1422.png 1272w, https://substackcdn.com/image/fetch/$s_!n5v4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e7a29c4-a7ca-4ea3-963e-44c50f2df74a_1318x1422.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This workflow survives crashes, resumes mid-execution, and pauses for approval - with zero manual checkpointing.</p><h2><strong>What Polos Gives You</strong></h2><p><strong>Durable state.</strong> If your agent crashes on step 18 of 20, it resumes from step 18. Not step 1. Every side effect like LLM calls, tool executions, API requests is checkpointed. If your agent already charged Stripe via a tool call before the crash, that charge isn&#8217;t repeated on resume. Polos replays the result from its log. No wasted LLM calls, no duplicate charges, no double-sends.</p><p><strong>Global concurrency.</strong> System-wide rate limiting so one rogue agent can&#8217;t exhaust your entire API quota. Queues and concurrency keys give you fine-grained control.</p><p><strong>Human-in-the-loop.</strong> Pause execution for hours or days, wait for a user signal or approval, and resume with full context. Paused agents consume zero compute.</p><p><strong>Exactly-once execution.</strong> Charging Stripe, sending an email - all actions happen once, even if you retry the workflow. Polos checkpoints every side effect.</p><p><strong>Built-in observability.</strong> Trace every tool call, every decision. See why your agent chose Tool B over Tool A.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3XzG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bce057a-596f-4e3c-9046-47642b6bb682_3002x1550.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3XzG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bce057a-596f-4e3c-9046-47642b6bb682_3002x1550.png 424w, https://substackcdn.com/image/fetch/$s_!3XzG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bce057a-596f-4e3c-9046-47642b6bb682_3002x1550.png 848w, https://substackcdn.com/image/fetch/$s_!3XzG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bce057a-596f-4e3c-9046-47642b6bb682_3002x1550.png 1272w, https://substackcdn.com/image/fetch/$s_!3XzG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bce057a-596f-4e3c-9046-47642b6bb682_3002x1550.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3XzG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bce057a-596f-4e3c-9046-47642b6bb682_3002x1550.png" width="1456" height="752" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6bce057a-596f-4e3c-9046-47642b6bb682_3002x1550.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:752,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:515447,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ndeodhar.substack.com/i/187253934?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bce057a-596f-4e3c-9046-47642b6bb682_3002x1550.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!3XzG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bce057a-596f-4e3c-9046-47642b6bb682_3002x1550.png 424w, https://substackcdn.com/image/fetch/$s_!3XzG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bce057a-596f-4e3c-9046-47642b6bb682_3002x1550.png 848w, https://substackcdn.com/image/fetch/$s_!3XzG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bce057a-596f-4e3c-9046-47642b6bb682_3002x1550.png 1272w, https://substackcdn.com/image/fetch/$s_!3XzG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6bce057a-596f-4e3c-9046-47642b6bb682_3002x1550.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Z25I!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cff5755-6087-4c03-b1db-8b16da52b392_2998x1558.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Z25I!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cff5755-6087-4c03-b1db-8b16da52b392_2998x1558.png 424w, https://substackcdn.com/image/fetch/$s_!Z25I!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cff5755-6087-4c03-b1db-8b16da52b392_2998x1558.png 848w, https://substackcdn.com/image/fetch/$s_!Z25I!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cff5755-6087-4c03-b1db-8b16da52b392_2998x1558.png 1272w, https://substackcdn.com/image/fetch/$s_!Z25I!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cff5755-6087-4c03-b1db-8b16da52b392_2998x1558.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Z25I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cff5755-6087-4c03-b1db-8b16da52b392_2998x1558.png" width="1456" height="757" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2cff5755-6087-4c03-b1db-8b16da52b392_2998x1558.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:757,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:464748,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ndeodhar.substack.com/i/187253934?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cff5755-6087-4c03-b1db-8b16da52b392_2998x1558.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Z25I!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cff5755-6087-4c03-b1db-8b16da52b392_2998x1558.png 424w, https://substackcdn.com/image/fetch/$s_!Z25I!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cff5755-6087-4c03-b1db-8b16da52b392_2998x1558.png 848w, https://substackcdn.com/image/fetch/$s_!Z25I!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cff5755-6087-4c03-b1db-8b16da52b392_2998x1558.png 1272w, https://substackcdn.com/image/fetch/$s_!Z25I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cff5755-6087-4c03-b1db-8b16da52b392_2998x1558.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>See It In Action</strong></h2><p>Here&#8217;s a short demo showing <strong>crash recovery</strong> in practice:</p><ol><li><p>We start an order workflow. The agent charges the customer via Stripe - the charge succeeds, and Polos checkpoints the result.</p></li><li><p>Since the order amount is flagged as unusual, the workflow suspends for a fraud review.</p></li><li><p>While waiting for the fraud team, the worker crashes.</p></li><li><p>In most frameworks, this workflow is dead. You&#8217;d need to handle the failure manually and risk charging the customer again.</p></li><li><p>With Polos, we simply start a new worker. When the fraud team approves, Worker 2 picks up the workflow exactly where it left off.</p></li><li><p>Stripe is not called again - Polos replays the result from its log. The confirmation email is sent, and the workflow completes.</p></li></ol><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;8c7e09a3-a5b0-472a-8a4c-ece3dab80988&quot;,&quot;duration&quot;:null}"></div><h2><strong>How It Works</strong></h2><p>Polos has three components:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!a2u5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F896e71de-8a32-419c-8ce1-b66c2c9a6210_3024x1694.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!a2u5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F896e71de-8a32-419c-8ce1-b66c2c9a6210_3024x1694.png 424w, https://substackcdn.com/image/fetch/$s_!a2u5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F896e71de-8a32-419c-8ce1-b66c2c9a6210_3024x1694.png 848w, https://substackcdn.com/image/fetch/$s_!a2u5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F896e71de-8a32-419c-8ce1-b66c2c9a6210_3024x1694.png 1272w, https://substackcdn.com/image/fetch/$s_!a2u5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F896e71de-8a32-419c-8ce1-b66c2c9a6210_3024x1694.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!a2u5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F896e71de-8a32-419c-8ce1-b66c2c9a6210_3024x1694.png" width="1456" height="816" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/896e71de-8a32-419c-8ce1-b66c2c9a6210_3024x1694.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:816,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:264122,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ndeodhar.substack.com/i/187253934?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F896e71de-8a32-419c-8ce1-b66c2c9a6210_3024x1694.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!a2u5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F896e71de-8a32-419c-8ce1-b66c2c9a6210_3024x1694.png 424w, https://substackcdn.com/image/fetch/$s_!a2u5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F896e71de-8a32-419c-8ce1-b66c2c9a6210_3024x1694.png 848w, https://substackcdn.com/image/fetch/$s_!a2u5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F896e71de-8a32-419c-8ce1-b66c2c9a6210_3024x1694.png 1272w, https://substackcdn.com/image/fetch/$s_!a2u5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F896e71de-8a32-419c-8ce1-b66c2c9a6210_3024x1694.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Orchestrator</strong>: manages workflow state, persists every side effect to a durable log, handles event routing, scheduling, and concurrency control. If a worker dies, the orchestrator knows exactly where execution left off and schedules the workflow on a different worker.</p><p><strong>Workers</strong>: run your code. They connect to the orchestrator, pick up workflow steps, execute them (including LLM calls and tool invocations), and report results back. Workers are stateless and horizontally scalable.</p><p><strong>SDK</strong>: what you import in your code. Provides the @workflow decorator, Agent class, and the WorkflowContext that gives you durable steps, suspend/resume, events, and concurrency primitives.</p><p>Under the hood, Polos captures the result of every side effect - tool calls, API responses, time delays - as a durable log. If your process dies, Polos replays the workflow from the log, returning previously-recorded results instead of re-executing them. Your agent&#8217;s exact local variables and call stack are restored in milliseconds.</p><p>Completed steps are never re-executed - so you never pay for an LLM call twice.</p><h2><strong>Get Involved</strong></h2><p>Polos is open source: <strong><a href="http://github.com/polos-dev/polos">github.com/polos-dev/polos</a></strong></p><p>Star us on GitHub, join the <strong><a href="https://discord.gg/ZAxHKMPwFG">Discord</a></strong>, and give it a spin. We&#8217;re building this in the open and would love your feedback and contributions.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.polos.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Neha's Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[How Prompt Caching Cut My API Costs by 60%: A Real-World Experiment]]></title><description><![CDATA[If you&#8217;re building multi-turn conversations or agentic workflows, you&#8217;re probably re-sending the same tokens over and over again - tool definitions, system prompts, and conversation history.]]></description><link>https://blog.polos.dev/p/how-prompt-caching-cut-my-api-costs</link><guid isPermaLink="false">https://blog.polos.dev/p/how-prompt-caching-cut-my-api-costs</guid><dc:creator><![CDATA[Neha Deodhar]]></dc:creator><pubDate>Sun, 08 Feb 2026 18:01:27 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!20iM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a450181-6e7a-44cf-afe3-f31fbaf25511_2310x740.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you&#8217;re building multi-turn conversations or agentic workflows, you&#8217;re probably re-sending the same tokens over and over again - tool definitions, system prompts, and conversation history. Every single request. That&#8217;s expensive.</p><p>Prompt caching lets you cache static content and pay just 10% of the normal input token price on subsequent requests. I ran an experiment using Anthropic Claude Sonnet 4.5 to see how much this actually saves in practice.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.polos.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Neha's Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>But first, a bit of background on how prompt caching works...</p><h2><strong>How Prompt Caching Works Under the Hood</strong></h2><p>When you send a request with caching enabled, Anthropic computes a hash of your prompt content up to each cache breakpoint (see the section below to understand what a cache breakpoint is). If that exact hash exists in the cache from a recent request, the system skips reprocessing those tokens entirely - it just loads the cached computation state and continues from there. This is why cache reads are so cheap (10% of base price): you&#8217;re not paying for the model to process those tokens again, just to retrieve the precomputed state.</p><p>The default cache lifetime is 5 minutes, but here&#8217;s the key detail: <strong>the cache refreshes for no additional cost each time it&#8217;s used</strong>. So if you&#8217;re actively conversing with the model and hitting the cache every minute or two, that 5-minute window keeps resetting. The cache only expires after 5 minutes of <em>inactivity</em>. For active conversations or agentic loops, this means your cache essentially stays warm indefinitely. Anthropic also offers a 1-hour TTL at a higher write cost (2x base price instead of 1.25x) for workflows where requests are more spread out.</p><h3><strong>Cache Breakpoints and the Hierarchy</strong></h3><p>A <strong>cache breakpoint</strong> is where you place cache_control: {&#8221;type&#8221;: &#8220;ephemeral&#8221;} in your request. It tells Anthropic: &#8220;cache everything from the start of the request up to this point.&#8221; You can have up to 4 breakpoints per request.</p><p>The cache follows a strict hierarchy: <strong>tools &#8594; system &#8594; messages</strong>. This ordering matters because caches are cumulative - each level builds on the previous ones. Here&#8217;s how invalidation works:</p><ul><li><p><strong>Change your tools?</strong> The entire cache invalidates (tools, system, and messages).</p></li><li><p><strong>Change your system prompt?</strong> The tools cache survives, but system and messages caches invalidate.</p></li><li><p><strong>Change a message?</strong> Tools and system caches survive, but the messages cache from that point forward invalidates.</p></li></ul><p>This is why placing breakpoints strategically matters. If your tools rarely change but your system prompt updates daily, put separate breakpoints on each. That way, a system prompt change doesn&#8217;t force you to re-cache the tools.</p><p>One key difference from OpenAI: <strong>OpenAI caches prompts automatically</strong> with no configuration needed. Anthropic requires explicit cache_control breakpoints, giving you more control over what gets cached and when, but requiring more upfront thought about your caching strategy.</p><h2><strong>The Experiment</strong></h2><p>I set up a 7-turn conversation with Claude Sonnet 4.5 that included:</p><ul><li><p><strong>Tool definitions</strong> (~2K tokens of function schemas)</p></li><li><p><strong>A detailed system prompt</strong> (~6K tokens of instructions and context)</p></li><li><p><strong>Growing conversation history</strong> (accumulating with each turn)</p></li></ul><p>I ran the same conversation four different ways</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!20iM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a450181-6e7a-44cf-afe3-f31fbaf25511_2310x740.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!20iM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a450181-6e7a-44cf-afe3-f31fbaf25511_2310x740.png 424w, https://substackcdn.com/image/fetch/$s_!20iM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a450181-6e7a-44cf-afe3-f31fbaf25511_2310x740.png 848w, https://substackcdn.com/image/fetch/$s_!20iM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a450181-6e7a-44cf-afe3-f31fbaf25511_2310x740.png 1272w, https://substackcdn.com/image/fetch/$s_!20iM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a450181-6e7a-44cf-afe3-f31fbaf25511_2310x740.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!20iM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a450181-6e7a-44cf-afe3-f31fbaf25511_2310x740.png" width="1456" height="466" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1a450181-6e7a-44cf-afe3-f31fbaf25511_2310x740.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:466,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:176254,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ndeodhar.substack.com/i/187252757?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a450181-6e7a-44cf-afe3-f31fbaf25511_2310x740.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!20iM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a450181-6e7a-44cf-afe3-f31fbaf25511_2310x740.png 424w, https://substackcdn.com/image/fetch/$s_!20iM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a450181-6e7a-44cf-afe3-f31fbaf25511_2310x740.png 848w, https://substackcdn.com/image/fetch/$s_!20iM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a450181-6e7a-44cf-afe3-f31fbaf25511_2310x740.png 1272w, https://substackcdn.com/image/fetch/$s_!20iM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a450181-6e7a-44cf-afe3-f31fbaf25511_2310x740.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>The Results</strong></h2><p>Here&#8217;s what each request cost across the four strategies:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QYs_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6701015e-b949-45ee-98b7-111adce83ed6_1906x996.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QYs_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6701015e-b949-45ee-98b7-111adce83ed6_1906x996.png 424w, https://substackcdn.com/image/fetch/$s_!QYs_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6701015e-b949-45ee-98b7-111adce83ed6_1906x996.png 848w, https://substackcdn.com/image/fetch/$s_!QYs_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6701015e-b949-45ee-98b7-111adce83ed6_1906x996.png 1272w, https://substackcdn.com/image/fetch/$s_!QYs_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6701015e-b949-45ee-98b7-111adce83ed6_1906x996.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QYs_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6701015e-b949-45ee-98b7-111adce83ed6_1906x996.png" width="1456" height="761" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6701015e-b949-45ee-98b7-111adce83ed6_1906x996.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:761,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:233051,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ndeodhar.substack.com/i/187252757?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6701015e-b949-45ee-98b7-111adce83ed6_1906x996.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QYs_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6701015e-b949-45ee-98b7-111adce83ed6_1906x996.png 424w, https://substackcdn.com/image/fetch/$s_!QYs_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6701015e-b949-45ee-98b7-111adce83ed6_1906x996.png 848w, https://substackcdn.com/image/fetch/$s_!QYs_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6701015e-b949-45ee-98b7-111adce83ed6_1906x996.png 1272w, https://substackcdn.com/image/fetch/$s_!QYs_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6701015e-b949-45ee-98b7-111adce83ed6_1906x996.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Cost Breakdown</strong></h2><p>To see why caching wins, look at the economics:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!c1iD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c9d5bf-68de-44fa-96ce-0aa2828122dd_2158x858.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!c1iD!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c9d5bf-68de-44fa-96ce-0aa2828122dd_2158x858.png 424w, https://substackcdn.com/image/fetch/$s_!c1iD!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c9d5bf-68de-44fa-96ce-0aa2828122dd_2158x858.png 848w, https://substackcdn.com/image/fetch/$s_!c1iD!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c9d5bf-68de-44fa-96ce-0aa2828122dd_2158x858.png 1272w, https://substackcdn.com/image/fetch/$s_!c1iD!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c9d5bf-68de-44fa-96ce-0aa2828122dd_2158x858.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!c1iD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c9d5bf-68de-44fa-96ce-0aa2828122dd_2158x858.png" width="1456" height="579" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/86c9d5bf-68de-44fa-96ce-0aa2828122dd_2158x858.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:579,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:188314,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ndeodhar.substack.com/i/187252757?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c9d5bf-68de-44fa-96ce-0aa2828122dd_2158x858.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!c1iD!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c9d5bf-68de-44fa-96ce-0aa2828122dd_2158x858.png 424w, https://substackcdn.com/image/fetch/$s_!c1iD!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c9d5bf-68de-44fa-96ce-0aa2828122dd_2158x858.png 848w, https://substackcdn.com/image/fetch/$s_!c1iD!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c9d5bf-68de-44fa-96ce-0aa2828122dd_2158x858.png 1272w, https://substackcdn.com/image/fetch/$s_!c1iD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86c9d5bf-68de-44fa-96ce-0aa2828122dd_2158x858.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Without caching, you pay full price to process every token on every request. With caching, the first request pays a <strong>25% premium to populate the cache</strong>. But every subsequent request reads those tokens at <strong>just 10% of the base price</strong>. Over a 7-turn conversation, the math overwhelmingly favors caching.</p><h2><strong>What&#8217;s Actually Happening Under the Hood</strong></h2><p>Let&#8217;s look at the cache behavior (tokens) for the fully cached strategy:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gd5D!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26f160b3-c860-4b6c-b0a5-c39e656cce3f_1436x990.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gd5D!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26f160b3-c860-4b6c-b0a5-c39e656cce3f_1436x990.png 424w, https://substackcdn.com/image/fetch/$s_!gd5D!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26f160b3-c860-4b6c-b0a5-c39e656cce3f_1436x990.png 848w, https://substackcdn.com/image/fetch/$s_!gd5D!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26f160b3-c860-4b6c-b0a5-c39e656cce3f_1436x990.png 1272w, https://substackcdn.com/image/fetch/$s_!gd5D!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26f160b3-c860-4b6c-b0a5-c39e656cce3f_1436x990.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gd5D!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26f160b3-c860-4b6c-b0a5-c39e656cce3f_1436x990.png" width="526" height="362.63231197771586" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/26f160b3-c860-4b6c-b0a5-c39e656cce3f_1436x990.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:990,&quot;width&quot;:1436,&quot;resizeWidth&quot;:526,&quot;bytes&quot;:141282,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ndeodhar.substack.com/i/187252757?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26f160b3-c860-4b6c-b0a5-c39e656cce3f_1436x990.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gd5D!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26f160b3-c860-4b6c-b0a5-c39e656cce3f_1436x990.png 424w, https://substackcdn.com/image/fetch/$s_!gd5D!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26f160b3-c860-4b6c-b0a5-c39e656cce3f_1436x990.png 848w, https://substackcdn.com/image/fetch/$s_!gd5D!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26f160b3-c860-4b6c-b0a5-c39e656cce3f_1436x990.png 1272w, https://substackcdn.com/image/fetch/$s_!gd5D!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26f160b3-c860-4b6c-b0a5-c39e656cce3f_1436x990.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Request 1</strong> is the &#8220;cold start&#8221; - nothing is cached yet, so we write 8,093 tokens to the cache. This actually costs <em>more</em> than no caching (cache writes are 1.25x the base input price).</p><p><strong>Request 2</strong> onward is where caching pays off. We&#8217;re reading thousands of tokens from cache at 0.1x the normal price, and only writing the new conversation turns.</p><p>By <strong>Request 7</strong>, we&#8217;re reading 13,464 tokens from cache and only writing 362 new tokens. That&#8217;s why the cost dropped to $0.01.</p><h2><strong>The Counterintuitive First Request</strong></h2><p>Notice that the first request with caching enabled ($0.06) actually costs <em>more</em> than without caching ($0.05). This is the cache write penalty - you pay 25% extra to populate the cache.</p><p>But this pays for itself immediately. By Request 2, the cached version is already cheaper ($0.02 vs $0.04), and the gap only widens from there.</p><p><strong>The breakeven point is just 2 requests.</strong></p><h2><strong>Why &#8220;Tools Only&#8221; and &#8220;Partial&#8221; Performed the Same</strong></h2><p>In my experiment, caching just tools vs. caching tools + system prompt showed identical costs. This is because both approaches left the <strong>conversation history uncached</strong>, and that&#8217;s what dominated the cost.</p><p>By Request 7, the fully cached experiment was reading 13,464 tokens from cache. The tools and system prompt together account for maybe 8K of those tokens. The remaining 5K+ is conversation history that accumulated over the 7 turns.</p><p>When you only cache the static prefix (tools, or tools + system), you&#8217;re still reprocessing that growing conversation history on every single request. The marginal savings from caching an extra few thousand tokens of system prompt gets swamped by the cost of reprocessing 10K+ tokens of conversation.</p><p><strong>The real gains came from caching the conversation itself</strong> - that&#8217;s where the &#8220;fully cached&#8221; strategy pulled ahead.</p><h3><strong>Implementation Tips</strong></h3><p>Here&#8217;s the pattern that worked:</p><pre><code><code>response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=4096,
    tools=[
        # ... your tools here ...
        {
            "name": "final_tool",
            "description": "...",
            "input_schema": {...},
            "cache_control": {"type": "ephemeral"}  # Cache breakpoint 1
        }
    ],
    system=[
        {
            "type": "text",
            "text": "Your detailed system prompt...",
            "cache_control": {"type": "ephemeral"}  # Cache breakpoint 2
        }
    ],
    messages=[
        # ... previous conversation turns ...
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "Latest user message",
                    "cache_control": {"type": "ephemeral"} # Cache breakpoint 3
                }
            ]
        }
    ]
)</code></code></pre><p><strong>Key points:</strong></p><ol><li><p>Put cache_control on the <strong>last item</strong> in each section you want cached</p></li><li><p>The cache is hierarchical: tools &#8594; system &#8594; messages</p></li><li><p>Place your cache breakpoint at the end of each turn to incrementally cache the conversation</p></li></ol><h2><strong>When to Use Prompt Caching</strong></h2><p>Caching makes sense when you have:</p><ul><li><p><strong>Long system prompts</strong> (instructions, examples, documentation)</p></li><li><p><strong>Large context windows</strong> (RAG documents, code files)</p></li><li><p><strong>Multi-turn conversations</strong> (chatbots, agents)</p></li><li><p><strong>Repetitive tool definitions</strong> (same tools across many requests)</p></li></ul><p>It&#8217;s especially powerful for agentic workflows where you might make 10-20 API calls in a single task, each building on the previous context.</p><h2><strong>The Bottom Line</strong></h2><p>For a 7-turn conversation:</p><ul><li><p><strong>No caching</strong>: $0.32</p></li><li><p><strong>Full caching</strong>: $0.13</p></li><li><p><strong>Savings</strong>: 60%</p></li></ul><p>The cache writes cost extra on the first request, but you break even by Request 2 and save significantly from there. For any multi-turn application, prompt caching is essentially free money.</p><div><hr></div><p><em>Experiment run with Claude Sonnet 4.5. Actual savings will vary based on your prompt structure and conversation length.</em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.polos.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Neha's Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The Distributed Systems Problem: Why AI Agents Break in Production]]></title><description><![CDATA[If you&#8217;ve shipped an AI agent to production, you know the Day 2 problem.]]></description><link>https://blog.polos.dev/p/the-distributed-systems-problem-why</link><guid isPermaLink="false">https://blog.polos.dev/p/the-distributed-systems-problem-why</guid><dc:creator><![CDATA[Neha Deodhar]]></dc:creator><pubDate>Sun, 08 Feb 2026 01:32:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5Npj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2752c1b-fbc2-4835-aa11-dfa5acbe0b47_574x574.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you&#8217;ve shipped an AI agent to production, you know the Day 2 problem. <strong>Over 90% of AI agents fail before production.</strong> The models work. The infrastructure doesn&#8217;t. We&#8217;re running agents - long-running, stateful, autonomous workflows - on systems designed for stateless request-response.</p><p>When a 45-minute agent workflow dies at step 38 because your server restarted, you&#8217;ve lost more than an error log. You&#8217;ve burned tokens, API quota, and user trust. And unlike a failed HTTP request, you can&#8217;t just &#8220;retry&#8221; - the agent&#8217;s reasoning state is gone.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.polos.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Neha's Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>I&#8217;ve spent the last year building AI infrastructure. The tooling is shockingly immature. Every team hits the same failure modes and rebuilds the same broken solutions: brittle retry logic, state management hacks, rate limiting Band-Aids.</p><p>The problem isn&#8217;t the agents. It&#8217;s the <strong>infrastructure gap</strong>.</p><div><hr></div><h2><strong>Agents Break Every Infrastructure Assumption</strong></h2><h3><strong>Assumption #1: Fast &amp; Stateless</strong></h3><p><strong>Traditional apps:</strong> Handle request &#8594; return response &#8594; forget everything. Average response time: 50ms.</p><p><strong>Agents:</strong> Multi-step reasoning loops that run for minutes or hours. Call 20 different APIs. Make stochastic decisions at each step.</p><p><strong>The break:</strong> A single network blip or worker restart loses the entire workflow state. You can&#8217;t &#8220;just replay&#8221; because the agent&#8217;s call stack, local variables, and reasoning context are gone. No amount of retries will help you.</p><div><hr></div><h3><strong>Assumption #2: Concurrency is Request-Scoped</strong></h3><p><strong>Traditional apps:</strong> Each request is isolated. One user&#8217;s failed request doesn&#8217;t affect anyone else.</p><p><strong>Agents:</strong> Multiple agents running in parallel, all sharing the same API quotas, all racing to read and modify shared state.</p><p><strong>The break:</strong></p><p><strong>The rate limit cascade:</strong> Agent A is debugging a loop and burns through your OpenAI quota in 30 seconds. Agents B through Z all start failing with 429s. Your entire system is down because one agent misbehaved.</p><p><strong>The state race:</strong> Agent 1 reads user context at 10:00:00. Agent 2 modifies it at 10:00:01. Agent 1 writes based on stale data at 10:00:02. The user&#8217;s context is now corrupted, and you have no idea which agent&#8217;s view was &#8220;correct.&#8221;</p><p><strong>The resource starvation:</strong> One runaway agent spawns 50 parallel reasoning branches and starves everything else of memory and compute.</p><p>You need <strong>system-wide rate limiting</strong> (not per-agent), <strong>distributed locks</strong>, and <strong>transactional state management</strong>. None of this exists in standard application frameworks. You&#8217;re on your own.</p><div><hr></div><h3><strong>Assumption #3: Deterministic Execution</strong></h3><p><strong>Traditional software:</strong> Same input &#8594; same output. Bugs are reproducible. You read the stack trace, fix the code, deploy.</p><p><strong>Agents:</strong> Stochastic decision-making at every step. The same prompt can produce different tool calls, different reasoning paths, different outcomes.</p><p><strong>The break:</strong> Your agent hallucinates a tool call. Or it enters an infinite reasoning loop. Or it chooses Tool B when Tool A was obviously correct.</p><p>Your logs tell you <em>what</em> happened (&#8221;Agent called send_email with invalid parameters&#8221;). They don&#8217;t tell you <em>why</em> (was the context corrupted? did the model misinterpret the schema? did a previous tool call return bad data?).</p><p>Traditional monitoring - CPU graphs, error rates, p99 latency - is useless here. You need decision-level observability: <em>why</em> did the agent make that choice?</p><div><hr></div><h3><strong>Assumption #4: User-Scoped Auth</strong></h3><p><strong>Traditional apps:</strong> User clicks button &#8594; auth token attached to request &#8594; action performed &#8594; token discarded.</p><p><strong>Agents:</strong> Act on your behalf <em>while you&#8217;re offline</em>. Call 15 different APIs autonomously. Make decisions that require different permission levels depending on context.</p><p><strong>The break:</strong> Traditional OAuth wasn&#8217;t built for this. It assumes a human is present to click &#8220;Authorize&#8221; and handle browser redirects. Agents are headless - they act while the user is offline, asleep, or unreachable.</p><p>You can&#8217;t give the agent your master API key - that&#8217;s a security disaster. You can&#8217;t pre-scope every possible action - you don&#8217;t know what the agent will need until runtime.</p><p>You need <strong>dynamic, just-in-time credential delegation</strong>. Short-lived tokens scoped to exactly what the agent needs <em>right now</em>. And when the agent wants to do something sensitive - delete a database, charge a credit card - you need <strong>human-in-the-loop approval gates</strong> that pause execution, wait for a signature (sometimes for hours), and resume <em>without losing state</em>.</p><p>Your auth infrastructure doesn&#8217;t do this. You&#8217;re going to build it yourself, and you&#8217;re going to get it wrong the first three times.</p><div><hr></div><h3><strong>Assumption #5: Services Talk Via Contracts</strong></h3><p><strong>Traditional microservices:</strong> Rigid REST or gRPC interfaces. Typed schemas. You call GET /api/v2/items?sort=price&amp;limit=10 and you get exactly what you asked for.</p><p><strong>Agents:</strong> Communicate in high-level intents. &#8220;Find me the best option.&#8221; &#8220;Summarize the user&#8217;s preferences.&#8221; &#8220;Coordinate with the scheduling agent to find a time.&#8221;</p><p><strong>The break:</strong> Agent-to-agent communication isn&#8217;t just JSON over HTTP. It requires:</p><ul><li><p><strong>Context propagation:</strong> Agent B needs to know what Agent A was thinking, not just what data it returned.</p></li><li><p><strong>Shared working memory:</strong> A persistent space where agents can read and write state <em>across different servers and lifetimes</em>.</p></li><li><p><strong>Reasoning handoff:</strong> Agent A partially solves a problem, hands off to Agent B with full context, Agent B picks up where A left off.</p></li></ul><p>None of this exists in your service mesh. You&#8217;re going to build a custom &#8220;agent communication layer&#8221; and spend six months debugging context drift.</p><div><hr></div><h2><strong>What Reliable Agents Actually Require</strong></h2><p>If you want agents to work in production - not in a demo, not in a Jupyter notebook, but under real load with real users - you need infrastructure that doesn&#8217;t exist yet.</p><h3><strong>1. Durable Execution</strong></h3><p>Not checkpoints. Not retries. <strong>Guaranteed resumption.</strong></p><p>If a worker node dies mid-workflow, the agent must resume <em>on another node</em> with its call stack, local variables, and reasoning state intact. Progress isn&#8217;t &#8220;saved&#8221; - it&#8217;s <em>guaranteed</em>. The agent picks up at the exact line of code where it stopped, as if nothing happened.</p><p>This is how Temporal works for workflows. Agents need the same semantics. However, unlike a standard Temporal workflow, an agent&#8217;s path isn&#8217;t hardcoded in a DSL - it&#8217;s generated live by an LLM. This makes the durability requirement even more extreme: you aren&#8217;t just persisting data; you&#8217;re persisting a dynamic, evolving reasoning chain.</p><div><hr></div><h3><strong>2. Global Concurrency Control</strong></h3><p><strong>System-wide rate limiting:</strong> All agents share a single budget for OpenAI calls. If Agent A is burning tokens, Agents B-Z slow down proportionally. No cascading failures.</p><p><strong>Distributed coordination:</strong> Agents competing for the same resource (user state, external API, database row) use distributed locks or optimistic concurrency control. State corruption is impossible.</p><p><strong>Resource quotas:</strong> Runaway agents are killed before they starve the system. No single agent can take down production.</p><div><hr></div><h3><strong>3. Decision-Level Observability</strong></h3><p>Traditional logs: &#8220;Agent called Tool B at 10:00:03.&#8221;</p><p>What you need: &#8220;Agent chose Tool B over Tool A because the user&#8217;s context indicated preference X, and Tool A&#8217;s output schema didn&#8217;t match the downstream agent&#8217;s expectations.&#8221;</p><p>You need to trace the <em>reasoning</em>, not just the execution. Every decision point, every branch, every piece of context that influenced the outcome.</p><p>And when an agent fails, you need to replay its <em>thought process</em>, not just its API calls.</p><div><hr></div><h3><strong>4. Delegated Identity &amp; Dynamic Scoping</strong></h3><p><strong>Just-in-time credentials:</strong> When an agent needs to call Stripe, the infrastructure issues a short-lived token scoped to exactly the Stripe API and the specific user context. The token expires in 60 seconds. The agent never sees your master key.</p><p><strong>Approval gates:</strong> When the agent wants to execute a sensitive action, execution pauses. A notification goes to the user. The user approves or rejects (sometimes after several hours). Execution resumes <em>with full state intact</em>. The agent doesn&#8217;t restart from scratch.</p><p><strong>Audit trails:</strong> Every action the agent takes is logged with full attribution. Who authorized it? What context led to the decision? What credentials were used?</p><div><hr></div><h3><strong>5. Agent Communication Primitives</strong></h3><p><strong>Shared memory:</strong> A durable, transactional store where agents can read and write state. Agents running on different servers, at different times, can access the same working memory.</p><p><strong>Context propagation:</strong> When Agent A hands off to Agent B, B receives not just data but <em>the history of reasoning that produced that data</em>. B doesn&#8217;t start from zero.</p><p><strong>Handoff semantics:</strong> Agent A pauses. Agent B takes over. Agent A resumes later. The infrastructure manages the transition without dropping state.</p><div><hr></div><h2><strong>The Infrastructure Maturity Gap</strong></h2><p>Here&#8217;s what most teams do today:</p><ul><li><p>Build agents with Python scripts, cron jobs, and manual restarts</p></li><li><p>Add retry logic in application code (and get it wrong)</p></li><li><p>Store state in Redis or Postgres with custom serialization (and lose data during crashes)</p></li><li><p>Rate-limit by hoping agents don&#8217;t call OpenAI too fast</p></li><li><p>Debug failures by reading logs and guessing what the agent was thinking</p></li></ul><p><strong>Every team rebuilds the same broken infrastructure.</strong> The solutions are fragile, incomplete, and impossible to test.</p><p>Your team spends more time on infrastructure duct tape than on the agent itself. The work that actually differentiates your product - better reasoning, smoother user experience - barely gets attention.</p><div><hr></div><h2><strong>The Shift We Need</strong></h2><p><strong>From</strong>: Application-layer duct tape</p><p><strong>To:</strong> Infrastructure that handles state, concurrency, auth, and observability <em>so you don&#8217;t have to</em></p><div><hr></div><p>Agents are powerful. But right now, they&#8217;re production nightmares.</p><p>The teams that win won&#8217;t be the ones with the best prompts or the biggest models. They&#8217;ll be the ones who solved the infrastructure problem.</p><p>It&#8217;s time to stop building agents like scripts and start building them like distributed systems.</p><p>Because that&#8217;s what they are.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.polos.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Neha's Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Modern Voice Agent Architectures: A Deep Dive]]></title><description><![CDATA[Voice agents have become increasingly sophisticated, enabling natural human-computer interactions across various applications from virtual assistants to customer service agents.]]></description><link>https://blog.polos.dev/p/modern-voice-agent-architectures</link><guid isPermaLink="false">https://blog.polos.dev/p/modern-voice-agent-architectures</guid><dc:creator><![CDATA[Neha Deodhar]]></dc:creator><pubDate>Wed, 05 Nov 2025 19:55:13 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/2d7ac426-9fa4-4c7c-9230-9b13f51c0790_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.polos.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.polos.dev/subscribe?"><span>Subscribe now</span></a></p><p>Voice agents have become increasingly sophisticated, enabling natural human-computer interactions across various applications from virtual assistants to customer service agents. When designing these systems, developers typically choose between two architectural approaches, each with distinct advantages and trade-offs.</p><ol><li><p><strong>The Modular Pipeline</strong>: Speech-to-Text (STT) &#8594; LLM &#8594; Text-to-Speech (TTS): A  decomposed system where specialized components handle discrete functions in sequence.</p></li><li><p><strong>The Unified Approach</strong>: Speech-to-Speech Models: An end-to-end approach where a single integrated system processes audio input and generates audio output with minimal intermediate transformations.</p></li></ol><p>Let&#8217;s examine each approach in detail.</p><h2><strong>1. Modular Pipeline (STT &#8594; LLM &#8594; TTS)</strong></h2><p>In this architecture, three components operate as a pipeline:</p><p><strong>Speech-to-Text (STT)</strong>: This is the first stage of the pipeline. It captures and converts the user&#8217;s audio input into text transcription, often with specialized acoustic and language models.</p><p><strong>Large Language Model (LLM)</strong>: This is the brain of the agent. It processes the transcribed text, performs reasoning, calls tools and external APIs, manages context or memory, and generates appropriate responses.</p><p><strong>Text-to-Speech (TTS)</strong>: This is the final stage that synthesizes the LLM&#8217;s text response into spoken audio output.</p><p>Most modern implementations stream these components to reduce latency, allowing the agent to begin formulating responses even before the user finishes speaking.</p><h3><strong>Critical Auxiliary Components: VAD and Turn Detection</strong></h3><p>Beyond these three core components, effective voice agents require two additional systems:</p><p><strong>Voice Activity Detection (VAD)</strong>: This component identifies when a user is speaking versus when there is silence or background noise. VAD is essential for determining when to start and stop processing audio, conserving computational resources and reducing latency. High-quality VAD systems can distinguish between human speech and other sounds, preventing false activations.</p><p><strong>Turn Detection</strong>: This component determines when a user has completed their thought or utterance, signaling to the agent that it&#8217;s time to respond. Effective turn detection is crucial for natural conversation flow and prevents the agent from interrupting users mid-sentence. Turn detection may use a combination of silence duration, prosodic features (intonation patterns), and semantic completeness to identify appropriate response moments.</p><p>Consider this scenario for <strong>turn detection</strong>:</p><p><em><strong>&#8220;I&#8217;m looking for tickets to the concert &#8230; [brief pause] on Friday.&#8221;</strong></em></p><p>A naive turn detector might interpret the pause after &#8220;concert&#8221; as the end of the user&#8217;s turn and trigger a response. However, a sophisticated turn detector would recognize that the semantic content may be incomplete or that the intonation suggests more information is coming, and would wait for the complete utterance before prompting the agent to respond.</p><p>Newer STT models are increasingly incorporating these capabilities directly. For example, AssemblyAI&#8217;s Universal and OpenAI&#8217;s realtime models now offer built-in VAD and preliminary turn detection features, simplifying the architecture while potentially improving responsiveness.</p><h3>Example Agent</h3><p>Below is an example agent in LiveKit using modular pipeline architecture. It uses:</p><ul><li><p>Assembly AI for Speech-to-Text</p></li><li><p>OpenAI for LLM</p></li><li><p>Cartesia for Text-to-Speech</p></li></ul><p>This example uses an edited version of LiveKit&#8217;s example from <a href="https://github.com/livekit-examples/agent-starter-python">https://github.com/livekit-examples/agent-starter-python</a> </p><pre><code><code>import logging

from dotenv import load_dotenv
from livekit.agents import (
    Agent,
    AgentSession,
    JobContext,
    JobProcess,
    MetricsCollectedEvent,
    RoomInputOptions,
    WorkerOptions,
    cli,
    inference,
    metrics,
)
from livekit.plugins import noise_cancellation, silero
from livekit.plugins.turn_detector.multilingual import MultilingualModel

logger = logging.getLogger(&#8221;agent&#8221;)

load_dotenv(&#8221;.env&#8221;)


class Assistant(Agent):
    def __init__(self) -&gt; None:
        super().__init__(
            instructions=&#8221;&#8220;&#8221;You are a helpful voice AI assistant. The user is interacting with you via voice, even if you perceive the conversation as text.
            You eagerly assist users with their questions by providing information from your extensive knowledge.
            Your responses are concise, to the point, and without any complex formatting or punctuation including emojis, asterisks, or other symbols.
            You are curious, friendly, and have a sense of humor.&#8221;&#8220;&#8221;,
        )

def prewarm(proc: JobProcess):
    proc.userdata[&#8221;vad&#8221;] = silero.VAD.load()


async def entrypoint(ctx: JobContext):
    ctx.log_context_fields = {
        &#8220;room&#8221;: ctx.room.name,
    }

    # Set up a voice AI pipeline using OpenAI, Cartesia, AssemblyAI, and the LiveKit turn detector
    session = AgentSession(
        stt=inference.STT(model=&#8221;assemblyai/universal-streaming&#8221;, language=&#8221;en&#8221;),
        llm=inference.LLM(model=&#8221;openai/gpt-4.1-mini&#8221;),
        tts=inference.TTS(
            model=&#8221;cartesia/sonic-3&#8221;, voice=&#8221;9626c31c-bec5-4cca-baa8-f8ba9e84c8bc&#8221;
        ),
        turn_detection=MultilingualModel(),
        vad=ctx.proc.userdata[&#8221;vad&#8221;],
        preemptive_generation=True,
    )

    # Metrics collection, to measure pipeline performance
    usage_collector = metrics.UsageCollector()

    @session.on(&#8221;metrics_collected&#8221;)
    def _on_metrics_collected(ev: MetricsCollectedEvent):
        metrics.log_metrics(ev.metrics)
        usage_collector.collect(ev.metrics)

    async def log_usage():
        summary = usage_collector.get_summary()
        logger.info(f&#8221;Usage: {summary}&#8221;)

    ctx.add_shutdown_callback(log_usage)

    # Start the session, which initializes the voice pipeline and warms up the models
    await session.start(
        agent=Assistant(),
        room=ctx.room,
        room_input_options=RoomInputOptions(
            # For telephony applications, use `BVCTelephony` for best results
            noise_cancellation=noise_cancellation.BVC(),
        ),
    )

    # Join the room and connect to the user
    await ctx.connect()


if __name__ == &#8220;__main__&#8221;:
    cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint, prewarm_fnc=prewarm))</code></code></pre><h3><strong>Advantages</strong></h3><ol><li><p><strong>Independent Scaling &amp; Optimization:</strong> Each component is a separate service and can be scaled or optimized independently. For example, you can scale the STT workers based on user load and the LLM workers based on computational intensity independently.</p></li><li><p><strong>Flexibility &amp; Vendor Lock-in Mitigation:</strong> This is the ultimate &#8220;plug-and-play&#8221; architecture. You can easily swap an AWS Transcribe STT with a fine-tuned Whisper model or switch from GPT-4 to Claude for the LLM without major system rewrites.</p></li><li><p><strong>Tool and RAG Integration:</strong> LLMs can easily integrate with external tools, databases, and APIs between the STT and TTS stages.</p></li><li><p><strong>Explainability and Audit:</strong> The architecture inherently produces clean, traceable text transcripts at the STT output, which is critical for logging, compliance, fine-tuning, and downstream analytics.</p></li></ol><h3><strong>Limitations</strong></h3><ol><li><p><strong>Latency</strong>: Each transition between components in the pipeline introduces some latency, potentially affecting conversation flow and making the interaction feel less spontaneous.</p></li><li><p><strong>Error Propagation</strong>: Errors in earlier components (e.g., STT misrecognition) cascade through the pipeline.</p></li><li><p><strong>Context Loss</strong>: Prosodic information like tone or emphasis may be lost when converting speech to text, losing the user&#8217;s intent and limiting the ability to respond with genuine emotional context.</p><p></p></li></ol><h2><strong>2. Unified Approach: Speech-to-Speech Models</strong></h2><p>This newer architecture uses end-to-end models that process audio directly and generate audio responses with minimal intermediate steps. While text may still be used internally as a latent representation, the primary pathway is audio-to-audio, eliminating explicit modality conversions.</p><p>These models are trained to maintain conversational context and generate responses in real-time, often producing more natural-sounding interactions including non-verbal backchannels (like &#8220;mm-hmm&#8221; or &#8220;uh-huh&#8221;).</p><p>Below is an example agent in LiveKit using OpenAI&#8217;s realtime model.</p><pre><code>import logging

from dotenv import load_dotenv
from livekit.agents import (
    Agent,
    AgentSession,
    JobContext,
    JobProcess,
    MetricsCollectedEvent,
    RoomInputOptions,
    WorkerOptions,
    cli,
    inference,
    metrics,
)
from livekit.plugins import openai, noise_cancellation, silero
from livekit.plugins.turn_detector.multilingual import MultilingualModel


logger = logging.getLogger(&#8221;agent&#8221;)

load_dotenv(&#8221;.env&#8221;)


class Assistant(Agent):
    def __init__(self) -&gt; None:
        super().__init__(
            instructions=&#8221;&#8220;&#8221;You are a helpful voice AI assistant. The user is interacting with you via voice, even if you perceive the conversation as text.
            You eagerly assist users with their questions by providing information from your extensive knowledge.
            Your responses are concise, to the point, and without any complex formatting or punctuation including emojis, asterisks, or other symbols.
            You are curious, friendly, and have a sense of humor.&#8221;&#8220;&#8221;,
        )

def prewarm(proc: JobProcess):
    proc.userdata[&#8221;vad&#8221;] = silero.VAD.load()


async def entrypoint(ctx: JobContext):
    ctx.log_context_fields = {
        &#8220;room&#8221;: ctx.room.name,
    }

    session = AgentSession(
        llm=openai.realtime.RealtimeModel(voice=&#8221;marin&#8221;),
        turn_detection=MultilingualModel(),
        vad=ctx.proc.userdata[&#8221;vad&#8221;],
    )

    session = AgentSession(
         llm=openai.realtime.RealtimeModel(voice=&#8221;marin&#8221;)
    )

    # Metrics collection, to measure pipeline performance
    usage_collector = metrics.UsageCollector()

    @session.on(&#8221;metrics_collected&#8221;)
    def _on_metrics_collected(ev: MetricsCollectedEvent):
        metrics.log_metrics(ev.metrics)
        usage_collector.collect(ev.metrics)

    async def log_usage():
        summary = usage_collector.get_summary()
        logger.info(f&#8221;Usage: {summary}&#8221;)

    ctx.add_shutdown_callback(log_usage)

    # Start the session, which initializes the voice pipeline and warms up the models
    await session.start(
        agent=Assistant(),
        room=ctx.room,
        room_input_options=RoomInputOptions(
            # For telephony applications, use `BVCTelephony` for best results
            noise_cancellation=noise_cancellation.BVC(),
        ),
    )

    # Join the room and connect to the user
    await ctx.connect()


if __name__ == &#8220;__main__&#8221;:
    cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint, prewarm_fnc=prewarm))</code></pre><h3><strong>Advantages</strong></h3><ol><li><p><strong>Lower Latency</strong>: Direct audio processing enables faster turn-taking and more natural conversation flow.</p></li><li><p><strong>Preserved Prosody</strong>: Maintains acoustic features like tone, emphasis, and rhythm throughout processing. This results in the model:</p><ul><li><p>Responding with appropriate tone, e.g., a sympathetic voice to a frustrated user.</p></li><li><p>Generating natural backchanneling (&#8221;mm-hmm,&#8221; &#8220;uh-huh&#8221;) precisely timed during the user&#8217;s speech.</p></li></ul></li><li><p><strong>Simplified Deployment:</strong> Less operational complexity than coordinating three distinct, streaming components.</p></li></ol><h3><strong>Limitations</strong></h3><ol><li><p><strong>Black-Box Constraint:</strong> This is a major hurdle for enterprise deployments. The lack of an explicit, auditable text transcript makes debugging, explainability, and compliance more challenging. If the agent fails, diagnosing whether it was a &#8220;speech understanding&#8221; or &#8220;reasoning&#8221; failure is difficult.</p></li><li><p><strong>Reasoning capabilities: </strong>The reasoning capabilities of these models are lesser than their similar sized LLM counterparts.</p></li><li><p><strong>Reduced Flexibility:</strong> Hard to swap or fine-tune one aspect (e.g., improving speech recognition).</p></li></ol><p>As comparison, here are two voice recordings from agent interactions built using the two architectures. The first recording uses an agent built with the modular pipeline approach (STT &#8594; LLM &#8594; TTS), while the second uses OpenAI&#8217;s realtime speech model showcasing the unified approach.</p><p><strong>Modular STT -&gt; LLM -&gt; TTS agent</strong></p><div class="native-audio-embed" data-component-name="AudioPlaceholder" data-attrs="{&quot;label&quot;:null,&quot;mediaUploadId&quot;:&quot;c4803895-b305-4525-9dee-f393412cb45b&quot;,&quot;duration&quot;:30.249796,&quot;downloadable&quot;:false,&quot;isEditorNode&quot;:true}"></div><p><strong>Speech-to-speech agent using OpenAI</strong></p><div class="native-audio-embed" data-component-name="AudioPlaceholder" data-attrs="{&quot;label&quot;:null,&quot;mediaUploadId&quot;:&quot;0e567bf6-e777-4e49-96c5-c8ef2a77af1b&quot;,&quot;duration&quot;:22.204082,&quot;downloadable&quot;:false,&quot;isEditorNode&quot;:true}"></div><p></p><h2><strong>Conclusion</strong></h2><p>The choice between these architectures depends on your specific requirements, resources, and the nature of the voice interactions you&#8217;re designing. The modular pipeline approach is more widely used currently and offers flexibility and explainability at the cost of some latency and potential error propagation, while unified speech models provide more natural interactions but with less visibility and flexibility.</p><p>As the field evolves, we&#8217;ll see the realtime speech models continue to get better at handling complex interactions, supporting multiple languages, and integrating with external systems. Their capabilities will expand while maintaining the conversational fluidity that makes them compelling, potentially narrowing the flexibility gap with modular systems.</p><p>For technical teams building voice agents today, understanding these architectural trade-offs is essential for delivering experiences that meet user expectations for both functionality and naturalness.</p><p></p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.polos.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Neha's Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>