Guide ยท 7 min read
{
"action_type": "tool.invoke",
"resource": "api.openai.com",
"cost_cents": 900,
"currency": "USD"
}{
"outcome": "denied",
"rule": "run_budget_exhausted",
"headroom": { "budget_remaining_cents": 0 }
}It gets the same answer the first one would have if the first one had spent the envelope. That is what shared means.
Why the usual answers stop short
| What people use | What it does not cover |
|---|---|
| A hard cap at the model provider | Holds for that provider. Knows nothing about the other three, the search API, the scraper, or the payment the agent is about to make. Your exposure is the sum and nothing sums it. |
| A limit in the orchestrator | Real enforcement, and it lives inside the process it is supposed to constrain. Whatever compromises the agent compromises the check, and changing the number needs a deploy. |
| A virtual card with a ceiling | Stops one transaction that is too big. Does not see the same charge repeated nineteen times, and never sees the paid API call that is not a card transaction at all. |
| A cost dashboard with alerts | Tells you what happened. By the time the chart moves the money is spent. Observability is not control. |
| A prompt instruction | Asks the agent to police itself. Useful as a hint, worthless as a boundary. |
The longer versions: versus an if statement in your orchestrator and versus card limits.
What a real limit needs
In practice
{
"mode": "allowlist",
"rules": [
{ "kind": "spend_window", "max_cents": 25000, "window_seconds": 86400 },
{ "kind": "rate_limit", "max_actions": 200, "window_seconds": 60,
"action_types": ["tool.invoke"] },
{ "kind": "cost_cap", "max_cents": 5000,
"action_types": ["payment.create"] },
{ "kind": "condition", "effect": "require_approval",
"rule_name": "unknown_vendor",
"when": { "not": { "field": "resource", "operator": "in",
"value": ["api.openai.com", "api.anthropic.com"] } } }
]
}An unknown rule kind or a missing setting is rejected when you save it, not when an agent is waiting on it at three in the morning.
The same engine governs non-AI automation, which is usually where teams get their first measurable win:refund and payout authority. If you want the record of what was spent as well as the rule about what may be, that is the ledger.
Questions
No, keep them. They catch things a spend layer never sees, like what the model is about to say. The difference is that a framework hook holds no budget across runs and cannot produce evidence that anything was authorized.
One call before the action, and a simulate call when you want a dry decision that reserves nothing. Design the check into the path you already have rather than adding a round trip per token.
You choose: fail closed and the agent stops, or fail open and you accept the exposure for that window. The important part is choosing deliberately rather than discovering the answer during an incident.
That is the point of using dotted action types. tool.invoke and payment.create draw down the same envelope, so spend on one consumes headroom for the other.
A control layer only binds the paths you route through it. An agent holding a credential that reaches a rail directly is not governed by anything here, which is why the signed decision matters: make the executor demand it.