Budgets
Most gateways report cost. costhelm enforces it: every call is checked against the principal’s ceiling before any provider is contacted.
How admission works
Section titled “How admission works”- 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 frompricing.yaml, times a safety factor. - It reads the principal’s spend back out of the ledger — never a parallel counter — and compares projection against remaining allowance.
- Refusal is an HTTP 402 with the arithmetic in the body. Nothing was sent, so the refusal costs $0.
- 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..."}Principals
Section titled “Principals”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.
Periods
Section titled “Periods”minute, hour, day, month, or lifetime. Spend windows are computed
from the ledger at admission time.
Declaring budgets
Section titled “Declaring budgets”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 runtime — POST /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:
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.
Interaction with routing
Section titled “Interaction with routing”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.