The agent that cannot stop, and the invoice that follows.
One of these you have imagined. The other you have probably paid. They are the same problem: nothing was counting.
Rules only, so changing one needs no release.
The whole control
# Without exists, an undeclared ceiling reads as zero and refuses everyone.
rules:
record_agent_spend:
all:
- path: "event.type"
op: eq
value: "llm.completed"
- path: "event.data.cost_nano"
op: exists
- path: "event.data.agent.id"
op: exists # no agent, no bucket to add it to
effects:
verdict: approved
priority: 1
state_changes:
agent:
change_buckets:
spent_hour: "{{ event.data.cost_nano }}"
spent_day: "{{ event.data.cost_nano }}"
agent_spend_ceiling_day:
all:
- path: "event.type"
op: eq
value: "llm.input"
- path: "event.data.agent"
op: exists
- path: "event.data.agent.ceiling_day"
op: exists
- path: "state.agent.buckets.spent_day"
op: gte
value: "{{ event.data.agent.ceiling_day }}"
effects:
verdict: rejected
priority: 100
response:
reason: "Refused. This agent has reached the ceiling declared for it today." The numbers are not in the rule. The ceiling lives on the agent record, because a ceiling changes per agent and per week and is changed by a finance or security person. The logic — over the ceiling, stop — never changes. Put the number in the versioned rule and changing one agent's budget becomes a ruleset release.
The decision is named and versioned, it can be shadow-tested and backtested, it is audited, and it changes per customer without a release. A hundred lines of gateway code gives you none of that.
What you can count
Calls per agent per hour. Dollars per tenant per day. Tool invocations per task. Retries against the same failing endpoint. You choose what is counted, and against what.
The limit does not have to be a hard stop: slow the agent down, route the next call to a person, drop to a cheaper model, or refuse. One agent can carry several limits: warn at half, hand to a human at three quarters, stop at the ceiling.
How the accounting works
The counter sits in the path of the call, so a ceiling stops the next one. A call already running completes: its cost is recorded when it closes, a moment later.
On an endpoint where someone you do not control supplies the calling code, this is a spend control over a caller who cooperates, not a security control over a hostile one. For a hostile caller the control is authorization.
Why the loop is the dangerous half
A runaway agent is rarely one expensive call. It is a cheap call repeated faster than anyone is watching, and what usually ends it is a bill or a downstream service that starts refusing. The counter that stops it does not need to be clever. It needs to be in the path of the call rather than on a dashboard someone reads on Monday.