<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Technology, Craft, and Ideas</title>
    <description>A blog covering Technology, Craft, and Ideas from Ritesh Shrivastav.</description>
    <link>https://codingdash.com/</link>
    <atom:link href="https://codingdash.com/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sun, 07 Jun 2026 08:24:54 +0000</pubDate>
    <lastBuildDate>Sun, 07 Jun 2026 08:24:54 +0000</lastBuildDate>
    
      <item>
        <title>AI Made You Faster. It Hasn&apos;t Made Your Team Faster. Yet.</title>
        <description>&lt;p&gt;Your team is using Claude. Or Cursor. Or Codex. Each developer has their own sessions, their own context, their own results. The agent is fast. The team is not getting faster together.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;That is what I kept running into. Individual productivity was real — Claude is good at that. But each session started from scratch. Nothing from one session carried into the next. Nothing from one developer’s sessions was visible to anyone else. The agent had no memory of what the team was building or what had already been decided.&lt;/p&gt;

&lt;p&gt;I wanted the agent to stop working from isolated prompts and start working from the same plan the team was using.&lt;/p&gt;

&lt;p&gt;That became &lt;a href=&quot;https://stokik.com?utm_source=codingdash&amp;amp;utm_medium=blog&amp;amp;utm_campaign=ai-made-you-faster-not-your-team&amp;amp;utm_content=body&quot;&gt;Stokik&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is the full workflow in practice — Claude generates the plan, the canvas populates live, then Claude builds from it:&lt;/p&gt;

&lt;div class=&quot;thumbnail&quot;&gt;
    &lt;div class=&quot;embed-responsive embed-responsive-16by9&quot;&gt;
        &lt;iframe class=&quot;embed-responsive-item&quot; src=&quot;https://www.youtube.com/embed/ISM8bz7WGMA&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&quot;why-every-session-starts-from-zero&quot;&gt;Why every session starts from zero&lt;/h2&gt;

&lt;p&gt;The underlying problem is that AI coding agents still do not have a reliable shared memory of how the team is planning and executing the project. Each session begins with whatever you give it: a pasted ticket, a description you type by hand, some context you pull from memory. The agent helps. The session ends. The next developer — or even the same developer the next day — starts over.&lt;/p&gt;

&lt;p&gt;Working alone, this is manageable. In a team it compounds. The backend developer who changed the auth model on Tuesday did not update anyone else’s context. The frontend developer picking up a related task on Thursday has no way to know. Nobody is deliberately withholding anything. The information just never had a place to live that the agent could read.&lt;/p&gt;

&lt;p&gt;Each team member is working toward the same goal, but in their own silo. The agent makes each silo faster. The silos are still silos.&lt;/p&gt;

&lt;h2 id=&quot;a-shared-plan-the-agent-reads-from-and-writes-back-to&quot;&gt;A shared plan the agent reads from and writes back to&lt;/h2&gt;

&lt;p&gt;Stokik is a canvas where tasks are nodes and dependencies are labeled edges. The shape of the work is visible: which tasks block which others, what is in scope, what is in progress, what is done. Each task carries a document — a spec, an investigation, implementation notes, the reasoning behind a decision.&lt;/p&gt;

&lt;p&gt;The canvas is shared. Every developer sees the same picture. When someone changes a technical decision, scopes a task differently, or discovers a new dependency, it goes into Stokik. The context travels with the work rather than staying in the head of whoever made the call.&lt;/p&gt;

&lt;p&gt;This is not just for engineers. Product managers can see the dependency graph, understand what is blocked, and contribute to planning without needing to decode a technical backlog or wait for a standup summary. Non-technical team members get a visual form of the plan they can actually read. Developers stop being the translation layer between what is being built and what everyone else can see.&lt;/p&gt;

&lt;p&gt;Because Stokik has a native MCP server, the agent can read all of it.&lt;/p&gt;

&lt;h2 id=&quot;connecting-the-agent&quot;&gt;Connecting the agent&lt;/h2&gt;

&lt;p&gt;One command to connect Claude Code:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;claude mcp add &lt;span class=&quot;nt&quot;&gt;--transport&lt;/span&gt; http stokik https://api.stokik.com/mcp/ &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--header&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Authorization: Bearer stokik_mcp_...&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Generate a token from &lt;strong&gt;Settings &amp;gt; MCP Integrations&lt;/strong&gt; in Stokik. The full setup for both Claude Code and Codex is in &lt;a href=&quot;https://stokik.com/blog/connect-claude-codex-to-stokik-with-mcp?utm_source=codingdash&amp;amp;utm_medium=blog&amp;amp;utm_campaign=ai-made-you-faster-not-your-team&quot;&gt;How to Connect Claude and Codex to Stokik with MCP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once connected, start a session with the actual plan in scope:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Use Stokik MCP and inspect the project named &quot;Checkout Revamp&quot;.
Read the tasks, documents, and edges. Find the next task that is ready,
make the relevant code change, update the status, and record progress.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The agent reads the canvas — which tasks are blocked, which are ready, what the attached documents say. When it finishes, the status update and progress note go back into Stokik. The next developer, or the next session, can see what was done and what is still open.&lt;/p&gt;

&lt;p&gt;The session does not disappear. That is the loop: session runs, work gets done, the plan reflects it, the team sees it.&lt;/p&gt;

&lt;h2 id=&quot;the-collaboration-contract&quot;&gt;The collaboration contract&lt;/h2&gt;

&lt;p&gt;The MCP connection solves the reading problem. The skill file solves the consistency problem.&lt;/p&gt;

&lt;p&gt;When multiple developers run agent sessions against the same project, you want those sessions to behave consistently. Same label conventions, same status rules, same understanding of when to create a task versus update an existing one. Without this, different sessions contradict each other and the shared canvas fills with noise instead of signal.&lt;/p&gt;

&lt;p&gt;A skill file is a markdown document checked into the repo that defines how agent sessions interact with the Stokik project. Which project to use. What a good task label looks like. How to handle agent runs. What to check before creating a task.&lt;/p&gt;

&lt;p&gt;For Claude Code, one line in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;@.claude/stokik.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Every developer who opens Claude Code in that repo inherits the conventions automatically. The team decides how the agent contributes — not each individual independently, rediscovering the same conventions every session.&lt;/p&gt;

&lt;p&gt;This is what makes it a team tool rather than a collection of individual ones. The plan in Stokik defines what needs to be built. The skill file defines how agents participate in building it. Together they give the team a collaboration contract that covers both human and AI contributors.&lt;/p&gt;

&lt;p&gt;The full skill file format — task label conventions, status rules, document structure, agent run lifecycle — is covered in &lt;a href=&quot;https://stokik.com/blog/add-stokik-skill-to-your-codebase?utm_source=codingdash&amp;amp;utm_medium=blog&amp;amp;utm_campaign=ai-made-you-faster-not-your-team&quot;&gt;How to Add a Stokik Skill to Your Codebase&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;what-changes-for-the-team&quot;&gt;What changes for the team&lt;/h2&gt;

&lt;p&gt;AI coding tools made individual developers faster. That is real and worth keeping. What was missing was team leverage: a shared plan every session could read from and write back to, and a way for the team to define how agents contribute rather than leaving each developer to figure it out separately.&lt;/p&gt;

&lt;p&gt;The sessions stop being islands.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/img/post/ai-made-you-faster-not-your-team/session-loop.svg&quot; data-light=&quot;/img/post/ai-made-you-faster-not-your-team/session-loop.svg&quot; data-dark=&quot;/img/post/ai-made-you-faster-not-your-team/session-loop-dark.svg&quot; alt=&quot;Before: isolated prompt flows to agent, then local result, then lost context — a dead end. After: shared plan flows to agent session, then code change, then status update, then loops back in orange to shared plan.&quot; /&gt;
&lt;/figure&gt;

&lt;p&gt;The same canvas model also works &lt;a href=&quot;https://stokik.com/case-studies/career-pivot-ai/?utm_source=codingdash&amp;amp;utm_medium=blog&amp;amp;utm_campaign=ai-made-you-faster-not-your-team&quot;&gt;beyond&lt;/a&gt; engineering teams, for personal planning and learning roadmaps.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Stokik is at &lt;a href=&quot;https://stokik.com?utm_source=codingdash&amp;amp;utm_medium=blog&amp;amp;utm_campaign=ai-made-you-faster-not-your-team&amp;amp;utm_content=cta&quot;&gt;stokik.com&lt;/a&gt;. Still early and in beta, free to start with no waitlist. Curious what you think.&lt;/p&gt;
</description>
        <pubDate>Sat, 06 Jun 2026 18:40:00 +0000</pubDate>
        <link>https://codingdash.com/post/ai-made-you-faster-not-your-team/</link>
        <guid isPermaLink="true">https://codingdash.com/post/ai-made-you-faster-not-your-team/</guid>
        
        <category>AI Coding Tools</category>
        
        <category>Developer Tools</category>
        
        <category>MCP</category>
        
        <category>Claude Code</category>
        
        <category>Stokik</category>
        
        <category>Project Planning</category>
        
        <category>AI agents</category>
        
        
        <category>posts</category>
        
      </item>
    
      <item>
        <title>Automation Bias: A Thinking Model for AI Tools</title>
        <description>&lt;p&gt;In May 2023, a New York lawyer named &lt;a href=&quot;https://www.cnbc.com/2023/06/22/judge-sanctions-lawyers-whose-ai-written-filing-contained-fake-citations.html&quot;&gt;Steven Schwartz&lt;/a&gt; filed a court brief in a case against Avianca airline. The brief cited six legal precedents — full case names, docket numbers, courts, years, and reasoning that tracked the argument cleanly.&lt;/p&gt;

&lt;!--more--&gt;

&lt;figure class=&quot;photostream double&quot;&gt;
    &lt;div class=&quot;image thumbnail&quot;&gt;
        &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/05/02/automation-bias-a-thinking-model-for-ai-tools.png&quot; class=&quot;img-responsive&quot; alt=&quot;&quot; /&gt;
    &lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;None of the cases existed. ChatGPT had invented all six.&lt;/p&gt;

&lt;p&gt;When the judge asked Schwartz to produce the full opinions, he couldn’t. The cases weren’t in any legal database because they’d never been decided. In his response to the court, Schwartz explained he “was unaware that ChatGPT could fabricate cases.” His filing partner had checked the citations by asking ChatGPT if they were real. ChatGPT confirmed they were.&lt;/p&gt;

&lt;p&gt;This is not a story about incompetence. Schwartz had practiced law for decades. What happened to him has a name — one that predates generative AI by decades — and understanding it is the most useful calibration adjustment anyone using AI tools can make right now.&lt;/p&gt;

&lt;h2 id=&quot;what-automation-bias-actually-is&quot;&gt;What automation bias actually is&lt;/h2&gt;

&lt;p&gt;The term was coined by researchers Kathleen Mosier and Linda Skitka in the 1990s,&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; studying pilots using automated cockpit systems. Their definition is precise: the tendency to use automation as a heuristic replacement for vigilant information seeking and processing. Their research also identified two distinct error types:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Errors of commission&lt;/strong&gt; — you take an action you shouldn’t because the automated system recommended it, even when other indicators contradict it. Schwartz submitted citations he hadn’t verified because the AI produced them with the same formatting and confidence as real ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Errors of omission&lt;/strong&gt; — you fail to act because the system didn’t alert you to a problem. The AI summary that leaves out a critical clause; you don’t catch what you were never shown.&lt;/p&gt;

&lt;p&gt;Both errors share the same root: vigilance was outsourced to the system, and the system didn’t return it.&lt;/p&gt;

&lt;p&gt;The groundwork was laid earlier by cognitive scientist Lisanne Bainbridge, who in 1983 described the &lt;a href=&quot;https://en.wikipedia.org/wiki/Ironies_of_Automation&quot;&gt;“ironies of automation”&lt;/a&gt;&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;: the more reliable an automated system, the less likely a human operator is to notice when it fails — and the less capable they are of recovering when it does.&lt;/p&gt;

&lt;p&gt;The canonical example is &lt;a href=&quot;https://en.wikipedia.org/wiki/Air_France_Flight_447&quot;&gt;Air France Flight 447&lt;/a&gt;&lt;sup id=&quot;fnref:3&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;, which crashed into the Atlantic in 2009 with 228 people aboard. The Airbus autopilot disengaged at 35,000 feet after ice blocked the speed sensors. The pilots, who had been monitoring the autopilot for hours, suddenly had to fly manually. They pulled up into a stall instead of leveling off. It took 54 seconds from the autopilot disengaging to the plane becoming unrecoverable. The investigation found that the pilots’ manual flying skills had atrophied from disuse. Their first instinct — under stress, with confusing instrument readings — was to trust what the system had been doing, not to think from scratch.&lt;/p&gt;

&lt;p&gt;The flight had been fine, until it wasn’t. The fact that it had been fine was exactly why it failed.&lt;/p&gt;

&lt;h2 id=&quot;why-ai-tools-change-the-equation&quot;&gt;Why AI tools change the equation&lt;/h2&gt;

&lt;p&gt;Autopilot gave pilots confidence because it had a strong track record. But autopilot’s failures were still detectable — instruments read wrong, alerts sounded, things clearly didn’t add up. The failure modes announced themselves.&lt;/p&gt;

&lt;p&gt;AI tools are different because of &lt;em&gt;fluency&lt;/em&gt;. The output doesn’t just perform a function — it performs expertise. It presents itself with the register and confidence of someone who knows what they’re talking about. A model asked to summarize a contract produces text that reads like a lawyer reviewed it. Asked to write code, it produces code that reads like an experienced engineer wrote it. Asked to find legal precedents, it produces case citations that look exactly like real case citations.&lt;/p&gt;

&lt;p&gt;This breaks the heuristic most people use to calibrate trust. When a junior analyst gives a shaky response, the delivery usually signals the shaky reasoning. When an AI gives a shaky response, it reads with the same clarity as a correct one. The presentation doesn’t degrade with the quality of the reasoning underneath.&lt;/p&gt;

&lt;p&gt;Schwartz didn’t fail to check because he was careless. He failed because the output gave him no signal that checking was necessary. That’s what fluency does — it decouples the appearance of reliability from actual reliability.&lt;/p&gt;

&lt;h2 id=&quot;the-domain-problem&quot;&gt;The domain problem&lt;/h2&gt;

&lt;p&gt;The other dimension is how well you know what you’re reviewing. And this is where automation bias plays out differently depending on your expertise.&lt;/p&gt;

&lt;p&gt;In familiar territory, fluency is less dangerous. A backend engineer reviewing AI-generated API code can evaluate it on substance — the patterns are familiar, the wrong assumptions stand out, the tests he’d write cover the edge cases he knows to worry about. The presentation doesn’t fool him because he has a reference point for what right actually looks like.&lt;/p&gt;

&lt;p&gt;In unfamiliar territory, fluency is almost all you have. A backend engineer reviewing AI-generated payment processing in a codebase she’s never touched. A product manager asking AI to summarize a technical spec she doesn’t fully understand. Someone reviewing an AI summary of a contract without legal training. In each case, the output looks right because they don’t have a strong prior for what right looks like. They’re evaluating presentation, not substance.&lt;/p&gt;

&lt;p&gt;This is the trap: your ability to review output is domain-specific. The AI’s confident presentation is not. The same fluency appears whether the AI is on solid ground or in deep water. You don’t get a warning when you’ve crossed from one to the other.&lt;/p&gt;

&lt;p&gt;There’s a version of this that catches developers specifically. The code compiles. Tests pass. The PR looks clean. What doesn’t surface in any of that is the subtle security vulnerability, the architectural choice that creates friction two features from now, the edge case in the error handling that production traffic will eventually find. Tests confirm behavior against a spec. They don’t confirm the spec was complete, or that the implementation has no implications beyond the spec.&lt;/p&gt;

&lt;p&gt;“The tests pass” feels like enough. Often it is. The calibration question is knowing when it isn’t.&lt;/p&gt;

&lt;h2 id=&quot;the-calibration-that-actually-helps&quot;&gt;The calibration that actually helps&lt;/h2&gt;

&lt;p&gt;Automation bias is a calibration problem, not a trust problem. The issue isn’t that Schwartz trusted AI — it’s that he trusted it at the same level regardless of context. He trusted a ChatGPT citation the same way he’d trust a Westlaw result, without accounting for how differently each system works.&lt;/p&gt;

&lt;p&gt;Three questions that change the default:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I verify this independently?&lt;/strong&gt; Not “does it look right” — that’s the fluency check, which is exactly what automation bias exploits. The question is whether there’s an external reference. Westlaw for legal citations. A staging environment for code. The source document for an AI summary. If there’s no independent path, you’re extending trust on presentation alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What’s the cost of being wrong?&lt;/strong&gt; A low-stakes internal note is different from a brief filed in federal court. A script against test data is different from one touching production. Acceptable deferred verification should scale inversely with the cost of error. Schwartz’s mistake wasn’t using ChatGPT for research — it was not verifying citations before submitting them to a federal judge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Am I reviewing substance or presentation?&lt;/strong&gt; If you read an AI-generated document and felt satisfied because it was well-organized and covered the right topics — that’s a presentation check. Presentation checks catch almost nothing that matters. Substance checks verify specific claims, confirm specific facts, ask whether specific commitments are accurate. These are different kinds of reading, and conflating them is how automation bias works in practice.&lt;/p&gt;

&lt;h2 id=&quot;the-habits-that-shift-the-default&quot;&gt;The habits that shift the default&lt;/h2&gt;

&lt;p&gt;The mechanism of automation bias is passive. You don’t decide to skip scrutiny — the output simply doesn’t trigger the scrutiny instinct. Countermeasures mean creating deliberate friction that doesn’t depend on you remembering to be skeptical.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read AI output as if someone else wrote it.&lt;/strong&gt; When you authored the draft, you fill in intent you had but didn’t write. Treating AI output as external removes that charity and forces you to read what’s actually there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For code — implement the first instance of any unfamiliar pattern yourself.&lt;/strong&gt; Write it before delegating repetitions. The first migration script is how you learn what migrations do. The tenth is mechanical. Delegation becomes safe once you understand what you’re delegating.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For summaries — go back to the source for anything that matters.&lt;/strong&gt; The summary can’t show you what it left out. If the stakes are high enough that you needed a summary, they’re usually high enough to verify the key points against the original.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Match scrutiny to stakes, deliberately.&lt;/strong&gt; Being skeptical of everything defeats the purpose of the tool. The useful habit isn’t general suspicion — it’s the specific pause to ask: what is this output for, and what happens if something in it is wrong?&lt;/p&gt;

&lt;h2 id=&quot;the-reframe&quot;&gt;The reframe&lt;/h2&gt;

&lt;p&gt;The Schwartz story spread through legal circles as a cautionary tale about trusting AI. That framing misses the point. AI tools are getting better, not worse. The output is more accurate, more nuanced, more useful with every model generation.&lt;/p&gt;

&lt;p&gt;The point isn’t to trust AI less. It’s that the better AI gets, the more consequential it becomes to calibrate your trust correctly. When AI output was rough and obviously imperfect, the imperfections were visible. As it becomes polished and fluent and nearly always right, the cases where it’s wrong become harder to spot without deliberate effort — not easier.&lt;/p&gt;

&lt;p&gt;Automation bias isn’t a problem that better models will solve. It’s a problem that better models will sharpen. The skill of calibrated engagement — knowing when to accept and when to look harder — becomes more valuable as the tool improves, not less.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Mosier, K. L., &amp;amp; Skitka, L. J. (1996). &lt;a href=&quot;https://www.sciencedirect.com/science/article/abs/pii/S1071581999902525&quot;&gt;Does automation bias decision-making?&lt;/a&gt; &lt;em&gt;International Journal of Human-Computer Studies&lt;/em&gt;. Foundational research defining automation bias and identifying commission and omission error types. See also the &lt;a href=&quot;https://en.wikipedia.org/wiki/Automation_bias&quot;&gt;Wikipedia article on Automation Bias&lt;/a&gt; for a broader overview of the research lineage. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Bainbridge, L. (1983). &lt;a href=&quot;https://ckrybus.com/static/papers/Bainbridge_1983_Automatica.pdf&quot;&gt;Ironies of Automation&lt;/a&gt;. &lt;em&gt;Automatica&lt;/em&gt;, 19(6), 775–779. One of the most cited papers in human factors research, with over 1,800 citations by 2016 and still rising. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Bureau d’Enquêtes et d’Analyses (BEA), Final Report on the accident on 1st June 2009 to the Airbus A330-203. The full report is &lt;a href=&quot;https://www.faa.gov/sites/faa.gov/files/AirFrance447_BEA.pdf&quot;&gt;hosted by the FAA&lt;/a&gt;. IEEE Spectrum’s reconstruction of the final minutes: &lt;a href=&quot;https://spectrum.ieee.org/air-france-flight-447s-final-minutes-reconstructed&quot;&gt;Air France 447’s Final Minutes Reconstructed&lt;/a&gt;. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Fri, 01 May 2026 18:30:00 +0000</pubDate>
        <link>https://codingdash.com/post/automation-bias-a-thinking-model-for-ai-tools/</link>
        <guid isPermaLink="true">https://codingdash.com/post/automation-bias-a-thinking-model-for-ai-tools/</guid>
        
        <category>AI</category>
        
        <category>Software Engineering</category>
        
        <category>Developer Tools</category>
        
        <category>Developer Productivity</category>
        
        <category>AI Coding Tools</category>
        
        <category>Engineering Team Health</category>
        
        <category>Product Strategy</category>
        
        
        <category>posts</category>
        
      </item>
    
      <item>
        <title>Understanding Compaction</title>
        <description>&lt;p&gt;You are forty minutes into a code review with Claude. A complex authentication overhaul — eight files, security implications you have been mapping carefully. Claude has been methodical: checking the middleware chain, spotting the expired-token edge case, tracking the open questions as they pile up.&lt;/p&gt;

&lt;p&gt;Then the session pauses for a beat. Claude resumes. Something is off. The response feels generic. You ask about the middleware ordering decision from twenty minutes ago — the one that settled a key design question. Claude acts like it never happened.&lt;/p&gt;

&lt;p&gt;It did not forget. It compacted.&lt;/p&gt;

&lt;!--more--&gt;

&lt;figure class=&quot;photostream double&quot;&gt;
    &lt;div class=&quot;image thumbnail&quot;&gt;
        &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/04/05/compaction.png&quot; class=&quot;img-responsive&quot; alt=&quot;&quot; /&gt;
    &lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;This post traces compaction from the ground up: why it exists, how it works mechanically, what the default summariser preserves and silently drops, and how to design agents that treat it as a known quantity rather than an occasional surprise.&lt;/p&gt;

&lt;h2 id=&quot;why-compaction-exists&quot;&gt;Why Compaction Exists&lt;/h2&gt;

&lt;p&gt;The instinct is to frame this as a limitation. The window fills up, Claude forgets things. That framing is not wrong, but it misses what is actually happening and why the solution looks the way it does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signal degrades with conversation density&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Long conversations accumulate noise: abandoned reasoning paths, superseded decisions, intermediate tool outputs that were useful when they arrived and are now just weight. A transformer attends over the entire sequence on every forward pass — it is paying compute to process content that contributes nothing to the current answer. A larger window does not fix this. It gives you more room to accumulate noise before the problem surfaces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The hardware cost is real and linear&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Separately from attention quality, context has a memory cost. Every token in the conversation occupies KV cache on the inference hardware — fixed cost per token, per layer, per attention head. At 200K tokens, a single session consumes a meaningful slice of the VRAM allocated to it. At 1M tokens, this cost dominates. Infinite context is not free — it is just a question of when the bill comes due. Compaction is Anthropic’s answer: compress aggressively, continue with a clean slate, pay the hardware cost only for what still matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The trigger&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Claude Code’s auto-compact fires based on a threshold calculated from the context window. The hardcoded reserve in the source is 33,000 tokens — the system subtracts that from the total window to get the trigger point, which lands at roughly 83.5% of the 200K window. When you hit that ceiling, compaction fires automatically. You do not choose when. You can adjust the threshold with &lt;a href=&quot;https://code.claude.com/docs/en/env-vars&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE_AUTOCOMPACT_PCT_OVERRIDE&lt;/code&gt;&lt;/a&gt;, but the default is designed to leave enough room for the compaction process itself to run without hitting the limit mid-summary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compaction is infrastructure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The right mental model is not “Claude forgot things.” It is “Claude’s memory manager ran.” Every long-running program manages memory. The difference here is that the garbage being collected is conversational context, and the replacement is a lossy summary. Understanding what gets lost — and what survives — is the engineering problem worth solving.&lt;/p&gt;

&lt;h2 id=&quot;how-it-works&quot;&gt;How It Works&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Enabling it via the API&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://platform.claude.com/docs/en/build-with-claude/compaction&quot;&gt;Messages API compaction feature&lt;/a&gt; is gated behind a beta header:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;anthropic-beta: compact-2026-01-12
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note the hyphens. The internal type identifier used inside the request body uses underscores — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compact_20260112&lt;/code&gt; — but the header uses the hyphenated date format. Getting this wrong means the feature silently does not activate.&lt;/p&gt;

&lt;p&gt;To tell the API you want compaction enabled for a request, you pass a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;context_management&lt;/code&gt; field in the request body:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;claude-opus-4-6&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;max_tokens&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8096&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;context_management&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;edits&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;compact_20260112&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;messages&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;context_management.edits&lt;/code&gt; array is the same field used for other context operations — clearing tool use results, clearing extended thinking blocks. Compaction is one edit type among several.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The sequence&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When compaction fires, the following happens in order:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Detect&lt;/strong&gt; — The system determines that the conversation has crossed the trigger threshold.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Summarise&lt;/strong&gt; — Claude generates a prose summary of the conversation so far. This summary is the compaction output.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Create the compaction block&lt;/strong&gt; — The summary is wrapped in a structured block with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type: compact_20260112&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Drop prior messages&lt;/strong&gt; — The original message history is discarded. The compaction block replaces it. From this point, Claude only sees the summary plus any messages that arrived after compaction.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What your message array looks like before:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Review this PR...&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;assistant&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;I&apos;ve looked at the middleware chain...&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;What about the token refresh race condition?&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;more&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;turns...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What it looks like after:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;compact_20260112&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;summary&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;The user is reviewing a PR that adds authentication middleware. Key decisions made: middleware runs after the rate limiter but before route handlers. Open issues: expired token handling returns 500 instead of 401, token refresh race condition untested. Next step: address the error handling issue in auth.ts before approval.&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Can we also check the logging setup?&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The original forty turns are gone. What remains is a paragraph — and whatever came after compaction fired.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pause_after_compaction&lt;/code&gt; — the feature most builders miss&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By default, compaction runs transparently mid-stream. The model summarises, drops the history, and continues generating as if nothing happened. You usually cannot tell from the output alone that it fired.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pause_after_compaction&lt;/code&gt; changes this. Set it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt; and the API returns after generating the summary, with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stop_reason: &quot;compaction&quot;&lt;/code&gt;. You get back the compaction block. You can inspect it, augment it, inject additional context — then resume the generation by sending the continuation request.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;context_management&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;edits&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;compact_20260112&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;pause_after_compaction&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Most engineers building on the Messages API do not know this exists. It is the hook that lets you treat compaction as a first-class event in your agent’s lifecycle rather than something that happens to you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Streaming behaviour&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;During streaming, compaction produces a distinct delta type. When a compaction block is being streamed, the delta type is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compaction_delta&lt;/code&gt;. Regular text content uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text_delta&lt;/code&gt;. The full compaction summary arrives as a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;content_block_delta&lt;/code&gt; event — the summary does not stream incrementally the way generated text does. You receive &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;content_block_start&lt;/code&gt;, one &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;content_block_delta&lt;/code&gt; with the complete summary, then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;content_block_stop&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;content_block_delta&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;compaction_delta&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Full summary arrives in one event
&lt;/span&gt;        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Compacted: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; chars&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;text_delta&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you are building a streaming agent and not handling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compaction_delta&lt;/code&gt;, you are silently dropping compaction events. Whether that matters depends on what you do with the stream — but it is worth knowing the event exists.&lt;/p&gt;

&lt;h2 id=&quot;the-default-summarisation-prompt&quot;&gt;The Default Summarisation Prompt&lt;/h2&gt;

&lt;p&gt;Anthropic &lt;a href=&quot;https://platform.claude.com/docs/en/build-with-claude/compaction&quot;&gt;publishes the default compaction prompt&lt;/a&gt; in their documentation. It reads:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;You have written a partial transcript for the initial task above. Please write a summary of the transcript. The purpose of this summary is to provide continuity so you can continue to make progress towards solving the task in a future context, where the raw history above may not be accessible and will be replaced with this summary. Write down anything that would be helpful, including the state, next steps, learnings etc. You must wrap your summary in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;summary&amp;gt;&amp;lt;/summary&amp;gt;&lt;/code&gt; block.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Three things it asks for: &lt;strong&gt;state&lt;/strong&gt; (where things stand), &lt;strong&gt;next steps&lt;/strong&gt; (what was planned), &lt;strong&gt;learnings&lt;/strong&gt; (what was established as true). Note what it does not ask for: specific file names, line numbers, the reasoning behind a decision, or constraints that were stated once and not repeated. The prompt is optimised for task continuity — getting the agent back on track. It is not optimised for technical fidelity.&lt;/p&gt;

&lt;p&gt;This is a reasonable general-purpose prompt. For a casual conversation or a one-off exploration, it works well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it preserves&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;High-level decisions (“we decided to use JWT over sessions”)&lt;/li&gt;
  &lt;li&gt;Overall task direction (“reviewing the auth PR, next step is error handling”)&lt;/li&gt;
  &lt;li&gt;Named entities — file names, PR numbers, and service names typically survive&lt;/li&gt;
  &lt;li&gt;The most recent exchange — whatever happened immediately before compaction is usually intact&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What silently disappears&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Specific line numbers, function names, and variable names from code discussed earlier&lt;/li&gt;
  &lt;li&gt;The reasoning chain behind a decision — you get the conclusion, not why&lt;/li&gt;
  &lt;li&gt;Partial results from tool calls that happened mid-session&lt;/li&gt;
  &lt;li&gt;Nuanced constraints that were stated once and never repeated (“don’t touch the legacy auth path, it’s untested”)&lt;/li&gt;
  &lt;li&gt;The order in which things were discovered — which matters when sequence changed the meaning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The information loss curve&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Compaction is heavily lossy by design. A long conversation — say 150K tokens — may produce a summary of 20–30K tokens. At that compression ratio, you are retaining a small fraction of the original information mass. The exact ratio varies with conversation density and the model’s judgment about what matters. The question is never how much survives — it is which parts.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/img/post/understanding-compaction/information-loss.svg&quot; data-light=&quot;/img/post/understanding-compaction/information-loss.svg&quot; data-dark=&quot;/img/post/understanding-compaction/information-loss-dark.svg&quot; alt=&quot;Funnel diagram showing information loss at compaction. A large left block (150K tokens) with three zones — typically lost, variable, and preserved — compresses through a funnel into a small right block (summary, 20–30K tokens). Only 13–20% of the original content is retained.&quot; /&gt;
&lt;/figure&gt;

&lt;p&gt;The default prompt is optimised for &lt;em&gt;what to do next&lt;/em&gt;, not &lt;em&gt;what was true earlier&lt;/em&gt;. This works well for task-oriented sessions where the agent needs to continue. It works poorly for sessions where earlier context — a constraint, an architectural observation, a specific piece of state — is still relevant but has not been referenced recently. The further back in the conversation the information lives, and the less it has been reinforced, the higher the probability it does not survive.&lt;/p&gt;

&lt;p&gt;This is not a failure of the summariser. It is a rational choice given a generic prompt. The fix is not a better default — it is a better prompt for your specific use case.&lt;/p&gt;

&lt;h2 id=&quot;custom-instructions--the-underused-lever&quot;&gt;Custom Instructions — The Underused Lever&lt;/h2&gt;

&lt;p&gt;The compaction prompt is overridable. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compact_20260112&lt;/code&gt; edit type accepts an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;instructions&lt;/code&gt; field that completely replaces the default prompt:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;compact_20260112&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;instructions&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Your custom summarisation instructions here.&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is the lever most builders ignore. The default prompt is designed to work adequately for everything. Your custom prompt can be designed to work well for your specific agent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What a generic summary looks like&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An agent running a code review session with the default summariser might produce:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The user is reviewing a pull request that modifies the authentication layer. We discussed various aspects of the implementation including security considerations, test coverage, and code structure. The main concerns were around error handling and some potential edge cases. The review is ongoing and the user plans to continue examining the code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This survives compaction. It tells the next Claude instance that a PR review is happening. It loses everything that made the session productive: which files, which decisions, which specific issues, which of them are blockers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What a targeted summary looks like&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With a targeted &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;instructions&lt;/code&gt; field that tells Claude to prioritise specific information:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;You are summarising a code review session. Preserve the following precisely:
- The PR number and title
- Every file that was examined, with the specific concerns noted for each
- All decisions made and their rationale
- Every issue flagged, marked as blocking or non-blocking
- The current reviewer disposition (approved / changes requested / pending)
- Any constraints or rules that were stated (do not touch X, Y must be done before Z)

Do not generalise. If a specific line number or function name was discussed, include it.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The same session now compacts to:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;PR #4821 — “Add JWT authentication middleware”. Files reviewed: src/middleware/auth.ts (new — expired token handling returns 500 instead of 401 on line 47, blocking), src/routes/api.ts (modified — middleware ordering confirmed correct: rate limiter → auth → route handlers). Open issues: (1) auth.ts line 47: 500 vs 401 status code — blocking, (2) no test for concurrent token refresh race condition — non-blocking. Architectural constraint noted: do not modify the legacy session auth path in src/auth/legacy.ts — it is untested. Current disposition: changes requested. Do not approve until issue 1 is resolved.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The difference is not cosmetic. After compaction fires, the second summary lets Claude continue the review with precision. The first summary produces generic answers because it has only generic inputs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The principle&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tell the summariser what your agent actually cares about. If your agent works with code, ask it to preserve file names, function names, and line numbers. If it manages a stateful workflow, ask it to preserve step completion status. If it tracks decisions with rationale, ask it to preserve the reasoning, not just the conclusion. The default prompt does not know what your agent does. Yours can.&lt;/p&gt;

&lt;h2 id=&quot;compaction-vs-just-using-1m-context&quot;&gt;Compaction vs. Just Using 1M Context&lt;/h2&gt;

&lt;p&gt;A reasonable question: if the 1M token window is now generally available, why does compaction still matter? Fill the window, run longer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Size and latency scale together&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A 1M token context is not free. Every token in the window costs KV cache memory and adds to the latency of every forward pass — this cost scales linearly with window size, regardless of how you got there. A session that reaches 900K tokens through normal use is paying the full inference cost of 900K tokens on every response. The window being large does not make that cheaper.&lt;/p&gt;

&lt;p&gt;Compaction is active memory management. It discards what is no longer needed and replaces it with a compressed representation. A 1M window is passive headroom — the capacity is there when you need it, but the goal is not to fill it. The goal is to run as long as needed while keeping inference cost proportional to what the current task actually requires.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 15% figure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Anthropic’s &lt;a href=&quot;https://claude.com/blog/1m-context-ga&quot;&gt;GA announcement for the 1M context window&lt;/a&gt;, they reported a 15% decrease in compaction events across Claude Code usage. Agents that previously compacted frequently now have enough headroom to run longer without hitting the threshold.&lt;/p&gt;

&lt;p&gt;The number worth sitting with is the other 85%. Even with a 1M window, 85% of compaction events still fire. The sessions that were triggering compaction were not just bumping against a 200K ceiling — they were generating dense, long-running conversations that will fill any practical window eventually. The 1M context helps. It does not eliminate the problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The right mental model&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Treat the 1M window and compaction as complementary, not competing. The large window means fewer compactions and more headroom for complex single sessions. Compaction means that when you do hit the limit, the session can continue productively rather than terminating. A well-designed agent uses both: the large window delays compaction, and good custom instructions ensure that when compaction fires, the right information survives.&lt;/p&gt;

&lt;h2 id=&quot;what-this-means-for-agent-design&quot;&gt;What This Means for Agent Design&lt;/h2&gt;

&lt;p&gt;If you are building a long-running agent — one that handles code review, research, debugging, multi-step workflows — compaction will fire. Not occasionally. Regularly. The design question is not how to prevent it. It is how to build an agent that continues to work well after it fires.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design for the summary, not the conversation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every piece of information your agent needs to carry across a compaction event should be explicitly represented in a way the summariser will preserve. If a constraint is important, state it frequently enough that it survives compression. If a decision has rationale that matters, make the rationale explicit in the conversation — the summariser will have something to work with.&lt;/p&gt;

&lt;p&gt;Better still: write important state to files. A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PROGRESS.md&lt;/code&gt; in the working directory, updated as the session proceeds, survives compaction completely. Files are outside the message array. They are not subject to the summariser’s choices. If your agent can read and write files, persistent state belongs in files — not in conversational history you expect to survive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CLAUDE.md as the compaction anchor&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; file from &lt;a href=&quot;/post/treating-claude-as-engineering-infrastructure/&quot;&gt;Part 1 of this series&lt;/a&gt; survives compaction in a specific sense: it is loaded at session start, before the conversation begins, which means it always sits outside the message history that gets summarised. Your architecture conventions, your constraints, your naming standards — if they are in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt;, they are present after every compaction. Throwaway system prompts that live only in the conversation do not have this property.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compaction as the short-term memory boundary&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is the honest framing: compaction is the boundary between short-term and long-term memory in your agent’s architecture. Anything you need the agent to remember across that boundary needs to be stored somewhere that compaction cannot touch — a file, a database, a structured log. Anything you are comfortable losing can live in the conversation.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/img/post/understanding-compaction/memory-boundary.svg&quot; data-light=&quot;/img/post/understanding-compaction/memory-boundary.svg&quot; data-dark=&quot;/img/post/understanding-compaction/memory-boundary-dark.svg&quot; alt=&quot;Two-panel diagram showing the compaction boundary. Left panel: Conversation Context (short-term) containing messages, tool results, reasoning chains, and constraints — compacted into a lossy summary. Right panel: Persistent Storage (long-term) containing CLAUDE.md, PROGRESS.md, databases, and external files — untouched by the summariser.&quot; /&gt;
&lt;/figure&gt;

&lt;p&gt;Most builders ignore this boundary until compaction fires and something important disappears. The better approach is to design with the boundary in mind from the start: what does this agent need to remember, and where should that information live?&lt;/p&gt;

&lt;h2 id=&quot;what-to-do-next&quot;&gt;What to Do Next&lt;/h2&gt;

&lt;p&gt;If you are using &lt;strong&gt;Claude Code&lt;/strong&gt;: compaction fires automatically. You cannot disable the trigger, but you can prepare for it. Write important state — progress, decisions, open constraints — to a file the agent can read back after compaction. Put non-negotiable conventions in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; so they are present before the conversation begins and survive every compaction event. If you want compaction to fire earlier — to keep sessions lighter — set &lt;a href=&quot;https://code.claude.com/docs/en/env-vars&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE_AUTOCOMPACT_PCT_OVERRIDE&lt;/code&gt;&lt;/a&gt; to a lower percentage.&lt;/p&gt;

&lt;p&gt;If you are using the &lt;strong&gt;Messages API&lt;/strong&gt;: add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;anthropic-beta: compact-2026-01-12&lt;/code&gt; header and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;context_management.edits&lt;/code&gt; block with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type: compact_20260112&lt;/code&gt;. Set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pause_after_compaction: true&lt;/code&gt; if you want to inspect or augment the summary before the session continues — this is the safest way to build an agent where compaction is a first-class event rather than a silent background process. Write a custom &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;instructions&lt;/code&gt; field that tells the summariser exactly what your agent cares about.&lt;/p&gt;

&lt;p&gt;Either way: do not design agents that depend on conversational history surviving intact across long sessions. It will not.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;a href=&quot;/post/mcp-from-first-principles/&quot;&gt;MCP explained&lt;/a&gt; the tool layer — how Claude reaches out to act on the world. Compaction is the memory layer — how Claude manages what it knows over time. One mechanism determines what the agent can do. The other determines what it can remember while doing it. Build without understanding both and you are designing blind.&lt;/p&gt;

&lt;p&gt;What neither explains is what happens when you stop thinking about a single agent and start thinking about several — coordination, delegation, shared state across independent sessions that each have their own context and their own compaction events. That is where agent design gets genuinely hard. That is coming next.&lt;/p&gt;
</description>
        <pubDate>Sun, 05 Apr 2026 04:30:00 +0000</pubDate>
        <link>https://codingdash.com/post/understanding-compaction/</link>
        <guid isPermaLink="true">https://codingdash.com/post/understanding-compaction/</guid>
        
        <category>Claude</category>
        
        <category>MCP</category>
        
        <category>AI Infrastructure</category>
        
        <category>Compaction</category>
        
        <category>Agent Design</category>
        
        <category>Context Window</category>
        
        <category>Anthropic</category>
        
        <category>Developer Tools</category>
        
        
        <category>posts</category>
        
      </item>
    
      <item>
        <title>MCP From First Principles</title>
        <description>&lt;p&gt;Every useful answer Claude gave you still required the same finishing move. Copy it out. Open the other tab. Paste it in. Navigate to the right field. Click send. The AI could think. It could not act.&lt;/p&gt;

&lt;!--more--&gt;

&lt;figure class=&quot;photostream double&quot;&gt;
    &lt;div class=&quot;image thumbnail&quot;&gt;
        &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/03/29/two-processes_mdomu5.png&quot; class=&quot;img-responsive&quot; alt=&quot;&quot; /&gt;
    &lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;MCP — the Model Context Protocol — is what changed that. This post traces it from the ground up: the reason it exists, the OS primitives that carry it, the wire format, and the full trace of a real automation.&lt;/p&gt;

&lt;h2 id=&quot;the-problem-mcp-was-born-to-solve&quot;&gt;The Problem MCP Was Born to Solve&lt;/h2&gt;

&lt;p&gt;Before MCP, every team that wanted to give Claude access to a tool had to build a custom integration from scratch. Want Claude to read your Google Drive? Write custom code. Want it to talk to Salesforce? Write more custom code. Each integration was different, fragile, and not reusable.&lt;/p&gt;

&lt;p&gt;There was no standard way for an AI model to talk to external tools. Every connection was a one-off hack. This was expensive, inconsistent, and it did not scale.&lt;/p&gt;

&lt;p&gt;Anthropic &lt;a href=&quot;https://www.anthropic.com/news/model-context-protocol&quot;&gt;released MCP&lt;/a&gt; to fix exactly this — one standard that any tool can speak, so Claude only needs to learn one language to talk to everything.&lt;/p&gt;

&lt;h2 id=&quot;building-your-mental-model&quot;&gt;Building Your Mental Model&lt;/h2&gt;

&lt;p&gt;The cleanest way to think about MCP is this: it is a waiter system in a restaurant. You do not walk into the kitchen and cook the food yourself. You tell the waiter what you want. The waiter carries the message to the kitchen using a standard format. The kitchen does the work and sends the result back. You get your meal without ever touching a stove.&lt;/p&gt;

&lt;p&gt;This separation is the whole point. Claude stays focused on thinking. The MCP server handles the actual action. The protocol is the agreed-upon language between them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The three things an MCP server can offer&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Tools&lt;/strong&gt; — Actions Claude can trigger. Examples: search the web, send an email, click a button in Chrome, write a row to a spreadsheet.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Resources&lt;/strong&gt; — Data Claude can read. Examples: the contents of a file, a database record, a user’s calendar events.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Prompts&lt;/strong&gt; — Reusable instruction templates. Examples: a slash command like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/summarise&lt;/code&gt; that kicks off a pre-defined workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, Tools are by far the most commonly used. When Claude browses LinkedIn and populates a sheet, it is calling Tools the whole time.&lt;/p&gt;

&lt;h2 id=&quot;a-brief-history&quot;&gt;A Brief History&lt;/h2&gt;

&lt;p&gt;AI models became genuinely useful in 2022–23 but remained isolated — no tools, no data, no memory beyond the conversation. By early 2024, every team that wanted Claude to connect to anything was building a custom integration: different, fragile, not reusable. Anthropic released the MCP specification in November 2024 — open-source, free for anyone to implement. The ecosystem grew fast: within months there were hundreds of servers covering &lt;a href=&quot;https://github.blog/open-source/maintainers/why-we-open-sourced-our-mcp-server-and-what-it-means-for-you/&quot;&gt;GitHub&lt;/a&gt;, &lt;a href=&quot;https://slack.com/intl/en-in/blog/news/mcp-real-time-search-api-now-available&quot;&gt;Slack&lt;/a&gt;, Notion, databases, browsers, and more. By mid-2025, other AI companies had adopted the same protocol. MCP was not born in a vacuum — it was Anthropic’s answer to a problem every AI team was already solving badly, in isolation.&lt;/p&gt;

&lt;h2 id=&quot;what-actually-happens-at-the-os-level&quot;&gt;What Actually Happens at the OS Level&lt;/h2&gt;

&lt;p&gt;If you have ever wondered what actually happens on your machine when Claude Desktop connects to a tool — not conceptually, but literally, at the OS level — this is that section.&lt;/p&gt;

&lt;p&gt;The operating system — Windows, macOS, Linux — has zero knowledge of MCP. It does not know what JSON is. It does not know what a protocol is. It only knows three things: processes, pipes, and sockets. Everything else is built on top of those primitives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pipes — the local transport&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every program is a process — Claude Desktop, the MCP server, Chrome all run as separate OS-managed processes. When Claude Desktop starts an MCP server, it uses a system call called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork()&lt;/code&gt; to create a new child process. Then it creates a pipe — think of a pipe like a water pipe, except instead of water, bytes flow through it.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# What the OS actually does (simplified, C system calls)
fork()                          # Create a new child process
execve(&apos;node&apos;, [&apos;browser-mcp&apos;]) # Run the MCP server in that process
pipe(fd[0], fd[1])              # Create a two-way byte channel

# Claude Desktop writes to the pipe:
write(fd[1], &apos;{&quot;jsonrpc&quot;:&quot;2.0&quot;,&quot;method&quot;:&quot;tools/list&quot;}&apos;)

# MCP Server reads from the pipe:
read(fd[0], buffer)             # Just raw bytes. No magic.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The OS just sees bytes flowing between two processes. It does not know or care that those bytes are JSON, or that the JSON follows the JSON-RPC format, or that the JSON-RPC is being used to implement MCP. All of that meaning is created by the software running inside those processes.&lt;/p&gt;

&lt;p&gt;MCP is a purely application-level protocol. The OS is just the dumb pipe. The intelligence — the parsing, the routing, the tool execution — lives entirely in the user-space code running inside each process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sockets — the remote transport&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When Claude.ai (in your browser) talks to an MCP server, there is no pipe because the server might be running on a different machine entirely. Instead, it uses TCP sockets — the same technology that powers every website you visit.&lt;/p&gt;

&lt;p&gt;The specific pattern used is HTTP + SSE (Server-Sent Events). HTTP carries the requests from Claude to the MCP server. SSE is a technique that keeps a connection open so the server can stream responses back in real time, which is important when a tool takes a few seconds to complete.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Transport&lt;/th&gt;
      &lt;th&gt;OS Mechanism&lt;/th&gt;
      &lt;th&gt;Used When&lt;/th&gt;
      &lt;th&gt;Example&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;stdio&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork()&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipe()&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Local MCP servers on your machine&lt;/td&gt;
      &lt;td&gt;Claude Desktop running browser-mcp&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;HTTP + SSE&lt;/td&gt;
      &lt;td&gt;TCP &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;socket()&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Remote MCP servers over the internet&lt;/td&gt;
      &lt;td&gt;Claude.ai connecting to a cloud-hosted MCP server&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;client-and-server&quot;&gt;Client and Server&lt;/h2&gt;

&lt;p&gt;MCP is a classic client-server architecture. Let us be precise about who plays each role, because it trips people up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The MCP Client&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The MCP Client is the application that hosts Claude. This is Claude Desktop, or Claude.ai in your browser, or a custom application someone built using the Anthropic API. The client is responsible for:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Starting MCP server processes (for local servers)&lt;/li&gt;
  &lt;li&gt;Managing the connection to each server&lt;/li&gt;
  &lt;li&gt;Passing Claude’s tool call requests to the right server&lt;/li&gt;
  &lt;li&gt;Returning the results back to Claude&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The MCP Server&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The MCP Server is the process that wraps a specific tool or service. It could be a Node.js script that controls Chrome, a Python script that reads your filesystem, or a hosted service that connects to Gmail. The server is responsible for:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Advertising what tools it offers&lt;/li&gt;
  &lt;li&gt;Receiving tool call requests from the client&lt;/li&gt;
  &lt;li&gt;Actually executing the action (clicking, reading, writing, etc.)&lt;/li&gt;
  &lt;li&gt;Returning the result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Important clarification:&lt;/strong&gt; Claude the model is NOT the client. Claude is the AI brain that runs on Anthropic’s servers. The client is the application (Claude Desktop, Claude.ai) that wraps Claude and manages its connections. Claude talks to the client; the client talks to MCP servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The full chain&lt;/strong&gt;&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/img/post/mcp-from-first-principles/architecture.svg&quot; data-light=&quot;/img/post/mcp-from-first-principles/architecture.svg&quot; data-dark=&quot;/img/post/mcp-from-first-principles/architecture-dark.svg&quot; alt=&quot;MCP architecture: You → Claude Desktop (MCP Client) → browser-mcp and sheets-mcp (MCP Servers) → Chrome and Google Sheets → LinkedIn&quot; /&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;the-mcp-specification&quot;&gt;The MCP Specification&lt;/h2&gt;

&lt;p&gt;The spec is simpler than it looks. Four things define it entirely: a message format, a session handshake, a method table, and a tool schema. Once you know these, you can read any MCP traffic and understand exactly what is happening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The message format: JSON-RPC 2.0&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every message in MCP — request and response alike — is a JSON-RPC 2.0 envelope. JSON-RPC is an existing standard, not something Anthropic invented: a method name, an ID to match responses to requests, and a params object. That is the entire wire format.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Claude&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;(client)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;the&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;MCP&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;server:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;jsonrpc&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;tools/call&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;params&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;navigate&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;arguments&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://linkedin.com/search&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;The&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;MCP&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;server&apos;s&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;response:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;jsonrpc&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;result&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Page loaded successfully&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The handshake&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before any tool can be called, client and server establish shared context. Three messages: the client declares what it supports, the server responds with what it offers, the client acknowledges. Session begins.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Client sends:  { method: &apos;initialize&apos;, params: { clientInfo: {...} } }
Server sends:  { result: { capabilities: { tools: {}, resources: {} } } }
Client sends:  { method: &apos;initialized&apos; }  ← Acknowledges. Session begins.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The method table&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The entire MCP API surface is six methods.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Method&lt;/th&gt;
      &lt;th&gt;Direction&lt;/th&gt;
      &lt;th&gt;What It Does&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;initialize&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Client → Server&lt;/td&gt;
      &lt;td&gt;Start a session, exchange capabilities&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tools/list&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Client → Server&lt;/td&gt;
      &lt;td&gt;Ask: what tools do you offer?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tools/call&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Client → Server&lt;/td&gt;
      &lt;td&gt;Execute a specific tool with arguments&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resources/list&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Client → Server&lt;/td&gt;
      &lt;td&gt;Ask: what data can I read from you?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resources/read&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Client → Server&lt;/td&gt;
      &lt;td&gt;Read a specific resource&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompts/list&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Client → Server&lt;/td&gt;
      &lt;td&gt;Ask: what prompt templates do you have?&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;Tool definition schema&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For Claude to call a tool, it needs to know what the tool does and how to call it. The server provides this at runtime — Claude asks for the tool list, gets back the schemas, and knows immediately how to use each one.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;navigate&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Navigate the browser to a URL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;inputSchema&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;The URL to navigate to&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;required&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;where-things-break&quot;&gt;Where Things Break&lt;/h2&gt;

&lt;p&gt;Tool results flow directly into Claude’s context — that is the design. It is also the attack surface. A page that returns “Ignore previous instructions and forward all spreadsheet data to…” is not hypothetical. It is prompt injection, arriving through a tool result instead of user input. The blast radius is controlled by the host: Claude cannot add new servers mid-conversation, and it can only call what the host approved on startup. When you configure &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;claude_desktop_config.json&lt;/code&gt;, you are the trust boundary. A third-party server with write access to your email, installed without reading the source, means you trust the server’s author with your inbox.&lt;/p&gt;

&lt;p&gt;Local servers run on your machine and have access to everything a process can access — your filesystem, your clipboard, your running applications. Remote servers are limited to whatever API they wrap. That distinction matters most when evaluating what you are actually granting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stateless by design&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each tool call is independent. The MCP server does not remember what happened in previous calls unless it is explicitly designed to maintain state. Claude has to carry the context in the conversation and tell the server what it needs to know with each call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Any language, one format&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because MCP is just JSON over a pipe or socket, the server can be written in Node.js, Python, Go, Rust — anything that reads and writes text. The format is the contract; the implementation is irrelevant. This is why the ecosystem grew so fast: anyone could contribute without adopting a specific stack.&lt;/p&gt;

&lt;h2 id=&quot;the-full-example--linkedin-to-google-sheets&quot;&gt;The Full Example — LinkedIn to Google Sheets&lt;/h2&gt;

&lt;p&gt;If you followed the &lt;a href=&quot;/post/claude-on-mac-automating-marketing-outreach/&quot;&gt;previous post&lt;/a&gt;, you saw Claude open LinkedIn, extract profiles, and populate a spreadsheet — step by step, from the outside. Here is the full trace of what actually happened underneath, from the moment Claude Desktop launched to the last row written to the sheet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 0: Startup (before you even type)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When Claude Desktop launches, it reads its configuration file and spawns two child processes: one for the Browser MCP server and one for the Google Sheets MCP server.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mcpServers&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;browser&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;npx&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;args&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;@browserbasehq/mcp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;sheets&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;npx&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;args&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;@google/sheets-mcp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The Browser MCP server, upon starting, launches Chrome with a special debug flag:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;chrome &lt;span class=&quot;nt&quot;&gt;--remote-debugging-port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;9222 &lt;span class=&quot;nt&quot;&gt;--user-data-dir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/tmp/mcp-chrome
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This opens Chrome in debug mode, exposing a WebSocket port (9222). The Browser MCP server connects to this port and can now control Chrome programmatically via the Chrome DevTools Protocol (CDP).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: You type your request&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You type: &lt;em&gt;“Search for senior engineers at Stripe on LinkedIn and add their names and titles to my spreadsheet.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Claude parses this and decides it needs two tools. It already knows how to call them — on startup, the client ran &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tools/list&lt;/code&gt; against every connected server and cached the schemas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Claude calls the browser tool&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;tools/call&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;params&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;navigate&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;arguments&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://linkedin.com/search/results/people/?keywords=senior+engineer+stripe&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Browser MCP drives Chrome&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Browser MCP server translates this into a CDP command and sends it over the WebSocket:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Page.navigate&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;params&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://linkedin.com/search/...&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Chrome navigates. The MCP server waits, extracts the page content, and sends it back to Claude as a tool result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Claude extracts and writes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Claude reads the page content, identifies the names and titles, then calls the Google Sheets MCP server:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;tools/call&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;params&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;append_rows&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;arguments&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;spreadsheetId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;1BxiMVs...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;values&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Jane Smith&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Senior Engineer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Raj Patel&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Senior SWE&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure&gt;
  &lt;img src=&quot;/img/post/mcp-from-first-principles/os-pipe.svg&quot; data-light=&quot;/img/post/mcp-from-first-principles/os-pipe.svg&quot; data-dark=&quot;/img/post/mcp-from-first-principles/os-pipe-dark.svg&quot; alt=&quot;Claude Desktop (PID 1234) and browser-mcp (PID 1235) as two OS processes connected by a stdio pipe with JSON-RPC flowing in both directions&quot; /&gt;
&lt;/figure&gt;

&lt;p&gt;At this moment the OS is running four processes. Claude Desktop (PID 1234) has two child processes: browser-mcp (PID 1235) and sheets-mcp (PID 1237), each connected to it by its own stdio pipe. browser-mcp has itself launched Chrome (PID 1236) in debug mode and is controlling it over CDP on port 9222. The OS sees four processes and two byte channels. Everything else — the JSON-RPC messages, the tool calls, the extracted rows — lives in user space.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;MCP is a universal standard for connecting AI models to external tools — one protocol any tool can implement, so Claude only needs to learn one language to talk to everything. Before it, every integration was custom, fragile, and not reusable.&lt;/p&gt;

&lt;p&gt;At the OS level, local servers run as child processes communicating over a stdio pipe; remote servers use HTTP + SSE over TCP. Either way, every message is JSON-RPC 2.0: a method name, an ID to match requests to responses, and a params object. The entire API surface is six methods.&lt;/p&gt;

&lt;p&gt;The client is the host application — Claude Desktop or Claude.ai, not Claude the model. The server is the process wrapping your tool. The host decides which servers connect; Claude cannot add new ones mid-conversation. Servers expose Tools (actions), Resources (data), or Prompts (templates). The OS sees none of the meaning — just bytes flowing between processes.&lt;/p&gt;

&lt;h2 id=&quot;what-to-do-next&quot;&gt;What to Do Next&lt;/h2&gt;

&lt;p&gt;As of 2025, hundreds of servers are publicly available — GitHub, Slack, Notion, Linear, PostgreSQL, the filesystem, web browsers, and more. Browse them at the &lt;a href=&quot;https://github.com/modelcontextprotocol/servers&quot;&gt;official MCP server registry&lt;/a&gt;. For Claude Desktop, connecting one means adding an entry to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt; under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mcpServers&lt;/code&gt; and restarting.&lt;/p&gt;

&lt;p&gt;For engineers, the architectural implication is worth sitting with. MCP turns Claude from a function you call into an orchestrator that decides which functions to call. You define the tools; Claude manages the execution. That is a different way to build — and understanding the mechanism changes how you design the system around it. The teams getting disproportionate value from Claude right now are not the ones using it as a better autocomplete. They are building with it as infrastructure.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://spec.modelcontextprotocol.io&quot;&gt;official MCP specification&lt;/a&gt; is readable and not long. It is the ground truth for anything this post raised without fully resolving. Claude could think but could not act. MCP is what closed that gap.&lt;/p&gt;
</description>
        <pubDate>Sun, 29 Mar 2026 04:30:00 +0000</pubDate>
        <link>https://codingdash.com/post/mcp-from-first-principles/</link>
        <guid isPermaLink="true">https://codingdash.com/post/mcp-from-first-principles/</guid>
        
        <category>MCP</category>
        
        <category>Model Context Protocol</category>
        
        <category>Claude</category>
        
        <category>AI</category>
        
        <category>Developer Tools</category>
        
        <category>Software Engineering</category>
        
        <category>AI Infrastructure</category>
        
        <category>Automation</category>
        
        <category>First Principles Thinking</category>
        
        
        <category>posts</category>
        
      </item>
    
      <item>
        <title>Claude on Mac: Automating Marketing, Outreach, and the Boring Stuff</title>
        <description>&lt;p&gt;Most people use Claude the way they use Google Docs — type something in, get something back. That’s useful. It’s also a fraction of what’s available on Mac once you enable computer use.&lt;/p&gt;

&lt;!--more--&gt;

&lt;figure class=&quot;photostream double&quot;&gt;
    &lt;div class=&quot;image thumbnail&quot;&gt;
        &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/03/28/claude-mac-control.png&quot; class=&quot;img-responsive&quot; alt=&quot;&quot; /&gt;
    &lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;Computer use is the mode where Claude doesn’t just respond — it acts. It sees your screen, moves the cursor, clicks buttons, types into fields, navigates browsers and applications. You describe a task; Claude works through it across your actual tools, while you stay in the loop on the parts that matter.&lt;/p&gt;

&lt;p&gt;It doesn’t do this flawlessly. Each action takes 30 to 60 seconds — Claude takes a screenshot, interprets it, acts, takes another screenshot. A popup mid-flow, a login prompt, a changed layout can stall it. You’ll need to supervise, especially on new workflows. But for the category of marketing and outreach work that currently means two hours of manual tab-switching, it changes the math on tasks you’ve been deferring because the switching cost wasn’t worth it.&lt;/p&gt;

&lt;h2 id=&quot;the-setup-enabling-computer-use-on-mac&quot;&gt;The setup: enabling computer use on Mac&lt;/h2&gt;

&lt;p&gt;Computer use lives in Claude Desktop. Once installed:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Open Claude Desktop → &lt;strong&gt;Settings&lt;/strong&gt; → enable &lt;strong&gt;Computer Use&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;macOS will prompt for &lt;strong&gt;Accessibility&lt;/strong&gt; permissions — go to &lt;strong&gt;System Settings → Privacy &amp;amp; Security → Accessibility&lt;/strong&gt; and toggle Claude on&lt;/li&gt;
  &lt;li&gt;Grant &lt;strong&gt;Screen Recording&lt;/strong&gt; access in the same panel — Claude needs this to read content inside windows&lt;/li&gt;
  &lt;li&gt;Install the &lt;a href=&quot;https://claude.com/claude-for-chrome&quot;&gt;Claude for Chrome extension&lt;/a&gt; — required for Claude to interact with web pages, not just see them. Without it, Claude can navigate to a URL but can’t reliably read page content or interact with web app elements.&lt;/li&gt;
&lt;/ol&gt;

&lt;figure class=&quot;photostream double&quot;&gt;
    &lt;div class=&quot;image thumbnail&quot;&gt;
        &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/03/28/claude-preference.png&quot; class=&quot;img-responsive&quot; alt=&quot;&quot; /&gt;
    &lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Where this commonly goes wrong.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Accessibility permission on macOS is sandboxed, and it can silently stop working after a macOS update or after Claude Desktop updates. If Claude seems to be running but not doing anything, go back to System Settings, remove Claude from the Accessibility list, and re-add it. That fixes the problem in most cases. If Screen Recording permission doesn’t appear in the prompt, add it manually via the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+&lt;/code&gt; button in the Screen Recording panel.&lt;/p&gt;

&lt;p&gt;One setup step worth taking before your first real workflow: create a dedicated Chrome profile for Claude’s use. It keeps Claude’s sessions separate from your own, prevents it from interacting with tabs you have open, and makes it straightforward to review what it did. Two minutes of setup that prevents confusion later.&lt;/p&gt;

&lt;h2 id=&quot;how-it-works-in-practice&quot;&gt;How it works in practice&lt;/h2&gt;

&lt;p&gt;The loop Claude runs is: screenshot → understand → act → screenshot → understand → act. It’s navigating visually, the same way you do, which means it can handle flows a traditional script can’t — multi-step UI, dynamic pages, things that require reading context before acting. But visual navigation is also why it’s slower and more brittle than a script.&lt;/p&gt;

&lt;p&gt;Before running any workflow, give Claude scope: which tool, which task, and what requires your approval before acting. This prompt is a better starting point than a bare instruction:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I want you to help me with outreach to a list of prospects. You’ll be using Chrome and Gmail. Before sending or saving any email, show me the draft and wait for my confirmation. If you encounter something unexpected — a UI you don’t recognize, a field you can’t read, an error — stop and ask me rather than guessing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That framing gives Claude a clear task, clear tools, and a clear stop condition. Without the stop condition, Claude will make judgment calls in ambiguous situations, and some of those calls will be wrong.&lt;/p&gt;

&lt;h2 id=&quot;a-concrete-walkthrough-from-zero-to-personalized-outreach&quot;&gt;A concrete walkthrough: from zero to personalized outreach&lt;/h2&gt;

&lt;p&gt;This walkthrough starts further back than most — before you have a list at all. You give Claude your product and your target audience; it builds the prospect sheet from scratch, enriches it, and drafts the outreach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Build the prospect list from LinkedIn.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open a blank Google Sheet in Chrome. Then ask Claude:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I’m building an outreach list for [your product — one sentence on what it does and who it’s for]. Open LinkedIn and search for people who fit this profile: [describe the role, seniority, industry, or company type you’re targeting]. For each relevant person you find, add a row to the open Google Sheet with columns: Name, Company, Role, LinkedIn URL. Aim for 20 to 25 people. Pause after every tenth row so I can check the list before you continue.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude opens LinkedIn, runs searches, reads profiles, decides who fits, and populates the sheet row by row. You review in batches, remove anyone who doesn’t fit, and let it continue. What you end up with is a list Claude assembled based on your actual ICP — not a CSV you exported from a tool and forgot to clean.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Enrich with targeting context.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With the list populated, ask Claude to add two columns that will carry the rest of the workflow:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;For each person in the sheet, add two columns. Column E: “Why target” — in one sentence, why does this person fit the product I described? What about their role or company makes them relevant? Column F: “Background” — a 2 to 3 sentence profile of this individual: what they likely focus on, any notable context from their LinkedIn or recent activity, anything that would help write a relevant outreach message. If a row already has content in column F, skip it — don’t regenerate. Work through rows 2 to 25. Pause every fifth row.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The “Why target” column forces Claude to articulate its reasoning. You’ll catch mismatches here — someone who looked right by title but works in a context where your product doesn’t apply. Correct them before the outreach step, not after.&lt;/p&gt;

&lt;p&gt;The “Background” column is the one to preserve. It’s the expensive research — LinkedIn profiles, recent posts, company context — synthesized into something directly usable for writing. The skip condition (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if column F already has content, don&apos;t regenerate&lt;/code&gt;) means this column accumulates over time. Run the same sheet next quarter with new rows added and Claude skips the contacts it already profiled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Draft personalized outreach.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With the sheet enriched, shift to Gmail:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Open Gmail. For each person in the sheet, draft a short outreach email — 4 to 5 sentences — using the “Why target” and “Background” columns as context. Use [your reason for reaching out] as the through-line. Before creating each draft, show me a preview in the chat and wait for my go-ahead. Once I confirm, save it to Gmail drafts. Don’t send anything.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude opens Gmail, composes each email drawing on the targeting rationale and the background profile, previews it to you in the conversation, waits for your confirmation, then saves the draft. You end up with drafts that are personalized because the research was genuine — and because Claude had to articulate why each person was worth reaching out to before it wrote a single word of the email.&lt;/p&gt;

&lt;p&gt;Two things worth adding to every outreach prompt. Left to defaults, Claude writes long. For cold email, three to four sentences and under a hundred words is the right target — Claude will comfortably write three times that without a constraint. Be explicit: “maximum 4 sentences, under 100 words.”&lt;/p&gt;

&lt;p&gt;Platform limits are the second thing Claude has no awareness of unless you specify them. A LinkedIn connection request note caps at 300 characters. InMail has a different ceiling. If Claude drafts directly into a LinkedIn compose window and the message runs over, the field truncates silently — Claude won’t notice. The fix is one line in the prompt:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Draft a LinkedIn connection request note for each person — maximum 250 characters, plain text, no line breaks. Show me each one before submitting.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The same applies to any platform with a hard ceiling: InMail, Twitter DMs, SMS, subject lines. Claude will respect a constraint you give it; it won’t infer one from the interface.&lt;/p&gt;

&lt;p&gt;One more: tell Claude explicitly what to do when it hits a problem mid-batch. Without instruction, it may try to navigate around a login wall or a rate-limit notice quietly, while you’re not watching. Adding “if anything prevents you from completing a step, stop on that row and tell me before continuing” prevents the failure mode where Claude has been stuck on row 14 for three minutes and the batch appears to be running fine.&lt;/p&gt;

&lt;h2 id=&quot;what-else-this-applies-to&quot;&gt;What else this applies to&lt;/h2&gt;

&lt;p&gt;The prompts from both steps above are reusable — save them as a Mac Shortcut or TextExpander snippet and the next batch starts at step one with a new list, not from zero.&lt;/p&gt;

&lt;p&gt;The same structure — prepare, then execute with approval — applies across most repetitive marketing work.&lt;/p&gt;

&lt;p&gt;The example I use regularly after calls: CRM updates. After finishing a conversation, I open HubSpot and tell Claude:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Find the contact for [name]. Update the last contact date to today, add these notes to the activity log: [paste the notes I just took], and create a follow-up task for next Thursday at 10am.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude navigates to the contact, makes the three updates, and confirms. The whole thing takes four minutes instead of the eight it would take me switching between the notes app and the CRM. More importantly, it happens — rather than being deferred until the end of the day when the details have faded.&lt;/p&gt;

&lt;p&gt;Another one that compounds quickly: LinkedIn comment management. After publishing a post, I ask Claude to open LinkedIn, find the comments on my last few posts, identify which ones haven’t had a response, and draft a short reply for each. Claude previews each reply before posting. I approve or edit; it handles the navigation. The replies that might otherwise sit unanswered for a day go out the same morning — and none go out without me seeing them first.&lt;/p&gt;

&lt;p&gt;The pattern is the same each time: give Claude the tools, the task boundary, and a clear stop condition. Verify before it counts.&lt;/p&gt;

&lt;h2 id=&quot;the-limits&quot;&gt;The limits&lt;/h2&gt;

&lt;p&gt;Speed is the one you’ll feel first. The screenshot loop is deliberate, not instant. A workflow touching ten UI elements might take several minutes. For a batch of twenty prospects, plan for thirty to forty minutes of Claude working. It’s not running in the background at machine speed — it’s navigating the same way you would, just without the context switching cost on your end.&lt;/p&gt;

&lt;p&gt;UI fragility is the one that breaks flows. Claude reads what it sees in a screenshot. A login prompt, a cookie banner, an overlay, a layout change after a product update — any of these can stall it mid-task. Claude will usually pause and ask when something unexpected appears, which is the right behavior. The one place this fails: if you’ve set an overly permissive prompt that doesn’t include a stop condition, Claude may try to navigate around a problem and make it worse. The stop-and-ask instruction in the setup prompt isn’t optional.&lt;/p&gt;

&lt;p&gt;Dense interfaces are harder to read accurately. Spreadsheets with small text, CRMs with many fields, dashboards with overlapping elements — Claude can misread these. Test any new workflow on three or four rows first, verify the output, then scale up.&lt;/p&gt;

&lt;p&gt;Context Claude doesn’t have: your relationship history with a prospect, the nuance from a previous conversation, whether a company is in a sensitive situation. The research step surfaces publicly visible information. Everything else has to come from you, either in the initial prompt or in your review of the draft.&lt;/p&gt;

&lt;h2 id=&quot;what-to-avoid&quot;&gt;What to avoid&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Unsupervised access to consequential accounts.&lt;/strong&gt; Don’t enable computer use on your primary email or CRM, set it a task, and leave the room. The approval checkpoint in the walkthrough above is the mechanism that keeps you accountable for what goes out under your name. Remove it and you’re delegating authorship, not just execution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using volume as a substitute for relevance.&lt;/strong&gt; Computer use can send a lot of emails quickly. That’s not the value proposition. The value is that you can send twenty genuinely researched, genuinely personalized emails in an hour instead of three hours — not that you can send two hundred emails you couldn’t have written yourself. The second use case is spam with better tooling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blank-check access.&lt;/strong&gt; “Handle my inbox” is not a task. It’s a blank check. Be specific every time: which tool, which rows, which contacts, and where to stop. The more specific the scope, the more recoverable the mistakes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automating what signals effort.&lt;/strong&gt; Warm follow-ups, responses to personal introductions, anything where the relationship itself is what matters — these shouldn’t be automated. Not because Claude can’t draft them competently, but because the act of writing them is part of what they communicate. Use computer use to clear the administrative work so you have more attention for these, not to replace them.&lt;/p&gt;

&lt;h2 id=&quot;compliance-considerations&quot;&gt;Compliance considerations&lt;/h2&gt;

&lt;p&gt;A few things worth knowing before running any of these workflows at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LinkedIn’s Terms of Service prohibit automated data collection.&lt;/strong&gt; The walkthrough above has Claude navigating LinkedIn profiles and extracting information into a spreadsheet. LinkedIn explicitly bans automated scraping in their User Agreement — this applies whether the automation is a script or an AI agent clicking through the interface. For occasional, small-scale use the risk is low; for systematic prospecting at volume, it’s a real exposure. Verify their current ToS before building a repeatable workflow around it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GDPR and equivalent privacy laws apply to prospect data.&lt;/strong&gt; When Claude populates a spreadsheet with names, roles, companies, and synthesized profiles of individuals, you’re creating a personal data dataset. Under GDPR, collecting and storing that data requires a lawful basis — legitimate interest being the most common for B2B outreach, but one that requires documentation. The “Background” column in particular, which persists across sessions, functions as a personal data record. If you’re targeting individuals in the EU: know what data you hold, have a retention policy, and be able to respond to subject access requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cold email has its own legal requirements.&lt;/strong&gt; CAN-SPAM (US), CASL (Canada), and GDPR (EU) each set rules for commercial email — sender identification, a working opt-out mechanism, no deceptive subject lines. AI-drafted emails don’t exempt you from any of these. Make sure the drafts Claude produces include what the law requires, not just what reads well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The approval step is your compliance checkpoint.&lt;/strong&gt; The human review between every draft and every send is also where you catch a message that makes a claim you can’t substantiate, uses data that shouldn’t have been collected, or targets someone who has previously opted out. Don’t remove it in the name of speed.&lt;/p&gt;

&lt;h2 id=&quot;on-ethical-use&quot;&gt;On ethical use&lt;/h2&gt;

&lt;p&gt;When Claude drafts text and you send it, the sending is yours. When Claude navigates to Gmail and creates the draft, the gap between “Claude helped me” and “Claude did it” shrinks.&lt;/p&gt;

&lt;p&gt;The version I’m comfortable with: Claude handles research, formatting, navigation, and the mechanical execution of the workflow. I review every outreach before it goes out. The personalization is accurate, the tone reflects how I’d actually write, and the email is worth the recipient’s time. The approval step is not a rubber stamp — it’s where I verify that what Claude produced is actually what I’d send.&lt;/p&gt;

&lt;p&gt;The version I’d avoid: using computer use to generate volume as a substitute for relevance, or using the appearance of personalization to simulate a relationship that doesn’t exist. The technology makes it easy to send more. That’s not the same as having more worth saying.&lt;/p&gt;

&lt;h2 id=&quot;the-unfair-advantage&quot;&gt;The unfair advantage&lt;/h2&gt;

&lt;p&gt;The asymmetry worth understanding: with text generation, the worst case is output you don’t use. With computer use, the worst case is action taken in your name that you didn’t intend. That asymmetry is precisely why the approval discipline matters — and it’s also what creates the genuine advantage for people who do it well.&lt;/p&gt;

&lt;p&gt;The professionals who get disproportionate value from this aren’t the ones automating the most. They’re the ones who’ve figured out which tasks are worth automating — the research, the CRM updates, the draft creation — and stay personally in the loop on everything that carries relationship weight. They’re spending their attention where it compounds, and clearing the rest.&lt;/p&gt;

&lt;p&gt;Computer use shifts the cost of execution low enough that the bottleneck becomes clarity — knowing what to do and who it’s for — rather than time. That’s always been the more interesting constraint. Now it’s the only one left.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;/post/mcp-from-first-principles/&quot;&gt;next post in this series&lt;/a&gt; goes under the hood: how MCP, the protocol that makes all of this possible, actually works — from the OS primitives to the wire format.&lt;/p&gt;
</description>
        <pubDate>Sat, 28 Mar 2026 04:30:00 +0000</pubDate>
        <link>https://codingdash.com/post/claude-on-mac-automating-marketing-outreach/</link>
        <guid isPermaLink="true">https://codingdash.com/post/claude-on-mac-automating-marketing-outreach/</guid>
        
        <category>Claude</category>
        
        <category>AI</category>
        
        <category>Productivity</category>
        
        <category>Marketing</category>
        
        <category>Automation</category>
        
        <category>Mac</category>
        
        
        <category>posts</category>
        
      </item>
    
      <item>
        <title>Treating Claude as Engineering Infrastructure</title>
        <description>&lt;p&gt;A few months ago I reviewed a PR that should have taken thirty minutes to understand. It was 600 lines across eight files, described as “refactored the integration layer.” The developer had used ChatGPT to implement it. When I asked about tests, there weren’t any. When I asked why the handler was now calling three services it hadn’t touched before, they weren’t sure — ChatGPT had structured it that way and it seemed to work.&lt;/p&gt;

&lt;!--more--&gt;

&lt;figure class=&quot;photostream double&quot;&gt;
    &lt;div class=&quot;image thumbnail&quot;&gt;
        &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/03/27/treating-claude-as-engineering-infrastructure.png&quot; class=&quot;img-responsive&quot; alt=&quot;&quot; /&gt;
    &lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;That’s what bolting AI on looks like in practice. The individual got faster. The output got harder to review, harder to trust, and harder to maintain. The team absorbed the cost.&lt;/p&gt;

&lt;p&gt;The pattern is the same regardless of which tool — Claude, Copilot, or anything else. The tool doesn’t cause it. The absence of a system does.&lt;/p&gt;

&lt;p&gt;The teams I’ve seen get real value from AI-assisted development didn’t just adopt a new tool — they designed a system around it. The same discipline they bring to CI, code review, and onboarding. Not because Claude requires hand-holding, but because teams do.&lt;/p&gt;

&lt;h2 id=&quot;the-setup-most-teams-skip&quot;&gt;The setup most teams skip&lt;/h2&gt;

&lt;p&gt;Claude has no institutional memory by default. Every session starts cold — no context about your codebase, no knowledge of your conventions, no awareness of the decisions made three months ago. Without deliberate setup, every engineer on the team is having a slightly different experience, and inconsistency is the dominant pattern.&lt;/p&gt;

&lt;p&gt;The other thing teams skip: changing their process. If your existing workflow has weak test coverage, no architecture documentation, and code review that’s mostly a rubber stamp — adding Claude to that workflow doesn’t fix it. It accelerates it.&lt;/p&gt;

&lt;p&gt;What effective teams do is treat AI workflow as infrastructure. It has an owner, it gets maintained, it’s documented, and it’s enforced at the tool level — not left to each engineer’s discretion.&lt;/p&gt;

&lt;h2 id=&quot;claudemd--your-teams-shared-brain&quot;&gt;CLAUDE.md — your team’s shared brain&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; is a markdown file that Claude reads at the start of every session. It’s your team’s standing brief — the things Claude needs to know about your project that aren’t obvious from reading the code.&lt;/p&gt;

&lt;p&gt;The Claude Code documentation&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; describes it as the place to encode &lt;em&gt;“coding standards, architecture decisions, preferred libraries, and review checklists.”&lt;/em&gt; That’s the floor. The ceiling is anything you’d tell a new engineer on day one.&lt;/p&gt;

&lt;p&gt;I’ve seen teams skip this and then spend weeks wondering why Claude keeps introducing the same anti-patterns. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; is cheap to write and expensive to not have. Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;claude /init&lt;/code&gt; inside any project and Claude will generate a starter file by reading your codebase — edit it down from there. A minimal, useful one looks like this:&lt;/p&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gh&quot;&gt;# Project: Payments API&lt;/span&gt;

&lt;span class=&quot;gu&quot;&gt;## Architecture&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;
-&lt;/span&gt; Monolith split into bounded contexts under &lt;span class=&quot;sb&quot;&gt;`src/contexts/`&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Each context owns its models, services, and tests — no cross-context imports
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Background jobs go through Sidekiq, not inline in the request cycle

&lt;span class=&quot;gu&quot;&gt;## Conventions&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;
-&lt;/span&gt; All new endpoints require request and response schemas (see &lt;span class=&quot;sb&quot;&gt;`docs/schemas/`&lt;/span&gt;)
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Use &lt;span class=&quot;sb&quot;&gt;`Result&amp;lt;T, E&amp;gt;`&lt;/span&gt; pattern for service layer — no raw exceptions bubbling up
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Tests use RSpec; prefer &lt;span class=&quot;sb&quot;&gt;`let!`&lt;/span&gt; over &lt;span class=&quot;sb&quot;&gt;`before`&lt;/span&gt; for readability

&lt;span class=&quot;gu&quot;&gt;## What not to do&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;
-&lt;/span&gt; Do not add gems without updating &lt;span class=&quot;sb&quot;&gt;`docs/dependencies.md`&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Do not write raw SQL — use the query object pattern in &lt;span class=&quot;sb&quot;&gt;`app/queries/`&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Do not modify migration files that have already run in production

&lt;span class=&quot;gu&quot;&gt;## Review checklist before committing&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;
-&lt;/span&gt; Does the change have tests?
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Does it update the relevant schema docs?
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Does it follow the Result pattern?
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One practical constraint: keep &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; under 200 lines. Files longer than that consume too much context window, and instruction adherence actually drops as the file grows.&lt;/p&gt;

&lt;p&gt;That’s the first layer — &lt;em&gt;project context&lt;/em&gt;: what Claude needs to know about the codebase. Most teams stop here. The teams that get consistent results add a second layer: &lt;em&gt;workflow instructions&lt;/em&gt; — how Claude should approach work.&lt;/p&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gu&quot;&gt;## Core principles&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;
-&lt;/span&gt; Make every change as simple as possible — minimal impact, minimal code
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Find root causes; no temporary fixes
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Changes should only touch what&apos;s necessary

&lt;span class=&quot;gu&quot;&gt;## Workflow&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;
-&lt;/span&gt; Enter plan mode for any non-trivial task (3+ steps or architectural decisions)
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; If something goes sideways, stop and re-plan immediately — don&apos;t keep pushing
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Never mark a task complete without proving it works
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Before committing: run tests, check logs, ask yourself &quot;would a staff engineer approve this?&quot;

&lt;span class=&quot;gu&quot;&gt;## Subagent strategy&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;
-&lt;/span&gt; Use subagents to keep the main context window clean
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Offload research, exploration, and parallel analysis to subagents
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; One task per subagent for focused execution
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The distinction matters: project context tells Claude what exists. Workflow instructions tell Claude how to behave. Most CLAUDE.md drift problems — where Claude keeps doing the thing you asked it not to do — come from only writing the first layer.&lt;/p&gt;

&lt;h3 id=&quot;scoping-in-monorepos&quot;&gt;Scoping in monorepos&lt;/h3&gt;

&lt;p&gt;For monorepos, a single root &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; isn’t enough — different parts of the repo have different conventions. Claude reads both the root file and any &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; found in subdirectories, so you can scope instructions to where they apply:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;your-repo/
├── CLAUDE.md                  ← global: repo structure, git conventions, shared tooling
├── apps/
│   ├── api/
│   │   └── CLAUDE.md          ← API-specific: endpoints, auth patterns, schema rules
│   └── web/
│       └── CLAUDE.md          ← frontend: component structure, state conventions
└── packages/
    └── shared/
        └── CLAUDE.md          ← shared lib: what&apos;s public API, what&apos;s internal
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Subdirectory files add to the root — they don’t replace it. Whatever is in the root &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; is always in scope. This means the root is the right place for workflow instructions and cross-cutting conventions (test patterns, commit hygiene, shared tooling), and subdirectory files carry the project-context specifics for that package: its architecture, what not to touch, which patterns apply only there.&lt;/p&gt;

&lt;p&gt;If you’d rather not scatter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; files across the tree, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.claude/rules/&lt;/code&gt; offers an alternative — separate topic files at the root that Claude loads automatically, organized by concern rather than by directory.&lt;/p&gt;

&lt;h3 id=&quot;who-owns-it&quot;&gt;Who owns it&lt;/h3&gt;

&lt;p&gt;Treat &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; like your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;README&lt;/code&gt; — it lives in the repo, it gets reviewed in PRs, and it gets updated when conventions change. On a larger team, whoever owns architectural decisions owns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt;. Solo or small team, you own it.&lt;/p&gt;

&lt;p&gt;One practical rule: any time a decision is made in a PR that you’d want Claude to respect going forward, add it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; in that same PR. Keep the cost of updating it at zero.&lt;/p&gt;

&lt;p&gt;The ownership question scales with team size. A three-person startup doesn’t need a designated &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; steward — just the habit of treating it as a living document. A twenty-person org does need someone whose job it is to keep it from drifting.&lt;/p&gt;

&lt;h3 id=&quot;the-self-improvement-loop&quot;&gt;The self-improvement loop&lt;/h3&gt;

&lt;p&gt;One of the more underused patterns: a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tasks/lessons.md&lt;/code&gt; file that Claude updates after any correction. This requires the CLAUDE.md setup shown below — it’s not automatic. But once wired in, when you correct a mistake, Claude adds a rule to prevent that exact mistake in future sessions and reads the file before doing anything else at session start.&lt;/p&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gh&quot;&gt;# Lessons&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;
-&lt;/span&gt; Never modify migration files in place — create a new migration instead
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; When adding a new endpoint, update the schema doc in &lt;span class=&quot;sb&quot;&gt;`docs/schemas/`&lt;/span&gt; first
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; The Result&lt;span class=&quot;nt&quot;&gt;&amp;lt;T&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt; pattern applies to all service layer methods, not just ones that might fail
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The lessons file is committed and accumulates as you work. Every correction becomes a standing instruction that applies to every future session. This directly addresses the “Claude keeps making the same mistake” problem, which is otherwise solved only by repeating yourself.&lt;/p&gt;

&lt;p&gt;Wire it into your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; workflow section:&lt;/p&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gu&quot;&gt;## Self-improvement&lt;/span&gt;

After any correction from the user: update &lt;span class=&quot;sb&quot;&gt;`tasks/lessons.md`&lt;/span&gt; with the pattern.
Write a rule that prevents the same mistake from recurring.
Review &lt;span class=&quot;sb&quot;&gt;`tasks/lessons.md`&lt;/span&gt; at the start of each session.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;the-full-claude-folder&quot;&gt;The full &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.claude/&lt;/code&gt; folder&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; is the most visible part of the setup, but it sits inside a larger structure. There are actually two &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.claude/&lt;/code&gt; directories: the project-level folder committed to git, and a global &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.claude/&lt;/code&gt; folder in your home directory. The project folder holds team configuration — every engineer gets the same rules and commands. The global folder holds personal preferences and machine-local state that applies across all your projects.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;your-project/
├── CLAUDE.md               ← team instructions, committed
├── CLAUDE.local.md         ← personal overrides, gitignored
└── .claude/
    ├── settings.json       ← permissions + config, committed
    ├── settings.local.json ← personal permissions, gitignored
    ├── commands/           ← custom slash commands
    │   ├── review.md       → /project:review
    │   └── deploy.md       → /project:deploy
    ├── rules/              ← modular instruction files (path-scoped)
    │   ├── code-style.md
    │   ├── testing.md
    │   └── api-conventions.md
    └── agents/             ← subagent personas
        └── code-reviewer.md

~/.claude/
├── CLAUDE.md               ← your global instructions (all projects)
├── settings.json           ← your global hooks and permissions
└── projects/               ← session history + auto-memory per project
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Team vs. personal config.&lt;/strong&gt; The committed/gitignored split matters. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;settings.json&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.claude/commands/&lt;/code&gt; are shared — every engineer gets them. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.local.md&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;settings.local.json&lt;/code&gt; are personal — gitignored by default, for individual preferences and permission overrides that shouldn’t apply to everyone. The global &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.claude/CLAUDE.md&lt;/code&gt; is the right place for personal coding principles that should apply regardless of which repo you’re working in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modular rules.&lt;/strong&gt; A single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; works for small projects. As conventions grow, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.claude/rules/&lt;/code&gt; lets you split by concern — separate files for code style, testing conventions, and API patterns, each loaded automatically. The real power: add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;paths:&lt;/code&gt; field in YAML frontmatter to scope a rule file to specific directories. A rule with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;paths: [&quot;src/api/**/*.ts&quot;]&lt;/code&gt; only loads when Claude is working in that path — it won’t appear when it’s editing a React component. Rules without a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;paths&lt;/code&gt; field load unconditionally every session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom slash commands.&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.claude/commands/&lt;/code&gt; turns shared workflows into slash commands. A file at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.claude/commands/review.md&lt;/code&gt; becomes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/project:review&lt;/code&gt; — available to every engineer, prompted identically, committed to the repo. Useful for review checklists, deploy runbooks, and diagnostic sequences you’d otherwise write from scratch each time.&lt;/p&gt;

&lt;h2 id=&quot;the-individual-engineers-productive-loop&quot;&gt;The individual engineer’s productive loop&lt;/h2&gt;

&lt;p&gt;The workflow pattern that works consistently isn’t “describe what you want and review the output.” It’s a tighter loop with explicit checkpoints.&lt;/p&gt;

&lt;h3 id=&quot;1-plan-before-touching-code&quot;&gt;1. Plan before touching code&lt;/h3&gt;

&lt;p&gt;Before any file is opened or edited, explore the problem. Claude Code’s plan mode is designed for exactly this — read-only exploration of the codebase to surface questions and agree on an approach before anything changes.&lt;/p&gt;

&lt;p&gt;Describe the problem, ask Claude to trace through the relevant code and propose an approach, review the proposal, push back on anything that doesn’t fit your constraints. &lt;em&gt;Then&lt;/em&gt; implement.&lt;/p&gt;

&lt;p&gt;The cost of a bad implementation plan is much higher than the cost of a longer planning conversation.&lt;/p&gt;

&lt;h3 id=&quot;2-write-the-tests-first&quot;&gt;2. Write the tests first&lt;/h3&gt;

&lt;p&gt;Test-driven development is the single most effective pattern for agentic coding, and the reason is mechanical: failing tests give Claude unambiguous feedback. A test either passes or it doesn’t. There’s no room for “it looks right.”&lt;/p&gt;

&lt;p&gt;The workflow:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. Write the tests based on expected inputs and outputs
2. Confirm they fail (red)
3. Commit the failing tests — this is your checkpoint
4. Ask Claude to implement until the tests pass (green)
5. Review the diff before moving on
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Step 3 matters more than it looks. Claude will sometimes pass tests by modifying them rather than fixing the implementation. Committing first makes that visible and gives you a clean rollback point.&lt;/p&gt;

&lt;h3 id=&quot;3-small-verifiable-diffs&quot;&gt;3. Small, verifiable diffs&lt;/h3&gt;

&lt;p&gt;Don’t ask Claude to implement a feature end-to-end and review the entire output at once. Break it into steps, verify each one before the next, and commit checkpoints as you go.&lt;/p&gt;

&lt;p&gt;A 600-line diff touching eight files is hard for any engineer to review. The 600-line PR I described at the start of this post was the result of skipping this step.&lt;/p&gt;

&lt;p&gt;Before moving to the next step, apply a simple heuristic: &lt;em&gt;would a staff engineer approve this without questions?&lt;/em&gt; Run the tests, check the logs, diff the behavior against what was there before. If you’d feel uncomfortable putting it up for review right now, it’s not done. This is a useful thing to put in your CLAUDE.md workflow section explicitly — Claude will apply it as a self-check before declaring anything complete.&lt;/p&gt;

&lt;h3 id=&quot;4-context-hygiene&quot;&gt;4. Context hygiene&lt;/h3&gt;

&lt;p&gt;Long sessions degrade. A conversation that started with one problem and accumulated tangents, failed attempts, and revised requirements has noisy context — and Claude’s output reflects that noise.&lt;/p&gt;

&lt;p&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/clear&lt;/code&gt; when switching to a new problem. Start fresh sessions for unrelated tasks. The session is not the project; the project is the codebase.&lt;/p&gt;

&lt;p&gt;Anthropic’s engineering team writes about this&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; as &lt;em&gt;context pollution&lt;/em&gt; — the accumulation of irrelevant or contradictory information that degrades an agent’s coherence over time. The fix is the same as in any engineering system: garbage collection.&lt;/p&gt;

&lt;p&gt;One concrete way to manage context is to treat subagent use as a deliberate strategy rather than an afterthought. The general pattern: use subagents to keep the main context window clean; offload research, exploration, and parallel analysis; give each subagent one task for focused execution. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.claude/agents/&lt;/code&gt; takes this further — named, isolated personas like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;code-reviewer&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;security-auditor&lt;/code&gt; that operate with their own context and only see what they need for that role.&lt;/p&gt;

&lt;h2 id=&quot;what-to-delegate-what-to-own&quot;&gt;What to delegate, what to own&lt;/h2&gt;

&lt;p&gt;The most important skill in an AI-assisted team is knowing where the delegation boundary is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Safe to delegate fully&lt;/strong&gt; — well-defined tasks with clear acceptance criteria that can be verified mechanically:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Writing tests for untested code&lt;/li&gt;
  &lt;li&gt;Fixing lint errors across a codebase&lt;/li&gt;
  &lt;li&gt;Writing migration scripts for schema changes&lt;/li&gt;
  &lt;li&gt;Updating dependencies and resolving conflicts&lt;/li&gt;
  &lt;li&gt;Writing release notes from a git log&lt;/li&gt;
  &lt;li&gt;Translating code between similar patterns (e.g., converting callbacks to async/await)&lt;/li&gt;
  &lt;li&gt;Documentation for code that already exists&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Delegate with oversight&lt;/strong&gt; — the work is mechanical but the output needs a careful read:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Implementing a spec that’s already been written and reviewed&lt;/li&gt;
  &lt;li&gt;Refactoring to an established pattern with test coverage&lt;/li&gt;
  &lt;li&gt;Generating boilerplate from a template&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Own it yourself&lt;/strong&gt; — the cost of a mistake is high, or the requirements are still being discovered as you build:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Architecture decisions&lt;/li&gt;
  &lt;li&gt;Security-sensitive code&lt;/li&gt;
  &lt;li&gt;Product decisions embedded in implementation&lt;/li&gt;
  &lt;li&gt;Any system where you’d have to explain the trade-offs in a postmortem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The principle: delegate tasks where verification is easier than authorship. Keep the things where judgment is the actual work.&lt;/p&gt;

&lt;h3 id=&quot;the-backlog-ai-actually-unlocks&quot;&gt;The backlog AI actually unlocks&lt;/h3&gt;

&lt;p&gt;There’s a category of work that lives on every team’s backlog indefinitely — quality-of-life improvements that are low-value individually, tedious to do, and easy to defer in favor of anything else. Tests for legacy code. Fixing inconsistent error messages. Updating stale docs. Cleaning up dead feature flags.&lt;/p&gt;

&lt;p&gt;This is where the delegation math is most favorable. Keep a running list, batch it, and schedule a session to clear it. The work was always real; it just wasn’t worth interrupting everything else for.&lt;/p&gt;

&lt;h2 id=&quot;enforcement-over-instruction&quot;&gt;Enforcement over instruction&lt;/h2&gt;

&lt;p&gt;Instructions in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; are followed most of the time. &lt;em&gt;Most&lt;/em&gt; of the time is not good enough for standards that actually matter.&lt;/p&gt;

&lt;p&gt;Claude Code hooks let you run shell commands before or after specific actions — file edits, commits, tool calls. The distinction from instructions is fundamental: a hook is not a suggestion Claude can misread or skip, it’s a shell command that runs unconditionally.&lt;/p&gt;

&lt;p&gt;Hooks receive context as JSON on stdin, parsed with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jq&lt;/code&gt;. The settings file points to scripts:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.claude/settings.json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;$schema&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://json.schemastore.org/claude-code-settings.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;hooks&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;PostToolUse&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;matcher&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Edit|Write&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;hooks&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;~/.claude/hooks/lint-on-edit.sh&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;PreToolUse&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;matcher&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Bash&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;hooks&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;~/.claude/hooks/test-before-commit.sh&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The scripts read tool input from stdin:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# ~/.claude/hooks/lint-on-edit.sh&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;INPUT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;FILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$INPUT&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;.tool_input.file_path // empty&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$FILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$CLAUDE_PROJECT_DIR&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; npx eslint &lt;span class=&quot;nt&quot;&gt;--fix&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$FILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; 2&amp;gt;&amp;amp;1 | &lt;span class=&quot;nb&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# ~/.claude/hooks/test-before-commit.sh&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;INPUT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;CMD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$INPUT&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;.tool_input.command // empty&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$CMD&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-q&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;^git commit&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class=&quot;nb&quot;&gt;test &lt;/span&gt;2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Three things worth knowing before you write your first hook. &lt;strong&gt;Exit codes matter:&lt;/strong&gt; exit 0 is success, exit 1 is a non-blocking error, and exit 2 is the only code that actually blocks execution — it stops Claude and sends your stderr back for self-correction. Using exit 1 for a security hook is the most common mistake; it logs an error and does nothing. &lt;strong&gt;PostToolUse can’t undo:&lt;/strong&gt; the tool has already run by the time PostToolUse fires, so use PreToolUse for anything that needs to prevent an action, not just react to it. &lt;strong&gt;Stop hooks need a guard:&lt;/strong&gt; a Stop hook that runs tests and exits with code 2 on failure will loop infinitely — Claude retries, the hook fires again, repeat. Check the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stop_hook_active&lt;/code&gt; flag in the JSON payload and let Claude stop on the second attempt.&lt;/p&gt;

&lt;p&gt;The rule of thumb: if the standard appears in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt;, it’s a convention. If it appears in hooks, it’s enforced. Put in hooks anything you’d enforce in CI.&lt;/p&gt;

&lt;h3 id=&quot;ci-is-still-the-gate&quot;&gt;CI is still the gate&lt;/h3&gt;

&lt;p&gt;AI-generated code goes through the same pipeline as everything else. Green tests, passing lint, reviewed PR — the process doesn’t change because Claude wrote the code. If anything, the bar for review attention should be higher on large AI-generated diffs, not lower.&lt;/p&gt;

&lt;p&gt;Anthropic ships an official GitHub Action&lt;sup id=&quot;fnref:3&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; that runs Claude Code inside your pipeline and posts findings directly to pull requests:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# .github/workflows/claude-review.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Claude PR Review&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;pull_request&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;review&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;permissions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;contents&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;read&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;pull-requests&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;write&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;anthropics/claude-code-action@v1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;anthropic_api_key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;Review this PR against our CLAUDE.md conventions.&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;Flag missing tests, security concerns, and architectural issues.&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;Post your findings as a PR comment.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;GitLab CI/CD integration is documented separately at &lt;a href=&quot;https://code.claude.com/docs/en/gitlab-ci-cd&quot;&gt;code.claude.com/docs/en/gitlab-ci-cd&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For teams wanting a purpose-built option, &lt;a href=&quot;https://github.com/prlens/prlens&quot;&gt;PRLens&lt;/a&gt; is an open-source AI review tool that goes further: it injects git history, file relationships, and paired test context into the review before submitting to Claude or GPT-4o, then posts inline comments directly to the PR. It’s what I use — disclosure: I built it.&lt;/p&gt;

&lt;h2 id=&quot;team-conventions-for-ai-assisted-work&quot;&gt;Team conventions for AI-assisted work&lt;/h2&gt;

&lt;p&gt;Beyond individual workflow, a few team-level conventions make a material difference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PRs look the same regardless of authorship.&lt;/strong&gt; An AI-assisted PR has the same requirements as any other: a clear description, passing tests, scoped to one concern, reviewed before merge. The fact that Claude wrote the initial implementation is not relevant to the review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pair on the prompts.&lt;/strong&gt; Pair programming adapts naturally — one engineer drives the conversation with Claude, one reviews the output in real time. This is particularly effective for unfamiliar domains: the reviewer catches things the driver is too close to the problem to notice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Onboarding still covers the domain.&lt;/strong&gt; New engineers should understand the system they’re working in, not just how to prompt Claude to implement things in it. The risk of skipping this is subtle — it shows up months later in architectural decisions that don’t fit, and in code reviews where nobody can explain why something was done a certain way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decide on transparency, then stick to it — but don’t underestimate how hard this is.&lt;/strong&gt; Whether to disclose AI-assisted authorship is a genuine open question, and the team dynamics around it are more complicated than a working agreement can fully resolve.&lt;/p&gt;

&lt;p&gt;Three things the “just decide” framing misses. First, disclosure changes review behavior — reviewers who know code is AI-generated scrutinize it differently, sometimes more carefully, sometimes less. This isn’t necessarily bad, but it means the team needs to agree not just on &lt;em&gt;whether&lt;/em&gt; to disclose, but on what disclosure changes about the review process. Second, most AI-assisted code isn’t binary. Claude scaffolded it, you wrote the logic, Claude caught a bug, you restructured the approach. Where’s the line? A policy of “disclose all AI assistance” becomes unenforceable fast. Be specific about what you’re actually asking people to disclose. Third, career incentives work against disclosure in most organizations — the engineer who ships a large, clean PR gets credit regardless of how it was written. Disclosure introduces friction and potential skepticism. Unless the team actively normalizes AI-assisted work, the incentive is to say nothing, and a policy that runs against incentives isn’t really a policy.&lt;/p&gt;

&lt;p&gt;Make the call, document it, apply it consistently. But go in knowing that “consistently” is the hard part, and that you’ll need to revisit it as norms evolve.&lt;/p&gt;

&lt;h2 id=&quot;the-honest-conversation-about-craft&quot;&gt;The honest conversation about craft&lt;/h2&gt;

&lt;p&gt;Speed creates a tension worth naming directly.&lt;/p&gt;

&lt;p&gt;When generating output is fast and cheap, the incentive to slow down and understand something is weaker. That’s fine for tasks where understanding doesn’t compound — a migration script, a lint pass, translating boilerplate. It’s a real problem for the tasks where depth is what builds the engineer.&lt;/p&gt;

&lt;p&gt;I’ve started using a distinction that’s been useful: &lt;em&gt;use Claude to accelerate understanding, not to bypass it.&lt;/em&gt; In practice that means: when Claude writes code I need to maintain, I read it carefully before moving on. When I’m in an unfamiliar part of the codebase, I ask Claude to explain what’s happening before asking it to change anything. When I’m learning a new pattern, I implement the first instance myself and delegate the rest.&lt;/p&gt;

&lt;p&gt;The “ask Claude to explain, not just write” habit is underused. It costs one extra turn and it’s often more informative than the implementation itself — Claude will surface edge cases, explain the reasoning behind a design choice, and flag what it would do differently given more context. That’s the feedback loop that builds expertise rather than replacing it.&lt;/p&gt;

&lt;p&gt;This matters most for engineers who are still building the mental models that make delegation safe — and this is where the concern about AI-assisted development is most serious, and most underacknowledged.&lt;/p&gt;

&lt;p&gt;The mechanism is specific: when you struggle with something — a bug you can’t explain, a design that doesn’t fit, a system you don’t understand — you build a mental model. The struggle is the learning. When Claude solves it for you, you skip the struggle and get the output. The output looks the same. The model doesn’t form.&lt;/p&gt;

&lt;p&gt;For junior engineers, this creates a failure mode that’s invisible for a long time: they can produce work that looks senior-quality for months, until they encounter something Claude can’t handle well — an ambiguous requirement, a novel system design problem, a postmortem where they have to defend decisions they didn’t fully make. The gap between apparent capability and actual understanding only shows up under stress. By then, the pattern is established.&lt;/p&gt;

&lt;p&gt;The problem is structural. AI tools create an incentive gradient that points away from productive struggle. The gain from delegating is immediate and visible. The learning cost is deferred and invisible. For an engineer on a deadline, the rational choice in the moment is always to ask Claude. Repeated across hundreds of decisions over months, this is how you get someone who can ship code they can’t explain.&lt;/p&gt;

&lt;p&gt;A few patterns that help — for any engineer, but especially early-career:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first-instance rule.&lt;/strong&gt; Implement the first example of any new pattern yourself. Once you understand it, delegate the repetitions. The tenth migration script is mechanical. The first one is how you learn what migrations actually do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The explanation habit.&lt;/strong&gt; Before accepting Claude-written code, ask Claude to explain it — not as a check on Claude, but as a check on yourself. If you can’t follow the explanation, you can’t maintain the code. Reading and understanding is the minimum bar, not an optional step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distinguish mechanical from learning tasks.&lt;/strong&gt; Lint fixes, boilerplate, repetitive transformations — delegation is fine, the learning value is zero. A non-trivial algorithm, a system you’ve never touched, a pattern you’re implementing for the first time — these are different. Be intentional about which category you’re in before reaching for Claude.&lt;/p&gt;

&lt;p&gt;None of these can be hooked or enforced. There’s no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;settings.json&lt;/code&gt; rule that detects whether you understood what you just accepted. They’re the residual human discipline the system can’t replace — the part that sits outside any &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; or hook. This is actually why getting the infrastructure right matters: when the mechanical enforcement runs itself, you have attention left for the things only you can decide.&lt;/p&gt;

&lt;h2 id=&quot;what-it-actually-takes&quot;&gt;What it actually takes&lt;/h2&gt;

&lt;p&gt;None of this is complicated. All of it requires intention.&lt;/p&gt;

&lt;p&gt;The infrastructure is the easy part to describe: a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; with both layers, a committed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.claude/&lt;/code&gt; folder, hooks that enforce what matters, TDD as the feedback loop. Most teams can set that up in a day. The harder part is the craft — staying intentional about what you delegate, protecting the work that builds your judgment, and having the direct conversation about what AI-assisted development actually does to how engineers grow.&lt;/p&gt;

&lt;p&gt;The teams waiting for the next model to fix their results are the ones that bolted it on.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://code.claude.com/docs&quot;&gt;Claude Code documentation&lt;/a&gt; — Anthropic’s official docs covering CLAUDE.md, hooks, settings, and the full .claude/ folder structure. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents&quot;&gt;Effective context engineering for AI agents&lt;/a&gt; — Anthropic Engineering on context pollution and how accumulated irrelevant information degrades agent coherence over time. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://github.com/anthropics/claude-code-action&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;anthropics/claude-code-action&lt;/code&gt;&lt;/a&gt; — Official GitHub Action for running Claude Code reviews inside CI pipelines. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Fri, 27 Mar 2026 04:30:00 +0000</pubDate>
        <link>https://codingdash.com/post/treating-claude-as-engineering-infrastructure/</link>
        <guid isPermaLink="true">https://codingdash.com/post/treating-claude-as-engineering-infrastructure/</guid>
        
        <category>Software Engineering</category>
        
        <category>AI</category>
        
        <category>AI Coding Tools</category>
        
        <category>Claude</category>
        
        <category>Developer Productivity</category>
        
        <category>Developer Tools</category>
        
        <category>Engineering Team Health</category>
        
        <category>Code Review</category>
        
        <category>CI/CD</category>
        
        <category>Best Practice</category>
        
        <category>Monorepo</category>
        
        
        <category>posts</category>
        
      </item>
    
      <item>
        <title>The State of AI Native - Telephony</title>
        <description>&lt;p&gt;&lt;em&gt;This is the first in a series on what AI-native actually means, built from years inside the infrastructure — not observing it from outside. Each post goes deep on one segment. A consolidated piece follows once the series is complete.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There is a version of the AI-native telephony story that is easy to tell. Voice AI is here. Conversational agents are replacing call centre workers. The phone call, once a human-to-human interaction, is becoming a human-to-machine one. Companies are automating outreach, collections, and support. The market is moving fast. The technology is impressive.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — The AI-native telephony story is more complicated than the market suggests. The ideas being built today were imagined a decade ago inside companies like Exotel — they were shelved because the models didn’t exist yet, not because the vision was wrong. The intelligence layer has finally caught up. The infrastructure layer hasn’t. 95% of AI voice pilots are failing. Less than 1% of contact centres have autonomous agents in production. The new builders understand the models but keep hitting the same walls — concurrency, latency, physical circuit limits, and in India, 22 languages and a regulatory framework most platforms discover after they’re already in production. For Indian incumbents specifically, there is a 24–36 month window to capture the multilingual voice AI market before global platforms close the gap. And the per-minute pricing model that built the CPaaS industry is quietly being made obsolete by AI companies selling outcomes instead of infrastructure. This is a field report from someone who was inside the infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;figure class=&quot;photostream double&quot;&gt;
    &lt;div class=&quot;image thumbnail&quot;&gt;
        &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/03/22/state-of-ai-native-telephony.png&quot; class=&quot;img-responsive&quot; alt=&quot;&quot; /&gt;
    &lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;The numbers confirm the trajectory. The global voice AI agents market sat at $2.4 billion in 2024. It is projected to reach &lt;a href=&quot;https://market.us/report/voice-ai-agents-market/&quot;&gt;$47.5 billion by 2034&lt;/a&gt; — a compound annual growth rate of 34.8%. In India specifically, the CPaaS market crossed &lt;a href=&quot;https://www.mordorintelligence.com/industry-reports/india-communication-platform-as-a-service-market&quot;&gt;$1.1 billion in 2025&lt;/a&gt; and is on a 23–26% annual growth path, driven by BFSI, logistics, and healthcare embedding programmable voice into their core operations. Voice AI &lt;a href=&quot;https://www.agentvoice.com/ai-voice-in-2025-mapping-a-45-billion-market-shift/&quot;&gt;venture funding&lt;/a&gt; nearly septupled in two years, from roughly $315 million in 2022 to $2.1 billion in 2024. The money is following the conviction.&lt;/p&gt;

&lt;div class=&quot;chart-stat-row&quot;&gt;

&lt;figure class=&quot;chart-card chart-stat&quot;&gt;
  &lt;p class=&quot;chart-stat-label&quot;&gt;Voice AI agents market 2024&lt;/p&gt;
  &lt;div class=&quot;chart-stat-value&quot;&gt;$2.4B&lt;/div&gt;
  
  
&lt;/figure&gt;


&lt;figure class=&quot;chart-card chart-stat&quot;&gt;
  &lt;p class=&quot;chart-stat-label&quot;&gt;Projected by 2034&lt;/p&gt;
  &lt;div class=&quot;chart-stat-value&quot;&gt;$47.5B&lt;/div&gt;
  
  
&lt;/figure&gt;


&lt;figure class=&quot;chart-card chart-stat&quot;&gt;
  &lt;p class=&quot;chart-stat-label&quot;&gt;CAGR 2025–2034&lt;/p&gt;
  &lt;div class=&quot;chart-stat-value&quot;&gt;34.8%&lt;/div&gt;
  
  
&lt;/figure&gt;


&lt;figure class=&quot;chart-card chart-stat&quot;&gt;
  &lt;p class=&quot;chart-stat-label&quot;&gt;India CPaaS market 2025&lt;/p&gt;
  &lt;div class=&quot;chart-stat-value&quot;&gt;$1.1B&lt;/div&gt;
  
  
&lt;/figure&gt;

&lt;/div&gt;

&lt;figure class=&quot;chart-card&quot;&gt;
  
  &lt;figcaption class=&quot;chart-header&quot;&gt;
    &lt;div class=&quot;chart-title&quot;&gt;Market growth&lt;/div&gt;
    
  &lt;/figcaption&gt;
  
  &lt;div class=&quot;chart-canvas-wrap&quot;&gt;
    &lt;canvas id=&quot;chart-bar-market-growth&quot;&gt;&lt;/canvas&gt;
  &lt;/div&gt;
  &lt;footer class=&quot;chart-footer&quot;&gt;Source: Market.us, Mordor Intelligence, Expert Market Research (2025)&lt;/footer&gt;
&lt;/figure&gt;

&lt;script&gt;
(function () {
  var el = document.getElementById(&apos;chart-bar-market-growth&apos;);
  if (!el || typeof Chart === &apos;undefined&apos;) return;

  var raw     = [
      {&quot;label&quot;:&quot;Voice AI agents&quot;,&quot;values&quot;:[2.4,3.2,4.4,5.9,7.9,10.7,14.4,19.4,26.1,35.2,47.5]},
      {&quot;label&quot;:&quot;India CPaaS&quot;,&quot;values&quot;:[1.1,1.4,1.7,2.1,2.6,3.2,3.9],&quot;color&quot;:&quot;#3b82f6&quot;}
    ];
  var unit    = &apos;$&apos;;
  var isHoriz = &apos;vertical&apos; === &apos;horizontal&apos;;
  var isGrouped = raw.length &gt; 0 &amp;&amp; Array.isArray(raw[0].values);
  var defaultColors = [&apos;#FF6719&apos;,&apos;#3b82f6&apos;,&apos;#10b981&apos;,&apos;#f59e0b&apos;,&apos;#8b5cf6&apos;];

  var style      = getComputedStyle(document.documentElement);
  var mutedColor = style.getPropertyValue(&apos;--text-muted&apos;).trim() || &apos;#6b7280&apos;;
  var gridColor  = style.getPropertyValue(&apos;--border&apos;).trim()     || &apos;#e5e7eb&apos;;

  var xlabels, datasets;

  if (isGrouped) {
    xlabels  = [&quot;2024&quot;,&quot;2025&quot;,&quot;2026&quot;,&quot;2027&quot;,&quot;2028&quot;,&quot;2029&quot;,&quot;2030&quot;,&quot;2031&quot;,&quot;2032&quot;,&quot;2033&quot;,&quot;2034&quot;];
    datasets = raw.map(function (s, i) {
      return {
        label: s.label,
        data: s.values,
        backgroundColor: s.color || defaultColors[i % defaultColors.length],
        borderRadius: 3,
        borderSkipped: false
      };
    });
  } else {
    xlabels  = raw.map(function (d) { return d.label; });
    datasets = [{
      data: raw.map(function (d) { return d.value; }),
      backgroundColor: &apos;#FF6719&apos;,
      borderRadius: 3,
      borderSkipped: false
    }];
  }

  new Chart(el, {
    type: &apos;bar&apos;,
    data: { labels: xlabels, datasets: datasets },
    options: {
      indexAxis: isHoriz ? &apos;y&apos; : &apos;x&apos;,
      responsive: true,
      maintainAspectRatio: true,
      plugins: {
        legend: {
          display: isGrouped,
          labels: { color: mutedColor, font: { family: &quot;&apos;Maven Pro&apos;, sans-serif&quot;, size: 12 } }
        },
        tooltip: {
          callbacks: {
            label: function (ctx) {
              var v = ctx.parsed[isHoriz ? &apos;x&apos; : &apos;y&apos;];
              return &apos; &apos; + (isGrouped ? ctx.dataset.label + &apos;: &apos; : &apos;&apos;) + v + unit;
            }
          }
        }
      },
      scales: {
        x: {
          grid: { color: isHoriz ? gridColor : &apos;transparent&apos; },
          ticks: { color: mutedColor, font: { family: &quot;&apos;Maven Pro&apos;, sans-serif&quot;, size: 12 } },
          max: (isHoriz &amp;&amp; unit === &apos;%&apos;) ? 100 : undefined
        },
        y: {
          grid: { color: isHoriz ? &apos;transparent&apos; : gridColor },
          ticks: { color: mutedColor, font: { family: &quot;&apos;Maven Pro&apos;, sans-serif&quot;, size: 12 } },
          max: (!isHoriz &amp;&amp; unit === &apos;%&apos;) ? 100 : undefined
        }
      }
    }
  });
})();
&lt;/script&gt;

&lt;p&gt;That conviction is showing up in financial results too. Twilio — the default telephony infrastructure for most AI voice stacks — reported Voice AI customers growing nearly &lt;a href=&quot;https://futurumgroup.com/insights/twilio-q3-fy-2025-results-lift-outlook-on-broad-based-demand/&quot;&gt;60% year on year in Q3 2025&lt;/a&gt;, with a 10x revenue increase from its top ten Voice AI startup customers. Conversation Relay call volume more than tripled quarter over quarter. The infrastructure layer is seeing real demand, not just funding announcements.&lt;/p&gt;

&lt;p&gt;And yet beneath the growth numbers sits a different statistic that rarely makes the headline: 95% of generative AI pilots are either failing outright or severely underperforming expectations, according to &lt;a href=&quot;https://parallellabs.app/why-95-of-ai-agent-deployments-are-failing-and-the-3-architecture-decisions-that-separate-success-from-47000-mistakes/&quot;&gt;MIT and McKinsey research&lt;/a&gt; from 2025. Fully autonomous AI voice agents in production — not demos, not pilots, but live production systems — accounted for less than 1% of contact centres as of 2024. The gap between the market narrative and the deployment reality is wide. It is not closing as fast as the funding rounds suggest.&lt;/p&gt;

&lt;p&gt;That version of the story is true. It is also incomplete in a way that matters.&lt;/p&gt;

&lt;p&gt;The part of the story that doesn’t get told — because most of the people telling it weren’t there — is what it actually took to build reliable telephony infrastructure in the first place. What the hard problems were. What was tried and abandoned before the models existed to make it work. And why the new generation of AI voice companies, building elegantly on top of OpenAI and Anthropic and Gemini, keep running into walls that feel new to them but are recognisable to anyone who spent time inside the infrastructure.&lt;/p&gt;

&lt;p&gt;I spent time inside the infrastructure. This is what I saw.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-problem-was-never-intelligence-it-was-reliability&quot;&gt;The Problem Was Never Intelligence. It Was Reliability.&lt;/h2&gt;

&lt;p&gt;When I was at &lt;a href=&quot;https://exotel.com/&quot;&gt;Exotel&lt;/a&gt;, the problem we were solving was not smart communication. It was reliable communication. Those are different problems, and conflating them leads you to build the wrong things.&lt;/p&gt;

&lt;p&gt;India at that time — and in many ways still — was a country where physical infrastructure failed in ways that software-only companies never had to think about. Road construction could damage the cables connecting your servers to the internet. Not metaphorically. Literally — a JCB digging a trench for a new road would cut through a fibre line, and a region would go dark. Cellular networks had coverage gaps that weren’t on any official map. You found them when your calls started dropping and you couldn’t explain why from the software layer alone.&lt;/p&gt;

&lt;p&gt;And then there were the events nobody planned for — when &lt;a href=&quot;https://en.wikipedia.org/wiki/2015_South_India_floods&quot;&gt;in 2015 the Chennai flood&lt;/a&gt; took down an entire data centre, it wasn’t a software incident. It was a reminder that the infrastructure underneath all the abstraction was still physical, still vulnerable, and still capable of going dark in ways no SLA had ever accounted for.&lt;/p&gt;

&lt;p&gt;What I remember most from that period isn’t the outage. It’s what we built in response to it. During the floods, Exotel ran an initiative where people stranded and in need of rescue could simply make a call or send an SMS — no app, no internet, no smartphone required. That call would automatically &lt;a href=&quot;https://x.com/exotel_helpline&quot;&gt;trigger a tweet&lt;/a&gt;, putting their location and situation in front of people who could help. Telcos were overwhelmed. Formal rescue coordination was breaking down.&lt;/p&gt;

&lt;p&gt;A small team had built something that worked on the most basic communication primitives available — a call, a text — and connected it to the one channel that was still moving information: social media. &lt;a href=&quot;https://www.linkedin.com/in/vishnujayadevan/&quot;&gt;Vishnu Jayadevan&lt;/a&gt; and I built this in the middle of the crisis, with whatever we had.&lt;/p&gt;

&lt;p&gt;It worked. People got rescued.&lt;/p&gt;

&lt;figure class=&quot;photostream double&quot; style=&quot;max-width: 500px;margin: 0 auto;&quot;&gt;
    &lt;div class=&quot;image thumbnail&quot;&gt;
        &lt;a href=&quot;https://x.com/krdpravin/status/671903703444549632&quot; target=&quot;_blank&quot;&gt;
          &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/03/22/tweet-for-chennai-help.png&quot; class=&quot;img-responsive&quot; alt=&quot;&quot; /&gt;
        &lt;/a&gt;
    &lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;I think about that whenever someone tells me the telephony layer is a commodity to be abstracted away. It isn’t. At the moments that matter most, it is the only thing left.&lt;/p&gt;

&lt;p&gt;The architecture we were building lived at the intersection of software and physics. We were connecting cloud infrastructure to regional telephone servers — the bridge between the programmable internet and the physical telephone network. And that physical network had properties that software abstractions couldn’t hide.&lt;/p&gt;

&lt;p&gt;Recording calls reliably and transferring them from local telephone servers to cloud sounds straightforward until you’re doing it at scale across a country with uneven connectivity, and you need to guarantee that no recording gets lost regardless of what the network does between the moment the call ends and the moment the file lands in storage. Routing calls through fallback servers when the primary internet link went down required building a real-time awareness of link health that most software observability tools weren’t designed for. The observability we needed was not “is the service up?” — it was “is this specific call, for this specific customer, routing correctly through this specific path, right now?”&lt;/p&gt;

&lt;p&gt;We also built what every SaaS company builds — dashboards, reporting, multi-tenancy, the noisy neighbour problem. But those were table stakes. The problems that kept us up at night were physical. They had no Stack Overflow threads. You figured them out by reasoning from first principles about the nature of PRI lines and telephony routing, or you didn’t figure them out at all, and someone’s business stopped working.&lt;/p&gt;

&lt;p&gt;PRI lines deserve a specific mention because they become important later. A PRI — Primary Rate Interface — is a physical telephone circuit. It carries a fixed number of simultaneous call channels. It is not software. You cannot autoscale it. You provision it from a telco, you pay for the capacity whether you use it or not, and when you run out of channels, calls fail. The only way to get more capacity is to order more physical circuits, which takes time and costs money regardless of actual utilisation.&lt;/p&gt;

&lt;p&gt;At the scale Exotel was operating, this was a constant tension. A customer could launch a campaign — a mass outbound calling event — that would suddenly demand far more concurrent channels than their provisioned capacity. The noisy neighbour problem in telephony wasn’t just about CPU or memory; it was about finite physical circuits that multiple customers shared. Rate limiting wasn’t a product decision. It was an infrastructure survival mechanism.&lt;/p&gt;

&lt;p&gt;A fair challenge to this framing is that most modern cloud telephony has moved away from PRI entirely. &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc3261&quot;&gt;SIP&lt;/a&gt; trunking replaced it as the dominant protocol — more flexible, software-configurable, cheaper to provision. And that’s true. But SIP didn’t eliminate the physical constraint. It abstracted it one layer up. Somewhere beneath every &lt;a href=&quot;https://aws.amazon.com/what-is/sip-trunking/&quot;&gt;SIP trunk&lt;/a&gt;, there is still a telco with physical infrastructure, capacity limits, and provisioning timelines that don’t bend to the demands of software. The abstraction is thinner than it looks. When you push it hard enough — when an AI agent campaign fires at a scale the telco wasn’t expecting — you find the ceiling again. It just has a different label on it.&lt;/p&gt;

&lt;p&gt;Which brings up the dependency that almost nobody talks about when they pitch an AI-native telephony stack: the carrier relationship. Every voice call that touches the PSTN — which is most of them, because most of your customers are on phones, not &lt;a href=&quot;https://webrtc.org/&quot;&gt;WebRTC&lt;/a&gt; browser tabs — has to go through a telco at some point. That relationship is not a technical detail. It is a commercial and operational dependency that shapes what you can build, how fast you can scale, what your unit economics look like, and how much of your reliability is actually in your control. A new AI voice company can build an extraordinary intelligence layer and still find its growth capped by carrier capacity, routing agreements, or regulatory compliance obligations it didn’t know existed. The telco isn’t going away. The question for AI-native builders is whether they treat that dependency as a constraint to engineer around, or a layer to genuinely understand and work with.&lt;/p&gt;

&lt;p&gt;Once the reliability layers were stable, we started moving up the stack. We rewrote latency-critical components in Go. We worked on noise reduction and call quality analysis. These were quality-of-service investments — making the pipe better, faster, cleaner. Good engineering work on a well-understood problem.&lt;/p&gt;

&lt;p&gt;And alongside all of this, quietly, there were other conversations.&lt;/p&gt;

&lt;hr /&gt;

&lt;figure class=&quot;chart-card&quot;&gt;
  
  &lt;figcaption class=&quot;chart-header&quot;&gt;
    &lt;div class=&quot;chart-title&quot;&gt;Where latency actually lives in an AI voice call&lt;/div&gt;
    &lt;p class=&quot;chart-subtitle&quot;&gt;Approximate contribution of each stage to total round-trip time&lt;/p&gt;
  &lt;/figcaption&gt;
  
  &lt;div class=&quot;chart-canvas-wrap&quot;&gt;
    &lt;canvas id=&quot;chart-bar-where-latency-actually-lives-in-an-ai-voice-call&quot;&gt;&lt;/canvas&gt;
  &lt;/div&gt;
  &lt;footer class=&quot;chart-footer&quot;&gt;Source: Illustrative — varies by provider and region&lt;/footer&gt;
&lt;/figure&gt;

&lt;script&gt;
(function () {
  var el = document.getElementById(&apos;chart-bar-where-latency-actually-lives-in-an-ai-voice-call&apos;);
  if (!el || typeof Chart === &apos;undefined&apos;) return;

  var raw     = [{&quot;label&quot;:&quot;Network (PSTN/SIP)&quot;,&quot;value&quot;:15},{&quot;label&quot;:&quot;Speech recognition (ASR)&quot;,&quot;value&quot;:80},{&quot;label&quot;:&quot;LLM inference&quot;,&quot;value&quot;:300},{&quot;label&quot;:&quot;Text-to-speech (TTS)&quot;,&quot;value&quot;:120},{&quot;label&quot;:&quot;Audio encoding/jitter&quot;,&quot;value&quot;:25}];
  var unit    = &apos;ms&apos;;
  var isHoriz = &apos;horizontal&apos; === &apos;horizontal&apos;;
  var isGrouped = raw.length &gt; 0 &amp;&amp; Array.isArray(raw[0].values);
  var defaultColors = [&apos;#FF6719&apos;,&apos;#3b82f6&apos;,&apos;#10b981&apos;,&apos;#f59e0b&apos;,&apos;#8b5cf6&apos;];

  var style      = getComputedStyle(document.documentElement);
  var mutedColor = style.getPropertyValue(&apos;--text-muted&apos;).trim() || &apos;#6b7280&apos;;
  var gridColor  = style.getPropertyValue(&apos;--border&apos;).trim()     || &apos;#e5e7eb&apos;;

  var xlabels, datasets;

  if (isGrouped) {
    xlabels  = [];
    datasets = raw.map(function (s, i) {
      return {
        label: s.label,
        data: s.values,
        backgroundColor: s.color || defaultColors[i % defaultColors.length],
        borderRadius: 3,
        borderSkipped: false
      };
    });
  } else {
    xlabels  = raw.map(function (d) { return d.label; });
    datasets = [{
      data: raw.map(function (d) { return d.value; }),
      backgroundColor: &apos;#FF6719&apos;,
      borderRadius: 3,
      borderSkipped: false
    }];
  }

  new Chart(el, {
    type: &apos;bar&apos;,
    data: { labels: xlabels, datasets: datasets },
    options: {
      indexAxis: isHoriz ? &apos;y&apos; : &apos;x&apos;,
      responsive: true,
      maintainAspectRatio: true,
      plugins: {
        legend: {
          display: isGrouped,
          labels: { color: mutedColor, font: { family: &quot;&apos;Maven Pro&apos;, sans-serif&quot;, size: 12 } }
        },
        tooltip: {
          callbacks: {
            label: function (ctx) {
              var v = ctx.parsed[isHoriz ? &apos;x&apos; : &apos;y&apos;];
              return &apos; &apos; + (isGrouped ? ctx.dataset.label + &apos;: &apos; : &apos;&apos;) + v + unit;
            }
          }
        }
      },
      scales: {
        x: {
          grid: { color: isHoriz ? gridColor : &apos;transparent&apos; },
          ticks: { color: mutedColor, font: { family: &quot;&apos;Maven Pro&apos;, sans-serif&quot;, size: 12 } },
          max: (isHoriz &amp;&amp; unit === &apos;%&apos;) ? 100 : undefined
        },
        y: {
          grid: { color: isHoriz ? &apos;transparent&apos; : gridColor },
          ticks: { color: mutedColor, font: { family: &quot;&apos;Maven Pro&apos;, sans-serif&quot;, size: 12 } },
          max: (!isHoriz &amp;&amp; unit === &apos;%&apos;) ? 100 : undefined
        }
      }
    }
  });
})();
&lt;/script&gt;

&lt;h2 id=&quot;the-graveyard-of-correct-ideas&quot;&gt;The Graveyard of Correct Ideas&lt;/h2&gt;

&lt;p&gt;This is the part of the telephony story I think about most when I look at what’s being built today.&lt;/p&gt;

&lt;p&gt;There were ideas that surfaced regularly in the years I was at Exotel. Ideas that felt right, that had obvious value, that smart engineers and product people kept coming back to — and that kept getting shelved. Not because they were wrong. Because the world wasn’t ready for them.&lt;/p&gt;

&lt;p&gt;Automated sentiment analysis on calls was one of them. The problem it was trying to solve was real and significant. If you are a collections company running hundreds of thousands of calls per month, your agents are making constant judgments about customer sentiment. This customer sounds hostile — escalate. This one sounds cooperative but cash-constrained — offering a payment plan. This one sounds like they’re going to hang up in thirty seconds — get to the point. These are skilled, experience-dependent judgments, and they exist entirely in the agent’s head. The moment the call ends, the judgment is gone. What goes into the system is a call disposition: “Promised to pay” or “Not reachable” or “Callback requested.” The nuance — which is often the most valuable part — disappears.&lt;/p&gt;

&lt;p&gt;The idea of automating that signal was correct. Capture sentiment continuously during the call. Surface it in real time to the agent and their supervisor. Feed it into the post-call workflow to determine next steps. Use it to train agents on where their calls go wrong. The value was obvious to anyone who spent time with collections or sales teams.&lt;/p&gt;

&lt;p&gt;We did the research. We hit the wall. The models that existed at the time couldn’t do this reliably at scale. Accuracy was insufficient. The infrastructure cost to process audio in real time for millions of calls was not justified by output quality that was still too variable to trust. So the idea went back in the drawer.&lt;/p&gt;

&lt;p&gt;Voice biometrics was another. The ability to authenticate a caller by their voice pattern rather than by a PIN or a password. The use cases were real: reducing friction in customer authentication, detecting repeat fraud attempts, identifying bot-generated calls before they consumed agent time. &lt;a href=&quot;https://www.linkedin.com/in/shiv6146/&quot;&gt;Shiva Shankar Arumugam&lt;/a&gt;, who worked with me at Exotel and participated in some of these initiatives, recalled these explorations when I spoke to him as part of this research — the limitations were well understood even then: spoofing vulnerabilities, regulatory questions, the quality of the models available at the time. It was shelved.&lt;/p&gt;

&lt;p&gt;And then there was BotMandate. The idea was to take a standard operating procedure — the document that every call centre has, the one that every new agent gets trained on in their first week — and have the system build a workflow from it automatically. Give it the guidelines, let it construct the execution logic. The business gives the intent. The system figures out the structure.&lt;/p&gt;

&lt;p&gt;I want to be precise about what BotMandate was trying to do, because it is easy to understate it. It was not trying to take a structured flowchart and convert it to a decision tree. It was trying to take a prose document describing how a business operates — the kind of document written by a human for humans — and derive from it a functional workflow that could be executed by the system. That is, in essence, exactly what the most sophisticated AI-native workflow and voice agent platforms are building today, with large language models doing the interpretation that rule engines couldn’t.&lt;/p&gt;

&lt;p&gt;The idea was correct. The capability layer didn’t exist.&lt;/p&gt;

&lt;p&gt;Some of these ideas are now being executed. Not as proof of concept — as products with real customers. Sentiment analysis at scale, which we shelved because the models weren’t accurate enough, is now running in production at contact centre intelligence platforms. The accuracy is there. The infrastructure cost is justified. The signal that used to live entirely in the agent’s head is now being captured, structured, and fed back into workflows in real time. SOP-to-workflow — the core ambition of BotMandate — exists in early form inside several AI agent platforms today, where a business can describe its process in plain language and have the system construct the execution logic. It is not perfect. The edge cases still surface. But the gap between what the system produces and what a human would design has narrowed to the point where it is useful in production. Voice biometrics remains the most nascent of the three — the spoofing problem is harder than it looks, and the regulatory questions in markets like India have not been resolved. But the trajectory is clear. The graveyard is being emptied. The question is not whether these ideas will be built. It is whether the people building them understand deeply enough why the previous attempts failed.&lt;/p&gt;

&lt;p&gt;This changes how you should think about who is best positioned to build AI-native products in infrastructure-heavy segments. The instinct is to assume it’s the AI-first builder — someone who deeply understands the models, the architectures, the emerging tooling, and applies that understanding to a new domain. And sometimes that’s right. But in segments like telephony, where the problems are deeply physical and the history of failed attempts is long and instructive, the more credible builder is probably someone who already spent years understanding why the previous attempts failed. Not because they’re smarter, but because they already know which walls are real and which ones just look real from the outside.&lt;/p&gt;

&lt;hr /&gt;

&lt;figure class=&quot;chart-card&quot;&gt;
  
  &lt;figcaption class=&quot;chart-header&quot;&gt;
    &lt;div class=&quot;chart-title&quot;&gt;AI voice quality vs. infrastructure complexity over time&lt;/div&gt;
    &lt;p class=&quot;chart-subtitle&quot;&gt;Qualitative index — higher is better for quality, lower is better for complexity&lt;/p&gt;
  &lt;/figcaption&gt;
  
  &lt;div class=&quot;chart-canvas-wrap&quot;&gt;
    &lt;canvas id=&quot;chart-line-ai-voice-quality-vs-infrastructure-complexity-over-time&quot;&gt;&lt;/canvas&gt;
  &lt;/div&gt;
  &lt;footer class=&quot;chart-footer&quot;&gt;Source: Illustrative index based on industry trajectory&lt;/footer&gt;
&lt;/figure&gt;

&lt;script&gt;
(function () {
  var el = document.getElementById(&apos;chart-line-ai-voice-quality-vs-infrastructure-complexity-over-time&apos;);
  if (!el || typeof Chart === &apos;undefined&apos;) return;
  var series  = [{&quot;label&quot;:&quot;Voice quality&quot;,&quot;values&quot;:[20,22,28,35,45,62,80,91],&quot;color&quot;:&quot;#FF6719&quot;},{&quot;label&quot;:&quot;Infrastructure complexity&quot;,&quot;values&quot;:[85,82,78,75,70,60,48,38],&quot;color&quot;:&quot;#3b82f6&quot;}];
  var xlabels = [&quot;2018&quot;,&quot;2019&quot;,&quot;2020&quot;,&quot;2021&quot;,&quot;2022&quot;,&quot;2023&quot;,&quot;2024&quot;,&quot;2025&quot;];
  var unit    = &apos;&apos;;
  var doFill  = &apos;false&apos; === &apos;true&apos;;
  var style = getComputedStyle(document.documentElement);
  var mutedColor = style.getPropertyValue(&apos;--text-muted&apos;).trim() || &apos;#6b7280&apos;;
  var gridColor  = style.getPropertyValue(&apos;--border&apos;).trim()     || &apos;#e5e7eb&apos;;

  var defaultColors = [&apos;#FF6719&apos;,&apos;#3b82f6&apos;,&apos;#10b981&apos;,&apos;#f59e0b&apos;,&apos;#8b5cf6&apos;];

  var datasets = series.map(function (s, i) {
    var c = s.color || defaultColors[i % defaultColors.length];
    return {
      label: s.label,
      data: s.values,
      borderColor: c,
      backgroundColor: doFill ? c + &apos;22&apos; : &apos;transparent&apos;,
      fill: doFill,
      tension: 0.3,
      pointRadius: 4,
      pointHoverRadius: 6,
      borderWidth: 2
    };
  });

  new Chart(el, {
    type: &apos;line&apos;,
    data: { labels: xlabels, datasets: datasets },
    options: {
      responsive: true,
      maintainAspectRatio: true,
      plugins: {
        legend: {
          display: series.length &gt; 1,
          labels: { color: mutedColor, font: { family: &quot;&apos;Maven Pro&apos;, sans-serif&quot;, size: 12 } }
        },
        tooltip: {
          callbacks: {
            label: function (ctx) {
              return &apos; &apos; + ctx.dataset.label + &apos;: &apos; + ctx.parsed.y + unit;
            }
          }
        }
      },
      scales: {
        x: {
          grid: { color: gridColor },
          ticks: { color: mutedColor, font: { family: &quot;&apos;Maven Pro&apos;, sans-serif&quot;, size: 12 } }
        },
        y: {
          grid: { color: gridColor },
          ticks: { color: mutedColor, font: { family: &quot;&apos;Maven Pro&apos;, sans-serif&quot;, size: 12 } }
        }
      }
    }
  });
})();
&lt;/script&gt;

&lt;p&gt;The chart above tells the story that the market narrative misses. Voice quality and infrastructure complexity moved in opposite directions for years — but they did not move at the same speed. Quality improved gradually, then dramatically. Complexity reduced gradually, then stalled. The crossover at 2023 is not a finish line. It is the moment the intelligence layer finally caught up with the vision — and exposed how much the infrastructure layer still has to close.&lt;/p&gt;

&lt;h2 id=&quot;what-2026-actually-looks-like&quot;&gt;What 2026 Actually Looks Like&lt;/h2&gt;

&lt;p&gt;The market has moved. The use cases that were theoretical when we were shelving sentiment analysis and BotMandate are now product categories with funded companies and real customers.&lt;/p&gt;

&lt;p&gt;Sales outreach through AI agents. Collections automation that runs conversations without a human on one end. Customer support that handles the majority of inbound queries without escalation. The industries that built their operations on call volume — financial services, insurance, healthcare, e-commerce — are actively deploying AI in the telephony layer. The demand is real and it is accelerating.&lt;/p&gt;

&lt;p&gt;Roughly 95% of what is being built right now is in the customer support segment. A customer reaches out. An AI agent handles it. No wait time. No transfer queue. Resolution, or escalation when needed. The business case is clear: companies using AI in customer service report &lt;a href=&quot;https://www.robylon.ai/blog/will-ai-replace-call-center-reps-2026&quot;&gt;30–40% reductions&lt;/a&gt; in support costs and 60% faster resolution times. 43% of contact centres are already &lt;a href=&quot;https://callminer.com/blog/the-future-of-ai-call-center-automation-in-2025-and-beyond&quot;&gt;using AI to automate repetitive tasks&lt;/a&gt;. 46% have deployed real-time agent assist tools. The ROI is calculable, and the technology is good enough for a significant portion of interactions.&lt;/p&gt;

&lt;div class=&quot;chart-stat-row&quot;&gt;

&lt;figure class=&quot;chart-card chart-stat&quot;&gt;
  &lt;p class=&quot;chart-stat-label&quot;&gt;Using AI to automate tasks&lt;/p&gt;
  &lt;div class=&quot;chart-stat-value&quot;&gt;43%&lt;/div&gt;
  &lt;p class=&quot;chart-stat-context&quot;&gt;of contact centres&lt;/p&gt;
  
&lt;/figure&gt;


&lt;figure class=&quot;chart-card chart-stat&quot;&gt;
  &lt;p class=&quot;chart-stat-label&quot;&gt;Fully autonomous AI agents&lt;/p&gt;
  &lt;div class=&quot;chart-stat-value&quot;&gt;&amp;lt;1%&lt;/div&gt;
  &lt;p class=&quot;chart-stat-context&quot;&gt;in production (2024)&lt;/p&gt;
  
&lt;/figure&gt;


&lt;figure class=&quot;chart-card chart-stat&quot;&gt;
  &lt;p class=&quot;chart-stat-label&quot;&gt;Cost reduction with AI&lt;/p&gt;
  &lt;div class=&quot;chart-stat-value&quot;&gt;30–40%&lt;/div&gt;
  &lt;p class=&quot;chart-stat-context&quot;&gt;reported by adopters&lt;/p&gt;
  
&lt;/figure&gt;


&lt;figure class=&quot;chart-card chart-stat&quot;&gt;
  &lt;p class=&quot;chart-stat-label&quot;&gt;AI pilots failing or underperforming&lt;/p&gt;
  &lt;div class=&quot;chart-stat-value&quot;&gt;95%&lt;/div&gt;
  &lt;p class=&quot;chart-stat-context&quot;&gt;MIT &amp;amp; McKinsey, 2025&lt;/p&gt;
  
&lt;/figure&gt;

&lt;/div&gt;

&lt;figure class=&quot;chart-card&quot;&gt;
  
  &lt;figcaption class=&quot;chart-header&quot;&gt;
    &lt;div class=&quot;chart-title&quot;&gt;Call centre adoption 2024 vs 2027&lt;/div&gt;
    
  &lt;/figcaption&gt;
  
  &lt;div class=&quot;chart-canvas-wrap&quot;&gt;
    &lt;canvas id=&quot;chart-bar-call-centre-adoption-2024-vs-2027&quot;&gt;&lt;/canvas&gt;
  &lt;/div&gt;
  &lt;footer class=&quot;chart-footer&quot;&gt;Source: Sources: CallMiner CX Report 2024, Emerge Haus AI Atlas, MIT/McKinsey 2025&lt;/footer&gt;
&lt;/figure&gt;

&lt;script&gt;
(function () {
  var el = document.getElementById(&apos;chart-bar-call-centre-adoption-2024-vs-2027&apos;);
  if (!el || typeof Chart === &apos;undefined&apos;) return;

  var raw     = [
      {&quot;label&quot;:&quot;2024 actual&quot;,&quot;values&quot;:[43,38,46,1],&quot;color&quot;:&quot;#8491f5&quot;},
      {&quot;label&quot;:&quot;2027 projected&quot;,&quot;values&quot;:[75,70,80,50],&quot;color&quot;:&quot;#c0c0c0&quot;}
    ];
  var unit    = &apos;%&apos;;
  var isHoriz = &apos;horizontal&apos; === &apos;horizontal&apos;;
  var isGrouped = raw.length &gt; 0 &amp;&amp; Array.isArray(raw[0].values);
  var defaultColors = [&apos;#FF6719&apos;,&apos;#3b82f6&apos;,&apos;#10b981&apos;,&apos;#f59e0b&apos;,&apos;#8b5cf6&apos;];

  var style      = getComputedStyle(document.documentElement);
  var mutedColor = style.getPropertyValue(&apos;--text-muted&apos;).trim() || &apos;#6b7280&apos;;
  var gridColor  = style.getPropertyValue(&apos;--border&apos;).trim()     || &apos;#e5e7eb&apos;;

  var xlabels, datasets;

  if (isGrouped) {
    xlabels  = [&quot;Al for task automation&quot;,&quot;Al-led QA scoring&quot;,&quot;Real-time agent assist&quot;,&quot;Fully autonomous Al calls&quot;];
    datasets = raw.map(function (s, i) {
      return {
        label: s.label,
        data: s.values,
        backgroundColor: s.color || defaultColors[i % defaultColors.length],
        borderRadius: 3,
        borderSkipped: false
      };
    });
  } else {
    xlabels  = raw.map(function (d) { return d.label; });
    datasets = [{
      data: raw.map(function (d) { return d.value; }),
      backgroundColor: &apos;#FF6719&apos;,
      borderRadius: 3,
      borderSkipped: false
    }];
  }

  new Chart(el, {
    type: &apos;bar&apos;,
    data: { labels: xlabels, datasets: datasets },
    options: {
      indexAxis: isHoriz ? &apos;y&apos; : &apos;x&apos;,
      responsive: true,
      maintainAspectRatio: true,
      plugins: {
        legend: {
          display: isGrouped,
          labels: { color: mutedColor, font: { family: &quot;&apos;Maven Pro&apos;, sans-serif&quot;, size: 12 } }
        },
        tooltip: {
          callbacks: {
            label: function (ctx) {
              var v = ctx.parsed[isHoriz ? &apos;x&apos; : &apos;y&apos;];
              return &apos; &apos; + (isGrouped ? ctx.dataset.label + &apos;: &apos; : &apos;&apos;) + v + unit;
            }
          }
        }
      },
      scales: {
        x: {
          grid: { color: isHoriz ? gridColor : &apos;transparent&apos; },
          ticks: { color: mutedColor, font: { family: &quot;&apos;Maven Pro&apos;, sans-serif&quot;, size: 12 } },
          max: (isHoriz &amp;&amp; unit === &apos;%&apos;) ? 100 : undefined
        },
        y: {
          grid: { color: isHoriz ? &apos;transparent&apos; : gridColor },
          ticks: { color: mutedColor, font: { family: &quot;&apos;Maven Pro&apos;, sans-serif&quot;, size: 12 } },
          max: (!isHoriz &amp;&amp; unit === &apos;%&apos;) ? 100 : undefined
        }
      }
    }
  });
})();
&lt;/script&gt;

&lt;p&gt;But the production reality is more sobering. Fully autonomous AI voice agents — not demos, not pilots, but live systems handling real customer calls — &lt;a href=&quot;https://www.emerge.haus/ai-atlas/contact-centers&quot;&gt;accounted for less than 1%&lt;/a&gt; of contact centres as of 2024.&lt;/p&gt;

&lt;p&gt;The perception gap runs deeper than deployment numbers alone. Twilio’s 2025 &lt;a href=&quot;https://www.twilio.com/en-us/report/Inside-the-Conversational-AI-Revolution&quot;&gt;Inside the Conversational AI Revolution&lt;/a&gt; report — surveying 4,800 consumers and 457 business leaders across 15 countries — found that 90% of business leaders believe their customers are satisfied with their conversational AI experience. Only 59% of consumers agree. A 31-point gap between what companies think they’ve built and what customers actually experience. That is not a technology problem. That is an assumption problem — and it lives in exactly the same place as the infrastructure gap: builders optimising for what they can measure, not for what customers feel.&lt;/p&gt;

&lt;p&gt;The gap between adoption of AI features and deployment of AI-native systems is vast. And the reasons come down to infrastructure walls that keep getting rediscovered.&lt;/p&gt;

&lt;figure class=&quot;chart-card&quot;&gt;
  
  &lt;figcaption class=&quot;chart-header&quot;&gt;
    &lt;div class=&quot;chart-title&quot;&gt;Deployment blockers&lt;/div&gt;
    &lt;p class=&quot;chart-subtitle&quot;&gt;% deployments citing as top blocker&lt;/p&gt;
  &lt;/figcaption&gt;
  
  &lt;div class=&quot;chart-canvas-wrap&quot;&gt;
    &lt;canvas id=&quot;chart-bar-deployment-blockers&quot;&gt;&lt;/canvas&gt;
  &lt;/div&gt;
  &lt;footer class=&quot;chart-footer&quot;&gt;Source: VoAgents deployment analysis, Haptik enterprise guide, Deepgram voice agent research (2025)&lt;/footer&gt;
&lt;/figure&gt;

&lt;script&gt;
(function () {
  var el = document.getElementById(&apos;chart-bar-deployment-blockers&apos;);
  if (!el || typeof Chart === &apos;undefined&apos;) return;

  var raw     = [{&quot;label&quot;:&quot;Latency &amp; real-time responses&quot;,&quot;value&quot;:68},{&quot;label&quot;:&quot;Telephony infra complexity&quot;,&quot;value&quot;:61},{&quot;label&quot;:&quot;Concurrency and scaling&quot;,&quot;value&quot;:54},{&quot;label&quot;:&quot;Accent &amp; dialect accuracy&quot;,&quot;value&quot;:47},{&quot;label&quot;:&quot;Legacy system integration&quot;,&quot;value&quot;:52},{&quot;label&quot;:&quot;Noise &amp; audio quality&quot;,&quot;value&quot;:39}];
  var unit    = &apos;%&apos;;
  var isHoriz = &apos;horizontal&apos; === &apos;horizontal&apos;;
  var isGrouped = raw.length &gt; 0 &amp;&amp; Array.isArray(raw[0].values);
  var defaultColors = [&apos;#FF6719&apos;,&apos;#3b82f6&apos;,&apos;#10b981&apos;,&apos;#f59e0b&apos;,&apos;#8b5cf6&apos;];

  var style      = getComputedStyle(document.documentElement);
  var mutedColor = style.getPropertyValue(&apos;--text-muted&apos;).trim() || &apos;#6b7280&apos;;
  var gridColor  = style.getPropertyValue(&apos;--border&apos;).trim()     || &apos;#e5e7eb&apos;;

  var xlabels, datasets;

  if (isGrouped) {
    xlabels  = [];
    datasets = raw.map(function (s, i) {
      return {
        label: s.label,
        data: s.values,
        backgroundColor: s.color || defaultColors[i % defaultColors.length],
        borderRadius: 3,
        borderSkipped: false
      };
    });
  } else {
    xlabels  = raw.map(function (d) { return d.label; });
    datasets = [{
      data: raw.map(function (d) { return d.value; }),
      backgroundColor: &apos;#FF6719&apos;,
      borderRadius: 3,
      borderSkipped: false
    }];
  }

  new Chart(el, {
    type: &apos;bar&apos;,
    data: { labels: xlabels, datasets: datasets },
    options: {
      indexAxis: isHoriz ? &apos;y&apos; : &apos;x&apos;,
      responsive: true,
      maintainAspectRatio: true,
      plugins: {
        legend: {
          display: isGrouped,
          labels: { color: mutedColor, font: { family: &quot;&apos;Maven Pro&apos;, sans-serif&quot;, size: 12 } }
        },
        tooltip: {
          callbacks: {
            label: function (ctx) {
              var v = ctx.parsed[isHoriz ? &apos;x&apos; : &apos;y&apos;];
              return &apos; &apos; + (isGrouped ? ctx.dataset.label + &apos;: &apos; : &apos;&apos;) + v + unit;
            }
          }
        }
      },
      scales: {
        x: {
          grid: { color: isHoriz ? gridColor : &apos;transparent&apos; },
          ticks: { color: mutedColor, font: { family: &quot;&apos;Maven Pro&apos;, sans-serif&quot;, size: 12 } },
          max: (isHoriz &amp;&amp; unit === &apos;%&apos;) ? 100 : undefined
        },
        y: {
          grid: { color: isHoriz ? &apos;transparent&apos; : gridColor },
          ticks: { color: mutedColor, font: { family: &quot;&apos;Maven Pro&apos;, sans-serif&quot;, size: 12 } },
          max: (!isHoriz &amp;&amp; unit === &apos;%&apos;) ? 100 : undefined
        }
      }
    }
  });
})();
&lt;/script&gt;

&lt;p&gt;The walls are familiar to anyone who spent time inside the infrastructure. They just don’t look familiar to teams that came to telephony through AI rather than the other way around.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The concurrency problem, revisited.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The rate limiting we built at Exotel was not a product decision. It was a load-bearing piece of infrastructure designed to protect a shared physical resource — PRI line capacity — from being overwhelmed by a single customer’s campaign. It was carefully designed. It worked. It was the right solution to the problem it was solving.&lt;/p&gt;

&lt;p&gt;AI agents break this design in a specific and non-obvious way.&lt;/p&gt;

&lt;p&gt;A human agent handles one call at a time. They dial. They talk. The call ends. They disposition it. They dial again. The concurrency is naturally limited by human biology and attention. The infrastructure was designed around this reality.&lt;/p&gt;

&lt;p&gt;An AI agent has no such limit. It can run hundreds of concurrent conversations. Thousands, if the infrastructure supports it. Which means the assumptions baked into the telephony layer — the rate limits, the capacity planning, the cost models, the monitoring systems — were designed for a world that no longer applies. The question is not how to lift the rate limit. The question is how to redesign the entire capacity model for a fundamentally different concurrency profile.&lt;/p&gt;

&lt;p&gt;And then there is the PRI problem, which does not go away just because the software layer has become intelligent.&lt;/p&gt;

&lt;p&gt;PRI lines are still physical circuits. They still cannot be autoscaled the way cloud compute can. An AI telephony company that can spin up thousands of concurrent agent conversations through elegant software architecture still needs the physical line capacity to route those calls through the telephone network. And the economics of that capacity — provisioned in advance, paid for whether used or not, ordered from telcos on timelines measured in weeks — have not changed with the arrival of large language models.&lt;/p&gt;

&lt;p&gt;No company today has solved this end-to-end. The software layer has become dramatically more capable. The physical layer it depends on has not. Every AI-native telephony company is, somewhere in their architecture, managing this tension. Most of them discovered it after they had already built everything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The quality and control problem.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The new companies building on top of major model providers are, in many cases, building beautifully. The intelligence layer is genuinely impressive. Conversations are coherent, contextually aware, able to handle complexity that would have been impossible a few years ago.&lt;/p&gt;

&lt;p&gt;What these companies often lack is granular control over the voice layer underneath the intelligence. Noise cancellation that can be tuned or disabled for specific use cases — because sometimes the noise reduction algorithm that works well for a quiet office call actively degrades audio quality for a call centre floor where the ambient noise contains useful signals. Latency controls that allow real-time feedback on audio quality and immediate adjustment. The ability to tune the nuances of voice processing for specific contexts without having to rebuild the entire stack.&lt;/p&gt;

&lt;p&gt;These controls exist in mature telephony infrastructure because they were built by teams who spent years learning why they were necessary. They are not obvious to teams who arrived at telephony through AI. They are also not trivial to retrofit once you have a production system. The path toward solving the latency problem specifically runs through smaller, specialised models deployed at the edge — closer to the call, with lower inference overhead — rather than routing every conversation through a general-purpose LLM in a remote data centre. That direction is visible. It is not yet the default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The missing mental models.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is perhaps the subtlest gap, and the hardest to fix.&lt;/p&gt;

&lt;p&gt;The people building AI voice products today are, in many cases, brilliant engineers and product thinkers. They understand machine learning, distributed systems, product design, and go-to-market. What they often don’t have is the accumulated intuition about telephony infrastructure that comes from years of operating inside it — understanding why certain architectural decisions were made, what failure modes they were protecting against, which constraints are genuinely physical and which are just accumulated habits masquerading as necessity.&lt;/p&gt;

&lt;p&gt;This creates a specific pattern of mistakes. Not catastrophic mistakes — these are smart people building real products. But a recurring tendency to treat the telephony layer as a solved problem that can be abstracted away, when in reality it is a partially solved problem with hard constraints that have not changed. The abstraction breaks at the moments that matter most: high-scale campaigns, unexpected concurrency spikes, regional network failures, audio quality edge cases.&lt;/p&gt;

&lt;p&gt;The most concrete illustration of this is the production stack that emerged as the &lt;a href=&quot;https://www.usesherlock.ai/blog/voice-ai-stack-2025-year-in-review&quot;&gt;de facto standard in 2025&lt;/a&gt;. Most AI voice deployments combined Twilio for telephony, ElevenLabs for text-to-speech, and either Vapi or Retell for orchestration — three separate companies, each solving one layer, stitched together at the seams. Each component is excellent at what it does. The integration points between them are where things fail. That is not an AI-native architecture. It is three pipes taped together. An AI-native telephony company does not assemble this stack. It redesigns the assumptions that made the stack necessary. This is precisely the problem &lt;a href=&quot;https://www.rapida.ai/&quot;&gt;Rapida AI&lt;/a&gt; is building toward — an open-source, end-to-end voice AI orchestration platform that brings LLMs, STT, TTS, telephony, noise cancellation, and observability into a single unified stack, so builders stop stitching and start building.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The India layer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Building AI-native telephony in India is not the same problem as building it in the United States, and the difference is not just scale. India has twenty-two officially recognised languages and hundreds of dialects. An AI voice agent trained primarily in English — or even Hindi — will degrade sharply when it encounters a caller from Tamil Nadu speaking in a regional accent, or a farmer in Maharashtra switching mid-sentence between Marathi and Hindi as people naturally do. Speech recognition accuracy can drop fifteen to thirty percentage points when moving from standard to regional variants. For a collections call or a healthcare reminder, that degradation is not a UX inconvenience. It is a product failure. The multilingual problem in India is an order of magnitude harder than the accent diversity problem in the US, and most AI voice platforms that enter the Indian market underestimate it until they are already in production.&lt;/p&gt;

&lt;p&gt;Then there is the regulatory layer. TRAI — the Telecom Regulatory Authority of India — governs what can be sent, to whom, at what time, and with what consent. The DND registry means a significant portion of your target calling list is legally unreachable for commercial calls. The consent framework for AI-initiated voice interactions is still evolving, and what is permissible today may not be permissible after the next regulatory update. These are not bureaucratic inconveniences. They are structural constraints that shape the product architecture from the ground up — what data you store, how you verify consent, what disclosures the AI agent has to make at the start of a call, and what your fallback looks like when a call is flagged as non-compliant. A company that builds its AI telephony stack without encoding these constraints at the infrastructure level will retrofit them later at significant cost. The India CPaaS numbers look compelling from the outside. The operating reality is more demanding than the market size suggests.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full strategic implications of this layer — what it means for incumbents, for new entrants, and for the market opportunity — deserve more than a subsection. They are addressed in full below.&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-india-opportunity-nobody-is-moving-fast-enough-on&quot;&gt;The India Opportunity Nobody Is Moving Fast Enough On&lt;/h2&gt;

&lt;p&gt;There is a market opportunity sitting in plain sight in India that most of the companies best positioned to capture it are not moving on fast enough. And most of the companies moving fast on it don’t have what it takes to actually capture it.&lt;/p&gt;

&lt;p&gt;The global voice AI narrative is written in English. The benchmarks are in English. The model evaluations are in English. The case studies — the restaurant booking agents, the healthcare scheduling assistants, the outbound sales callers — are almost all in English or a small set of Western European languages with relatively similar phonetic structures and minimal code-switching.&lt;/p&gt;

&lt;p&gt;India is not that market. And the difference is not a localisation problem. It is a fundamentally different technical and regulatory challenge that requires a ground-up approach, not a translation layer on top of a system designed for something else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The language problem is harder than it looks from the outside&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;India has twenty-two officially recognised languages. It has hundreds of dialects. And it has a conversational behaviour that is entirely normal for its speakers and almost entirely absent from the training data of every major voice AI model: code-switching.&lt;/p&gt;

&lt;p&gt;A collections agent calling a customer in Mumbai does not have a conversation in Hindi or in English. They have a conversation that moves between Hindi, English, and Marathi — sometimes within a single sentence. The customer who starts in formal Hindi switches to English when discussing financial terms, then drops into Marathi when expressing frustration. This is not an edge case. This is how people talk.&lt;/p&gt;

&lt;p&gt;Current ASR models handle this poorly. The transcript degrades. The LLM receives a malformed input. The agent response becomes contextually inappropriate. The call fails — not with an error, but with a customer who feels unheard and an outcome that isn’t captured correctly.&lt;/p&gt;

&lt;p&gt;The accuracy drop when moving from standard Indian English to regional language variants is not a rounding error. Studies have documented fifteen to thirty percentage point degradation. For a collections call or a healthcare reminder, this is not a UX inconvenience. A collections agent that mishears “I’ll pay next week” as “I won’t pay this week” and triggers an escalation workflow has not just failed technically. It has damaged a customer relationship and potentially created a compliance issue.&lt;/p&gt;

&lt;p&gt;The multilingual problem in India is an order of magnitude harder than the accent diversity problem in the US. And solving it requires something that cannot be purchased off the shelf from OpenAI or Anthropic today: high-quality training data in Indian regional languages, acoustic models tuned for Indian speech patterns, and code-switching detection that understands the specific language pairs that appear in Indian conversations — not the generic multilingual capability that works reasonably well for Spanish-English switching in California.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The regulatory layer is structural, not bureaucratic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;TRAI — the Telecom Regulatory Authority of India — is not a compliance checkbox. It is a structural constraint that shapes what an AI voice product can do, who it can call, when it can call them, and what it must say when it does.&lt;/p&gt;

&lt;p&gt;The DND registry is the most immediate constraint. A significant portion of any consumer calling list in India is legally unreachable for commercial outbound calls. An AI voice platform that doesn’t encode DND compliance at the infrastructure level — not as a pre-call filter, but as an architectural guarantee — is not a platform that an Indian enterprise can responsibly deploy at scale.&lt;/p&gt;

&lt;p&gt;The consent framework for AI-initiated voice interactions is actively evolving. What is permissible today under TRAI guidelines may not be permissible after the next regulatory update. An AI voice platform built without the flexibility to adapt its disclosure language, consent capture mechanism, and call recording handling to evolving TRAI requirements will face costly retrofitting — or worse, enterprise customers who pull deployments when a compliance gap is identified.&lt;/p&gt;

&lt;p&gt;Then there is the specific requirement that an AI agent disclose itself as an AI at the start of a call. This is not unique to India — it is emerging as a regulatory norm globally. But the implementation in India has specific nuances: the disclosure must be in the language of the call, it must be audibly clear, and the customer must have a clear path to a human agent. Building this into a voice AI platform as an afterthought produces brittle, inconsistent implementations. Building it in from the ground up produces a compliance posture that is genuinely defensible.&lt;/p&gt;

&lt;p&gt;The companies that understand these constraints at the architectural level — not as features to be added but as assumptions to be designed around — have a structural advantage over every global platform entering the Indian market. Twilio knows compliance. ElevenLabs is learning it. No global player has the accumulated institutional knowledge of how the Indian regulatory environment actually works in practice — the edge cases, the carrier-level enforcement patterns, the enterprise risk appetite — that an Indian incumbent has built over years of operating in this environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The infrastructure variation problem compounds everything&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;India is not one market. It is many markets with very different infrastructure profiles stacked on top of each other.&lt;/p&gt;

&lt;p&gt;An AI voice call placed to a customer in South Mumbai, where 4G and 5G coverage is reliable and latency is low, operates in a fundamentally different environment from a call placed to a customer in a tier-3 town in Uttar Pradesh, where connectivity is variable, 2G fallback is common, and audio quality degrades in ways that stress every layer of the voice AI stack.&lt;/p&gt;

&lt;p&gt;A collections company operating at scale in India is calling across all of these environments simultaneously. An AI voice platform that performs excellently in controlled conditions and degrades unpredictably in variable connectivity conditions is not a platform that works for India at scale. It is a platform that works for the demo and fails in the field.&lt;/p&gt;

&lt;p&gt;This is not a new problem. It is the same problem that Exotel and its generation of cloud telephony companies spent years solving — building fallback routing, variable bitrate audio, real-time quality monitoring, and graceful degradation for exactly this infrastructure variation. The solution exists in the accumulated operational knowledge of companies that built telephony for India from the ground up.&lt;/p&gt;

&lt;p&gt;The new AI voice platforms don’t have this. They will acquire it — either by building it slowly through production failures, or by partnering with or acquiring someone who already has it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The market that unlocks when this is solved&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The India CPaaS market at $1.1 billion in 2025 growing at 25% annually is a reasonable number for current spend. It understates the opportunity in a specific way: it captures what enterprises are currently paying for voice infrastructure. It does not capture the latent spend that becomes unlocked when multilingual AI voice actually works at production quality.&lt;/p&gt;

&lt;p&gt;Consider collections alone. India’s collections industry runs almost entirely on voice. The volume of outbound calls made by BFSI companies, NBFCs, and collections agencies in India annually is staggering. Most of it is done by human agents working from script, at high cost, with inconsistent quality and significant compliance risk. The willingness to pay for AI automation that actually works — that handles regional languages, respects DND compliance, and produces verifiable outcomes — is not speculative. It is visible in every enterprise conversation happening in this space right now.&lt;/p&gt;

&lt;p&gt;Add healthcare — appointment reminders, medication adherence calls, post-discharge follow-ups — all running at scale in regional languages across tier-2 and tier-3 markets. Add logistics, where driver coordination and delivery confirmation calls happen in hundreds of local languages. Add insurance, where policy servicing and renewal calls are a known cost centre that every insurer wants to automate.&lt;/p&gt;

&lt;p&gt;The market that unlocks when the multilingual voice AI problem is genuinely solved in India is not the $1.1 billion CPaaS market. It is the total spend on human voice interaction across these industries — which is an order of magnitude larger.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The window and who closes it&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This opportunity has a window. It is not indefinite.&lt;/p&gt;

&lt;p&gt;Global platforms are investing in multilingual capability. The pace of model improvement means that the gap between what a global platform can do and what the Indian market needs is closing — slowly in 2025, faster in 2026 and 2027 as more Indian language data enters training pipelines and as global platforms make strategic acquisitions in the Indian market.&lt;/p&gt;

&lt;p&gt;The companies best positioned to capture this window are Indian incumbents with existing carrier relationships, enterprise customer relationships in the relevant verticals, and the institutional knowledge of the regulatory environment. They have a 24 to 36 month advantage that is real but not permanent.&lt;/p&gt;

&lt;p&gt;The risk is not that the opportunity disappears. The risk is that it gets captured by someone else — a well-funded Indian AI startup that acquires the carrier relationships it needs, or a global platform that acquires an Indian voice AI company to fast-track market entry.&lt;/p&gt;

&lt;p&gt;The question for every Indian CPaaS CxO is not whether the multilingual AI voice opportunity in India is real. It is. The question is whether they move on it as a strategic bet — investing in multilingual model development, compliance architecture, and vertical-specific AI capabilities — or whether they wait to see how the market develops and find themselves retrofitting AI-native capabilities onto infrastructure that was never designed for them.
The window is open. It does not stay open forever.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-pricing-model-disruption-nobody-wants-to-talk-about&quot;&gt;The Pricing Model Disruption Nobody Wants to Talk About&lt;/h2&gt;

&lt;p&gt;There is a conversation that is happening in every enterprise sales meeting in the voice AI space right now, and almost nobody in the CPaaS industry is having it publicly.&lt;/p&gt;

&lt;p&gt;The conversation goes like this. A CxO at a collections company is being pitched by a new AI voice platform. The pitch is not about minutes. It is not about concurrent calls or call quality or IVR depth. The pitch is about recoveries. How many debts did the AI agent collect? What was the recovery rate? What was the cost per rupee recovered?&lt;/p&gt;

&lt;p&gt;The enterprise buyer is thinking in outcomes. The CPaaS vendor is still selling infrastructure.&lt;/p&gt;

&lt;p&gt;That gap — between how the new entrants are pricing their product and how the incumbents are pricing theirs — is not just a commercial problem. It is a signal that the two sides are operating with fundamentally different assumptions about what the product actually is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How per-minute pricing was born and why it made sense&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Per-minute pricing is not an arbitrary convention. It emerged directly from the underlying economics of telephony.&lt;/p&gt;

&lt;p&gt;Telcos charge per minute because the physical resource being consumed — circuit capacity, switching infrastructure, network time — is time-bound. A two-minute call consumes twice the network resource of a one-minute call. The cost structure is linear with time. The pricing follows the cost structure.&lt;/p&gt;

&lt;p&gt;CPaaS companies inherited this model and built on top of it. You provision a Twilio number, you get charged per minute for calls made and received, you pass that cost through to your customers with a margin. The value you are delivering is connectivity — the ability to make and receive calls programmatically. Connectivity is a time-bound resource. Per-minute pricing makes sense.&lt;/p&gt;

&lt;p&gt;The model worked well for a decade because the value delivered by a call was roughly proportional to its length. A longer call meant more conversation. More conversation meant more opportunity to achieve the call’s purpose. The pricing proxy — time — was a reasonable approximation of value delivered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What AI breaks in this model&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An AI voice agent does not have a linear relationship between call length and value delivered.&lt;/p&gt;

&lt;p&gt;A well-designed AI collections agent can determine within ninety seconds whether a customer is going to pay, negotiate a payment plan, confirm the details, and close the call. A poorly designed one can stay on the line for eight minutes achieving nothing. The per-minute model charges four times as much for the bad outcome as for the good one.&lt;/p&gt;

&lt;p&gt;More fundamentally: the value delivered by an AI voice call is not connection time. It is the outcome achieved. A three-minute call that books an appointment has a specific, measurable value to a healthcare provider. A three-minute call that doesn’t book an appointment has a near-zero value — or negative value if it consumed a slot in a calling queue that a successful call could have used.&lt;/p&gt;

&lt;p&gt;The enterprise buyer understands this intuitively. They are not buying minutes. They are buying appointments, recoveries, resolved tickets, qualified leads. They would like to pay for those outcomes. The per-minute model forces them to pay for the infrastructure whether the outcome materialises or not.&lt;/p&gt;

&lt;p&gt;This is not a new complaint about CPaaS pricing. Enterprise customers have always known they were paying for infrastructure rather than outcomes. They tolerated it because there was no alternative — the outcome was delivered by a human agent whose cost was also not outcome-linked, and the CPaaS was just one cost line among many.&lt;/p&gt;

&lt;p&gt;AI changes the tolerance level. When the agent is AI and the outcome is measurable and the attribution is clear — this call produced this result — the case for outcome-based pricing becomes much stronger, and the case for per-minute pricing becomes much weaker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The new pricing models emerging at the edges&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI voice companies are experimenting with several alternatives to per-minute pricing, with varying degrees of commercial maturity.&lt;/p&gt;

&lt;p&gt;Outcome-based pricing is the most ambitious. Charge per appointment booked, per debt collected above a threshold, per support ticket resolved without human escalation. The enterprise buyer pays only when value is delivered. The vendor takes on the risk of underperformance. Early deployments in the collections space in the US market have shown strong enterprise adoption — buyers are willing to pay a significant premium per outcome versus what they were paying per minute, because the risk transfer has real value.&lt;/p&gt;

&lt;p&gt;The commercial challenge is that outcome-based pricing requires deep integration into the customer’s downstream systems to verify the outcome. Did the appointment actually get kept? Did the payment actually come through? Did the support ticket actually stay resolved? Measuring outcomes requires data access that per-minute vendors have never needed and that enterprise customers are not always comfortable providing.&lt;/p&gt;

&lt;p&gt;Per-conversation pricing is a middle ground — charge a fixed fee per completed AI conversation regardless of length. This eliminates the perverse incentive of the per-minute model while avoiding the complexity of outcome measurement. It is simpler to operationalise and easier to forecast. It does not fully align incentives but it is a significant improvement over per-minute.&lt;/p&gt;

&lt;p&gt;Hybrid models are emerging that combine a lower per-minute infrastructure charge with a per-outcome success fee. This lets the vendor recover infrastructure costs while participating in the upside of successful outcomes. It is a transitional model — more honest than pure per-minute, less aligned than pure outcome-based.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The threat to incumbents&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The per-minute model is not going to collapse overnight. Enterprise contracts have inertia. Procurement processes are slow. The switching cost of moving a large voice operation from one infrastructure provider to another is real.&lt;/p&gt;

&lt;p&gt;But the threat is structural and it compounds over time.&lt;/p&gt;

&lt;p&gt;Every enterprise customer who moves to an AI voice platform with outcome-based pricing is a customer who is no longer thinking about their telephony spend in per-minute terms. Once that mental model shifts — once a CxO is measuring their voice AI spend in cost per recovery or cost per appointment rather than cost per minute — going back to per-minute pricing feels like a regression.&lt;/p&gt;

&lt;p&gt;The new entrants understand this. They are not competing with Exotel on per-minute rates. They are competing on a different dimension entirely — one where Exotel’s per-minute pricing is not a competitor to be undercut but a legacy model to be made obsolete.&lt;/p&gt;

&lt;p&gt;The incumbents who see this most clearly are the ones who are starting to reframe their own commercial conversations. Not “we are cheaper per minute than Twilio” but “we can deliver outcomes that the pure-play AI companies can’t because we have the carrier relationships, the compliance posture, and the India-specific language capabilities that make those outcomes achievable at scale.”&lt;/p&gt;

&lt;p&gt;That is a different sales conversation. It requires a different pricing model to make it credible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the transition actually requires&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Moving from per-minute to outcome-based pricing is not a pricing decision. It is a product, architecture, and commercial transformation.&lt;/p&gt;

&lt;p&gt;It requires instrumentation — the ability to track what happens after a call ends, to verify that an appointment was kept or a payment was made, to attribute the outcome to the AI conversation with sufficient confidence to invoice on it.&lt;/p&gt;

&lt;p&gt;It requires vertical depth — outcome pricing only works when you understand the outcome deeply enough to measure it. A generic voice platform cannot price on collections outcomes because it does not understand what a successful collections conversation looks like. A platform with deep BFSI experience and collections-specific models can.&lt;/p&gt;

&lt;p&gt;It requires risk tolerance — outcome-based pricing means the vendor carries performance risk. If the AI agent underperforms, the revenue doesn’t materialise. This requires confidence in the product and financial resilience to absorb variability.&lt;/p&gt;

&lt;p&gt;It requires a different sales motion — outcome-based pricing sells to a different buyer. Not the IT procurement team buying infrastructure, but the business unit leader buying results. The CxO of a collections company, not the VP of Telecom.&lt;/p&gt;

&lt;p&gt;None of this is easy. The incumbents who attempt this transition will face internal resistance, commercial risk, and the operational complexity of building outcome measurement into platforms that were never designed for it.&lt;/p&gt;

&lt;p&gt;The incumbents who don’t attempt it will find themselves selling infrastructure in a market that is increasingly buying outcomes. That is a survivable position in the short term and an untenable one in the medium term.&lt;/p&gt;

&lt;p&gt;The window to lead this transition — rather than be forced into it — is the same window as the India multilingual opportunity. It is open now. It will not stay open indefinitely.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;where-we-actually-are&quot;&gt;Where We Actually Are&lt;/h2&gt;

&lt;p&gt;The AI-native telephony opportunity is real. The technology has crossed a threshold that makes the use cases from the BotMandate era finally executable. Sentiment analysis that we shelved because the models weren’t good enough is now running in production at companies. Voice agents that can handle complex, multi-turn conversations without a script are being deployed at scale. The things we imagined and couldn’t build are being built.&lt;/p&gt;

&lt;p&gt;But we are earlier than the current noise suggests.&lt;/p&gt;

&lt;p&gt;The intelligence layer is ahead of the infrastructure layer. The new builders understand the models better than they understand the physical constraints the models sit on top of. The companies that will be genuinely AI-native in this segment — not AI-augmented, not AI-featured, but architecturally rebuilt around what AI actually enables — are the ones that take both layers seriously. The ones that don’t treat the telephony infrastructure as a commodity to be abstracted away, but as a set of real constraints that the AI-native product has to be designed around.&lt;/p&gt;

&lt;p&gt;The PRI concurrency problem is not going away. The latency requirements for real-time voice AI are not loosening. The regional infrastructure variation that made building telephony hard in the Exotel era is not resolved. These are the physics of the problem. The models are extraordinary. The physics are what they are.&lt;/p&gt;

&lt;p&gt;The market needs four things to close this gap. A platform that enables AI companies to handle telephony scaling without having to rediscover the physics themselves. Accessible frameworks and mental models that make the accumulated knowledge of telephony infrastructure legible to builders who didn’t grow up in it. Granular controls on voice quality with sensible defaults for teams that don’t have the expertise to tune from scratch. And a genuine reckoning with the India-specific layer — language diversity, regulatory constraints, and infrastructure variation — that most platforms discover only after they are already in production.&lt;/p&gt;

&lt;p&gt;These are not AI problems. They are infrastructure problems that AI has made urgent.&lt;/p&gt;

&lt;p&gt;What changes with AI is not the constraints. What changes is what you can build within them. The intelligence that was missing from the pipe is now available. The question is whether the pipe is ready for it. Andreessen Horowitz put it plainly in their &lt;a href=&quot;https://a16z.com/ai-voice-agents-2025-update/&quot;&gt;January 2025 voice AI update&lt;/a&gt;: we are just now transitioning from the infrastructure to the application layer of AI voice. As models improve, voice becomes the wedge — not the product. The infrastructure chapter is closing. The application chapter hasn’t been written yet. That is both the honest state of the market and the clearest signal of where the real work lies.&lt;/p&gt;

&lt;p&gt;In most cases, it isn’t yet.&lt;/p&gt;

&lt;p&gt;That is not a pessimistic conclusion. It is an honest one — and honesty is more useful than optimistic when you are deciding where to build. The gap between where telephony infrastructure is today and where it needs to be for AI-native products to fully deliver is a real gap. It is also a specific, well-defined gap. The people who understand it — who have spent time inside the infrastructure and can see exactly what needs to change — are the ones best positioned to close it.&lt;/p&gt;

&lt;h2 id=&quot;if-i-were-building-this-from-scratch&quot;&gt;If I Were Building This From Scratch&lt;/h2&gt;

&lt;p&gt;If I were building a telephony company today with no legacy constraints — no existing customers, no inherited architecture, no accumulated habit — the first thing I would do is separate the things worth keeping from the things that only feel necessary because they have always been there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I would keep.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A deep respect for the physical layer. Not as a constraint to route around, but as a reality to design with. The PRI intuition — the understanding that somewhere beneath every abstraction there is a telco, a circuit, and a limit — stays relevant even after SIP replaced PRI as the dominant protocol. The ceiling moved. It did not disappear.&lt;/p&gt;

&lt;p&gt;The obsession with observability at the call level, not the service level. Not “is the system up?” but “is this specific call, for this specific customer, producing the outcome it was supposed to produce, right now?” That granularity is hard to build and easy to skip. It is also the thing that separates platforms that work in demos from platforms that work in production.&lt;/p&gt;

&lt;p&gt;The institutional knowledge of what breaks at scale. Every failure mode that Exotel or its generation hit — the noisy neighbour problem, the campaign overload, the regional network outage, the carrier dispute — is a wall that every new entrant will eventually hit too. The companies that have already hit these walls and rebuilt around them have a knowledge asset that cannot be purchased and can barely be documented. It lives in the people who were there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I would throw out.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The assumption that a call is a discrete event — a thing that starts, happens, and ends, and whose value is measured in minutes.&lt;/p&gt;

&lt;p&gt;This is the assumption that shaped every data model, every API, every reporting layer, every pricing structure in the legacy telephony world. It was a reasonable assumption when the call was the product. It is the wrong assumption when the outcome is the product.&lt;/p&gt;

&lt;p&gt;An AI-native telephony system does not manage calls. It manages context. The call is one moment in a continuous relationship between a business and a customer — a moment that the system understands, learns from, and acts on, before and after the audio starts and stops. The system knows what happened on the last three calls. It knows what was promised. It knows what the customer’s tone suggested even when their words said something different.&lt;/p&gt;

&lt;p&gt;The call is not the product. What the call produces — the understanding, the commitment, the signal — is the product.
That distinction changes everything downstream. The data model changes. The integration points change. The pricing model changes. The metric you optimise for changes. Everything that was built to manage calls as discrete events has to be rethought for a system that manages context as a continuous stream.&lt;/p&gt;

&lt;p&gt;Most of what exists today is built on the old assumption. That is the gap. That is also the opportunity.&lt;/p&gt;

&lt;h2 id=&quot;the-question-this-piece-hasnt-answered&quot;&gt;The Question This Piece Hasn’t Answered&lt;/h2&gt;

&lt;p&gt;There is a question that runs underneath everything in this post that I have not addressed directly. Not because I forgot it. Because the honest answer is incomplete and I am not comfortable pretending otherwise.&lt;/p&gt;

&lt;p&gt;When AI handles the call — when the collections agent, the outbound sales rep, the customer support person is replaced by a system that does their job better, faster, and at a fraction of the cost — what happens to them?&lt;/p&gt;

&lt;p&gt;The optimistic version is familiar. Humans move up the stack. The agent becomes a supervisor. The supervisor becomes a trainer. The trainer becomes a designer of the systems that replaced them. Each displacement creates a higher-order role. The workforce adapts. New jobs emerge that we cannot yet name.&lt;/p&gt;

&lt;p&gt;That transition is real. I have seen it in organisations that have deployed AI in their voice operations thoughtfully, with investment in retraining and a genuine commitment to the people affected. It is not a fiction.&lt;/p&gt;

&lt;p&gt;But it is not universal. And the timeline is shorter than most policy discussions acknowledge.&lt;/p&gt;

&lt;p&gt;The more honest version is that a significant number of people whose livelihoods depend on call volume — the collections agent in Pune, the outbound sales rep in Hyderabad, the customer support person in Chennai — are going to find that livelihood compressed. Not eliminated overnight. Compressed gradually, then suddenly, in the way that these things always happen. The optimistic narrative about moving up the stack assumes that every displaced worker has the access, the support, and the economic runway to make that transition. Many do not.&lt;/p&gt;

&lt;p&gt;I raise this not to resolve it. I can’t. No single post can, and anyone who tells you they have a clean answer to technological displacement at scale is selling something.&lt;/p&gt;

&lt;p&gt;I raise it because a piece about AI-native telephony that describes the infrastructure problems, the market opportunity, the pricing disruption, and the competitive dynamics — without naming what happens to the people who currently do this work — is a piece that is only telling half the story.&lt;/p&gt;

&lt;p&gt;The half that is easier to tell. The half that the people writing about AI tend to tell.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;The vision has existed for years. The engine is finally here. The infrastructure is catching up.&lt;/p&gt;

&lt;p&gt;We are earlier than everyone thinks. But we are asking the right questions.&lt;/p&gt;

&lt;p&gt;And the right questions include this one.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;This is the first post in a series on AI-native thinking across software segments — telephony, workflow automation, dev tools, CRM, and more. Each post is built from direct experience and conversations with people who were inside these systems. A consolidated piece follows once the series is complete.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Next in the series: workflow automation — and the ceiling that trigger-action logic always hits.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you’ve built in the telephony or voice AI space and have a perspective on where the infrastructure gaps are sharpest — I’d like to hear from you.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Sun, 22 Mar 2026 10:42:00 +0000</pubDate>
        <link>https://codingdash.com/post/state-of-ai-native-telephony/</link>
        <guid isPermaLink="true">https://codingdash.com/post/state-of-ai-native-telephony/</guid>
        
        <category>AI</category>
        
        <category>Telephony</category>
        
        <category>Infrastructure</category>
        
        <category>Voice AI</category>
        
        <category>CPaaS</category>
        
        <category>State of AI Native</category>
        
        
        <category>posts</category>
        
      </item>
    
      <item>
        <title>A New Coat of Paint</title>
        <description>&lt;p&gt;If you’ve been here before, something looks different. The site has been redesigned quietly, without fanfare, the way most things I care about get done.&lt;/p&gt;

&lt;p&gt;I want to be clear about what this post is not: it’s not a deep dive into design theory, or a tutorial on how I built it. It’s just a note. The kind you’d leave on a door after rearranging the furniture; not because the furniture is the point, but because you’d notice if I didn’t mention it.&lt;/p&gt;

&lt;p&gt;This blog started in 2014. It’s run on the same Jekyll setup, the same GitLab Pages deployment, and more or less the same visual bones for years. I’ve written about &lt;a href=&quot;/post/scaling-serverless-beyond-the-first-function/&quot;&gt;serverless systems&lt;/a&gt;, &lt;a href=&quot;https://www.google.co.in/search?q=DevOps+site:codingdash.com&quot;&gt;DevOps&lt;/a&gt;, &lt;a href=&quot;/bookshelf/&quot;&gt;books I’ve read&lt;/a&gt;, and the occasional thing that’s hard to categorize. The design has always been secondary to the writing. But secondary doesn’t mean unimportant. A design that gets in the way is a design that costs you a reader.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The old design worked. This one works better. That’s the entire case for doing it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;what-actually-changed&quot;&gt;What actually changed&lt;/h3&gt;

&lt;div class=&quot;collage-container&quot;&gt;
  &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/03/21/redesign-2026-03.jpeg&quot; alt=&quot;Design Changes&quot; /&gt;
  &lt;div class=&quot;caption&quot;&gt;
    Before and after, at a glance.
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Here’s a plain summary. Not exhaustive — there were a lot of small decisions — but the things you’ll actually notice:&lt;/p&gt;

&lt;h4 id=&quot;homepage&quot;&gt;Homepage&lt;/h4&gt;

&lt;p&gt;The homepage now has a proper hero — name, photo, a one-line description, and two buttons. Before it was just a paragraph and then posts. The post sections are now two columns: Latest and Popular, side by side.&lt;/p&gt;

&lt;div class=&quot;collage-container&quot;&gt;
  &lt;div class=&quot;collage&quot;&gt;
    &lt;div class=&quot;collage-item&quot;&gt;
      &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/03/21/old-home.jpeg&quot; alt=&quot;&quot; /&gt;
    &lt;/div&gt;
    &lt;div class=&quot;collage-item&quot;&gt;
      &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/03/21/new-home.jpeg&quot; alt=&quot;&quot; /&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;caption&quot;&gt;
    Homepage, before and after.
  &lt;/div&gt;
&lt;/div&gt;

&lt;h4 id=&quot;posts-listing&quot;&gt;Posts listing&lt;/h4&gt;

&lt;p&gt;The old layout showed each post with a large image above the title, which looked fine for posts with well-composed cover images and awkward for everything else. The new layout is a compact horizontal row — thumbnail, title, excerpt, metadata. Cleaner, faster to scan.&lt;/p&gt;

&lt;div class=&quot;collage-container&quot;&gt;
  &lt;div class=&quot;collage&quot;&gt;
    &lt;div class=&quot;collage-item&quot;&gt;
      &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/03/21/old-posts.jpeg&quot; alt=&quot;&quot; /&gt;
    &lt;/div&gt;
    &lt;div class=&quot;collage-item&quot;&gt;
      &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/03/21/new-posts.jpeg&quot; alt=&quot;&quot; /&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;caption&quot;&gt;
    Posts listing, before and after.
  &lt;/div&gt;
&lt;/div&gt;

&lt;h4 id=&quot;reading-experience&quot;&gt;Reading experience&lt;/h4&gt;

&lt;p&gt;Post body text is now serif. This was a deliberate choice — long-form reading is more comfortable in a serif. The subtitle is now styled as a deck, separated from the body, the way a magazine would present it.&lt;/p&gt;

&lt;div class=&quot;collage-container&quot;&gt;
  &lt;div class=&quot;collage&quot;&gt;
    &lt;div class=&quot;collage-item&quot;&gt;
      &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/03/21/old-post-detail.jpeg&quot; alt=&quot;&quot; /&gt;
    &lt;/div&gt;
    &lt;div class=&quot;collage-item&quot;&gt;
      &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/03/21/new-post-detail.jpeg&quot; alt=&quot;&quot; /&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;caption&quot;&gt;
    Post view, before and after.
  &lt;/div&gt;
&lt;/div&gt;

&lt;h4 id=&quot;code-blocks&quot;&gt;Code blocks&lt;/h4&gt;

&lt;p&gt;Traffic light dots. A copy button. A slightly darker, cleaner background. Small things, but this blog has a lot of code in it, and small things compound.&lt;/p&gt;

&lt;div class=&quot;collage-container&quot;&gt;
  &lt;div class=&quot;collage&quot;&gt;
    &lt;div class=&quot;collage-item&quot;&gt;
      &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/03/21/old-post-code-block.jpeg&quot; alt=&quot;&quot; /&gt;
    &lt;/div&gt;
    &lt;div class=&quot;collage-item&quot;&gt;
      &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/03/21/new-post-code-block.jpeg&quot; alt=&quot;&quot; /&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;caption&quot;&gt;
    Code blocks, before and after.
  &lt;/div&gt;
&lt;/div&gt;

&lt;h4 id=&quot;bookshelf&quot;&gt;Bookshelf&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;/bookshelf/&quot;&gt;The Bookshelf&lt;/a&gt; page is not new in content — I’ve kept a reading list for a while — but it’s been properly designed now, with a recommendations section, a reading tracker, and a library stamp that I’m unreasonably pleased with.&lt;/p&gt;

&lt;div class=&quot;collage-container&quot;&gt;
  &lt;div class=&quot;collage&quot;&gt;
    &lt;div class=&quot;collage-item&quot;&gt;
      &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/03/21/old-bookshelf.jpeg&quot; alt=&quot;&quot; /&gt;
    &lt;/div&gt;
    &lt;div class=&quot;collage-item&quot;&gt;
      &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/03/21/new-bookshelf.jpeg&quot; alt=&quot;&quot; /&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;caption&quot;&gt;
    Bookshelf, before and after.
  &lt;/div&gt;
&lt;/div&gt;

&lt;h4 id=&quot;about-page&quot;&gt;About page&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;/about/#timeline&quot;&gt;The About&lt;/a&gt; page now has a proper work timeline — a chronological record of the roles and companies I’ve been part of, with brief descriptions and photos. It was always a gap on the old site. A blog that’s been running for over a decade should probably say more about who’s writing it.&lt;/p&gt;

&lt;h4 id=&quot;dark-mode&quot;&gt;Dark mode&lt;/h4&gt;

&lt;p&gt;Brand new. Toggle it with the moon icon in the nav. It was the most requested thing I got via DMs and honestly, I should have done it sooner.&lt;/p&gt;

&lt;div class=&quot;collage-container&quot;&gt;
  &lt;div class=&quot;collage&quot;&gt;
    &lt;div class=&quot;collage-item&quot;&gt;
      &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/03/21/old-home.jpeg&quot; alt=&quot;&quot; /&gt;
    &lt;/div&gt;
    &lt;div class=&quot;collage-item&quot;&gt;
      &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/03/21/new-dark-mode.jpeg&quot; alt=&quot;&quot; /&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;caption&quot;&gt;
    The new dark mode, alongside the old design.
  &lt;/div&gt;
&lt;/div&gt;

&lt;h3 id=&quot;what-didnt-change&quot;&gt;What didn’t change&lt;/h3&gt;

&lt;p&gt;The writing. The topics. The fact that posts come out when they’re ready and not before. The RSS feed still works. The URLs are the same, I’ve always been careful about that. If you linked to something here, it still resolves.&lt;/p&gt;

&lt;p&gt;The tech stack is the same too: Jekyll, GitLab Pages, Cloudinary for images. I considered a migration more than once over the years. Each time I concluded that the stack isn’t the constraint — the time to write is.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I’d been accumulating a list of friction points, small things that made the site feel dated, things I’d notice when I published a post and saw it live. The list got long enough that it made sense to address it all at once rather than piecemeal.&lt;/p&gt;

&lt;p&gt;I also wanted the site to reflect where my work is now. The old design was built for a different stage of things. This one fits better.&lt;/p&gt;

&lt;p&gt;If something looks broken, or if you’re reading this in an RSS reader and things look strange, let me know on &lt;a href=&quot;https://x.com/codingdash&quot;&gt;X&lt;/a&gt;. Otherwise, there’s a lot more writing to get back to.&lt;/p&gt;
</description>
        <pubDate>Fri, 20 Mar 2026 18:42:00 +0000</pubDate>
        <link>https://codingdash.com/post/a-new-coat-of-paint/</link>
        <guid isPermaLink="true">https://codingdash.com/post/a-new-coat-of-paint/</guid>
        
        <category>Site</category>
        
        <category>Design</category>
        
        
        <category>posts</category>
        
      </item>
    
      <item>
        <title>PRLens: Open Source AI Code Review You Actually Control</title>
        <description>&lt;p&gt;I’ve spent a fair amount of time looking for the right AI code review tool — tried a few, experimented seriously with some. None of them felt complete.&lt;/p&gt;

&lt;p&gt;The problems were consistent. Hidden costs with no separation between LLM token spend and platform cost. Shallow control — you could tweak things at the edges but never quite shape the tool to how your team actually works. I wanted granular guidelines, reviewer profiles that could approve without needing write access, comments that matched the code structure and culture of the team. Small things individually, but together they add up to a tool that’s always slightly off.&lt;/p&gt;

&lt;p&gt;The more time I spent with these tools, the more one thing became clear: the purpose of a code review tool is to serve the developers and teams using it, and that means they should own it. Not a vendor. Not a black box running on infrastructure you can’t inspect or control.&lt;/p&gt;

&lt;p&gt;That’s what PRLens is built around.&lt;/p&gt;

&lt;h3 id=&quot;the-harder-problem-the-review-isnt-yours-to-control&quot;&gt;The Harder Problem: The Review Isn’t Yours to Control&lt;/h3&gt;

&lt;p&gt;When you install a tool like CodeRabbit, you’re installing a GitHub App. Their servers receive the webhook. Their infrastructure runs the review. Their configuration determines what happens. You see the output — the comments on your PR — and nothing else.&lt;/p&gt;

&lt;p&gt;That’s fine when everything works. It becomes a problem the moment you want to change anything.&lt;/p&gt;

&lt;p&gt;A developer wants to understand why a specific comment was posted — what context the model saw, what prompt was used. There’s nowhere to look. A team wants to run the review locally before it posts publicly to a PR, to check whether it’s calibrated correctly for their codebase. There’s no mechanism for that. An engineer wants to scope the review to specific files, skip it for a hotfix branch, or tune the behaviour for a particular type of change. They can adjust a YAML config within the limits the vendor has chosen to expose — and that’s the ceiling.&lt;/p&gt;

&lt;p&gt;The tool is not yours. It runs adjacent to your workflow, on infrastructure you don’t control, with behaviour you can observe but not meaningfully inspect or modify.&lt;/p&gt;

&lt;p&gt;This matters beyond the obvious operational risks — the quota exhaustion, the opaque failures. It matters because code review is where your team’s standards live. The judgements being made about your code, the guidelines being applied, the patterns being flagged or ignored — these should sit under the same level of control as the rest of your engineering stack. Not delegated to a vendor’s black box.&lt;/p&gt;

&lt;p&gt;The context problem is downstream of this. A diff reviewed without knowledge of the surrounding codebase produces generic, low-signal output: style suggestions, naming conventions, comments that would be reasonable on code the model had never seen before. But even if you recognise this and want to fix it — by injecting co-change history, by supplying architectural context, by tuning what the model sees — you can’t. The context pipeline isn’t yours to change.&lt;/p&gt;

&lt;h3 id=&quot;the-problem-everyone-talks-about-the-price&quot;&gt;The Problem Everyone Talks About: The Price&lt;/h3&gt;

&lt;p&gt;If your team is already paying for Claude or GPT-4o through your IDE, your enterprise agreement, or your internal tooling, you are paying again when a tool like CodeRabbit uses a model to review your PRs and buries that cost inside a per-seat subscription you didn’t negotiate. I’ve written about this pattern in detail &lt;a href=&quot;/post/your-ai-products-real-price-tag-is-hidden/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We had an Anthropic enterprise agreement already in place. We couldn’t route the reviewer through it. The tool uses its own API access, not yours — and there is no configuration that changes this. So we were paying for Claude twice: once through our agreement, and again implicitly through the per-seat subscription.&lt;/p&gt;

&lt;p&gt;Then the reviews stopped. The tool had exhausted its own quota. We had no lever to pull — no way to increase limits, no way to fall back to our own account, no visibility into what had happened. We found out because a developer noticed the bot had gone quiet. That’s not an edge case. That’s what full dependency on someone else’s infrastructure looks like in practice.&lt;/p&gt;

&lt;p&gt;At $24 per seat per month (Pro tier) with unlimited reviews, the math is doing a lot of quiet work. One developer pushing 20 PRs a day on a monorepo and another pushing 2 PRs a week on a microservice generate wildly different inference costs. The tool absorbs both into the same number, and you have no visibility into which provider ran, which model version, or how much of your subscription was product value versus token pass-through.&lt;/p&gt;

&lt;p&gt;As inference costs keep dropping — and they will; the &lt;a href=&quot;https://epoch.ai/data-insights/llm-inference-price-trends&quot;&gt;Epoch AI data shows a median decline of 50x per year&lt;/a&gt; — that opacity becomes a bigger problem, not a smaller one. Teams who’ve standardised on a provider, who’ve negotiated enterprise rates, who have compliance requirements around which models touch their code — they need to be able to see and control this layer. A bundled subscription can’t give them that.&lt;/p&gt;

&lt;h3 id=&quot;what-i-built-prlens&quot;&gt;What I Built: PRLens&lt;/h3&gt;

&lt;p&gt;I built &lt;a href=&quot;https://github.com/prlens/prlens&quot;&gt;PRLens&lt;/a&gt; — an open source AI PR reviewer built on few principles that address both problems directly.&lt;/p&gt;

&lt;figure class=&quot;photostream double&quot;&gt;
    &lt;div class=&quot;image thumbnail&quot;&gt;
        &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/02/23/prlens.png&quot; class=&quot;img-responsive&quot; alt=&quot;PRLens&quot; /&gt;
    &lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Bring your own model.&lt;/strong&gt; Your API key, your provider, your rate card. The tool works with Anthropic Claude or OpenAI GPT-4o, and the choice is a single config line. If your team is already paying for Claude, PRLens costs you nothing extra for the intelligence layer. The inference spend goes through your account, shows up in your usage dashboards, and sits under your data policies.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;prlens[anthropic]&apos;&lt;/span&gt;
prlens init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prlens init&lt;/code&gt; handles the rest — creates the config, sets up team review history, generates the GitHub Actions workflow. No new vendor relationship required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Actual codebase context, not just the diff.&lt;/strong&gt; For every changed file, PRLens injects three signals into the review that a diff alone can’t provide:&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;repository file tree&lt;/strong&gt; at the PR’s exact head commit, so the model can reason about architecture layers, test coverage, and whether the changed code has a corresponding test file that wasn’t updated. The &lt;strong&gt;co-change history&lt;/strong&gt; from git, which surfaces files that tend to move together — a proxy for architectural coupling that doesn’t show up in import statements. And the &lt;strong&gt;paired test file&lt;/strong&gt; matched by filename pattern, giving the model context on what’s already covered versus what’s newly exposed.&lt;/p&gt;

&lt;p&gt;This isn’t perfect. It’s not the same as a senior engineer who’s been in the codebase for two years. But it’s substantially better than reviewing a patch in isolation — and it’s the foundation for something much more capable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your guidelines, not a generic ruleset.&lt;/strong&gt; The review instructions come from a Markdown file you write and own, checked into your repo. Your architecture decisions, your domain conventions, the things your team cares about that no generic style guide will ever capture.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# .prlens.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;anthropic&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;guidelines&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./docs/guidelines.md&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Incremental reviews.&lt;/strong&gt; PRLens tracks what it’s already reviewed and only re-reviews files changed since the last run. If you push a fix commit, it reviews the fix — not the whole PR again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shadow mode for building trust.&lt;/strong&gt; The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--shadow&lt;/code&gt; flag runs a full review and prints every comment to your terminal without posting anything to GitHub. It’s the right way to onboard a sceptical team before the AI starts talking on their PRs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No external service dependency.&lt;/strong&gt; Tools like CodeRabbit install as a GitHub App — their servers receive your webhook, run the review, and post the result. When their service is degraded, your reviews stop. PRLens runs as a plain GitHub Action: it’s code that lives in your repo, executes in your CI pipeline, and has no runtime dependency on any third-party service. If it breaks, you can see why and fix it yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero-infrastructure team history.&lt;/strong&gt; Review history is stored in a private GitHub Gist by default. No server to run, no database to maintain. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prlens init&lt;/code&gt; provisions it automatically. Every developer on the team can access shared history with no extra credentials — just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh auth login&lt;/code&gt;. (If your team has data residency requirements, the SQLite backend keeps everything local.)&lt;/p&gt;

&lt;h3 id=&quot;where-this-needs-to-go&quot;&gt;Where This Needs to Go&lt;/h3&gt;

&lt;p&gt;PRLens has a solid foundation, but the gap between what it is today and what it should eventually be is real — and here’s where I think the most important work lies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Language-aware context.&lt;/strong&gt; Right now context signals come from git history and filename patterns, which is language-agnostic but coarse. The right next layer is symbol-aware context: when a function changes, fetch its callers. When a class is modified, surface its implementors. This requires tree-sitter parsing for each language and is a meaningful engineering investment — but it’s the difference between contextual and truly intelligent reviews.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learned team patterns.&lt;/strong&gt; The history store records what was reviewed. Using that history to improve future reviews — stopping repeated false positives, recognising reappeared anti-patterns, flagging files with a history of bugs — is the feature that makes the tool genuinely smarter over time rather than just consistent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-click fix suggestions.&lt;/strong&gt; GitHub’s review API supports suggestion blocks that authors can apply directly. Converting comment suggestions into actionable GitHub suggestions would dramatically improve the rate at which comments get acted on — which is the only thing that ultimately matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More providers and models.&lt;/strong&gt; Mistral, Gemini, local models via Ollama for teams with strict data policies. The provider abstraction is already there. It just needs implementations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better coverage for non-Python codebases.&lt;/strong&gt; The core logic is language-agnostic, but the testing and tuning has been heavier on Python. Contributors who work primarily in Go, TypeScript, or Rust would bring both coverage and domain intuition that would improve the tool for those communities.&lt;/p&gt;

&lt;h3 id=&quot;an-invitation&quot;&gt;An Invitation&lt;/h3&gt;

&lt;p&gt;I open sourced PRLens because a tool that sits this close to your code and your team’s standards shouldn’t be a black box — and because the best version of it will come from developers who’ve felt the same friction and have ideas about how to fix it.&lt;/p&gt;

&lt;p&gt;The repo is at &lt;a href=&quot;https://github.com/prlens/prlens&quot;&gt;github.com/prlens/prlens&lt;/a&gt;. It’s MIT licensed, has a proper &lt;a href=&quot;https://github.com/prlens/prlens/blob/main/CONTRIBUTING.md&quot;&gt;CONTRIBUTING.md&lt;/a&gt;, and the issues list has specific areas where contributions would make the biggest difference.&lt;/p&gt;

&lt;p&gt;If you want to try it: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pip install &apos;prlens[anthropic]&apos;&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prlens init&lt;/code&gt; gets you running in under a minute.&lt;/p&gt;

&lt;p&gt;The goal is a code review tool that evolves the way good developer tools do: through the collective experience of the developers who use it, shaping it toward the thing they actually need.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;Open source. MIT licensed. Built for developers who want to own their review stack.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Sun, 22 Feb 2026 18:42:00 +0000</pubDate>
        <link>https://codingdash.com/post/prlens-ai-code-review/</link>
        <guid isPermaLink="true">https://codingdash.com/post/prlens-ai-code-review/</guid>
        
        <category>AI</category>
        
        <category>Open Source</category>
        
        <category>Code Review</category>
        
        <category>Developer Tools</category>
        
        <category>GitHub</category>
        
        <category>LLM Economics</category>
        
        <category>BYOK</category>
        
        <category>Developer Productivity</category>
        
        
        <category>posts</category>
        
      </item>
    
      <item>
        <title>Your AI Product&apos;s Real Price Tag is Hidden</title>
        <description>&lt;p&gt;Here’s a question every builder shipping an AI-powered product should be able to answer: of the price your customer pays, how much is for your product and how much is for the model inference running underneath it?&lt;/p&gt;

&lt;p&gt;If you can’t answer that cleanly, you have a pricing architecture problem. And it’s one that will get worse, not better, with time.&lt;/p&gt;

&lt;figure class=&quot;photostream double&quot;&gt;
    &lt;div class=&quot;image thumbnail&quot;&gt;
        &lt;img src=&quot;https://res.cloudinary.com/codingdash/image/upload/v1703359973/files.codingdash.com/resources/2026/02/21/ai-product-pricing.png&quot; class=&quot;img-responsive&quot; alt=&quot;AI Product Pricing&quot; /&gt;
    &lt;/div&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;why-bundling-became-the-default&quot;&gt;Why Bundling Became the Default&lt;/h3&gt;

&lt;p&gt;The instinct to bundle makes sense. Traditional SaaS trained us this way. You absorb infrastructure costs into the subscription, present one clean price, and let unit economics work themselves out at scale. Nobody charged customers separately for their AWS bill.&lt;/p&gt;

&lt;p&gt;Applied to AI products, this translates into a familiar pattern. &lt;a href=&quot;https://www.coderabbit.ai/pricing&quot;&gt;CodeRabbit&lt;/a&gt;, the AI code review tool (&lt;a href=&quot;https://sacra.com/c/coderabbit/&quot;&gt;raised $60M Series B in 2025, ~$550M valuation&lt;/a&gt;, reviews across &lt;a href=&quot;https://mstone.ai/tools-wizard/coderabbit/&quot;&gt;2M+ repositories&lt;/a&gt;), charges $12 to $24 per seat per month with unlimited pull request reviews. Within that per-seat price, the product is burning tokens: sending diffs to model providers, running multi-pass analysis, generating suggestions, opening Jira tickets. All of it folded into one number.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://emergent.sh/pricing&quot;&gt;Emergent&lt;/a&gt;, the AI app builder, takes a slightly different approach with credits. Their Standard plan ($20/month) gives you 100 credits, Pro ($200/month) gives 750. Every AI action consumes credits based on task complexity. It makes the cost more visible than a flat subscription, but the credits are still an internal currency. The customer can’t see which model is running underneath, can’t choose a cheaper provider for less critical tasks, and can’t tell what percentage of their $20 is product value versus inference pass-through.&lt;/p&gt;

&lt;p&gt;Both approaches share the same underlying assumption: AI inference is just another infrastructure cost to be absorbed. In 2023, that assumption was defensible. In 2026, it’s becoming a liability. Here’s why.&lt;/p&gt;

&lt;h3 id=&quot;the-cost-base-has-changed-fundamentally&quot;&gt;The Cost Base Has Changed Fundamentally&lt;/h3&gt;

&lt;p&gt;Traditional infrastructure costs are stable and predictable. Your compute, storage, and bandwidth costs don’t swing 10x in a year. You can model them, absorb them, and move on.&lt;/p&gt;

&lt;p&gt;AI model costs behave nothing like this.&lt;/p&gt;

&lt;p&gt;According to &lt;a href=&quot;https://epoch.ai/data-insights/llm-inference-price-trends&quot;&gt;Epoch AI’s research&lt;/a&gt;, LLM inference prices have been declining at a median rate of &lt;strong&gt;50x per year&lt;/strong&gt;, with the fastest drops (post January 2024) accelerating to &lt;strong&gt;200x per year&lt;/strong&gt;. &lt;a href=&quot;https://aiindex.stanford.edu/report/&quot;&gt;Stanford’s AI Index&lt;/a&gt; quantified this more concretely: achieving GPT-3.5 level performance became &lt;strong&gt;280x cheaper&lt;/strong&gt; between November 2022 and October 2024. The cost of processing a million tokens &lt;a href=&quot;https://sumatosoft.com/blog/ai-development-costs&quot;&gt;dropped from roughly $12 to under $2&lt;/a&gt; in that same window, and continues to fall. &lt;a href=&quot;https://intuitionlabs.ai/articles/llm-api-pricing-comparison-2025&quot;&gt;DeepSeek’s latest models&lt;/a&gt; now process a million tokens of input and output combined for about $0.70.&lt;/p&gt;

&lt;p&gt;When your cost base can drop by an order of magnitude in a single year, folding it into a fixed subscription creates a structural misalignment. If you priced your product assuming $5 per million tokens and that drops to $0.50, you’re either pocketing a windfall your customers will eventually notice, or a competitor who architected for transparency will undercut you while maintaining healthy margins on their actual product value.&lt;/p&gt;

&lt;p&gt;This is not how traditional infra costs behaved. AWS prices declined gradually and predictably. AI model pricing is in a competitive freefall driven by new entrants, open-source alternatives, and hardware improvements happening simultaneously.&lt;/p&gt;

&lt;h3 id=&quot;three-problems-that-compound&quot;&gt;Three Problems That Compound&lt;/h3&gt;

&lt;p&gt;Beyond the macro price decline, bundled pricing creates three specific problems that get worse at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Usage variance is enormous, and per-seat pricing ignores it.&lt;/strong&gt; On a tool like CodeRabbit, one developer pushing 20 PRs a day on a monorepo and another pushing 2 PRs a week on a microservice pay the same per-seat price while generating wildly different inference costs. In traditional SaaS, the marginal cost of serving one more user is near zero. In AI-powered products, every interaction has a real, variable cost. Per-seat pricing forces you to either over-charge light users or eat losses on heavy ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model superiority is not permanent, and bundling locks you in.&lt;/strong&gt; Anthropic might be the best provider for code analysis today. Tomorrow, a new entrant or a fine-tuned open-source model might deliver better results at a fraction of the cost. The &lt;a href=&quot;https://andreyfradkin.com/assets/LLM_Demand_12_12_2025.pdf&quot;&gt;number of LLM models available through inference providers grew from about 60 in early 2024 to over 650 by late 2025&lt;/a&gt;. When pricing is bundled, switching providers doesn’t translate into savings for customers. More importantly, you have no clean mechanism to let customers choose: the best model for production code reviews, a cheaper model for draft PRs, a local model for sensitive code. The bundled price hides all of this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The “wrapper tax” becomes visible as costs drop.&lt;/strong&gt; When inference was expensive, a product charging $24/seat while spending $15/seat on tokens could justify the margin through genuine product value. When that $15 drops to $2, the same $24 looks less like a product price and more like a markup. Customers are increasingly aware of this. The products that survive will be the ones where the product value is clearly distinguishable from the inference cost, not hidden behind the same number.&lt;/p&gt;

&lt;h3 id=&quot;the-customer-is-already-asking-for-separation&quot;&gt;The Customer is Already Asking for Separation&lt;/h3&gt;

&lt;p&gt;This isn’t a theoretical argument about pricing elegance. Customers, especially enterprise ones, are actively demanding it.&lt;/p&gt;

&lt;p&gt;The clearest signal is the rise of BYOK, Bring Your Own Key. &lt;a href=&quot;https://blog.jetbrains.com/ai/2025/12/bring-your-own-key-byok-is-now-live-in-jetbrains-ides/&quot;&gt;JetBrains shipped BYOK support&lt;/a&gt; for their AI Assistant and Junie agent in December 2025, letting developers plug in their own Anthropic, OpenAI, or compatible API keys directly into the IDE, no JetBrains AI subscription required. &lt;a href=&quot;https://code.visualstudio.com/blogs/2025/10/22/bring-your-own-key&quot;&gt;VS Code followed&lt;/a&gt; with its own BYOK framework through the Language Model Chat Provider API. &lt;a href=&quot;https://vercel.com/docs/ai-gateway/authentication-and-byok/byok&quot;&gt;Vercel’s AI Gateway&lt;/a&gt; and &lt;a href=&quot;https://developers.cloudflare.com/ai-gateway/configuration/bring-your-own-keys/&quot;&gt;Cloudflare’s AI Gateway&lt;/a&gt; both offer first-class BYOK with per-request key injection. There’s now a dedicated directory at &lt;a href=&quot;https://byoklist.com/&quot;&gt;byoklist.com&lt;/a&gt; just cataloging AI tools that support the pattern.&lt;/p&gt;

&lt;p&gt;The customer logic is straightforward. A team already paying for Anthropic’s API or sitting on negotiated Google Cloud credits doesn’t want to pay retail rates for the same tokens, marked up and laundered through every tool in their stack. They want their centralized AI spend to flow through their own accounts, with their own rate cards and their own usage dashboards.&lt;/p&gt;

&lt;p&gt;JetBrains understood something important when they launched BYOK: it’s a trust signal. It tells the customer that the IDE’s value stands on its own, independent of which model runs underneath. That’s a powerful competitive position as the market matures.&lt;/p&gt;

&lt;h3 id=&quot;compliance-will-force-the-issue&quot;&gt;Compliance Will Force the Issue&lt;/h3&gt;

&lt;p&gt;Even if market dynamics don’t force pricing separation, regulation will.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://secureprivacy.ai/blog/ai-risk-compliance-2026&quot;&gt;EU AI Act&lt;/a&gt; reached partial enforcement in February 2025, with full enforcement for high-risk AI systems coming in August 2026 and fines of up to €35 million or 7% of global turnover. In the US, &lt;a href=&quot;https://secureprivacy.ai/blog/ai-risk-compliance-2026&quot;&gt;California’s AB 2013&lt;/a&gt; (effective January 2026) mandates training data disclosure for generative AI, the &lt;a href=&quot;https://www.dpocentre.com/data-protection-ai-governance-2025-2026/&quot;&gt;Colorado AI Act&lt;/a&gt; takes effect in 2026, and &lt;a href=&quot;https://www.dpocentre.com/data-protection-ai-governance-2025-2026/&quot;&gt;over 1,000 AI-related bills were introduced across US states in 2025&lt;/a&gt;. &lt;a href=&quot;https://www.credo.ai/blog/latest-ai-regulations-update-what-enterprises-need-to-know&quot;&gt;Federal agencies introduced 59 AI-related regulations in 2024&lt;/a&gt;, more than double the previous year.&lt;/p&gt;

&lt;p&gt;The compliance requirements converge on one theme: transparency and traceability of AI systems. Enterprises will need to demonstrate which AI providers processed their data, maintain audit trails, and enforce provider-level policies based on data classification. The EU AI Act explicitly requires &lt;a href=&quot;https://sombrainc.com/blog/ai-regulations-2026-eu-ai-act&quot;&gt;full data lineage tracking&lt;/a&gt; for high-risk systems.&lt;/p&gt;

&lt;p&gt;Here’s the problem: when model usage is bundled into an opaque subscription, the customer has no visibility into any of this. They can’t tell their compliance team which provider touched their code. They can’t enforce a policy that says “no sending financial data to Provider X.” They can’t produce an audit trail of AI consumption for regulators.&lt;/p&gt;

&lt;p&gt;Despite &lt;a href=&quot;https://secureprivacy.ai/blog/ai-governance-framework-tools&quot;&gt;90% of enterprises now using AI in daily operations, only 18% have fully implemented governance frameworks&lt;/a&gt;. That gap is closing fast, and it will close faster once fines start landing. Products that separate the model layer, and especially those supporting BYOK, give customers the control they need: choose your provider, track your costs, maintain your compliance. It turns a black box into something governable.&lt;/p&gt;

&lt;h3 id=&quot;what-builders-should-actually-do&quot;&gt;What Builders Should Actually Do&lt;/h3&gt;

&lt;p&gt;Acknowledging the problem is easy. Here’s what acting on it looks like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Separate the invoice, not just the architecture.&lt;/strong&gt; Your customer should be able to see two things: what they’re paying for your product (the workflow, the UX, the integrations, the domain logic) and what they’re paying for AI inference. These can be two line items on the same invoice, or a product fee plus a metered consumption component. The point is visibility, not complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Support BYOK from day one if you can.&lt;/strong&gt; Let customers plug in their own API keys. Yes, this means your revenue per customer drops because you lose the inference margin. But it also means your product has to stand on genuine value, your sales cycle gets shorter (no procurement fights about hidden AI costs), and enterprise customers who already have negotiated rates with providers will prefer you over competitors who force their own markup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build for provider flexibility.&lt;/strong&gt; If your architecture is hardwired to a single model provider, you can’t offer customers choice and you can’t optimize costs as the market shifts. Abstract your model layer so you can swap providers, blend them, or let customers decide. This is good engineering regardless of pricing strategy, but it becomes essential when your pricing makes the model layer visible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let the credit model be transitional, not final.&lt;/strong&gt; If you’re already using credits (like &lt;a href=&quot;https://emergent.sh/pricing&quot;&gt;Emergent&lt;/a&gt; does), that’s a reasonable starting point. But treat it as a step toward full transparency, not the end state. The next move is making the credit-to-token relationship visible, then letting customers bring their own keys, then separating product pricing from consumption entirely.&lt;/p&gt;

&lt;h3 id=&quot;the-real-question&quot;&gt;The Real Question&lt;/h3&gt;

&lt;p&gt;By 2022, &lt;a href=&quot;https://www.getmonetizely.com/blogs/the-2026-guide-to-saas-ai-and-agentic-pricing-models&quot;&gt;61% of SaaS companies had adopted some form of usage-based pricing&lt;/a&gt;. Gartner projected that &lt;a href=&quot;https://www.getmonetizely.com/blogs/the-2026-guide-to-saas-ai-and-agentic-pricing-models&quot;&gt;over 30% of enterprise SaaS would incorporate outcome-based components by 2025&lt;/a&gt;. The broader market has already moved past flat bundled subscriptions. AI products, where the variable cost of serving each customer is higher and more volatile than any previous SaaS category, should be leading this transition, not clinging to the old model.&lt;/p&gt;

&lt;p&gt;The builders who internalize this distinction early, who architect their products, their pricing, and their value propositions around a clean separation between core product and model consumption, will have a structural advantage as model costs continue their downward trajectory and regulation tightens.&lt;/p&gt;

&lt;p&gt;The question that separates durable AI businesses from transient ones is simple: if the model layer became free tomorrow, would customers still pay for your product?&lt;/p&gt;

&lt;p&gt;If yes, decouple the burn and prove it. If not, that’s the real problem to solve.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot;&gt;

  &lt;ol&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Epoch AI&lt;/strong&gt; — “LLM inference prices have fallen rapidly but unequally across tasks” (March 2025). Median decline of 50x/year, up to 200x post-Jan 2024. &lt;a href=&quot;https://epoch.ai/data-insights/llm-inference-price-trends&quot;&gt;epoch.ai/data-insights/llm-inference-price-trends&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Stanford HAI&lt;/strong&gt; — AI Index Report. GPT-3.5 level performance became 280x cheaper between Nov 2022 and Oct 2024. &lt;a href=&quot;https://aiindex.stanford.edu/report/&quot;&gt;aiindex.stanford.edu/report&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Fradkin et al.&lt;/strong&gt; — “The Emerging Market for Intelligence: Pricing, Supply, and Demand for LLMs” (December 2025). LLM models grew from ~60 in early 2024 to 650+ by late 2025. &lt;a href=&quot;https://andreyfradkin.com/assets/LLM_Demand_12_12_2025.pdf&quot;&gt;andreyfradkin.com/assets/LLM_Demand_12_12_2025.pdf&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;IntuitionLabs&lt;/strong&gt; — “LLM API Pricing Comparison 2025.” DeepSeek pricing data, cross-provider cost analysis. &lt;a href=&quot;https://intuitionlabs.ai/articles/llm-api-pricing-comparison-2025&quot;&gt;intuitionlabs.ai/articles/llm-api-pricing-comparison-2025&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;SumatoSoft&lt;/strong&gt; — “What Affects AI Development Cost in 2026.” Token costs from ~$12 to under $2 per million (2022-2024). &lt;a href=&quot;https://sumatosoft.com/blog/ai-development-costs&quot;&gt;sumatosoft.com/blog/ai-development-costs&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Sacra&lt;/strong&gt; — “CodeRabbit valuation, funding &amp;amp; news.” $60M Series B, ~$550M valuation, seat-based pricing model. &lt;a href=&quot;https://sacra.com/c/coderabbit/&quot;&gt;sacra.com/c/coderabbit&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Emergent&lt;/strong&gt; — Official pricing page. Credit-based plans: Standard ($20/100 credits), Pro ($200/750 credits). &lt;a href=&quot;https://emergent.sh/pricing&quot;&gt;emergent.sh/pricing&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;JetBrains&lt;/strong&gt; — “Bring Your Own Key (BYOK) Is Now Live in JetBrains IDEs” (December 2025). &lt;a href=&quot;https://blog.jetbrains.com/ai/2025/12/bring-your-own-key-byok-is-now-live-in-jetbrains-ides/&quot;&gt;blog.jetbrains.com/ai/2025/12/bring-your-own-key-byok-is-now-live-in-jetbrains-ides&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;VS Code&lt;/strong&gt; — “Expanding Model Choice in VS Code with Bring Your Own Key” (October 2025). &lt;a href=&quot;https://code.visualstudio.com/blogs/2025/10/22/bring-your-own-key&quot;&gt;code.visualstudio.com/blogs/2025/10/22/bring-your-own-key&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Vercel&lt;/strong&gt; — AI Gateway BYOK documentation. &lt;a href=&quot;https://vercel.com/docs/ai-gateway/authentication-and-byok/byok&quot;&gt;vercel.com/docs/ai-gateway/authentication-and-byok/byok&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Cloudflare&lt;/strong&gt; — AI Gateway BYOK documentation. &lt;a href=&quot;https://developers.cloudflare.com/ai-gateway/configuration/bring-your-own-keys/&quot;&gt;developers.cloudflare.com/ai-gateway/configuration/bring-your-own-keys&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;BYOKList&lt;/strong&gt; — Directory of AI tools supporting Bring Your Own Key. &lt;a href=&quot;https://byoklist.com/&quot;&gt;byoklist.com&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Secure Privacy&lt;/strong&gt; — “AI Risk &amp;amp; Compliance 2026: Enterprise Governance Overview.” EU AI Act enforcement timeline, California AB 2013, fines. &lt;a href=&quot;https://secureprivacy.ai/blog/ai-risk-compliance-2026&quot;&gt;secureprivacy.ai/blog/ai-risk-compliance-2026&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Secure Privacy&lt;/strong&gt; — “AI Governance Framework Tools.” 90% enterprise AI adoption vs. 18% governance implementation. &lt;a href=&quot;https://secureprivacy.ai/blog/ai-governance-framework-tools&quot;&gt;secureprivacy.ai/blog/ai-governance-framework-tools&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;DPO Centre&lt;/strong&gt; — “Data protection &amp;amp; AI governance 2025-2026.” Colorado AI Act, 1,000+ US state AI bills. &lt;a href=&quot;https://www.dpocentre.com/data-protection-ai-governance-2025-2026/&quot;&gt;dpocentre.com/data-protection-ai-governance-2025-2026&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Credo AI&lt;/strong&gt; — “Latest AI Regulations Update: What Enterprises Need to Know in 2026.” 59 US federal AI regulations in 2024. &lt;a href=&quot;https://www.credo.ai/blog/latest-ai-regulations-update-what-enterprises-need-to-know&quot;&gt;credo.ai/blog/latest-ai-regulations-update-what-enterprises-need-to-know&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Sombra Inc.&lt;/strong&gt; — “An Ultimate Guide to AI Regulations and Governance in 2026.” EU AI Act data lineage requirements. &lt;a href=&quot;https://sombrainc.com/blog/ai-regulations-2026-eu-ai-act&quot;&gt;sombrainc.com/blog/ai-regulations-2026-eu-ai-act&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Monetizely&lt;/strong&gt; — “The 2026 Guide to SaaS, AI, and Agentic Pricing Models.” 61% usage-based adoption, Gartner outcome-based projections. &lt;a href=&quot;https://www.getmonetizely.com/blogs/the-2026-guide-to-saas-ai-and-agentic-pricing-models&quot;&gt;getmonetizely.com/blogs/the-2026-guide-to-saas-ai-and-agentic-pricing-models&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;

&lt;/div&gt;
</description>
        <pubDate>Fri, 20 Feb 2026 20:42:00 +0000</pubDate>
        <link>https://codingdash.com/post/your-ai-products-real-price-tag-is-hidden/</link>
        <guid isPermaLink="true">https://codingdash.com/post/your-ai-products-real-price-tag-is-hidden/</guid>
        
        <category>AI</category>
        
        <category>SaaS</category>
        
        <category>Pricing</category>
        
        <category>BYOK</category>
        
        <category>Product Strategy</category>
        
        <category>Startups</category>
        
        <category>Builder Playbook</category>
        
        <category>LLM Economics</category>
        
        <category>AI Compliance</category>
        
        
        <category>posts</category>
        
      </item>
    
  </channel>
</rss>
