<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dr. Agentic</title>
    <description>The latest articles on DEV Community by Dr. Agentic (@rcsxplatform).</description>
    <link>https://dev.to/rcsxplatform</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3838608%2Fd6e684bc-1463-46b3-9a41-eee309d68dd1.png</url>
      <title>DEV Community: Dr. Agentic</title>
      <link>https://dev.to/rcsxplatform</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rcsxplatform"/>
    <language>en</language>
    <item>
      <title>Time-Filtered Web Search: Stop Burning Tokens on Stale Results</title>
      <dc:creator>Dr. Agentic</dc:creator>
      <pubDate>Sun, 26 Jul 2026 01:59:31 +0000</pubDate>
      <link>https://dev.to/rcsxplatform/time-filtered-web-search-stop-burning-tokens-on-stale-results-44ic</link>
      <guid>https://dev.to/rcsxplatform/time-filtered-web-search-stop-burning-tokens-on-stale-results-44ic</guid>
      <description>&lt;h2&gt;
  
  
  The Problem Nobody Talks About: Your AI Agent Is Reading Last Year's News
&lt;/h2&gt;

&lt;p&gt;Every time an AI agent does research, it wastes tokens on stale results.&lt;/p&gt;

&lt;p&gt;This is not a hypothetical. A cron job running a daily brief at 7am using a standard web search is returning results that may be hours, days, or weeks old — mixed in with genuinely recent content. The agent reads all of it. You pay for all of it.&lt;/p&gt;

&lt;p&gt;The math is brutal.&lt;/p&gt;

&lt;p&gt;A typical context window costs money per token. A research query that returns 10 results might consume 2,000–5,000 tokens of context just to read titles, descriptions, and URLs. If half those results are outdated, you're burning tokens on content that has zero value to your agent's task.&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;token value problem&lt;/strong&gt; — and time filtering is the fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tool Nobody Built (Until Now)
&lt;/h2&gt;

&lt;p&gt;Search APIs that power AI agents — Tavily, Firecrawl, Exa, Brave — are powerful. But none of them expose a simple, reliable time filter at the tool schema level that agents can actually use.&lt;/p&gt;

&lt;p&gt;There are workarounds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google's &lt;code&gt;after:&lt;/code&gt; operator&lt;/strong&gt; — works sometimes, breaks often, requires the agent to know and remember the syntax&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DDG's &lt;code&gt;timelimit=&lt;/code&gt; parameter&lt;/strong&gt; — available directly in the URL, but not as a first-class tool parameter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend-specific date filtering&lt;/strong&gt; — requires knowing which backend supports what, and the interface is inconsistent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result: most agents search without time filters by default. Stale results are the norm, not the exception.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Plugin: &lt;code&gt;hermes-web-search-time-limit&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Built for &lt;a href="https://github.com/NousResearch/hermes-agent" rel="noopener noreferrer"&gt;Hermes Agent&lt;/a&gt;, this plugin adds &lt;code&gt;web_search_timed&lt;/code&gt; as a first-class tool — no backend configuration, no API keys, no vendor lock-in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;web_search_timed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;time_range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;d&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;y&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;time_range&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;What it returns&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Past 24 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;w&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Past week&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;m&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Past month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Past year&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Example prompts an agent can now follow directly:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;"Search for AI agent funding news from the past week using web_search_timed"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Research climate tech developments from the past month — use time-filtered search"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Find the latest developments in open-source LLMs — past day only"&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent sees the schema. The agent chooses the right tool. No workarounds needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Math: Why This Matters for Token Economics
&lt;/h2&gt;

&lt;p&gt;Let's make it concrete.&lt;/p&gt;

&lt;p&gt;A daily brief cron runs 365 times a year. Each run searches for 10 results with no time filter. Half the results are more than 30 days old. The agent spends ~500 tokens reading stale content per run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;365 runs × 500 stale tokens × $0.001 per 1K tokens (approximate)
= $182.50/year wasted on content that didn't matter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scale that to a team of 10 agents, each running multiple daily research tasks, and you're looking at real money — not because the agents are doing anything wrong, but because the search tool isn't giving them time awareness.&lt;/p&gt;

&lt;p&gt;Now do the same math with &lt;code&gt;time_range='w'&lt;/code&gt; on every run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Same usage, zero stale content
= $0 waste
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plugin costs nothing to run (it uses DuckDuckGo via the &lt;code&gt;ddgs&lt;/code&gt; Python package — no API key required). The token savings are entirely free.&lt;/p&gt;




&lt;h2&gt;
  
  
  Privacy and Cost: Two Reasons to Love DuckDuckGo
&lt;/h2&gt;

&lt;p&gt;This is not a paid promotion of DuckDuckGo. It's just the truth about what makes this stack work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Zero cost&lt;/strong&gt;&lt;br&gt;
DDG has no paid API tier. No rate limits that require a credit card. No tiered plans. The &lt;code&gt;ddgs&lt;/code&gt; Python package is free and open source. The plugin is free and open source. You run it on your own infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Privacy by default&lt;/strong&gt;&lt;br&gt;
DuckDuckGo doesn't track your searches. Doesn't build user profiles. Doesn't sell data. When your agent is doing research on a competitor, a medical topic, or a sensitive business question — you don't want that query logged by your search provider. DDG doesn't log it.&lt;/p&gt;

&lt;p&gt;Compare this to search backends that require API keys, track usage, and build behavioral profiles. For agent workloads, DDG is the privacy-respecting choice that also happens to work without any credential setup.&lt;/p&gt;


&lt;h2&gt;
  
  
  Introducing &lt;code&gt;ddgs&lt;/code&gt;: The Library That Makes This Possible
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;ddgs&lt;/code&gt; Python package, maintained by &lt;a href="https://github.com/deedy5/ddgs" rel="noopener noreferrer"&gt;deedy5&lt;/a&gt; on GitHub, is a metasearch library that aggregates results from multiple search backends — including DuckDuckGo — without requiring an API key.&lt;/p&gt;

&lt;p&gt;It is not affiliated with DuckDuckGo. It is a community-built tool that uses DuckDuckGo's HTML interface directly, in the same way a browser does. This is why it works without keys and why it doesn't log your queries.&lt;/p&gt;

&lt;p&gt;Key properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No API key required&lt;/strong&gt; — works out of the box&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No query logging&lt;/strong&gt; — uses DDG's HTML interface, same privacy as the DDG search bar&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MIT licensed&lt;/strong&gt; — open source, community maintained&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active development&lt;/strong&gt; — regular releases, active GitHub issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The plugin wraps &lt;code&gt;ddgs&lt;/code&gt; with a Hermes-compatible provider interface (&lt;code&gt;WebSearchProvider&lt;/code&gt; ABC), so it slots into Hermes's plugin system cleanly. Other search backends that implement the same ABC can be swapped in without changing the tool schema.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Broader Principle: Token Value Maximizing
&lt;/h2&gt;

&lt;p&gt;The name &lt;code&gt;web_search_timed&lt;/code&gt; is descriptive. But the underlying principle is bigger.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token value maximizing&lt;/strong&gt; is a design philosophy for AI agent tools: every tool should maximize the ratio of useful output per token consumed.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search tools should filter by recency when the task is time-sensitive&lt;/li&gt;
&lt;li&gt;Summarization tools should prioritize recent sources over older ones&lt;/li&gt;
&lt;li&gt;Monitoring tools should alert on anomalies, not on unchanged baselines&lt;/li&gt;
&lt;li&gt;Routing tools should send requests to the cheapest capable model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Time filtering is the simplest, highest-leverage expression of this principle. It requires no model change, no infrastructure change, no cost negotiation. Just a parameter on a search call.&lt;/p&gt;

&lt;p&gt;If you're building agent tools and you're not thinking about token value, you're leaving money on the table — and your users are paying for content they don't need.&lt;/p&gt;


&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;The plugin is published at:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Dr-Agentic/hermes-web-search-time-limit" rel="noopener noreferrer"&gt;Dr-Agentic/hermes-web-search-time-limit&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Option 1: pip install (auto-discovers via entry point)&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;git+https://github.com/Dr-Agentic/hermes-web-search-time-limit.git

&lt;span class="c"&gt;# Option 2: clone locally&lt;/span&gt;
git clone https://github.com/Dr-Agentic/hermes-web-search-time-limit.git &lt;span class="se"&gt;\&lt;/span&gt;
  ~/.hermes/plugins/hermes-web-search-time-limit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart Hermes. The agent will see &lt;code&gt;web_search_timed&lt;/code&gt; alongside &lt;code&gt;web_search&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python ≥ 3.10&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ddgs&lt;/code&gt; (installed automatically with pip)&lt;/li&gt;
&lt;li&gt;Hermes Agent&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Ask
&lt;/h2&gt;

&lt;p&gt;If you build AI agents and you've been burning tokens on stale search results — you now have the fix. It's free, it's private, it's open source.&lt;/p&gt;

&lt;p&gt;And if you've been thinking about the token economics of your agent stack — this is a good moment to look at every tool your agent uses and ask: &lt;em&gt;is this tool maximizing the value of every token, or am I paying for waste I don't need to pay for?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ddgs&lt;/code&gt;-powered &lt;code&gt;hermes-web-search-time-limit&lt;/code&gt; plugin is one concrete answer to that question. What are yours?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tags: &lt;code&gt;ai&lt;/code&gt;, &lt;code&gt;agents&lt;/code&gt;, &lt;code&gt;hermes-agent&lt;/code&gt;, &lt;code&gt;python&lt;/code&gt;, &lt;code&gt;open-source&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Time-Filtered Web Search: How to Maximize Every Token in Your LLM Agent</title>
      <dc:creator>Dr. Agentic</dc:creator>
      <pubDate>Sun, 26 Jul 2026 01:59:16 +0000</pubDate>
      <link>https://dev.to/rcsxplatform/time-filtered-web-search-how-to-maximize-every-token-in-your-llm-agent-2118</link>
      <guid>https://dev.to/rcsxplatform/time-filtered-web-search-how-to-maximize-every-token-in-your-llm-agent-2118</guid>
      <description>&lt;h2&gt;
  
  
  The Problem Nobody Talks About: Your AI Agent Is Reading Last Year's News
&lt;/h2&gt;

&lt;p&gt;Every time an AI agent does research, it wastes tokens on stale results.&lt;/p&gt;

&lt;p&gt;This is not a hypothetical. A cron job running a daily brief at 7am using a standard web search is returning results that may be hours, days, or weeks old — mixed in with genuinely recent content. The agent reads all of it. You pay for all of it.&lt;/p&gt;

&lt;p&gt;The math is brutal.&lt;/p&gt;

&lt;p&gt;A typical context window costs money per token. A research query that returns 10 results might consume 2,000–5,000 tokens of context just to read titles, descriptions, and URLs. If half those results are outdated, you're burning tokens on content that has zero value to your agent's task.&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;token value problem&lt;/strong&gt; — and time filtering is the fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tool Nobody Built (Until Now)
&lt;/h2&gt;

&lt;p&gt;Search APIs that power AI agents — Tavily, Firecrawl, Exa, Brave — are powerful. But none of them expose a simple, reliable time filter at the tool schema level that agents can actually use.&lt;/p&gt;

&lt;p&gt;There are workarounds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google's &lt;code&gt;after:&lt;/code&gt; operator&lt;/strong&gt; — works sometimes, breaks often, requires the agent to know and remember the syntax&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DDG's &lt;code&gt;timelimit=&lt;/code&gt; parameter&lt;/strong&gt; — available directly in the URL, but not as a first-class tool parameter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend-specific date filtering&lt;/strong&gt; — requires knowing which backend supports what, and the interface is inconsistent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result: most agents search without time filters by default. Stale results are the norm, not the exception.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Plugin: &lt;code&gt;hermes-web-search-time-limit&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Built for &lt;a href="https://github.com/NousResearch/hermes-agent" rel="noopener noreferrer"&gt;Hermes Agent&lt;/a&gt;, this plugin adds &lt;code&gt;web_search_timed&lt;/code&gt; as a first-class tool — no backend configuration, no API keys, no vendor lock-in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;web_search_timed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;time_range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;d&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;y&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;time_range&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;What it returns&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Past 24 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;w&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Past week&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;m&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Past month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Past year&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Example prompts an agent can now follow directly:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;"Search for AI agent funding news from the past week using web_search_timed"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Research climate tech developments from the past month — use time-filtered search"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Find the latest developments in open-source LLMs — past day only"&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent sees the schema. The agent chooses the right tool. No workarounds needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Math: Why This Matters for Token Economics
&lt;/h2&gt;

&lt;p&gt;Let's make it concrete.&lt;/p&gt;

&lt;p&gt;A daily brief cron runs 365 times a year. Each run searches for 10 results with no time filter. Half the results are more than 30 days old. The agent spends ~500 tokens reading stale content per run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;365 runs × 500 stale tokens × $0.001 per 1K tokens (approximate)
= $182.50/year wasted on content that didn't matter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scale that to a team of 10 agents, each running multiple daily research tasks, and you're looking at real money — not because the agents are doing anything wrong, but because the search tool isn't giving them time awareness.&lt;/p&gt;

&lt;p&gt;Now do the same math with &lt;code&gt;time_range='w'&lt;/code&gt; on every run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Same usage, zero stale content
= $0 waste
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plugin costs nothing to run (it uses DuckDuckGo via the &lt;code&gt;ddgs&lt;/code&gt; Python package — no API key required). The token savings are entirely free.&lt;/p&gt;




&lt;h2&gt;
  
  
  Privacy and Cost: Two Reasons to Love DuckDuckGo
&lt;/h2&gt;

&lt;p&gt;This is not a paid promotion of DuckDuckGo. It's just the truth about what makes this stack work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Zero cost&lt;/strong&gt;&lt;br&gt;
DDG has no paid API tier. No rate limits that require a credit card. No tiered plans. The &lt;code&gt;ddgs&lt;/code&gt; Python package is free and open source. The plugin is free and open source. You run it on your own infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Privacy by default&lt;/strong&gt;&lt;br&gt;
DuckDuckGo doesn't track your searches. Doesn't build user profiles. Doesn't sell data. When your agent is doing research on a competitor, a medical topic, or a sensitive business question — you don't want that query logged by your search provider. DDG doesn't log it.&lt;/p&gt;

&lt;p&gt;Compare this to search backends that require API keys, track usage, and build behavioral profiles. For agent workloads, DDG is the privacy-respecting choice that also happens to work without any credential setup.&lt;/p&gt;


&lt;h2&gt;
  
  
  Introducing &lt;code&gt;ddgs&lt;/code&gt;: The Library That Makes This Possible
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;ddgs&lt;/code&gt; Python package, maintained by &lt;a href="https://github.com/deedy5/ddgs" rel="noopener noreferrer"&gt;deedy5&lt;/a&gt; on GitHub, is a metasearch library that aggregates results from multiple search backends — including DuckDuckGo — without requiring an API key.&lt;/p&gt;

&lt;p&gt;It is not affiliated with DuckDuckGo. It is a community-built tool that uses DuckDuckGo's HTML interface directly, in the same way a browser does. This is why it works without keys and why it doesn't log your queries.&lt;/p&gt;

&lt;p&gt;Key properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No API key required&lt;/strong&gt; — works out of the box&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No query logging&lt;/strong&gt; — uses DDG's HTML interface, same privacy as the DDG search bar&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MIT licensed&lt;/strong&gt; — open source, community maintained&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active development&lt;/strong&gt; — regular releases, active GitHub issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The plugin wraps &lt;code&gt;ddgs&lt;/code&gt; with a Hermes-compatible provider interface (&lt;code&gt;WebSearchProvider&lt;/code&gt; ABC), so it slots into Hermes's plugin system cleanly. Other search backends that implement the same ABC can be swapped in without changing the tool schema.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Broader Principle: Token Value Maximizing
&lt;/h2&gt;

&lt;p&gt;The name &lt;code&gt;web_search_timed&lt;/code&gt; is descriptive. But the underlying principle is bigger.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token value maximizing&lt;/strong&gt; is a design philosophy for AI agent tools: every tool should maximize the ratio of useful output per token consumed.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search tools should filter by recency when the task is time-sensitive&lt;/li&gt;
&lt;li&gt;Summarization tools should prioritize recent sources over older ones&lt;/li&gt;
&lt;li&gt;Monitoring tools should alert on anomalies, not on unchanged baselines&lt;/li&gt;
&lt;li&gt;Routing tools should send requests to the cheapest capable model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Time filtering is the simplest, highest-leverage expression of this principle. It requires no model change, no infrastructure change, no cost negotiation. Just a parameter on a search call.&lt;/p&gt;

&lt;p&gt;If you're building agent tools and you're not thinking about token value, you're leaving money on the table — and your users are paying for content they don't need.&lt;/p&gt;


&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;The plugin is published at:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Dr-Agentic/hermes-web-search-time-limit" rel="noopener noreferrer"&gt;Dr-Agentic/hermes-web-search-time-limit&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Option 1: pip install (auto-discovers via entry point)&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;git+https://github.com/Dr-Agentic/hermes-web-search-time-limit.git

&lt;span class="c"&gt;# Option 2: clone locally&lt;/span&gt;
git clone https://github.com/Dr-Agentic/hermes-web-search-time-limit.git &lt;span class="se"&gt;\&lt;/span&gt;
  ~/.hermes/plugins/hermes-web-search-time-limit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart Hermes. The agent will see &lt;code&gt;web_search_timed&lt;/code&gt; alongside &lt;code&gt;web_search&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python ≥ 3.10&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ddgs&lt;/code&gt; (installed automatically with pip)&lt;/li&gt;
&lt;li&gt;Hermes Agent&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Ask
&lt;/h2&gt;

&lt;p&gt;If you build AI agents and you've been burning tokens on stale search results — you now have the fix. It's free, it's private, it's open source.&lt;/p&gt;

&lt;p&gt;And if you've been thinking about the token economics of your agent stack — this is a good moment to look at every tool your agent uses and ask: &lt;em&gt;is this tool maximizing the value of every token, or am I paying for waste I don't need to pay for?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ddgs&lt;/code&gt;-powered &lt;code&gt;hermes-web-search-time-limit&lt;/code&gt; plugin is one concrete answer to that question. What are yours?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tags: &lt;code&gt;ai&lt;/code&gt;, &lt;code&gt;agents&lt;/code&gt;, &lt;code&gt;hermes-agent&lt;/code&gt;, &lt;code&gt;python&lt;/code&gt;, &lt;code&gt;open-source&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>MCP Just Grew Up — What the 2026-07-28 Spec Actually Means</title>
      <dc:creator>Dr. Agentic</dc:creator>
      <pubDate>Wed, 08 Jul 2026 17:44:32 +0000</pubDate>
      <link>https://dev.to/rcsxplatform/mcp-just-grew-up-what-the-2026-07-28-spec-actually-means-3f71</link>
      <guid>https://dev.to/rcsxplatform/mcp-just-grew-up-what-the-2026-07-28-spec-actually-means-3f71</guid>
      <description>&lt;h2&gt;
  
  
  The news hook
&lt;/h2&gt;

&lt;p&gt;On &lt;strong&gt;May 21, 2026&lt;/strong&gt;, the team behind the &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt; locked the release candidate for version &lt;strong&gt;2026-07-28&lt;/strong&gt;. The final version ships &lt;strong&gt;July 28&lt;/strong&gt;. It is the largest change to MCP since Anthropic first published it in late 2024.&lt;/p&gt;

&lt;p&gt;MCP is the open standard that lets AI agents talk to outside tools. When your assistant reads a file, sends an email, or queries a database through an AI agent, MCP is often the wiring underneath. It has grown fast. By mid-2026 there are about &lt;strong&gt;97 million MCP SDK downloads a month&lt;/strong&gt;, roughly &lt;strong&gt;28% of the Fortune 500&lt;/strong&gt; run MCP-backed agents in production, and the contributor community on Discord has passed 2,900 people.&lt;/p&gt;

&lt;p&gt;The update matters because MCP is leaving its "experimental Anthropic project" phase behind.&lt;/p&gt;




&lt;h2&gt;
  
  
  The reframe
&lt;/h2&gt;

&lt;p&gt;MCP now lives under the &lt;strong&gt;Linux Foundation&lt;/strong&gt;, the same vendor-neutral home that hosts Kubernetes and the Linux kernel. Decisions happen through public proposals (called SEPs) and biweekly votes by named maintainers. The current Lead Maintainers are &lt;strong&gt;David Soria Parra&lt;/strong&gt; and &lt;strong&gt;Den Delimarsky&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What changes on July 28 is not a feature. It is the protocol becoming the kind of standard any company can build on, not just the kind one lab invented.&lt;/p&gt;




&lt;h2&gt;
  
  
  The four things that actually change
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. The protocol stops keeping secrets.&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Until now, when an AI agent and a tool server connected, they exchanged a "session ID." Every follow-up message had to carry that ID, which meant every request had to land on the same server instance.&lt;/p&gt;

&lt;p&gt;Starting July 28, the session ID is gone. Each request carries everything it needs. Any server in the cluster can handle it. The protocol no longer hides state in transport metadata. If a tool needs to remember something across calls, the server returns a normal ID (like &lt;code&gt;basket_123&lt;/code&gt;) and the model passes it back on the next call. The state is visible to the model, not buried in the protocol.&lt;/p&gt;

&lt;p&gt;The practical effect: a company can now run MCP servers behind a plain round-robin load balancer. No sticky sessions. No special gateway. No shared memory between servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. New capabilities ship as extensions, not core changes.&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This is the structural fix that should keep MCP from breaking every six months. A new Extensions framework gives new features their own homes, their own version numbers, and their own maintainers. They can stabilize there before (or if) they ever graduate into the core spec.&lt;/p&gt;

&lt;p&gt;Two extensions ship as official in this release:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP Apps.&lt;/strong&gt; Servers can now deliver interactive HTML interfaces (a chart, a form, a video player) that render inside the chat in a sandboxed iframe. The UI talks back to the host through the same plumbing MCP already uses, so every click and keystroke goes through the same audit trail as a normal tool call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tasks.&lt;/strong&gt; Long-running work (think "run this analysis and come back in ten minutes") gets a proper lifecycle. The server decides when a call should run as a task, hands the client a handle, and the client polls, updates, or cancels it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both extensions were built collaboratively. MCP Apps was co-developed with the MCP-UI maintainers at OpenAI and Anthropic. That kind of cross-vendor agreement on UI transport is rare, and it is the reason the extension feels finished.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Authorization gets real-world hardening.&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Six proposals in this release fix the parts of MCP's authentication that did not survive contact with actual enterprise identity systems. The headline change is small and important: clients must now check the &lt;code&gt;iss&lt;/code&gt; (issuer) field on every authorization response, per &lt;strong&gt;RFC 9207&lt;/strong&gt;. That single check closes a class of "mix-up" attack that is uniquely dangerous in MCP's pattern of one client talking to many servers.&lt;/p&gt;

&lt;p&gt;The other five clean up registration, refresh tokens, scope escalation, and discovery. None are exciting on their own. Together they are the difference between MCP working in a demo and MCP working in a Fortune 500 procurement review.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Old features get a 12-month deprecation floor.&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;For the first time, MCP has a formal lifecycle policy. Once a feature is marked deprecated, it has to keep working for &lt;strong&gt;at least twelve months&lt;/strong&gt; before it can be removed, and removing it requires its own public proposal.&lt;/p&gt;

&lt;p&gt;Three features are deprecated in this release. &lt;strong&gt;Roots&lt;/strong&gt; (a way to point the model at filesystem paths) is being folded into ordinary tool arguments. &lt;strong&gt;Sampling&lt;/strong&gt; (letting servers ask the model to generate text) is being moved to direct integration with model providers. &lt;strong&gt;Logging&lt;/strong&gt; is moving from a custom MCP mechanism to plain &lt;code&gt;stderr&lt;/code&gt; for command-line tools and OpenTelemetry for structured observability.&lt;/p&gt;

&lt;p&gt;The policy matters more than any individual deprecation. It is the rule that tells builders their code will not break under them between releases.&lt;/p&gt;




&lt;h2&gt;
  
  
  The punchline
&lt;/h2&gt;

&lt;p&gt;Until now, MCP has been the protocol that worked, sort of, if you set up the right infrastructure. After July 28, MCP is the protocol that runs on the infrastructure you already have.&lt;/p&gt;

&lt;p&gt;Stateless core. Ordinary load balancers. Observability through OpenTelemetry. Authentication through the same OAuth servers your enterprise already uses. UI extensions co-developed by the two biggest labs. Deprecation floors that respect implementers.&lt;/p&gt;

&lt;p&gt;The MCP project's own framing is the right one. This was the foundational change that needed a clean break. Future revisions are not expected to require transport rewrites. They ship as extensions or as conformance-tested updates.&lt;/p&gt;

&lt;p&gt;For the teams shipping agents, the next 23 days are not a "skip and migrate next quarter" window. They are the window in which your code picks the version it will live on for the next several years.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The release-candidate blog post&lt;/strong&gt; (May 21, 2026), the canonical narrative for every change:
&lt;a href="https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/" rel="noopener noreferrer"&gt;https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Governance and stewardship&lt;/strong&gt; (Linux Foundation structure, named Lead and Core Maintainers):
&lt;a href="https://modelcontextprotocol.io/community/governance" rel="noopener noreferrer"&gt;https://modelcontextprotocol.io/community/governance&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Is Growing Up&lt;/strong&gt; (May 27, 2026, Agentic AI Foundation / Linux Foundation):
&lt;a href="https://aaif.io/blog/mcp-is-growing-up/" rel="noopener noreferrer"&gt;https://aaif.io/blog/mcp-is-growing-up/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Filed in &lt;code&gt;content/mcp-2026-07-28-article.md&lt;/code&gt;. The deep-dive research that grounded this article is in &lt;code&gt;content/mcp-2026-07-28-explainer.md&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>openai</category>
      <category>protocol</category>
    </item>
    <item>
      <title>MCP Apps: The iframe Is the Agent</title>
      <dc:creator>Dr. Agentic</dc:creator>
      <pubDate>Wed, 08 Jul 2026 15:35:25 +0000</pubDate>
      <link>https://dev.to/rcsxplatform/mcp-apps-the-iframe-is-the-agent-40k9</link>
      <guid>https://dev.to/rcsxplatform/mcp-apps-the-iframe-is-the-agent-40k9</guid>
      <description>&lt;p&gt;On July 28, 2026, the Model Context Protocol ships its biggest update yet, and&lt;br&gt;
the part most people are sleeping on is MCP Apps. The 2026-07-28 release moves&lt;br&gt;
MCP under the Linux Foundation, drops session IDs, and ships an Extensions&lt;br&gt;
framework. MCP Apps is the first official extension out of that framework, and&lt;br&gt;
its design choice is the one that changes what a chat can host.&lt;/p&gt;

&lt;p&gt;The part most people miss is what the HTML an MCP App returns actually is at&lt;br&gt;
the protocol level. It is a full MCP client running in a sandboxed iframe,&lt;br&gt;
talking to the host over JSON-RPC. Every click, every keystroke, every form&lt;br&gt;
submission is a structured protocol call that flows through the same audit&lt;br&gt;
trail as a model-driven tool call. That single architectural decision is why&lt;br&gt;
this spec is the one that finally settles how agents and UIs talk.&lt;/p&gt;

&lt;p&gt;This post is a deep dive on the interactivity surface. Not a tutorial. Not a&lt;br&gt;
"how to build your first app" walkthrough. A spec-level look at what the&lt;br&gt;
protocol actually gives you, and why it matters.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the protocol actually gives the iframe
&lt;/h2&gt;

&lt;p&gt;MCP Apps is a named extension: &lt;code&gt;io.modelcontextprotocol/ui&lt;/code&gt;. The spec is&lt;br&gt;
&lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1865" rel="noopener noreferrer"&gt;SEP-1865&lt;/a&gt;,&lt;br&gt;
stable as of 2026-01-26, shipping as official in the 2026-07-28 release. It was&lt;br&gt;
co-developed with the MCP-UI maintainers at OpenAI and Anthropic, which is why&lt;br&gt;
the resulting spec feels finished in a way that prior attempts at "MCP + UI"&lt;br&gt;
did not.&lt;/p&gt;

&lt;p&gt;The transport is the part that matters. From the&lt;br&gt;
&lt;a href="https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx" rel="noopener noreferrer"&gt;spec&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;MCP Apps uses JSON-RPC 2.0 over postMessage for iframe-host communication.&lt;br&gt;
UI capabilities (e.g., tool call, prompt, etc.) will reuse MCP's existing&lt;br&gt;
protocol. In the future, we may choose to enrich the communication protocol&lt;br&gt;
with additional MCP capabilities.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In practice, the rendered iframe initializes a JSON-RPC client over the&lt;br&gt;
browser's &lt;code&gt;postMessage&lt;/code&gt; channel. The app advertises its own capabilities,&lt;br&gt;
negotiates with the host, and gets a stable message pipe. Once that handshake&lt;br&gt;
completes, the iframe is a peer MCP client. It can call the same tools the&lt;br&gt;
model calls. It can use the same capabilities. Every action it takes flows&lt;br&gt;
through the same audit trail.&lt;/p&gt;

&lt;p&gt;Here is the conceptual shape, in code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The iframe is an MCP client. The postMessage transport is its wire.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MessageTransport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ui-view&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.0.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole trick. The iframe is a peer MCP client. The sandboxed&lt;br&gt;
rendering is what it looks like from the outside. The protocol is what it is&lt;br&gt;
from the inside, and all the implications follow from there.&lt;/p&gt;
&lt;h2&gt;
  
  
  The seven interactivity primitives
&lt;/h2&gt;

&lt;p&gt;What does the spec actually let the UI do? Here is the surface, in plain&lt;br&gt;
terms.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. The UI can call tools on the server
&lt;/h3&gt;

&lt;p&gt;The most direct interactive capability. A chart, a map, a dashboard can refresh&lt;br&gt;
its own data without the user saying a word to the model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;callServerTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get_weather&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Paris&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user clicks a region. The UI calls a tool. Fresh data renders. The model is&lt;br&gt;
not in the loop. The result goes through the same MCP plumbing the model would&lt;br&gt;
have used, including the same consent and logging path. Sort, filter, paginate&lt;br&gt;
all happen on the client. None of it costs a tool call or a round trip to the&lt;br&gt;
model.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Apps can have tools the model cannot see
&lt;/h3&gt;

&lt;p&gt;A tool can declare &lt;code&gt;visibility: ["app"]&lt;/code&gt; in its &lt;code&gt;_meta.ui&lt;/code&gt; field. The host&lt;br&gt;
hides it from the model's &lt;code&gt;tools/list&lt;/code&gt;. The agent does not know the tool&lt;br&gt;
exists. Only the rendered UI can call it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"refresh_dashboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Refresh dashboard data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputSchema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"_meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ui"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"resourceUri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ui://weather-server/dashboard-template"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"visibility"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the cleanest way to wire a frontend to a private backend action. The&lt;br&gt;
model never sees a &lt;code&gt;refresh_dashboard&lt;/code&gt; or &lt;code&gt;apply_filter&lt;/code&gt; tool in its list. The&lt;br&gt;
UI calls it directly. Cross-server calls for app-only tools are blocked by&lt;br&gt;
spec. Visibility defaults to &lt;code&gt;["model", "app"]&lt;/code&gt; if omitted, so opting into the&lt;br&gt;
hidden-tool pattern is a deliberate choice.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Hosts can stream tool inputs to the app before the tool finishes
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;_meta.ui.resourceUri&lt;/code&gt; field lets the host preload the UI resource ahead&lt;br&gt;
of the tool call. When the model invokes a tool with a long-running argument&lt;br&gt;
set, the host can stream those arguments into the rendered UI in real time.&lt;/p&gt;

&lt;p&gt;The user sees a form filling itself in. The UI prepares its layout. The tool&lt;br&gt;
result lands, and the app is already in the right state to display it. The&lt;br&gt;
"loading spinner" pattern is replaced by a "watch the data arrive" pattern.&lt;/p&gt;

&lt;p&gt;The spec is explicit that hosts MAY prefetch and cache UI resource content for&lt;br&gt;
performance. Resource templates are static. Tool data is dynamic. The split is&lt;br&gt;
the design.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Apps can update the model's context
&lt;/h3&gt;

&lt;p&gt;A user makes a selection in the UI. The app sends a structured message back to&lt;br&gt;
the host. The host can choose to inject that selection into the model's&lt;br&gt;
context.&lt;/p&gt;

&lt;p&gt;A chart lets the user mark a date range. The model now knows the date range.&lt;br&gt;
The next reply uses it. A form returns a configuration object. The model&lt;br&gt;
inherits it. The app and the model share state through the host. Hidden DOM events are&lt;br&gt;
out of scope. The conversation stays the source of truth.&lt;/p&gt;

&lt;p&gt;This is the bridge that makes the iframe part of the conversation. The chat&lt;br&gt;
stays the source of truth, and the UI inherits it.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Capabilities are declared, not assumed
&lt;/h3&gt;

&lt;p&gt;An app that needs the camera, microphone, geolocation, or clipboard-write&lt;br&gt;
declares the request in &lt;code&gt;_meta.ui.permissions&lt;/code&gt;. The host sets the iframe's&lt;br&gt;
Permission Policy accordingly, or denies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"_meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ui"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"camera"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"microphone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"geolocation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"clipboardWrite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apps must feature-detect at runtime, because the host can refuse. The&lt;br&gt;
principle is the same as the rest of MCP: capability before access, never the&lt;br&gt;
other way around.&lt;/p&gt;
&lt;h3&gt;
  
  
  6. External origins are declared, not guessed
&lt;/h3&gt;

&lt;p&gt;An app that needs to call an external API declares &lt;code&gt;connectDomains&lt;/code&gt; in&lt;br&gt;
&lt;code&gt;_meta.ui.csp&lt;/code&gt;. The host writes a Content Security Policy header based on the&lt;br&gt;
declaration. The default is &lt;code&gt;default-src 'none'&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"_meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ui"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"csp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"connectDomains"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://api.openweathermap.org"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"resourceDomains"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://cdn.jsdelivr.net"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"frameDomains"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://www.youtube.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;connectDomains&lt;/code&gt; maps to &lt;code&gt;connect-src&lt;/code&gt; (fetch, XHR, WebSocket)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resourceDomains&lt;/code&gt; maps to &lt;code&gt;script-src&lt;/code&gt;, &lt;code&gt;style-src&lt;/code&gt;, &lt;code&gt;img-src&lt;/code&gt;, &lt;code&gt;font-src&lt;/code&gt;,
&lt;code&gt;media-src&lt;/code&gt; (wildcard subdomains supported)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;frameDomains&lt;/code&gt; maps to &lt;code&gt;frame-src&lt;/code&gt; (nested iframes)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;baseUriDomains&lt;/code&gt; maps to &lt;code&gt;base-uri&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The host enforces, the server declares, and the gap between the two is the&lt;br&gt;
audit trail. If a domain is not declared, the request is blocked. There is no&lt;br&gt;
implicit trust.&lt;/p&gt;
&lt;h3&gt;
  
  
  7. Hosts can give an app a stable origin
&lt;/h3&gt;

&lt;p&gt;Some apps need a predictable origin for OAuth callbacks, CORS allowlists, or&lt;br&gt;
third-party API keys. The &lt;code&gt;_meta.ui.domain&lt;/code&gt; field lets the server request a&lt;br&gt;
dedicated origin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"_meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ui"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"domain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a904794854a047f6.claudemcpcontent.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude uses hash-based subdomains. ChatGPT uses URL-derived subdomains like&lt;br&gt;
&lt;code&gt;www-example-com.oaiusercontent.com&lt;/code&gt;. The format is host-specific, but the&lt;br&gt;
principle is portable. An app that needs to be a first-class OAuth client can&lt;br&gt;
be one, without the host exposing its own cookies or storage to the iframe.&lt;/p&gt;
&lt;h2&gt;
  
  
  The security model, end to end
&lt;/h2&gt;

&lt;p&gt;The sandboxing is the part that makes third-party apps deployable in&lt;br&gt;
production. Hosts render MCP Apps in a sandboxed iframe with a deny-by-default&lt;br&gt;
CSP. The app cannot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access the parent window's DOM&lt;/li&gt;
&lt;li&gt;Read the host's cookies or localStorage&lt;/li&gt;
&lt;li&gt;Navigate the parent page&lt;/li&gt;
&lt;li&gt;Execute scripts in the parent context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All communication between the app and the host goes through &lt;code&gt;postMessage&lt;/code&gt;. The&lt;br&gt;
host controls which capabilities the app can access. A host might restrict&lt;br&gt;
which tools an app can call, disable the &lt;code&gt;sendOpenLink&lt;/code&gt; capability, or refuse&lt;br&gt;
to honor a permission request. The iframe cannot escalate.&lt;/p&gt;

&lt;p&gt;The default CSP, when no declarations are made, is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;default&lt;/span&gt;-&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="s1"&gt;'none'&lt;/span&gt;;
&lt;span class="n"&gt;script&lt;/span&gt;-&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="s1"&gt;'self'&lt;/span&gt; &lt;span class="s1"&gt;'unsafe-inline'&lt;/span&gt;;
&lt;span class="n"&gt;style&lt;/span&gt;-&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="s1"&gt;'self'&lt;/span&gt; &lt;span class="s1"&gt;'unsafe-inline'&lt;/span&gt;;
&lt;span class="n"&gt;img&lt;/span&gt;-&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="s1"&gt;'self'&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;:;
&lt;span class="n"&gt;media&lt;/span&gt;-&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="s1"&gt;'self'&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;:;
&lt;span class="n"&gt;connect&lt;/span&gt;-&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="s1"&gt;'none'&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app is, by default, an island. Every external connection has to be&lt;br&gt;
declared, every capability has to be requested, every tool call goes through&lt;br&gt;
JSON-RPC. The audit trail is the protocol.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is the spec that settled it
&lt;/h2&gt;

&lt;p&gt;There have been several attempts at "agents plus UI" in the last two years.&lt;br&gt;
Most failed at one of two points: fragmentation or trust.&lt;/p&gt;

&lt;p&gt;MCP-UI and OpenAI's Apps SDK both proved the demand. MCP-UI validated the&lt;br&gt;
HTML/external URL/remote DOM content types and the bidirectional communication&lt;br&gt;
model. OpenAI's Apps SDK, launched November 2025, validated that developers&lt;br&gt;
would build rich interactive apps inside ChatGPT using MCP as the backbone.&lt;br&gt;
Both shipped to real users. Neither became the standard on its own.&lt;/p&gt;

&lt;p&gt;The 2026-01-26 spec is the merger. MCP Apps is the unified pattern, and the&lt;br&gt;
contributor list reads like a roll call of who actually shipped:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Authors: Ido Salomon, Liad Yosef, Olivier Chafik, Jerome Swannack, Jonathan&lt;br&gt;
Hefner, Anton Pidkuiko, Nick Cooper, Bryan Ashley, Alexi Christakis&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ido Salomon is the creator of MCP-UI. Olivier Chafik is on the MCP core team.&lt;br&gt;
The rest come from the OpenAI Apps SDK side. That is what "cross-vendor&lt;br&gt;
agreement on UI transport" looks like in practice: the people who would&lt;br&gt;
otherwise build competing standards agreed on one spec, one content type&lt;br&gt;
(&lt;code&gt;text/html;profile=mcp-app&lt;/code&gt;), one URI scheme (&lt;code&gt;ui://&lt;/code&gt;), and one security&lt;br&gt;
model.&lt;/p&gt;

&lt;p&gt;The first content type is HTML. Other types are explicitly reserved for future&lt;br&gt;
extensions. The spec is a foundation, not a ceiling.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this actually changes
&lt;/h2&gt;

&lt;p&gt;Three things shift, in order of how much they matter for builders.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The chat window becomes a real client surface.&lt;/strong&gt; The pattern used to be: a&lt;br&gt;
chat tool with a "click here to open the dashboard" link. The dashboard is a&lt;br&gt;
separate web app, with its own auth, its own state, its own audit trail. MCP&lt;br&gt;
Apps collapses that into the conversation. The UI is right there. The model is&lt;br&gt;
in the loop or not, depending on the design. The audit trail is one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend-to-backend wiring becomes standard.&lt;/strong&gt; A form in an MCP App can call&lt;br&gt;
a tool that the model does not see. The server can do authorization, validation,&lt;br&gt;
and persistence on those calls, the same way it does for model-driven calls.&lt;br&gt;
The pattern that took web teams a decade to converge on (a frontend that calls&lt;br&gt;
a backend with auth, validation, and observability) is now the default in the&lt;br&gt;
chat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The audit and consent story closes.&lt;/strong&gt; Every UI action is a JSON-RPC message.&lt;br&gt;
The host sees it. The user can consent to it. The logs record it. The&lt;br&gt;
Permissions are declared upfront. The CSP is enforced at the wire. The audit&lt;br&gt;
trail records every action. For teams that had to write a custom audit layer&lt;br&gt;
for every agent-plus-UI deployment, the spec does that work now.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;MCP Apps is the part of the 2026-07-28 release that will quietly matter most&lt;br&gt;
over the next 18 months. The Linux Foundation move earns trust. The stateless&lt;br&gt;
core earns scale. The Extensions framework earns stability. MCP Apps earns&lt;br&gt;
distribution, because every chat client that adopts the spec is a new place&lt;br&gt;
where your server can run.&lt;/p&gt;

&lt;p&gt;The pattern is the same one OpenAI's Apps SDK and MCP-UI both used, now&lt;br&gt;
standardized. The iframe is the agent. JSON-RPC is the wiring. The host is&lt;br&gt;
the consent layer. The result is a chat window that can host anything a web&lt;br&gt;
app can host, with the agent's audit trail preserved.&lt;/p&gt;

&lt;p&gt;For teams choosing between a custom UI and an MCP App, the real question is&lt;br&gt;
whether you want the model in the loop on every click. The protocol is ready&lt;br&gt;
for either answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx" rel="noopener noreferrer"&gt;MCP Apps specification (SEP-1865, stable 2026-01-26)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/extensions/apps/overview" rel="noopener noreferrer"&gt;MCP Apps overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/" rel="noopener noreferrer"&gt;The 2026-07-28 release-candidate post&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html" rel="noopener noreferrer"&gt;MCP Apps quickstart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/extensions/client-matrix" rel="noopener noreferrer"&gt;Client support matrix&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mcp</category>
      <category>mcpapps</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>5 Credential Management Mistakes That Will Get Your AI Agent Pwned</title>
      <dc:creator>Dr. Agentic</dc:creator>
      <pubDate>Mon, 18 May 2026 04:15:46 +0000</pubDate>
      <link>https://dev.to/rcsxplatform/5-credential-management-mistakes-that-will-get-your-ai-agent-pwned-5gh3</link>
      <guid>https://dev.to/rcsxplatform/5-credential-management-mistakes-that-will-get-your-ai-agent-pwned-5gh3</guid>
      <description>&lt;p&gt;Every AI agent you deploy needs access to something: APIs, databases, email accounts, payment systems. Each credential is a potential breach vector. And most teams are handling this dangerously wrong.&lt;/p&gt;

&lt;p&gt;I've audited AI agent deployments at dozens of companies. Here are the credential management mistakes I see most often — and how to fix them before they become incidents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistake 1: Hardcoded API Keys in Agent Code
&lt;/h2&gt;

&lt;p&gt;This is the #1 most common mistake. It looks innocent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ❌ NEVER DO THIS
&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CustomerSupportAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sk-live-abc123...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;slack_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;xoxb-456...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;database_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgresql://admin:password@db.host...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This key ends up in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git history (even after you "remove" it)&lt;/li&gt;
&lt;li&gt;Log files (printed in debug output)&lt;/li&gt;
&lt;li&gt;Error traces (sent to Sentry, Datadog, etc.)&lt;/li&gt;
&lt;li&gt;Container images (baked into Docker layers)&lt;/li&gt;
&lt;li&gt;Agent conversation logs (if the agent is prompted to "show your config")&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Fix: Environment Variables with Agent-Scoped Secrets
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AgentCredentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Agent-scoped credentials with minimal permissions.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;crm_api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AGENT_CRM_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;messaging_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AGENT_MESSAGING_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Each agent gets its own set — never share between agents
&lt;/span&gt;
&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CustomerSupportAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;AgentCredentials&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better yet, use a secrets manager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ✅ Fetch from secrets manager at runtime
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_agent_credentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secretsmanager&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Each agent has its own secret with minimal permissions
&lt;/span&gt;    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_secret_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;SecretId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/credentials&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SecretString&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_agent_credentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-support-v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Mistake 2: Shared Credentials Between Agents
&lt;/h2&gt;

&lt;p&gt;You have 5 agents. They all need access to the same CRM. So you give them all the same API key.&lt;/p&gt;

&lt;p&gt;When one agent is compromised, all 5 are compromised. When you need to rotate the key, you have to update all 5 simultaneously. When audit asks "which agent accessed this record?", you can't tell.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix: One Credential Per Agent
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agents/
├── customer-support-v2/
│   └── credentials/
│       ├── crm_api_key (read-only, customers scope)
│       └── messaging_token (send-only, support queue)
├── appointment-bot/
│   └── credentials/
│       ├── crm_api_key (read-write, appointments scope)
│       └── calendar_api_key (read-write, availability scope)
└── billing-agent/
    └── credentials/
        ├── crm_api_key (read-only, billing scope)
        └── stripe_key (read-only, invoices scope)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each agent gets its own credential with the minimum scope needed. This is non-negotiable for production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistake 3: No Credential Rotation
&lt;/h2&gt;

&lt;p&gt;API keys live forever in most agent deployments. A key leaked 6 months ago is still valid today.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix: Automatic Rotation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RotatingCredential&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rotation_days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secret_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secret_name&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotation_days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rotation_days&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_rotation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Check if rotation is needed
&lt;/span&gt;        &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_rotation&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; 
            &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_rotation&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotation_days&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_value&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Generate new key, update all services, invalidate old key.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="c1"&gt;# 1. Generate new credential
&lt;/span&gt;        &lt;span class="n"&gt;new_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generate_new_api_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secret_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# 2. Update the secret store
&lt;/span&gt;        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;update_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secret_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# 3. Update the running agent (hot reload)
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_key&lt;/span&gt;

        &lt;span class="c1"&gt;# 4. Invalidate old key (with grace period)
&lt;/span&gt;        &lt;span class="n"&gt;old_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_value&lt;/span&gt;
        &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;invalidate_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# 1hr grace
&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_rotation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Usage
&lt;/span&gt;&lt;span class="n"&gt;crm_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RotatingCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent/support/crm_api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rotation_days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;crm_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;crm_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Mistake 4: Agents Logging Sensitive Data
&lt;/h2&gt;

&lt;p&gt;AI agents are chatty. They log everything — including credentials, PII, and secrets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ❌ Agent logs that leak credentials
&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connecting to database: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;database_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Contains password!
&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API response: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# May contain PII!
&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Using token: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# The token itself!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Fix: Structured Logging with Redaction
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;SENSITIVE_PATTERNS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sk-[a-zA-Z0-9]{20,}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sk-***REDACTED***&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;password=[^&amp;amp;\s]+&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;password=***REDACTED***&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;token=[^&amp;amp;\s]+&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;token=***REDACTED***&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\b\d{3}-\d{2}-\d{4}\b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SSN-REDACTED&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# SSN
&lt;/span&gt;    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\b[A-Z]{2}\d{9}\b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PASSPORT-REDACTED&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;redact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;replacement&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SENSITIVE_PATTERNS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;replacement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;safe_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Log with automatic redaction of sensitive data.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;redacted_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;redact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;redacted_kwargs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;redact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;redacted_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;redacted_kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Usage
&lt;/span&gt;&lt;span class="nf"&gt;safe_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;info&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connecting to CRM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;crm_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cust_123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Output: "Connecting to CRM" api_key=sk-***REDACTED*** customer_id=cust_123
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Mistake 5: No Audit Trail for Agent Actions
&lt;/h2&gt;

&lt;p&gt;When something goes wrong — a customer record is modified, a payment is processed, data is deleted — you need to know which agent did it, when, and why.&lt;/p&gt;

&lt;p&gt;Without audit trails, you're flying blind on compliance and debugging.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix: Immutable Action Logging
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuditLogger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;  &lt;span class="c1"&gt;# Could be S3, database, or dedicated audit service
&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;log_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;details&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;integrity_hash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;  &lt;span class="c1"&gt;# Will be set
&lt;/span&gt;        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;# Create integrity hash (prevents tampering)
&lt;/span&gt;        &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;integrity_hash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;

&lt;span class="c1"&gt;# Usage
&lt;/span&gt;&lt;span class="n"&gt;audit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AuditLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;AuditStore&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;audit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-support-v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;update_customer_email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cust_123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;old_email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;***REDACTED***&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new_email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;***REDACTED***&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer requested email change&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;credential_used&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;crm_api_key_support_readwrite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model_used&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Complete Security Checklist
&lt;/h2&gt;

&lt;p&gt;Before deploying any AI agent to production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;No hardcoded credentials&lt;/strong&gt; — all secrets from environment or secrets manager&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;One credential per agent&lt;/strong&gt; — no shared keys between agents&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Minimum scope&lt;/strong&gt; — each credential has the least permissions needed&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Automatic rotation&lt;/strong&gt; — credentials rotate every 30-90 days&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Logging redaction&lt;/strong&gt; — sensitive data never appears in logs&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Audit trail&lt;/strong&gt; — every agent action is logged with integrity hash&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Credential isolation&lt;/strong&gt; — agents can't access each other's secrets&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Incident response&lt;/strong&gt; — documented procedure for credential compromise&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Regular access review&lt;/strong&gt; — quarterly audit of agent permissions&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Encryption at rest and in transit&lt;/strong&gt; — credentials encrypted in storage and over network&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Matters More for AI Agents
&lt;/h2&gt;

&lt;p&gt;Traditional software has 1-3 integrations. AI agents routinely have 10-20. Each integration multiplies your attack surface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;More credentials&lt;/strong&gt; = more potential leak points&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Autonomous actions&lt;/strong&gt; = faster breach propagation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversation context&lt;/strong&gt; = credentials can be extracted via prompt injection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic behavior&lt;/strong&gt; = harder to predict what an agent will access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The good news: treating agent credentials as a first-class concern from day one is dramatically cheaper than cleaning up after a breach. A credential management system takes 1-2 weeks to build. A data breach takes months to recover from and can cost millions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;AI agents are only as secure as their credential management. If your agents have access to production systems and you're not following these patterns, you have a ticking time bomb.&lt;/p&gt;

&lt;p&gt;Fix it now. Not after the incident.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building secure AI agents? The &lt;a href="https://morsy.gumroad.com/l/rcs-developer-starter-kit" rel="noopener noreferrer"&gt;RCS Developer Starter Kit&lt;/a&gt; includes production-ready credential management patterns, secure agent templates, and deployment guides built by people who've shipped agents at scale.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Follow &lt;a href="https://dev.to/rcsxplatform"&gt;@rcsxplatform&lt;/a&gt; for more on AI agent security and production deployment.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>developers</category>
      <category>python</category>
    </item>
    <item>
      <title>6 Production Patterns That Turn AI Agent Demos Into Reliable Systems</title>
      <dc:creator>Dr. Agentic</dc:creator>
      <pubDate>Mon, 18 May 2026 04:15:02 +0000</pubDate>
      <link>https://dev.to/rcsxplatform/6-production-patterns-that-turn-ai-agent-demos-into-reliable-systems-3mco</link>
      <guid>https://dev.to/rcsxplatform/6-production-patterns-that-turn-ai-agent-demos-into-reliable-systems-3mco</guid>
      <description>&lt;p&gt;Your AI agent works perfectly in testing. Then you deploy it to production and everything falls apart.&lt;/p&gt;

&lt;p&gt;Sound familiar? You're not alone. A recent survey found that &lt;strong&gt;73% of AI projects never make it past the prototype stage&lt;/strong&gt;, and the #1 reason cited is reliability — agents that work in demos but fail under real-world conditions.&lt;/p&gt;

&lt;p&gt;After deploying dozens of AI agents across production environments, here's a battle-tested framework for making agents that actually work when it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Production Readiness Checklist
&lt;/h2&gt;

&lt;p&gt;Before any agent goes live, it needs to pass every item on this list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Graceful degradation&lt;/strong&gt; — when the LLM fails, the system doesn't&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Timeout enforcement&lt;/strong&gt; — every API call has a hard timeout&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Idempotency&lt;/strong&gt; — retrying the same request produces the same result&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Observability&lt;/strong&gt; — every agent action is logged with context&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Rate limiting&lt;/strong&gt; — the agent can't DOS itself or external APIs&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Human escalation&lt;/strong&gt; — there's always a path to a real person&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Cost controls&lt;/strong&gt; — per-customer and per-task spend limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Miss any one of these and you'll learn about it at 3 AM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: The Circuit Breaker
&lt;/h2&gt;

&lt;p&gt;Your agent calls an external API. That API goes down. What happens?&lt;/p&gt;

&lt;p&gt;Without a circuit breaker: your agent retries forever, accumulating latency and cost. Eventually it times out, but not before burning through your error budget.&lt;/p&gt;

&lt;p&gt;With a circuit breaker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CircuitBreaker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;failure_threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recovery_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failure_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failure_threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;failure_threshold&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recovery_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;recovery_timeout&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_failure_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;closed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# closed, open, half-open
&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;open&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_failure_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recovery_timeout&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;half-open&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Try once
&lt;/span&gt;            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;CircuitOpenError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Circuit is open — failing fast&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;half-open&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;closed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# We're back
&lt;/span&gt;            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failure_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failure_count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_failure_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failure_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failure_threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;open&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Production rule:&lt;/strong&gt; Every external dependency gets a circuit breaker. No exceptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2: The Multi-Model Fallback
&lt;/h2&gt;

&lt;p&gt;GPT-4 goes down. Or returns garbage. Or is suddenly 10x slower. Your agent needs to keep working.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Model fallback chain with automatic switching
&lt;/span&gt;&lt;span class="n"&gt;MODEL_CHAIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet-4-20250514&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_with_fallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;model_config&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;MODEL_CHAIN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;call_llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;model_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;timeout_ms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;log_model_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
        &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;TimeoutError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RateLimitError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;APIError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;log_model_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

    &lt;span class="c1"&gt;# All models failed — use cached response or escalate
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;get_cached_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;escalate_to_human&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Production rule:&lt;/strong&gt; Never depend on a single model provider. Always have a fallback chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3: Structured Output Validation
&lt;/h2&gt;

&lt;p&gt;LLMs generate text. Your agent needs structured data. The gap between these two things is where most production failures live.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppointmentBooking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;customer_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;service_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;book_appointment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;AppointmentBooking&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Parse agent response into a validated appointment booking.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;parse_structured_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AppointmentBooking&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;is_business_hour&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Booking outside business hours: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Re-ask the agent with the error context
&lt;/span&gt;        &lt;span class="n"&gt;corrected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;call_llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The previous response had errors: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Please correct and return a valid appointment booking.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;parse_structured_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;corrected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AppointmentBooking&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Production rule:&lt;/strong&gt; Every LLM output goes through schema validation. Re-ask once on failure, then escalate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 4: Cost Guardrails
&lt;/h2&gt;

&lt;p&gt;An AI agent without cost controls is a credit card with no limit. I've seen agents accidentally loop and burn through $500 in a single night.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CostGuardrail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_cost_per_task&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_cost_per_customer_daily&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;5.00&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_cost_per_task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max_cost_per_task&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_cost_per_customer_daily&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max_cost_per_customer_daily&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_spend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_before_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;estimated_cost&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;daily_spend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_spend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;estimated_cost&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_cost_per_task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;CostExceededError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Task cost $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;estimated_cost&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; exceeds limit $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_cost_per_task&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;daily_spend&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;estimated_cost&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_cost_per_customer_daily&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;DailyBudgetExceededError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Customer daily spend $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;daily_spend&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; + $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;estimated_cost&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exceeds limit $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_cost_per_customer_daily&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record_spend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual_cost&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_spend&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_spend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;actual_cost&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Production rule:&lt;/strong&gt; Set per-task and per-customer daily limits. Alert on 80% threshold. No exceptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 5: Observability Stack
&lt;/h2&gt;

&lt;p&gt;You can't fix what you can't see. Production AI agents need three layers of observability:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Agent-Level Logging
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;structlog&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_logger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;agent_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;duration_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cost_usd&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ainfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent_action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;action_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;action_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;intent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;duration_ms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;duration_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;cost_usd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cost_usd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tokens_used&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Business Metrics Dashboard
&lt;/h3&gt;

&lt;p&gt;Track these metrics daily:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Task completion rate&lt;/strong&gt; — % of interactions that end successfully&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human escalation rate&lt;/strong&gt; — % of interactions routed to humans&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Average cost per task&lt;/strong&gt; — total LLM spend / completed tasks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency P50/P95/P99&lt;/strong&gt; — how fast your agent responds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customer satisfaction&lt;/strong&gt; — CSAT or NPS after agent interactions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Alert Rules
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="n"&gt;ALERT&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="n"&gt;task_completion_rate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt;
&lt;span class="n"&gt;ALERT&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="n"&gt;escalation_rate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt;
&lt;span class="n"&gt;ALERT&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cost_per_task&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mf"&gt;0.75&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt; &lt;span class="nb"&gt;hour&lt;/span&gt;
&lt;span class="n"&gt;ALERT&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="n"&gt;P99&lt;/span&gt; &lt;span class="n"&gt;latency&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt;
&lt;span class="n"&gt;ALERT&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="n"&gt;error_rate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pattern 6: The Human Escalation Pathway
&lt;/h2&gt;

&lt;p&gt;AI agents fail. The question isn't if — it's how gracefully.&lt;/p&gt;

&lt;p&gt;A proper escalation system has three tiers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Auto-retry with correction&lt;/strong&gt; — agent re-attempts with error context (handles 70% of failures)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alternative path&lt;/strong&gt; — switch to a simpler approach or different model (handles 20% of failures)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human handoff&lt;/strong&gt; — route to a human with full context (handles the remaining 10%)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_with_escalation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Tier 1: Try with the best model
&lt;/span&gt;    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;confidence&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;AgentError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

    &lt;span class="c1"&gt;# Tier 2: Try simpler approach
&lt;/span&gt;    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;simple_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;confidence&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;AgentError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

    &lt;span class="c1"&gt;# Tier 3: Human handoff
&lt;/span&gt;    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;notify_human&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Agent escalation needed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;conversation_history&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;history&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;All automated paths failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;EscalationResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;escalated&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Deployment Checklist
&lt;/h2&gt;

&lt;p&gt;Before pushing any agent to production, verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Circuit breakers on all external dependencies&lt;/li&gt;
&lt;li&gt;[ ] Multi-model fallback chain configured&lt;/li&gt;
&lt;li&gt;[ ] Output validation with Pydantic schemas&lt;/li&gt;
&lt;li&gt;[ ] Cost guardrails (per-task and per-customer)&lt;/li&gt;
&lt;li&gt;[ ] Structured logging with business context&lt;/li&gt;
&lt;li&gt;[ ] Alert rules configured in monitoring&lt;/li&gt;
&lt;li&gt;[ ] Human escalation pathway tested end-to-end&lt;/li&gt;
&lt;li&gt;[ ] Load tested at 2x expected peak traffic&lt;/li&gt;
&lt;li&gt;[ ] Rollback plan documented and tested&lt;/li&gt;
&lt;li&gt;[ ] On-call runbook written and accessible&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real Results
&lt;/h2&gt;

&lt;p&gt;After implementing these patterns across production agents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Task completion rate:&lt;/strong&gt; 94% (up from 72%)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human escalation rate:&lt;/strong&gt; 6% (down from 28%)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Average cost per task:&lt;/strong&gt; $0.08 (down from $0.32)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;P99 latency:&lt;/strong&gt; 3.2 seconds (down from 12 seconds)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3 AM pages:&lt;/strong&gt; 0 (down from 2-3 per week)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference between a demo agent and a production agent isn't intelligence — it's engineering discipline. These patterns turn a fragile prototype into something that runs reliably 24/7.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Want to skip the infrastructure work? The &lt;a href="https://morsy.gumroad.com/l/rcs-developer-starter-kit" rel="noopener noreferrer"&gt;RCS Developer Starter Kit&lt;/a&gt; includes production-ready agent templates with circuit breakers, cost guardrails, and monitoring built in — so you can focus on your agent logic, not the plumbing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Follow &lt;a href="https://dev.to/rcsxplatform"&gt;@rcsxplatform&lt;/a&gt; for more on production AI agent deployment.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>developers</category>
      <category>productivity</category>
      <category>python</category>
    </item>
    <item>
      <title>10 AI Agent Business Ideas That Can Hit $1M ARR</title>
      <dc:creator>Dr. Agentic</dc:creator>
      <pubDate>Mon, 18 May 2026 04:13:25 +0000</pubDate>
      <link>https://dev.to/rcsxplatform/10-ai-agent-business-ideas-that-can-hit-1m-arr-2caj</link>
      <guid>https://dev.to/rcsxplatform/10-ai-agent-business-ideas-that-can-hit-1m-arr-2caj</guid>
      <description>&lt;p&gt;Most "AI business ideas" floating around are either too vague ("build an AI tool!") or too complex for a small team to execute. I've filtered through hundreds of ideas and stress-tested them against one question: &lt;strong&gt;Can this realistically hit $1M ARR within 12 months?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here are 10 that can — with specific paths to get there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Selection Criteria
&lt;/h2&gt;

&lt;p&gt;Every idea on this list had to pass these filters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TAM ≥ $1B&lt;/strong&gt; — big enough market that $1M is a rounding error&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear $0→$10K path&lt;/strong&gt; — you can get initial customers without enterprise sales&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-serve or low-touch&lt;/strong&gt; — no 6-month sales cycles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Defensible moat&lt;/strong&gt; — something beyond "we wrapped an API"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proven willingness to pay&lt;/strong&gt; — customers already spend money on adjacent solutions&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. AI Receptionist for Small Businesses
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Idea:&lt;/strong&gt; Replace missed calls with an AI agent that answers, qualifies, and books appointments via RCS/SMS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; Small businesses miss 30-40% of calls. Each missed call is $200-500 in lost revenue. An AI receptionist that catches even half of those pays for itself in a week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$1M Path:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$49-99/month per business&lt;/li&gt;
&lt;li&gt;1,700-3,400 customers to $1M ARR&lt;/li&gt;
&lt;li&gt;Distribution: partner with vertical SaaS (dental, HVAC, salons)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Moat:&lt;/strong&gt; Proprietary conversation data + vertical-specific workflows that compound over time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Core loop: incoming call → AI answers → books appointment → updates CRM
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_incoming_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;business&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;intent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;classify_intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;appointment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;slots&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;check_availability&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;business&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calendar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;booking&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;book_appointment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;send_confirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;booking&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rcs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;booking&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;question&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;answer_from_knowledge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;business&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;faq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;send_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rcs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;transfer_to_human&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;business&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. RCS Business Messaging Platform
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Idea:&lt;/strong&gt; Developer-first RCS messaging platform with templates, analytics, and agent integration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; RCS is replacing SMS for business communication. Google Messages supports it on 800M+ devices. But the developer tooling is still primitive — most platforms are enterprise-only with $10K+ minimums.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$1M Path:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Usage-based pricing: $0.005-0.02 per message + $99-299/month platform fee&lt;/li&gt;
&lt;li&gt;500-2,000 active developer customers&lt;/li&gt;
&lt;li&gt;Revenue compounds as their applications scale&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Moat:&lt;/strong&gt; Developer experience + pre-built compliance/regulatory templates + agent orchestration layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. AI Agent Orchestration as a Service
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Idea:&lt;/strong&gt; Hosted platform for running, monitoring, and scaling AI agents with built-in credential management and multi-agent coordination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; Every company building AI agents hits the same walls: credential management, observability, agent-to-agent communication, and deployment. Currently each team rebuilds this infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$1M Path:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$49/month (starter) → $499/month (team) → $2,000/month (enterprise)&lt;/li&gt;
&lt;li&gt;200 enterprise customers at $416/month = $1M ARR&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Moat:&lt;/strong&gt; Network effects from agent marketplace + deep integrations with LLM providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Automated Customer Support for E-Commerce
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Idea:&lt;/strong&gt; AI agent that handles returns, order tracking, product questions, and upsells via messaging (RCS, WhatsApp, SMS).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; E-commerce support is 80% repetitive queries. Average cost per ticket: $6-12. AI can handle 70%+ at $0.10-0.50 per resolution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$1M Path:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$299-799/month per e-commerce store&lt;/li&gt;
&lt;li&gt;100-280 customers to $1M ARR&lt;/li&gt;
&lt;li&gt;Shopify App Store = built-in distribution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Moat:&lt;/strong&gt; Store-specific training data + deep Shopify integration + conversion tracking.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. AI-Powered Lead Qualification Bot
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Idea:&lt;/strong&gt; AI agent that engages website visitors, qualifies them using ICP criteria, and routes hot leads to sales via RCS/SMS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; Sales teams waste 65% of their time on unqualified leads. A qualification bot that filters before human contact increases close rates by 30-50%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$1M Path:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$500-2,000/month per B2B company&lt;/li&gt;
&lt;li&gt;40-170 customers to $1M ARR&lt;/li&gt;
&lt;li&gt;Distribution: Salesforce/HubSpot marketplace&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Moat:&lt;/strong&gt; Vertical-specific qualification logic + CRM data enrichment loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Developer Credential &amp;amp; Secret Management for AI Agents
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Idea:&lt;/strong&gt; Purpose-built vault for AI agent credentials — API keys, OAuth tokens, and secrets with granular permissions, rotation, and audit trails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; AI agents need access to dozens of services. Managing credentials for 10+ agents is a security nightmare. Existing solutions (1Password, HashiCorp Vault) aren't designed for agent workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$1M Path:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$19/month per developer → $99/month per team&lt;/li&gt;
&lt;li&gt;8,300+ developer customers or 840 team customers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Moat:&lt;/strong&gt; Agent-native architecture + granular per-agent permissions + automatic rotation.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. AI Compliance Monitor for Regulated Industries
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Idea:&lt;/strong&gt; Continuously monitors AI agent outputs for regulatory compliance (HIPAA, FINRA, SOC2) and generates audit reports.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; Regulated industries want AI but can't deploy without compliance controls. This removes the #1 blocker. Healthcare and financial services alone represent a $4B market.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$1M Path:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$1,000-5,000/month per regulated entity&lt;/li&gt;
&lt;li&gt;17-83 customers to $1M ARR&lt;/li&gt;
&lt;li&gt;High willingness to pay — compliance is a must-have, not nice-to-have&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Moat:&lt;/strong&gt; Regulatory rule library + audit trail format accepted by auditors + industry-specific models.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Vertical AI Agent for Real Estate
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Idea:&lt;/strong&gt; AI agent that handles lead nurturing, showing scheduling, and follow-ups for real estate agents via RCS/SMS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; Real estate agents are solo operators who spend 40% of time on administrative tasks. A $99/month agent that recovers 10 hours/week is a no-brainer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$1M Path:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$99-199/month per agent&lt;/li&gt;
&lt;li&gt;420-850 agents to $1M ARR&lt;/li&gt;
&lt;li&gt;Distribution: real estate CRM partnerships&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Moat:&lt;/strong&gt; MLS data integration + local market knowledge + agent personality customization.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. AI-Powered Invoice &amp;amp; Payment Follow-Up Agent
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Idea:&lt;/strong&gt; Agent that sends personalized payment reminders via RCS/SMS, handles payment plan negotiations, and processes payments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; SMBs have $3.1T in unpaid invoices. A 5% improvement in collection rates = thousands of dollars per business per month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$1M Path:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$149-299/month per business&lt;/li&gt;
&lt;li&gt;280-560 customers to $1M ARR&lt;/li&gt;
&lt;li&gt;Revenue share model on recovered payments = faster scaling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Moat:&lt;/strong&gt; Payment processing integration + negotiation logic that improves with data + compliance templates.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Multi-Channel Notification &amp;amp; Alerting Platform
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Idea:&lt;/strong&gt; Developer API to send alerts, updates, and notifications via RCS, SMS, email, and push — with AI-powered content optimization and delivery timing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; Every SaaS needs notifications. Current solutions are channel-specific (Twilio for SMS, SendGrid for email). A unified API with AI-powered optimization is the natural evolution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$1M Path:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Usage-based: $0.005-0.02 per notification&lt;/li&gt;
&lt;li&gt;Platform fee: $49-299/month&lt;/li&gt;
&lt;li&gt;2,000+ developer customers using the platform&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Moat:&lt;/strong&gt; Multi-channel delivery optimization + AI-powered send-time optimization + template marketplace.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pattern: Why These Work
&lt;/h2&gt;

&lt;p&gt;Look at the common threads:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Messaging is the interface&lt;/strong&gt; — 8 of 10 ideas use RCS, SMS, or messaging as the primary user interaction. This isn't accidental. Business communication is shifting to rich messaging.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vertical beats horizontal&lt;/strong&gt; — "AI for everyone" loses to "AI for real estate agents" or "AI for dental offices." Vertical focus reduces acquisition cost and increases willingness to pay.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The money is in integration, not intelligence&lt;/strong&gt; — The LLM is a commodity. The value is in connecting AI to existing business systems (CRM, calendar, payments).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Self-serve scales faster than enterprise&lt;/strong&gt; — Every idea here has a $49-299/month entry point. Low-touch = faster revenue growth.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;RCS is the underpriced channel&lt;/strong&gt; — Rich messaging with read receipts, carousels, and actions — delivered natively on Android. Most developers haven't caught on yet.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to Get Started
&lt;/h2&gt;

&lt;p&gt;Pick one idea. Validate it with 10 customers in 30 days. Here's the playbook:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Build an MVP in 2 weeks&lt;/strong&gt; — use existing APIs and agent frameworks, don't over-engineer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get 10 paying customers&lt;/strong&gt; — charge from day one, even if it's $1&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate unit economics&lt;/strong&gt; — ensure CAC &amp;lt; 3× monthly revenue&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Double down or pivot&lt;/strong&gt; — with real data, not vibes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;a href="https://morsy.gumroad.com/l/rcs-developer-starter-kit" rel="noopener noreferrer"&gt;RCS Developer Starter Kit&lt;/a&gt; includes production-ready templates, API integration code, and deployment guides that can shortcut weeks of development for any messaging-based idea on this list.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;These aren't theoretical. I'm building some of these myself. Follow &lt;a href="https://dev.to/rcsxplatform"&gt;@rcsxplatform&lt;/a&gt; for build-in-public updates.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>startup</category>
      <category>business</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The 76-14 Gap: Why 76% of SMBs Use AI But Only 14% Actually Integrated It</title>
      <dc:creator>Dr. Agentic</dc:creator>
      <pubDate>Mon, 18 May 2026 04:12:47 +0000</pubDate>
      <link>https://dev.to/rcsxplatform/the-76-14-gap-why-76-of-smbs-use-ai-but-only-14-actually-integrated-it-4e9n</link>
      <guid>https://dev.to/rcsxplatform/the-76-14-gap-why-76-of-smbs-use-ai-but-only-14-actually-integrated-it-4e9n</guid>
      <description>&lt;p&gt;The AI adoption numbers look incredible on the surface: &lt;strong&gt;76% of SMBs say they're "using AI."&lt;/strong&gt; But dig deeper and the real story emerges — &lt;strong&gt;only 14% have actually integrated AI into their core workflows.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's a &lt;strong&gt;62-point gap&lt;/strong&gt; between experimentation and execution. And it's the single biggest revenue opportunity in the developer tools space right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data Behind the Gap
&lt;/h2&gt;

&lt;p&gt;Recent surveys from McKinsey, Gartner, and SMB Group all tell the same story:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Percentage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SMBs "using AI" in some form&lt;/td&gt;
&lt;td&gt;76%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMBs with AI in production workflows&lt;/td&gt;
&lt;td&gt;14%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMBs with a documented AI strategy&lt;/td&gt;
&lt;td&gt;11%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMBs measuring AI ROI&lt;/td&gt;
&lt;td&gt;8%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The gap isn't about awareness — SMBs know about AI. The gap is about &lt;strong&gt;integration&lt;/strong&gt;. They're experimenting with ChatGPT, playing with copilots, running one-off automations. But they haven't wired AI into the systems that actually run their business.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why SMBs Get Stuck at "Experimentation"
&lt;/h2&gt;

&lt;p&gt;I've talked to dozens of small business owners and developers building for SMBs. Here are the four patterns I see over and over:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Integration Tax
&lt;/h3&gt;

&lt;p&gt;Every AI tool promises to "save hours." But connecting it to your existing stack — CRM, invoicing, scheduling, email — requires API work, webhooks, and custom logic that most SMBs can't afford to build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# What SMBs expect:
&lt;/span&gt;&lt;span class="n"&gt;ai_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handle_customers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# ✨ magic ✨
&lt;/span&gt;
&lt;span class="c1"&gt;# What actually happens:
# 1. Authenticate with 3 different APIs
# 2. Map inconsistent data schemas
# 3. Handle rate limits and retries
# 4. Build error recovery for when things break
# 5. Monitor the whole thing
# Total: 40-80 hours of integration work
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. The Reliability Cliff
&lt;/h3&gt;

&lt;p&gt;Demos work great. Production is different. AI agents hallucinate, APIs change, edge cases multiply. SMBs don't have QA teams or SREs. When an AI workflow breaks at 2 AM, it stays broken.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The ROI Fog
&lt;/h3&gt;

&lt;p&gt;"I spent $500/month on AI tools and saved... some time? Maybe?" Without clear measurement frameworks, SMBs can't justify expanding AI beyond experimental use.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The Talent Bottleneck
&lt;/h3&gt;

&lt;p&gt;Most SMBs don't have in-house developers. They rely on agencies or freelancers who are still learning AI integration themselves. The supply of AI-literate developers hasn't caught up with demand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The $42B Opportunity in the Gap
&lt;/h2&gt;

&lt;p&gt;Here's why this gap matters: the SMB AI market is projected to hit &lt;strong&gt;$120B by 2028&lt;/strong&gt;. But 76-14 = 62% of SMBs are in the "experimentation" zone — meaning they're spending money without getting transformative value.&lt;/p&gt;

&lt;p&gt;The companies that close this gap — that help SMBs move from "trying AI" to &lt;strong&gt;running on AI&lt;/strong&gt; — will capture disproportionate market share.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where the Money Is
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Integration platforms&lt;/strong&gt; that connect AI agents to existing SMB tools (the "last mile" problem). Think Zapier but purpose-built for AI agents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Turnkey AI agent templates&lt;/strong&gt; that solve specific business problems out of the box — no integration work required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI monitoring and observability&lt;/strong&gt; designed for non-technical users. "Your agent stopped working at 2 AM" is a notification every SMB needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ROI dashboards&lt;/strong&gt; that automatically measure time saved, revenue generated, and cost reduced by AI implementations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing the Gap: A Practical Framework
&lt;/h2&gt;

&lt;p&gt;For developers and founders building in this space, here's what works:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Pick One Workflow, Not Ten
&lt;/h3&gt;

&lt;p&gt;Don't try to AI-ify the entire business. Pick the highest-leverage workflow — usually customer communication, appointment scheduling, or invoice processing — and automate that end-to-end.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Build the Integration, Not Just the Agent
&lt;/h3&gt;

&lt;p&gt;An AI agent that can't talk to your CRM isn't an agent — it's a chatbot. The value is in the &lt;strong&gt;connections&lt;/strong&gt;, not the intelligence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Agent orchestration that handles the full workflow&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;appointment-handler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read_calendar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// Google Calendar API&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;send_rcs_message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// RCS Business Messaging&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;update_crm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// Salesforce/HubSpot&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;process_payment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;     &lt;span class="c1"&gt;// Stripe&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;escalate_to_human&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;// Always have an escape hatch&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// When the AI can't handle it, route to a human seamlessly&lt;/span&gt;
&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;escalation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;notify_owner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;log_escalation_reason&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Measure From Day One
&lt;/h3&gt;

&lt;p&gt;Define clear success metrics before deploying:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Time saved per transaction&lt;/li&gt;
&lt;li&gt;Customer satisfaction scores (before vs. after)&lt;/li&gt;
&lt;li&gt;Revenue attributed to AI-assisted workflows&lt;/li&gt;
&lt;li&gt;Reduction in manual processing errors&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 4: Design for the Reliability Cliff
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always have a fallback&lt;/strong&gt; — when the AI can't handle something, route to a human&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor everything&lt;/strong&gt; — set up alerts for error rates, response times, and cost per interaction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version your prompts&lt;/strong&gt; — treat prompt changes like code changes, with rollback capability&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What This Means for Developers
&lt;/h2&gt;

&lt;p&gt;If you're a developer, the 76-14 gap is your career opportunity. Companies are desperate for people who can bridge AI capabilities with real business systems. The skills that matter most:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;API orchestration&lt;/strong&gt; — connecting AI agents to real systems&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RCS and messaging integration&lt;/strong&gt; — the primary channel for AI-SMB interaction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent reliability engineering&lt;/strong&gt; — keeping AI running in production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business metrics translation&lt;/strong&gt; — connecting technical improvements to revenue outcomes&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;76% adoption with 14% integration isn't a failure — it's a &lt;strong&gt;massive bottleneck&lt;/strong&gt; waiting to be solved. The SMBs are ready. The AI is ready. What's missing is the connective tissue: developers, tools, and templates that make AI work inside real businesses.&lt;/p&gt;

&lt;p&gt;That's the gap. That's the opportunity. And it's worth billions.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building AI agents for business? The &lt;a href="https://morsy.gumroad.com/l/rcs-developer-starter-kit" rel="noopener noreferrer"&gt;RCS Developer Starter Kit&lt;/a&gt; gives you production-ready templates, API integration code, and deployment guides — everything you need to bridge the 76-14 gap for your customers.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article is part of a series on AI agent deployment for business. Follow &lt;a href="https://dev.to/rcsxplatform"&gt;@rcsxplatform&lt;/a&gt; for more.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>startup</category>
      <category>business</category>
      <category>productivity</category>
    </item>
    <item>
      <title>RCS Business Messaging: 5 Templates You Can Use Today (JSON + Validation Checklist)</title>
      <dc:creator>Dr. Agentic</dc:creator>
      <pubDate>Tue, 12 May 2026 01:16:01 +0000</pubDate>
      <link>https://dev.to/rcsxplatform/rcs-business-messaging-5-templates-you-can-use-today-json-validation-checklist-4f52</link>
      <guid>https://dev.to/rcsxplatform/rcs-business-messaging-5-templates-you-can-use-today-json-validation-checklist-4f52</guid>
      <description>&lt;h1&gt;
  
  
  RCS Business Messaging: 5 Templates You Can Use Today
&lt;/h1&gt;

&lt;p&gt;Building for RCS? The GSMA Universal Profile is 200+ pages. Most developers just need working templates and a validation checklist.&lt;/p&gt;

&lt;p&gt;After building &lt;a href="https://rcsxplatform.net" rel="noopener noreferrer"&gt;RCS X&lt;/a&gt; — an RCS emulator for developers — we distilled the most useful patterns into this guide.&lt;/p&gt;




&lt;h2&gt;
  
  
  Template 1: Text Message with Suggested Actions
&lt;/h2&gt;

&lt;p&gt;The most useful RCS message type. Adds quick-reply buttons to guide user interaction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contentMessage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"What would you like to do today?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"suggestions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"📦 Track Order"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"postbackData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"track_order"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"🛒 Place Order"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"postbackData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"place_order"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"📞 Contact Support"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"postbackData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contact_support"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"📍 Find Store"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"postbackData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"find_store"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key rules:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Max 4 suggestions per message&lt;/li&gt;
&lt;li&gt;Suggestion text: max 25 characters&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;postbackData&lt;/code&gt;: max 200 characters&lt;/li&gt;
&lt;li&gt;Use emoji sparingly for visual hierarchy&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Template 2: Rich Card (Vertical + Media)
&lt;/h2&gt;

&lt;p&gt;The workhorse of RCS messaging. Image + title + description + buttons.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contentMessage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"richCard"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"standaloneCard"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"cardOrientation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VERTICAL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"cardContent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Coffee Time ☕"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Start your morning with the perfect cup. Fresh roasted beans delivered to your door."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"media"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MEDIUM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"contentInfo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"fileUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-cdn.com/images/coffee-hero.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"mimeType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image/jpeg"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"suggestions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Order Now"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"postbackData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"order_coffee"&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"View Menu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"postbackData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"view_menu"&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Media height options:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SHORT&lt;/code&gt; — 112dp (thumbnail/icon)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MEDIUM&lt;/code&gt; — 168dp (standard, recommended)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TALL&lt;/code&gt; — 280dp (hero/feature image)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Image specs:&lt;/strong&gt; Max 3.5MB JPEG, 1MB PNG. Recommended 600x340px for MEDIUM.&lt;/p&gt;




&lt;h2&gt;
  
  
  Template 3: Carousel (2 Cards)
&lt;/h2&gt;

&lt;p&gt;Multiple cards in a swipeable carousel. Max 10 cards per carousel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contentMessage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"richCard"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"carouselCard"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"cardWidth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MEDIUM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"cardContents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Eiffel Tower"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Iconic landmark of Paris."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"media"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MEDIUM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"contentInfo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"fileUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-cdn.com/images/eiffel.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"mimeType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image/jpeg"&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"suggestions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"reply"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Book Eiffel Tour"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"postbackData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"book_eiffel"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Louvre Museum"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Worlds largest art museum."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"media"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MEDIUM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"contentInfo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"fileUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-cdn.com/images/louvre.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"mimeType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image/jpeg"&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"suggestions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"reply"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Book Louvre Tour"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"postbackData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"book_louvre"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Carousel rules:&lt;/strong&gt; &lt;code&gt;cardWidth&lt;/code&gt; is SMALL or MEDIUM. All cards must have the same width. Best engagement: 2-5 cards.&lt;/p&gt;




&lt;h2&gt;
  
  
  Template 4: Calendar Action
&lt;/h2&gt;

&lt;p&gt;Add events directly to the users calendar from an RCS message.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contentMessage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Join our product launch event!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"suggestions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Add to Calendar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"postbackData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"add_launch_calendar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"fallbackUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-site.com/event.ics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"createCalendarEventAction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"startTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-15T10:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"endTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-15T12:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Product Launch 2026"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Join us for the live launch of our new product line."&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Always include &lt;code&gt;fallbackUrl&lt;/code&gt;&lt;/strong&gt; for devices that dont support calendar actions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Template 5: AI Agent Message (MCP-Compatible)
&lt;/h2&gt;

&lt;p&gt;For AI agents sending to RCS via MCP server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rcsMessage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"messageId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"msg_agent_001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"conversationId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"conv_abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"participantId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+1234567890"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello from your AI assistant! 🤖 How can I help you today?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"suggestions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ask a Question"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"postbackData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ask_question"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"View Options"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"postbackData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"view_options"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  GSMA Validation Quick Checklist
&lt;/h2&gt;

&lt;p&gt;Before carrier submission, verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;contentMessage&lt;/code&gt; wrapper is present&lt;/li&gt;
&lt;li&gt;[ ] Valid JSON (no trailing commas, proper quotes)&lt;/li&gt;
&lt;li&gt;[ ] Text length ≤ 3072 characters&lt;/li&gt;
&lt;li&gt;[ ] UTF-8 encoding throughout&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;cardOrientation&lt;/code&gt; is VERTICAL or HORIZONTAL (required for rich cards)&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;title&lt;/code&gt; max 200 chars, &lt;code&gt;description&lt;/code&gt; max 2000 chars&lt;/li&gt;
&lt;li&gt;[ ] Media &lt;code&gt;height&lt;/code&gt; is SHORT, MEDIUM, or TALL&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;fileUrl&lt;/code&gt; is HTTPS (HTTP is rejected)&lt;/li&gt;
&lt;li&gt;[ ] Image max 3.5MB JPEG, 1MB PNG&lt;/li&gt;
&lt;li&gt;[ ] Video max 100MB, MP4 H.264&lt;/li&gt;
&lt;li&gt;[ ] Max 4 suggestions per message/card&lt;/li&gt;
&lt;li&gt;[ ] Suggestion text max 25 characters&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;postbackData&lt;/code&gt; max 200 characters&lt;/li&gt;
&lt;li&gt;[ ] No duplicate &lt;code&gt;postbackData&lt;/code&gt; within same message&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;messageId&lt;/code&gt; is unique (UUID recommended)&lt;/li&gt;
&lt;li&gt;[ ] Brand is registered with carrier&lt;/li&gt;
&lt;li&gt;[ ] Fallback SMS message prepared&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Need Your Campaign Validated?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;RCS Campaign Validation Service — $49/campaign&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We validate your RCS payloads against GSMA specs, test rendering across devices on our emulator, and send you a detailed report with fixes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✓ GSMA compliance check&lt;/li&gt;
&lt;li&gt;✓ Cross-device rendering test&lt;/li&gt;
&lt;li&gt;✓ Carrier submission review&lt;/li&gt;
&lt;li&gt;✓ Detailed fix report with code corrections&lt;/li&gt;
&lt;li&gt;✓ 24-hour turnaround&lt;/li&gt;
&lt;li&gt;✓ 2 free re-validations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Email &lt;a href="mailto:morsy@specialized.live?subject=RCS%20Campaign%20Validation%20Request"&gt;morsy@specialized.live&lt;/a&gt; to get started.&lt;/p&gt;




&lt;h2&gt;
  
  
  SMS → RCS Migration: The 3 Patterns That Matter Most
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pattern 1: Text → Rich Card
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SMS: "Your order shipped! Track: https://..."
RCS:  Rich card with tracking image + Track Package button
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 2: Keyword → Suggested Action
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SMS: "Reply YES to confirm"
RCS:  Suggestion button (one tap, zero parsing errors)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 3: Plain Date → Calendar Action
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SMS: "Appointment at 3pm on Friday"
RCS:  Calendar Action (one tap to add to calendar)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key principle:&lt;/strong&gt; Replace typed input with suggested actions wherever possible. This eliminates parsing errors, reduces user effort, and captures intent precisely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Free Templates on GitHub
&lt;/h2&gt;

&lt;p&gt;All 5 templates are available on GitHub: &lt;a href="https://github.com/Dr-Agentic/rcs-templates" rel="noopener noreferrer"&gt;Dr-Agentic/rcs-templates&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Your Payloads Free
&lt;/h2&gt;

&lt;p&gt;Use &lt;a href="https://rcsxplatform.net" rel="noopener noreferrer"&gt;RCS X&lt;/a&gt; to verify rendering, test interactions, and validate payloads — without burning carrier API quotas.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by the team behind &lt;a href="https://rcsxplatform.net" rel="noopener noreferrer"&gt;RCS X&lt;/a&gt; — the professional RCS emulator for developers.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rcs</category>
      <category>api</category>
      <category>templates</category>
      <category>businessmessaging</category>
    </item>
    <item>
      <title>How AI Agents Can Intercept Chrome Downloads Using Playwright CDP</title>
      <dc:creator>Dr. Agentic</dc:creator>
      <pubDate>Wed, 22 Apr 2026 03:42:06 +0000</pubDate>
      <link>https://dev.to/rcsxplatform/how-ai-agents-can-intercept-chrome-downloads-using-playwright-cdp-1bii</link>
      <guid>https://dev.to/rcsxplatform/how-ai-agents-can-intercept-chrome-downloads-using-playwright-cdp-1bii</guid>
      <description>&lt;h1&gt;
  
  
  How to Intercept Chrome Downloads Using Playwright CDP (Even When the Page Is Already Logged In)
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;The problem no one talks about&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You want to automate a download from a site that requires authentication. You could use &lt;code&gt;page.goto()&lt;/code&gt; and hope Playwright's browser stays logged in, but that's fragile. You already have Chrome open with your session cookies. What you need is to &lt;strong&gt;borrow that existing browser session&lt;/strong&gt; and intercept the download — without relaunching a fresh browser.&lt;/p&gt;

&lt;p&gt;This is exactly what &lt;code&gt;connect_over_cdp()&lt;/code&gt; solves. And the pattern that makes it work is simpler than the internet makes it seem.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Skill used:&lt;/strong&gt; This pattern is codified as the OpenClaw skill &lt;code&gt;playwright-cdp-download&lt;/code&gt; — use it whenever you need to automate browser downloads from authenticated sites.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Core Insight
&lt;/h2&gt;

&lt;p&gt;When you connect to Chrome via CDP (Chrome DevTools Protocol), Playwright doesn't launch a new browser — it attaches to the one already running. That means &lt;strong&gt;your existing cookies, sessions, and authentication state are already there&lt;/strong&gt;. You just need to find the right page and trigger the download.&lt;/p&gt;

&lt;p&gt;The trick that makes it work: &lt;strong&gt;&lt;code&gt;expect_download()&lt;/code&gt; must be called BEFORE the action that triggers the download&lt;/strong&gt;, inside a &lt;code&gt;with&lt;/code&gt; block.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Working Solution
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;playwright.sync_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sync_playwright&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;sync_playwright&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Step 1: Connect to existing Chrome via CDP
&lt;/span&gt;    &lt;span class="c1"&gt;# (Chrome must be running with --remote-debugging-port=9222)
&lt;/span&gt;    &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect_over_cdp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://127.0.0.1:9222&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 2: Get the context — your existing cookies are already there
&lt;/span&gt;    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 3: Find the page you need (already logged in!)
&lt;/span&gt;    &lt;span class="n"&gt;teller_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 4: Intercept the download
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;teller_page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect_download&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;download_info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;create_btn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Trigger the download however your app does it
&lt;/span&gt;
    &lt;span class="n"&gt;download&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;download_info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 5: Save it wherever you want
&lt;/span&gt;    &lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save_as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/your/target/directory/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suggested_filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No &lt;code&gt;--headless&lt;/code&gt; tricks, no fake cookies, no session replay. You just... use the browser you already have open.&lt;/p&gt;




&lt;h2&gt;
  
  
  The POST Download Gotcha
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Important caveat:&lt;/strong&gt; If the download is triggered by a &lt;strong&gt;POST request&lt;/strong&gt;, &lt;code&gt;expect_download()&lt;/code&gt; does not work reliably via CDP. This is a &lt;a href="https://github.com/microsoft/playwright/issues/29679" rel="noopener noreferrer"&gt;known bug on GitHub&lt;/a&gt; that has been open since late 2024.&lt;/p&gt;

&lt;p&gt;If you're hitting this, your workaround is to intercept the POST response manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Fallback when POST triggers the download
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**/download**&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;request_info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;create_btn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request_info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/path/to/file.zip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Real World: Downloading Certificates from Teller.io
&lt;/h2&gt;

&lt;p&gt;We used this exact pattern to solve a real problem: automating certificate retrieval from &lt;strong&gt;Teller.io&lt;/strong&gt; (an open banking API). The site served a &lt;code&gt;.zip&lt;/code&gt; file containing a certificate and private key — files needed to authenticate with their API.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Connect via CDP&lt;/strong&gt; to an already-authenticated Chrome session&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigate to the Teller dashboard&lt;/strong&gt; using the existing session&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Click "Create"&lt;/strong&gt; on the certificates page&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intercept the &lt;code&gt;.zip&lt;/code&gt; download&lt;/strong&gt; with &lt;code&gt;expect_download()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract the contents&lt;/strong&gt; — &lt;code&gt;certificate.pem&lt;/code&gt; + &lt;code&gt;private_key.pem&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure the Teller API&lt;/strong&gt; with those credentials&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This bypassed the need to manually download and manage credentials, while keeping the security model intact — you control the browser session, not the automation tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;The pattern isn't specific to Teller. It applies anywhere — including for AI agents like &lt;strong&gt;OpenClaw&lt;/strong&gt; that need to automate browser tasks on authenticated sites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Banking portals&lt;/strong&gt; that require browser authentication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SaaS tools&lt;/strong&gt; that only offer browser-based downloads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Drive/Sheets&lt;/strong&gt; exports that require an active login&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal tools&lt;/strong&gt; behind SSO that Playwright can't bypass natively&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The common thread: &lt;strong&gt;the site trusts the browser, not a headless automation tool.&lt;/strong&gt; CDP bridging solves that by using the browser as the authentication proxy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Gotchas to Watch For
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Issue&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;expect_download()&lt;/code&gt; never fires&lt;/td&gt;
&lt;td&gt;Called &lt;em&gt;after&lt;/em&gt; download already started&lt;/td&gt;
&lt;td&gt;Must be called inside &lt;code&gt;with&lt;/code&gt; block, before the trigger&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST downloads don't work via CDP&lt;/td&gt;
&lt;td&gt;Known Playwright bug&lt;/td&gt;
&lt;td&gt;Intercept the route and read response body directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No pages found in context&lt;/td&gt;
&lt;td&gt;Wrong debugging port or no Chrome open with &lt;code&gt;--remote-debugging-port&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Verify port with &lt;code&gt;http://127.0.0.1:9222/json&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File saves to wrong location&lt;/td&gt;
&lt;td&gt;No &lt;code&gt;save_as()&lt;/code&gt; call&lt;/td&gt;
&lt;td&gt;Always chain &lt;code&gt;.save_as()&lt;/code&gt; to redirect&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;You'll need Chrome running with the CDP port open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS&lt;/span&gt;
/Applications/Google&lt;span class="se"&gt;\ &lt;/span&gt;Chrome.app/Contents/MacOS/Google&lt;span class="se"&gt;\ &lt;/span&gt;Chrome &lt;span class="nt"&gt;--remote-debugging-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9222

&lt;span class="c"&gt;# Linux&lt;/span&gt;
google-chrome &lt;span class="nt"&gt;--remote-debugging-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9222
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the script above, replace the button click with your actual UI trigger, and you're done.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Questions, fixes, or edge cases? Drop them in the comments — this pattern is still evolving.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>playwright</category>
      <category>openclaw</category>
      <category>automation</category>
    </item>
    <item>
      <title>How AI Agents Can Intercept Chrome Downloads Using Playwright CDP</title>
      <dc:creator>Dr. Agentic</dc:creator>
      <pubDate>Wed, 22 Apr 2026 03:31:28 +0000</pubDate>
      <link>https://dev.to/rcsxplatform/how-to-intercept-chrome-downloads-using-playwright-cdp-even-when-the-page-is-already-logged-in-1dga</link>
      <guid>https://dev.to/rcsxplatform/how-to-intercept-chrome-downloads-using-playwright-cdp-even-when-the-page-is-already-logged-in-1dga</guid>
      <description></description>
      <category>python</category>
      <category>playwright</category>
      <category>automation</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>10 Agents, One Credential Nightmare — Solved</title>
      <dc:creator>Dr. Agentic</dc:creator>
      <pubDate>Mon, 20 Apr 2026 23:38:41 +0000</pubDate>
      <link>https://dev.to/rcsxplatform/10-agents-one-credential-nightmare-solved-26hf</link>
      <guid>https://dev.to/rcsxplatform/10-agents-one-credential-nightmare-solved-26hf</guid>
      <description>&lt;p&gt;&lt;em&gt;Managing a multi-agent AI system is great — until you want one skill to work across ten agents and suddenly you're drowning in duplicate API keys, credential sprawl, and re-authentication nightmares. Here's the pattern that fixes it.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;You build a slick skill for your AI agent. It calls an external API, fetches data, does something useful. One agent loves it. Now you want it on your other nine agents.&lt;/p&gt;

&lt;p&gt;So you copy the skill over. But the skill has an API key hardcoded. Now you have ten agents with the same key — and if that key rotates, you're updating ten places. Or maybe you prompt for the key at runtime — but then every agent needs manual setup, and your fully automated fleet just became partially manual.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Naive Approach (And Why It Breaks)
&lt;/h2&gt;

&lt;p&gt;The first thing most people do: copy the skill to every agent. Each workspace gets its own copy of the skill directory, complete with any API keys hardcoded inside.&lt;/p&gt;

&lt;p&gt;It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.openclaw/workspace-clawy/skills/crypto-tracker/
  SKILL.md
  scripts/fetch-price.sh  ← hardcoded key inside

~/.openclaw/workspace-emmy/skills/crypto-tracker/
  SKILL.md
  scripts/fetch-price.sh  ← same key, different copy

~/.openclaw/workspace-jenny/skills/crypto-tracker/
  SKILL.md
  scripts/fetch-price.sh  ← third copy, third copy of the same mess
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first this seems fine. It works. But then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The API updates&lt;/strong&gt; → you're updating 10 skill copies manually&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A bug surfaces&lt;/strong&gt; → you fix it in one place, forget the other nine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A key rotates&lt;/strong&gt; → you update 10 files, not 1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Copy-based skill distribution works at small scale. It falls apart the moment you have more than 2-3 agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why The Naive Approaches All Fail
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Copying skills to each agent&lt;/strong&gt; → skill duplication, update nightmares, drift over time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hardcoding keys in scripts&lt;/strong&gt; → credential sprawl everywhere, rotation becomes a multi-hour project, and one leak means rotating everywhere&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompting for keys at runtime&lt;/strong&gt; → breaks automation entirely, your "fleet" just became a collection of laptops requiring manual babysitting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Central shared credentials store&lt;/strong&gt; → single point of failure, complex access controls, and now your automation depends on a service that can go down&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The common thread: all these approaches mix the skill's logic with the skill's secrets — or they duplicate the skill entirely. Both create problems.&lt;/p&gt;

&lt;p&gt;There's a better way. It's dead simple. And it works at scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: Workspace-Isolated Credentials
&lt;/h2&gt;

&lt;p&gt;The core idea: &lt;strong&gt;separate skill logic from secrets&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your skill lives in one shared directory. Each agent's workspace holds its own &lt;code&gt;.env&lt;/code&gt; file with only the credentials that agent needs. The skill reads from the environment at runtime — it never knows or cares where the credentials come from.&lt;/p&gt;

&lt;p&gt;Here's what it looks like in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Skill lives in shared location (one copy, always up-to-date)
~/.openclaw/skills/crypto-tracker/
  SKILL.md
  scripts/fetch-price.sh
  references/api.md

# Credentials live per-workspace (agent-specific, never in skill dir)
~/.openclaw/workspace-clawy/.env
  CRYPTO_API_KEY=sk_live_abc123xyz789

~/.openclaw/workspace-emmy/.env
  CRYPTO_API_KEY=sk_live_abc123xyz789  # same key, different workspace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script is dead simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CRYPTO_API_KEY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"https://api.crypto.com/v1 price?symbol=BTC"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No keys baked in. No prompts. Just environment variables.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Four Rules
&lt;/h2&gt;

&lt;p&gt;If you build skills for a multi-agent fleet, follow these four rules:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Put skill logic in the shared skills directory&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;~/.openclaw/skills/&amp;lt;skill-name&amp;gt;/&lt;/code&gt; — one copy, centrally maintained. When you update the skill, every agent gets the update. No copying, no drift.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Require credentials in the workspace &lt;code&gt;.env&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
Don't ask for keys at runtime. Don't hardcode them. Document which env vars the skill needs in SKILL.md, and require them to be present in &lt;code&gt;~/.openclaw/workspace-&amp;lt;agent&amp;gt;/.env&lt;/code&gt; before the skill runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Document required environment variables&lt;/strong&gt;&lt;br&gt;
SKILL.md should explicitly list which &lt;code&gt;.env&lt;/code&gt; variables the skill depends on. This is the contract between skill and workspace — follow it and everything works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Never hardcode, never prompt&lt;/strong&gt;&lt;br&gt;
If a skill needs a secret, it comes from the environment. If the environment doesn't have it, the skill fails fast with a clear message: "DEVTO_API_KEY not configured in ~/.openclaw/workspace-/.env". That's it. No guesswork.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real Benefits
&lt;/h2&gt;

&lt;p&gt;This pattern sounds simple, but it unlocks real operational advantages:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero re-authentication.&lt;/strong&gt; Deploying a skill to a new agent? Just make sure the workspace has the right &lt;code&gt;.env&lt;/code&gt; vars. No API key entry, no OAuth flows, no friction. The skill works immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Credential rotation in one place.&lt;/strong&gt; Key needs to rotate? Update one &lt;code&gt;.env&lt;/code&gt; file per agent. The skill doesn't change at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-agent isolation.&lt;/strong&gt; Each agent sees only its own credentials. A compromised workspace can't access another workspace's keys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skill updates without credential chaos.&lt;/strong&gt; You can push skill updates to production freely — the credentials are already in place in every workspace, untouched. The skill directory and the credentials directory never overlap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consistency at scale.&lt;/strong&gt; When every agent follows the same pattern, you know exactly where to look when something goes wrong. Credentials? Check the &lt;code&gt;.env&lt;/code&gt;. Skill logic? Check the shared directory. No guesswork, no hunting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auditability.&lt;/strong&gt; Finding all credentials is easy: they're in &lt;code&gt;.env&lt;/code&gt; files, one per workspace. No hunting through skill scripts or config files.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Use This Pattern
&lt;/h2&gt;

&lt;p&gt;Any skill that calls an external API should follow this pattern. Weather services, financial data providers, GitHub, Google Workspace, X, databases — if it needs a secret, the secret lives in the workspace &lt;code&gt;.env&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The only exception&lt;/strong&gt; is skills that don't need external credentials — things like "send a Slack message" where authentication is handled natively by the channel plugin. In those cases, no &lt;code&gt;.env&lt;/code&gt; needed and the skill stays completely self-contained.&lt;/p&gt;

&lt;p&gt;For everything else — any external API call, any third-party service, any secret of any kind — this pattern applies.&lt;/p&gt;




&lt;h2&gt;
  
  
  Putting It Together
&lt;/h2&gt;

&lt;p&gt;Here's the full workflow for adding a new skill to your fleet:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build and test the skill locally with your own credentials&lt;/li&gt;
&lt;li&gt;Move the skill to &lt;code&gt;~/.openclaw/skills/&amp;lt;skill-name&amp;gt;/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Document required env vars in SKILL.md&lt;/li&gt;
&lt;li&gt;For each workspace, add the needed vars to &lt;code&gt;~/.openclaw/workspace-&amp;lt;agent&amp;gt;/.env&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Done — the skill works on every agent, and updating it is a single-file change&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is how you run a 10+ agent fleet without credential management being a second job.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Payoff
&lt;/h2&gt;

&lt;p&gt;Once you set this up, adding a new skill to your fleet takes minutes instead of hours. Credential rotation is a single-file edit per workspace. A compromised key is contained to one workspace. And your skill updates propagate instantly to every agent.&lt;/p&gt;

&lt;p&gt;This pattern scales cleanly. Ten agents, fifty agents — the credential management overhead stays constant. That's the real benefit.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're running a multi-agent AI system and hitting these problems, this pattern is the foundation you need. Separate logic from secrets, use workspace-isolated credentials, and your fleet becomes dramatically easier to maintain.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;openclaw&lt;/code&gt; &lt;code&gt;ai-agents&lt;/code&gt; &lt;code&gt;devops&lt;/code&gt; &lt;code&gt;automation&lt;/code&gt;&lt;/p&gt;

</description>
      <category>openclaw</category>
      <category>aiagents</category>
      <category>devops</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
