HARD CEILINGS

Cap what an AI agent can spend before it spends it

Refused before egress, priced against everything the route could reach, never estimated at zero. The head of IT explaining a four-figure line that arrived six weeks late A retry loop or a promoted model turns an ordinary week into an invoice nobody budgeted for. You find out from the vendor, and the control everybody assumed was on turns out to have been a chart.

Token Observe decides the money before the request leaves your network. Four optional USD ceilings per governed subject (per request, per rolling hour, per UTC day, per UTC month) sit alongside per-minute ceilings on requests, tool calls and tokens, and a call that would cross one is refused with a typed error.
USD ceilings
Per request, rolling hour, UTC day and UTC month
Rate ceilings
Requests, tool calls and tokens per minute
Unpriced route
Refused with a 409 before egress, never priced at zero
Concurrency
One per-agent transaction; in-flight traces count
What a hard ceiling costs youOne billable egress: no retry, no failover

A dashboard tells you what you spent after you spent it.

The control a finance owner is asking for is the one that refuses the call, and that means holding a defensible price before it leaves the building. That is a harder problem than drawing the chart, which is why most of the market stops at the chart. A token quota is no substitute: it bounds volume, and volume and money diverge exactly when it matters.

4 steps, in this order.

Start to finish
Step 1

Give each agent a record

Ceilings bind a governed subject, so every agent needs a registry row and a named owner. The gateway resolves it per call, so an edit applies without a redeploy.

then
Step 2

Populate the price table

The 50 shipped rows load additively at boot. Check every model your agents can reach has one, fallbacks included.

then
Step 3

Set the ceilings that match the failure

Per request catches one absurd call, hourly a retry loop, daily and monthly the drift. Day and month start at 00:00 UTC.

then
Step 4

Decide what happens when it bites

A refusal is a 429 with a typed reason and a recorded blocked trace, and nothing partially executes.

See how budgets are checked against every reachable fallback.

See how a ceiling is enforced

Built for

  • Agents you can point at a new base URL, whose model traffic arrives at the gateway first.
  • A price table you have checked covers every model those agents can reach, fallbacks included.
  • Anyone who would rather a call was refused than reported, and can accept the availability trade.

Wrong for

  • Anyone who wants a ceiling on Copilot, ChatGPT or Cursor seats. A seat is priced per seat, so a per-request ceiling is inert.
  • Anyone who needs retry and failover underneath a hard ceiling. A call under one gets one potentially billable attempt.
  • Anyone who wants a team-level or fleet-level budget pool. Ceilings bind one governed subject at a time.

One thing on the record

The shipped price rows once loaded only under the demo seeder, which the documentation tells production operators not to run, so the documented install started empty, and every per-request ceiling admitted every request while the console displayed it.

Rows now load additively at boot, and a budgeted agent whose route reaches an unpriced model is refused with a 409 before egress.

Where this is published

The money verdict is taken last, and priced against everything reachable.

USD is skipped in the first pass for any agent with a ceiling, because money cannot be decided until routing has fixed which upstream will serve the call. The primary target and every fallback are then priced against the resolved model rather than the name the caller typed; otherwise a rerouted target is a zero-dollar escape hatch.

perRequestUsd
Refuses one call whose conservative estimate exceeds the ceiling. Inert on a subscription seat, which prices the seat rather than the request.
hourlyUsd, dailyUsd, monthlyUsd
A rolling sixty minutes, the UTC day and month. Each blocks when the window is at the limit and when the projection would cross it.
The unpriced refusal
A 409 before egress naming the model, the provider that would have served it, and how many fallbacks are also unpriced.
Long cache writes
Anthropic and Bedrock bill a five-minute cache write at 1.25 times input and an hour at 2 times. Any other TTL prices at twice input.
one request, one verdict, four ways out

Every check lives in one function, so you can read your rules and know what they do.

one request from a governed agent

POST /v1/chat/completions

The key resolves to one registered agent with a named owner, and the trace is opened before anything can refuse the request.

one evaluator, no input or output of its own

Kill switches, then lifecycle, then deny-by-default permissions across every delegation hop, then rate limits, then your policies by priority.

returns: allow · block · require_approval  —  plus a redaction plan

A spend ceiling is the deliberate exception: it is deferred until routing has fixed which provider and model will serve the call, so it is tested against the price you will pay.

allow
Forwarded as sent

Routed to the provider you configured, on your key. Metering runs last and the trace closes with a terminal status.

200
allow + redaction plan
Forwarded, values masked

The plan is applied to the outbound payload and a redaction event records kinds and counts, never values. A redaction that cannot be applied faithfully becomes a refusal instead.

200
require_approval
Held for a named person

An approval bound to a hash of this exact payload, single-use and expiring. The caller receives the approval id and the header to resume with.

403 ACP_APPROVAL_REQUIRED
block
Refused before egress

429 ACP_BUDGET_EXCEEDED or ACP_RATE_LIMITED where a ceiling decided it. The trace closes as blocked and is kept as thoroughly as one that went through.

403 ACP_POLICY_BLOCKED

Verdict names and status codes are the product’s own. Every rule can run in shadow mode first, where the decision is recorded and the request is not changed. That is how you learn your false-positive rate before you refuse real work.

A parked request holds its money until the decision ends.

Forget the estimate while somebody deliberates and an agent queues a thousand expensive calls past its ceiling; hold it forever and a denied approval counts against that budget until someone edits the database by hand. So the reservation lasts exactly as long as the decision, and every terminal path releases it in the same transaction as the state change that ended the wait.

4 things this still does not solve.

  • There is no team-level or fleet-level budget pool. A team figure is the sum of the per-agent ceilings that exist, beside a count of the agents that have none.
  • Nothing is reconciled against a vendor invoice. Every figure is metered against price rows you hold, and a shipped row is a list price captured on a date that will go stale.
  • A hard ceiling costs you retry and failover, because a timeout cannot prove the vendor did not complete and bill the call.
  • Token Observe cannot bound spend it never sees. A vendor credential used directly is a discovery problem before it is a budget one.

Check the product limits alongside the job you want to do.

What we do not claim

Questions

What happens when an agent hits its monthly ceiling mid-conversation?

The next call is refused before it reaches a provider, with a 429 naming the limit type, the configured value and the actual figure. Nothing partially executes and nothing is billed, and the attempt is still recorded as a blocked trace. A budget warning is published at 80 per cent of any window and a budget-exceeded event at 100 per cent.

Can two concurrent requests both slip past the same cap?

No. The USD admission runs inside one per-agent transaction that reads the windows, tests the projection and writes the reservation before releasing the lock, and in-flight requests count: a running trace contributes the greater of its billed cost and its reservation.

What happens if a model has no price row?

For an agent with any USD ceiling, the request is refused with a 409 before egress, naming the unpriced model and how many fallbacks are also unpriced. The alternative was tried and was worse: an unpriced model produced a zero estimate, which silently disarmed every per-request ceiling.

Your next step

Describe the version of this you have.

Say what your estate contains and which part of it worries you, and the reply will say where to start.