Per-user budget caps
A budget caps how many tokens, how much money and how much wall-clock time an agent may spend. Set one before real users can reach the agent, so a runaway loop or a single heavy user can’t run up an unbounded bill.
StandardBudget tracks usage both globally and per user_id. The
global caps catch runaway aggregate usage; the per-user caps stop one
tenant from exhausting another’s quota.
from datetime import timedelta
from loomflow import Agent
from loomflow.governance.budget import BudgetConfig, StandardBudget
budget = StandardBudget(BudgetConfig(
# Global: applies to all users combined.
max_tokens=10_000_000,
max_cost_usd=100.0,
max_wall_clock=timedelta(hours=1),
# Per-user: applies to each user_id's bucket independently.
per_user_max_tokens=100_000,
per_user_max_cost_usd=2.0,
per_user_max_wall_clock=timedelta(minutes=10),
soft_warning_at=0.8, # warn at 80% of any cap
))
agent = Agent("...", model="claude-opus-4-7", budget=budget)A run is blocked when either its user’s cap or the global cap is
exceeded, whichever is hit first. A blocked run comes back with
result.interrupted = True and
result.interruption_reason = "budget:per_user_max_tokens" (or
whichever cap was hit, such as "budget:max_cost_usd").
There are also input- and output-only token caps: max_input_tokens,
max_output_tokens, per_user_max_input_tokens and
per_user_max_output_tokens.
How the wall clock counts
max_wall_clock and per_user_max_wall_clock count time spent
running, not time since the budget was created. The agent wraps
each run in budget.running(user_id=...), and the clock only ticks
while at least one run is in progress, so an agent that sits idle
between requests isn’t blocked for it. Concurrent or nested runs (a
sub-agent inside a run) count once. With the caps above, alice is
blocked after ten minutes of her runs in total, however they’re spread
over the day.
To cap a single run, use Agent(timeout=...) instead; see
Wall-clock timeout.
If you drive the budget yourself (calling allows_step / consume
outside an agent run), wrap the work in running() so it counts:
async with budget.running(user_id="alice"):
status = await budget.allows_step(user_id="alice")
if not status.blocked:
... # do the work
await budget.consume(tokens_in=120, tokens_out=40, cost_usd=0.001, user_id="alice")Inspecting usage
Inspect a single user’s running totals at any time:
usage = budget.usage_for("alice")
# {"tokens_in": 1240, "tokens_out": 880, "tokens_total": 2120,
# "cost_usd": 0.0042}The agent loop passes each run’s user_id to the budget
automatically; you don’t need to thread it manually. Per-user state
is bounded by default (100k users, 24-hour idle TTL); see Bounded
in-process state.