Skip to content

Budgets

Most gateways report cost. costhelm enforces it: every call is checked against the principal’s ceiling before any provider is contacted.

  1. The controller projects the worst case for the model about to be called: estimated input tokens plus the full max_tokens, at that model’s rates from pricing.yaml, times a safety factor.
  2. It reads the principal’s spend back out of the ledger — never a parallel counter — and compares projection against remaining allowance.
  3. Refusal is an HTTP 402 with the arithmetic in the body. Nothing was sent, so the refusal costs $0.
  4. Inside the failover ring, a refused candidate is dropped and a cheaper candidate is tried before the 402 fires — a run near its ceiling drifts down the ladder instead of slamming into the refusal.
{
"error": "budget_exceeded",
"message": "session:run-42 would exceed its lifetime budget: spent $0.047 of $0.05, this call projects $0.016",
"principal": "session:run-42",
"limit_usd": 0.05, "spent_usd": 0.047,
"remaining_usd": 0.003, "projected_usd": 0.016, "shortfall_usd": 0.013,
"hint": "The call was refused before any provider was contacted, so nothing was billed..."
}

A budget names "<dimension>:<value>" over the five attribution dimensions — tenant, project, user, agent, session — and values may glob: tenant:* puts a ceiling under every tenant.

minute, hour, day, month, or lifetime. Spend windows are computed from the ledger at admission time.

In budgets.yaml (ships empty — a fresh gateway refuses nothing):

budgets:
"session:*": { limit_usd: 0.50, period: day }
"agent:bulk": { limit_usd: 0.05, period: hour }

At runtimePOST /v1/budget shadows a same-named yaml entry, so an operator can clamp a runaway session without a deploy. limit_usd: 0 refuses everything for that principal immediately:

Terminal window
curl -s localhost:8111/v1/budget -H 'Content-Type: application/json' \
-d '{"principal": "session:run-42", "limit_usd": 0, "period": "lifetime"}'

GET /v1/budget shows the controller’s full view — including any config error that disarmed it, so a broken budgets.yaml is visible, never silent.

routing.yaml’s budget: block adds two preferences: prefer_affordable deprioritises tiers whose cheapest provider would breach the remaining allowance, and block_escalation_on_breach stops the cascade from escalating into a tier the budget cannot pay for. The controller remains the authority; these only shape candidate order.