Your agent started at 11pm. By 6am, it had made 847 API calls. The bill landed in your inbox at 7:15am: $4,287.
This happened to a team running a document processing agent on LangGraph. The agent was supposed to extract metadata from PDFs, calling Claude with each document. A retry mechanism meant to handle transient errors instead created a cascade. One failed call triggered a retry, which triggered another, which loaded more context from the previous attempts, which caused new failures, which triggered more retries. By the time the bug was caught, the agent had drained most of a month's compute budget in six hours.
This pattern repeats across teams deploying LLM agents. The bill always arrives by surprise. The architecture that stops it requires moving cost enforcement upstream, before the API call fires.
The three ways agents blow through budgets
Agent loops. An agent with tool access enters a repeat loop: it calls a tool, the tool returns a result, the agent decides to call the same tool again. Normally this is intentional (searching, then refining the search, then extracting). But if the agent's goal condition never becomes true, it loops forever. A well-designed agent has a maximum iteration cap. Many don't. Default LangGraph agent loops will run until they hit the token context limit or the parent times out, whichever comes first. Each iteration calls the LLM, and each call costs money.
Retry cascades. Exponential backoff is a standard pattern for handling transient failures. It's also a footgun in systems with agent loops. When an agent tool fails and the agent retries, the retry often includes the full prior conversation history as context. If the retry fails again, the next retry includes both the original history and the first failure, doubling the context. After three retries, the context is four times the original size. After five retries, the cost per token is amplified by 32x. If the underlying issue is not transient, the agent is now paying exponentially more money to hit the same failure, then retrying again.
Unexpected document sizes. An agent that processes uploaded documents often loads the entire document into context before reasoning about it. A user who uploads a 200-page PDF expects a quick summary. The agent doesn't validate the size before loading it. The tokenization is often wrong (PDF→text conversion is messier than most teams expect). A 200-page PDF can tokenize to 150,000 tokens. A call that should cost $0.45 costs $7.50. If the agent is running in a loop processing ten documents, that's $75 for work that should have cost $4.50.
Why standard approaches fail
Observability tools log after the call fires. Helicone, Portkey, and LangSmith are excellent at tracking what happened. They see the full call, the response, the cost, and the latency. But they see it after the system has already paid. By the time you get the alert that someone crossed their budget, the charges are already on your bill. For a fast-moving agent, that's a six-hour window between the start of overspend and the alert.
Application-level rate limiting ignores token cost. Most teams implement rate limiting by request count: "allow 100 requests per minute." But the cost isn't uniform. A request with 5,000 tokens of context costs 25x more than a request with 200 tokens. Request-based rate limiting treats both the same. An agent making 100 small requests stays under the rate limit but uses a tiny fraction of budget. An agent making 10 requests with massive context can exceed budget while appearing well-behaved by the rate limit metric.
Cost is estimated after tokenization, not before. Teams that do cost estimation calculate it based on usage logs. The problem is the sequence: the API call fires, the tokenizer runs, then the cost is known, then the bill arrives. The decision to make the call was made before the cost was known. By the time you know it's expensive, you've already paid for it.
The correct architecture: pre-flight enforcement
The fix is to estimate token cost before the request fires and reject the request if it would exceed the budget. This requires three layers.
Layer 1: Token estimation on the client. Before any API call, estimate the token count of the prompt and context. For OpenAI, Anthropic, and LiteLLM, SDKs provide tokenizers. For LangChain and LangGraph, you have access to the message history before it's sent. Multiply the estimated token count by the model's per-token rate to get an estimated cost, and pad the estimate rather than rounding it down — an overestimate costs you a rejected call that would have been fine; an underestimate lets exactly the overspend through that this layer exists to stop.
Layer 2: A budget check that blocks before the call fires. The estimated cost gets checked against a budget counter — per agent run, per user, per project — and the call is rejected if it would exceed the cap. This has to be synchronous and happen before the request leaves your process, not logged afterward for a dashboard to flag later. The counter itself needs to be atomic under concurrent calls (the same race condition that affects per-user budgets in single-request systems applies here too, just faster, since an agent can fire several tool calls in the same loop iteration).
def check_and_reserve(run_id: str, estimated_cost: float, run_budget_cap: float) -> bool:
new_total = redis.incrbyfloat(f"run:{run_id}:spend", estimated_cost)
if new_total > run_budget_cap:
redis.incrbyfloat(f"run:{run_id}:spend", -estimated_cost) # roll back
return False
return TrueLayer 3: A hard iteration and run-level circuit breaker. Budget checks alone don't catch a loop where each individual call is cheap but the loop itself never terminates — the agent loops example from earlier, where 847 calls each cost a few dollars in aggregate but none individually crosses a per-call threshold. A run-level cap on both total spend and iteration count closes this gap: set a hard ceiling (e.g., $50 or 100 iterations, whichever comes first) on the entire agent run, independent of the per-call budget check, and kill the run outright when either limit is hit rather than letting it degrade gracefully into more calls.
class AgentRun:
def __init__(self, budget_cap_usd: float, max_iterations: int):
self.budget_cap = budget_cap_usd
self.max_iterations = max_iterations
self.spend = 0.0
self.iterations = 0
def before_call(self, estimated_cost: float):
self.iterations += 1
if self.iterations > self.max_iterations:
raise RunTerminated("iteration cap exceeded")
if self.spend + estimated_cost > self.budget_cap:
raise RunTerminated("budget cap exceeded")Back to the $4,287 morning
Applied to the document-processing agent from the start of this piece: Layer 1 would have estimated the ballooning context size on each retry and flagged the cost increase immediately, rather than after the sixth hour. Layer 2 would have rejected the retry once its estimated cost crossed the per-call threshold that a normal (non-cascading) call would never approach. Layer 3 would have killed the run entirely once total spend crossed a sane ceiling — $50, say — long before it reached four figures. None of these layers require the agent's underlying logic to be correct; they assume it won't be, and enforce a ceiling regardless of what caused the loop.
Where noburn fits
The tools compared in this article handle observability, routing, or evaluation — all of which operate after the LLM call completes. noburn operates before it. It wraps your existing OpenAI, Anthropic, LangChain, and the Vercel AI SDK client, estimates the token cost of each call, and blocks it if the calling user or project has exceeded their budget. Nothing in this comparison does that at a self-serve price point.
Per-user metering lets you enforce separate limits per end-customer, and Stripe passthrough lets you bill them for their LLM usage without writing a billing layer yourself. The free tier covers 50,000 requests per month. Documentation and SDKs are at noburn.dev/docs.