<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Llm Ops on RockB</title><link>https://baeseokjae.github.io/tags/llm-ops/</link><description>Recent content in Llm Ops on RockB</description><image><title>RockB</title><url>https://baeseokjae.github.io/images/og-default.png</url><link>https://baeseokjae.github.io/images/og-default.png</link></image><generator>Hugo</generator><language>en-us</language><lastBuildDate>Mon, 13 Jul 2026 12:00:00 +0000</lastBuildDate><atom:link href="https://baeseokjae.github.io/tags/llm-ops/index.xml" rel="self" type="application/rss+xml"/><item><title>Deterministic Agent Loop Failures 2026: Why Your AI Agent Keeps Repeating Itself</title><link>https://baeseokjae.github.io/posts/deterministic-agent-loop-failures-2026/</link><pubDate>Mon, 13 Jul 2026 12:00:00 +0000</pubDate><guid>https://baeseokjae.github.io/posts/deterministic-agent-loop-failures-2026/</guid><description>Why AI agents get stuck in infinite loops and four production-proven patterns to break them — OODA loops, state machines, inhibitory synapses, and synthetic SLAs.</description><content:encoded><![CDATA[<p>Your AI agent is stuck in a loop. It tried the same API call three times, got the same 503, and it&rsquo;s about to try a fourth. The log looks like a broken record. This is a deterministic agent loop failure — and it&rsquo;s the single most common reason production agent deployments fail in 2026.</p>
<p>I&rsquo;ve been running autonomous agents in production for the past year, and loop failures are the problem that keeps coming up. Not model quality, not prompt engineering — agents that get stuck repeating the same failing action until they burn through their token budget or hit a hard timeout. The frameworks that work in demos break in production because they treat the LLM as a reliable component. It isn&rsquo;t. Here&rsquo;s what I&rsquo;ve learned about why loops happen and how to actually fix them.</p>
<h2 id="what-are-deterministic-agent-loop-failures">What Are Deterministic Agent Loop Failures?</h2>
<p>A deterministic agent loop failure is when an AI agent repeats the same action sequence indefinitely because its decision loop has no mechanism to detect that the outcome won&rsquo;t change. The agent observes state X, decides action Y, action Y fails, the failure is logged as an error (not a state transition), and the loop resets to the same state X. Rinse and repeat.</p>
<p>The word &ldquo;deterministic&rdquo; matters here. These aren&rsquo;t random hallucinations or creative misinterpretations. The agent is following its instructions perfectly — it&rsquo;s just that the instructions don&rsquo;t account for the possibility that an action might never succeed. The loop is deterministic in the sense that given the same inputs, the agent will make the same failing choice every time.</p>
<p>I&rsquo;ve seen this pattern in every agent framework I&rsquo;ve worked with. LangGraph-based agents halt or loop when the happy path breaks because error handling is rigid. Claude Code spawned parallel sessions on a 16GB Mac Mini and got OOM kills with no shared scheduler — the Centurion project documented this exact gap. Even well-designed Zapier AI agents can loop if a connected app returns an unexpected response format and the agent keeps retrying the same parsing logic.</p>
<h2 id="why-traditional-dag-based-frameworks-hit-the-toy-app-ceiling">Why Traditional DAG-Based Frameworks Hit the Toy App Ceiling</h2>
<p>Most agent frameworks in 2025-2026 use a DAG (Directed Acyclic Graph) under the hood. You define nodes: &ldquo;search web&rdquo; → &ldquo;extract content&rdquo; → &ldquo;summarize&rdquo; → &ldquo;write report.&rdquo; The agent walks the graph, and if a node fails, it retries. This works great for demos and simple pipelines.</p>
<p>In production, it falls apart for three reasons.</p>
<p>First, DAGs assume the graph structure is correct. If the agent needs to pivot — say, the search returned no results and it should try a different search strategy — the DAG has no built-in mechanism for that. You&rsquo;d need to add a conditional edge for every possible failure mode, which is exactly the kind of brittle error handling that breaks in practice.</p>
<p>Second, DAGs don&rsquo;t have a concept of &ldquo;this action has failed N times, try something different.&rdquo; The retry logic is usually a simple count-and-abort. The agent either succeeds on retry or the whole pipeline crashes. There&rsquo;s no middle ground where the agent adapts its strategy.</p>
<p>Third, DAGs conflate control flow with business logic. The graph structure encodes both &ldquo;what to do&rdquo; and &ldquo;in what order,&rdquo; which makes it hard to add loop detection without rewriting the entire graph. I&rsquo;ve seen teams try to add a &ldquo;loop breaker&rdquo; node to their LangGraph pipeline and end up with a graph that&rsquo;s harder to debug than the original loop problem.</p>
<p>The Hive framework&rsquo;s approach — treating exceptions as observations rather than crashes — directly addresses this. When a FileNotFound error occurs, it becomes a new state signal instead of a pipeline abort. The agent observes &ldquo;file not found&rdquo; and can decide what to do next, rather than crashing or blindly retrying.</p>
<h2 id="pattern-1-exceptions-as-observations-the-ooda-loop-approach">Pattern 1: Exceptions as Observations (The OODA Loop Approach)</h2>
<p>The OODA loop — Observe, Orient, Decide, Act — is a decision-making framework originally developed for military aviation. It turns out to be a surprisingly good fit for agent runtime design.</p>
<p>Here&rsquo;s how it works in practice. Instead of a DAG where each node is a step, the agent runs a continuous loop:</p>
<ol>
<li><strong>Observe</strong>: Collect current state — tool outputs, environment signals, previous action results</li>
<li><strong>Orient</strong>: Interpret what the observations mean in context. Was the API call rejected because of auth, rate limiting, or a bad payload?</li>
<li><strong>Decide</strong>: Choose the next action based on the interpreted state</li>
<li><strong>Act</strong>: Execute the action and capture the result</li>
</ol>
<p>The key insight is in step 2. When an action fails, the failure isn&rsquo;t an exception — it&rsquo;s an observation. The agent orients around it: &ldquo;The API returned 503. I&rsquo;ve seen this before. Last time, waiting 30 seconds and retrying worked. Let me try that.&rdquo;</p>
<p>The Hive framework uses this pattern in production ERP environments and reports that it solved roughly 70% of brittleness issues. That tracks with my experience. The agents that survive in production are the ones that can interpret failure signals and adapt, not the ones that crash on the first unexpected response.</p>
<p>The trade-off is complexity. An OODA loop agent needs a richer runtime than a DAG agent. It needs state persistence across loop iterations, a way to track action history, and a decision model that can handle ambiguous observations. You&rsquo;re trading implementation simplicity for runtime robustness.</p>
<h2 id="pattern-2-state-machines-as-deterministic-guardrails">Pattern 2: State Machines as Deterministic Guardrails</h2>
<p>State machines take the opposite approach from OODA loops. Instead of giving the agent more freedom to interpret failures, they constrain the agent&rsquo;s behavior within defined states and transitions.</p>
<p>Statewright, a visual state machine framework for AI agents, got 126 points and 59 comments on Hacker News in early 2026. The community interest makes sense — state machines provide a hard boundary around stochastic LLM behavior. The agent can only be in one state at a time, and only certain transitions are allowed from each state.</p>
<p>In practice, I&rsquo;ve found state machines work well for agents with well-defined workflows. A customer support agent might have states like &ldquo;awaiting_input&rdquo;, &ldquo;searching_knowledge_base&rdquo;, &ldquo;drafting_response&rdquo;, &ldquo;awaiting_approval&rdquo;, and &ldquo;response_sent.&rdquo; The state machine guarantees the agent can&rsquo;t skip from &ldquo;awaiting_input&rdquo; to &ldquo;response_sent&rdquo; without going through the intermediate states.</p>
<p>The limitation is that state machines are only as good as their state definitions. If the agent encounters a situation that doesn&rsquo;t fit any defined state, you&rsquo;re back to the same problem as DAGs — the agent has no graceful way to handle the unexpected. I&rsquo;ve seen teams spend weeks enumerating every possible state their agent could enter, only to discover edge cases they never considered.</p>
<p>The sweet spot is combining state machines with OODA loops. Use the state machine for high-level workflow guardrails (what states are valid, what transitions are allowed) and the OODA loop for within-state decision making (how to handle failures inside a state). This is essentially what the VS Code 1.115 Agents preview does — it provides an execution infrastructure with worktrees and isolation, while the agent itself handles the decision logic.</p>
<h2 id="pattern-3-inhibitory-synapses-and-neuroplasticity">Pattern 3: Inhibitory Synapses and Neuroplasticity</h2>
<p>This is the most biologically inspired approach, and honestly, it&rsquo;s also the most creative. VNOL (a framework that showed up on HN in January 2026) uses something called &ldquo;Inhibitory Synapses&rdquo; — after an action fails, the framework injects negative-weight synapses into the agent&rsquo;s memory graph to prevent the same action from being chosen again.</p>
<p>Think of it as a self-modifying constraint system. Every time the agent tries action A and it fails, the &ldquo;synapse&rdquo; between the current state and action A gets a negative weight. After enough failures, the weight crosses a threshold and the agent can no longer choose action A from that state. The agent literally learns to stop doing what doesn&rsquo;t work.</p>
<p>The Hive framework has a similar mechanism called &ldquo;homeostasis/stress metrics.&rdquo; If an action fails three times, the agent&rsquo;s &ldquo;neuroplasticity&rdquo; drops, forcing a strategy shift. The agent doesn&rsquo;t just retry — it changes its approach because the system penalizes the failing action.</p>
<p>I haven&rsquo;t deployed VNOL in production, but the concept is sound. The challenge is tuning the thresholds. Set the inhibition too low and the agent gives up too easily. Set it too high and the agent wastes tokens on doomed retries. In practice, I&rsquo;ve found that a decay function works better than a hard threshold — each failure increases the inhibition weight, but the weight decays over time so the agent can retry after a cooldown period.</p>
<p>VNOL also introduces &ldquo;Pre-Disk Intent Locking&rdquo; — intercepting agent actions before they hit the system with a 12ms latency budget. This is the kind of runtime infrastructure that most agent frameworks are missing. The action is inspected, validated, and potentially blocked before it ever reaches the filesystem or network. Combined with inhibitory synapses, this gives you a two-layer defense against loop failures: the action is blocked before execution if it matches a known failing pattern, and the synapse weight increases if it does execute and fails.</p>
<h2 id="pattern-4-synthetic-slas-and-best-of-n-verification">Pattern 4: Synthetic SLAs and Best-of-N Verification</h2>
<p>Sometimes you can&rsquo;t prevent loops, but you can guarantee they don&rsquo;t matter. That&rsquo;s the idea behind synthetic SLAs.</p>
<p>The Hive framework&rsquo;s synthetic SLA approach wraps an 80% accurate model in a Best-of-3 verification loop. The math is straightforward: if each individual attempt has an 80% chance of being correct, running three independent attempts and taking the majority result pushes the error rate down to roughly (3 choose 2) * 0.2² * 0.8 + (3 choose 3) * 0.2³ ≈ 10.4%. That&rsquo;s a 50% reduction in error rate by trading latency and tokens for certainty.</p>
<p>I use a variation of this for critical agent actions — anything that modifies production data or sends external communications. The agent runs the action three times with different temperature settings (0.1, 0.5, 0.9), compares the results, and only proceeds if at least two agree. If they disagree, the action is flagged for human review.</p>
<p>The cost is real. Best-of-3 triples your token consumption for those actions. But for high-stakes operations, it&rsquo;s cheaper than cleaning up after a loop failure. I reserve synthetic SLAs for the 10-20% of actions that have the highest blast radius and let the rest run with standard single-attempt logic.</p>
<h2 id="the-missing-runtime-layer">The Missing Runtime Layer</h2>
<p>Every pattern I&rsquo;ve described requires runtime infrastructure that most agent frameworks don&rsquo;t provide out of the box. The Hacker News thread on agent runtimes (February 2026) nailed the list: resource management, concurrency control, failure handling, state persistence, and permission boundaries.</p>
<p>The LLM is an unreliable subprocess. Treat it like one. That means:</p>
<ul>
<li><strong>Circuit breakers</strong>: If the agent fails three times in a row, open the circuit. Stop calling the LLM for that action and escalate to a fallback handler.</li>
<li><strong>Resource budgets</strong>: Track token consumption per action, per session, and per time window. Kill loops at the budget boundary, not when the agent decides to stop.</li>
<li><strong>State persistence</strong>: Every loop iteration should write its state to durable storage. If the agent crashes mid-loop, the next iteration picks up from the last persisted state instead of starting over.</li>
<li><strong>Permission boundaries</strong>: The agent should not be able to perform an action that it can&rsquo;t undo. This is especially important for agents that modify databases or send emails.</li>
</ul>
<p>The Windsurf Cascade deep dive shows how a well-designed agent runtime handles this — it uses RAG-based context tracking and a Memories system to persist state across sessions. The Zep AI review covers temporal knowledge graphs for agent memory, which is the same problem from a different angle: how do you give an agent durable, queryable state that survives loop iterations?</p>
<h2 id="choosing-the-right-loop-breaking-strategy">Choosing the Right Loop-Breaking Strategy</h2>
<p>There&rsquo;s no one-size-fits-all answer. Here&rsquo;s how I decide:</p>
<ul>
<li><strong>Simple pipelines with well-defined failure modes</strong>: State machines. The constraints are worth the upfront design work.</li>
<li><strong>Complex, open-ended tasks where the agent needs to adapt</strong>: OODA loops with exceptions as observations. Accept the runtime complexity for the flexibility.</li>
<li><strong>Agents that interact with external systems (APIs, databases, filesystems)</strong>: Inhibitory synapses or stress metrics. The self-modifying constraint system prevents the agent from repeating destructive actions.</li>
<li><strong>High-stakes actions where correctness is critical</strong>: Synthetic SLAs with Best-of-N verification. The token cost is insurance against expensive mistakes.</li>
</ul>
<p>Most production agents need a combination. I run a state machine for workflow guardrails, OODA loops for within-state decisions, stress metrics to prevent loop repetition, and synthetic SLAs for the top 15% of actions by blast radius. It&rsquo;s not elegant, but it works.</p>
<h2 id="implementation-guide-adding-loop-detection-to-your-agent-pipeline">Implementation Guide: Adding Loop Detection to Your Agent Pipeline</h2>
<p>If you&rsquo;re running an agent today and want to add loop detection without rewriting the whole framework, start here:</p>
<ol>
<li><strong>Log every action with a hash of (state, action, parameters)</strong>. This gives you a fingerprint for detecting repeated actions.</li>
<li><strong>Set a loop threshold</strong>. If the same action fingerprint appears N times in M minutes, trigger the loop breaker. I start with N=3, M=5.</li>
<li><strong>Implement a strategy shift on loop detection</strong>. The simplest approach: change one parameter (temperature, search query, retry delay) and try again. If the loop persists, escalate to a human.</li>
<li><strong>Add a circuit breaker</strong>. After the loop breaker fires, stop calling the LLM for that action for a cooldown period. This prevents the agent from burning tokens on a fundamentally broken action.</li>
<li><strong>Persist loop state</strong>. Write the loop detection state to a file or database so it survives agent restarts. Otherwise, a crash resets the loop counter and the agent starts the same loop from scratch.</li>
</ol>
<p>The implementation is about 50 lines of Python for the basic version. The hard part is tuning the thresholds for your specific use case.</p>
<h2 id="faq">FAQ</h2>
<h3 id="what-causes-ai-agent-loop-failures">What causes AI agent loop failures?</h3>
<p>The root cause is that LLM-based agents follow deterministic decision logic that doesn&rsquo;t account for repeated failure. When an action fails, the agent observes the same state, makes the same decision, and repeats the same action. Without loop detection, this cycle continues until the agent hits a token limit, timeout, or human intervention.</p>
<h3 id="how-do-i-detect-if-my-agent-is-in-a-loop">How do I detect if my agent is in a loop?</h3>
<p>Log a hash of (state, action, parameters) for every action. If the same hash appears three or more times within a short window, the agent is likely in a loop. Most agent frameworks don&rsquo;t include this by default — you need to add it yourself.</p>
<h3 id="can-state-machines-prevent-all-loop-failures">Can state machines prevent all loop failures?</h3>
<p>No. State machines prevent invalid state transitions, but they can&rsquo;t prevent loops within a single state. An agent stuck in the &ldquo;searching_knowledge_base&rdquo; state will keep searching regardless of the state machine. You need additional loop detection inside each state.</p>
<h3 id="whats-the-cheapest-way-to-add-loop-protection">What&rsquo;s the cheapest way to add loop protection?</h3>
<p>Start with a simple retry counter and strategy shift. After three failures, change one parameter (temperature, search strategy, retry delay) and try again. This costs almost nothing to implement and catches most simple loops. Add circuit breakers and state persistence only after you&rsquo;ve confirmed loops are a recurring problem.</p>
<h3 id="do-synthetic-slas-work-with-any-llm">Do synthetic SLAs work with any LLM?</h3>
<p>Yes. The Best-of-N approach works with any model because it&rsquo;s a statistical technique — run N independent attempts, compare results, take the majority. The trade-off is linear token cost scaling with N. For most use cases, N=3 is the sweet spot between reliability gains and cost.</p>
]]></content:encoded></item><item><title>AI Agent API Cost Horror Story 2026: How Runaway Agents Burn Token Budgets</title><link>https://baeseokjae.github.io/posts/ai-agent-api-cost-horror-story-2026/</link><pubDate>Wed, 08 Jul 2026 12:00:00 +0000</pubDate><guid>https://baeseokjae.github.io/posts/ai-agent-api-cost-horror-story-2026/</guid><description>A practical postmortem-style guide to preventing runaway AI agent API costs with hard limits, loop guards, and FinOps controls.</description><content:encoded><![CDATA[<p>The AI agent API cost horror story in 2026 is not a single expensive prompt. It is usually a loop: an agent retries a tool, hands off to another agent, grows context, and keeps spending after dashboards have already warned you. The fix is hard runtime limits, not better vibes around prompt engineering.</p>
<h2 id="why-are-ai-agent-api-cost-horror-stories-surging-in-2026">Why Are AI Agent API Cost Horror Stories Surging In 2026?</h2>
<p>AI agent costs are surging because the unit of failure changed. A chatbot request fails once. An agent request can fail for hours.</p>
<p>When I build agent systems, the expensive part is rarely the first model call. It is the second-order behavior: validation retries, tool retries, planner loops, handoffs, context replay, trace storage, vector search, browser sessions, and background evaluation. Each piece looks reasonable in isolation. Together they create a control plane where the system is technically &ldquo;working&rdquo; while financially melting down.</p>
<p>That is why the 2026 horror stories feel different from the old &ldquo;we accidentally used GPT-4 for autocomplete&rdquo; incidents. <a href="https://techcrunch.com/2026/06/05/the-token-bill-comes-due-inside-the-industry-scramble-to-manage-ais-runaway-costs/">TechCrunch reported</a> teams exceeding full-year token budgets by April, one reported $500 million token bill, and a CTO-level example of one engineer spending $40,000 in a month. Even if you treat the largest number as an outlier, the pattern is familiar to anyone running agents in production: budgets were sized for requests, but the product shipped workflows.</p>
<p>The <a href="https://clyro.dev/blog/the-47k-loop-a-complete-forensic-analysis/">Clyro $47K loop postmortem</a> is the more useful engineering story. The reported failure was not &ldquo;the model got expensive.&rdquo; It was an 11-day multi-agent retry spiral that looked healthy at the tool level. The agent was making calls, receiving responses, and continuing. No single stack frame screamed &ldquo;infinite loop.&rdquo; The budget failed because there was no enforceable definition of progress.</p>
<p>This is where agent engineering starts looking like SRE. You need turn limits, time limits, concurrency limits, cost limits, anomaly detection, and a runbook. If your only defense is a monthly provider alert, you have monitoring, not containment.</p>
<p>For implementation patterns, this post pairs well with the <a href="/posts/openai-responses-api-tutorial-2026/">OpenAI Responses API tutorial</a> and the <a href="/posts/langgraph-tutorial-2026/">LangGraph tutorial</a>, because both APIs make state and tool execution explicit enough to wrap with budget controls.</p>
<h2 id="how-do-ai-agent-runs-go-financially-wrong">How Do AI Agent Runs Go Financially Wrong?</h2>
<p>In practice, I see three repeatable cost failures: retry storms, context inflation, and fan-out without accounting.</p>
<table>
  <thead>
      <tr>
          <th>Failure mode</th>
          <th>What it looks like</th>
          <th>Why the bill grows</th>
          <th>Control that actually helps</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Retry storm</td>
          <td>Agent keeps trying a failing tool or invalid output</td>
          <td>Every retry repeats model input, tool output, and reasoning</td>
          <td>Max turns, progress checks, retry budget</td>
      </tr>
      <tr>
          <td>Context inflation</td>
          <td>Each turn includes more history, files, or tool logs</td>
          <td>Input tokens grow with every step</td>
          <td>Context trimming, summarized handoffs, cache-aware prompts</td>
      </tr>
      <tr>
          <td>Fan-out</td>
          <td>One request starts many sub-agents, searches, or evals</td>
          <td>Parallel work hides total cost until later</td>
          <td>Per-root-request budget and concurrency caps</td>
      </tr>
  </tbody>
</table>
<h3 id="why-are-retry-storms-so-expensive">Why Are Retry Storms So Expensive?</h3>
<p>Retry storms are expensive because many frameworks make retries feel harmless. Pydantic validation retry? Sensible. Tool error retry? Sensible. Handoff to a specialist agent? Sensible. Retry after the specialist returns partial output? Also sensible.</p>
<p>The problem is multiplication. Suppose a support automation workflow uses a $3 per million input / $15 per million output model. A normal request uses 35,000 input tokens and 4,000 output tokens across a few turns, about $0.165 before tool costs:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>(35,000 / 1,000,000 * $3) + (4,000 / 1,000,000 * $15) = $0.165
</span></span></code></pre></div><p>That looks cheap. Now let the same agent loop 800 times because a CRM update tool returns a recoverable schema error. If context is stable, that is already $132. If context grows by 3,000 tokens per turn because the full error transcript is appended, the curve stops being linear. The last turns can be far more expensive than the first turns.</p>
<p>I&rsquo;ve found that the simplest early warning is not &ldquo;total tokens.&rdquo; It is &ldquo;tokens per successful business event.&rdquo; If your invoice-classification agent normally spends 9,000 tokens per accepted invoice and suddenly spends 90,000, page someone before the provider bill catches up.</p>
<h3 id="why-does-context-inflation-hide-until-late">Why Does Context Inflation Hide Until Late?</h3>
<p>Context inflation is quiet. Engineers notice output cost because generated text feels visible. Input cost hides inside convenience APIs: session history, retrieved files, tool observations, browser snapshots, logs, and prior agent messages.</p>
<p>OpenAI&rsquo;s Agents SDK, LangGraph, Pydantic AI, CrewAI, and Mastra all make it easier to preserve state. That is good for correctness. It is dangerous when every turn drags the full transcript forward.</p>
<p>The OpenAI Agents SDK documentation describes the runner loop plainly: the model calls tools or performs handoffs, the runner appends results, and the loop continues until final output or a <code>MaxTurnsExceeded</code> exception when <code>max_turns</code> is exceeded. That is the right mental model. If you do not cap the loop, state accumulation becomes a billing feature.</p>
<p>With <a href="/posts/pydantic-ai-tutorial-2026/">Pydantic AI</a>, typed outputs and validation retries are excellent for production reliability. The trade-off is that validation failures should be counted as spend events. A malformed JSON retry is not &ldquo;free correctness.&rdquo; It is another model call, another trace, and usually more context.</p>
<h3 id="why-is-fan-out-harder-than-it-looks">Why Is Fan-Out Harder Than It Looks?</h3>
<p>Multi-agent fan-out is where teams get surprised. A manager agent delegates to research, coding, QA, and summarization agents. Each sub-agent has its own turns. Each sub-agent may use tools. Each tool output returns to the sub-agent, then back to the manager. The root request still looks like &ldquo;one user clicked Generate.&rdquo;</p>
<p>This is why per-agent budgets are not enough. You need a budget attached to the root workflow ID:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>root_request_id = &#34;req_01J...&#34;
</span></span><span style="display:flex;"><span>budget_usd = 2.50
</span></span><span style="display:flex;"><span>children = [
</span></span><span style="display:flex;"><span>  &#34;planner&#34;,
</span></span><span style="display:flex;"><span>  &#34;researcher&#34;,
</span></span><span style="display:flex;"><span>  &#34;browser&#34;,
</span></span><span style="display:flex;"><span>  &#34;writer&#34;,
</span></span><span style="display:flex;"><span>  &#34;critic&#34;
</span></span><span style="display:flex;"><span>]
</span></span></code></pre></div><p>Every child spends against the same ledger. If the researcher burns $2.35, the critic does not get to start just because its own local budget is empty. This is basic accounting, but many first-generation agent deployments forget it because the code is organized by agent class instead of business transaction.</p>
<h2 id="what-real-incidents-should-have-triggered-earlier-warnings">What Real Incidents Should Have Triggered Earlier Warnings?</h2>
<p>The best horror-story analysis is not &ldquo;someone should have watched the dashboard.&rdquo; Dashboards lag. Humans sleep. Alerts get routed to Slack channels that nobody owns. A useful postmortem asks which machine-checkable signal should have stopped the run.</p>
<p>The <a href="https://arxiv.org/abs/2607.01641">Infinite Agentic Loops paper</a> is useful because it treats runaway behavior as a software failure mode, not a billing anecdote. The research scanned 6,549 repositories, produced 74 candidate findings, and confirmed 68 infinite agentic loop failures across 47 projects. That is a strong signal that loops are not edge cases created by careless teams. They are a natural failure mode when model calls, tools, and handoffs form feedback paths without hard bounds.</p>
<p>Here is the warning map I use when reviewing agent incidents:</p>
<table>
  <thead>
      <tr>
          <th>Signal</th>
          <th>Bad pattern</th>
          <th>Action</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Turn count</td>
          <td>More than 8-12 turns for a task that normally finishes in 3</td>
          <td>Stop and return a recoverable error</td>
      </tr>
      <tr>
          <td>Tool repetition</td>
          <td>Same tool called with equivalent arguments 3 times</td>
          <td>Stop or require human approval</td>
      </tr>
      <tr>
          <td>State progress</td>
          <td>No new durable artifact after N turns</td>
          <td>Stop and mark as non-progressing</td>
      </tr>
      <tr>
          <td>Token slope</td>
          <td>Tokens per turn rising faster than expected</td>
          <td>Summarize, trim, or terminate</td>
      </tr>
      <tr>
          <td>Cost slope</td>
          <td>Dollars per minute above baseline</td>
          <td>Kill workflow and revoke queue lease</td>
      </tr>
      <tr>
          <td>Fan-out count</td>
          <td>Child agents exceed configured branch factor</td>
          <td>Stop spawning new work</td>
      </tr>
  </tbody>
</table>
<p>The important word is &ldquo;equivalent.&rdquo; A tool loop is not always identical strings. The model may slightly rephrase arguments, add whitespace, or include a new timestamp. In production, I normalize tool inputs before comparing them: sort JSON keys, remove volatile fields, hash stable fields, and compare semantic keys like <code>customer_id</code>, <code>ticket_id</code>, <code>file_path</code>, and <code>operation</code>.</p>
<h2 id="how-do-provider-and-framework-controls-actually-behave-in-2026">How Do Provider And Framework Controls Actually Behave In 2026?</h2>
<p>Provider controls help, but they do not replace application-level hard stops.</p>
<p><a href="https://help.openai.com/en/articles/9186755-managing-projects-in-the-api-platform">OpenAI project budgets</a> are useful for visibility. Project owners can set monthly budgets, notification thresholds, model usage permissions, rate limits, and API key permissions. The crucial detail is that monthly budgets are soft spending thresholds. API requests continue after the threshold is exceeded. That is fine for account-level monitoring. It is not a kill switch.</p>
<p>The OpenAI Agents SDK is more directly useful at runtime. As of <code>openai-agents==0.18.0</code> in July 2026, the SDK requires Python 3.10+ and exposes <code>max_turns</code> on runner calls. When a run exceeds the turn limit, it raises <code>MaxTurnsExceeded</code>. It also exposes <code>RunConfig</code>, tool execution configuration, guardrails, tracing settings, and function-tool concurrency limits.</p>
<p>For LangGraph, current <code>langgraph==1.2.8</code> gives you graph-level control over long-running stateful agents. The specific control I always look for is <code>recursion_limit</code>, because graph execution can otherwise bounce between nodes in ways that look valid locally. If you are already building graph-based agents, the cost guard belongs at the graph boundary, not buried inside one node.</p>
<h3 id="what-should-a-minimal-openai-agents-guard-look-like">What Should A Minimal OpenAI Agents Guard Look Like?</h3>
<p>Here is the kind of wrapper I use before letting an agent touch production data. The numbers are intentionally boring. They should be tuned per workflow, but they should exist from day one.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># Python 3.12</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># openai-agents==0.18.0</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> dataclasses <span style="color:#f92672">import</span> dataclass
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> time <span style="color:#f92672">import</span> monotonic
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> agents <span style="color:#f92672">import</span> Agent, Runner
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> agents.exceptions <span style="color:#f92672">import</span> MaxTurnsExceeded
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@dataclass</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">CostBudget</span>:
</span></span><span style="display:flex;"><span>    max_turns: int <span style="color:#f92672">=</span> <span style="color:#ae81ff">8</span>
</span></span><span style="display:flex;"><span>    max_seconds: float <span style="color:#f92672">=</span> <span style="color:#ae81ff">45.0</span>
</span></span><span style="display:flex;"><span>    max_estimated_usd: float <span style="color:#f92672">=</span> <span style="color:#ae81ff">1.25</span>
</span></span><span style="display:flex;"><span>    spent_usd: float <span style="color:#f92672">=</span> <span style="color:#ae81ff">0.0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">charge</span>(self, input_tokens: int, output_tokens: int) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Example for a $3 input / $15 output per 1M token model.</span>
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>spent_usd <span style="color:#f92672">+=</span> (input_tokens <span style="color:#f92672">/</span> <span style="color:#ae81ff">1_000_000</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">3.00</span>)
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>spent_usd <span style="color:#f92672">+=</span> (output_tokens <span style="color:#f92672">/</span> <span style="color:#ae81ff">1_000_000</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">15.00</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> self<span style="color:#f92672">.</span>spent_usd <span style="color:#f92672">&gt;</span> self<span style="color:#f92672">.</span>max_estimated_usd:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">raise</span> <span style="color:#a6e22e">RuntimeError</span>(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Agent budget exceeded: $</span><span style="color:#e6db74">{</span>self<span style="color:#f92672">.</span>spent_usd<span style="color:#e6db74">:</span><span style="color:#e6db74">.2f</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">run_with_budget</span>(agent: Agent, prompt: str, budget: CostBudget):
</span></span><span style="display:flex;"><span>    started <span style="color:#f92672">=</span> monotonic()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>        result <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> Runner<span style="color:#f92672">.</span>run(
</span></span><span style="display:flex;"><span>            agent,
</span></span><span style="display:flex;"><span>            prompt,
</span></span><span style="display:flex;"><span>            max_turns<span style="color:#f92672">=</span>budget<span style="color:#f92672">.</span>max_turns,
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">except</span> MaxTurnsExceeded <span style="color:#66d9ef">as</span> exc:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">raise</span> <span style="color:#a6e22e">RuntimeError</span>(<span style="color:#e6db74">&#34;Agent stopped after max_turns&#34;</span>) <span style="color:#f92672">from</span> exc
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> monotonic() <span style="color:#f92672">-</span> started <span style="color:#f92672">&gt;</span> budget<span style="color:#f92672">.</span>max_seconds:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">raise</span> <span style="color:#a6e22e">RuntimeError</span>(<span style="color:#e6db74">&#34;Agent exceeded wall-clock budget&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Wire this to provider usage fields or tracing export in real code.</span>
</span></span><span style="display:flex;"><span>    usage <span style="color:#f92672">=</span> getattr(result, <span style="color:#e6db74">&#34;usage&#34;</span>, <span style="color:#66d9ef">None</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> usage:
</span></span><span style="display:flex;"><span>        budget<span style="color:#f92672">.</span>charge(
</span></span><span style="display:flex;"><span>            input_tokens<span style="color:#f92672">=</span>getattr(usage, <span style="color:#e6db74">&#34;input_tokens&#34;</span>, <span style="color:#ae81ff">0</span>),
</span></span><span style="display:flex;"><span>            output_tokens<span style="color:#f92672">=</span>getattr(usage, <span style="color:#e6db74">&#34;output_tokens&#34;</span>, <span style="color:#ae81ff">0</span>),
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> result
</span></span></code></pre></div><p>This wrapper is not enough by itself because it charges after the run. In a mature system I charge per model call via middleware, tracing hooks, or a gateway. But even this version prevents the worst class of unbounded turn loops.</p>
<h3 id="what-should-a-minimal-langgraph-guard-look-like">What Should A Minimal LangGraph Guard Look Like?</h3>
<p>LangGraph makes the workflow explicit, so use that explicitness. Do not let the graph decide forever.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># Python 3.12</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># langgraph==1.2.8</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>config <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;recursion_limit&#34;</span>: <span style="color:#ae81ff">12</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;configurable&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;root_request_id&#34;</span>: <span style="color:#e6db74">&#34;req_01JZ_COST_GUARD&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;max_estimated_usd&#34;</span>: <span style="color:#ae81ff">2.50</span>,
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>result <span style="color:#f92672">=</span> graph<span style="color:#f92672">.</span>invoke(
</span></span><span style="display:flex;"><span>    {<span style="color:#e6db74">&#34;task&#34;</span>: <span style="color:#e6db74">&#34;Classify this support case and draft a reply.&#34;</span>},
</span></span><span style="display:flex;"><span>    config<span style="color:#f92672">=</span>config,
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>Then add a guard node that checks the shared ledger before expensive nodes:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">budget_gate</span>(state: dict) <span style="color:#f92672">-&gt;</span> dict:
</span></span><span style="display:flex;"><span>    spent <span style="color:#f92672">=</span> state<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;estimated_usd&#34;</span>, <span style="color:#ae81ff">0.0</span>)
</span></span><span style="display:flex;"><span>    limit <span style="color:#f92672">=</span> state<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;max_estimated_usd&#34;</span>, <span style="color:#ae81ff">2.50</span>)
</span></span><span style="display:flex;"><span>    repeated_tools <span style="color:#f92672">=</span> state<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;repeated_tool_calls&#34;</span>, <span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> spent <span style="color:#f92672">&gt;=</span> limit:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> {<span style="color:#e6db74">&#34;status&#34;</span>: <span style="color:#e6db74">&#34;stopped&#34;</span>, <span style="color:#e6db74">&#34;reason&#34;</span>: <span style="color:#e6db74">&#34;budget_exceeded&#34;</span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> repeated_tools <span style="color:#f92672">&gt;=</span> <span style="color:#ae81ff">3</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> {<span style="color:#e6db74">&#34;status&#34;</span>: <span style="color:#e6db74">&#34;stopped&#34;</span>, <span style="color:#e6db74">&#34;reason&#34;</span>: <span style="color:#e6db74">&#34;tool_loop_detected&#34;</span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> {<span style="color:#e6db74">&#34;status&#34;</span>: <span style="color:#e6db74">&#34;continue&#34;</span>}
</span></span></code></pre></div><p>In practice, I prefer returning a structured stopped state over throwing generic exceptions from every node. It makes the incident easier to debug, and it lets the UI say, &ldquo;I stopped because this task exceeded its configured budget&rdquo; instead of timing out.</p>
<h2 id="what-is-the-1-hour-prevention-blueprint">What Is The 1-Hour Prevention Blueprint?</h2>
<p>If a team asked me to harden an agent today with only one hour available, I would do five things.</p>
<p>First, set a default maximum turn count. For most business workflows, start at 8. For coding agents, start at 15. For long-running research agents, start at 20 but require explicit approval above that. The exact number matters less than making &ldquo;unbounded&rdquo; impossible.</p>
<p>Second, add a root request budget. Use dollars, not tokens, because product managers and finance teams understand dollars. Internally you can still calculate from tokens.</p>
<p>Third, cap tool repetition. A model calling <code>search_docs(&quot;refund policy&quot;)</code> five times is not researching; it is stuck. Stop after three equivalent calls unless the tool result changed materially.</p>
<p>Fourth, cap parallelism. If the planner wants to spawn 20 workers, it should be rejected before those workers start. I usually start with a branch factor of 3 for production user flows and higher limits only for offline batch jobs.</p>
<p>Fifth, route budget failures to the same incident path as reliability failures. A runaway agent is not &ldquo;just cost.&rdquo; It can also hammer third-party APIs, mutate customer records repeatedly, fill queues, and hide useful traces under noise.</p>
<p>Here is a simple config I would accept for a first production pass:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">agent_budget_policy</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">default_model</span>: <span style="color:#e6db74">&#34;claude-sonnet-5&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">max_turns</span>: <span style="color:#ae81ff">10</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">max_wall_clock_seconds</span>: <span style="color:#ae81ff">60</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">max_estimated_usd_per_root_request</span>: <span style="color:#ae81ff">1.50</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">max_child_agents</span>: <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">max_parallel_tools</span>: <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">max_equivalent_tool_calls</span>: <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">alert_at_percent</span>: [<span style="color:#ae81ff">50</span>, <span style="color:#ae81ff">80</span>, <span style="color:#ae81ff">100</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">hard_stop_at_percent</span>: <span style="color:#ae81ff">100</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">on_stop</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">user_message</span>: <span style="color:#e6db74">&#34;The agent stopped before completing because it hit a safety budget.&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">notify</span>: [<span style="color:#e6db74">&#34;#ai-agent-incidents&#34;</span>, <span style="color:#e6db74">&#34;oncall-ai-platform&#34;</span>]
</span></span></code></pre></div><p>The trade-off is obvious: some legitimate tasks will stop early. That is acceptable. A stopped workflow with a clear reason is much cheaper to debug than an agent that spends all weekend proving it is confused.</p>
<h2 id="what-is-the-1-week-prevention-blueprint">What Is The 1-Week Prevention Blueprint?</h2>
<p>The one-week version is where you move from guardrails to operating model.</p>
<h3 id="how-should-you-track-cost-per-business-event">How Should You Track Cost Per Business Event?</h3>
<p>Do not stop at provider dashboards. They are too coarse. Track cost by:</p>
<ul>
<li><code>root_request_id</code></li>
<li>user or service account</li>
<li>feature name</li>
<li>model</li>
<li>agent name</li>
<li>tool name</li>
<li>environment</li>
<li>customer or tenant</li>
<li>final status</li>
</ul>
<p>The most important derived metric is cost per successful event. Examples:</p>
<table>
  <thead>
      <tr>
          <th>Workflow</th>
          <th>Useful cost metric</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Support agent</td>
          <td>Dollars per resolved ticket</td>
      </tr>
      <tr>
          <td>Coding agent</td>
          <td>Dollars per merged patch</td>
      </tr>
      <tr>
          <td>Research agent</td>
          <td>Dollars per accepted report</td>
      </tr>
      <tr>
          <td>Sales enrichment</td>
          <td>Dollars per verified account</td>
      </tr>
      <tr>
          <td>QA agent</td>
          <td>Dollars per failing test reproduced</td>
      </tr>
  </tbody>
</table>
<p>This exposes the cases where spend rises but value does not. A coding agent that spends $8 on a merged patch may be excellent. A support agent that spends $0.90 to fail a basic password-reset ticket is a product bug.</p>
<h3 id="how-should-you-build-a-kill-switch">How Should You Build A Kill Switch?</h3>
<p>Build the hard stop outside the agent framework. Framework limits are useful, but your final authority should be middleware, a gateway, or a queue worker that can deny the next model call.</p>
<p>The control should answer three questions before every model request:</p>
<ol>
<li>Is this root workflow still allowed to spend?</li>
<li>Is this service account still allowed to use this model?</li>
<li>Is the current spend slope normal for this feature?</li>
</ol>
<p>If any answer is no, return a typed failure to the agent and stop scheduling new work. Do not rely on the model to politely stop itself after being told it is expensive.</p>
<h3 id="how-should-you-treat-provider-budgets">How Should You Treat Provider Budgets?</h3>
<p>Use provider budgets as the outer fence. OpenAI project budgets, Anthropic workspace controls, Google Cloud budgets, and cloud billing alerts are all useful. They should page humans and catch configuration mistakes across projects.</p>
<p>But application hard caps should fire first. If your OpenAI project budget alert fires at 100%, you have already lost the product-level containment game. I prefer alerts at 50% and 80%, with application-level hard stops per feature long before the monthly number is at risk.</p>
<h2 id="what-checklist-should-teams-use-for-2026-ai-cost-governance">What Checklist Should Teams Use For 2026 AI Cost Governance?</h2>
<p>Here is the checklist I would put in a production readiness review for agents.</p>
<table>
  <thead>
      <tr>
          <th>Area</th>
          <th>Requirement</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Runtime limits</td>
          <td>Every agent has max turns, max wall-clock time, and max tool repetition</td>
      </tr>
      <tr>
          <td>Spend limits</td>
          <td>Every root request has a dollar budget enforced before model calls</td>
      </tr>
      <tr>
          <td>Model policy</td>
          <td>Expensive models are allowlisted per feature, not available by default</td>
      </tr>
      <tr>
          <td>Key policy</td>
          <td>Service accounts use restricted API keys scoped to one project or environment</td>
      </tr>
      <tr>
          <td>Context policy</td>
          <td>Tool logs and history are summarized or trimmed before replay</td>
      </tr>
      <tr>
          <td>Fan-out policy</td>
          <td>Child agents and parallel tools have explicit caps</td>
      </tr>
      <tr>
          <td>Observability</td>
          <td>Traces include root request ID, model, tokens, estimated cost, and stop reason</td>
      </tr>
      <tr>
          <td>Alerts</td>
          <td>Cost slope alerts page an owner, not a general channel</td>
      </tr>
      <tr>
          <td>Runbooks</td>
          <td>On-call knows how to pause queues, rotate keys, and disable a feature flag</td>
      </tr>
      <tr>
          <td>Reviews</td>
          <td>New agent workflows include cost tests before launch</td>
      </tr>
  </tbody>
</table>
<p>Cost tests are still uncommon, but they catch real bugs. Add fixture prompts that simulate tool errors, invalid structured outputs, empty search results, and ambiguous user requests. Assert that the agent stops inside budget.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">test_refund_agent_stops_on_repeated_crm_failure</span>(refund_agent):
</span></span><span style="display:flex;"><span>    result <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> run_fixture(
</span></span><span style="display:flex;"><span>        refund_agent,
</span></span><span style="display:flex;"><span>        user_message<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Refund order 1234&#34;</span>,
</span></span><span style="display:flex;"><span>        crm_behavior<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;always_500&#34;</span>,
</span></span><span style="display:flex;"><span>        budget<span style="color:#f92672">=</span>{<span style="color:#e6db74">&#34;max_turns&#34;</span>: <span style="color:#ae81ff">6</span>, <span style="color:#e6db74">&#34;max_estimated_usd&#34;</span>: <span style="color:#ae81ff">0.40</span>},
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">assert</span> result<span style="color:#f92672">.</span>status <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;stopped&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">assert</span> result<span style="color:#f92672">.</span>reason <span style="color:#f92672">in</span> {<span style="color:#e6db74">&#34;tool_loop_detected&#34;</span>, <span style="color:#e6db74">&#34;budget_exceeded&#34;</span>}
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">assert</span> result<span style="color:#f92672">.</span>estimated_usd <span style="color:#f92672">&lt;=</span> <span style="color:#ae81ff">0.40</span>
</span></span></code></pre></div><p>This is not theoretical purity. It is the same discipline we already apply to database connection pools, queue retry limits, and payment idempotency. Agents just made the failure mode more expensive.</p>
<h2 id="what-should-you-do-after-a-cost-incident">What Should You Do After A Cost Incident?</h2>
<p>Start by preserving the trace. Do not only export the invoice. You need the sequence of model calls, tool calls, prompts, token counts, outputs, retries, handoffs, and queue events. Without that sequence, the team will argue about prompts when the real bug may be orchestration.</p>
<p>Then answer these questions:</p>
<ul>
<li>Which root request started the spend?</li>
<li>Which guard should have stopped it?</li>
<li>Was the provider budget only a soft alert?</li>
<li>Did the agent repeat equivalent tool calls?</li>
<li>Did context grow each turn?</li>
<li>Did a child agent spend outside the parent budget?</li>
<li>Did anyone own the alert?</li>
<li>Could the same workflow restart from a queue retry?</li>
</ul>
<p>The remediation should include a code change and an operations change. A code change might be <code>max_turns=8</code>, <code>recursion_limit=12</code>, or a budget middleware check. An operations change might be an on-call route, a feature flag, a model allowlist, or a service account permission change.</p>
<p>I&rsquo;ve seen teams treat cost incidents as embarrassing finance mistakes. That is the wrong framing. A runaway agent is a production incident with a dollar symptom. Handle it with the same seriousness as a latency regression or a data corruption bug.</p>
<h2 id="faq">FAQ</h2>
<h3 id="what-is-an-ai-agent-api-cost-horror-story">What Is An AI Agent API Cost Horror Story?</h3>
<p>An AI agent API cost horror story is a runaway workflow where an agent keeps making model calls, tool calls, retries, or handoffs until the API bill becomes much larger than expected. In 2026, the common pattern is an agentic loop rather than one unusually large prompt.</p>
<h3 id="are-openai-project-budgets-hard-spending-caps">Are OpenAI Project Budgets Hard Spending Caps?</h3>
<p>No. OpenAI project budgets are soft spending thresholds. They are useful for alerts and visibility, but API requests continue after the monthly threshold is exceeded. Use application-level hard stops if you need guaranteed containment.</p>
<h3 id="what-is-a-good-max-turn-limit-for-ai-agents">What Is A Good Max Turn Limit For AI Agents?</h3>
<p>For ordinary business workflows, I usually start with 8 to 10 turns. For coding or research agents, 15 to 20 can be reasonable. The right limit depends on the task, but an unlimited production agent is almost never defensible.</p>
<h3 id="how-do-i-detect-an-agentic-loop">How Do I Detect An Agentic Loop?</h3>
<p>Track repeated tool calls, lack of durable progress, rising tokens per turn, repeated handoffs, and cost per successful business event. Normalize tool arguments before comparing them so small formatting changes do not hide equivalent repeated calls.</p>
<h3 id="should-i-use-cheaper-models-to-avoid-runaway-costs">Should I Use Cheaper Models To Avoid Runaway Costs?</h3>
<p>Cheaper models help, but they do not solve runaway behavior. A low-cost model in an unbounded loop can still burn serious money, hammer tools, and create operational noise. Fix the control plane first, then optimize model selection.</p>
]]></content:encoded></item></channel></rss>