Every agent that calls a model in a loop is a small open tab at the token counter. Most of the time it closes fine. The times it doesn't — a tool that keeps re-calling itself, a context that quietly balloons, a sub-agent that spawns another sub-agent — are the times you find out after the invoice. Pydantic AI's v2.9.0 (July 10, 2026) turns the budget from something you hope holds into a value your code can read and enforce.
The takeaway up front#
Two features landed. A /usage slash command in the clai CLI prints cumulative token usage for a session — handy while you're iterating interactively. The one that matters in production is RunContext.usage_limits: the run's budget is now visible inside your tools. A tool can look at how much it has already spent, look at the ceiling, and decide to do less. That's the difference between an agent that crashes at the limit and one that lands softly under it.
Set a hard ceiling on the run#
Start with the blunt instrument: a UsageLimits passed to agent.run (or run_sync). This is the safety net — when a limit would be crossed, the run raises UsageLimitExceeded instead of quietly spending more.
from pydantic_ai import Agent
from pydantic_ai.usage import UsageLimits
agent = Agent("openai:gpt-5.6", system_prompt="Be concise.")
result = agent.run_sync(
"Summarize the incident report and list action items.",
usage_limits=UsageLimits(
total_tokens_limit=100_000, # input + output, per run
tool_calls_limit=8, # stop a tool that keeps re-calling
request_limit=25, # backstop the loop (default is 50)
),
)
print(result.output)
The checks fire at two different moments, and the distinction matters when you're debugging a limit that tripped:
request_limitandtool_calls_limitare checked before the next request or tool call — the agent never makes the call that would cross the line.request_tokens_limit,response_tokens_limit, andtotal_tokens_limitare checked after each response, because the token count comes back from the model.
The full set of knobs, and when to reach for each:
The real upgrade: tools that can see the budget#
Before v2.9.0, if a tool wanted to behave differently when the budget was nearly spent, you had to hand it its own copy of the limits — a second source of truth that drifts from what the run is actually enforcing. Now the run's UsageLimits is on the RunContext, so the tool reads the same ceiling the run is already checking.
Give the tool a RunContext as its first parameter and combine two things: ctx.usage (what's been spent) and ctx.usage_limits (the ceiling).
from pydantic_ai import Agent, RunContext
agent = Agent("openai:gpt-5.6")
@agent.tool
def deep_research(ctx: RunContext, query: str) -> str:
limits = ctx.usage_limits
used = ctx.usage # tokens and tool calls spent so far this run
# During a real run usage_limits is always set; it's only None on a
# bare/synthetic context. Compute how much room is left.
budget = limits.total_tokens_limit
if budget is not None:
remaining = budget - used.total_tokens
if remaining < 20_000:
# Not enough headroom for a full pass — degrade instead of crashing.
return quick_lookup(query)
return full_multi_source_research(query)
A budget your tools can't see is a budget they can only blow. The fix isn't a bigger limit — it's letting the expensive code path check the gauge before it floors it.
This is the pattern that pays off in multi-agent and RAG setups, where one tool call can fan out into many model requests. Instead of a hard UsageLimitExceeded at the worst possible moment — mid-answer, with nothing to show the user — the tool notices the tank is low and returns a shorter, cheaper result. The run finishes. The user gets something. (For where the token cost actually accrues in agent memory specifically, we went deep in agent memory: read vs write token cost.)
Watch it live from the CLI#
While you're developing, the new /usage command in clai gives you the running total without wiring up any instrumentation:
clai
> summarize the changelog and open a PR
> /usage
# prints cumulative token usage for the session
It's a small thing, but it collapses the feedback loop: you see the cost of a prompt immediately, next to the prompt, instead of reconstructing it from provider dashboards an hour later.
Then upgrade to v2.9.1 — it's a patch, not a feature#
v2.9.1 landed three days later (July 13) and it's a security release, so treat it as patch-now rather than optional:
- Security: bumps
soupsieveto 2.8.4 to fix a ReDoS (CVE-2026-49477). - Bug fixes: typed-composition recursion in the JSON schema transformer, and integer timeout handling for
MistralModel.
Separately, v2.9.0 carried advisory GHSA-jpr8-2v3g-wgf9 (CWE-863) in the AG-UI adapter's UIAdapter.sanitize_messages — the dangling-tool-call strip. If you expose the AG-UI adapter to untrusted client history, read that one and make sure you're on a fixed line. (We covered the underlying risk in sanitizing untrusted client message history.)
What to do this week#
- Upgrade to v2.9.1 — it's a security patch, not a judgment call.
- Put a
total_tokens_limitand atool_calls_limiton every agent that runs unattended. The defaults (request_limit=50, everything else unbounded) are a loop backstop, not a budget. - Find your one expensive tool — the research call, the sub-agent spawn, the big retrieval — and give it a
RunContextso it can checkctx.usageagainstctx.usage_limitsand degrade instead of dying.
The framing that changed in v2.9 is worth internalizing: the budget stopped being a wall the agent runs into and became a number the agent can plan around. Cheaper, and a lot less likely to strand a user mid-answer.



