Govern & operate
Budgets & cost
Agents spend money on every turn, and a looping graph or a public embed can spend a lot of it quickly. Caps are the control that turns a bad day into a stopped run.
What actually costs money
Model calls, billed per token in and out. Everything else is rounding. The things that make it add up faster than people expect:
| Driver | Why it costs |
|---|---|
| Retrieved context | Every retrieved chunk is input tokens on every turn that carries it. |
| Conversation history | A long chat resends its history each turn — cost grows with the conversation. |
| Tool loops | Each tool round trip is another full model call with the transcript so far. |
| Swarm fan-out | Parallel branches multiply calls; a loop node multiplies them again. |
| Deep document generation | A large deck is a big plan plus a render-verify vision pass. |
| Public embeds | Unbounded strangers, at your expense — the case that most needs a cap. |
Setting caps
There are two separate mechanisms, and it is worth knowing which one you are setting.
Per-user cap
| Property | Value |
|---|---|
| Stored in | budget_settings.monthly_cap_usd |
| Scope | One person's own spend, across everything they do |
| Period | Calendar month |
| Default | A very high number — effectively unlimited until you set it |
Scoped caps
Set in Observe → Budgets, and per group in Admin → IAM → Budgets. Three scopes exist, enforced by a database constraint:
| scope_type | Applies to | Why you would set it |
|---|---|---|
group | Everyone in an IAM group, shared | A team's combined monthly ceiling |
embed_key | One public embed placement | The most important one to set — unbounded strangers, at your expense |
swarm_api_key | One integration | Bounds a retry storm in someone else's code |
| Field | Type | Notes |
|---|---|---|
monthly_cap_usd | numeric(10,2) | Must be greater than zero. A calendar-month ceiling in USD. |
is_active | boolean, default true | Turn a cap off without deleting it. |
Note
Enforcement is opt-in
Caps only block when ENFORCE_BUDGET_CAP is set on the deployment (accepted values: 1, true, yes). Without it they still track and alert, but every call proceeds.
Why it works this way
monthly_cap_usd ships at a very high value that nobody chose. Enforcing it on upgrade would have started refusing model calls on instances whose cap was never meant to bite. Turn it on deliberately once your caps reflect reality — and do turn it on before exposing a public embed.The check fails open — unless you say otherwise
Set
BUDGET_FAIL_CLOSED=true to invert that and have an unknown figure refuse the call instead. Either way the failure is logged; the difference is whether an unreadable number is treated as "probably fine" or "not proven safe". Public embeds are the usual reason to choose the second.Unknown is not zero
Worked example: a support bot on a public site
The shape that costs people money unexpectedly — an embed anyone can use, spending your credits, with no login to rate-limit against. Here is the whole configuration, in the order it wants doing.
- 1
Turn enforcement on before the embed is public
SetENFORCE_BUDGET_CAP=trueandBUDGET_FAIL_CLOSED=trueon the deployment, then restart. Without the first, caps only alert. Without the second, a spend query that times out reads as "not proven over" and the call proceeds. - 2
Give yourself a personal ceiling
Observe → Budgets, setmonthly_cap_usdto a figure you would genuinely be unhappy to exceed. This is the backstop, not the control — everything you own counts against it, including your own testing. - 3
Cap the embed key itself
This is the one that matters. A scoped cap on theembed_keybounds what that one placement can spend, whoever is using it — so a leaked key, or a bot that discovers the widget, drains a number you chose rather than your whole allowance. Start low; you can raise it once you have a week of real traffic. - 4
Set alert thresholds below the cap
Thresholds fire once each per calendar month, at the highest one crossed, so a busy afternoon does not produce a stream of mail. Pick values that leave you time to act — 50 and 80 give you warning; 95 mostly tells you it already happened. - 5
Check the attribution after a day of traffic
In Analytics, confirm the embed's calls are landing against the key and not just against you. If they are not scoped, the per-key cap has nothing to measure and only the personal cap is holding.
A cap is a ceiling, not a brake
How a number becomes a cost
Every figure on this page is an estimate the platform computes, not an invoice line from your provider. Knowing the two places it can drift keeps you from reconciling the wrong thing at month end.
Tokens: measured, or approximated
When a provider returns a usage block, those counts are used verbatim. When it does not — some streaming responses, some gateways — the text is approximated at roughly one token per 3.8 characters, and the trace is marked tokens_estimated so you can tell the two apart in Analytics.
Price: resolved per provider, per model
The same model does not cost the same everywhere — a gateway adds a margin, a cloud has its own rate card — so price is looked up by provider and model together, in this order:
| Layer | Where it comes from | Why it wins |
|---|---|---|
| Operator override | Set by an admin | Committed-use and enterprise-agreement rates are not list price, and no public source knows yours |
| Synced catalog | Public price data vendored into the repo | Broad coverage, reviewed in version control rather than fetched at runtime |
| Bundled table | Ships with the app | Keeps an air-gapped install pricing its calls |
| Self-hosted | Ollama, vLLM | Runs on hardware you already pay for — a known zero, not an unknown one |
A model nobody has priced counts as $0
pricing_missing. It still appears in your usage; it contributes nothing to a cap. Filter for that flag before trusting a monthly total, and add an override for anything that shows up.Note
Reducing spend
- Use smaller models for mechanical steps. Routing, classification and extraction rarely need a frontier model — and in a swarm, each node picks its own.
- Retrieve less. Fewer, better chunks beat many mediocre ones on both cost and accuracy.
- Shorten conversations. Memory summarisation exists so a long chat doesn't resend everything forever.
- Cap loop iterations in swarms. An unbounded loop is the classic runaway.
- Import rather than direct-query for dashboards many people open.
- Check Analytics for the top spender. It is usually one agent or one integration, not a broad increase.
Retention
Cost isn't the only thing worth bounding. Three retention windows are configurable, and each is enforced by a scheduled purge:
| Data | Where to set it | Default |
|---|---|---|
| Chat history and generated documents | Agent Builder → Memory (per agent) | 7 days (minimum 7; can be increased) |
| Embed transcripts | Per embed key | 30 days |
| Audit events | Retained long-term, with export | 365 days |
When chat history is purged, the generated documents stored with it are deleted from storage too — the file doesn't outlive the conversation that produced it.
Note
Alerts
A budget warns before it stops anything — a hard stop at 100% with no warning is how a team finds out by being blocked. Thresholds default to 50%, 75% and 90%, and each one emails once per month: the check runs after every model call, so "notify on crossing" has to mean once. Crossing 100% sends its own message, tracked separately so a 90% warning cannot suppress it.
| Scope | Who is emailed | Default |
|---|---|---|
| Personal cap | The person whose budget it is | On — see Observe → Budgets |
| Group cap | Superadmins — the people who can raise it | Off — enable per group under Admin → IAM → Budgets |
A team alert goes to admins, not to the team
Alerting and enforcing are separate switches
Attribution
Every model call is recorded with the user, agent and credential that caused it, so spend can be traced to a person, a swarm, an embed key or an API key. See Analytics & audit.