Skip to main content
An agent loop can spend money without bound. Each iteration makes an LLM call, each call costs, and a loop that fails to converge repeats until something stops it. AgentArea bounds three separate currencies: inference cost in the billing currency (see Currency), service cost in USD for payments an agent makes on your behalf, and tokens. Every ceiling is a policy dimension, resolved through the same monotonic merge as everything else, so a lower scope can only tighten what a higher one set.

The problem

Budget enforcement fails in two directions. Enforced only at admission, a budget stops new tasks and does nothing about the one that is currently looping. Enforced only inside the loop, a workspace that has already blown its monthly ceiling keeps accepting work as long as each individual run is small. The second problem is arithmetic. The loop tracks spend to decide whether to keep iterating; the per-call gate tracks spend to decide whether to allow the next call. If those two read different numbers, the one that is easier to satisfy is the real limit, and the other is decoration.

The dimensions and their ceilings

They are written as cap rules. target: spend with params.period of month or run sets the spend caps: a monthly rule bound to the workspace caps the workspace, the same rule bound to an agent caps that agent’s own spend, and an agent cap above the workspace cap is refused; target: service sets the service cap; target: tokens sets both token values. A cap rule missing its amount is skipped and logged rather than treated as zero. The default baseline for a new workspace is 500.00 USD per month, 50.00 USD per run, 20,000,000 total tokens and 100,000 tokens per call.

Currency

Inference amounts — task costs, monthly_spend_cap_usd and run_budget_usd — are in the deployment’s billing currency, despite the _usd suffix. The suffix is kept for API compatibility. The platform converts each LLM call once, when the call returns, through the customer_pricing extension, and every total and budget check after that reads the converted amount. Without the extension the billing currency is USD and the amount is the provider cost, so an OSS deployment behaves as the field names say. GET /v1/pricing/currency returns the code. Service spend is outside this: it stays in USD, because the payments it bounds are made in USD.

Where each is enforced

Monthly caps: the spend ledger

Month-to-date spend is summed from the spend ledger, cost_entries: one row per paid line — an LLM call, a compaction, a platform model a tool ran, a payment a tool made from the agent’s wallet — with the task, the agent, what was bought and the evidence it was priced from. Rows are written from the run’s persisted events as they are stored, so a running task’s spend counts as it happens. Wallet payments are recorded in USD and count towards the caps only where USD is the billing currency. When a task is created or continued, TaskService._enforce_budget_cap compares the workspace’s month-to-date spend with monthly_spend_cap_usd and the agent’s own with agent_monthly_spend_cap_usd, and raises BudgetCapExceededError when either has been reached. No cap configured means no check. The rejection reaches a client as HTTP 402 in problem+json, with code: budget_cap_exceeded, current_mtd_usd, cap_usd, workspace_id and, for an agent cap, agent_id. A run reads both again when it starts and whenever it resumes. The agent cap is also checked after every paid call: once the agent’s month-to-date spend plus what the run has spent since reaches the cap, further tool calls are denied and the run stops before its next model turn. The cap can be overshot by the call that crosses it, and by what other runs of the same agent spend between two of this run’s checks.

Run budget: two enforcement points, one number

The per-task USD budget has two enforcement points, and they are reconciled deliberately. resolve_effective_budget takes the minimum of the budget on the task request and the policy’s run_budget_usd. That single number configures the workflow’s BudgetTracker, which decides whether the loop continues, and the same policy value reaches CostBudgetGuard through the flattened execution_state. Neither source can loosen the other. CostBudgetGuard runs at both pre_llm_call (priority 100) and pre_tool_call (also 100). It compares consumed cost against the budget: WARN at 80 percent, DENY at 100 percent. A denial at the activity boundary raises GovernanceDenied, which fails the activity. If nothing is configured at all, BudgetTracker falls back to a default of 10.00 USD per run, and its warning threshold is 80 percent.

Service budget

Service spend — payments an agent makes through a paid tool — is tracked separately from inference cost. ServiceBudgetGuard runs at pre_tool_call (priority 105) with the same thresholds: warn at 80 percent, deny when exhausted. BudgetTracker maintains the matching counters in the workflow, where a limit of zero or less means unlimited.

Tokens

TokenBudgetGuard runs at pre_llm_call (priority 110), warning at 85 percent of max_tokens and denying at 100 percent.

How the numbers reach the gates

The gates do not read policy objects. The effective policy is flattened into a plain dictionary — budget_usd, service_budget_usd, max_tokens, tools_config, escalation_rules, content_safety — and the runtime counters the activity request carries (cost_used, service_cost_used, tokens_used) are merged into it before each phase runs. Money crosses that boundary as a float, on purpose. The gates do ratio arithmetic against running counters, and mixing Decimal with float there would raise inside the gate — where the pipeline swallows the exception and the guard would silently stop guarding. Authoritative money accounting stays in Money (Decimal) in BudgetTracker and upstream of it.

Why not a central quota service

The obvious alternative is one counter service that every component increments and consults, so there is exactly one number. AgentArea does not have one, for a structural reason: the loop check and the call check are asked at different times and need different guarantees. The loop decision has to be deterministic. It runs inside a Temporal workflow, which replays its history on recovery, so it cannot make a network call to a counter service — the answer would differ between the original run and the replay and break determinism. The per-call decision runs inside an activity, where IO is allowed but where the value must already be in the request for the interceptor to see it. The design that survives that constraint is: resolve one ceiling, carry it in the snapshot, and have both points enforce the same number. What it costs is that counters are per-task and local. There is no cross-task, real-time spend counter, which is why the monthly cap is checked at admission against the database rather than continuously.

Limits

  • max_tokens_per_call is configured and never enforced. It is a field on the token policy, it is merged monotonically across scopes, it is in the default workspace baseline at 100,000, and it appears in the API. It is not written into execution_state and no gate reads it. A single call may exceed it freely.
  • The monthly cap is admission-only. A task that starts under the cap runs to completion regardless of how far past the cap the workspace goes, and concurrent task creation can cross it — the check is a read-then-decide with no lock.
  • A budget denial fails the activity. CostBudgetGuard denying at pre_tool_call raises GovernanceDenied rather than returning a message the model can respond to. The graceful path — the loop noticing exhaustion and completing — comes from BudgetTracker, not from the gate.
  • A guard that crashes stops guarding. The pipeline catches exceptions from an interceptor, logs the traceback and moves on. Failure of a budget gate is fail-open for that dimension.
  • Unconfigured means unlimited. Each guard allows unconditionally when its ceiling is absent or not greater than zero. A workspace whose default policies were removed has no token ceiling and no service ceiling.
  • Cost figures depend on the provider. Per-call cost is read from the LLM response where the provider supplies it, and estimated at a flat rate per token when it does not. A budget is therefore as accurate as the upstream cost reporting.
  • There are no rate or concurrency quotas. Core bounds spend and tokens. It does not bound requests per second, concurrent tasks per workspace, or tool calls per minute. Anonymous or public execution has no quota of its own.
  • Nothing writes budget events to the audit log. Budget warnings and denials appear in the task event stream and in logs, not in audit_events. See audit.

The policy engine

How ceilings merge and where the snapshot comes from

Tool authorization

The other gates sharing the pre-tool phase

Audit

What is and is not recorded
Last modified on October 12, 2026