Every few months someone ships an agent that gets jailbroken, or gets confused, or just hits a weird edge of its prompt, and does something it was never supposed to do. The reaction is always the same: we need a better model, a stronger system prompt, more guardrails in the prompt. And every time, that is the wrong fix, because it locates the problem in the agent’s judgment when the problem is in the agent’s authority.
Here is the distinction I have come to build my whole approach around. Judgment is what the agent decides to do. Authority is what the system will actually let it do. You cannot make judgment perfect. Models are probabilistic, prompts leak, and a sufficiently strange input will eventually produce a sufficiently strange decision. So you stop trying to perfect the judgment and you bound the authority instead. The boundary is the product. I call it the grant envelope.
A prompt is not a permission
The most common mistake is encoding limits as instructions. “Only refund up to $200.” “Never delete customer records.” “Do not email more than 50 people per run.” These read like rules. They are not rules. They are suggestions to a stochastic process, and a stochastic process will, given enough runs, ignore every one of them.
I learned this concretely. An early version of a billing agent had “never issue a refund over $500” in its prompt. It held for thousands of runs. Then a customer message included a long quoted email thread that happened to contain the phrase “approved refund $4,000,” and the agent, pattern-matching, proposed a $4,000 refund. The prompt rule was right there. The model walked past it because the model does not enforce anything. It generates.
The grant envelope is the same limit, moved out of the prose and into code that the action physically cannot bypass:
class Envelope:
allowed_tools: set[str] # exactly which tools, nothing else
refund_cap_cents: int # hard ceiling, enforced at execution
daily_action_budget: int # rate limit across the whole run
allowed_object_types: set[str] # may touch invoices, not employees
forbidden: set[str] # actions it can never take
requires_human: set[str] # actions it may propose, never run
def authorize(action, env: Envelope):
if action.tool not in env.allowed_tools: deny("tool not granted")
if action.type in env.forbidden: deny("forbidden action")
if action.type in env.requires_human: return PROPOSE
if action.object_type not in env.allowed_object_types: deny("object out of scope")
if action.tool == "refund" and action.cents > env.refund_cap_cents: deny("over cap")
if env.daily_action_budget <= 0: deny("budget exhausted")
return ALLOW
That $4,000 refund proposal now hits refund_cap_cents and dies, regardless of what the model decided. The model’s judgment was wrong. The model’s authority was bounded. The customer was not refunded. That is the entire game.
A guardrail you wrote in the prompt is a thing you hope the model respects. A grant envelope is a thing the model cannot get past no matter what it decides. Build the second one.
What goes in the envelope
A good envelope is specific in five dimensions. Vagueness in any one of them is where the agent escapes.
Tools. Not “the agent can use the billing API.” The exact set of tool names it may call, allowlisted. Everything else returns access-denied at the dispatch layer, before the model’s intent matters. An agent that should send reminders gets send_reminder and read_invoice, full stop. It does not get delete_invoice even though the billing API exposes it, because that tool was never placed in its envelope.
Objects. Which records, of which types, in which states. A collections agent may read any invoice but may only act on invoices already marked overdue. It may not touch draft invoices, employee records, or anything outside its lane. In a multi-tenant system this is also the tenant boundary, and it is the one I am most paranoid about, because a cross-tenant action is not a bug, it is a breach.
Magnitude. Hard numeric ceilings. Refund caps, credit caps, the largest payment it may record. These are the limits that turn a confused agent into a contained one.
Rate. A budget of actions per run and per day. This is the limit that stops a loop bug or a prompt injection from turning into a thousand emails. An agent with a daily budget of 50 reminders cannot send 4,000, because the 51st call is denied by arithmetic, not by good behavior.
Reversibility class. Whether each action is reversible, and if not, whether it requires a human. This is where the envelope connects to the broader autonomy spectrum. Irreversible actions get pushed into requires_human no matter how small they are, because the cost of being wrong about an irreversible action is unbounded.
The envelope is data, not code, and that matters
If the envelope is hardcoded, it ossifies. Every change to what an agent may do becomes a deploy, and worse, the envelope becomes invisible: nobody can look at a running agent and see its authority at a glance. So I store envelopes as data, attached to each agent run, and I make them inspectable.
This buys three things that pure code does not. You can grant a tighter envelope for a new, untrusted capability and widen it as the capability earns trust, without redeploying. You can show a human, or an auditor, exactly what a given agent run was permitted to do, which in a federal context is not a nicety, it is a requirement. And you can revoke. If an agent is misbehaving, you narrow or void its envelope live, and the very next action it attempts is denied. You do not have to kill the process or roll back a deploy. You change a row.
| Agent | Tools granted | Object scope | Magnitude cap | Daily budget | Requires human |
|---|---|---|---|---|---|
| Collections | read_invoice, send_reminder, apply_credit | overdue invoices, own tenant | credit ≤ $200 | 50 actions | refunds, deletions |
| Federal intel | read public award data, read spending data, draft_brief | public records only | none (read-only) | n/a | every write action |
| Voice order | check_availability, book_slot, charge_card | services in catalog | charge ≤ $500 | per-call budget 3 | charges over cap, refunds |
Look at the federal intel agent in that table. Its envelope grants aggressive reads and essentially zero writes. That is not because the model is dumb. It is because in the federal world the recourse story for an autonomous write is so demanding that I would rather the agent propose everything and a human commit it. The envelope encodes a policy decision, and because it is data, the policy is visible and changeable without touching the agent’s brain at all.
Envelopes compose, and that is where it gets powerful
Real systems have agents that spawn sub-agents. A planning agent delegates a billing task to a billing agent. The critical rule, the one that prevents privilege escalation, is that a sub-agent’s envelope can only ever be a subset of its parent’s. A child cannot be granted authority the parent did not have. Delegation narrows; it never widens.
def delegate(parent_env, requested_env):
# the child gets the intersection, never more than the parent holds
child = intersect(parent_env, requested_env)
assert child.is_subset_of(parent_env)
return child
This closes the most insidious autonomy hole: an agent that cannot do something directly trying to spawn a helper that can. With strict subset composition, there is no helper that can exceed the original grant. Authority only ever flows downhill and gets narrower as it goes. The root envelope, the one a human signs off on, is the maximum authority anything in that whole tree will ever have.
Why this beats a smarter model every time
The seductive belief is that capability buys safety, that a smart enough agent will not make dangerous mistakes. It is backwards. A more capable model is a more capable actor, which means when it is wrong, or jailbroken, or simply surprised by an input, it is wrong more effectively. Capability and safety are orthogonal. You buy capability from the model. You buy safety from the boundary.
The envelope also degrades gracefully in a way prompts never do. When a prompt rule fails, it fails silently and totally, and you find out from the consequences. When an envelope denies an action, it denies loudly and is logged, and that denial is itself a signal: an agent repeatedly hitting its envelope is an agent trying to do something it should not, which is exactly the thing you want surfaced. A spike in envelope denials is one of the most useful alarms I run.
I will say the uncomfortable part plainly. I do not trust the agents I build. I trust the envelopes around them. HANNA, the GovCon intelligence engine I architected, the collections agents inside Vontra, the voice-driven ordering pipeline I built on a realtime telephony provider and a payments API, every one of them is a model I assume will eventually do something stupid, wrapped in a boundary I have made certain it cannot cross. That is not pessimism about AI. It is how you ship autonomy into a real business and sleep at night. The model is the part that thinks. The envelope is the part that means I do not have to lie awake hoping it thinks correctly every single time.
The day an agent does something genuinely dangerous, the question will not be “why was the model wrong.” Models are wrong. The question will be “why was it allowed.” Build so the answer is: it wasn’t.