I run 20 AI agents unattended. Last week they all froze at once — the Claude API returned 402 "credit balance too low" and every agent crashed.

The fix was a small stdlib-only Python budget guard that checks the cap before each call:

import json, os, time BUDGET_FILE = "budget.json" DAILY_CAP_USD = 25.0 def can_spend(usd: float) -> bool: if not os.path.exists(BUDGET_FILE): return True with open(BUDGET_FILE) as f: data = json.load(f) today = time.strftime("%Y-%m-%d") spent = data.get(today, 0.0) return spent + usd <= DAILY_CAP_USD def record_spend(usd: float): today = time.strftime("%Y-%m-%d") data = {} if os.path.exists(BUDGET_FILE): with open(BUDGET_FILE) as f: data = json.load(f) data[today] = data.get(today, 0.0) + usd with open(BUDGET_FILE, "w") as f: json.dump(data, f) 

Before every LLM call, I now do:

if not can_spend(estimated_cost_usd): raise RuntimeError("Daily budget exceeded — owner action required") # ... make the call ... record_spend(actual_cost_usd) 

Three things make this pattern hold up in production:

  1. Check before the call, not after. A guard that runs after the request is a report, not a guard.
  2. Estimate, then reconcile. can_spend(estimated_cost_usd) uses an upper bound; record_spend(actual_cost_usd) uses the real number once you have it.
  3. Never let the counter be the only truth. Keep an append-only log of every call (amount, what was called, timestamp) so a vanished charge shows up as a gap you can replay instead of a total you cannot explain.

The failure modes to avoid: writing the record after the call returns (a timeout leaves you with a provider-side charge and a log that says nothing happened), and keeping the counter in a plain JSON file shared by two processes (one write wins, the other vanishes — use SQLite with a transaction, or one writer per file).

What's your daily AI budget ceiling? Share your number in the comments — I'm curious what other teams set.