Budgets
A budget caps what an agent may spend — dollars, tokens, model calls, tool calls, sandbox seconds — for one run, a session, the agent, or the whole application. By the end of this page you will have a run that stops before a model call it cannot afford, waits for a person to top it up, and carries on.Every output on this page is what the code printed when it was run. Prices and
the model’s wording will differ on your run.
A run that waits for money
How it works
1
Every scope that covers the run is charged
A run charges its own
request budget, its session’s, its agent’s, and the
application’s. Any one of them running out stops the work, and the run says
which.2
A model call is held before it is made
The runtime counts the input it is about to send and prices the most the
call could return:
model_config["max_tokens"], or 4,096 tokens when none
is set (sent to the provider as the call’s ceiling, so an answer that
reaches it ends with termination_reason length). That amount is
held, together with one model call. If it does not fit, the call is
not made.3
The real cost replaces the hold
When the call returns, the hold is settled at what it really cost and its
tokens are counted. The trace records a
budget_warning when a meter
reaches warn_at (80%) of its limit, and budget_exhausted when it runs
out.4
At the wall: pause or end
With
on_exhausted: "pause" (the default) the run returns
status: "awaiting_budget" with a budget_request, keeping the work done
so far. With "terminate" it ends with status: "error" and
termination_reason: "budget_exhausted".request counter is removed and its spend kept on the run’s record;
session, agent and application counters stay.
Scopes, meters and windows
A window resets a counter:
total (never), day (00:00 UTC) or month (the
1st, 00:00 UTC).
A call’s output is known only when it returns, so a call can cross a token
limit: what it used is still counted in full, and the next call is stopped
before it is made (its input would not fit). Leave a call’s worth of headroom
if the limit must never be passed. (In 0.4.1 the call that crossed the limit
was not counted, and resuming made it again.)
budgets: in the
policy); set them in one place or the other, not both.
When a budget runs out
Pause, then grant or deny
budget_request says what stopped the run:
Then, from this process or any other sharing the memory store:
await agent.grant_budget(run_id, approver="alice", amount=..., note=...)adds to that one budget. Withoutamount, the grant is exactly theshortfall: enough for the call that stopped, so the run may stop again at its next call. The policy is not changed; the grant is recorded with the approver’s name.await agent.deny_budget(run_id, approver="alice", note=...)refuses it.await agent.resume(run_id)then continues a granted run, or ends a denied one cleanly.await agent.budget_status(run_id)lists every budget covering the run:limit,granted,spent,reservedandremaining.
End instead of waiting
terminate where no person is watching: batch jobs, evaluations, a
background worker. A budget a person has already denied for this run is not
asked about again: the run ends.
A model with no published price
A model behindbase_url (vLLM, LM Studio, a gateway) usually has no
published price. Its calls are made and recorded, but a model_cost_usd
budget cannot govern them: it would never trip. Budget its calls and tokens
instead. Here against a local OpenAI-compatible server (a small stand-in that
gives the same answer to every question):
cost_complete: False says the 0.0
is not a real price, and the trace has a budget_cost_incomplete event for
each such call.
Over HTTP
OmniServe has the same flow. This server (on port 8123, beside the stand-in model on 8000) allows one model call per session:demo was answered; the second returned
"status": "awaiting_budget" with its run_id and budget_request:
GET /runs/{run_id}/budget returns budgets (as budget_status) and
requests (each time the run ran out). Its budget entry and the request’s
status:
"decision": "deny"), and the run is resumed:
status and response. Leave out
amount to grant the shortfall.
Budgets and the other limits
Budgets are not the only thing that stops a run; they are the only limit in dollars, and the only one across runs.
More in which limit to use.
When things go wrong
ValueError: budget meter must be one of …
ValueError: budget meter must be one of …
A meter name that does not exist:The window and
on_exhausted are checked the same way:ValueError: budgets.application_id is required to budget an application
ValueError: budgets.application_id is required to budget an application
An Add
application budget needs to know which application it belongs to:"application_id": "support-desk" beside the scopes.Run … is not waiting for a budget decision
Run … is not waiting for a budget decision
grant_budget or deny_budget on a run that is not awaiting_budget —
already granted, or never stopped — raises LookupError; over HTTP it is
a 404:A dollar budget never stops a local model
A dollar budget never stops a local model
The model has no published price, so
model_cost_usd has nothing to
count; cost_complete is False in the run’s totals. Budget
model_calls and model_tokens instead (above).The run keeps stopping after every grant
The run keeps stopping after every grant
A grant without
amount covers only the call that stopped. Grant more
headroom: amount=0.50.Next
Budgets reference
Every meter, window, scope and field.
Approvals
Pausing for a person, for tools as well as money.
Policies
Budgets can live in a policy file, with its rules.
Sandboxes and execution
Where sandbox seconds are spent.
Durable runs
Resume a waiting run from another process, days later.
Security model
What is enforced, and where.