You give an agent work by giving it limits. A sentence in a system prompt is advice the model can ignore. A cap proven at deploy is a write that gets rejected. Only one of those lets you go to sleep.
The usual way to keep an AI agent in line is a paragraph in a system prompt. "Only refund up to $50. Never delete production data. Be careful with customer records." I have written those paragraphs. They do not do what you want, and the reason is simple: a system prompt is advice, and the model is allowed to ignore advice. You give an agent real work by giving it real limits.
A sentence is not a limit
A system prompt is the model's suggestion box. On a good day it follows it. On a bad day, a long context, a clever user, a confusing tool result, it does not, and there is no layer underneath that stops it. The instruction and the enforcement are the same string, which means there is no enforcement.
The clearest public example is still Replit in July 2025: an agent deleted a production database, more than 1,200 executives' records, during a code freeze. The CEO's line was that it "should never be possible." Look at what the fix was. They did not write a firmer prompt. The remediation was hard infrastructure controls. When something must not happen, you do not ask nicely. You make it impossible.
Access is not magnitude
The industry has plenty of tools for agent safety, and almost all of them answer the same question: which tools may this agent call. Scopes, allow-lists, OAuth brokers, approval routers. All useful. But "which tool" is not "how much." A scope can say the agent may call the refund tool. It cannot say "refund up to $49." In one access vendor's own words, OAuth grants narrowly-scoped permissions, and a scope cannot express a dollar amount.
Even the rails that do enforce amounts are looser than they look. Stripe's spend controls are real, but Stripe's own docs say the aggregation is best-effort with up to a 30-second lag, and post-hoc adjustments can breach the limit. Thirty seconds is a long time for an agent running in a loop.
A limit is a write that gets rejected
In Bounded you declare the cap once, in policy.json, next to the data. Amounts in cents, so this is a $500-per-day refund ceiling, per agent:
policy.json
"refunds/$agentId/$refundId": {
"fields": { "amount": "UInt" },
"tier": "durable",
"rules": {
"read": "@user.id != null",
"create": "@user.id != null",
"update": "false",
"delete": "false"
},
"invariants": [
{ "type": "rollingSum", "name": "refund_daily_cap",
"field": "amount", "windowSeconds": 86400, "limit": 50000,
"scopeVariable": "$agentId" }
]
}
That is proven at deploy against every possible sequence of writes, then enforced atomically on every write at runtime. When the agent tries to exceed it, the write returns 409 and nothing happens:
terminal
agent: issue_refund $49 ✓ committed (day: $49 / $500)
agent: issue_refund $12,000 ✗ 409 refund_daily_cap
Not "the model decides not to." The refund does not occur, because the transaction that would have recorded it was refused. That line is three seconds to read, and it means the same thing whether the agent is well-behaved, jailbroken, buggy, or replaced next quarter by a different model. The boundary does not depend on the agent's cooperation. That is the point.
Limits are how you ship faster, not slower
Here is the counterintuitive part. The limit is what lets you hand the agent more, not less. Gartner expects more than 40% of agentic projects to be canceled by 2027, with "inadequate risk controls" named as a cause. The project that dies is the one stuck in review because nobody can state the worst case. The one that ships is the one where the worst case is written down and physically bounded.
I do not want to give an agent a lecture and hope. I want to give it a budget it cannot exceed and then stop thinking about it. "Refund up to $500 a day" as a proven invariant is not a brake on the agent. It is the permission slip that lets me give it the job.
Give agents limits so you can give them work. That is what Bounded is for, and it is the layer poof.new runs every app on.