Skip to Content
ProductionPer-user budget caps

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.

Last updated on