Stop a runaway agent bill: per-tenant hard spend caps for LLM apps
A practical how-to: check the budget before every model call, keep a ledger per tenant, fail closed, and alert at 80%. Short TypeScript included.
What it looks like
“I just got hit with a $4,200 OpenAI bill because an agent I deployed for customer support got stuck in a recursive loop with my CRM tool call.” / “LangSmith showed the logs after the damage was done, but nothing actually stopped the bleeding in real-time.” (https://www.reddit.com/r/SaaS/comments/1sel0ju/i_almost_went_broke_because_of_an_ai_infinite_loop/)
An agent loop does not look like an attack. Each call is valid, small and fast; there are just thousands of them. Anything that only acts after the fact will report the damage rather than prevent it.
Why provider limits and observability do not stop a loop
Provider spend limits sit at the organization, project or workspace level. They are coarse: one tenant’s loop spends the budget everyone shares, and when the limit hits, every customer stops, not just the one in the loop. Enforcement is also often asynchronous, so spend can pass the limit before requests start failing.
Tracing and observability tools record each call after it is made. They are essential for finding the loop, but by design they do not sit in the request path, so they cannot refuse the next call. Alerts have the same problem: an email at 3 a.m. does not stop call number 40,000.
The fix is a check you own, in the request path, before every model call, scoped to the tenant that is paying.
The pattern
1. Pre-call budget check: before each model call, estimate its worst-case cost (prompt tokens plus max_tokens at the model’s price) and compare spent + reserved + estimate with the tenant’s cap. 2. Per-tenant ledger: record a reservation before the call and the actual cost after it, keyed by tenant and period, in a store with atomic updates. 3. Fail closed: if the ledger cannot be read or the cap is reached, do not call the model; return a clear error or hand the work to a person. 4. Alert at 80%: notify the owner once when a tenant crosses 80% of its cap, so the cap is rarely a surprise.
TypeScript: the budget guard
type Ledger = {
// atomically add amount if spent + reserved + amount <= cap; returns false otherwise
reserve(tenant: string, period: string, usd: number, cap: number): Promise<boolean>;
settle(tenant: string, period: string, reservedUsd: number, actualUsd: number): Promise<number>; // returns new spent
};
class BudgetExceeded extends Error {}
export async function guardedCall<T>(
ledger: Ledger, tenant: string, capUsd: number,
estimateUsd: number, call: () => Promise<{ result: T; costUsd: number }>,
alert: (tenant: string, spent: number) => Promise<void>,
): Promise<T> {
const period = new Date().toISOString().slice(0, 7); // monthly window
let ok = false;
try {
ok = await ledger.reserve(tenant, period, estimateUsd, capUsd);
} catch {
ok = false; // ledger unavailable: fail closed
}
if (!ok) throw new BudgetExceeded(`tenant ${tenant} is at its spend cap`);
let actual = estimateUsd;
try {
const { result, costUsd } = await call();
actual = costUsd;
return result;
} finally {
const spent = await ledger.settle(tenant, period, estimateUsd, actual);
if (spent >= 0.8 * capUsd && spent - actual < 0.8 * capUsd) await alert(tenant, spent);
}
}reserve() must be atomic: a single SQL UPDATE … WHERE spent + reserved + $1 <= cap, or a Redis Lua script. Without atomicity, ten concurrent loop iterations can each see room under the cap and all go through. settle() releases the reservation and records the real cost from the provider’s usage field.
Details that matter
Estimate high: use max_tokens for output, so a reservation always covers the worst case. Treat BudgetExceeded as non-retryable in your agent framework, or the retry logic becomes the loop. Add a daily window on top of the monthly one for agents, and a per-tenant kill switch checked in the same place. Expect a small overshoot from calls already in flight when the cap is crossed; reservations keep it to one call per worker.
Getting it with Avenbridge
Avenbridge is a hosted knowledge MCP, not a general model API: a business forwards its emails and documents, confirms its answers, and buyers ask them from their own AI at https://mcp.avenbridgetech.com/mcp. The cap applies to the AI work Avenbridge does for a business, priced at $10 per million tokens, the first 10 million at $8, with a $300 monthly cap by default that you can lower.
The cap is per business (per tenant) and checked in real time before every metered model call. At the cap the call is not made. At 80% the owner gets an email. A call already in flight can take spend slightly past the cap. Businesses can also set optional daily or monthly USD caps and a kill switch that turns the model off.
What it does not do: there is no separate cap per end user inside a business. If you need per-end-user budgets in your own app, use the pattern above.
Last updated: 31 October 2026