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:
- Check before the call, not after. A guard that runs after the request is a report, not a guard.
-
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. - 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.
Top comments (0)