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:

DriverWhy it costs
Retrieved contextEvery retrieved chunk is input tokens on every turn that carries it.
Conversation historyA long chat resends its history each turn — cost grows with the conversation.
Tool loopsEach tool round trip is another full model call with the transcript so far.
Swarm fan-outParallel branches multiply calls; a loop node multiplies them again.
Deep document generationA large deck is a big plan plus a render-verify vision pass.
Public embedsUnbounded 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

PropertyValue
Stored inbudget_settings.monthly_cap_usd
ScopeOne person's own spend, across everything they do
PeriodCalendar month
DefaultA 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_typeApplies toWhy you would set it
groupEveryone in an IAM group, sharedA team's combined monthly ceiling
embed_keyOne public embed placementThe most important one to set — unbounded strangers, at your expense
swarm_api_keyOne integrationBounds a retry storm in someone else's code
FieldTypeNotes
monthly_cap_usdnumeric(10,2)Must be greater than zero. A calendar-month ceiling in USD.
is_activeboolean, default trueTurn a cap off without deleting it.

Note

Where several caps apply, the most restrictive wins. A user with a $50 personal cap who belongs to a group capped at $20 is limited to $20.

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

The default is off because 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

Cap evaluation happens before a call is dispatched. If the spend lookup itself errors, work continues by default rather than the platform bricking itself over an accounting question — enforcement is a cost control, not a security boundary, and an outage in it should not take down your agents.

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

A failed lookup is recorded as unknown, never as $0 spent. That distinction is the whole reason the setting above can exist: for a while both outcomes produced the same number, so a slow query silently read as "nothing spent yet" and every cap passed.

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. 1

    Turn enforcement on before the embed is public

    Set ENFORCE_BUDGET_CAP=true and BUDGET_FAIL_CLOSED=true on 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. 2

    Give yourself a personal ceiling

    Observe → Budgets, set monthly_cap_usd to 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. 3

    Cap the embed key itself

    This is the one that matters. A scoped cap on the embed_key bounds 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. 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. 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

Enforcement is checked before a call is dispatched, and month-to-date spend is cached briefly, so the last few calls before a cap engages can carry you slightly past it. Size the cap as "the most I am willing to lose", not "the exact amount I will be billed", and pair it with rate limits — see Embedding.

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:

LayerWhere it comes fromWhy it wins
Operator overrideSet by an adminCommitted-use and enterprise-agreement rates are not list price, and no public source knows yours
Synced catalogPublic price data vendored into the repoBroad coverage, reviewed in version control rather than fetched at runtime
Bundled tableShips with the appKeeps an air-gapped install pricing its calls
Self-hostedOllama, vLLMRuns on hardware you already pay for — a known zero, not an unknown one

A model nobody has priced counts as $0

If none of those layers knows a model, the call is recorded with real tokens and a cost of zero, and the trace is flagged 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

Historical rows keep the price that applied when they were written. A vendor changing their rate does not silently rewrite last quarter's spend — which is what you want for an audit, and what to remember when a figure disagrees with today's price sheet.

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:

DataWhere to set itDefault
Chat history and generated documentsAgent Builder → Memory (per agent)7 days (minimum 7; can be increased)
Embed transcriptsPer embed key30 days
Audit eventsRetained long-term, with export365 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

Retention is a privacy control as much as a storage one. The shortest window that still serves your purpose is the right answer, particularly for public embeds where strangers type personal details into a chat box.

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.

ScopeWho is emailedDefault
Personal capThe person whose budget it isOn — see Observe → Budgets
Group capSuperadmins — the people who can raise itOff — enable per group under Admin → IAM → Budgets

A team alert goes to admins, not to the team

Only a superadmin can raise a group cap, so they are the people who can act on the warning. Mailing every member would also publish the team's total spend to the whole team, which is a disclosure decision nobody made when they set a budget. If you want members notified, that should be a deliberate choice rather than a side effect of capping them.

Alerting and enforcing are separate switches

A group can be warned without being blocked, and blocked without being warned. That is what makes it safe to introduce a cap: turn alerts on first, watch a month of real spend against the number you guessed, and enable enforcement once you know the guess was right.

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.