Your AI Agent Needs a Hard Spending Cap

Simon Willison is right: soft limits don't stop runaway agents. Here's how to enforce hard caps on tokens, dollars, and time in your code.

Share

Simon Willison's post sitting on Hacker News today makes a blunt argument: we're going to need default hard budget caps on pretty much everything. The trigger is obvious — AI agents that can spend tokens, and therefore money, without asking. The moment software spends on your behalf, "I'll check the dashboard tomorrow" stops being a plan.

Here's the uncomfortable part: agents fail in loops. A coding agent that can't fix a test will retry forever, and every retry costs real money. Infinite loops used to burn CPU. These burn your credit card.

Why this matters

LLM calls break the assumptions our cost controls were built on. Traditional code has roughly predictable cost per request. An agent's spend is non-deterministic — it depends on how a model handles your task today, and one weird input can triple it.

Soft limits don't save you here. Alerts fire after the burn. Prompting the agent to "be efficient" is a suggestion, not a control — the model ignores it precisely when it's stuck. You need caps the agent cannot argue with.

How it works

A hard cap is an invariant enforced outside the thing being capped. Three layers, all cheap:

  • Per request: max_tokens and a timeout on every call. This bounds the worst case of a single call.
  • Per task: a budget — call count, dollars, deadline — checked before each call, not after. Check, spend, decrement, repeat until the budget says stop or the task says done.
  • Per key: a spend ceiling on your provider's billing page, as the backstop that catches everything your wrapper missed.

The critical design choice: enforce the cap in a wrapper that throws, so every code path inherits it. If each feature remembers to check its own budget, one will forget — at 3 a.m.

Where this helps

Batch jobs that summarize or classify thousands of items. One malformed input triggers a retry storm, and your $200 job quietly becomes a $2,000 job.

Unattended coding agents. "Refactor this module" expands until the agent rewrites half the repo in circles at per-token rates.

User-facing AI features. A user pasting the same giant document into your chat app all afternoon should hit their session cap, not your margin.

Watch out

Checking before the call still lets one oversized call through, so cap max_tokens and input length per request too. And budget the retry loop, not just the happy path — retries multiply spend.

Shared budgets across concurrent workers have races: check-then-decrement isn't atomic. Use an atomic counter (Redis INCR works) or a single writer.

Make "budget exceeded" a first-class error. A cap that surfaces as a generic timeout looks like an outage and gets "fixed" by someone raising it to infinity.

Try it yourself

Wrap your LLM call site so every agent inherits hard caps — calls, dollars, and wall clock:

type Caps = { maxCalls: number; maxUsd: number; wallMs: number };

async function cappedAgent(task: string, caps: Caps) {
  const deadline = Date.now() + caps.wallMs;
  let calls = 0, spentUsd = 0;

  while (true) {
    if (calls >= caps.maxCalls)  throw new Error("cap: call count");
    if (Date.now() > deadline)    throw new Error("cap: deadline");
    if (spentUsd >= caps.maxUsd)  throw new Error("cap: budget");

    const res = await callLLM(task, { max_tokens: 4096 }); // your call site
    calls++;
    spentUsd += usd(res.usage); // tokens × your model's rates
    if (res.done) return res.answer;
  }
}

Every path through your agent now has a ceiling. Set the provider-side limit while you're at it.

TL;DR

  • What changed: Willison argues anything that spends money or time — AI agents especially — needs hard budget caps by default.
  • Why it matters: agents fail in loops, alerts fire after the burn, and prompts aren't controls. A wrapper that throws fails closed.
  • Try today: wrap your LLM call site with call, dollar, and deadline caps — then set your provider's spend limit.