An unattended agent has no natural stopping point on cost. A research task with web search can drift into another hundred fetches. A coding loop can retry a flaky test until morning. Without a ceiling you find out from the invoice.
A session budget is an enforced spend cap on a single session. You set a maximum list cost when you create the session. The platform tracks the token cost of every thread in that session at public list prices, and when the running total reaches the cap the session goes idle with the stop reason budget_reached. Its files, tool state, and conversation stay intact. Raise the cap and the work continues from where it stopped.
nt-mono break-words box-decoration-clone">sessions.create
reading
usage.list_cost
and the
session.usage
snapshot the stream delivers as the turn ends
the
budget_reached
stop and what a paused session keeps
raising, lowering, and removing the cap with
sessions.update
1. Set up the client
l-8">
- Build an agent with room to overspend
The agent for this walkthrough writes a competitive landscape brief. It has web search and web fetch, and nothing in its prompt bounds how many sources it reads. That open-endedness is what a budget is for.
px-2 py-0.5 rounded text-sm font-mono break-words box-decoration-clone">USD
is the only accepted currency, and every cost amount the API returns (
usage.list_cost
included) uses the same encoding.
The cap counts model token cost at public list price, summed across every thread in the session, including subagent threads. List price applies regardless of any negotiated discount, so the cap fires at or before your actual charge, and you can reproduce the number from
session.usage
plus the public rate card.
Every model the session can run needs a public list price. If one does not, create fails with
model_not_budgetable
.
Omit
budget
and the session is uncapped. A budget can only be attached at creation: a session created without one can never gain one later, so decide up front.
The cap here is ten cents, deliberately low so the stop shows up in a couple of minutes. The usd helper renders minor-unit amounts as dollars for every printout below.
max-h-[inherit] min-h-0 flex-1 overflow-auto scroll-fade-y scroll-fade-size-6 focus-visible:outline-none">
events = list(client.beta.sessions.events.list(session.id, limit=1000, betas=BETAS))
tool_calls = [ev for ev in events if ev.type == "agent.tool_use"]
messages = [ev for ev in events if ev.type == "agent.message"]
print(f"tool calls before the cap: {len(tool_calls)}")
if messages:
last = messages[-1]
print("".join(b.text for b in last.content if b.type == "text")[:600])
else:
print("(no agent.message yet: the agent was still gathering sources when the cap fired)")
tool calls before the cap: 11 (no agent.message yet: the agent was still gathering sources when the cap fired) 6. Raise the cap and let it finish sessions.update accepts the same budget shape. Raising max_list_cost above the consumed cost lifts the pause and the session resumes the interrupted turn on its own, from the state it stopped in.