<?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: Jack M</title>
    <description>The latest articles on DEV Community by Jack M (@jackm-singularity).</description>
    <link>https://dev.to/jackm-singularity</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%2F3953435%2F35a14dd7-6df4-4155-95f8-b475eb620f37.png</url>
      <title>DEV Community: Jack M</title>
      <link>https://dev.to/jackm-singularity</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jackm-singularity"/>
    <language>en</language>
    <item>
      <title>AI Agent Data Deletion Pipeline: Remove Prompts, Traces, and Memory for Real</title>
      <dc:creator>Jack M</dc:creator>
      <pubDate>Mon, 17 Aug 2026 03:36:05 +0000</pubDate>
      <link>https://dev.to/jackm-singularity/ai-agent-data-deletion-pipeline-remove-prompts-traces-and-memory-for-real-7nh</link>
      <guid>https://dev.to/jackm-singularity/ai-agent-data-deletion-pipeline-remove-prompts-traces-and-memory-for-real-7nh</guid>
      <description>&lt;p&gt;A delete button is easy to ship. Real deletion is much harder.&lt;/p&gt;

&lt;p&gt;That gap matters more with AI agents than with normal apps because one user action can scatter data across prompts, traces, memory stores, vector indexes, tool logs, temporary files, model gateways, retry queues, and analytics events. If your product only deletes the visible chat row, the user may be gone from the UI while their data still lives in five backend systems.&lt;/p&gt;

&lt;p&gt;For AI app builders, this is not just a compliance chore. It is a trust feature. Users will forgive slow answers faster than they forgive a system that says “deleted” but keeps enough context to reconstruct the conversation later.&lt;/p&gt;

&lt;p&gt;This guide shows how to design an AI agent data deletion pipeline that removes user data for real, proves what happened, and avoids breaking production workflows while doing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AI deletion is different
&lt;/h2&gt;

&lt;p&gt;Traditional deletion usually starts with a known record: a user, a project, a file, a message, or a row in a database. AI agents create a messier shape.&lt;/p&gt;

&lt;p&gt;A single agent run may include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;raw user prompt&lt;/li&gt;
&lt;li&gt;rewritten prompt&lt;/li&gt;
&lt;li&gt;retrieved documents&lt;/li&gt;
&lt;li&gt;embeddings&lt;/li&gt;
&lt;li&gt;cached model input&lt;/li&gt;
&lt;li&gt;tool arguments&lt;/li&gt;
&lt;li&gt;tool responses&lt;/li&gt;
&lt;li&gt;browser snapshots&lt;/li&gt;
&lt;li&gt;screenshots&lt;/li&gt;
&lt;li&gt;uploaded files&lt;/li&gt;
&lt;li&gt;generated artifacts&lt;/li&gt;
&lt;li&gt;chain-of-thought-like internal notes you should not store&lt;/li&gt;
&lt;li&gt;memory summaries&lt;/li&gt;
&lt;li&gt;trace logs&lt;/li&gt;
&lt;li&gt;billing metadata&lt;/li&gt;
&lt;li&gt;support debug events&lt;/li&gt;
&lt;li&gt;queue state&lt;/li&gt;
&lt;li&gt;approval comments&lt;/li&gt;
&lt;li&gt;eval replay packets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of those records are user-visible. Many are not.&lt;/p&gt;

&lt;p&gt;That is why “delete the chat” is not enough. Agent deletion needs a map of every place where user data can land, plus a workflow that deletes, redacts, or tombstones each location according to its risk and legal retention rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode: UI deletion without backend deletion
&lt;/h2&gt;

&lt;p&gt;The dangerous pattern looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user clicks delete.&lt;/li&gt;
&lt;li&gt;The app removes the conversation from the sidebar.&lt;/li&gt;
&lt;li&gt;The backend keeps traces, embeddings, prompts, and tool logs for debugging.&lt;/li&gt;
&lt;li&gt;A restored pointer, support export, analytics query, or vector search can still reveal the old content.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From the product team's view, the item is gone. From the user's view, the promise was deletion. From the system's view, it was only hidden.&lt;/p&gt;

&lt;p&gt;AI makes this worse because deleted content can reappear indirectly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a memory summary keeps the important facts&lt;/li&gt;
&lt;li&gt;an embedding still retrieves the old document&lt;/li&gt;
&lt;li&gt;a cached prompt remains in a gateway&lt;/li&gt;
&lt;li&gt;a support trace includes tool arguments&lt;/li&gt;
&lt;li&gt;an agent artifact contains copied text&lt;/li&gt;
&lt;li&gt;a fine-tuning dataset accidentally includes the run&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real deletion means removing the data path, not just the UI path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a deletion inventory
&lt;/h2&gt;

&lt;p&gt;Before writing deletion code, list every storage surface. Keep this inventory in your repo, not in someone's head.&lt;/p&gt;

&lt;p&gt;A useful inventory table looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Surface&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Contains user data?&lt;/th&gt;
&lt;th&gt;Deletion action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Primary DB&lt;/td&gt;
&lt;td&gt;conversations, messages&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;hard delete or tombstone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent runs&lt;/td&gt;
&lt;td&gt;run steps, tool calls&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;redact payloads, keep minimal metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector DB&lt;/td&gt;
&lt;td&gt;embeddings, chunks&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;delete by source id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object storage&lt;/td&gt;
&lt;td&gt;uploads, screenshots&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;delete object + variants&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory store&lt;/td&gt;
&lt;td&gt;user profile, summaries&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;delete or recompute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Queue&lt;/td&gt;
&lt;td&gt;pending jobs&lt;/td&gt;
&lt;td&gt;Maybe&lt;/td&gt;
&lt;td&gt;cancel and purge payload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache&lt;/td&gt;
&lt;td&gt;prompt/result cache&lt;/td&gt;
&lt;td&gt;Maybe&lt;/td&gt;
&lt;td&gt;purge by key prefix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logs&lt;/td&gt;
&lt;td&gt;app logs, traces&lt;/td&gt;
&lt;td&gt;Often&lt;/td&gt;
&lt;td&gt;redact or expire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analytics&lt;/td&gt;
&lt;td&gt;usage events&lt;/td&gt;
&lt;td&gt;Sometimes&lt;/td&gt;
&lt;td&gt;pseudonymize&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Billing&lt;/td&gt;
&lt;td&gt;invoice events&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;retain non-content metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key column is “deletion action.” Not every record should be handled the same way.&lt;/p&gt;

&lt;p&gt;For example, billing may need to keep a non-content record that says “12 model calls occurred.” But it should not keep the raw prompt. Observability may keep latency, token count, model name, and error code while dropping message text and tool payloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a data lineage ID for every agent run
&lt;/h2&gt;

&lt;p&gt;Deletion fails when systems cannot find related records. The fix is simple but often skipped: give every user-owned data object a lineage ID.&lt;/p&gt;

&lt;p&gt;A lineage ID connects the root object to all derived objects.&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;type&lt;/span&gt; &lt;span class="nx"&gt;DataLineage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;tenantId&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="nl"&gt;userId&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="nl"&gt;subjectType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;conversation&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;file&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;agent_run&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;memory&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;subjectId&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="nl"&gt;lineageId&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every derived record should carry that lineage ID:&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;type&lt;/span&gt; &lt;span class="nx"&gt;AgentTraceEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;traceId&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="nl"&gt;lineageId&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="nl"&gt;tenantId&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="nl"&gt;runId&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="nl"&gt;step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;retrieve&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;model_call&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;tool_call&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;approval&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;payloadRef&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="nl"&gt;redactionState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;raw&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;redacted&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;deleted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;createdAt&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not rely only on foreign keys to the visible chat. AI systems often create records outside the main app database. The vector store, object bucket, and model gateway may not know your conversation schema. They can know a lineage ID.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate content deletion from audit retention
&lt;/h2&gt;

&lt;p&gt;A common mistake is treating deletion as all-or-nothing. That creates two bad outcomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;teams keep too much because they need audit history&lt;/li&gt;
&lt;li&gt;teams delete too much and lose the ability to explain abuse, billing, or incidents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, split records into content and metadata.&lt;/p&gt;

&lt;p&gt;Content includes prompts, responses, retrieved chunks, uploaded text, screenshots, tool arguments, and memory facts.&lt;/p&gt;

&lt;p&gt;Metadata includes timestamps, actor IDs, token counts, model names, status codes, cost totals, approval state, deletion receipt IDs, and policy decisions.&lt;/p&gt;

&lt;p&gt;When a deletion request arrives, content should be removed or irreversibly redacted. Minimal metadata can remain if you need it for security, billing, legal, or operational reasons.&lt;/p&gt;

&lt;p&gt;Example deletion-safe event:&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;"event_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"evt_93"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lineage_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lin_abc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tenant_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tenant_7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"run_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run_42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event_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;"model_call"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deleted"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"primary-chat-model"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1840&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;420&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&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-08-17T03:32: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;"deleted_at"&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-08-17T03:41: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;"deletion_receipt_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"del_771"&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;Notice what is missing: no prompt, no answer, no retrieved chunk, no tool result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build deletion as a workflow, not a button handler
&lt;/h2&gt;

&lt;p&gt;A delete request should create a durable deletion job. Do not try to delete everything inside one HTTP request.&lt;/p&gt;

&lt;p&gt;Use a workflow like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Accept request.&lt;/li&gt;
&lt;li&gt;Create deletion receipt in &lt;code&gt;pending&lt;/code&gt; state.&lt;/li&gt;
&lt;li&gt;Freeze or cancel active agent runs for the lineage ID.&lt;/li&gt;
&lt;li&gt;Discover related records across systems.&lt;/li&gt;
&lt;li&gt;Delete or redact content in each system.&lt;/li&gt;
&lt;li&gt;Verify each deletion target.&lt;/li&gt;
&lt;li&gt;Mark receipt as &lt;code&gt;completed&lt;/code&gt;, &lt;code&gt;partial&lt;/code&gt;, or &lt;code&gt;failed&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Expose a user-safe deletion status.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A simple receipt schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;deletion_receipts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;requested_by&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;subject_type&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;subject_id&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;lineage_id&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;targets&lt;/span&gt; &lt;span class="n"&gt;jsonb&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;completed_at&lt;/span&gt; &lt;span class="n"&gt;timestamptz&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each target should track its own state:&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;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vector_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;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"delete_by_lineage_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"completed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"records_matched"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"records_remaining"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&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 gives developers and support teams a safe way to answer, “What happened when the user deleted this?” without exposing the deleted data again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cancel active runs before deleting memory
&lt;/h2&gt;

&lt;p&gt;AI agents are often long-running. A deletion request can arrive while an agent is still working with the soon-to-be-deleted context.&lt;/p&gt;

&lt;p&gt;Handle this first.&lt;/p&gt;

&lt;p&gt;When deletion starts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;block new runs for the subject&lt;/li&gt;
&lt;li&gt;cancel queued jobs using that lineage ID&lt;/li&gt;
&lt;li&gt;revoke leases for active workers&lt;/li&gt;
&lt;li&gt;stop scheduled follow-up tasks&lt;/li&gt;
&lt;li&gt;invalidate approval links&lt;/li&gt;
&lt;li&gt;clear pending tool calls&lt;/li&gt;
&lt;li&gt;prevent memory writes from completing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you skip this, a worker can recreate deleted data after the deletion job finishes.&lt;/p&gt;

&lt;p&gt;A simple runtime check:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;assertLineageIsActive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lineageId&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deletion&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deletionReceipts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findActive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lineageId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deletion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Lineage &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lineageId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is under deletion`&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;p&gt;Call this before retrieval, model calls, memory writes, and tool execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delete vector data by source, not by similarity
&lt;/h2&gt;

&lt;p&gt;Never delete embeddings by running a similarity search for the user's text. That is slow, incomplete, and risky.&lt;/p&gt;

&lt;p&gt;Every vector chunk should include metadata:&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;"chunk_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chunk_22"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tenant_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tenant_7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_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;"conversation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_id"&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_99"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lineage_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lin_abc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_by_run_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run_42"&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;Then deletion is deterministic:&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;vectorStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lineage_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lineageId&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;After deletion, run a metadata lookup for that lineage ID. The result should be zero. Do not ask the model whether the data is gone. Ask the storage system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recompute memory instead of patching it blindly
&lt;/h2&gt;

&lt;p&gt;Agent memory is tricky because it often stores summaries, not exact source text.&lt;/p&gt;

&lt;p&gt;If a memory summary was created from ten conversations and one is deleted, you may not know which sentence came from which source unless you tracked provenance.&lt;/p&gt;

&lt;p&gt;The safer pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;store memory facts with source lineage IDs&lt;/li&gt;
&lt;li&gt;delete facts derived only from deleted lineage&lt;/li&gt;
&lt;li&gt;recompute mixed summaries from remaining sources&lt;/li&gt;
&lt;li&gt;mark old summaries as stale during recompute&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&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;type&lt;/span&gt; &lt;span class="nx"&gt;MemoryFact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;factId&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="nl"&gt;tenantId&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="nl"&gt;userId&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="nl"&gt;text&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="nl"&gt;sourceLineageIds&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="nl"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;active&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;stale&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;deleted&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;If &lt;code&gt;sourceLineageIds&lt;/code&gt; includes deleted data and also active data, do not keep the old sentence unchanged. Rebuild it from active sources or remove it.&lt;/p&gt;

&lt;p&gt;This is where many AI systems leak deleted data: the raw chat is gone, but the “user prefers quarterly revenue charts” memory remains because it was copied into a profile summary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Be careful with model provider retention
&lt;/h2&gt;

&lt;p&gt;Your deletion pipeline can control your systems. It may not be able to delete every transient copy inside a model provider.&lt;/p&gt;

&lt;p&gt;That means you need clear data-routing rules before the deletion request happens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;avoid sending sensitive content to providers that train on inputs&lt;/li&gt;
&lt;li&gt;use zero-retention or enterprise controls when available&lt;/li&gt;
&lt;li&gt;keep request IDs for provider-side deletion support if offered&lt;/li&gt;
&lt;li&gt;avoid storing full prompts in your gateway logs&lt;/li&gt;
&lt;li&gt;document what is retained, where, and for how long&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not promise more than your architecture can deliver. If a provider retains abuse-monitoring logs for a fixed window, say so in your internal policy and user-facing terms.&lt;/p&gt;

&lt;p&gt;Trust improves when deletion promises are precise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add deletion tests to CI
&lt;/h2&gt;

&lt;p&gt;Deletion should be tested like payments or authentication.&lt;/p&gt;

&lt;p&gt;Create a synthetic user with known marker text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DELETE_TEST_MARKER_7f3a9b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run a normal agent workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;send a prompt with the marker&lt;/li&gt;
&lt;li&gt;create a retrieval chunk&lt;/li&gt;
&lt;li&gt;call a tool&lt;/li&gt;
&lt;li&gt;write memory&lt;/li&gt;
&lt;li&gt;store a trace&lt;/li&gt;
&lt;li&gt;upload an artifact&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then trigger deletion and assert the marker is gone from every content surface.&lt;/p&gt;

&lt;p&gt;Test targets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;relational DB content columns&lt;/li&gt;
&lt;li&gt;vector metadata and chunks&lt;/li&gt;
&lt;li&gt;object storage&lt;/li&gt;
&lt;li&gt;prompt cache&lt;/li&gt;
&lt;li&gt;trace payloads&lt;/li&gt;
&lt;li&gt;memory store&lt;/li&gt;
&lt;li&gt;queue payloads&lt;/li&gt;
&lt;li&gt;exported debug bundles&lt;/li&gt;
&lt;li&gt;support search&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A crude but effective test:&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;surfaces&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;collectDebugSurfaces&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;marker&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;for &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;surface&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;surfaces&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;surface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Deletion marker found in &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;surface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;p&gt;This test will catch the boring leaks that become serious later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give users a deletion status without exposing internals
&lt;/h2&gt;

&lt;p&gt;Users do not need to see your vector store target list. They need a truthful status.&lt;/p&gt;

&lt;p&gt;Good statuses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deletion requested&lt;/li&gt;
&lt;li&gt;deletion in progress&lt;/li&gt;
&lt;li&gt;deleted from active systems&lt;/li&gt;
&lt;li&gt;retained only where required for security or billing&lt;/li&gt;
&lt;li&gt;deletion failed; support has been notified&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bad statuses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“deleted” before background jobs finish&lt;/li&gt;
&lt;li&gt;“permanently deleted” when provider retention still applies&lt;/li&gt;
&lt;li&gt;“removed from your account” when traces remain searchable internally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For developer tools, an admin deletion receipt can show target categories without exposing deleted content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical rollout plan
&lt;/h2&gt;

&lt;p&gt;Start with the highest-risk surfaces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 1: Stop obvious false deletion&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;delete visible messages&lt;/li&gt;
&lt;li&gt;purge vector chunks by source ID&lt;/li&gt;
&lt;li&gt;remove uploaded files&lt;/li&gt;
&lt;li&gt;redact trace payloads&lt;/li&gt;
&lt;li&gt;cancel active runs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phase 2: Add lineage everywhere&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add &lt;code&gt;lineage_id&lt;/code&gt; to runs, tool calls, memories, cache keys, artifacts, and embeddings&lt;/li&gt;
&lt;li&gt;backfill recent records where possible&lt;/li&gt;
&lt;li&gt;block new storage writes that lack lineage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phase 3: Add receipts and verification&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create deletion receipts&lt;/li&gt;
&lt;li&gt;verify each target&lt;/li&gt;
&lt;li&gt;store deletion metadata&lt;/li&gt;
&lt;li&gt;add support-safe status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phase 4: Add automated tests&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;marker-based deletion tests&lt;/li&gt;
&lt;li&gt;memory recompute tests&lt;/li&gt;
&lt;li&gt;vector deletion tests&lt;/li&gt;
&lt;li&gt;queue cancellation tests&lt;/li&gt;
&lt;li&gt;provider-retention checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This sequence improves trust while building toward a complete deletion system.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is an AI agent data deletion pipeline?
&lt;/h3&gt;

&lt;p&gt;An AI agent data deletion pipeline is a backend workflow that deletes or redacts user data across prompts, traces, embeddings, memory, caches, files, tool logs, queues, and analytics. It is more complete than deleting a visible chat row.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is deleting the conversation enough?
&lt;/h3&gt;

&lt;p&gt;Usually no. The conversation may have created derived records such as vector chunks, memory summaries, trace payloads, tool results, prompt caches, and artifacts. Those need separate deletion or redaction steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should AI logs be hard deleted?
&lt;/h3&gt;

&lt;p&gt;Content-heavy logs should usually be deleted, redacted, or expired quickly. Minimal operational metadata may be retained when needed for billing, abuse prevention, security, or legal reasons. Separate content from metadata so you do not keep raw prompts by accident.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I delete embeddings safely?
&lt;/h3&gt;

&lt;p&gt;Store source metadata such as &lt;code&gt;tenant_id&lt;/code&gt;, &lt;code&gt;source_id&lt;/code&gt;, and &lt;code&gt;lineage_id&lt;/code&gt; with every vector chunk. Delete by metadata filter, then verify that no chunks remain for that lineage ID. Do not rely on similarity search for deletion.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens if an agent is running during deletion?
&lt;/h3&gt;

&lt;p&gt;The deletion workflow should cancel queued work, revoke active worker leases, block new tool calls, and prevent memory writes for that lineage ID. Otherwise, an agent may recreate data after the deletion job finishes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I promise permanent deletion if I use third-party model APIs?
&lt;/h3&gt;

&lt;p&gt;Only if your provider contracts and retention settings support that promise. Many teams should use more precise wording: deleted from active product systems, with limited provider or security retention where applicable.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the first deletion test I should add?
&lt;/h3&gt;

&lt;p&gt;Create a synthetic prompt with a unique marker, let the agent complete a normal workflow, delete it, then search every content surface for that marker. If the marker appears anywhere user content is stored, the pipeline is incomplete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;AI deletion is not a settings-page feature. It is a data architecture feature.&lt;/p&gt;

&lt;p&gt;If agents can read, transform, remember, retrieve, and act on user data, then deletion must follow the same paths. The goal is simple: when a user asks you to remove their data, your system should know where it went, stop it from being reused, delete what can be deleted, redact what must be retained, and produce a receipt you can trust.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>privacy</category>
      <category>security</category>
    </item>
    <item>
      <title>Open-Weight Model Benchmark Harness: Test Cheaper Models Before You Route Traffic</title>
      <dc:creator>Jack M</dc:creator>
      <pubDate>Sun, 16 Aug 2026 03:51:50 +0000</pubDate>
      <link>https://dev.to/jackm-singularity/open-weight-model-benchmark-harness-test-cheaper-models-before-you-route-traffic-42e6</link>
      <guid>https://dev.to/jackm-singularity/open-weight-model-benchmark-harness-test-cheaper-models-before-you-route-traffic-42e6</guid>
      <description>&lt;p&gt;A cheaper model is not cheaper if it silently breaks the workflow.&lt;/p&gt;

&lt;p&gt;That is the trap many AI product teams are walking into as open-weight models get stronger. A model looks good in a leaderboard, a demo feels fast, and the per-token price looks friendly. Then production traffic arrives. Support answers lose citations. JSON starts drifting. Tool calls become noisy. A workflow that looked 40% cheaper now needs retries, escalations, and manual cleanup.&lt;/p&gt;

&lt;p&gt;The safer path is not "use the biggest model forever." That will burn margin. The safer path is a benchmark harness that tests each model against the jobs your product actually performs before you route real users to it.&lt;/p&gt;

&lt;p&gt;This guide shows how to design that harness for AI app builders, solo founders, and engineering teams who want to compare open-weight models, closed models, and local inference without trusting generic benchmarks alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Viral hook and SEO intelligence notes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Chosen hook:&lt;/strong&gt; surprising contrast plus urgent mistake. Open-weight models can cut cost, but only if the full workflow still succeeds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Headline options compared:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open-Weight Model Benchmark Harness: Test Cheaper Models Before You Route Traffic&lt;/li&gt;
&lt;li&gt;Stop Swapping Models by Vibes: Build an Open-Weight Benchmark Harness&lt;/li&gt;
&lt;li&gt;Qwen-Class Model Testing: A Practical Harness for Production AI Apps&lt;/li&gt;
&lt;li&gt;Cheaper LLMs Need Proof: Benchmark Open-Weight Models on Real Workflows&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Option 1 won because it uses the high-intent phrase "open-weight model benchmark harness," states the practical action, and promises a concrete payoff without hype.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Viral keywords:&lt;/strong&gt; open-weight model benchmark harness, open-weight model evaluation, Qwen model testing, LLM benchmark harness, model routing, AI cost optimization, production AI evaluation, LLM regression tests, task-based model selection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prediction scores:&lt;/strong&gt; virality 8/10, CTR 9/10, retention 9/10. The topic is timely because open-weight adoption is accelerating, practical because builders feel model-cost pressure, and sticky because the article gives schemas, code, and routing rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters now
&lt;/h2&gt;

&lt;p&gt;Recent AI news points in the same direction: model choice is becoming more fragmented. Qwen and other open-weight model families are seeing major developer adoption. Agent frameworks, web context tools, workflow automation platforms, and local agent stacks are becoming normal. At the same time, cost-governance reports keep showing a painful gap: many teams can see AI spend after it happens, but they struggle to predict it before traffic runs.&lt;/p&gt;

&lt;p&gt;For developers, that creates a real problem.&lt;/p&gt;

&lt;p&gt;You do not just need a model that is "smart." You need a model that is smart enough for a specific task, cheap enough for your margin, fast enough for your UX, and stable enough for your contracts.&lt;/p&gt;

&lt;p&gt;Generic leaderboards help, but they miss product-level details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your prompt style&lt;/li&gt;
&lt;li&gt;Your schema requirements&lt;/li&gt;
&lt;li&gt;Your retrieval quality&lt;/li&gt;
&lt;li&gt;Your tool contracts&lt;/li&gt;
&lt;li&gt;Your user tone&lt;/li&gt;
&lt;li&gt;Your domain facts&lt;/li&gt;
&lt;li&gt;Your latency budget&lt;/li&gt;
&lt;li&gt;Your failure policy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A benchmark harness turns those messy product requirements into repeatable tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an open-weight model benchmark harness?
&lt;/h2&gt;

&lt;p&gt;An open-weight model benchmark harness is a repeatable test system that runs candidate models against real product tasks and scores whether each result is good enough to receive traffic.&lt;/p&gt;

&lt;p&gt;It usually includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A task catalog&lt;/li&gt;
&lt;li&gt;Test inputs and expected outcomes&lt;/li&gt;
&lt;li&gt;Model adapters&lt;/li&gt;
&lt;li&gt;Prompt versions&lt;/li&gt;
&lt;li&gt;Scoring rules&lt;/li&gt;
&lt;li&gt;Cost and latency measurement&lt;/li&gt;
&lt;li&gt;Regression history&lt;/li&gt;
&lt;li&gt;Routing recommendations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it as CI for model selection.&lt;/p&gt;

&lt;p&gt;Instead of asking, "Is this model good?" you ask better questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can this model answer support questions with citations?&lt;/li&gt;
&lt;li&gt;Can it produce valid JSON for our automation workflow?&lt;/li&gt;
&lt;li&gt;Can it call the right tool without leaking context?&lt;/li&gt;
&lt;li&gt;Can it summarize long documents without losing risky details?&lt;/li&gt;
&lt;li&gt;Can it stay below our cost-per-success target?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last phrase matters: &lt;strong&gt;cost per successful result&lt;/strong&gt;, not cost per token.&lt;/p&gt;

&lt;p&gt;A model with cheap tokens can be expensive if it needs three retries and a human review. A premium model can be cheaper if it succeeds once on a high-value task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick tasks before models
&lt;/h2&gt;

&lt;p&gt;Many teams start backward. They choose a model, then try to make every workflow fit it.&lt;/p&gt;

&lt;p&gt;Start with tasks instead.&lt;/p&gt;

&lt;p&gt;Create a task catalog like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Risk&lt;/th&gt;
&lt;th&gt;Success definition&lt;/th&gt;
&lt;th&gt;Main metric&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rewrite onboarding email&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Helpful, on-brand, no policy issue&lt;/td&gt;
&lt;td&gt;Quality score&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extract invoice fields&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Valid schema, correct totals&lt;/td&gt;
&lt;td&gt;Exact match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Answer account question&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Grounded answer with allowed sources&lt;/td&gt;
&lt;td&gt;Citation accuracy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trigger refund workflow&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Correct tool, approval required&lt;/td&gt;
&lt;td&gt;Policy pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Summarize sales call&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Captures objections and next steps&lt;/td&gt;
&lt;td&gt;Rubric score&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This table does two things.&lt;/p&gt;

&lt;p&gt;First, it stops one benchmark score from hiding different failure modes. A model may write well but fail structured extraction. Another may be great at JSON but weak at long-context reasoning.&lt;/p&gt;

&lt;p&gt;Second, it gives your router a future path. Low-risk tasks can move to cheaper models faster. High-risk tasks need more evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a small golden set
&lt;/h2&gt;

&lt;p&gt;You do not need 10,000 examples to start. You need a small set that represents the ways your product can fail.&lt;/p&gt;

&lt;p&gt;A useful first golden set might contain 30 to 100 cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10 normal cases&lt;/li&gt;
&lt;li&gt;10 edge cases&lt;/li&gt;
&lt;li&gt;10 adversarial or messy cases&lt;/li&gt;
&lt;li&gt;10 historical failures, if you have them&lt;/li&gt;
&lt;li&gt;10 high-value user workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each case should include the user input, context packet, expected behavior, and scoring method.&lt;/p&gt;

&lt;p&gt;Example JSON:&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"support_refund_014"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"support_answer_with_policy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"risk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Can I get a refund if my trial ended yesterday?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&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;"plan"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"team"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"account_age_days"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sources"&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;"refund_policy_v3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terms_v7"&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;"expected"&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;"must_cite"&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;"refund_policy_v3"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"must_not_do"&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;"promise_refund"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invent_exception"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"requires_handoff"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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;"scoring"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rubric_plus_policy_checks"&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;Do not make the expected answer too narrow unless the task requires exact output. For many AI workflows, the goal is not one perfect sentence. The goal is safe, useful behavior inside constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Score more than answer quality
&lt;/h2&gt;

&lt;p&gt;A production benchmark should score the whole workflow.&lt;/p&gt;

&lt;p&gt;Use at least these dimensions:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Correctness
&lt;/h3&gt;

&lt;p&gt;Did the model answer the user or complete the task?&lt;/p&gt;

&lt;p&gt;For extraction, this can be exact match. For reasoning, use a rubric. For RAG, check whether the answer is supported by the retrieved sources.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Structure
&lt;/h3&gt;

&lt;p&gt;Did the output match the contract?&lt;/p&gt;

&lt;p&gt;If your app expects JSON, invalid JSON is a failure. If the model skipped a required field, that is also a failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Grounding
&lt;/h3&gt;

&lt;p&gt;Did the answer rely on approved context?&lt;/p&gt;

&lt;p&gt;This matters for support bots, analytics assistants, document agents, and internal copilots. A fluent answer without evidence is still risky.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Policy safety
&lt;/h3&gt;

&lt;p&gt;Did the model respect risk rules?&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do not promise refunds.&lt;/li&gt;
&lt;li&gt;Do not expose another tenant's data.&lt;/li&gt;
&lt;li&gt;Do not execute write tools without approval.&lt;/li&gt;
&lt;li&gt;Do not reveal hidden prompts.&lt;/li&gt;
&lt;li&gt;Do not store sensitive memory.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Latency
&lt;/h3&gt;

&lt;p&gt;Did it fit the user experience?&lt;/p&gt;

&lt;p&gt;Track time to first token, total response time, queue time, and tool-call time. A cheaper model that doubles latency may hurt activation.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Cost per success
&lt;/h3&gt;

&lt;p&gt;This is the metric builders often miss.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cost_per_success = total_model_cost / successful_runs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can refine it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cost_per_success = (model_cost + tool_cost + retry_cost + review_cost) / successful_runs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That number is much closer to real margin.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple benchmark harness architecture
&lt;/h2&gt;

&lt;p&gt;A minimal harness can be built with plain files, a script, and a database table. You do not need a big evaluation platform on day one.&lt;/p&gt;

&lt;p&gt;Basic flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Load benchmark cases.&lt;/li&gt;
&lt;li&gt;Load model candidates.&lt;/li&gt;
&lt;li&gt;Render the prompt version.&lt;/li&gt;
&lt;li&gt;Run each candidate.&lt;/li&gt;
&lt;li&gt;Validate structure.&lt;/li&gt;
&lt;li&gt;Score the result.&lt;/li&gt;
&lt;li&gt;Store cost, latency, and traces.&lt;/li&gt;
&lt;li&gt;Generate a routing recommendation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a simple Python-style skeleton:&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;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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;perf_counter&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;ModelCandidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;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;provider&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;cost_per_1k_input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;cost_per_1k_output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&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;BenchmarkResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;case_id&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;model&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;passed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;latency_ms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;estimated_cost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&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;run_case&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;case&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;render_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;case&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;perf_counter&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&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;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;response_format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;case&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;response_format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;latency_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;started&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="n"&gt;structure_ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validate_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;case&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;schema&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;policy_ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;check_policy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;case&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;score_answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;case&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="n"&gt;structure_ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;schema_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;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;policy_ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;policy_failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;passed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;structure_ok&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;policy_ok&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;case&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;min_score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.8&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;BenchmarkResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;case_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;case&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;latency_ms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;latency_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;estimated_cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;estimate_cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real value is not the code. The value is the discipline: every candidate model faces the same cases, same prompts, same scoring rules, and same cost math.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add model adapters instead of rewriting your app
&lt;/h2&gt;

&lt;p&gt;Your harness should call models through adapters. That keeps model testing separate from product logic.&lt;/p&gt;

&lt;p&gt;Example adapter shape:&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;type&lt;/span&gt; &lt;span class="nx"&gt;GenerateRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;model&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="nl"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system&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;user&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;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;content&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="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;responseFormat&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;json&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;text&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;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GenerateResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;text&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="nl"&gt;inputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;latencyMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ModelAdapter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GenerateRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GenerateResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;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;Then you can plug in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A closed-model API&lt;/li&gt;
&lt;li&gt;A hosted open-weight model endpoint&lt;/li&gt;
&lt;li&gt;A local Ollama or vLLM server&lt;/li&gt;
&lt;li&gt;A fallback provider&lt;/li&gt;
&lt;li&gt;A fine-tuned model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This also helps you test operational details. Some models have different JSON behavior. Some need stricter prompts. Some have weaker tool-calling support. The adapter lets your harness normalize the interface while still storing raw evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use promotion gates
&lt;/h2&gt;

&lt;p&gt;Do not route production traffic just because a model wins one test run.&lt;/p&gt;

&lt;p&gt;Use promotion stages:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Traffic&lt;/th&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lab&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;Pass golden set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shadow&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;Run beside current model, compare outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canary&lt;/td&gt;
&lt;td&gt;1-5%&lt;/td&gt;
&lt;td&gt;Pass live metrics and rollback rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;10-25%&lt;/td&gt;
&lt;td&gt;Stable cost, latency, quality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;Most eligible traffic&lt;/td&gt;
&lt;td&gt;Meets task-specific target&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Shadow mode is especially useful. The new model sees real inputs, but users still get the old model's answer. You compare outputs, scores, and cost without risking user trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create task-based routing rules
&lt;/h2&gt;

&lt;p&gt;Once you trust the harness, model routing gets simpler.&lt;/p&gt;

&lt;p&gt;Example policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;support_rewrite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;default_model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;qwen-class-small&lt;/span&gt;
    &lt;span class="na"&gt;fallback_model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;premium-reasoning&lt;/span&gt;
    &lt;span class="na"&gt;max_latency_ms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2500&lt;/span&gt;
    &lt;span class="na"&gt;min_benchmark_pass_rate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.92&lt;/span&gt;

  &lt;span class="na"&gt;account_policy_answer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;default_model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;premium-reasoning&lt;/span&gt;
    &lt;span class="na"&gt;candidate_model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;qwen-class-large&lt;/span&gt;
    &lt;span class="na"&gt;require_citations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;min_benchmark_pass_rate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.97&lt;/span&gt;
    &lt;span class="na"&gt;shadow_runs_required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;

  &lt;span class="na"&gt;invoice_extraction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;default_model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;open-weight-structured&lt;/span&gt;
    &lt;span class="na"&gt;fallback_model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;premium-json&lt;/span&gt;
    &lt;span class="na"&gt;require_schema_valid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;max_retry_count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This avoids the classic mistake: moving all AI traffic to one cheaper model at once. Instead, each task earns its route.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch for hidden costs
&lt;/h2&gt;

&lt;p&gt;Open-weight models can reduce vendor cost, but they introduce other costs.&lt;/p&gt;

&lt;p&gt;Track these before declaring victory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GPU or inference hosting cost&lt;/li&gt;
&lt;li&gt;Cold starts&lt;/li&gt;
&lt;li&gt;Queue time&lt;/li&gt;
&lt;li&gt;Context window limits&lt;/li&gt;
&lt;li&gt;Retry rate&lt;/li&gt;
&lt;li&gt;Prompt changes needed per model&lt;/li&gt;
&lt;li&gt;Human review rate&lt;/li&gt;
&lt;li&gt;Failed tool calls&lt;/li&gt;
&lt;li&gt;Support escalations&lt;/li&gt;
&lt;li&gt;Engineering maintenance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful dashboard shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model_name
task_name
pass_rate
schema_error_rate
policy_error_rate
p95_latency_ms
avg_cost_per_run
cost_per_success
fallback_rate
human_review_rate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a model is cheaper per call but has a high fallback rate, it may not be cheaper in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes to avoid
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Mistake 1: Benchmarking only easy examples
&lt;/h3&gt;

&lt;p&gt;Easy examples make every model look good. Include messy inputs, partial context, outdated docs, vague user requests, and policy traps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 2: Using one score for every task
&lt;/h3&gt;

&lt;p&gt;Summarization, extraction, tool use, support, and analytics need different scoring rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 3: Ignoring prompt portability
&lt;/h3&gt;

&lt;p&gt;A prompt tuned for one model may fail on another. Store prompt version with every result.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 4: Treating open-weight as automatically private
&lt;/h3&gt;

&lt;p&gt;Running an open-weight model does not automatically solve privacy. You still need data minimization, access controls, logs, retention rules, and tenant isolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 5: Shipping without rollback
&lt;/h3&gt;

&lt;p&gt;Every routing change needs a rollback plan. If quality drops, the router should move traffic back without a dramatic incident call.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical weekly workflow
&lt;/h2&gt;

&lt;p&gt;For small teams, keep the process lightweight:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add new failed production examples to the golden set.&lt;/li&gt;
&lt;li&gt;Run candidate models every week.&lt;/li&gt;
&lt;li&gt;Compare pass rate, latency, and cost per success.&lt;/li&gt;
&lt;li&gt;Promote only task routes that clear the threshold.&lt;/li&gt;
&lt;li&gt;Keep a short decision log explaining why traffic changed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That decision log helps later. When quality or cost changes, you can trace the model route, benchmark evidence, and rollout date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content map for this topic
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pillar:&lt;/strong&gt; Production AI architecture&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cluster:&lt;/strong&gt; model evaluation, open-weight rollout, task routing, cost governance, and AI reliability&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search intent:&lt;/strong&gt; practical implementation guide for builders evaluating open-weight models before production routing&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Funnel stage:&lt;/strong&gt; middle. The reader already has AI features or is choosing infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Internal-link targets:&lt;/strong&gt; open-weight model rollout checklist, LLM model selection matrix, LLM gateway architecture, AI metrics baseline, inference efficiency ratio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next recommended articles:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open-Weight Shadow Testing for Production AI Features&lt;/li&gt;
&lt;li&gt;LLM Cost Per Successful Task: A Better Metric Than Tokens&lt;/li&gt;
&lt;li&gt;Model Router Rollback Rules for AI Workflows&lt;/li&gt;
&lt;li&gt;Golden Dataset Design for AI Product Teams&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final checklist
&lt;/h2&gt;

&lt;p&gt;Before you route traffic to a cheaper model, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did it pass the task-specific golden set?&lt;/li&gt;
&lt;li&gt;Did it handle messy and adversarial cases?&lt;/li&gt;
&lt;li&gt;Did it keep structured outputs valid?&lt;/li&gt;
&lt;li&gt;Did it respect policy and tenant boundaries?&lt;/li&gt;
&lt;li&gt;Did it meet latency targets?&lt;/li&gt;
&lt;li&gt;Did cost per success actually improve?&lt;/li&gt;
&lt;li&gt;Did you test it in shadow mode?&lt;/li&gt;
&lt;li&gt;Do you have rollback rules?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer is no, the model is not ready. It may still be promising. It may even be powerful. But production traffic deserves evidence.&lt;/p&gt;

&lt;p&gt;Open-weight models are becoming too good to ignore. They are also too important to adopt by vibes. A benchmark harness gives you the middle path: experiment aggressively, route carefully, and let each model earn the work it is allowed to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is an open-weight model benchmark harness?
&lt;/h3&gt;

&lt;p&gt;It is a repeatable testing system that compares candidate models on your real product tasks. It measures quality, schema validity, grounding, policy safety, latency, and cost per successful result.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are open-weight models always cheaper than closed models?
&lt;/h3&gt;

&lt;p&gt;No. Token price is only one part of cost. Hosting, retries, latency, fallback calls, human review, and maintenance can change the real cost. Measure cost per successful task.&lt;/p&gt;

&lt;h3&gt;
  
  
  How many benchmark examples do I need to start?
&lt;/h3&gt;

&lt;p&gt;Start with 30 to 100 strong examples. Include normal cases, edge cases, adversarial cases, and historical failures. Quality matters more than size at the beginning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I use public LLM leaderboards for model selection?
&lt;/h3&gt;

&lt;p&gt;Use them as a starting signal, not a production decision. Public benchmarks rarely match your prompts, schemas, tools, users, latency needs, or risk rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is shadow testing for AI models?
&lt;/h3&gt;

&lt;p&gt;Shadow testing runs a candidate model beside your current production model without showing its output to users. You compare quality, cost, and latency on real traffic before canary routing.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I know when a model is ready for production routing?
&lt;/h3&gt;

&lt;p&gt;A model is ready when it passes task-specific benchmarks, performs well in shadow mode, meets cost and latency targets, respects policies, and has clear rollback rules.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>testing</category>
    </item>
    <item>
      <title>Build a Privacy Filter Before Your AI Agent Remembers User Actions</title>
      <dc:creator>Jack M</dc:creator>
      <pubDate>Sat, 15 Aug 2026 03:35:01 +0000</pubDate>
      <link>https://dev.to/jackm-singularity/build-a-privacy-filter-before-your-ai-agent-remembers-user-actions-31fe</link>
      <guid>https://dev.to/jackm-singularity/build-a-privacy-filter-before-your-ai-agent-remembers-user-actions-31fe</guid>
      <description>&lt;p&gt;AI agents are starting to remember more than chats. They can watch clicks, typed text, app switches, browser context, files, tool calls, and workflow history. That memory can make an agent feel useful fast, but it can also turn a helpful feature into a quiet privacy incident.&lt;/p&gt;

&lt;p&gt;If you are building an AI product, do not start with “how much can we capture?” Start with “what is the smallest event stream that still helps the user?” This guide shows a practical privacy filter you can place between raw user activity and agent memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters now
&lt;/h2&gt;

&lt;p&gt;Recent AI tooling trends point in the same direction: agents are moving from chat boxes into operating systems, browsers, IDEs, customer support tools, analytics dashboards, and workflow automation platforms. The more useful the agent becomes, the more context it wants.&lt;/p&gt;

&lt;p&gt;That creates a new engineering problem.&lt;/p&gt;

&lt;p&gt;Traditional app logs record requests and errors. Agent memory records intent, context, and behavior. A raw event can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What the user clicked&lt;/li&gt;
&lt;li&gt;What they typed&lt;/li&gt;
&lt;li&gt;Which customer record was open&lt;/li&gt;
&lt;li&gt;Which browser page was active&lt;/li&gt;
&lt;li&gt;Which tool the agent called&lt;/li&gt;
&lt;li&gt;Which file or message was summarized&lt;/li&gt;
&lt;li&gt;Which secrets or personal details appeared nearby&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not just observability. It is a privacy boundary.&lt;/p&gt;

&lt;p&gt;The practical trigger is simple: computer-use agents and workflow agents now need history to resume work, personalize answers, and automate multi-step tasks. But developers, security reviewers, and buyers are asking harder questions about PII, retention, auditability, user consent, and whether agent traces can leak private business data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The common mistake: treating memory like logs
&lt;/h2&gt;

&lt;p&gt;Most teams already have logs, traces, analytics events, and support transcripts. So when they add agent memory, they often reuse the same pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Capture the event.&lt;/li&gt;
&lt;li&gt;Save it to storage.&lt;/li&gt;
&lt;li&gt;Index it for search.&lt;/li&gt;
&lt;li&gt;Let the agent retrieve it later.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is easy to ship. It is also too broad.&lt;/p&gt;

&lt;p&gt;Agent memory needs a stricter path because it may be used to generate future answers or actions. A normal log line might be seen by engineers. A memory item might be read by a model, combined with other data, and used to make a decision.&lt;/p&gt;

&lt;p&gt;The safer pattern is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Raw event → privacy filter → purpose check → redacted memory → retention policy → retrieval policy&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The privacy filter is not a prompt. It is application code that decides what the agent is allowed to remember.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an AI agent privacy filter should do
&lt;/h2&gt;

&lt;p&gt;A good privacy filter has five jobs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Question it answers&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Event allowlist&lt;/td&gt;
&lt;td&gt;Should this event be captured at all?&lt;/td&gt;
&lt;td&gt;Save “opened invoice page,” not every mouse coordinate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sensitive data detection&lt;/td&gt;
&lt;td&gt;Does the payload contain PII, secrets, or regulated data?&lt;/td&gt;
&lt;td&gt;Detect emails, API keys, card-like numbers, tokens.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Purpose binding&lt;/td&gt;
&lt;td&gt;Why is this memory needed?&lt;/td&gt;
&lt;td&gt;Resume task, improve support, audit approval.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retention control&lt;/td&gt;
&lt;td&gt;How long can this memory live?&lt;/td&gt;
&lt;td&gt;48 hours for raw traces, 30 days for redacted task summaries.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retrieval control&lt;/td&gt;
&lt;td&gt;Who or what can read it later?&lt;/td&gt;
&lt;td&gt;Only the same user, tenant, role, and task type.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The filter should run before indexing, summarization, embedding, analytics export, or model calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: classify your event stream
&lt;/h2&gt;

&lt;p&gt;Do not start with redaction. Start with event classes. Redaction helps when you must keep data. Classification helps you avoid collecting data in the first place.&lt;/p&gt;

&lt;p&gt;A simple event taxonomy might look like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event class&lt;/th&gt;
&lt;th&gt;Risk&lt;/th&gt;
&lt;th&gt;Store by default?&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Navigation event&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Yes, redacted&lt;/td&gt;
&lt;td&gt;Page type, not full URL if it contains IDs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool call metadata&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Store tool name, status, cost, policy result.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User typed text&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Store only if explicitly needed and redacted.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Screen content&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Prefer structured app state over screenshots.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File content&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Store references and hashes, not full content.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Approval decision&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Keep reviewer, action, timestamp, and reason.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secret or credential&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;td&gt;Block and alert if detected.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here is a small TypeScript example:&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;type&lt;/span&gt; &lt;span class="nx"&gt;EventClass&lt;/span&gt; &lt;span class="o"&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;navigation&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;tool_call&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;typed_text&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;screen_content&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;file_content&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;approval&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;secret&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CaptureDecision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;store&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;redact_then_store&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;summarize_only&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;drop&lt;/span&gt;&lt;span class="dl"&gt;"&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;capturePolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;EventClass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CaptureDecision&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;navigation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;redact_then_store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tool_call&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;typed_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summarize_only&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;screen_content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;drop&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;file_content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summarize_only&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;approval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;drop&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;This looks boring. That is the point. Privacy should not depend on a clever prompt at runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: reduce the event before redacting it
&lt;/h2&gt;

&lt;p&gt;A raw event often contains too much context. Reduce it into a smaller shape before running PII detection.&lt;/p&gt;

&lt;p&gt;Bad memory candidate:&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;"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;"typed_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;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My card is 4242 4242 4242 4242 and my email is alex@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&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://app.example.com/customers/cus_782/orders/ord_991"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dom"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"...full page 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;"timestamp"&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-08-15T03:30:00Z"&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;Better memory candidate:&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;"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;"task_signal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User entered payment-related information during checkout setup."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"page_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;"checkout_settings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tenant_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tenant_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&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-08-15T03:30:00Z"&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;Notice what changed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No full typed text&lt;/li&gt;
&lt;li&gt;No full DOM&lt;/li&gt;
&lt;li&gt;No full URL with record IDs&lt;/li&gt;
&lt;li&gt;No card number&lt;/li&gt;
&lt;li&gt;No personal email&lt;/li&gt;
&lt;li&gt;Enough context to resume the task&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reduction is the cheapest privacy win you can ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: add layered PII and secret detection
&lt;/h2&gt;

&lt;p&gt;Use multiple detectors. Regex is not enough, but regex is still useful.&lt;/p&gt;

&lt;p&gt;You want to detect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Email addresses&lt;/li&gt;
&lt;li&gt;Phone numbers&lt;/li&gt;
&lt;li&gt;Access tokens&lt;/li&gt;
&lt;li&gt;API keys&lt;/li&gt;
&lt;li&gt;Session cookies&lt;/li&gt;
&lt;li&gt;Credit-card-like patterns&lt;/li&gt;
&lt;li&gt;Private keys&lt;/li&gt;
&lt;li&gt;OAuth codes&lt;/li&gt;
&lt;li&gt;Personal addresses&lt;/li&gt;
&lt;li&gt;Customer names in risky contexts&lt;/li&gt;
&lt;li&gt;Health, legal, financial, or employment data when relevant&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example filter:&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;type&lt;/span&gt; &lt;span class="nx"&gt;Redaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;redacted&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="nl"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&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="nl"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;redactSensitiveText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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;Redaction&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;findings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Redaction&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;findings&lt;/span&gt;&lt;span class="dl"&gt;"&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&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;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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;A-Z0-9._%+-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+@&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;A-Z0-9.-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.[&lt;/span&gt;&lt;span class="sr"&gt;A-Z&lt;/span&gt;&lt;span class="se"&gt;]{2,}&lt;/span&gt;&lt;span class="sr"&gt;/gi&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;credit_card_like&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b(?:\d[&lt;/span&gt;&lt;span class="sr"&gt; -&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;?){13,19}\b&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;api_key_like&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b(?:&lt;/span&gt;&lt;span class="sr"&gt;sk|pk|ghp|xoxb|AKIA&lt;/span&gt;&lt;span class="se"&gt;)[&lt;/span&gt;&lt;span class="sr"&gt;A-Za-z0-9_&lt;/span&gt;&lt;span class="se"&gt;\-]{16,}\b&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;private_key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/-----BEGIN &lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;A-Z &lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*PRIVATE KEY-----&lt;/span&gt;&lt;span class="se"&gt;[\s\S]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;-----END &lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;A-Z &lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*PRIVATE KEY-----/g&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;for &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;pattern&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;patterns&lt;/span&gt;&lt;span class="p"&gt;)&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;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`[REDACTED_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;redacted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;findings&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;For production, combine this with a structured PII service, domain-specific dictionaries, and field-level policies. The key is to store the findings separately from the raw value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: bind every memory to a purpose
&lt;/h2&gt;

&lt;p&gt;A memory without a purpose becomes a future liability. Add a purpose field when the memory is created.&lt;/p&gt;

&lt;p&gt;Common purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;resume_task&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;support_debugging&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;security_audit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;billing_dispute&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;quality_evaluation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;personalization&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each purpose should control retention and retrieval.&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;type&lt;/span&gt; &lt;span class="nx"&gt;MemoryPurpose&lt;/span&gt; &lt;span class="o"&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;resume_task&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;support_debugging&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;security_audit&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;billing_dispute&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;quality_evaluation&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;personalization&lt;/span&gt;&lt;span class="dl"&gt;"&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;retentionDays&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MemoryPurpose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;resume_task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;support_debugging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;security_audit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;billing_dispute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;365&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;quality_evaluation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;personalization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90&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;This gives your product, legal, and engineering teams one shared control surface.&lt;/p&gt;

&lt;p&gt;It also prevents a common failure mode: using data collected for debugging as long-term personalization memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: separate raw traces from agent memory
&lt;/h2&gt;

&lt;p&gt;Raw traces and agent memory should not live in the same bucket.&lt;/p&gt;

&lt;p&gt;A useful split:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Raw event buffer&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Short-lived, encrypted, tightly restricted, used for immediate debugging or user-visible replay.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Redacted memory store&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Longer-lived, purpose-bound, searchable by the agent only through policy checks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Audit ledger&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Append-only records of decisions: what was stored, why, which policy allowed it, and when it expires.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The agent should usually retrieve from the redacted memory store, not the raw buffer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[User activity]
      |
      v
[Raw event buffer: short TTL]
      |
      v
[Privacy filter]
      |
      +--&amp;gt; [Drop / block / alert]
      |
      v
[Redacted memory store]
      |
      v
[Policy-checked retrieval]
      |
      v
[Agent response or action]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure makes deletion easier and audits less painful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: check consent at capture and retrieval
&lt;/h2&gt;

&lt;p&gt;Consent is not a checkbox on the settings page. It is runtime state.&lt;/p&gt;

&lt;p&gt;Check consent when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The event is captured&lt;/li&gt;
&lt;li&gt;The event is transformed into memory&lt;/li&gt;
&lt;li&gt;The memory is retrieved&lt;/li&gt;
&lt;li&gt;The memory is exported&lt;/li&gt;
&lt;li&gt;The user revokes access&lt;/li&gt;
&lt;li&gt;The tenant changes policy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&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;type&lt;/span&gt; &lt;span class="nx"&gt;ConsentState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;userId&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="nl"&gt;tenantId&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="nl"&gt;allowAgentMemory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;allowPersonalization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;allowSupportReview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;revokedAt&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="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;canStoreMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConsentState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;purpose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MemoryPurpose&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;consent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowAgentMemory&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;consent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revokedAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;purpose&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;personalization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;consent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowPersonalization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;purpose&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;support_debugging&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;consent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowSupportReview&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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;If consent is revoked, new memory should stop immediately. Existing memory should either expire, be deleted, or become inaccessible depending on your product policy and legal requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: make retrieval policy stricter than storage policy
&lt;/h2&gt;

&lt;p&gt;A memory can be safe to store but unsafe to retrieve in a different context.&lt;/p&gt;

&lt;p&gt;Before retrieving memory for an agent, check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same tenant&lt;/li&gt;
&lt;li&gt;Same user or approved team scope&lt;/li&gt;
&lt;li&gt;Same purpose&lt;/li&gt;
&lt;li&gt;Role permissions&lt;/li&gt;
&lt;li&gt;Data region&lt;/li&gt;
&lt;li&gt;Retention expiry&lt;/li&gt;
&lt;li&gt;Sensitivity level&lt;/li&gt;
&lt;li&gt;Current task relevance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents awkward bugs like a support agent retrieving billing context during a product tutorial, or a workspace agent pulling private notes into a shared channel.&lt;/p&gt;

&lt;p&gt;A retrieval policy can be simple:&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;function&lt;/span&gt; &lt;span class="nf"&gt;canRetrieveMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;requesterTenantId&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="nl"&gt;requesterUserId&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="nl"&gt;memoryTenantId&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="nl"&gt;memoryUserId&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="nl"&gt;purpose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MemoryPurpose&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;requestedPurpose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MemoryPurpose&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&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;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requesterTenantId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memoryTenantId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requesterUserId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memoryUserId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;purpose&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestedPurpose&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;Date&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;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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;In team products, replace the user equality check with a role and resource policy. Keep the default narrow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: log decisions, not secrets
&lt;/h2&gt;

&lt;p&gt;You still need audit logs. Just do not put raw secrets in them.&lt;/p&gt;

&lt;p&gt;A good audit record includes:&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;"memory_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mem_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tenant_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tenant_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event_class"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"typed_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;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"summarize_only"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"purpose"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resume_task"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pii_findings"&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;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;"policy_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"privacy-filter-v4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&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-08-15T03:30: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;"expires_at"&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-08-22T03:30:00Z"&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 record helps you answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why did the agent remember this?&lt;/li&gt;
&lt;li&gt;Which policy allowed it?&lt;/li&gt;
&lt;li&gt;Was PII detected?&lt;/li&gt;
&lt;li&gt;When will it expire?&lt;/li&gt;
&lt;li&gt;Who can retrieve it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is much better than saving the whole raw payload and hoping nobody looks too closely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical implementation checklist
&lt;/h2&gt;

&lt;p&gt;Use this as a build checklist for your first privacy filter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Capture controls
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Define event classes before instrumentation.&lt;/li&gt;
&lt;li&gt;Default high-risk events to &lt;code&gt;drop&lt;/code&gt; or &lt;code&gt;summarize_only&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Avoid storing raw typed text unless the user explicitly asks the agent to remember it.&lt;/li&gt;
&lt;li&gt;Prefer page type, resource type, and task state over full URL or DOM text.&lt;/li&gt;
&lt;li&gt;Keep raw buffers short-lived and encrypted.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Redaction controls
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Run PII and secret detection before storage.&lt;/li&gt;
&lt;li&gt;Replace sensitive values with typed placeholders.&lt;/li&gt;
&lt;li&gt;Store finding counts, not raw matches.&lt;/li&gt;
&lt;li&gt;Add domain-specific detectors for your product.&lt;/li&gt;
&lt;li&gt;Block critical secrets instead of masking them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Purpose and consent controls
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Require a purpose for every memory object.&lt;/li&gt;
&lt;li&gt;Map each purpose to retention and retrieval rules.&lt;/li&gt;
&lt;li&gt;Check consent at capture and retrieval.&lt;/li&gt;
&lt;li&gt;Support revocation and deletion workflows.&lt;/li&gt;
&lt;li&gt;Do not reuse debugging data for personalization without explicit permission.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Retrieval controls
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Enforce tenant isolation.&lt;/li&gt;
&lt;li&gt;Enforce user or role scope.&lt;/li&gt;
&lt;li&gt;Match requested purpose to stored purpose.&lt;/li&gt;
&lt;li&gt;Filter expired memories before retrieval.&lt;/li&gt;
&lt;li&gt;Keep sensitive memories out of shared contexts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Audit controls
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Log privacy decisions with policy version.&lt;/li&gt;
&lt;li&gt;Keep audit logs redacted.&lt;/li&gt;
&lt;li&gt;Track who or what retrieved memory.&lt;/li&gt;
&lt;li&gt;Build a simple memory inspection page for users or admins.&lt;/li&gt;
&lt;li&gt;Test deletion, export, and expiry before launch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where most articles stop short
&lt;/h2&gt;

&lt;p&gt;Most privacy guides cover PII redaction, audit logs, or broad data governance. Agent memory needs one more layer: event design. Decide what to do with clicks, typed text, page context, tool calls, desktop actions, and model-readable summaries before they enter storage.&lt;/p&gt;

&lt;p&gt;Ask this before saving anything:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Would this memory still feel reasonable if the user inspected it, exported it, or saw it during an incident review?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer feels uncomfortable, reduce it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple architecture you can ship first
&lt;/h2&gt;

&lt;p&gt;For a small team, do not overbuild. Start with this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an &lt;code&gt;agent_events&lt;/code&gt; table with short retention.&lt;/li&gt;
&lt;li&gt;Create an &lt;code&gt;agent_memories&lt;/code&gt; table with redacted summaries only.&lt;/li&gt;
&lt;li&gt;Create an &lt;code&gt;agent_memory_audit&lt;/code&gt; table for policy decisions.&lt;/li&gt;
&lt;li&gt;Add a privacy filter service before any embedding or model call.&lt;/li&gt;
&lt;li&gt;Add a nightly expiry job.&lt;/li&gt;
&lt;li&gt;Add a user-facing “forget my agent memory” action.&lt;/li&gt;
&lt;li&gt;Add tests for PII, secrets, tenant isolation, and consent revocation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Schema sketch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;agent_memories&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;purpose&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;sensitivity&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;policy_version&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;agent_memories_lookup&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;agent_memories&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;purpose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expires_at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then test it like a security feature, not like a logging feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test cases worth adding
&lt;/h2&gt;

&lt;p&gt;Add these to CI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A fake API key is never stored in memory.&lt;/li&gt;
&lt;li&gt;A card-like number is redacted before storage.&lt;/li&gt;
&lt;li&gt;A revoked user cannot create new personalization memory.&lt;/li&gt;
&lt;li&gt;Expired memory is not retrieved.&lt;/li&gt;
&lt;li&gt;User A cannot retrieve User B’s memory in the same tenant.&lt;/li&gt;
&lt;li&gt;Tenant A cannot retrieve Tenant B’s memory.&lt;/li&gt;
&lt;li&gt;Debug memory is not retrieved for personalization.&lt;/li&gt;
&lt;li&gt;Raw event buffer expires on schedule.&lt;/li&gt;
&lt;li&gt;Audit logs contain findings but not raw sensitive values.&lt;/li&gt;
&lt;li&gt;Shared-channel agents cannot access private one-to-one memory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tests will catch more real issues than another paragraph in your privacy policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Agent memory is powerful because it compresses user context into future usefulness. That same compression can hide privacy mistakes if you capture too much, store it too long, or retrieve it in the wrong place.&lt;/p&gt;

&lt;p&gt;The safest path is not “never remember anything.” That makes agents weak. The safest path is to remember less, explain why, expire it on purpose, and retrieve it only when the current task deserves it.&lt;/p&gt;

&lt;p&gt;Build the privacy filter before the memory feature becomes popular. It is much easier to start narrow than to clean up a giant pile of raw history later.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is an AI agent privacy filter?
&lt;/h3&gt;

&lt;p&gt;An AI agent privacy filter is application logic that decides which user activity events can become agent memory. It classifies events, redacts sensitive data, checks consent, assigns a purpose, applies retention, and controls retrieval.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should AI agents store raw user actions?
&lt;/h3&gt;

&lt;p&gt;Usually no. Raw actions such as typed text, full page content, screenshots, and file contents are high risk. Store reduced summaries, task state, tool metadata, or redacted memory instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is agent memory different from normal logs?
&lt;/h3&gt;

&lt;p&gt;Logs are mainly used for debugging and operations. Agent memory may be retrieved by a model and used to generate future responses or actions. That makes purpose, consent, retention, and retrieval policy more important.&lt;/p&gt;

&lt;h3&gt;
  
  
  What data should never be stored in agent memory?
&lt;/h3&gt;

&lt;p&gt;Do not store raw secrets, API keys, session cookies, private keys, payment details, or regulated personal data unless you have a very specific, compliant reason. In most products, these should be blocked or heavily redacted.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long should AI agent memory be retained?
&lt;/h3&gt;

&lt;p&gt;Retention depends on purpose. Task-resume memory may only need days. Support debugging may need weeks. Security audit records may need longer. Avoid one global retention window for every memory type.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do prompts provide enough privacy protection?
&lt;/h3&gt;

&lt;p&gt;No. Prompts can remind a model not to reveal sensitive data, but privacy enforcement should happen in code before storage, indexing, embedding, and retrieval.&lt;/p&gt;

&lt;h3&gt;
  
  
  How can users trust agent memory?
&lt;/h3&gt;

&lt;p&gt;Give users visibility and control. Provide settings to disable memory, inspect saved memory, delete memory, and understand what the agent remembers and why.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>saas</category>
      <category>privacy</category>
      <category>agents</category>
    </item>
    <item>
      <title>LLM Model Selection Matrix: Pick the Cheapest Reliable Model for Each Feature</title>
      <dc:creator>Jack M</dc:creator>
      <pubDate>Fri, 14 Aug 2026 05:18:46 +0000</pubDate>
      <link>https://dev.to/jackm-singularity/llm-model-selection-matrix-pick-the-cheapest-reliable-model-for-each-feature-1n1m</link>
      <guid>https://dev.to/jackm-singularity/llm-model-selection-matrix-pick-the-cheapest-reliable-model-for-each-feature-1n1m</guid>
      <description>&lt;p&gt;Most AI product teams do not have a model problem. They have a matching problem.&lt;/p&gt;

&lt;p&gt;A chat rewrite, a support answer, a SQL assistant, and an autonomous workflow should not all use the same large model just because it is the default in your SDK. That habit feels safe in a prototype, then quietly turns into slow responses, messy invoices, weak margins, and confusing quality bugs in production.&lt;/p&gt;

&lt;p&gt;The better path is boring in the best way: build a model selection matrix. Map each feature to the cheapest model that reliably meets its accuracy, latency, safety, and product requirements. Then prove it with small evals before traffic scales.&lt;/p&gt;

&lt;p&gt;This guide shows a practical workflow for solo SaaS developers, AI SaaS builders, micro SaaS builders, and technical founders who need production AI features without guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why one default model becomes expensive fast
&lt;/h2&gt;

&lt;p&gt;Using one premium model everywhere has a few advantages. It is easy to ship. It lowers decision fatigue. It avoids early routing complexity.&lt;/p&gt;

&lt;p&gt;But the cost shows up later.&lt;/p&gt;

&lt;p&gt;You start with one AI feature. Then you add summaries, tags, embeddings, support drafts, workflow suggestions, document parsing, extraction jobs, and agentic actions. Suddenly the “one model” decision touches every request path.&lt;/p&gt;

&lt;p&gt;The common failure modes are predictable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Overpaying for simple tasks&lt;/strong&gt; like classification, cleanup, and short extraction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Under-testing complex tasks&lt;/strong&gt; because a strong model feels trustworthy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow user flows&lt;/strong&gt; because every step waits on a heavy model call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No fallback plan&lt;/strong&gt; when a provider has an outage or quality regression.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No cost explanation&lt;/strong&gt; when a tenant, customer, or workflow gets expensive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A model selection matrix turns this from vibes into an engineering process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The simple matrix
&lt;/h2&gt;

&lt;p&gt;Start with columns that force the right tradeoffs. Do not begin with vendor names. Begin with task needs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Task type&lt;/th&gt;
&lt;th&gt;Risk&lt;/th&gt;
&lt;th&gt;Quality target&lt;/th&gt;
&lt;th&gt;Latency target&lt;/th&gt;
&lt;th&gt;Max cost&lt;/th&gt;
&lt;th&gt;Context need&lt;/th&gt;
&lt;th&gt;Suggested model tier&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ticket tagging&lt;/td&gt;
&lt;td&gt;Classification&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;95% label accuracy&lt;/td&gt;
&lt;td&gt;&amp;lt; 800ms&lt;/td&gt;
&lt;td&gt;Very low&lt;/td&gt;
&lt;td&gt;Short&lt;/td&gt;
&lt;td&gt;Small / fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email rewrite&lt;/td&gt;
&lt;td&gt;Generation&lt;/td&gt;
&lt;td&gt;Low-medium&lt;/td&gt;
&lt;td&gt;Human preference win rate&lt;/td&gt;
&lt;td&gt;&amp;lt; 2s&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Short&lt;/td&gt;
&lt;td&gt;Mid-tier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contract clause answer&lt;/td&gt;
&lt;td&gt;RAG answer&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Grounded citation accuracy&lt;/td&gt;
&lt;td&gt;&amp;lt; 5s&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Long&lt;/td&gt;
&lt;td&gt;Strong reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refund approval agent&lt;/td&gt;
&lt;td&gt;Tool use&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Policy compliance + audit&lt;/td&gt;
&lt;td&gt;&amp;lt; 10s&lt;/td&gt;
&lt;td&gt;Medium-high&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Strong + approval gate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch summary&lt;/td&gt;
&lt;td&gt;Summarization&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Faithfulness score&lt;/td&gt;
&lt;td&gt;Async&lt;/td&gt;
&lt;td&gt;Very low&lt;/td&gt;
&lt;td&gt;Long&lt;/td&gt;
&lt;td&gt;Cheap long-context or batch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The goal is not to find the “best LLM.” The goal is to find the least expensive reliable model for each job.&lt;/p&gt;

&lt;p&gt;That phrase matters: &lt;strong&gt;least expensive reliable&lt;/strong&gt;, not cheapest.&lt;/p&gt;

&lt;p&gt;Cheap but wrong is expensive. Premium but unnecessary is also expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Split features by task shape
&lt;/h2&gt;

&lt;p&gt;A feature name is usually too broad for model selection. Break it into task shapes.&lt;/p&gt;

&lt;p&gt;For example, “AI support assistant” might contain:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Classify the ticket topic.&lt;/li&gt;
&lt;li&gt;Detect urgency and sentiment.&lt;/li&gt;
&lt;li&gt;Retrieve relevant docs.&lt;/li&gt;
&lt;li&gt;Draft a response.&lt;/li&gt;
&lt;li&gt;Check whether the answer is grounded.&lt;/li&gt;
&lt;li&gt;Decide whether to escalate to a human.&lt;/li&gt;
&lt;li&gt;Summarize the conversation after resolution.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those seven steps may need three or four different model choices.&lt;/p&gt;

&lt;p&gt;A small model may classify topics well. A mid-tier model may draft friendly answers. A stronger model may verify policy-sensitive claims. A rules engine may handle escalation better than any model.&lt;/p&gt;

&lt;p&gt;Use task shapes like these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Classification&lt;/li&gt;
&lt;li&gt;Extraction&lt;/li&gt;
&lt;li&gt;Rewrite&lt;/li&gt;
&lt;li&gt;Summarization&lt;/li&gt;
&lt;li&gt;RAG answer&lt;/li&gt;
&lt;li&gt;Code generation&lt;/li&gt;
&lt;li&gt;SQL generation&lt;/li&gt;
&lt;li&gt;Tool calling&lt;/li&gt;
&lt;li&gt;Planning&lt;/li&gt;
&lt;li&gt;Verification&lt;/li&gt;
&lt;li&gt;Safety review&lt;/li&gt;
&lt;li&gt;Long-running agent step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This avoids the most common mistake: paying reasoning-model prices for tasks that are not reasoning tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Assign risk before you assign a model
&lt;/h2&gt;

&lt;p&gt;Risk should control model choice more than hype.&lt;/p&gt;

&lt;p&gt;A wrong tag in an internal dashboard is annoying. A wrong refund, medical summary, financial explanation, legal clause, or account deletion is a trust event.&lt;/p&gt;

&lt;p&gt;Use four simple risk levels:&lt;/p&gt;

&lt;h3&gt;
  
  
  Low risk
&lt;/h3&gt;

&lt;p&gt;The output is reversible, internal, or easy for the user to ignore.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Labeling notes&lt;/li&gt;
&lt;li&gt;Generating title suggestions&lt;/li&gt;
&lt;li&gt;Rewriting short copy&lt;/li&gt;
&lt;li&gt;Creating draft summaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use cheaper models first. Add sampling-based review.&lt;/p&gt;

&lt;h3&gt;
  
  
  Medium risk
&lt;/h3&gt;

&lt;p&gt;The output appears to a user, but does not directly change money, permissions, health, legal status, or production data.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Support draft&lt;/li&gt;
&lt;li&gt;Customer-facing summary&lt;/li&gt;
&lt;li&gt;Product recommendation explanation&lt;/li&gt;
&lt;li&gt;Workflow suggestion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use a mid-tier model and run targeted evals.&lt;/p&gt;

&lt;h3&gt;
  
  
  High risk
&lt;/h3&gt;

&lt;p&gt;The output can affect user trust, policy compliance, revenue, or customer operations.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Billing explanation&lt;/li&gt;
&lt;li&gt;Contract Q&amp;amp;A&lt;/li&gt;
&lt;li&gt;Compliance response&lt;/li&gt;
&lt;li&gt;Security assistant&lt;/li&gt;
&lt;li&gt;Refund recommendation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use stronger models, evidence checks, stricter prompts, citations, and human review for edge cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Critical risk
&lt;/h3&gt;

&lt;p&gt;The output triggers irreversible actions or touches regulated decisions.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deleting customer data&lt;/li&gt;
&lt;li&gt;Approving payments&lt;/li&gt;
&lt;li&gt;Changing permissions&lt;/li&gt;
&lt;li&gt;Medical or legal guidance&lt;/li&gt;
&lt;li&gt;Autonomous production actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not rely on model choice alone. Add approvals, scoped tools, audit logs, rollback, and policy enforcement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Define the quality target in plain language
&lt;/h2&gt;

&lt;p&gt;“Good enough” is not an eval target. It is a hope.&lt;/p&gt;

&lt;p&gt;Write the target like a product requirement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ticket tags must match the human label in at least 95% of sampled cases.&lt;/li&gt;
&lt;li&gt;Extracted invoice fields must be exactly correct for amount, date, vendor, and currency.&lt;/li&gt;
&lt;li&gt;RAG answers must cite at least one approved source and avoid unsupported claims.&lt;/li&gt;
&lt;li&gt;SQL generation must pass read-only policy checks and return within the query budget.&lt;/li&gt;
&lt;li&gt;A support response must not promise refunds, discounts, timelines, or legal conclusions unless the source says so.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quality targets help you avoid two bad outcomes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choosing a model because it “feels smart.”&lt;/li&gt;
&lt;li&gt;Rejecting a cheaper model without evidence.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 4: Build a small eval set before comparing models
&lt;/h2&gt;

&lt;p&gt;You do not need a giant benchmark to make better model decisions. You need a small, honest eval set that reflects your real users.&lt;/p&gt;

&lt;p&gt;Start with 30 to 100 examples per task. Include normal cases, edge cases, and ugly cases.&lt;/p&gt;

&lt;p&gt;For a RAG answer feature, your eval set might include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;20 common questions from real support tickets&lt;/li&gt;
&lt;li&gt;10 questions with missing docs&lt;/li&gt;
&lt;li&gt;10 questions where two docs conflict&lt;/li&gt;
&lt;li&gt;10 questions involving pricing, cancellation, or security&lt;/li&gt;
&lt;li&gt;10 adversarial questions asking the assistant to ignore rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then define how each response is judged.&lt;/p&gt;

&lt;p&gt;A simple scoring format:&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;"case_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"refund_policy_014"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"support_answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"must_include"&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;"refund window"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"account plan"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"must_not_include"&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;"guaranteed refund"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"legal advice"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required_sources"&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;"refund-policy-v3"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pass_conditions"&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;"grounded"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"safe"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"helpful"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"under_200_words"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;Keep the first version simple. The main win is not statistical perfection. The win is forcing models to compete on your task instead of on generic benchmark charts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Compare models by cost per successful result
&lt;/h2&gt;

&lt;p&gt;Token price alone is a weak metric.&lt;/p&gt;

&lt;p&gt;A model that costs half as much but fails twice as often is not cheaper. A model that needs long retries, repair prompts, or human cleanup may be the expensive one.&lt;/p&gt;

&lt;p&gt;Track &lt;strong&gt;cost per successful result&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cost_per_success = total_model_cost / number_of_passed_outputs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add latency too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;usable_model = pass_rate &amp;gt;= target
            AND p95_latency &amp;lt;= latency_budget
            AND cost_per_success &amp;lt;= feature_budget
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a clearer ranking than “input token price” or “best benchmark score.”&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model tier&lt;/th&gt;
&lt;th&gt;Pass rate&lt;/th&gt;
&lt;th&gt;Avg cost / run&lt;/th&gt;
&lt;th&gt;Cost per success&lt;/th&gt;
&lt;th&gt;p95 latency&lt;/th&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;td&gt;82%&lt;/td&gt;
&lt;td&gt;$0.001&lt;/td&gt;
&lt;td&gt;$0.0012&lt;/td&gt;
&lt;td&gt;700ms&lt;/td&gt;
&lt;td&gt;Fails quality target&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mid&lt;/td&gt;
&lt;td&gt;94%&lt;/td&gt;
&lt;td&gt;$0.004&lt;/td&gt;
&lt;td&gt;$0.0043&lt;/td&gt;
&lt;td&gt;1.8s&lt;/td&gt;
&lt;td&gt;Good for drafts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;98%&lt;/td&gt;
&lt;td&gt;$0.018&lt;/td&gt;
&lt;td&gt;$0.0184&lt;/td&gt;
&lt;td&gt;4.8s&lt;/td&gt;
&lt;td&gt;Use for high-risk checks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The strong model is better. It is not always the right default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Use routing rules instead of model loyalty
&lt;/h2&gt;

&lt;p&gt;Once you have eval results, convert them into routing rules.&lt;/p&gt;

&lt;p&gt;A basic router can be a few if statements:&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;type&lt;/span&gt; &lt;span class="nx"&gt;TaskRisk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;low&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;medium&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;high&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;critical&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ModelChoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;provider&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="nl"&gt;model&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="nl"&gt;reason&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="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;chooseModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;task&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="nl"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TaskRisk&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tokenEstimate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;userPlan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;free&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;pro&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;enterprise&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;needsCitations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nx"&gt;ModelChoice&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;critical&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;strong-reasoning-model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;critical workflow requires strongest eval pass rate and audit path&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;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;needsCitations&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;high&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;strong-balanced-model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;high-risk grounded answer&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;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;classification&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokenEstimate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secondary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;small-fast-model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;low-risk short classification&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mid-tier-model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default for medium-risk generation&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;This is not about building a fancy orchestration platform on day one. It is about making the decision visible, testable, and adjustable.&lt;/p&gt;

&lt;p&gt;Log the routing reason with every request. Later, when cost or quality shifts, you can see which rules are helping and which rules are wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Add fallbacks for known failure modes
&lt;/h2&gt;

&lt;p&gt;Model selection is not finished when the first model returns text.&lt;/p&gt;

&lt;p&gt;Production AI workflows need fallback behavior.&lt;/p&gt;

&lt;p&gt;Good fallback examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If JSON validation fails, run a repair prompt once.&lt;/li&gt;
&lt;li&gt;If citation checks fail, ask the model to answer “not enough evidence.”&lt;/li&gt;
&lt;li&gt;If latency exceeds the budget, stream a partial response or switch to async.&lt;/li&gt;
&lt;li&gt;If the provider errors, retry with a compatible fallback model.&lt;/li&gt;
&lt;li&gt;If the task is high risk, escalate instead of guessing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bad fallback examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retry the same broken prompt five times.&lt;/li&gt;
&lt;li&gt;Silently use a weaker model for a high-risk action.&lt;/li&gt;
&lt;li&gt;Remove citations because they are hard.&lt;/li&gt;
&lt;li&gt;Let the model decide whether it should follow policy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fallbacks should reduce harm, not hide it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: Put model decisions in your product telemetry
&lt;/h2&gt;

&lt;p&gt;If you cannot explain why a model was used, you cannot optimize it.&lt;/p&gt;

&lt;p&gt;Log these fields for every AI run:&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;"run_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run_7db42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tenant_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tenant_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"feature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"support_answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task_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;"rag_answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"risk_level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"strong-balanced-model"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"routing_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high-risk grounded answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1840&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;312&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"estimated_cost_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.014&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"latency_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3820&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eval_result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pass"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fallback_used"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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 gives you the raw material for weekly decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which features spend the most?&lt;/li&gt;
&lt;li&gt;Which tenants create unusual model usage?&lt;/li&gt;
&lt;li&gt;Which model routes fail evals?&lt;/li&gt;
&lt;li&gt;Which low-risk tasks can move to cheaper models?&lt;/li&gt;
&lt;li&gt;Which high-risk tasks need stricter gates?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without this layer, model choice becomes tribal knowledge.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical selection workflow
&lt;/h2&gt;

&lt;p&gt;Use this process whenever you add a new AI feature:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Break the feature into task shapes.&lt;/li&gt;
&lt;li&gt;Assign risk level per task.&lt;/li&gt;
&lt;li&gt;Set quality, latency, and cost targets.&lt;/li&gt;
&lt;li&gt;Build a 30 to 100 case eval set.&lt;/li&gt;
&lt;li&gt;Test at least one small, one mid, and one strong model.&lt;/li&gt;
&lt;li&gt;Compare cost per successful result.&lt;/li&gt;
&lt;li&gt;Choose the cheapest model that passes the target.&lt;/li&gt;
&lt;li&gt;Add routing, fallback, and telemetry.&lt;/li&gt;
&lt;li&gt;Re-run evals when prompts, docs, providers, or product rules change.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is lightweight enough for a solo developer and disciplined enough for a growing AI SaaS team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where common articles leave a gap
&lt;/h2&gt;

&lt;p&gt;Most model comparison posts focus on benchmark scores, public leaderboards, or broad “best model” rankings. Those are useful signals, but they rarely answer the question a builder actually has:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which model should power this exact feature, for this exact risk level, at this exact cost and latency budget?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the search gap this matrix fills. The practical value is not another leaderboard. It is a repeatable decision system for production AI workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Internal links to build your topic cluster
&lt;/h2&gt;

&lt;p&gt;If you are building an AI SaaS content library or engineering wiki, connect this guide to nearby production topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LLM gateway architecture for routing, caching, and provider abstraction&lt;/li&gt;
&lt;li&gt;RAG evaluation checklists for grounded answers&lt;/li&gt;
&lt;li&gt;AI agent cost forecasting before a user clicks run&lt;/li&gt;
&lt;li&gt;Structured output validation for JSON and workflow steps&lt;/li&gt;
&lt;li&gt;Model failover drills for provider incidents&lt;/li&gt;
&lt;li&gt;Inference efficiency metrics for margin protection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a stronger topical cluster around production AI architecture instead of isolated posts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final checklist
&lt;/h2&gt;

&lt;p&gt;Before shipping a new AI feature, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did we split the workflow into individual model tasks?&lt;/li&gt;
&lt;li&gt;Did we assign risk before choosing a model?&lt;/li&gt;
&lt;li&gt;Do we know the quality target?&lt;/li&gt;
&lt;li&gt;Did we test real examples, not only happy paths?&lt;/li&gt;
&lt;li&gt;Are we measuring cost per successful result?&lt;/li&gt;
&lt;li&gt;Is there a fallback for validation, latency, provider errors, and low confidence?&lt;/li&gt;
&lt;li&gt;Can we explain why this model was used for this request?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer is no, the model decision is still a guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is an LLM model selection matrix?
&lt;/h3&gt;

&lt;p&gt;An LLM model selection matrix is a table that maps each AI feature or workflow step to the best-fit model based on task type, risk, quality target, latency budget, cost limit, context size, and fallback needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I choose the right LLM for a production feature?
&lt;/h3&gt;

&lt;p&gt;Break the feature into smaller tasks, assign risk levels, create a small eval set, compare models by pass rate, latency, and cost per successful result, then choose the cheapest model that reliably meets the target.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I use the strongest model for every AI feature?
&lt;/h3&gt;

&lt;p&gt;Usually no. Strong models are useful for high-risk reasoning, grounded answers, and complex tool use. Simple classification, extraction, and rewrite tasks often work well on smaller or mid-tier models if evals prove they meet your quality target.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is cost per successful result?
&lt;/h3&gt;

&lt;p&gt;Cost per successful result measures how much you spend for outputs that actually pass your quality checks. It is better than token price alone because it includes failures, retries, repairs, and model accuracy.&lt;/p&gt;

&lt;h3&gt;
  
  
  How often should I re-run model evals?
&lt;/h3&gt;

&lt;p&gt;Re-run evals whenever you change prompts, retrieval logic, product policy, model versions, providers, or user workflows. For active production AI features, a weekly or release-based eval run is a good starting point.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the biggest mistake in model selection?
&lt;/h3&gt;

&lt;p&gt;The biggest mistake is choosing one default model for every task without measuring task risk, quality, latency, and cost. That creates hidden spend and weak reliability as the product grows.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>saas</category>
      <category>llm</category>
      <category>devops</category>
    </item>
    <item>
      <title>AI Agent Cost Forecasting: Predict Workflow Spend Before Users Hit Run</title>
      <dc:creator>Jack M</dc:creator>
      <pubDate>Thu, 13 Aug 2026 06:46:24 +0000</pubDate>
      <link>https://dev.to/jackm-singularity/ai-agent-cost-forecasting-predict-workflow-spend-before-users-hit-run-190h</link>
      <guid>https://dev.to/jackm-singularity/ai-agent-cost-forecasting-predict-workflow-spend-before-users-hit-run-190h</guid>
      <description>&lt;p&gt;One failed AI workflow is annoying. One successful workflow that quietly costs more than the customer paid is worse.&lt;/p&gt;

&lt;p&gt;That is the uncomfortable gap many builders hit after the demo works. The agent can search, retrieve, call tools, draft outputs, and recover from errors. But before a user clicks &lt;strong&gt;Run&lt;/strong&gt;, the product often has no honest answer to a simple question: &lt;strong&gt;How much could this job cost?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This guide shows how to build AI agent cost forecasting into your product workflow before spend hurts pricing, reliability, or trust.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The goal is not to make every token predictable. The goal is to make cost visible enough that your app can choose safer routes before money disappears.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Cost Forecasting Is Becoming a Product Feature
&lt;/h2&gt;

&lt;p&gt;AI cost tracking is no longer rare. Recent AI cost governance reporting highlighted a sharp split: most teams can see AI infrastructure spend after it happens, but only a small minority can forecast it accurately before the work runs.&lt;/p&gt;

&lt;p&gt;That matters because agent workflows are not simple API calls. They branch.&lt;/p&gt;

&lt;p&gt;A normal LLM feature might look 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;input -&amp;gt; model -&amp;gt; output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An agent workflow often looks more 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;input
  -&amp;gt; plan
  -&amp;gt; retrieve documents
  -&amp;gt; call tool
  -&amp;gt; inspect result
  -&amp;gt; retry with different arguments
  -&amp;gt; call another model
  -&amp;gt; summarize
  -&amp;gt; validate
  -&amp;gt; repair output
  -&amp;gt; send final answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every branch can add tokens, tool calls, latency, and failure handling. If your product only calculates cost after the run, you are not forecasting. You are reading the receipt.&lt;/p&gt;

&lt;p&gt;For solo developers and small teams, this is painful because one cost mistake can damage margin, pricing, reliability, trust, and support at the same time.&lt;/p&gt;

&lt;p&gt;A cost forecast gives your app a chance to warn, route, cap, queue, downgrade, or ask for approval before the workflow starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Search Gap: Builders Need Pre-Run Patterns, Not More Dashboards
&lt;/h2&gt;

&lt;p&gt;Most AI cost content focuses on dashboards, provider pricing, or generic optimization tips. Those help after spend exists, but they miss the decision point that matters most in agent products:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should happen before the user starts an expensive workflow?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Common developer questions are practical: estimating tokens before a call, pricing variable tool usage, stopping retry waste, handling trials, showing credits clearly, and forecasting across tenants without leaking data.&lt;/p&gt;

&lt;p&gt;That is the underserved angle. The product needs a &lt;strong&gt;forecasting layer&lt;/strong&gt;, not just a monitoring chart.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Simple Mental Model: Quote, Reserve, Run, Reconcile
&lt;/h2&gt;

&lt;p&gt;Treat each agent run like a job with a cost contract.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Quote       -&amp;gt; estimate likely, low, and high cost
2. Reserve     -&amp;gt; hold budget or credits before execution
3. Run         -&amp;gt; enforce limits while work happens
4. Reconcile   -&amp;gt; compare forecast vs actual and learn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern works whether you charge by credits, seats, tasks, usage, or internal plan limits.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Quote
&lt;/h3&gt;

&lt;p&gt;Before the run starts, estimate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;input tokens&lt;/li&gt;
&lt;li&gt;retrieval tokens&lt;/li&gt;
&lt;li&gt;model output tokens&lt;/li&gt;
&lt;li&gt;tool call count&lt;/li&gt;
&lt;li&gt;retry count&lt;/li&gt;
&lt;li&gt;validation or repair calls&lt;/li&gt;
&lt;li&gt;fallback model probability&lt;/li&gt;
&lt;li&gt;expected latency band&lt;/li&gt;
&lt;li&gt;worst-case cap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The quote should not pretend to be exact. Use ranges.&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;"workflow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"research_report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"estimated_cost_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"low_cost_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"high_cost_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&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;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Large source set and possible citation repair step"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_allowed_cost_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.25&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;A range is more honest than a fake precise number.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Reserve
&lt;/h3&gt;

&lt;p&gt;A forecast without enforcement is just decoration. Reserve budget before the job starts: subtract estimated credits, hold tenant-level budget, block runs above policy, ask approval for expensive jobs, or downgrade to a cheaper route when budget is tight.&lt;/p&gt;

&lt;p&gt;Reservation prevents the classic failure mode: a user has 20 credits, the agent spends 80, and your app must either eat the cost or create a bad user experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Run
&lt;/h3&gt;

&lt;p&gt;During execution, compare actual spend against the forecast.&lt;/p&gt;

&lt;p&gt;Useful runtime checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stop if actual cost passes the hard cap&lt;/li&gt;
&lt;li&gt;warn if spend crosses 50%, 75%, and 90% of budget&lt;/li&gt;
&lt;li&gt;switch models if the job is low risk&lt;/li&gt;
&lt;li&gt;reduce retrieval window when context grows too large&lt;/li&gt;
&lt;li&gt;stop retry loops after a fixed budget&lt;/li&gt;
&lt;li&gt;ask for approval before continuing expensive branches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The workflow should know when it is becoming more expensive than promised.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Reconcile
&lt;/h3&gt;

&lt;p&gt;After the run finishes, compare forecast and actual.&lt;/p&gt;

&lt;p&gt;Track variance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;forecast_variance = (actual_cost - estimated_cost) / estimated_cost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a workflow repeatedly costs 2x the estimate, you have a model problem, prompt problem, retrieval problem, or product problem. Reconciliation turns cost surprises into engineering feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the Forecast From Workflow Steps
&lt;/h2&gt;

&lt;p&gt;Do not forecast one giant blob. Forecast each stage.&lt;/p&gt;

&lt;p&gt;Here is a practical structure:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Forecast Signal&lt;/th&gt;
&lt;th&gt;Common Cost Risk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Intake&lt;/td&gt;
&lt;td&gt;user input length, attachments&lt;/td&gt;
&lt;td&gt;huge files, pasted logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retrieval&lt;/td&gt;
&lt;td&gt;top-k, chunk size, filters&lt;/td&gt;
&lt;td&gt;too many irrelevant chunks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Planning&lt;/td&gt;
&lt;td&gt;model choice, task complexity&lt;/td&gt;
&lt;td&gt;over-planning simple tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool calls&lt;/td&gt;
&lt;td&gt;allowed tools, rate limits&lt;/td&gt;
&lt;td&gt;loops, bad arguments, slow APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generation&lt;/td&gt;
&lt;td&gt;output length, format&lt;/td&gt;
&lt;td&gt;long reports, verbose JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validation&lt;/td&gt;
&lt;td&gt;schema checks, judges, repair&lt;/td&gt;
&lt;td&gt;repeated repair calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fallback&lt;/td&gt;
&lt;td&gt;provider health, confidence&lt;/td&gt;
&lt;td&gt;expensive backup models&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This stage-level forecast is easier to debug than a single total.&lt;/p&gt;

&lt;p&gt;Example forecast object:&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;type&lt;/span&gt; &lt;span class="nx"&gt;CostForecast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;workflow&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="nl"&gt;tenantId&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="nl"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&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="s1"&gt;credits&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;estimate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;low&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;high&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;low&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="s1"&gt;medium&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="s1"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;hardCap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;estimate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;high&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;assumptions&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="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="nl"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;requireApproval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;downgradeAllowed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;stopOnCap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&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;
  
  
  Start With a Rough Token Estimate
&lt;/h2&gt;

&lt;p&gt;You can estimate input tokens before calling the model. It will not be perfect, but it is enough for routing.&lt;/p&gt;

&lt;p&gt;For many English-heavy apps, a quick approximation is:&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;function&lt;/span&gt; &lt;span class="nf"&gt;roughTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For production, use the tokenizer for your target model when possible. But even a rough estimate catches obvious problems like a user pasting a 90,000-character transcript into a workflow meant for short tickets.&lt;/p&gt;

&lt;p&gt;A basic model call estimate:&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;type&lt;/span&gt; &lt;span class="nx"&gt;ModelPricing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;inputPerMillion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outputPerMillion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;estimateModelCost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;inputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;expectedOutputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;pricing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ModelPricing&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inputCost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputTokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pricing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputPerMillion&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000&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;outputCost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expectedOutputTokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pricing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputPerMillion&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;inputCost&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;outputCost&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;Then multiply by workflow assumptions:&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;plannedCalls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&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;retryMultiplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.4&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;validationMultiplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.2&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;forecast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;baseModelCost&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;plannedCalls&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;retryMultiplier&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;validationMultiplier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not elegant. It is useful. Early forecasting is about catching bad orders of magnitude.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add Complexity Bands Instead of Guessing Every Branch
&lt;/h2&gt;

&lt;p&gt;Trying to predict every possible agent path will drive you mad. Use complexity bands.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Band&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Multiplier&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;td&gt;short input, one tool, no retrieval&lt;/td&gt;
&lt;td&gt;1.0x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;retrieval, two to four model calls&lt;/td&gt;
&lt;td&gt;2.5x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;multiple tools, long output, validation&lt;/td&gt;
&lt;td&gt;5.0x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Risky&lt;/td&gt;
&lt;td&gt;unknown input, browser/tool loops, low confidence&lt;/td&gt;
&lt;td&gt;8.0x+&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A classifier can assign the band before execution.&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;function&lt;/span&gt; &lt;span class="nf"&gt;classifyRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;inputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;toolsAllowed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;needsRetrieval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;expectedOutput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;short&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="s1"&gt;medium&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="s1"&gt;long&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;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputTokens&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20000&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toolsAllowed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;risky&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attachments&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expectedOutput&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;long&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;large&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;needsRetrieval&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toolsAllowed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;small&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;This gives your product a clear policy surface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Small runs execute immediately.&lt;/li&gt;
&lt;li&gt;Medium runs execute with a normal cap.&lt;/li&gt;
&lt;li&gt;Large runs show an estimate.&lt;/li&gt;
&lt;li&gt;Risky runs require approval or a trimmed scope.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Forecast Tool Costs Separately From Token Costs
&lt;/h2&gt;

&lt;p&gt;Agent tools are often treated as free because they do not appear in the model invoice. That is a mistake.&lt;/p&gt;

&lt;p&gt;Tool calls can cost money through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;paid APIs&lt;/li&gt;
&lt;li&gt;database load&lt;/li&gt;
&lt;li&gt;vector search queries&lt;/li&gt;
&lt;li&gt;browser sessions&lt;/li&gt;
&lt;li&gt;queue workers&lt;/li&gt;
&lt;li&gt;file processing&lt;/li&gt;
&lt;li&gt;web scraping bandwidth&lt;/li&gt;
&lt;li&gt;human review time&lt;/li&gt;
&lt;li&gt;support risk from bad actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create a tool price table even when the first prices are internal estimates.&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;"web_search"&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;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"call"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"estimated_cost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.015&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;"browser_extract"&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;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"estimated_cost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.03&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;"vector_search"&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;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"estimated_cost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.002&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;"pdf_parse"&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;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"estimated_cost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.001&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;"human_review"&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;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"minute"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"estimated_cost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.75&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 helps you avoid the trap where model tokens look cheap but the workflow is expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Budget Contracts Inside the Agent Runtime
&lt;/h2&gt;

&lt;p&gt;The forecast should become a runtime contract.&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;type&lt;/span&gt; &lt;span class="nx"&gt;BudgetContract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;runId&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="nl"&gt;tenantId&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="nl"&gt;estimatedCost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;hardCap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;spent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxModelCalls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxToolCalls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;canSpend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BudgetContract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nextCost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spent&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;nextCost&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hardCap&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;Before every model or tool call:&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;canSpend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;estimatedNextCost&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stopped&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;budget_cap_reached&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This workflow needs more budget to continue safely.&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;This makes the cap real. The agent is not merely asked to stay cheap in a prompt. The runtime enforces it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design User-Facing Cost UX Carefully
&lt;/h2&gt;

&lt;p&gt;Do not overload users with token math. Most users do not care about input-token versus output-token pricing. They care about whether the job is small, normal, or expensive.&lt;/p&gt;

&lt;p&gt;Good cost UX can show:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Estimated effort: Medium
Expected credits: 8-15
Why: This task uses document search and a validation pass.
Limit: The run will stop before 20 credits unless you approve more.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avoid scary or vague messages like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This may use tokens depending on your model provider and context window.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is technically true and practically useless.&lt;/p&gt;

&lt;p&gt;For developer-focused products, add a detail view:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;estimated model calls&lt;/li&gt;
&lt;li&gt;estimated tool calls&lt;/li&gt;
&lt;li&gt;selected model route&lt;/li&gt;
&lt;li&gt;max retries&lt;/li&gt;
&lt;li&gt;hard cap&lt;/li&gt;
&lt;li&gt;downgrade policy&lt;/li&gt;
&lt;li&gt;forecast confidence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best UX is transparent without making the user become your FinOps team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing Plans Need Forecast Classes
&lt;/h2&gt;

&lt;p&gt;Map workflows to forecast classes so every AI action is not treated as equal.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Forecast Class&lt;/th&gt;
&lt;th&gt;Typical Use&lt;/th&gt;
&lt;th&gt;Product Policy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tiny&lt;/td&gt;
&lt;td&gt;rewrite, classify, short summary&lt;/td&gt;
&lt;td&gt;included generously&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normal&lt;/td&gt;
&lt;td&gt;support answer, single tool&lt;/td&gt;
&lt;td&gt;included with fair-use caps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heavy&lt;/td&gt;
&lt;td&gt;long report, multi-document analysis&lt;/td&gt;
&lt;td&gt;consumes credits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extreme&lt;/td&gt;
&lt;td&gt;browser agent, bulk job, deep research&lt;/td&gt;
&lt;td&gt;approval or paid add-on&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This makes limits easier to explain and safer to enforce.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch These Forecast Accuracy Metrics
&lt;/h2&gt;

&lt;p&gt;A forecasting layer becomes better when you measure it. Track forecast-to-actual variance, P50/P90/P99 actual cost, cap-hit rate, approval rate, downgrade rate, retry cost share, tool cost share, margin by tenant, and confidence calibration.&lt;/p&gt;

&lt;p&gt;The most useful metric is often cost per successful outcome, not cost per model call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cost_per_success = total_workflow_cost / successful_runs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A cheap workflow that fails half the time may be more expensive than a stronger workflow that works on the first try.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes to Avoid
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Mistake 1: Forecasting Only Tokens
&lt;/h3&gt;

&lt;p&gt;Tokens are part of the bill, not the whole bill. Include tools, retries, parsing, queues, validation, and fallbacks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 2: Using Average Cost as the Cap
&lt;/h3&gt;

&lt;p&gt;Average cost is not a safe cap. Use high-percentile actuals. If the average run costs 5 credits but the P90 costs 30, your cap should know that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 3: Letting Retries Spend Without a Budget
&lt;/h3&gt;

&lt;p&gt;Retries feel harmless during testing. In production, they can become a hidden tax. Give retries their own budget.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 4: Hiding Cost Until After the Run
&lt;/h3&gt;

&lt;p&gt;Users are more forgiving of limits before work starts than surprise failures after a long wait.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 5: Treating All Tenants the Same
&lt;/h3&gt;

&lt;p&gt;Forecast by tenant, plan, workflow, and input size.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Minimal Implementation Plan
&lt;/h2&gt;

&lt;p&gt;If you are starting from zero, do not build a giant FinOps platform. Add one thin layer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a pricing config for models and tools.&lt;/li&gt;
&lt;li&gt;Estimate input size before the first model call.&lt;/li&gt;
&lt;li&gt;Assign a complexity band to each run.&lt;/li&gt;
&lt;li&gt;Generate a quote with low, estimate, high, confidence, and hard cap.&lt;/li&gt;
&lt;li&gt;Reserve credits or tenant budget before execution.&lt;/li&gt;
&lt;li&gt;Check budget before every model and tool call.&lt;/li&gt;
&lt;li&gt;Log actual cost by stage.&lt;/li&gt;
&lt;li&gt;Reconcile forecast versus actual after completion.&lt;/li&gt;
&lt;li&gt;Review high-variance workflows weekly.&lt;/li&gt;
&lt;li&gt;Update multipliers from real runs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is enough to prevent the worst surprises.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Fits in Your AI Architecture
&lt;/h2&gt;

&lt;p&gt;AI agent cost forecasting connects your LLM gateway, tool gateway, workflow engine, billing system, observability stack, policy engine, and product UI.&lt;/p&gt;

&lt;p&gt;Think of it as the pre-flight check for expensive AI work and a natural part of a broader production cost-control cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;AI agent cost forecasting is not about perfect prediction. It is about giving your product enough foresight to make safer choices.&lt;/p&gt;

&lt;p&gt;Before users hit &lt;strong&gt;Run&lt;/strong&gt;, your app should know the likely cost range, the worst-case cap, the risky branches, and what to do if the workflow starts drifting.&lt;/p&gt;

&lt;p&gt;If you can quote, reserve, run, and reconcile, you can turn AI cost from a surprise invoice into a product control system.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is AI agent cost forecasting?
&lt;/h3&gt;

&lt;p&gt;AI agent cost forecasting is the practice of estimating the likely cost of an agent workflow before it runs. It includes model tokens, tool calls, retries, validation, fallbacks, and workflow complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is cost forecasting different from cost tracking?
&lt;/h3&gt;

&lt;p&gt;Cost tracking tells you what happened after the workflow ran. Cost forecasting estimates what may happen before execution, so the product can set caps, show warnings, reserve credits, or choose cheaper routes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can token estimates be accurate enough before a model call?
&lt;/h3&gt;

&lt;p&gt;Yes, for useful ranges. They will not be exact, but rough token counts plus workflow multipliers can catch expensive inputs and high-risk jobs before execution starts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should users see the exact dollar cost of every AI run?
&lt;/h3&gt;

&lt;p&gt;Not always. Many users prefer simple ranges such as small, medium, or heavy. Developer-facing products can also expose detailed estimates for model calls, tools, retries, and hard caps.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the best first metric to track?
&lt;/h3&gt;

&lt;p&gt;Start with forecast-to-actual variance by workflow. It quickly shows which workflows are predictable, which ones need better multipliers, and which ones may need product or engineering changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do retries affect AI workflow cost?
&lt;/h3&gt;

&lt;p&gt;Retries can multiply cost because each retry may trigger another model call, tool call, validation step, or fallback. Give retries their own budget and stop them when the expected value is low.&lt;/p&gt;

&lt;h3&gt;
  
  
  How should pricing plans handle expensive agent workflows?
&lt;/h3&gt;

&lt;p&gt;Group workflows into forecast classes such as tiny, normal, heavy, and extreme. Then map each class to plan limits, credits, approvals, or add-ons instead of treating every AI action as equal.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>llm</category>
      <category>saas</category>
    </item>
    <item>
      <title>AI Agent Workspace Architecture: Give Agents Files, Tools, and Limits</title>
      <dc:creator>Jack M</dc:creator>
      <pubDate>Tue, 11 Aug 2026 04:40:16 +0000</pubDate>
      <link>https://dev.to/jackm-singularity/ai-agent-workspace-architecture-give-agents-files-tools-and-limits-1g87</link>
      <guid>https://dev.to/jackm-singularity/ai-agent-workspace-architecture-give-agents-files-tools-and-limits-1g87</guid>
      <description>&lt;p&gt;An AI agent does not become useful because it has a longer prompt. It becomes useful when it has the right place to work: files it can inspect, tools it can call, state it can resume, and limits it cannot ignore.&lt;/p&gt;

&lt;p&gt;That is the shift many builders are feeling now. Chatbots answer. Agents operate. But if you drop an agent into your product with only a system prompt and a handful of API tools, you will soon hit the same problems: messy context, unclear permissions, hard-to-debug tool calls, and costs that rise quietly in the background.&lt;/p&gt;

&lt;p&gt;The fix is not “more autonomy.” The fix is a workspace architecture.&lt;/p&gt;

&lt;p&gt;A good AI agent workspace gives the model a controlled environment where it can explore, plan, act, pause, and leave evidence. This guide covers what to store, expose, scope, review, and trace for real customers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is an AI Agent Workspace?
&lt;/h2&gt;

&lt;p&gt;An AI agent workspace is the runtime environment where an agent does its work.&lt;/p&gt;

&lt;p&gt;It usually includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a task brief&lt;/li&gt;
&lt;li&gt;user or tenant context&lt;/li&gt;
&lt;li&gt;files, documents, or structured records&lt;/li&gt;
&lt;li&gt;tools and APIs&lt;/li&gt;
&lt;li&gt;memory or run history&lt;/li&gt;
&lt;li&gt;permissions&lt;/li&gt;
&lt;li&gt;budgets&lt;/li&gt;
&lt;li&gt;traces&lt;/li&gt;
&lt;li&gt;approval gates&lt;/li&gt;
&lt;li&gt;output artifacts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it as the difference between giving a contractor a vague Slack message and giving them a project folder, access rules, a checklist, and a way to submit work for review.&lt;/p&gt;

&lt;p&gt;The workspace decides what the model can see, change, resume, and prove.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Workspace Design Matters Now
&lt;/h2&gt;

&lt;p&gt;Recent AI tool trends point in one direction: agents are moving from chat boxes into work environments.&lt;/p&gt;

&lt;p&gt;News and search signals show growing interest in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;agent apps that switch between models and tools&lt;/li&gt;
&lt;li&gt;embedded agent frameworks for app builders&lt;/li&gt;
&lt;li&gt;enterprise agent workspaces with durable state&lt;/li&gt;
&lt;li&gt;browser and desktop environments for agents&lt;/li&gt;
&lt;li&gt;AI workflows that need permissions, traces, and cost controls&lt;/li&gt;
&lt;li&gt;open-source automation tools adding native AI capabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Developers are not only asking, “Which model should I use?” They are asking, “Where should the agent work?”&lt;/p&gt;

&lt;p&gt;That matters because many production failures are environment failures, not pure model failures.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure&lt;/th&gt;
&lt;th&gt;Workspace cause&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agent forgets the goal&lt;/td&gt;
&lt;td&gt;No durable task state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent leaks data across customers&lt;/td&gt;
&lt;td&gt;Shared context or weak tenant filters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent calls the wrong API&lt;/td&gt;
&lt;td&gt;Tools lack scoped contracts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent burns tokens&lt;/td&gt;
&lt;td&gt;No budget or progress checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent gives polished nonsense&lt;/td&gt;
&lt;td&gt;No source evidence or review gate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent cannot recover&lt;/td&gt;
&lt;td&gt;No step log, artifacts, or retry plan&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you are building AI features for customers, the workspace is not a nice extra. It is the control plane.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Workspace Layers
&lt;/h2&gt;

&lt;p&gt;A production-ready agent workspace has five layers.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Task Layer
&lt;/h3&gt;

&lt;p&gt;The task layer defines what the agent is trying to do.&lt;/p&gt;

&lt;p&gt;It should include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;user request&lt;/li&gt;
&lt;li&gt;success criteria&lt;/li&gt;
&lt;li&gt;constraints&lt;/li&gt;
&lt;li&gt;expected output format&lt;/li&gt;
&lt;li&gt;deadline or budget&lt;/li&gt;
&lt;li&gt;risk level&lt;/li&gt;
&lt;li&gt;allowed data sources&lt;/li&gt;
&lt;li&gt;approval requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid sending only the raw user prompt. User prompts are often vague, emotional, or missing context. Convert the request into a task object the system can inspect.&lt;/p&gt;

&lt;p&gt;Example:&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;"task_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"task_481"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tenant_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tenant_acme"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"goal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Create a draft onboarding email sequence from the approved product notes."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success_criteria"&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="s2"&gt;"Use only approved product notes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Create 5 emails"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Include subject lines"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Do not send emails"&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;"risk_level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"draft_only"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_model_cost_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requires_human_approval"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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 turns a loose prompt into a contract.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Context Layer
&lt;/h3&gt;

&lt;p&gt;The context layer decides what the agent can read.&lt;/p&gt;

&lt;p&gt;This is where many teams make the first big mistake. They either send too little context, so the agent guesses, or too much context, so the agent gets slow, expensive, and easier to manipulate.&lt;/p&gt;

&lt;p&gt;Use a context packet instead of a context dump.&lt;/p&gt;

&lt;p&gt;A good context packet has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;short task summary&lt;/li&gt;
&lt;li&gt;selected records&lt;/li&gt;
&lt;li&gt;source IDs&lt;/li&gt;
&lt;li&gt;permissions attached to each source&lt;/li&gt;
&lt;li&gt;freshness timestamp&lt;/li&gt;
&lt;li&gt;excluded data list&lt;/li&gt;
&lt;li&gt;citation rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&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;"context_packet"&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;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Customer is configuring billing alerts for usage-based plans."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sources"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"doc_17"&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;"help_doc"&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;"Usage Billing Alerts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"freshness"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"current"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"permission"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tenant_read"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ticket_3391"&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;"support_ticket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"permission"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_visible"&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;"excluded"&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;"internal_pricing_notes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"other_tenant_tickets"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"citation_required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;The point is not to hide useful information. The point is to make context intentional.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. File and Artifact Layer
&lt;/h3&gt;

&lt;p&gt;Agents work better when they can create and revise artifacts.&lt;/p&gt;

&lt;p&gt;A workspace should provide a small file system or artifact store where the agent can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;read approved inputs&lt;/li&gt;
&lt;li&gt;create drafts&lt;/li&gt;
&lt;li&gt;save intermediate notes&lt;/li&gt;
&lt;li&gt;produce final outputs&lt;/li&gt;
&lt;li&gt;attach evidence&lt;/li&gt;
&lt;li&gt;leave a diff for review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is especially useful for coding agents, report generators, onboarding assistants, research agents, and data analysis workflows.&lt;/p&gt;

&lt;p&gt;Keep files separated by purpose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/workspace
  /input
    product_notes.md
    customer_profile.json
  /scratch
    plan.md
    extracted_claims.json
  /output
    onboarding_sequence.md
  /evidence
    source_map.json
    tool_trace.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;/scratch&lt;/code&gt; folder is important. Agents need space to reason through work, but scratch content should not automatically become customer-facing output.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Tool Layer
&lt;/h3&gt;

&lt;p&gt;The tool layer defines what the agent can do.&lt;/p&gt;

&lt;p&gt;Wrap every tool in a contract; do not give raw API access.&lt;/p&gt;

&lt;p&gt;A tool contract should define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;name&lt;/li&gt;
&lt;li&gt;purpose&lt;/li&gt;
&lt;li&gt;input schema&lt;/li&gt;
&lt;li&gt;output schema&lt;/li&gt;
&lt;li&gt;permission requirement&lt;/li&gt;
&lt;li&gt;side effects&lt;/li&gt;
&lt;li&gt;rate limit&lt;/li&gt;
&lt;li&gt;cost estimate&lt;/li&gt;
&lt;li&gt;risk tier&lt;/li&gt;
&lt;li&gt;whether approval is required&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example TypeScript-style contract:&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;type&lt;/span&gt; &lt;span class="nx"&gt;AgentTool&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;O&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;description&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="nl"&gt;risk&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&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;draft&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;write&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;external&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;requiresApproval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;I&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ToolContext&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;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;O&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&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;createDraftEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgentTool&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;customerId&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="nl"&gt;subject&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="nl"&gt;body&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;draftId&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="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&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;create_draft_email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Create an email draft. Does not send it.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;draft&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;requiresApproval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertTenant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDraft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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;p&gt;Notice the wording: “Does not send it.” Tool descriptions should remove ambiguity. If a tool writes, sends, deletes, pays, invites, exports, or changes permissions, say that clearly and gate it.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. State and Trace Layer
&lt;/h3&gt;

&lt;p&gt;The state layer lets the agent resume. The trace layer lets humans debug.&lt;/p&gt;

&lt;p&gt;Store:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;task status&lt;/li&gt;
&lt;li&gt;current step&lt;/li&gt;
&lt;li&gt;model calls&lt;/li&gt;
&lt;li&gt;tool calls&lt;/li&gt;
&lt;li&gt;tool inputs and redacted outputs&lt;/li&gt;
&lt;li&gt;cost per step&lt;/li&gt;
&lt;li&gt;approvals&lt;/li&gt;
&lt;li&gt;errors&lt;/li&gt;
&lt;li&gt;final artifacts&lt;/li&gt;
&lt;li&gt;user-visible summary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You do not need to log every token forever. But you do need enough evidence to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What did the agent know?&lt;/li&gt;
&lt;li&gt;What did it decide?&lt;/li&gt;
&lt;li&gt;What tool did it call?&lt;/li&gt;
&lt;li&gt;Who approved it?&lt;/li&gt;
&lt;li&gt;What changed?&lt;/li&gt;
&lt;li&gt;How much did it cost?&lt;/li&gt;
&lt;li&gt;Can we replay or fix it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without traces, every production issue becomes a mystery.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Simple Reference Architecture
&lt;/h2&gt;

&lt;p&gt;Here is a practical flow for an AI agent workspace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User request
   ↓
Task builder
   ↓
Policy check ── rejects unsafe or unsupported tasks
   ↓
Context packet builder
   ↓
Workspace created
   ↓
Agent explores files and tools
   ↓
Plan generated
   ↓
Risk check
   ↓
Tool execution / draft artifact creation
   ↓
Approval gate if needed
   ↓
Final output + evidence summary
   ↓
Trace stored for audit and improvement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not tied to one framework. You can build it with a custom orchestrator, a workflow engine, an agent SDK, serverless functions, queues, or a background worker.&lt;/p&gt;

&lt;p&gt;The important part is the boundary: the agent does not float freely through your product. It works inside a workspace with rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Scope Files Without Breaking Usefulness
&lt;/h2&gt;

&lt;p&gt;File access should be boring and explicit.&lt;/p&gt;

&lt;p&gt;Use these rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Default deny.&lt;/strong&gt; The agent sees no file unless the task builder includes it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate input, scratch, output, and evidence.&lt;/strong&gt; Do not mix raw data with generated answers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attach permissions to files.&lt;/strong&gt; A support ticket, invoice, and internal note should not have the same visibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make writes reversible.&lt;/strong&gt; Draft first. Apply later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expire workspaces.&lt;/strong&gt; Do not keep sensitive temporary context longer than needed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A common pattern is to create a workspace per task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/workspaces/{tenant_id}/{task_id}/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then enforce all reads and writes through a workspace service. The model should never receive a raw storage bucket path or unrestricted file browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Design Tool Permissions
&lt;/h2&gt;

&lt;p&gt;Tool permissions should follow the action, not only the user.&lt;/p&gt;

&lt;p&gt;A user may have permission to delete a record. That does not mean an agent should inherit delete access for every task.&lt;/p&gt;

&lt;p&gt;Use risk tiers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;th&gt;Default behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read&lt;/td&gt;
&lt;td&gt;search docs, fetch ticket, inspect settings&lt;/td&gt;
&lt;td&gt;allow with tenant scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Draft&lt;/td&gt;
&lt;td&gt;create draft email, generate report, propose config&lt;/td&gt;
&lt;td&gt;allow, no external side effect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write&lt;/td&gt;
&lt;td&gt;update CRM field, change workflow, create ticket&lt;/td&gt;
&lt;td&gt;require policy check or approval&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External&lt;/td&gt;
&lt;td&gt;send email, charge card, invite user, publish post&lt;/td&gt;
&lt;td&gt;require explicit approval&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dangerous&lt;/td&gt;
&lt;td&gt;delete data, rotate keys, change permissions&lt;/td&gt;
&lt;td&gt;block or require high-trust flow&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This model keeps simple tasks fast while preventing quiet damage.&lt;/p&gt;

&lt;p&gt;Also add tool budgets:&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;"tool_budget"&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;"max_calls_total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max_search_calls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max_write_calls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max_runtime_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max_cost_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.00&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;Budgets are not only for cost. They also catch stuck workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workspace Memory: What to Keep and What to Forget
&lt;/h2&gt;

&lt;p&gt;Agent memory is useful, but it should not store everything. Split it into three buckets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Run memory:&lt;/strong&gt; temporary state for the current task&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User memory:&lt;/strong&gt; stable preferences the user expects you to remember&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System memory:&lt;/strong&gt; product rules, policies, and workflow instructions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not let run memory silently become user memory. If the agent learns something long-term, make that an explicit product decision. Add simple rules: short TTL for run memory, consent for user memory, no sensitive fields by default, and no cross-tenant memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Human Review Should Be Part of the Workspace
&lt;/h2&gt;

&lt;p&gt;Human-in-the-loop should be built into the workspace, not bolted on later. When a task crosses a risk boundary, pause the run and create a review packet with the requested action, exact tool input, expected side effect, source evidence, and approve/reject/edit controls.&lt;/p&gt;

&lt;p&gt;Bad review UX says: “The agent wants to proceed. Approve?”&lt;/p&gt;

&lt;p&gt;Good review UX says: “The agent wants to send this email to these 142 users using this subject and body, based on these sources. Approve, edit, or cancel?”&lt;/p&gt;

&lt;p&gt;Approval is a trust interface, not a checkbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Minimum Viable Workspace
&lt;/h2&gt;

&lt;p&gt;If you are early, start with a minimum viable workspace:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Task object&lt;/li&gt;
&lt;li&gt;Context packet&lt;/li&gt;
&lt;li&gt;Scoped file/artifact store&lt;/li&gt;
&lt;li&gt;Tool registry with risk tiers&lt;/li&gt;
&lt;li&gt;Cost and tool-call budget&lt;/li&gt;
&lt;li&gt;Trace log&lt;/li&gt;
&lt;li&gt;Approval gate for external actions&lt;/li&gt;
&lt;li&gt;Final answer with evidence links&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is enough to move from “cool demo” to “controlled workflow.”&lt;/p&gt;

&lt;p&gt;The exact code will change by stack, but the shape should not: create a task, build scoped context, attach allowed tools, enforce budgets, record traces, and pause when approval is required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes to Avoid
&lt;/h2&gt;

&lt;p&gt;The fastest way to weaken an agent workspace is to treat soft instructions as hard controls. Watch for these traps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompt-only safety:&lt;/strong&gt; a prompt can say “do not access private data,” but the workspace should make private data unavailable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full user permissions:&lt;/strong&gt; user access should be narrowed to task-scoped agent access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No scratch space:&lt;/strong&gt; without drafts, plans and final answers get mixed together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No cost visibility:&lt;/strong&gt; retries, retrieval, and long context can hide expensive runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No replay path:&lt;/strong&gt; if you cannot replay a failed run, you cannot improve it reliably.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Search Gap to Target
&lt;/h2&gt;

&lt;p&gt;Most ranking content explains agent tools, memory, permissions, or broad enterprise diagrams. The underserved angle is practical glue: how files, scratch space, task-scoped tools, review packets, state, and replay fit into one workspace developers can actually build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Checklist
&lt;/h2&gt;

&lt;p&gt;Before shipping an agent workspace, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does every run have a task object?&lt;/li&gt;
&lt;li&gt;Is context selected, scoped, and cited?&lt;/li&gt;
&lt;li&gt;Are files separated by input, scratch, output, and evidence?&lt;/li&gt;
&lt;li&gt;Does every tool have a risk tier?&lt;/li&gt;
&lt;li&gt;Are external actions approved before execution?&lt;/li&gt;
&lt;li&gt;Are cost and tool budgets enforced?&lt;/li&gt;
&lt;li&gt;Can the agent pause and resume?&lt;/li&gt;
&lt;li&gt;Can humans inspect the trace?&lt;/li&gt;
&lt;li&gt;Can failed runs be replayed safely?&lt;/li&gt;
&lt;li&gt;Does memory expire or require consent?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer is “no” to several of these, the agent is not ready for production autonomy. Keep it in draft mode until the workspace catches up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The next wave of useful AI products will not be won by prompts alone. It will be won by builders who give agents a safe, structured place to work.&lt;/p&gt;

&lt;p&gt;An AI agent workspace turns a model call into an operating environment. It gives the agent files, tools, memory, permissions, budgets, traces, and human review. It also gives your team something just as important: a way to understand what happened when the agent succeeds, fails, or asks for help.&lt;/p&gt;

&lt;p&gt;Start small: create the task object, build the context packet, scope the tools, store the trace, and require approval before external actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is an AI agent workspace?
&lt;/h3&gt;

&lt;p&gt;An AI agent workspace is a controlled runtime where an agent can read context, use tools, create files, store state, and produce outputs under defined permissions and budgets.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is an agent workspace different from a prompt?
&lt;/h3&gt;

&lt;p&gt;A prompt tells the model what to do. A workspace controls what the agent can access, where it can write, which tools it can call, how much it can spend, and when it must ask for approval.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do small teams need an AI agent workspace?
&lt;/h3&gt;

&lt;p&gt;Yes, but it can be simple. A small team can start with a task object, context packet, scoped tools, trace log, and approval gate for external actions. That is enough to reduce many early production risks.&lt;/p&gt;

&lt;h3&gt;
  
  
  What should an agent workspace store?
&lt;/h3&gt;

&lt;p&gt;Store task state, selected context, input files, scratch files, output artifacts, tool calls, model calls, approvals, cost, errors, and evidence links. Redact sensitive fields where needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should agents inherit user permissions?
&lt;/h3&gt;

&lt;p&gt;Agents should not blindly inherit all user permissions. They should receive task-scoped permissions based on the current goal, risk tier, tenant, and approval state.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I control agent workspace cost?
&lt;/h3&gt;

&lt;p&gt;Set budgets for model spend, tool calls, retries, runtime, and context size. Track cost per step and stop runs that exceed the budget or stop making progress.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>saas</category>
      <category>architecture</category>
      <category>agents</category>
    </item>
    <item>
      <title>Visual QA Agents: Catch UI Regressions Before AI-Written Code Ships</title>
      <dc:creator>Jack M</dc:creator>
      <pubDate>Mon, 10 Aug 2026 04:46:15 +0000</pubDate>
      <link>https://dev.to/jackm-singularity/visual-qa-agents-catch-ui-regressions-before-ai-written-code-ships-28pg</link>
      <guid>https://dev.to/jackm-singularity/visual-qa-agents-catch-ui-regressions-before-ai-written-code-ships-28pg</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;AI coding agents can ship a working feature and still break the page users actually see. A visual QA agent closes that gap by driving the app like a user, comparing screenshots, checking flows, and refusing to let a polished pull request hide a broken interface.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AI-assisted development has changed the speed of shipping. A solo builder can ask an agent to add a dashboard, wire a settings page, or refactor onboarding in minutes. That speed is useful, but it creates a new failure mode: the code compiles, the unit tests pass, and the UI is wrong.&lt;/p&gt;

&lt;p&gt;The button moved under a modal. A pricing card overflows on mobile. A loading state covers the main action. A generated component uses the wrong tenant data. The pull request looks fine in text, but the product feels broken.&lt;/p&gt;

&lt;p&gt;That is where visual QA agents are becoming practical. Instead of treating QA as a manual pass at the end, you give an agent a scoped test mission: open the app, perform real user journeys, capture evidence, compare against baselines, and report what changed.&lt;/p&gt;

&lt;p&gt;This guide shows how to build that workflow without turning it into a flaky science project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why visual QA agents matter now
&lt;/h2&gt;

&lt;p&gt;The current wave of AI developer tools is not only writing code. Tools are moving toward full development environments where agents edit files, run tests, inspect browser output, and watch production signals. Recent product launches and developer discussions point in the same direction: builders want AI speed, but they do not want regression risk to grow with every generated change.&lt;/p&gt;

&lt;p&gt;Traditional automated tests still matter. Unit tests catch logic errors. API tests catch contract breaks. Type checks catch shape mismatches. But UI regressions are often visual, contextual, and workflow-specific.&lt;/p&gt;

&lt;p&gt;A visual QA agent is useful because it can combine four things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browser automation that follows real user paths&lt;/li&gt;
&lt;li&gt;Screenshot and DOM inspection for visible regressions&lt;/li&gt;
&lt;li&gt;Test reasoning that explains why a change is risky&lt;/li&gt;
&lt;li&gt;CI evidence that a reviewer can inspect quickly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to replace human judgment. The goal is to stop obvious, expensive UI mistakes before a human reviewer has to find them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search intent and content gap
&lt;/h2&gt;

&lt;p&gt;Most content around AI testing falls into one of three buckets:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tool lists that compare AI QA products&lt;/li&gt;
&lt;li&gt;High-level posts about test automation&lt;/li&gt;
&lt;li&gt;Visual regression tutorials focused on pixel diffs only&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The missing practical guide is the middle layer: how a small product team should design visual QA agents for AI-written code. Builders need a pattern that covers baselines, browser flows, accessibility checks, false positives, tenant-safe test data, CI gates, and human review.&lt;/p&gt;

&lt;p&gt;That is the gap this article targets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target keyword:&lt;/strong&gt; visual QA agents&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Long-tail variants:&lt;/strong&gt; AI visual regression testing, AI coding regression testing, browser QA agents, visual testing for AI-generated code, AI QA agent workflow&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Audience:&lt;/strong&gt; solo developers, micro product builders, AI product engineers, and technical founders shipping AI-assisted features&lt;/p&gt;
&lt;h2&gt;
  
  
  What a visual QA agent should do
&lt;/h2&gt;

&lt;p&gt;A useful visual QA agent is not a vague prompt that says, “check the UI.” It needs a clear job contract.&lt;/p&gt;

&lt;p&gt;A good contract looks like this:&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;"mission"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Validate the billing settings flow after a UI change"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"routes"&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;"/login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/settings/billing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/checkout"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"viewports"&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;"desktop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mobile"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_roles"&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;"owner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"member"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"must_verify"&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="s2"&gt;"primary actions are visible"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"current plan is shown correctly"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"upgrade button opens checkout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"member role cannot edit payment method"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"no layout overflow on mobile"&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;"evidence_required"&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;"screenshots"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DOM notes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"console errors"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"network failures"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"risk_threshold"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"block_on_high"&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 keeps the agent from wandering. It also gives your CI system a concrete pass/fail shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture: browser runner, evidence store, judge, and gate
&lt;/h2&gt;

&lt;p&gt;You can build visual QA agents with a simple four-part architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Browser runner
&lt;/h3&gt;

&lt;p&gt;The browser runner opens your app in a controlled environment. It logs in with seeded test accounts, visits target routes, performs actions, and captures screenshots.&lt;/p&gt;

&lt;p&gt;Popular choices include Playwright, Cypress, WebDriver, and browser automation APIs built into agent environments. The specific tool matters less than repeatability.&lt;/p&gt;

&lt;p&gt;The runner should capture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Screenshot before and after the change&lt;/li&gt;
&lt;li&gt;Viewport size&lt;/li&gt;
&lt;li&gt;URL and route params&lt;/li&gt;
&lt;li&gt;Console errors&lt;/li&gt;
&lt;li&gt;Failed network calls&lt;/li&gt;
&lt;li&gt;Accessibility snapshot when available&lt;/li&gt;
&lt;li&gt;DOM snippets around important elements&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Evidence store
&lt;/h3&gt;

&lt;p&gt;Do not let the agent only return a paragraph. Store evidence as files and metadata.&lt;/p&gt;

&lt;p&gt;A simple structure works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;qa-runs/
  2026-08-10-billing-settings/
    run.json
    desktop-before.png
    desktop-after.png
    desktop-diff.png
    mobile-before.png
    mobile-after.png
    console.log
    network.json
    report.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters because reviewers need proof. If the agent says “the layout looks broken,” the report should link to the screenshot and the exact route.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Visual judge
&lt;/h3&gt;

&lt;p&gt;The judge compares the current run against an expected baseline. It can use pixel diffing, layout rules, OCR, DOM assertions, or an LLM vision check.&lt;/p&gt;

&lt;p&gt;Use more than one signal. Pixel diffs are good at catching movement, but bad at understanding intent. A small copy update may create a big diff. A broken disabled button may create almost no diff.&lt;/p&gt;

&lt;p&gt;Better checks combine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pixel difference threshold&lt;/li&gt;
&lt;li&gt;Element visibility assertions&lt;/li&gt;
&lt;li&gt;Text presence checks&lt;/li&gt;
&lt;li&gt;Accessibility checks&lt;/li&gt;
&lt;li&gt;Console and network error checks&lt;/li&gt;
&lt;li&gt;LLM-assisted explanation for uncertain cases&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. CI gate
&lt;/h3&gt;

&lt;p&gt;The gate decides what happens next.&lt;/p&gt;

&lt;p&gt;A practical gate has three outcomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pass:&lt;/strong&gt; no meaningful visual or flow risk detected&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Warn:&lt;/strong&gt; visible change found, but likely intentional; reviewer should inspect&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Block:&lt;/strong&gt; critical action broken, layout unusable, security issue, wrong data, or checkout/login/onboarding failure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not make the AI judge the final business decision alone. Let it produce evidence and a risk score. Let CI enforce rules for clearly unsafe states.&lt;/p&gt;

&lt;h2&gt;
  
  
  A minimal Playwright-based visual QA flow
&lt;/h2&gt;

&lt;p&gt;Here is a simplified example using Playwright. It captures screenshots for two viewports and checks that important actions are visible.&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&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;viewports&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="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;desktop&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1440&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;900&lt;/span&gt; &lt;span class="p"&gt;},&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;mobile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;390&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;844&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;for &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;viewport&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;viewports&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`billing settings visual QA - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;page&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="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setViewportSize&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;owner@example.test&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TEST_PASSWORD&lt;/span&gt;&lt;span class="o"&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&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;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;Sign in&lt;/span&gt;&lt;span class="dl"&gt;"&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/settings/billing&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;heading&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;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/billing/i&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&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;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&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;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/upgrade|change plan/i&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`qa-runs/billing-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.png`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;fullPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;p&gt;This is not yet an “agent.” It is the deterministic core. The agent layer should generate or select missions, inspect failures, summarize evidence, and suggest the likely cause.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add an agent report on top of deterministic tests
&lt;/h2&gt;

&lt;p&gt;After the browser run, pass structured evidence to the agent. Do not dump the whole app into the prompt. Give it a clean packet.&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;"pull_request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;184&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"changed_files"&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="s2"&gt;"src/pages/settings/billing.tsx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"src/components/PlanCard.tsx"&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;"test_mission"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"billing settings visual QA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"failures"&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;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/settings/billing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"viewport"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mobile"&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;"visibility"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Upgrade button not visible without horizontal scroll"&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;"console_errors"&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;"screenshots"&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="s2"&gt;"qa-runs/billing-mobile.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"qa-runs/billing-mobile-diff.png"&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;Then ask for a constrained report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are reviewing visual QA evidence for a pull request.
Return:
1. pass, warn, or block
2. the user impact in one sentence
3. the likely changed file responsible
4. the exact screenshot evidence
5. the smallest suggested fix
Do not invent evidence that is not in the packet.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last sentence is important. Visual QA agents should explain evidence, not hallucinate new evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which flows deserve visual QA first?
&lt;/h2&gt;

&lt;p&gt;Do not start by testing every page. You will drown in false positives and slow CI runs.&lt;/p&gt;

&lt;p&gt;Start with flows where visual breakage directly damages trust or revenue:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flow&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;th&gt;Block condition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Signup&lt;/td&gt;
&lt;td&gt;First impression and activation&lt;/td&gt;
&lt;td&gt;User cannot complete account creation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Login&lt;/td&gt;
&lt;td&gt;Access to product&lt;/td&gt;
&lt;td&gt;User cannot sign in or recover access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Billing&lt;/td&gt;
&lt;td&gt;Revenue and trust&lt;/td&gt;
&lt;td&gt;Plan, price, or checkout action is wrong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Onboarding&lt;/td&gt;
&lt;td&gt;Activation&lt;/td&gt;
&lt;td&gt;Primary next step is hidden or broken&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dashboard&lt;/td&gt;
&lt;td&gt;Daily value&lt;/td&gt;
&lt;td&gt;Key metric or action is missing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Admin settings&lt;/td&gt;
&lt;td&gt;Safety&lt;/td&gt;
&lt;td&gt;Destructive action appears for wrong role&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Support widget&lt;/td&gt;
&lt;td&gt;Retention&lt;/td&gt;
&lt;td&gt;User cannot ask for help&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For most small teams, five to ten critical journeys are enough to catch the majority of painful UI regressions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Baselines: the part teams underestimate
&lt;/h2&gt;

&lt;p&gt;Visual testing fails when baselines are messy. A baseline is the expected visual state for a route, role, viewport, and data fixture.&lt;/p&gt;

&lt;p&gt;Bad baseline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/settings/billing latest screenshot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good baseline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;route: /settings/billing
role: owner
viewport: mobile-390x844
data_fixture: paid_team_basic
feature_flags: checkout_v2=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Freeze time, seed accounts, disable animations, mask dynamic regions, separate desktop/mobile baselines, and require human approval for baseline updates. If an AI coding agent can update baselines without review, it can hide the regression it created.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to handle false positives
&lt;/h2&gt;

&lt;p&gt;Visual QA can become annoying if every harmless change blocks a merge. The answer is not to lower standards everywhere. The answer is to classify risk.&lt;/p&gt;

&lt;p&gt;Use a simple scoring 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;type&lt;/span&gt; &lt;span class="nx"&gt;VisualRisk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;routeCriticality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;elementCriticality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;diffSeverity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;assertionFailed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;consoleError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;scoreRisk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VisualRisk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;routeCriticality&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elementCriticality&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;diffSeverity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assertionFailed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;consoleError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;score&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;Then define policy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;0-4: pass&lt;/li&gt;
&lt;li&gt;5-7: warn and attach evidence&lt;/li&gt;
&lt;li&gt;8+: block until reviewed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps visual QA agents useful. A copy change on a help page should not block the same way as a missing checkout button.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the agent inspect pull request intent
&lt;/h2&gt;

&lt;p&gt;A good visual QA agent should know what changed. If the pull request edits only backend billing logic, a UI diff on the dashboard may be suspicious. If it edits a global layout component, many diffs may be expected.&lt;/p&gt;

&lt;p&gt;Give the agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Changed files&lt;/li&gt;
&lt;li&gt;Pull request summary&lt;/li&gt;
&lt;li&gt;Routes affected by those files&lt;/li&gt;
&lt;li&gt;Recent feature flags&lt;/li&gt;
&lt;li&gt;Test mission results&lt;/li&gt;
&lt;li&gt;Screenshot evidence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ask it to answer one practical question: “Does the visual change match the intent of the code change?”&lt;/p&gt;

&lt;p&gt;That framing is stronger than “does this look good?” It reduces vague feedback and helps reviewers focus.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tenant safety for test accounts
&lt;/h2&gt;

&lt;p&gt;AI product builders often work with multi-tenant data, so visual QA agents must never test against real customer accounts. Use isolated tenants with fake but realistic data: owner, member, suspended user, empty workspace, large workspace, trial workspace, and paid workspace.&lt;/p&gt;

&lt;p&gt;Add negative checks too. A member should not see billing edit controls. A user from Tenant A should never see Tenant B’s project names. Many permission bugs show up first as visible UI mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where visual QA fits in CI/CD
&lt;/h2&gt;

&lt;p&gt;A practical pipeline looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AI coding agent opens a pull request&lt;/li&gt;
&lt;li&gt;Static checks and unit tests run&lt;/li&gt;
&lt;li&gt;Browser smoke tests run on critical flows&lt;/li&gt;
&lt;li&gt;Visual QA agent captures screenshots and evidence&lt;/li&gt;
&lt;li&gt;Agent writes a short report with pass/warn/block&lt;/li&gt;
&lt;li&gt;Human reviewer checks warnings and baseline updates&lt;/li&gt;
&lt;li&gt;Merge is allowed only when critical gates pass&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For speed, do not run the full suite on every commit. Use tiers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PR quick check:&lt;/strong&gt; changed routes, one browser, core viewport&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-merge check:&lt;/strong&gt; critical journeys, desktop and mobile&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nightly check:&lt;/strong&gt; full route map, multiple roles, accessibility, slower visual comparisons&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post-deploy check:&lt;/strong&gt; production smoke tests using synthetic accounts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps feedback fast while still catching deeper issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to include in the final QA report
&lt;/h2&gt;

&lt;p&gt;A visual QA report should be short enough for a busy reviewer.&lt;/p&gt;

&lt;p&gt;Use this format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Visual QA Report&lt;/span&gt;

Status: BLOCK
Risk score: 9/10
PR: #184
Mission: billing settings visual QA

&lt;span class="gu"&gt;### User impact&lt;/span&gt;
Mobile users cannot see the upgrade button on the billing page without horizontal scrolling.

&lt;span class="gu"&gt;### Evidence&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Route: /settings/billing
&lt;span class="p"&gt;-&lt;/span&gt; Viewport: mobile 390x844
&lt;span class="p"&gt;-&lt;/span&gt; Screenshot: qa-runs/billing-mobile.png
&lt;span class="p"&gt;-&lt;/span&gt; Diff: qa-runs/billing-mobile-diff.png

&lt;span class="gu"&gt;### Likely cause&lt;/span&gt;
PlanCard width changed from responsive grid to fixed 720px container.

&lt;span class="gu"&gt;### Suggested fix&lt;/span&gt;
Use max-width: 100% and restore the mobile grid breakpoint.

&lt;span class="gu"&gt;### Reviewer action&lt;/span&gt;
Fix before merge. Do not update the baseline for this run.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what is missing: long generic advice. The report is evidence, impact, cause, and action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes when building visual QA agents
&lt;/h2&gt;

&lt;p&gt;Avoid these traps early:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Letting the agent browse freely instead of giving approved routes and missions&lt;/li&gt;
&lt;li&gt;Trusting screenshots without assertions for critical buttons, forms, and permissions&lt;/li&gt;
&lt;li&gt;Allowing automatic baseline updates without human review&lt;/li&gt;
&lt;li&gt;Testing only desktop while mobile layouts silently break&lt;/li&gt;
&lt;li&gt;Ignoring roles, especially admin/member permission differences&lt;/li&gt;
&lt;li&gt;Blocking every tiny diff instead of scoring risk by user impact&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A lightweight implementation plan
&lt;/h2&gt;

&lt;p&gt;If you are starting from zero, do this in one week:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 1:&lt;/strong&gt; Pick five critical journeys: signup, login, dashboard, billing, settings.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Day 2:&lt;/strong&gt; Seed test tenants and freeze dynamic data.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Day 3:&lt;/strong&gt; Add Playwright smoke tests with screenshots.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Day 4:&lt;/strong&gt; Add visual diffing and masks for noisy regions.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Day 5:&lt;/strong&gt; Add an agent-generated report from structured evidence.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Day 6:&lt;/strong&gt; Add CI pass/warn/block policy.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Day 7:&lt;/strong&gt; Require human approval for baseline updates.&lt;/p&gt;

&lt;p&gt;This is enough to catch real issues without building a giant QA platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What are visual QA agents?
&lt;/h3&gt;

&lt;p&gt;Visual QA agents are automated testing workflows that use browser automation, screenshots, assertions, and AI-assisted review to detect visible product regressions. They are especially useful when AI coding agents change UI code quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are visual QA agents different from visual regression testing?
&lt;/h3&gt;

&lt;p&gt;Yes. Visual regression testing usually compares screenshots. A visual QA agent adds context: pull request intent, changed files, user journeys, risk scoring, and a human-readable report with evidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should visual QA agents block deployments?
&lt;/h3&gt;

&lt;p&gt;They should block only high-risk failures, such as broken signup, login, billing, permissions, or critical mobile layouts. Lower-risk visual changes should warn reviewers with evidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can an AI agent update screenshot baselines automatically?
&lt;/h3&gt;

&lt;p&gt;It should not update baselines without human review. Automatic baseline updates can hide regressions and make broken UI look approved.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the best first flow to test?
&lt;/h3&gt;

&lt;p&gt;Start with the flow closest to activation or revenue. For many products, that means signup, onboarding, billing, or the main dashboard action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final takeaway
&lt;/h2&gt;

&lt;p&gt;AI coding agents make it easier to produce code, not safer product experiences. Visual QA agents add the missing loop: drive the app, capture evidence, compare results, score risk, and surface regressions before users do.&lt;/p&gt;

&lt;p&gt;Start with five painful flows. Add screenshots, assertions, and reviewed baselines.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>frontend</category>
      <category>testing</category>
    </item>
    <item>
      <title>AI Agent Distress Signal: Let Stuck Workflows Ask for Help</title>
      <dc:creator>Jack M</dc:creator>
      <pubDate>Sun, 09 Aug 2026 08:11:57 +0000</pubDate>
      <link>https://dev.to/jackm-singularity/ai-agent-distress-signal-let-stuck-workflows-ask-for-help-46hi</link>
      <guid>https://dev.to/jackm-singularity/ai-agent-distress-signal-let-stuck-workflows-ask-for-help-46hi</guid>
      <description>&lt;p&gt;A production AI agent does not always fail loudly. Sometimes it loops, retries the same tool call, waits on missing context, spends tokens on a doomed plan, and still returns a polished update that looks fine from the outside.&lt;/p&gt;

&lt;p&gt;That is the risky part. If your agent can call tools, modify records, open tickets, query private data, or run long tasks for customers, it needs more than logs and dashboards. It needs a safe way to raise its hand.&lt;/p&gt;

&lt;p&gt;That pattern is an &lt;strong&gt;AI agent distress signal&lt;/strong&gt;: a controlled, auditable mechanism that lets an agent say, "I am stuck, blocked, uncertain, over budget, or about to do something risky. Please route this to the right human or fallback system."&lt;/p&gt;

&lt;p&gt;This guide shows how to build one without turning every workflow into a noisy support queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a distress signal belongs in your agent architecture
&lt;/h2&gt;

&lt;p&gt;Most AI SaaS teams already think about preventive controls, detection controls, and recovery controls. A distress signal sits between detection and recovery. It gives the agent a structured escape hatch before the workflow burns through cost, trust, or time.&lt;/p&gt;

&lt;p&gt;Think of it as the agent version of a circuit breaker, pager alert, human handoff, dead-letter queue, and &lt;code&gt;raise Exception&lt;/code&gt; with useful context.&lt;/p&gt;

&lt;p&gt;The goal is not to make the model emotional or magical. The goal is to give production workflows a reliable path for "I cannot safely finish this task."&lt;/p&gt;

&lt;h2&gt;
  
  
  The real problem: agents are trained to keep going
&lt;/h2&gt;

&lt;p&gt;Many failures are not single bad answers. They are process failures.&lt;/p&gt;

&lt;p&gt;A workflow may start with a simple goal:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Update this customer's renewal forecast using the latest usage data."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then the agent discovers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the usage API returns partial data&lt;/li&gt;
&lt;li&gt;the CRM record has conflicting account IDs&lt;/li&gt;
&lt;li&gt;the retrieved policy document is stale&lt;/li&gt;
&lt;li&gt;a tool call times out twice&lt;/li&gt;
&lt;li&gt;the customer has a restricted data flag&lt;/li&gt;
&lt;li&gt;the task requires a pricing exception&lt;/li&gt;
&lt;li&gt;the run budget is almost gone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A naive agent keeps trying. It searches again, retries tools, summarizes uncertainty in softer language, or chooses the least bad action.&lt;/p&gt;

&lt;p&gt;A production agent should do something better:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I am blocked because the billing API and CRM disagree on tenant ID. I have not updated the forecast. Route this to RevOps with the run trace and suggested next checks."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a distress signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should an AI agent ask for help?
&lt;/h2&gt;

&lt;p&gt;Do not let the model decide from vibes alone. Define explicit trigger categories.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Missing authority
&lt;/h3&gt;

&lt;p&gt;Escalate when the agent lacks permission to complete the task.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It needs write access but only has read access.&lt;/li&gt;
&lt;li&gt;The user asks it to act outside the current tenant scope.&lt;/li&gt;
&lt;li&gt;The task requires approval from finance, legal, security, or an account owner.&lt;/li&gt;
&lt;li&gt;The tool policy blocks an action that looks necessary.
&lt;/li&gt;
&lt;/ul&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;"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;"missing_authority"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"severity"&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;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The workflow requires updating invoice_terms, but the current tool scope is read_only."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requested_action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"route_to_billing_admin"&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;h3&gt;
  
  
  2. Conflicting context
&lt;/h3&gt;

&lt;p&gt;Agents often receive RAG chunks, CRM rows, tickets, emails, docs, and tool results. If trusted sources disagree, do not let the model quietly average them.&lt;/p&gt;

&lt;p&gt;Escalate when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;two systems disagree on customer status&lt;/li&gt;
&lt;li&gt;policy docs conflict&lt;/li&gt;
&lt;li&gt;the answer depends on stale data&lt;/li&gt;
&lt;li&gt;retrieved context has low confidence&lt;/li&gt;
&lt;li&gt;one source says "do not act" while another implies action&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Repeated tool failure
&lt;/h3&gt;

&lt;p&gt;A single failed tool call can be normal. Repeated failures become token waste and poor UX.&lt;/p&gt;

&lt;p&gt;Escalate when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the same tool fails more than N times&lt;/li&gt;
&lt;li&gt;retries produce different error classes&lt;/li&gt;
&lt;li&gt;a timeout blocks a user-visible workflow&lt;/li&gt;
&lt;li&gt;the agent switches tools without progress&lt;/li&gt;
&lt;li&gt;fallback tools return lower-trust data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple retry cap catches many expensive loops.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Budget pressure
&lt;/h3&gt;

&lt;p&gt;AI workflows need budgets at the run, tenant, user, and tool level. A distress signal should fire before the workflow exceeds them.&lt;/p&gt;

&lt;p&gt;Useful budget triggers include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;token spend above 80% of run budget&lt;/li&gt;
&lt;li&gt;tool calls above the allowed count&lt;/li&gt;
&lt;li&gt;wall-clock time above the workflow limit&lt;/li&gt;
&lt;li&gt;queue age above the customer-facing SLA&lt;/li&gt;
&lt;li&gt;cost per successful task above baseline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where many solo SaaS developers get surprised. The expensive incident is not one bad model call. It is a stuck workflow that looks busy for 20 minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Low confidence on high-risk output
&lt;/h3&gt;

&lt;p&gt;Confidence alone is not enough. Tie escalation to risk.&lt;/p&gt;

&lt;p&gt;Low-confidence output may be acceptable for draft copy, internal brainstorming, exploratory summaries, or non-critical recommendations.&lt;/p&gt;

&lt;p&gt;It should trigger help for billing changes, permissions, compliance statements, medical/legal/financial content, customer-facing support replies, and destructive production actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. User frustration or unclear intent
&lt;/h3&gt;

&lt;p&gt;If a user corrects the agent twice, repeats the same question, or says "that's not what I asked," the agent should not keep improvising.&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;"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;"user_frustration"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"severity"&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;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User rejected two attempted answers about workspace export limits."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"handoff_message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"I may be missing context. I am routing this with the conversation summary so a human can help faster."&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;
  
  
  What a good distress signal contains
&lt;/h2&gt;

&lt;p&gt;A useful signal is not just "help." It is a compact incident packet.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;run_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Links to the full trace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tenant_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Routes and scopes the issue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;workflow_name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shows what process failed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;trigger_type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Explains why the signal fired&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;severity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controls urgency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;last_safe_state&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shows what was completed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;blocked_step&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shows where work stopped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;evidence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Includes tool errors, context conflicts, or budget data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;recommended_route&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Picks the right queue or team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;safe_user_message&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gives the user a clear, non-leaky update&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here is a practical TypeScript shape:&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;type&lt;/span&gt; &lt;span class="nx"&gt;DistressType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;missing_authority&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="s1"&gt;conflicting_context&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="s1"&gt;tool_failure&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="s1"&gt;budget_pressure&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="s1"&gt;low_confidence_high_risk&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="s1"&gt;user_frustration&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="s1"&gt;policy_block&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="s1"&gt;unknown_blocker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;DistressSignal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&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="nl"&gt;runId&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="nl"&gt;tenantId&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="nl"&gt;userId&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="nl"&gt;workflowName&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="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DistressType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;low&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="s1"&gt;medium&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="s1"&gt;high&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="s1"&gt;critical&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;blockedStep&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="nl"&gt;lastSafeState&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="nl"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tool_error&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="s1"&gt;trace&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="s1"&gt;context_conflict&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="s1"&gt;budget&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="s1"&gt;policy&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="s1"&gt;user_message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;summary&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="nl"&gt;ref&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="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="nl"&gt;recommendedRoute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;support&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="s1"&gt;engineering&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="s1"&gt;security&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="s1"&gt;billing&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="s1"&gt;human_reviewer&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="s1"&gt;fallback_automation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;safeUserMessage&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="nl"&gt;createdAt&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep it boring. Boring fields become searchable, measurable, and easy to route.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture: detect, pause, package, route, recover
&lt;/h2&gt;

&lt;p&gt;A distress signal should follow a predictable lifecycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Detect the trigger
&lt;/h3&gt;

&lt;p&gt;Use both deterministic rules and model judgment.&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toolCallCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxToolCalls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;raiseDistress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;budget_pressure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tool call limit exceeded&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;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sameToolFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crm.updateAccount&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;raiseDistress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tool_failure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CRM update failed three times&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 model can help classify uncertainty, summarize blockers, draft a safe user message, or recommend a route. But deterministic policy should decide whether risky work pauses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Pause side effects
&lt;/h3&gt;

&lt;p&gt;When the signal fires, stop unsafe actions immediately.&lt;/p&gt;

&lt;p&gt;That means no more write tools, payment actions, external messages, permission changes, or customer-visible final answers unless the message is a safe status update.&lt;/p&gt;

&lt;p&gt;Read-only tools may continue if they help package evidence, but put a small budget on them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Package the evidence
&lt;/h3&gt;

&lt;p&gt;A human reviewer should not need to open five dashboards just to understand the issue.&lt;/p&gt;

&lt;p&gt;Attach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the goal the agent was given&lt;/li&gt;
&lt;li&gt;the last completed step&lt;/li&gt;
&lt;li&gt;the blocked step&lt;/li&gt;
&lt;li&gt;tool calls immediately before failure&lt;/li&gt;
&lt;li&gt;relevant error messages&lt;/li&gt;
&lt;li&gt;context snippets with source IDs&lt;/li&gt;
&lt;li&gt;budget counters&lt;/li&gt;
&lt;li&gt;policy decisions&lt;/li&gt;
&lt;li&gt;suggested next action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid attaching raw secrets, full prompts with private data, or unrelated conversation history. A distress signal should be useful without becoming a data leak.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Route to the right place
&lt;/h3&gt;

&lt;p&gt;Routing matters. If every distress signal goes to one Slack channel, people will ignore it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tool timeout&lt;/td&gt;
&lt;td&gt;Engineering queue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Permission block&lt;/td&gt;
&lt;td&gt;Admin or customer success queue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Billing conflict&lt;/td&gt;
&lt;td&gt;Billing operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt injection suspicion&lt;/td&gt;
&lt;td&gt;Security queue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Low confidence support reply&lt;/td&gt;
&lt;td&gt;Human support review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost runaway&lt;/td&gt;
&lt;td&gt;Platform owner or fallback automation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For small teams, this can be one inbox with labels. For larger teams, it can be a queue, ticket, incident channel, or workflow engine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Recover or resume
&lt;/h3&gt;

&lt;p&gt;A signal is only useful if the workflow can continue safely.&lt;/p&gt;

&lt;p&gt;Common recovery paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;human approves the next action&lt;/li&gt;
&lt;li&gt;human edits the missing field&lt;/li&gt;
&lt;li&gt;agent retries with corrected context&lt;/li&gt;
&lt;li&gt;workflow falls back to a simpler model path&lt;/li&gt;
&lt;li&gt;task becomes a manual ticket&lt;/li&gt;
&lt;li&gt;user receives a clear handoff message&lt;/li&gt;
&lt;li&gt;run is marked failed with a reason code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Make resume explicit. Do not let agents silently continue after a reviewer reads the alert.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple implementation pattern
&lt;/h2&gt;

&lt;p&gt;Start with three records: &lt;code&gt;agent_runs&lt;/code&gt;, &lt;code&gt;distress_signals&lt;/code&gt;, and &lt;code&gt;distress_evidence&lt;/code&gt;. The run stores workflow state and budget. The signal stores trigger, severity, route, status, and user-safe message. Evidence stores short redacted summaries with references back to traces, tool errors, or policy decisions.&lt;/p&gt;

&lt;p&gt;Then add one function every tool wrapper can call:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;maybeRaiseDistress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgentRun&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgentStep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgentEvent&lt;/span&gt;&lt;span class="p"&gt;)&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;trigger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;evaluateDistressPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;pauseRiskyTools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;signal&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;createDistressSignal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;runId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;workflowName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workflowName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;blockedStep&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;lastSafeState&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;summarizeLastSafeState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;evidence&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;collectEvidence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;recommendedRoute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;safeUserMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userMessage&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;routeSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;signal&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;Place this check after major model calls, tool calls, policy decisions, and retry loops.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to avoid alert fatigue
&lt;/h2&gt;

&lt;p&gt;The biggest risk is noise. If agents ask for help too often, humans stop trusting the signal.&lt;/p&gt;

&lt;p&gt;Use these rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deduplicate by run and trigger.&lt;/strong&gt; If a workflow already raised &lt;code&gt;tool_failure&lt;/code&gt; for the same API, update the existing signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use severity honestly.&lt;/strong&gt; Low means background and safe. Critical means security, data leakage, destructive action, or large spend risk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add auto-resolution.&lt;/strong&gt; Close signals when a tool outage recovers, a user cancels the task, or fallback automation finishes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track precision.&lt;/strong&gt; Measure useful-signal rate, time to acknowledgement, time to recovery, token spend saved, and repeated trigger types.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If 70% of signals are ignored, tune the policy. If critical issues arrive with no signal, add triggers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this fits with observability, approval gates, and queues
&lt;/h2&gt;

&lt;p&gt;A distress signal does not replace your other controls. It complements them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Observability&lt;/strong&gt; explains what happened.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approval gates&lt;/strong&gt; stop risky actions before execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limits&lt;/strong&gt; prevent runaway spend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durable queues&lt;/strong&gt; keep long-running work alive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distress signals&lt;/strong&gt; route blocked or unsafe work to recovery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For AI SaaS builders, the loop is simple: run the agent with scoped tools, watch steps and budgets, pause when a trigger fires, package evidence, route to a reviewer or fallback, then resume, repair, or close the run.&lt;/p&gt;

&lt;p&gt;Every distress signal is also a product insight. If agents keep asking for the same missing field, the workflow design is broken. If tool failures dominate, your integration layer needs work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world use cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Customer support agent:&lt;/strong&gt; escalates refunds, legal wording, repeated user frustration, or conflicting policy docs before a confident wrong reply goes out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Billing operations agent:&lt;/strong&gt; pauses when CRM, billing, and product usage data disagree, avoiding silent account changes based on mismatched tenant records.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data analysis agent:&lt;/strong&gt; asks for review when row-level security blocks a query, metric definitions conflict, or confidence is low for an executive report.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coding agent:&lt;/strong&gt; raises distress when tests fail repeatedly, it cannot reproduce a bug, it needs credentials, or the diff touches high-risk files.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Mistake 1: using only prompt instructions
&lt;/h3&gt;

&lt;p&gt;Do not rely on "ask for help if stuck" in the system prompt. Add real policy checks around tools, budgets, and workflow state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 2: sending raw traces to humans
&lt;/h3&gt;

&lt;p&gt;Raw traces are noisy and often contain sensitive data. Send summaries with references, redactions, and scoped links.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 3: escalating after the damage
&lt;/h3&gt;

&lt;p&gt;A distress signal should fire before risky side effects, not after the agent updates records or sends a customer message.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 4: routing everything to engineering
&lt;/h3&gt;

&lt;p&gt;Many blocks are product, policy, support, or customer-success issues. Route by trigger, not by habit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 5: never reviewing patterns
&lt;/h3&gt;

&lt;p&gt;A single signal fixes one run. A cluster of signals tells you what to redesign.&lt;/p&gt;

&lt;h2&gt;
  
  
  A launch checklist
&lt;/h2&gt;

&lt;p&gt;Before shipping your first version, confirm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Every long-running workflow has a run ID.&lt;/li&gt;
&lt;li&gt;[ ] Tool wrappers report failures and retries.&lt;/li&gt;
&lt;li&gt;[ ] Risky tools can be paused by policy.&lt;/li&gt;
&lt;li&gt;[ ] Budgets exist for tokens, tool calls, and wall-clock time.&lt;/li&gt;
&lt;li&gt;[ ] Distress trigger types are explicit.&lt;/li&gt;
&lt;li&gt;[ ] Signals include last safe state and blocked step.&lt;/li&gt;
&lt;li&gt;[ ] Evidence is redacted and scoped.&lt;/li&gt;
&lt;li&gt;[ ] Routes are mapped to humans or fallback systems.&lt;/li&gt;
&lt;li&gt;[ ] Reviewers can approve, reject, resume, or close the run.&lt;/li&gt;
&lt;li&gt;[ ] Metrics track signal usefulness and recovery time.&lt;/li&gt;
&lt;li&gt;[ ] Repeated signals feed back into evals and workflow design.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;The best production agents are not the ones that pretend they can finish every task. They are the ones that know when to stop, explain the blocker, preserve the last safe state, and bring the right help into the loop.&lt;/p&gt;

&lt;p&gt;An AI agent distress signal gives your system that habit. It turns agent failure into something your team can see, route, measure, and improve.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is an AI agent distress signal?
&lt;/h3&gt;

&lt;p&gt;An AI agent distress signal is a structured escalation event that lets an agent pause and ask for help when it is blocked, uncertain, over budget, missing authority, or facing a risky action. It should include the run ID, trigger type, evidence, last safe state, route, and safe user message.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is a distress signal the same as human-in-the-loop approval?
&lt;/h3&gt;

&lt;p&gt;No. Human-in-the-loop approval usually happens before a known risky action. A distress signal is broader. It can fire when the agent is stuck, confused by conflicting context, hitting tool failures, nearing a budget limit, or seeing user frustration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should every AI workflow have a distress signal?
&lt;/h3&gt;

&lt;p&gt;Every production workflow with tool access, customer-visible output, private data, long-running steps, or meaningful cost should have one. Low-risk draft and brainstorming features may only need simple retry and feedback controls.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I stop agents from overusing escalation?
&lt;/h3&gt;

&lt;p&gt;Use deterministic triggers, deduplication, severity levels, auto-resolution, and reviewer feedback. Track useful-signal rate over time. If the signal is noisy, tune the policy or improve the workflow design.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can the model decide when to raise distress?
&lt;/h3&gt;

&lt;p&gt;The model can help classify uncertainty and summarize blockers, but deterministic policy should own hard stops for budgets, tool failures, permissions, and high-risk actions. Treat model judgment as one input, not the whole safety system.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>architecture</category>
    </item>
    <item>
      <title>AI Support Escalation Router: Stop Confident Wrong Replies Before They Send</title>
      <dc:creator>Jack M</dc:creator>
      <pubDate>Fri, 07 Aug 2026 03:41:57 +0000</pubDate>
      <link>https://dev.to/jackm-singularity/ai-support-escalation-router-stop-confident-wrong-replies-before-they-send-4ich</link>
      <guid>https://dev.to/jackm-singularity/ai-support-escalation-router-stop-confident-wrong-replies-before-they-send-4ich</guid>
      <description>&lt;p&gt;An AI support agent does not have to be malicious to damage trust. It only has to answer one refund question, outage complaint, security concern, or enterprise renewal ticket with polished confidence and weak evidence.&lt;/p&gt;

&lt;p&gt;That is why serious builders need an &lt;strong&gt;AI support escalation router&lt;/strong&gt; before they let agents send replies on their own. The router decides when the AI can answer, when it should draft only, when it should ask a clarifying question, and when a human must take over.&lt;/p&gt;

&lt;p&gt;The goal is not to remove humans from support. The goal is to stop wasting human time on routine cases while protecting customers from the few cases where automation should slow down.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Working definition: an AI support escalation router is a policy layer that evaluates every support conversation for intent, risk, evidence, confidence, account context, and customer emotion before deciding the next safe action.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why this matters now
&lt;/h2&gt;

&lt;p&gt;Recent AI platform signals point in the same direction: agents are moving from demos into production workflows. Customer support products are launching AI agents that classify, draft, respond, and hand off tickets. AI gateway and spend-console launches show that teams now care about cost, routing, observability, and business impact.&lt;/p&gt;

&lt;p&gt;Developer discussions keep circling around the same uncomfortable questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do we stop AI support agents from repeating the same mistake?&lt;/li&gt;
&lt;li&gt;How do we prevent hallucinations from reaching customers?&lt;/li&gt;
&lt;li&gt;When should a human approve a reply before it sends?&lt;/li&gt;
&lt;li&gt;How do we preserve context during handoff so the customer does not repeat everything?&lt;/li&gt;
&lt;li&gt;How do we measure whether automation actually resolves issues instead of routing them faster?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Search results for AI escalation are full of platform pages, general customer-service advice, and high-level routing concepts. The missing piece is a practical builder guide: schemas, thresholds, queues, evidence checks, and safe defaults for a small AI product team.&lt;/p&gt;

&lt;p&gt;That is the gap this article fills.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core mistake: treating escalation as a fallback
&lt;/h2&gt;

&lt;p&gt;Many teams wire support automation 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;user message -&amp;gt; AI answer -&amp;gt; if user complains -&amp;gt; human handoff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That feels simple, but it is backwards.&lt;/p&gt;

&lt;p&gt;Escalation should not only happen after the AI fails. It should happen before the AI performs an unsafe action, gives an unsupported answer, or makes a customer more frustrated.&lt;/p&gt;

&lt;p&gt;A better flow 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;user message
  -&amp;gt; classify intent
  -&amp;gt; score risk
  -&amp;gt; check evidence
  -&amp;gt; estimate answer confidence
  -&amp;gt; inspect account context
  -&amp;gt; choose safe action
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The safe action may be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;send an AI answer&lt;/li&gt;
&lt;li&gt;ask a clarifying question&lt;/li&gt;
&lt;li&gt;draft a reply for human review&lt;/li&gt;
&lt;li&gt;route to billing, security, success, or engineering&lt;/li&gt;
&lt;li&gt;pause automation because the customer is angry, high-value, or affected by an incident&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This turns escalation from a panic button into a control plane.&lt;/p&gt;

&lt;h2&gt;
  
  
  The routing states every support agent needs
&lt;/h2&gt;

&lt;p&gt;Do not start with a dozen complicated workflows. Start with five routing states.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;auto_reply&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AI may send the response&lt;/td&gt;
&lt;td&gt;Simple how-to question with strong docs evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;clarify&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AI should ask one narrow question&lt;/td&gt;
&lt;td&gt;Missing plan name, browser, workspace, or error code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;draft_for_review&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AI may draft, human sends&lt;/td&gt;
&lt;td&gt;Refund request, sensitive account issue, uncertain answer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;human_handoff&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Human takes over immediately&lt;/td&gt;
&lt;td&gt;Angry customer, security issue, outage, legal risk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;block&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No reply until policy owner reviews&lt;/td&gt;
&lt;td&gt;Abuse, data exposure, suspicious request, unsafe instruction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These states are simple enough for a solo developer to implement and clear enough for a support team to audit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Classify the support intent
&lt;/h2&gt;

&lt;p&gt;The router should know what kind of problem it is handling before it thinks about confidence.&lt;/p&gt;

&lt;p&gt;Common intents for AI product support include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;login or access issue&lt;/li&gt;
&lt;li&gt;billing or refund question&lt;/li&gt;
&lt;li&gt;plan limit or quota question&lt;/li&gt;
&lt;li&gt;integration setup&lt;/li&gt;
&lt;li&gt;bug report&lt;/li&gt;
&lt;li&gt;data import/export issue&lt;/li&gt;
&lt;li&gt;feature request&lt;/li&gt;
&lt;li&gt;security or privacy concern&lt;/li&gt;
&lt;li&gt;cancellation risk&lt;/li&gt;
&lt;li&gt;outage or performance complaint&lt;/li&gt;
&lt;li&gt;unclear complaint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Intent is not only for analytics. It changes the safe action.&lt;/p&gt;

&lt;p&gt;A model can often answer “How do I connect Slack?” from docs. It should not freely answer “Why did your system expose another customer’s data?” without human review.&lt;/p&gt;

&lt;p&gt;Use a small schema:&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;type&lt;/span&gt; &lt;span class="nx"&gt;SupportIntent&lt;/span&gt; &lt;span class="o"&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;how_to&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;billing&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;bug_report&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;security_privacy&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;outage&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;cancellation_risk&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;feature_request&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;account_access&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;unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;IntentResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SupportIntent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 0 to 1&lt;/span&gt;
  &lt;span class="nl"&gt;reason&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A low intent confidence should never go straight to a confident answer. It should route to &lt;code&gt;clarify&lt;/code&gt; or &lt;code&gt;draft_for_review&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Score risk before confidence
&lt;/h2&gt;

&lt;p&gt;Confidence answers the question: “Does the AI think it knows?”&lt;/p&gt;

&lt;p&gt;Risk answers the better question: “What happens if the AI is wrong?”&lt;/p&gt;

&lt;p&gt;Use risk tiers before you decide what the model may do.&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;type&lt;/span&gt; &lt;span class="nx"&gt;RiskTier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;low&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;medium&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;high&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;critical&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RiskSignal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RiskTier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;reasons&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="nl"&gt;requiresHuman&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&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;A practical starting policy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;low&lt;/code&gt;: docs question, setup help, simple troubleshooting&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;medium&lt;/code&gt;: account-specific answer, workaround, plan-limit question&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;high&lt;/code&gt;: billing dispute, cancellation risk, production bug, broken integration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;critical&lt;/code&gt;: security incident, privacy concern, legal request, outage, data loss&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then set firm rules:&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;function&lt;/span&gt; &lt;span class="nf"&gt;routeByRisk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RiskSignal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tier&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;critical&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;human_handoff&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tier&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;draft_for_review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// continue scoring&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one rule prevents the most dangerous pattern in support automation: a fluent model treating a sensitive issue like a normal FAQ.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Require evidence, not vibes
&lt;/h2&gt;

&lt;p&gt;For support, the safest AI answer is usually grounded in one of these sources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;public documentation&lt;/li&gt;
&lt;li&gt;internal runbooks&lt;/li&gt;
&lt;li&gt;current account state&lt;/li&gt;
&lt;li&gt;recent incident status&lt;/li&gt;
&lt;li&gt;known bug list&lt;/li&gt;
&lt;li&gt;product changelog&lt;/li&gt;
&lt;li&gt;support macros&lt;/li&gt;
&lt;li&gt;engineering notes approved for support use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The router should ask: “What evidence supports this answer?”&lt;/p&gt;

&lt;p&gt;A useful evidence object looks like this:&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;type&lt;/span&gt; &lt;span class="nx"&gt;EvidenceItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sourceType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;docs&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;runbook&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;account_state&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;incident&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;ticket_history&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;sourceId&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="nl"&gt;title&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="nl"&gt;freshness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fresh&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;stale&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;unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;allowedForCustomer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;relevance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;Then enforce a simple rule:&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;function&lt;/span&gt; &lt;span class="nf"&gt;hasCustomerSafeEvidence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EvidenceItem&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowedForCustomer&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;freshness&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stale&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;relevance&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.75&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;If there is no customer-safe evidence, the AI should not send a confident answer. It can ask a clarifying question, draft for review, or escalate.&lt;/p&gt;

&lt;p&gt;Internal notes may be true and still not safe to repeat to the customer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Separate answer confidence from routing confidence
&lt;/h2&gt;

&lt;p&gt;Do not use one confidence score for everything.&lt;/p&gt;

&lt;p&gt;You need at least three scores:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Intent confidence&lt;/strong&gt;: did we classify the request correctly?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence confidence&lt;/strong&gt;: do we have reliable sources?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reply confidence&lt;/strong&gt;: is the generated response accurate and complete?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A reply should only be auto-sent when all three are strong and the risk tier allows it.&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;type&lt;/span&gt; &lt;span class="nx"&gt;RoutingScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;intentConfidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;evidenceConfidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;replyConfidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;sentimentScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// -1 angry to +1 happy&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;chooseRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RiskTier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RoutingScore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;critical&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;human_handoff&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;draft_for_review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sentimentScore&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.55&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;human_handoff&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;intentConfidence&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clarify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;evidenceConfidence&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;draft_for_review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replyConfidence&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.82&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;draft_for_review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;draft_for_review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auto_reply&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;These thresholds are not universal. The point is to make them explicit, measurable, and easy to tune.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Use account context without leaking it
&lt;/h2&gt;

&lt;p&gt;Good support routing needs account context. Bad support routing dumps everything into the prompt.&lt;/p&gt;

&lt;p&gt;Instead, pass only the facts needed for routing:&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;"account_tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"growth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"is_enterprise"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"open_incidents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"billing_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recent_tickets_30d"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recent_sentiment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"frustrated"&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;"can_view_billing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"can_discuss_security"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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;The support agent does not need raw invoices, private comments, full Slack threads, or every CRM note to decide whether to escalate. It needs enough scoped context to choose a safe route.&lt;/p&gt;

&lt;p&gt;This cuts token cost and lowers leakage risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Preserve handoff context
&lt;/h2&gt;

&lt;p&gt;A bad handoff makes users repeat themselves. A good handoff gives the human a clean packet.&lt;/p&gt;

&lt;p&gt;Your handoff packet should include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;customer message summary&lt;/li&gt;
&lt;li&gt;detected intent&lt;/li&gt;
&lt;li&gt;risk tier and reasons&lt;/li&gt;
&lt;li&gt;evidence found&lt;/li&gt;
&lt;li&gt;evidence missing&lt;/li&gt;
&lt;li&gt;attempted AI draft, if any&lt;/li&gt;
&lt;li&gt;customer emotion signal&lt;/li&gt;
&lt;li&gt;account context allowed for the human queue&lt;/li&gt;
&lt;li&gt;suggested next action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&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;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"human_handoff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"queue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"billing_specialist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Customer says they were charged after cancellation and is frustrated."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"intent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"billing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"risk_tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"risk_reasons"&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;"refund request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"negative sentiment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"account-specific billing"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evidence_found"&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;"subscription_status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"latest_invoice"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evidence_missing"&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;"cancellation_timestamp"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"suggested_next_action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Verify cancellation timestamp before replying."&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 keeps automation useful even when it does not send.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Add queue-specific routing
&lt;/h2&gt;

&lt;p&gt;Human handoff is not one queue.&lt;/p&gt;

&lt;p&gt;Route by skill:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;billing and refunds -&amp;gt; billing support&lt;/li&gt;
&lt;li&gt;enterprise account risk -&amp;gt; customer success&lt;/li&gt;
&lt;li&gt;security or privacy -&amp;gt; security support&lt;/li&gt;
&lt;li&gt;reproducible bug -&amp;gt; technical support or engineering triage&lt;/li&gt;
&lt;li&gt;outage complaint -&amp;gt; incident response queue&lt;/li&gt;
&lt;li&gt;vague angry message -&amp;gt; senior support generalist&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple queue selector:&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;function&lt;/span&gt; &lt;span class="nf"&gt;selectQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SupportIntent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RiskTier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intent&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;security_privacy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;security_support&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intent&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;billing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;billing_support&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intent&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;outage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;incident_response&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intent&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bug_report&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;low&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;technical_support&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intent&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cancellation_risk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer_success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;general_support&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 router should make the first human response faster, not just move the ticket somewhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: Measure resolution, not deflection
&lt;/h2&gt;

&lt;p&gt;“Deflection rate” can reward bad behavior. An AI agent can deflect tickets by giving shallow answers that make users give up.&lt;/p&gt;

&lt;p&gt;Track better metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;auto-reply resolution rate&lt;/li&gt;
&lt;li&gt;reopened tickets after AI reply&lt;/li&gt;
&lt;li&gt;human takeover rate by intent&lt;/li&gt;
&lt;li&gt;average time to correct queue&lt;/li&gt;
&lt;li&gt;customer sentiment after AI answer&lt;/li&gt;
&lt;li&gt;percentage of replies with customer-safe evidence&lt;/li&gt;
&lt;li&gt;false auto-send rate&lt;/li&gt;
&lt;li&gt;escalation precision by queue&lt;/li&gt;
&lt;li&gt;cost per resolved conversation&lt;/li&gt;
&lt;li&gt;review rejection rate for AI drafts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One practical metric is &lt;strong&gt;safe automation rate&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;safe automation rate = AI-resolved conversations without reopen / eligible low-risk conversations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This avoids punishing the router for escalating risky tickets. The router is doing its job when it blocks automation on unsafe cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical routing policy you can start with
&lt;/h2&gt;

&lt;p&gt;Here is a compact policy for a small AI product team:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Security, privacy, legal, data loss, outage&lt;/td&gt;
&lt;td&gt;Human handoff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Billing dispute or refund request&lt;/td&gt;
&lt;td&gt;Draft for review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Angry customer with negative sentiment&lt;/td&gt;
&lt;td&gt;Human handoff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No fresh customer-safe evidence&lt;/td&gt;
&lt;td&gt;Draft for review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Low intent confidence&lt;/td&gt;
&lt;td&gt;Clarify&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Low-risk docs question with strong evidence&lt;/td&gt;
&lt;td&gt;Auto reply&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium-risk account-specific issue&lt;/td&gt;
&lt;td&gt;Draft for review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enterprise or high-value customer issue&lt;/td&gt;
&lt;td&gt;Human handoff or priority review&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is not fancy. That is why it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this fits in your architecture
&lt;/h2&gt;

&lt;p&gt;Place the escalation router between conversation intake and response execution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;channels
  -&amp;gt; conversation normalizer
  -&amp;gt; intent classifier
  -&amp;gt; evidence retriever
  -&amp;gt; risk scorer
  -&amp;gt; escalation router
  -&amp;gt; AI reply / clarification / review queue / human handoff
  -&amp;gt; audit log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The router should not live only in prompt text. Prompts can explain policy, but code should enforce the final route.&lt;/p&gt;

&lt;p&gt;Keep these logs for every decision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;conversation ID&lt;/li&gt;
&lt;li&gt;tenant ID&lt;/li&gt;
&lt;li&gt;route selected&lt;/li&gt;
&lt;li&gt;model used&lt;/li&gt;
&lt;li&gt;scores&lt;/li&gt;
&lt;li&gt;thresholds at decision time&lt;/li&gt;
&lt;li&gt;evidence IDs&lt;/li&gt;
&lt;li&gt;human override, if any&lt;/li&gt;
&lt;li&gt;final outcome&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives you a feedback loop when customers complain, reviewers reject drafts, or a queue receives bad handoffs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common implementation mistakes
&lt;/h2&gt;

&lt;p&gt;Watch for these early:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Letting the model choose its own authority.&lt;/strong&gt; A model can suggest a route. Your application should enforce it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using sentiment alone.&lt;/strong&gt; An angry customer may have a simple issue. A calm customer may report a security breach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-sending account-specific answers too early.&lt;/strong&gt; If the answer depends on billing history, permissions, or incident status, raise the threshold.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hiding the reason for escalation.&lt;/strong&gt; “Low confidence” is weaker than “billing dispute, missing cancellation timestamp, angry sentiment.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measuring only ticket volume.&lt;/strong&gt; Watch reopens, complaints, refunds, review rejections, and churn-risk conversations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A lightweight rollout plan
&lt;/h2&gt;

&lt;p&gt;Start in read-only mode. Classify intent and risk, show suggested routes beside tickets, and compare the router with human decisions. Then auto-route only obvious critical handoffs and low-risk docs questions. Enable AI drafts for medium-risk tickets next. Auto-send should come last, only for narrow replies with strong evidence and low reopen rates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final checklist
&lt;/h2&gt;

&lt;p&gt;Before your AI support agent sends a reply, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did we classify the intent with enough confidence?&lt;/li&gt;
&lt;li&gt;Is the risk tier low enough for automation?&lt;/li&gt;
&lt;li&gt;Do we have fresh, customer-safe evidence?&lt;/li&gt;
&lt;li&gt;Is the reply grounded in that evidence?&lt;/li&gt;
&lt;li&gt;Is the customer angry, high-value, or affected by an incident?&lt;/li&gt;
&lt;li&gt;Would a wrong answer create financial, security, legal, or trust damage?&lt;/li&gt;
&lt;li&gt;Did we log the route, scores, thresholds, and evidence?&lt;/li&gt;
&lt;li&gt;If a human takes over, will they know exactly why?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer is unclear, do not auto-send. Draft, clarify, or hand off.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is an AI support escalation router?
&lt;/h3&gt;

&lt;p&gt;An AI support escalation router is a policy layer that decides whether a support conversation should receive an AI reply, a clarification question, a human-reviewed draft, or an immediate human handoff. It uses intent, risk, evidence, confidence, sentiment, and account context.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is escalation routing different from a chatbot fallback?
&lt;/h3&gt;

&lt;p&gt;A fallback usually happens after the chatbot fails. Escalation routing happens before an unsafe reply is sent. It prevents high-risk tickets from being treated like normal FAQs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should AI support agents use confidence scores?
&lt;/h3&gt;

&lt;p&gt;Yes, but not one score for everything. Track intent confidence, evidence confidence, and reply confidence separately. A reply should only auto-send when all required scores clear the threshold and the risk tier allows it.&lt;/p&gt;

&lt;h3&gt;
  
  
  When should a human review an AI support reply?
&lt;/h3&gt;

&lt;p&gt;Human review is best for billing disputes, refunds, account-specific issues, security concerns, privacy questions, outage complaints, angry customers, high-value accounts, and any answer without strong customer-safe evidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  What metrics show whether support automation is working?
&lt;/h3&gt;

&lt;p&gt;Useful metrics include auto-reply resolution rate, reopened tickets, review rejection rate, evidence coverage, human takeover rate, escalation precision, time to correct queue, customer sentiment after reply, and cost per resolved conversation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can a small team build this without a full support platform?
&lt;/h3&gt;

&lt;p&gt;Yes. Start with a simple schema, five route states, hard rules for critical risks, evidence checks, and an audit log. You can add advanced queue routing and tuning after you collect real outcomes.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>saas</category>
      <category>support</category>
      <category>agents</category>
    </item>
    <item>
      <title>AI Agent Permission Inheritance: Let Agents Act Without API Keys</title>
      <dc:creator>Jack M</dc:creator>
      <pubDate>Thu, 06 Aug 2026 03:40:57 +0000</pubDate>
      <link>https://dev.to/jackm-singularity/ai-agent-permission-inheritance-let-agents-act-without-api-keys-2cng</link>
      <guid>https://dev.to/jackm-singularity/ai-agent-permission-inheritance-let-agents-act-without-api-keys-2cng</guid>
      <description>&lt;p&gt;When an AI agent needs to do real work, the dangerous shortcut is simple: give it a service API key and hope the prompt behaves.&lt;/p&gt;

&lt;p&gt;That shortcut does not scale. A helpful agent can now read tickets, create issues, update CRM records, run queries, trigger workflows, and call internal tools. If that runs through one broad key, you do not have user permission. You have a robot with a borrowed master badge.&lt;/p&gt;

&lt;p&gt;A safer pattern is &lt;strong&gt;AI agent permission inheritance&lt;/strong&gt;: the agent acts with the current user's scoped permissions, for one task, with explicit limits, revocation, and an audit trail.&lt;/p&gt;

&lt;p&gt;This guide shows how to design that pattern for production AI tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why permission inheritance matters now
&lt;/h2&gt;

&lt;p&gt;Recent AI platform discussions keep circling the same problem: agents are becoming useful because they can act, not just answer. But action requires access.&lt;/p&gt;

&lt;p&gt;The pressure is coming from several directions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal teams want agents that work across support, sales, engineering, docs, and data systems.&lt;/li&gt;
&lt;li&gt;Developers are connecting agents through MCP servers, workflow platforms, browser tools, and private APIs.&lt;/li&gt;
&lt;li&gt;Security teams are seeing broad keys, copied tokens, and prompt-visible credentials appear in experiments.&lt;/li&gt;
&lt;li&gt;AI gateways and agent platforms are adding observability, access control, fallback, and spend controls because model calls are becoming infrastructure.&lt;/li&gt;
&lt;li&gt;Safety incidents around misconfigured agent environments are reminding builders that "testing" and "production" boundaries must be real.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The practical lesson is not "never let agents act." That is too blunt. The lesson is: &lt;strong&gt;agents should inherit bounded authority from the user and task, not from a permanent shared secret.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The common anti-pattern: one key for every agent
&lt;/h2&gt;

&lt;p&gt;Small teams often start with this architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User request
  -&amp;gt; AI agent
  -&amp;gt; tool router
  -&amp;gt; service API key
  -&amp;gt; internal systems
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It feels fast. It is easy to demo. It avoids OAuth complexity. It also creates a pile of problems:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;What happens in practice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No user boundary&lt;/td&gt;
&lt;td&gt;The agent can access data the user could not normally access.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weak revocation&lt;/td&gt;
&lt;td&gt;Disabling one user does not stop the shared key.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Poor auditability&lt;/td&gt;
&lt;td&gt;Logs show "agent-service" instead of the real actor and purpose.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt injection blast radius&lt;/td&gt;
&lt;td&gt;A malicious input can steer the agent while the tool still has broad access.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hard tenant isolation&lt;/td&gt;
&lt;td&gt;Multi-tenant filters become optional application logic instead of enforced policy.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secret exposure&lt;/td&gt;
&lt;td&gt;Keys can leak through traces, errors, screenshots, or debug prompts.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is not only a security issue. It is a product quality issue. If users cannot understand what an agent was allowed to do, what it actually did, and how to undo or revoke it, they will not trust the feature when it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is AI agent permission inheritance?
&lt;/h2&gt;

&lt;p&gt;AI agent permission inheritance means the agent receives a temporary, task-scoped authorization derived from the user's identity, role, tenant, consent, and current workflow.&lt;/p&gt;

&lt;p&gt;The agent does not get the user's raw password. It does not get a broad service key. It receives a delegation token or permission envelope that says:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who initiated the task&lt;/li&gt;
&lt;li&gt;which tenant or workspace it belongs to&lt;/li&gt;
&lt;li&gt;which tools may be called&lt;/li&gt;
&lt;li&gt;which records or resources are in scope&lt;/li&gt;
&lt;li&gt;which actions are read-only, draft-only, or executable&lt;/li&gt;
&lt;li&gt;how much spend, time, or tool usage is allowed&lt;/li&gt;
&lt;li&gt;when the permission expires&lt;/li&gt;
&lt;li&gt;what evidence must be logged&lt;/li&gt;
&lt;li&gt;what requires human approval&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple model 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;User session
  -&amp;gt; permission service
  -&amp;gt; task-scoped delegation token
  -&amp;gt; agent runtime
  -&amp;gt; policy-checked tool calls
  -&amp;gt; audit log + revocation path
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key idea: the agent's authority is not decided by the prompt. The prompt can explain the job, but the backend enforces what is allowed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The permission envelope
&lt;/h2&gt;

&lt;p&gt;Start with a plain object. Do not hide the design inside prompts.&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;"delegation_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dlg_01J8..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"actor_user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tenant_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tenant_456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agent_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"support_refund_agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"task_789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"purpose"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"draft_refund_response"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"allowed_tools"&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;"tickets.read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"orders.read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"refunds.draft"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resource_scope"&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;"ticket_ids"&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;"ticket_234"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"customer_ids"&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;"cust_987"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max_order_age_days"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&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_mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"draft_only"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"budget"&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;"max_model_calls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max_tool_calls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max_runtime_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;180&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;"approval_required_for"&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;"refunds.execute"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"emails.send"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expires_at"&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-08-06T10:30: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;"policy_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agent-policy-v4"&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 object becomes the contract between product, security, engineering, and support. It is also much easier to test than a vague instruction like "only access what the user can access."&lt;/p&gt;

&lt;h2&gt;
  
  
  Map tools to user permissions, not model intent
&lt;/h2&gt;

&lt;p&gt;An agent may claim it needs a tool. That claim is not enough.&lt;/p&gt;

&lt;p&gt;Every tool call should pass through a policy check that combines:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;User permissions&lt;/strong&gt;: Can this user perform this action without AI?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task scope&lt;/strong&gt;: Is this resource part of the current task?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent mode&lt;/strong&gt;: Is the agent in read-only, draft, copilot, or autopilot mode?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk tier&lt;/strong&gt;: Could this action send money, delete data, email a customer, change permissions, or expose secrets?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence&lt;/strong&gt;: Did the agent use trusted sources for the arguments?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget&lt;/strong&gt;: Has the run exceeded tool, token, or time limits?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a simplified TypeScript-style policy check:&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;type&lt;/span&gt; &lt;span class="nx"&gt;ToolCall&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;tool&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="nl"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Delegation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;actorUserId&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="nl"&gt;tenantId&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="nl"&gt;allowedTools&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="nl"&gt;actionMode&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_only&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;draft_only&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;supervised&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;bounded_autopilot&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;resourceScope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ticketIds&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="nl"&gt;customerIds&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="p"&gt;};&lt;/span&gt;
  &lt;span class="nl"&gt;approvalRequiredFor&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="nl"&gt;expiresAt&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="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;authorizeToolCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ToolCall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delegation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Delegation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delegation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;delegation_expired&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;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;delegation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowedTools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool_not_delegated&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;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;userCanUseTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delegation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actorUserId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delegation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_lacks_permission&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;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;resourceInScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delegation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resourceScope&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resource_out_of_scope&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;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delegation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;approvalRequiredFor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;requireApproval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;human_approval_required&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;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delegation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actionMode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;draft_only&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;isStateChanging&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;draft_mode_blocks_state_change&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;allow&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;Notice what is missing: there is no "the LLM said this is safe" branch.&lt;/p&gt;

&lt;p&gt;The model can propose. The runtime decides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use short-lived delegation tokens
&lt;/h2&gt;

&lt;p&gt;A delegation token should be boring. That is a compliment.&lt;/p&gt;

&lt;p&gt;Good delegation tokens are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;short-lived&lt;/li&gt;
&lt;li&gt;scoped to one tenant&lt;/li&gt;
&lt;li&gt;scoped to one task or workflow run&lt;/li&gt;
&lt;li&gt;bound to a user and agent identity&lt;/li&gt;
&lt;li&gt;unusable outside the agent runtime&lt;/li&gt;
&lt;li&gt;revocable&lt;/li&gt;
&lt;li&gt;logged every time they authorize a tool call&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid long-lived "agent tokens" that become shadow accounts. If you need a background agent to continue later, persist the workflow state and issue a fresh delegation after re-checking policy.&lt;/p&gt;

&lt;p&gt;For long-running work, use leases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;run starts -&amp;gt; delegation valid for 10 minutes
run pauses -&amp;gt; lease released
run resumes -&amp;gt; policy re-check -&amp;gt; new delegation issued
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps when a user's role changes, a customer record is locked, a tenant disables an integration, or a support manager revokes approval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate read, draft, and execute modes
&lt;/h2&gt;

&lt;p&gt;Most useful agent workflows do not need full autonomy on day one.&lt;/p&gt;

&lt;p&gt;Use modes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Agent can do&lt;/th&gt;
&lt;th&gt;Good for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read-only&lt;/td&gt;
&lt;td&gt;Search, summarize, inspect&lt;/td&gt;
&lt;td&gt;Research, support triage, analytics explanations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Draft-only&lt;/td&gt;
&lt;td&gt;Prepare changes without applying them&lt;/td&gt;
&lt;td&gt;Email drafts, refund drafts, CRM update previews&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Supervised&lt;/td&gt;
&lt;td&gt;Execute after approval&lt;/td&gt;
&lt;td&gt;Billing changes, user messaging, account updates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bounded autopilot&lt;/td&gt;
&lt;td&gt;Execute low-risk actions inside hard limits&lt;/td&gt;
&lt;td&gt;Labeling, routing, enrichment, small internal updates&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A permission envelope should include the mode. Tool handlers should enforce it.&lt;/p&gt;

&lt;p&gt;Do not rely on UI labels alone. If the product says "draft mode," the backend must reject state-changing calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design for revocation before launch
&lt;/h2&gt;

&lt;p&gt;Revocation is where many agent systems get fuzzy.&lt;/p&gt;

&lt;p&gt;You need at least four revocation paths:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;User revocation&lt;/strong&gt;: the user cancels the agent run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Admin revocation&lt;/strong&gt;: an admin disables a user, role, tenant, or integration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policy revocation&lt;/strong&gt;: the risk engine blocks a run because limits or evidence rules changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incident revocation&lt;/strong&gt;: security disables a tool, provider, connector, or agent class.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The agent runtime should check revocation before every tool call, not only when the run starts.&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;beforeToolCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delegation&lt;/span&gt;&lt;span class="p"&gt;)&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;status&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;delegationStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delegation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delegation_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revoked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Delegation revoked: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;return&lt;/span&gt; &lt;span class="nf"&gt;authorizeToolCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delegation&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;This can feel strict, but it prevents the worst version of agent failure: a workflow that continues acting after the human thinks it stopped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Store delegation receipts
&lt;/h2&gt;

&lt;p&gt;Every meaningful agent action should leave a receipt.&lt;/p&gt;

&lt;p&gt;A good receipt answers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who initiated this?&lt;/li&gt;
&lt;li&gt;Which agent acted?&lt;/li&gt;
&lt;li&gt;Which permission envelope allowed it?&lt;/li&gt;
&lt;li&gt;Which tool ran?&lt;/li&gt;
&lt;li&gt;What resources were touched?&lt;/li&gt;
&lt;li&gt;Was the action read, draft, or execute?&lt;/li&gt;
&lt;li&gt;Was approval required?&lt;/li&gt;
&lt;li&gt;What was the result?&lt;/li&gt;
&lt;li&gt;Can it be replayed, reviewed, or rolled back?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example receipt:&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;"receipt_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rcpt_01J9..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"delegation_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dlg_01J8..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"actor_user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tenant_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tenant_456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agent_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"support_refund_agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"refunds.draft"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resource_ids"&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;"order_555"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"allowed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"approval_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"policy_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agent-policy-v4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&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-08-06T10:18:14Z"&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;Receipts are not only for audits. They help developers debug wrong outputs, support teams explain agent behavior, and product teams see which workflows are trusted enough to automate further.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content gap: what most guides skip
&lt;/h2&gt;

&lt;p&gt;Top-ranking content around AI agents, OAuth, MCP, and API security often covers one layer well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how to connect tools&lt;/li&gt;
&lt;li&gt;how to store secrets&lt;/li&gt;
&lt;li&gt;how to build OAuth flows&lt;/li&gt;
&lt;li&gt;how to add prompt guardrails&lt;/li&gt;
&lt;li&gt;how to log model calls&lt;/li&gt;
&lt;li&gt;how to use an AI gateway&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The missing practical value is the connection between those layers. Permission inheritance is that connection.&lt;/p&gt;

&lt;p&gt;The question is not only "Where do I store the key?" It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What exact authority should this agent inherit from this user for this task, and how will the system prove it enforced that authority?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the architecture gap small teams should close early.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation checklist
&lt;/h2&gt;

&lt;p&gt;Use this checklist before giving an agent access to real tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Classify every tool
&lt;/h3&gt;

&lt;p&gt;Tag tools by risk:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;read customer data&lt;/li&gt;
&lt;li&gt;read internal data&lt;/li&gt;
&lt;li&gt;write draft&lt;/li&gt;
&lt;li&gt;write production state&lt;/li&gt;
&lt;li&gt;send external message&lt;/li&gt;
&lt;li&gt;spend money&lt;/li&gt;
&lt;li&gt;change permissions&lt;/li&gt;
&lt;li&gt;access secrets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you cannot classify a tool, it should not be available to an autonomous agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Create a permission envelope per run
&lt;/h3&gt;

&lt;p&gt;Do not let the agent discover its own authority from the prompt. Generate a backend permission envelope after checking the user's session, tenant, role, plan, consent, and integration status.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Bind tool calls to trusted arguments
&lt;/h3&gt;

&lt;p&gt;If an agent wants to refund an order, the &lt;code&gt;order_id&lt;/code&gt; should come from a trusted lookup or selected UI record, not from free text alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Add approval gates for risky actions
&lt;/h3&gt;

&lt;p&gt;Approvals should show the diff, the source evidence, and the exact action. "Approve agent" is too vague. "Approve refund draft for order_555 for $42.00" is reviewable.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Log receipts, not just traces
&lt;/h3&gt;

&lt;p&gt;Model traces show what the agent thought. Receipts show what the system allowed. You need both.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Test cross-tenant denial
&lt;/h3&gt;

&lt;p&gt;Add regression tests where the agent tries to use a valid tool on the wrong tenant, wrong customer, wrong record, or expired delegation. These tests should fail closed.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Give users a stop button
&lt;/h3&gt;

&lt;p&gt;A visible stop button should revoke the delegation, cancel queued steps, and prevent future tool calls from the same run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world use cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Support agent
&lt;/h3&gt;

&lt;p&gt;A support agent can read the current ticket, inspect recent orders, draft a refund, and prepare a reply. It cannot execute the refund or email the customer until a human approves the exact action.&lt;/p&gt;

&lt;h3&gt;
  
  
  Analytics agent
&lt;/h3&gt;

&lt;p&gt;An analytics agent can query metrics the user can already access. The delegation includes row-level tenant filters, metric definitions, query budgets, and blocked columns. The agent cannot bypass analytics permissions by writing raw SQL against a broader warehouse role.&lt;/p&gt;

&lt;h3&gt;
  
  
  Engineering agent
&lt;/h3&gt;

&lt;p&gt;A coding agent can read issues, inspect repository files, create a branch, and draft a pull request. It cannot rotate secrets, change deployment settings, or merge without approval.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sales operations agent
&lt;/h3&gt;

&lt;p&gt;A sales agent can enrich a lead, draft CRM notes, and suggest next steps. It cannot export a full customer list or send outbound messages without consent and rate limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple reference architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[User Session]
      |
      v
[Permission Service] ---- checks roles, tenant, consent, integration status
      |
      v
[Delegation Token / Envelope]
      |
      v
[Agent Runtime]
      |
      v
[Tool Policy Gateway] ---- checks tool, resource, mode, budget, approval
      |
      v
[Internal Tools / MCP Servers / APIs]
      |
      v
[Delegation Receipts + Audit Logs]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This architecture works whether your tools are REST APIs, MCP servers, queues, browser actions, SQL queries, or internal SDK calls. The important part is that every path to action crosses the policy gateway.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to measure
&lt;/h2&gt;

&lt;p&gt;Track metrics that reveal whether permission inheritance is working:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;denied tool calls by reason&lt;/li&gt;
&lt;li&gt;approval rate by action type&lt;/li&gt;
&lt;li&gt;revoked delegations&lt;/li&gt;
&lt;li&gt;expired delegations reused&lt;/li&gt;
&lt;li&gt;cross-tenant denial tests passing&lt;/li&gt;
&lt;li&gt;tool calls per run&lt;/li&gt;
&lt;li&gt;cost per successful delegated task&lt;/li&gt;
&lt;li&gt;incidents involving overbroad scope&lt;/li&gt;
&lt;li&gt;user trust signals, such as approval edits and cancellation rate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If every action is approved, your agent may be too restricted. If no action is denied, your policy may not be doing real work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The builder takeaway
&lt;/h2&gt;

&lt;p&gt;AI agents need access to be useful. But access should not mean handing a model broad keys, invisible permissions, or permanent authority.&lt;/p&gt;

&lt;p&gt;Permission inheritance gives builders a better middle path: agents can act with the user's bounded authority, for a specific task, with receipts, revocation, and policy checks around every tool call.&lt;/p&gt;

&lt;p&gt;That is how you move from impressive demos to agent workflows users can actually trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is AI agent permission inheritance?
&lt;/h3&gt;

&lt;p&gt;AI agent permission inheritance is a pattern where an agent receives temporary, task-scoped authority derived from the current user's permissions, tenant, consent, and workflow mode. The agent does not receive broad API keys or raw credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is permission inheritance the same as OAuth?
&lt;/h3&gt;

&lt;p&gt;No. OAuth can be part of the implementation, but permission inheritance is the broader product and runtime pattern. It includes task scope, resource limits, tool policy, approval gates, revocation, budgets, and audit receipts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should AI agents ever use service accounts?
&lt;/h3&gt;

&lt;p&gt;Sometimes, but service accounts should still be constrained by tenant, tool, task, and policy. A broad service account that can do everything is risky. Prefer user-scoped delegation for user-initiated work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can prompts enforce user permissions?
&lt;/h3&gt;

&lt;p&gt;Prompts can describe rules, but they should not be the enforcement layer. Permissions must be checked by backend services before tool calls execute.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long should a delegation token last?
&lt;/h3&gt;

&lt;p&gt;Keep it short. Many interactive tasks only need minutes. Long-running workflows should use leases and re-check policy before issuing a fresh delegation.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the biggest mistake small teams make?
&lt;/h3&gt;

&lt;p&gt;The biggest mistake is giving the agent one powerful key because it makes the demo easier. That shortcut usually creates weak audit logs, poor revocation, broad blast radius, and cross-tenant risk.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>saas</category>
      <category>agents</category>
      <category>security</category>
    </item>
    <item>
      <title>LLM Latency Budget: Make AI Features Feel Fast Without Burning Money</title>
      <dc:creator>Jack M</dc:creator>
      <pubDate>Wed, 05 Aug 2026 03:38:03 +0000</pubDate>
      <link>https://dev.to/jackm-singularity/llm-latency-budget-make-ai-features-feel-fast-without-burning-money-3mc3</link>
      <guid>https://dev.to/jackm-singularity/llm-latency-budget-make-ai-features-feel-fast-without-burning-money-3mc3</guid>
      <description>&lt;p&gt;A slow AI feature does not feel smart. It feels broken.&lt;/p&gt;

&lt;p&gt;That is the uncomfortable truth many AI SaaS builders hit after the demo works. The prototype answers well, the agent can call tools, and the RAG pipeline looks impressive. Then real users arrive. Prompts get longer. Queues form. Streaming starts late. One tenant uploads huge documents. Another runs bulk jobs at noon. Suddenly the same workflow that felt magical in testing feels like a spinner with an invoice attached.&lt;/p&gt;

&lt;p&gt;The fix is not simply “use a faster model.” You need an &lt;strong&gt;LLM latency budget&lt;/strong&gt;: a small set of rules that says how fast each AI workflow must feel, how many tokens it can spend, when to stream, when to cache, when to route to another model, and when to stop before cost and latency drift together.&lt;/p&gt;

&lt;p&gt;This guide is for solo SaaS developers, micro SaaS builders, and AI SaaS teams shipping production features with LLM APIs, RAG, agents, or self-hosted models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why latency budgets matter now
&lt;/h2&gt;

&lt;p&gt;AI platform news points in the same direction: builders are moving from chat demos to production workflows. Agent tools, web context APIs, voice agents, coding assistants, and RAG platforms are all getting more capable. At the same time, inference cost and reliability are under pressure.&lt;/p&gt;

&lt;p&gt;Latency is now a product metric. Inference efficiency is becoming a business metric. Yet many articles stop at TTFT, TPOT, quantization, batching, or model serving. Fewer show how a SaaS builder turns those ideas into a product-level budget with code, dashboards, fallbacks, and customer-safe limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  The simple model: TTFT, TPOT, and total time
&lt;/h2&gt;

&lt;p&gt;You do not need a PhD in serving systems to start. Track three numbers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time to First Token
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Time to First Token (TTFT)&lt;/strong&gt; is the delay between the user action and the first streamed token. It includes network time, queue time, provider overhead, tool setup, retrieval, and the model’s prefill phase.&lt;/p&gt;

&lt;p&gt;High TTFT is why a chat box feels dead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time Per Output Token
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Time Per Output Token (TPOT)&lt;/strong&gt; is the average time between generated tokens after the first token appears.&lt;/p&gt;

&lt;p&gt;High TPOT is why streaming feels like a dripping tap.&lt;/p&gt;

&lt;h3&gt;
  
  
  End-to-end latency
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;End-to-end latency&lt;/strong&gt; is the full time from request to final answer.&lt;/p&gt;

&lt;p&gt;A rough formula is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;end_to_end_latency = TTFT + (output_tokens - 1) * TPOT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That formula is not perfect for every provider, but it is good enough to reason about the user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build budgets by workflow, not by model
&lt;/h2&gt;

&lt;p&gt;A common mistake is to set one global target like “AI responses must finish in 5 seconds.” That sounds clean but fails fast.&lt;/p&gt;

&lt;p&gt;Different workflows need different budgets.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workflow&lt;/th&gt;
&lt;th&gt;User expectation&lt;/th&gt;
&lt;th&gt;Suggested latency budget&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Inline autocomplete&lt;/td&gt;
&lt;td&gt;Feels instant&lt;/td&gt;
&lt;td&gt;TTFT under 300ms, very short output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chat answer&lt;/td&gt;
&lt;td&gt;Starts quickly&lt;/td&gt;
&lt;td&gt;TTFT under 1.5s, stream response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAG answer with citations&lt;/td&gt;
&lt;td&gt;Trust matters&lt;/td&gt;
&lt;td&gt;TTFT under 3s, final answer under 15s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent with tool calls&lt;/td&gt;
&lt;td&gt;Progress matters&lt;/td&gt;
&lt;td&gt;First status under 1s, step updates every few seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bulk document task&lt;/td&gt;
&lt;td&gt;Completion matters&lt;/td&gt;
&lt;td&gt;Async job, no chat-style waiting&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key is to budget for the &lt;strong&gt;experience&lt;/strong&gt;, not the raw model call.&lt;/p&gt;

&lt;p&gt;A user can forgive a 40-second background report if the UI says what is happening. The same user may abandon a 6-second inline writing assistant if nothing appears.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical LLM latency budget template
&lt;/h2&gt;

&lt;p&gt;Create a budget object for each AI workflow.&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;"workflow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"support_rag_answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_ttft_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_total_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_output_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stream"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cache_policy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"semantic_and_exact"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fallback_model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fast_general_model"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requires_citations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"async_after_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12000&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 turns “make it faster” into engineering constraints. Your app can now decide whether to trim context, stream, route to a faster model, switch to async, reject an oversized request, or use a cached answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instrument every request
&lt;/h2&gt;

&lt;p&gt;Start by logging latency and token data for every AI request. Do this before buying another tool or changing providers.&lt;/p&gt;

&lt;p&gt;Here is a small TypeScript-style example.&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;type&lt;/span&gt; &lt;span class="nx"&gt;LlmTrace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;requestId&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="nl"&gt;tenantId&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="nl"&gt;workflow&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="nl"&gt;model&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="nl"&gt;inputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;ttftMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;totalMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;costUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cacheHit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&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;timeout&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;error&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;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runWithTrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;tenantId&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="nl"&gt;workflow&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="nl"&gt;prompt&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="p"&gt;})&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;started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;firstTokenAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&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;stream&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;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fast-general&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &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;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;firstTokenAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;firstTokenAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&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="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;sendToClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&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;finished&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LlmTrace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fast-general&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;inputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;estimateTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;outputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;estimateTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;ttftMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;firstTokenAt&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;firstTokenAt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;started&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;totalMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;finished&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;started&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;costUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;estimateCost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;cacheHit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&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="nf"&gt;saveTrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;output&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;Keep the trace simple. If you capture request ID, tenant ID, workflow, model, tokens, TTFT, total time, cost, cache hit, and status, you can answer most early performance questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Control input tokens before touching infrastructure
&lt;/h2&gt;

&lt;p&gt;Long prompts hurt TTFT. Long context means more work before the first token appears.&lt;/p&gt;

&lt;p&gt;For AI SaaS products, input bloat usually comes from full chat history, too many RAG chunks, raw HTML, unused tool descriptions, repeated system instructions, or entire customer records when only a few fields matter. Before optimizing GPUs or switching vendors, cut useless context.&lt;/p&gt;

&lt;p&gt;Use a context packer.&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;type&lt;/span&gt; &lt;span class="nx"&gt;ContextItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&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="nl"&gt;text&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="nl"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tokenEstimate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;packContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ContextItem&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;maxTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&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;sorted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&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;selected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ContextItem&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;used&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &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;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;used&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokenEstimate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;maxTokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;used&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokenEstimate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;selected&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;This is not fancy. That is the point. A basic priority-based packer often beats “send everything and hope.”&lt;/p&gt;

&lt;p&gt;For RAG, use fewer, better chunks. For agents, expose fewer tools per step. For browser automation, clean the page before putting it into the prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cap output tokens by job type
&lt;/h2&gt;

&lt;p&gt;Output tokens drive total latency and cost. Many AI features do not need long answers.&lt;/p&gt;

&lt;p&gt;Set output caps by workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rewrite suggestion: 120 tokens&lt;/li&gt;
&lt;li&gt;Error explanation: 250 tokens&lt;/li&gt;
&lt;li&gt;Support answer: 700 tokens&lt;/li&gt;
&lt;li&gt;Technical plan: 1,200 tokens&lt;/li&gt;
&lt;li&gt;Background report: async job with a larger cap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also give the model a structure that discourages rambling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Answer in this format:
1. Direct answer: 2 sentences max
2. Steps: up to 5 bullets
3. Caveat: 1 short note if needed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This improves scannability and reduces token drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use streaming for perception, not as a bandage
&lt;/h2&gt;

&lt;p&gt;Streaming can make an AI feature feel faster, but it does not fix everything.&lt;/p&gt;

&lt;p&gt;Use streaming when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user is reading generated text&lt;/li&gt;
&lt;li&gt;The answer may take more than 2 seconds&lt;/li&gt;
&lt;li&gt;Partial output is useful&lt;/li&gt;
&lt;li&gt;You can show citations or tool results after the draft begins&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not rely on streaming when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The workflow must return valid JSON&lt;/li&gt;
&lt;li&gt;The user needs a single deterministic result&lt;/li&gt;
&lt;li&gt;The model must complete tool calls before saying anything&lt;/li&gt;
&lt;li&gt;You are hiding a slow retrieval or database step before the model starts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For agent workflows, stream &lt;strong&gt;status events&lt;/strong&gt;, not only text.&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;"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;"status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Searching relevant docs"&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;"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;"status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Checking account permissions"&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;"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;"status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Drafting answer with citations"&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 keeps users oriented while the system does real work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Route models by latency class
&lt;/h2&gt;

&lt;p&gt;Not every request deserves your strongest model.&lt;/p&gt;

&lt;p&gt;Create latency classes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Model strategy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Instant&lt;/td&gt;
&lt;td&gt;autocomplete, labels, short rewrites&lt;/td&gt;
&lt;td&gt;smallest reliable model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;support chat, extraction, routing&lt;/td&gt;
&lt;td&gt;fast general model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Careful&lt;/td&gt;
&lt;td&gt;legal-ish, financial-ish, complex reasoning&lt;/td&gt;
&lt;td&gt;stronger model with tighter scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background&lt;/td&gt;
&lt;td&gt;reports, audits, batch enrichment&lt;/td&gt;
&lt;td&gt;slower model or queued worker&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A simple router can start with rules.&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;function&lt;/span&gt; &lt;span class="nf"&gt;chooseModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workflow&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;risk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;low&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;medium&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;high&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;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workflow&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;autocomplete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;small-fast&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workflow&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bulk_report&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;batch-careful&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;careful-reasoning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fast-general&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;Later, you can route based on measured performance, tenant plan, queue depth, or failure rate. Start with rules that developers can understand and debug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache the boring parts
&lt;/h2&gt;

&lt;p&gt;Caching is one of the easiest ways to improve both latency and cost, but cache the right things.&lt;/p&gt;

&lt;p&gt;Good cache candidates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Embeddings for unchanged documents&lt;/li&gt;
&lt;li&gt;RAG retrieval results for common queries&lt;/li&gt;
&lt;li&gt;System prompt templates&lt;/li&gt;
&lt;li&gt;Tool schemas&lt;/li&gt;
&lt;li&gt;Classification outputs&lt;/li&gt;
&lt;li&gt;Deterministic transformations&lt;/li&gt;
&lt;li&gt;Answers to low-risk, repeated questions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bad cache candidates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Permission-sensitive answers without tenant scoping&lt;/li&gt;
&lt;li&gt;Personalized answers without user scoping&lt;/li&gt;
&lt;li&gt;Answers based on rapidly changing data&lt;/li&gt;
&lt;li&gt;Outputs that may contain stale prices, policies, or account state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Always include tenant and permission context in cache keys.&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;function&lt;/span&gt; &lt;span class="nf"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;tenantId&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="nl"&gt;userRole&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="nl"&gt;workflow&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="nl"&gt;normalizedQuery&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="nl"&gt;sourceVersion&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="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userRole&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sourceVersion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;normalizedQuery&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:&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;A cache hit that leaks data is worse than no cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add graceful degradation
&lt;/h2&gt;

&lt;p&gt;Your app needs a plan for bad days: provider slowness, queue spikes, long documents, or tenants running large jobs.&lt;/p&gt;

&lt;p&gt;Useful degradation patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Switch from careful model to fast model for low-risk requests&lt;/li&gt;
&lt;li&gt;Reduce retrieved chunks when TTFT is at risk&lt;/li&gt;
&lt;li&gt;Shorten output length during load spikes&lt;/li&gt;
&lt;li&gt;Move long tasks to async jobs&lt;/li&gt;
&lt;li&gt;Show partial results with “continue generating”&lt;/li&gt;
&lt;li&gt;Ask the user to narrow the request before spending tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queueDepth&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;workflow&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;support_rag_answer&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;span class="nx"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;max_input_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;max_output_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fallback_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fast-general&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;This is not about lowering quality everywhere. It is about protecting the experience under pressure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch p95, not averages
&lt;/h2&gt;

&lt;p&gt;Average latency lies. Your happy path can look fine while real users suffer.&lt;/p&gt;

&lt;p&gt;Track these metrics by workflow and tenant tier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;p50 TTFT&lt;/li&gt;
&lt;li&gt;p95 TTFT&lt;/li&gt;
&lt;li&gt;p50 total latency&lt;/li&gt;
&lt;li&gt;p95 total latency&lt;/li&gt;
&lt;li&gt;input tokens per request&lt;/li&gt;
&lt;li&gt;output tokens per request&lt;/li&gt;
&lt;li&gt;cache hit rate&lt;/li&gt;
&lt;li&gt;timeout rate&lt;/li&gt;
&lt;li&gt;cost per successful task&lt;/li&gt;
&lt;li&gt;retries per request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple alert rule is enough at first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Alert when support_rag_answer p95 TTFT &amp;gt; 3000ms for 10 minutes.
Alert when cost per successful task rises 30% above 7-day baseline.
Alert when timeout rate &amp;gt; 2% for any paid tenant tier.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tie latency to cost. If p95 latency and cost both rise, you may have context bloat, retry loops, poor routing, or a workflow that should become async.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat retries as a budget risk
&lt;/h2&gt;

&lt;p&gt;Retries feel harmless in code and expensive in production.&lt;/p&gt;

&lt;p&gt;A retry can double cost, increase latency, and create duplicate tool actions. For agents, retry loops are even riskier because the model may call tools again.&lt;/p&gt;

&lt;p&gt;Use retry rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retry network errors with jitter&lt;/li&gt;
&lt;li&gt;Do not retry validation failures blindly&lt;/li&gt;
&lt;li&gt;Never retry write actions without idempotency keys&lt;/li&gt;
&lt;li&gt;Stop after a small number of attempts&lt;/li&gt;
&lt;li&gt;Log retry reason and added cost
&lt;/li&gt;
&lt;/ul&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;retryPolicy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;maxAttempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;retryOn&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;rate_limit&lt;/span&gt;&lt;span class="dl"&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;network_timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;neverRetryOn&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;invalid_json&lt;/span&gt;&lt;span class="dl"&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;permission_denied&lt;/span&gt;&lt;span class="dl"&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;policy_blocked&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;If a workflow needs three retries to feel reliable, it probably needs a better design, not a bigger retry loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use async instead of chat
&lt;/h2&gt;

&lt;p&gt;Some AI work should not pretend to be instant.&lt;/p&gt;

&lt;p&gt;Use async jobs for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Large document analysis&lt;/li&gt;
&lt;li&gt;Multi-source research&lt;/li&gt;
&lt;li&gt;Long agent workflows&lt;/li&gt;
&lt;li&gt;Bulk enrichment&lt;/li&gt;
&lt;li&gt;Report generation&lt;/li&gt;
&lt;li&gt;Evaluation runs&lt;/li&gt;
&lt;li&gt;Tasks with external API rate limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good async UX includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Immediate job receipt&lt;/li&gt;
&lt;li&gt;Progress updates&lt;/li&gt;
&lt;li&gt;Cancel button&lt;/li&gt;
&lt;li&gt;Estimated completion window&lt;/li&gt;
&lt;li&gt;Final summary&lt;/li&gt;
&lt;li&gt;Error state that explains what happened&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This protects your chat interface from becoming a waiting room.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation checklist
&lt;/h2&gt;

&lt;p&gt;Use this before shipping a new AI feature:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Define max TTFT and total latency by workflow&lt;/li&gt;
&lt;li&gt;[ ] Set input and output token caps&lt;/li&gt;
&lt;li&gt;[ ] Log tenant, workflow, model, tokens, cost, TTFT, total time, status&lt;/li&gt;
&lt;li&gt;[ ] Track p95 latency, not only averages&lt;/li&gt;
&lt;li&gt;[ ] Stream text or status events when useful&lt;/li&gt;
&lt;li&gt;[ ] Route models by workflow and risk&lt;/li&gt;
&lt;li&gt;[ ] Cache safe repeated work with tenant-aware keys&lt;/li&gt;
&lt;li&gt;[ ] Trim context before changing infrastructure&lt;/li&gt;
&lt;li&gt;[ ] Move long tasks to async jobs&lt;/li&gt;
&lt;li&gt;[ ] Alert on latency, timeout rate, retry rate, and cost per successful task&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;An LLM latency budget is not bureaucracy. It is a guardrail for product quality.&lt;/p&gt;

&lt;p&gt;When budgets are missing, every prompt can grow, every agent can wander, every retry can double spend, and every slow request can look like a mystery. When budgets exist, your team can make clear tradeoffs: faster first token, shorter output, better context, safer cache, async workflow, or stronger model only where it matters.&lt;/p&gt;

&lt;p&gt;Fast AI is not just about speed. It is about respecting the user’s time while protecting your margins.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is an LLM latency budget?
&lt;/h3&gt;

&lt;p&gt;An LLM latency budget is a set of limits for an AI workflow: maximum time to first token, maximum total response time, input token cap, output token cap, model route, caching rule, and fallback behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a good TTFT for AI features?
&lt;/h3&gt;

&lt;p&gt;It depends on the workflow. Inline suggestions should feel almost instant. Chat answers should usually start streaming within one or two seconds. RAG or agent workflows can take longer if the UI shows useful progress.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I reduce LLM latency quickly?
&lt;/h3&gt;

&lt;p&gt;Start by trimming input tokens, limiting output length, streaming responses, caching repeated work, and routing simple tasks to faster models. These changes are often easier than changing infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should every AI workflow stream output?
&lt;/h3&gt;

&lt;p&gt;No. Streaming works well for readable text and progress updates. It is less useful for strict JSON, hidden tool-call workflows, or tasks where partial output could confuse the user.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does latency relate to AI cost?
&lt;/h3&gt;

&lt;p&gt;Long prompts, long outputs, retries, and tool loops usually increase both latency and cost. That is why production teams should track tokens, latency, cache hit rate, and cost per successful task together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is self-hosting faster than using an API?
&lt;/h3&gt;

&lt;p&gt;Not automatically. Self-hosting can reduce control-plane uncertainty, but serving models well requires batching, memory management, scaling, monitoring, and hardware tuning. Measure TTFT, TPOT, and total cost before assuming self-hosting is better.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>performance</category>
      <category>saas</category>
    </item>
    <item>
      <title>Inference Efficiency Ratio: Measure Model Spend Before It Eats Your Margin</title>
      <dc:creator>Jack M</dc:creator>
      <pubDate>Tue, 04 Aug 2026 06:54:06 +0000</pubDate>
      <link>https://dev.to/jackm-singularity/inference-efficiency-ratio-measure-model-spend-before-it-eats-your-margin-23k6</link>
      <guid>https://dev.to/jackm-singularity/inference-efficiency-ratio-measure-model-spend-before-it-eats-your-margin-23k6</guid>
      <description>&lt;p&gt;A product can look healthy while its AI feature quietly loses money on every successful user action. The demo feels fast, the answers look useful, and usage is growing. Then the bill lands, and nobody can explain which workflow, tenant, prompt, model route, or retry loop consumed the margin.&lt;/p&gt;

&lt;p&gt;That is the practical value of &lt;strong&gt;inference efficiency ratio&lt;/strong&gt;. It gives builders a simple question to answer before scaling an AI workflow: for every dollar spent on production inference, how much product value did the system create?&lt;/p&gt;

&lt;p&gt;This article shows how to instrument that answer without turning your codebase into a finance spreadsheet.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Working definition: &lt;strong&gt;Inference Efficiency Ratio = AI-attributed product revenue / production inference cost&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You do not need a huge finance team to use it. You need clean events, honest cost attribution, and a dashboard that makes bad unit economics visible early.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why builders are talking about inference efficiency now
&lt;/h2&gt;

&lt;p&gt;Recent AI news has a clear pattern: agents are doing more real work, open-weight models are pushing prices down, and teams are moving from demos into production operations. At the same time, builders are asking harder questions about cost, security, reliability, and whether AI workflows can survive real customer usage.&lt;/p&gt;

&lt;p&gt;The current signals are hard to miss:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hacker News discussions are focused on open-source AI infrastructure, cloud coding agents, production access, and model price-performance.&lt;/li&gt;
&lt;li&gt;Developer content is moving from "try this model" toward "operate this workflow safely and cheaply."&lt;/li&gt;
&lt;li&gt;AI cost writing is shifting from token price alone to product-level unit economics.&lt;/li&gt;
&lt;li&gt;Multi-agent systems, web context pipelines, and voice agents are increasing the number of hidden model calls per user action.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The gap: many articles explain token counting, caching, or model routing. Fewer show how to connect those details to product margin in a way a solo builder can implement.&lt;/p&gt;

&lt;p&gt;That is the angle here.&lt;/p&gt;

&lt;h2&gt;
  
  
  What inference efficiency ratio actually measures
&lt;/h2&gt;

&lt;p&gt;Inference efficiency ratio, or IER, measures how much AI-attributed product revenue you generate for each dollar of inference cost.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IER = AI-attributed product revenue / production inference cost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an AI workflow generates $5,000 in attributable revenue and costs $1,000 to run, its IER is 5:1.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IER = 5000 / 1000 = 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means the workflow returns five dollars of product revenue for every dollar spent on model execution.&lt;/p&gt;

&lt;p&gt;Do not treat this as a universal benchmark. A support deflection feature, a premium research agent, an internal coding assistant, and a real-time voice workflow all have different economics. The useful move is to track IER by product line, tenant tier, workflow, and model route.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why token cost alone is not enough
&lt;/h2&gt;

&lt;p&gt;Token cost is useful, but it is too narrow.&lt;/p&gt;

&lt;p&gt;A workflow can have cheap tokens and still poor economics if it needs too many retries, human reviews, vector searches, browser sessions, tool calls, or failed runs. Another workflow can use an expensive model and still make sense if it closes high-value work with fewer failures.&lt;/p&gt;

&lt;p&gt;Track token cost, but do not stop there.&lt;/p&gt;

&lt;p&gt;A better inference cost model includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;input tokens&lt;/li&gt;
&lt;li&gt;output tokens&lt;/li&gt;
&lt;li&gt;cached tokens&lt;/li&gt;
&lt;li&gt;embedding calls&lt;/li&gt;
&lt;li&gt;reranker calls&lt;/li&gt;
&lt;li&gt;image, audio, or video model calls&lt;/li&gt;
&lt;li&gt;tool-call overhead when billed separately&lt;/li&gt;
&lt;li&gt;model retry cost&lt;/li&gt;
&lt;li&gt;failed run cost&lt;/li&gt;
&lt;li&gt;hosted inference or GPU serving cost&lt;/li&gt;
&lt;li&gt;provider minimums and reserved capacity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For small teams, start with model API cost. Then add the next biggest cost driver when it becomes visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where IER fits in your AI metrics stack
&lt;/h2&gt;

&lt;p&gt;IER should not replace quality metrics. It should sit next to them.&lt;/p&gt;

&lt;p&gt;A high ratio is not good if the answers are wrong. A low ratio is not always bad if the workflow is early, strategic, or intentionally subsidized. The goal is to make the tradeoff visible.&lt;/p&gt;

&lt;p&gt;Use this basic stack:&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;What it answers&lt;/th&gt;
&lt;th&gt;Example threshold&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cost per successful task&lt;/td&gt;
&lt;td&gt;What does one completed workflow cost?&lt;/td&gt;
&lt;td&gt;Under $0.25 for simple support answers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Success rate&lt;/td&gt;
&lt;td&gt;How often does the workflow finish correctly?&lt;/td&gt;
&lt;td&gt;Above 90% for low-risk automation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;Does the user wait too long?&lt;/td&gt;
&lt;td&gt;Under 5 seconds for interactive work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IER&lt;/td&gt;
&lt;td&gt;Does model spend create enough product value?&lt;/td&gt;
&lt;td&gt;Improving month over month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gross margin impact&lt;/td&gt;
&lt;td&gt;Does the feature hurt the business model?&lt;/td&gt;
&lt;td&gt;Positive after rollout stage&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The dangerous case is a workflow that looks good on success rate but has weak IER because each success costs too much.&lt;/p&gt;

&lt;h2&gt;
  
  
  The event schema you need first
&lt;/h2&gt;

&lt;p&gt;You cannot calculate IER from a monthly invoice alone. You need events.&lt;/p&gt;

&lt;p&gt;At minimum, log one event for every model call and one event for every workflow outcome.&lt;/p&gt;

&lt;h3&gt;
  
  
  Model call event
&lt;/h3&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;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ai.model_call.completed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tenant_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tenant_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workflow_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invoice_agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"run_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run_789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"step_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"extract_line_items"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model_provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"provider_a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model_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;"fast-model"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;780&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cached_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cost_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0184&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"latency_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2140&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"retry_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&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-08-04T06:50:00Z"&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;h3&gt;
  
  
  Workflow outcome event
&lt;/h3&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;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ai.workflow.completed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tenant_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tenant_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workflow_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invoice_agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"run_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run_789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_value_unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invoice_processed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"value_units"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"revenue_attribution_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"human_review_required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&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-08-04T06:50:08Z"&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;The important field is &lt;code&gt;run_id&lt;/code&gt;. It lets you connect cost to outcome. Without that join, your dashboard becomes guesswork.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to attribute revenue without lying to yourself
&lt;/h2&gt;

&lt;p&gt;Revenue attribution is the hardest part. Keep it simple and conservative.&lt;/p&gt;

&lt;p&gt;Here are three practical methods.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Subscription allocation
&lt;/h3&gt;

&lt;p&gt;If customers pay a flat subscription and the AI feature is part of the product, allocate a portion of monthly recurring revenue to the AI workflow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI-attributed revenue = account MRR × AI feature allocation percentage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$100 MRR × 20% allocation = $20 AI-attributed revenue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use this when AI is important but not the only value driver.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Usage-based revenue
&lt;/h3&gt;

&lt;p&gt;If the feature has usage pricing, attribution is direct.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI-attributed revenue = billable AI actions × price per action
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1,000 AI document reviews × $0.10 = $100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is cleanest, but not every product charges this way.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Outcome proxy
&lt;/h3&gt;

&lt;p&gt;If revenue is not directly tied to the workflow, use a proxy such as retained seats, resolved tickets, processed documents, or qualified leads. Then mark the metric as estimated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Estimated value = successful outcomes × value per outcome
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not pretend proxy value is real revenue. Label it clearly.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple SQL query for IER
&lt;/h2&gt;

&lt;p&gt;Assume you have two tables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ai_model_calls&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ai_workflow_outcomes&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can calculate IER by workflow like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;cost_by_run&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;run_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;workflow_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&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;AS&lt;/span&gt; &lt;span class="n"&gt;inference_cost_usd&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ai_model_calls&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;date_trunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'month'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;run_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;workflow_id&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;value_by_run&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;run_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;workflow_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;revenue_attribution_usd&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;attributed_revenue_usd&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ai_workflow_outcomes&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'success'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;date_trunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'month'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;run_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;workflow_id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;workflow_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&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="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;successful_runs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&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="n"&gt;attributed_revenue_usd&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&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;revenue_usd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inference_cost_usd&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&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;inference_cost_usd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&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="n"&gt;attributed_revenue_usd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;NULLIF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inference_cost_usd&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="mi"&gt;2&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;inference_efficiency_ratio&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cost_by_run&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;value_by_run&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;workflow_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;workflow_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;inference_efficiency_ratio&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first workflows in this result are your investigation queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Segment IER before you optimize anything
&lt;/h2&gt;

&lt;p&gt;A blended IER hides the problem.&lt;/p&gt;

&lt;p&gt;Segment by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tenant tier&lt;/li&gt;
&lt;li&gt;workflow&lt;/li&gt;
&lt;li&gt;model route&lt;/li&gt;
&lt;li&gt;prompt version&lt;/li&gt;
&lt;li&gt;region&lt;/li&gt;
&lt;li&gt;plan type&lt;/li&gt;
&lt;li&gt;integration source&lt;/li&gt;
&lt;li&gt;retry reason&lt;/li&gt;
&lt;li&gt;human review requirement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You may find that your overall IER is fine, but one free-tier workflow is burning cost. Or one enterprise customer is profitable only because a smaller model handles most requests. Or a new prompt version improved quality while doubling output tokens.&lt;/p&gt;

&lt;p&gt;Segmentation turns vague cost anxiety into a concrete engineering backlog.&lt;/p&gt;

&lt;h2&gt;
  
  
  What good and bad patterns look like
&lt;/h2&gt;

&lt;p&gt;Here are common patterns you will see once IER is visible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 1: High revenue, high cost, stable ratio
&lt;/h3&gt;

&lt;p&gt;This is usually acceptable. Keep monitoring quality, latency, and margin.&lt;/p&gt;

&lt;p&gt;Action: optimize slowly. Do not break a valuable workflow just to save cents.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 2: High usage, low revenue, low ratio
&lt;/h3&gt;

&lt;p&gt;This is dangerous. It often appears in generous free plans, chatty copilots, or workflows that users treat like a playground.&lt;/p&gt;

&lt;p&gt;Action: add budgets, rate limits, cheaper routes, or product boundaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 3: Low usage, high cost, unknown value
&lt;/h3&gt;

&lt;p&gt;This is an early warning. The workflow may be too complex, badly placed, or poorly explained.&lt;/p&gt;

&lt;p&gt;Action: interview users, inspect traces, and decide whether to simplify or remove it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 4: Good ratio, poor quality
&lt;/h3&gt;

&lt;p&gt;This is not a win. Cheap wrong answers create support burden and trust loss.&lt;/p&gt;

&lt;p&gt;Action: improve evals, retrieval, approval gates, or fallback behavior before scaling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimization levers that improve IER
&lt;/h2&gt;

&lt;p&gt;Once you know where the ratio is weak, use targeted fixes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Route by task difficulty
&lt;/h3&gt;

&lt;p&gt;Do not send every request to the strongest model.&lt;/p&gt;

&lt;p&gt;A simple routing policy:&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;type&lt;/span&gt; &lt;span class="nx"&gt;TaskRisk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;low&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;medium&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;high&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;chooseModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskRisk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TaskRisk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;needsReasoning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskRisk&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accurate-model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;needsReasoning&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;balanced-model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fast-cheap-model&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;Start with rules before building a complex router. Rules are easier to debug.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache stable context
&lt;/h3&gt;

&lt;p&gt;Repeated system prompts, policy text, product docs, and tool instructions should not be paid for from scratch when your provider or stack supports caching.&lt;/p&gt;

&lt;p&gt;Track cache hit rate next to IER. If cache hit rate falls after a prompt change, your ratio may fall too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cap retries by value
&lt;/h3&gt;

&lt;p&gt;Retries are useful when the task is valuable. They are wasteful when the task is low-value or already unlikely to succeed.&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;function&lt;/span&gt; &lt;span class="nf"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;valueUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TaskRisk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;valueUsd&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="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;valueUsd&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&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 key is not "never retry." The key is "retry when the expected value supports it."&lt;/p&gt;

&lt;h3&gt;
  
  
  Stop sending entire histories
&lt;/h3&gt;

&lt;p&gt;Long conversation history can quietly destroy margin. Summarize, retrieve, and pass only the pieces needed for the next step.&lt;/p&gt;

&lt;p&gt;A useful rule: every context block should have a job.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;user goal&lt;/li&gt;
&lt;li&gt;relevant source&lt;/li&gt;
&lt;li&gt;current state&lt;/li&gt;
&lt;li&gt;policy constraint&lt;/li&gt;
&lt;li&gt;output schema&lt;/li&gt;
&lt;li&gt;tool result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a block has no job, cut it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measure cost per successful task
&lt;/h3&gt;

&lt;p&gt;IER is the business view. Cost per successful task is the engineering view.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cost per successful task = total inference cost / successful outcomes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use both. If cost per task rises and IER falls, act fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add guardrails to prevent margin leaks
&lt;/h2&gt;

&lt;p&gt;You want bad economics to fail safely before they become normal.&lt;/p&gt;

&lt;p&gt;Add these controls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;per-tenant monthly inference budgets&lt;/li&gt;
&lt;li&gt;per-run maximum cost&lt;/li&gt;
&lt;li&gt;per-step token caps&lt;/li&gt;
&lt;li&gt;retry caps&lt;/li&gt;
&lt;li&gt;model route allowlists&lt;/li&gt;
&lt;li&gt;free-plan throttles&lt;/li&gt;
&lt;li&gt;anomaly alerts for cost spikes&lt;/li&gt;
&lt;li&gt;automatic downgrade when value is low&lt;/li&gt;
&lt;li&gt;human approval for expensive actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A basic run budget check might look like this:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;RunBudget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;maxCostUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;spentUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;assertBudget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RunBudget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nextCallEstimateUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spentUsd&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;nextCallEstimateUsd&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxCostUsd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AI run budget exceeded&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not just finance hygiene. It is reliability engineering. A workflow that can spend without limits can fail without limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dashboard: the first version
&lt;/h2&gt;

&lt;p&gt;Keep your first dashboard boring.&lt;/p&gt;

&lt;p&gt;Include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IER by workflow&lt;/li&gt;
&lt;li&gt;IER by tenant tier&lt;/li&gt;
&lt;li&gt;inference cost by model&lt;/li&gt;
&lt;li&gt;cost per successful task&lt;/li&gt;
&lt;li&gt;failed-run cost&lt;/li&gt;
&lt;li&gt;retry cost&lt;/li&gt;
&lt;li&gt;cache hit rate&lt;/li&gt;
&lt;li&gt;top 10 most expensive runs&lt;/li&gt;
&lt;li&gt;gross margin estimate&lt;/li&gt;
&lt;li&gt;week-over-week movement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add a small note beside every ratio explaining the revenue attribution method. Future you will be grateful.&lt;/p&gt;

&lt;h2&gt;
  
  
  A rollout plan for small teams
&lt;/h2&gt;

&lt;p&gt;Do not try to instrument everything in one sprint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Week 1: Capture cost
&lt;/h3&gt;

&lt;p&gt;Log model provider, model name, tokens, cost, workflow, tenant, and run ID.&lt;/p&gt;

&lt;h3&gt;
  
  
  Week 2: Capture outcomes
&lt;/h3&gt;

&lt;p&gt;Log success, failure, human review, and value units per run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Week 3: Add conservative revenue attribution
&lt;/h3&gt;

&lt;p&gt;Start with subscription allocation or usage revenue. Label estimates clearly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Week 4: Segment and alert
&lt;/h3&gt;

&lt;p&gt;Create IER views by workflow and tenant tier. Alert on sudden cost spikes or ratio drops.&lt;/p&gt;

&lt;h3&gt;
  
  
  Week 5: Optimize one weak workflow
&lt;/h3&gt;

&lt;p&gt;Pick the worst meaningful workflow. Apply routing, caching, retry caps, or context trimming. Measure the result.&lt;/p&gt;

&lt;p&gt;Small loops beat giant dashboards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Mistake: optimizing the cheapest workflow first
&lt;/h3&gt;

&lt;p&gt;Cheap workflows feel easy to fix, but they may not matter. Start where cost, usage, and weak IER overlap.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake: mixing experiments with production
&lt;/h3&gt;

&lt;p&gt;Keep test traffic out of production IER. Otherwise one evaluation run can distort your metric.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake: ignoring failed runs
&lt;/h3&gt;

&lt;p&gt;Failed runs still cost money. Track failed-run cost separately so you can see when reliability hurts margin.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake: hiding attribution assumptions
&lt;/h3&gt;

&lt;p&gt;If revenue attribution is estimated, say so in the dashboard. Hidden assumptions create false confidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake: treating IER as a product-quality score
&lt;/h3&gt;

&lt;p&gt;IER measures economic efficiency. It does not prove the feature is useful, safe, or correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content map for this topic
&lt;/h2&gt;

&lt;p&gt;This article belongs in a broader production AI architecture cluster.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pillar: production AI application architecture&lt;/li&gt;
&lt;li&gt;Cluster: AI cost control and product unit economics&lt;/li&gt;
&lt;li&gt;Funnel stage: middle&lt;/li&gt;
&lt;li&gt;Search intent: practical implementation guide&lt;/li&gt;
&lt;li&gt;Internal-link targets: LLM gateway, AI metrics baseline, usage metering, agent rate limiter, cost ledger&lt;/li&gt;
&lt;li&gt;Follow-up topics: AI gross margin dashboard, model routing by customer tier, failed-run cost analysis, prompt cache hit-rate monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final checklist
&lt;/h2&gt;

&lt;p&gt;Before you scale an AI workflow, answer these questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can you join every model call to a workflow run?&lt;/li&gt;
&lt;li&gt;Can you separate successful, failed, and reviewed runs?&lt;/li&gt;
&lt;li&gt;Do you know cost per successful task?&lt;/li&gt;
&lt;li&gt;Do you know which tenants and workflows drive cost?&lt;/li&gt;
&lt;li&gt;Do you have a conservative revenue attribution method?&lt;/li&gt;
&lt;li&gt;Can you see IER by workflow, tier, and model route?&lt;/li&gt;
&lt;li&gt;Do expensive workflows have budgets and retry caps?&lt;/li&gt;
&lt;li&gt;Do you track quality next to cost?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer is no, you are not ready to scale the feature with confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is inference efficiency ratio?
&lt;/h3&gt;

&lt;p&gt;Inference efficiency ratio measures AI-attributed product revenue divided by production inference cost. It helps teams see whether model spend is creating enough product value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is inference efficiency ratio the same as gross margin?
&lt;/h3&gt;

&lt;p&gt;No. Gross margin includes broader costs and revenue. IER focuses on the relationship between AI-attributed revenue and inference cost. It is a sharper metric for AI workflow economics.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a good inference efficiency ratio?
&lt;/h3&gt;

&lt;p&gt;There is no universal number. A mature usage-priced workflow should usually improve over time and stay comfortably above its cost base. Early experiments may have weak ratios while you validate demand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should free users be included in IER?
&lt;/h3&gt;

&lt;p&gt;Yes, but segment them separately. Free users often reveal product demand, but they can also hide margin leaks if their usage is blended with paid accounts.&lt;/p&gt;

&lt;h3&gt;
  
  
  How often should builders review IER?
&lt;/h3&gt;

&lt;p&gt;Review it weekly during rollout and monthly after the workflow stabilizes. Also alert on sudden cost spikes, retry increases, cache misses, or ratio drops.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I calculate IER without exact revenue attribution?
&lt;/h3&gt;

&lt;p&gt;Yes, but label it as estimated. Use conservative proxies such as successful tasks, retained seats, or usage-based value until direct attribution is available.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does a high IER mean the AI feature is good?
&lt;/h3&gt;

&lt;p&gt;Not by itself. A high ratio means the economics look efficient. You still need quality checks, evals, latency targets, security controls, and user feedback.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>product</category>
      <category>saas</category>
    </item>
  </channel>
</rss>
