All posts
Agent Governance

Your AI Agent Has No Kill Switch (And It Will Cost You)

How autonomous agents spiral into infinite loops and budget overruns — and the three guardrails every production agent needs before you ship.

·7 min read

You deployed an agent on a Friday afternoon. The first few test runs looked clean. By Saturday morning, you had a four-figure API bill from a single run that had been looping since the night before — calling the same tool, getting the same ambiguous output, and trying again. The agent had no ceiling on what it could spend. It had no concept of how many times it had tried. It just kept going.

This is the failure mode that every agent builder hits eventually. Usually right after they ship to production.

Why agents fail in ways that APIs don't

A REST endpoint is stateless. It either handles a request in milliseconds or it doesn't. The failure is immediate and obvious.

An agent is a loop. It persists state across steps, makes decisions at runtime, and can run for minutes or hours. The failure modes are fundamentally different:

  • An API call either responds or times out. An agent can respond correctly on every individual step while the overall run drifts further from resolution with each one.
  • An API call has a fixed, predictable cost. An agent's cost compounds with every step — and the number of steps is determined by the agent's own reasoning, not your code.
  • An API failure is usually obvious (4xx, 5xx). An agent failure can look like success: every tool call returns 200, every LLM response is valid JSON, but the agent never reaches its exit condition.

You can't apply the same mental model. You need different tooling.

The three failure modes

1. Infinite loops

The most common. The agent's exit condition is never satisfied. Maybe the tool it's waiting on returns ambiguous output and the agent keeps retrying. Maybe there's a bug in the prompt that causes it to circle back on itself. Maybe the LLM decides to “verify” its last answer by repeating the same step.

In a properly bounded system, an infinite loop is a recoverable error you debug and fix. In an unbounded one, it runs until you notice — or until your API balance hits zero.

2. Cost overruns without a ceiling

Even without a loop, a complex multi-step agent can spend far more than expected. A research agent that spawns sub-tasks. A code-writing agent that iterates on the same function fifteen times. Each step is a legitimate LLM call — just more of them than you planned for.

Without a per-run budget ceiling, you don't find out until the billing cycle ends.

3. Prompt replay

An agent that's stuck often starts replaying the same prompt. Same context, same inputs, slightly different phrasing. It isn't looping in the obvious “same function call” sense — it's generating novel text that accomplishes nothing new. Each replay burns tokens. If you track prompt fingerprints, you can detect this pattern after N repetitions and kill the run before the bill compounds further.

Why try/catch doesn't catch this

You can wrap your agent loop in a try/catch. You can set a wall-clock timeout on the whole run. These are reasonable first instincts. They don't solve the problem.

A timeout kills the run after time has already been wasted. The tokens were already consumed. The budget is already spent. You stopped the bleeding, but you didn't prevent it.

A try/catch catches exceptions that are thrown. A loop that's working correctly — calling valid tools, getting valid responses, making valid LLM calls — never throws an exception. There's nothing to catch.

The core problem:your try/catch handles errors your code knows about. A runaway agent is a situation your code considers normal — it's just running longer than you expected, spending more than you planned, and has no mechanism to know the difference.

What you need is enforcement that happens before each step executes, not after something goes wrong. Check-before-call, not catch-after-fail.

The three guardrails every production agent needs

1. A hard budget ceiling per run

Set a dollar limit when you start the run. Before every step, check whether the run has exceeded that limit. If it has, kill it — before any more tokens are consumed. Don't let it spend another cent.

This is the most important guardrail. Everything else is secondary. A $2 ceiling on a run that should cost $0.30 means even a catastrophic loop costs you $2, not $400.

2. An iteration limit

Cap the number of steps a run can take. 50 is a reasonable starting default for most agents. If your agent genuinely needs more, you'll know after watching a few successful runs — you can raise the limit deliberately. But the default should be conservative.

Iteration limits catch loops that haven't exceeded the budget ceiling yet. A cheap model in an infinite loop might run thousands of steps before it hits a $2 ceiling. The iteration limit kills it at step 50.

3. Loop detection

Track a fingerprint of each prompt input. If the same (or near-identical) prompt appears N times within a single run, the agent is stuck. Kill it.

The threshold matters. A single repeat isn't necessarily a loop — legitimate retries happen. Three to five consecutive identical prompts almost certainly means the agent has no path forward.

These three guardrails are independent. You need all of them. A budget ceiling without an iteration limit lets a cheap model loop indefinitely. An iteration limit without loop detection lets an agent burn 50 expensive steps on the same dead end. Loop detection without a budget ceiling still lets a subtle cost overrun accumulate.

What this looks like in practice

Here's the pattern — wrapping any agent framework with hard ceilings that enforce before each step, not after:

agent.ts
import { Thskyshield, ShieldKilledError } from '@thsky-21/thskyshield'

const shield = new Thskyshield({
  siteId: process.env.SITE_ID!,
  apiKey:  process.env.SHIELD_API_KEY!,
})

// Set your ceilings once at run start
const run = await shield.beginRun({
  budgetLimitUsd: 2.00,   // hard dollar ceiling — run dies here
  iterationLimit: 50,     // max steps before kill
  loopThreshold:  5,      // identical prompts in a row = stuck
  timeoutSeconds: 300,    // 5-minute wall-clock limit
})

try {
  while (!done) {
    // checked BEFORE the LLM call fires
    const { requestId, remainingUsd } = await run.beforeStep({
      stepType:        'llm',
      model:           'gpt-4o-mini',
      estimatedTokens: { input: 800, output: 300 },
      promptInput:     currentPrompt,  // fingerprinted for loop detection
    })

    const result = await callYourLLM(currentPrompt)

    // settle actual cost after the call
    await run.afterStep({
      requestId,
      model:        'gpt-4o-mini',
      actualTokens: { input: result.usage.input, output: result.usage.output },
    })

    done = checkExitCondition(result)
  }
} catch (err) {
  if (err instanceof ShieldKilledError) {
    // reason: 'budget' | 'iterations' | 'loop' | 'timeout'
    console.error(`Run killed: ${err.reason} after $${err.spent.toFixed(4)}`)
  }
} finally {
  const summary = await run.end()
}

The key is beforeStep. Every LLM call in your loop goes through it first. If the run is over budget, over its iteration limit, or stuck in a loop, the call is blocked and ShieldKilledError is thrown — before a single token is consumed.

afterStep settles the actual cost once the call returns. The combination gives you accurate per-step accounting without blocking on the hot path.

How to set your limits

Start conservative. Adjust once you have data from real runs.

  • Budget: 2× your expected cost per successful run. If a run should cost $0.20, set your ceiling at $0.40. Watch actual costs across 10–20 runs, then tighten.
  • Iterations:most agents that are working correctly finish in under 30 steps. Set 50 as your starting ceiling. If you're regularly hitting 40+, investigate why before raising the limit — it's usually a signal something is wrong.
  • Loop threshold:3 to 5. Fewer than 3 causes false positives on legitimate retries. More than 5 means you've already let the agent burn too many steps on the same dead end.

The math for getting this wrong is unflattering. A $2.00 run ceiling that loops becomes a $2.00 run with a clean error you can handle. The same run without a ceiling becomes whatever your API credit limit allows.

Thskyshield for Agents

All three guardrails in one SDK call

Budget ceilings, iteration limits, and loop detection — before each step fires. Works with LangGraph, CrewAI, or any agent loop you write yourself.

See how it works