The Only Constraints an Agent Cannot Bypass Are the Ones It Cannot See

Every constraint that lives inside an agent’s context is a constraint the agent participates in honoring. The system prompt telling it never to touch production, the instruction to validate before it writes, the policy tool it is supposed to consult first: all of these are inputs to a probabilistic process that decides, at runtime, what to do next. Most of the time the process complies. Complying most of the time is a different property from a guarantee, and on the paths where that difference matters, you do not get to choose which runs land on which side of it.

The way out is not a better instruction. It is to move the constraint out of the agent’s decision entirely, into a layer the agent neither calls nor holds any representation of, sitting at the boundary between its intent and the world’s state. The agent proposes an action. Before the action executes, the runtime hands that proposal to code with the authority to refuse it. No part of the model’s reasoning is involved, and nothing in the model’s context can reach it. That layer is what makes a nondeterministic system governable without making it deterministic.

Who invokes the check decides whether it holds

The distinction that decides whether a control actually holds is not code versus prompt. It is who invokes the check.

A validation function the agent is instructed to call before acting is code, and it is still in-band, because the agent decides whether to call it. An agent that skips it, or calls it and then acts on the result it prefers, has not defeated any mechanism. It has ignored an instruction, which is the thing instructions permit. The same is true of a verification step the agent inserts into its own plan, and of a policy tool sitting in its toolset. Anything the agent’s own reasoning gates is inside the loop, and its execution is contingent on the loop behaving.

Out-of-band means the runtime invokes the check, not the agent, triggered by an event the agent generates merely by attempting to act. The agent does not call the interceptor. The interceptor is called about the agent. That inversion is the entire property. It is not a stronger request; it is the removal of the request.

Everything else about the layer follows from that inversion. The interceptor holds no space in the context window, so it does not compete with the thousand other tokens steering the model, and its influence does not decay as the context fills. It cannot be talked out of firing, because there is nothing in the loop to talk to. A user-supplied input that persuades the model to attempt something destructive still produces the same proposal at the boundary and meets the same refusal, which is why this layer, and not the system prompt, is where the constraints that must survive hostile input belong. And its behavior on the millionth run is identical to its behavior on the first, which is a property no prompt has ever had.

Every boundary in the agent loop is an interception point

An agent loop is a sequence of transitions, and the runtime observes all of them. It accepts an input, reasons, proposes an action, executes it, takes the result back, and repeats until it decides it is finished. Each of those seams is a moment where control passes through the runtime, and any moment the runtime controls is a moment it can hand to code you wrote.

The seams worth knowing are the ones that carry different information. Before an action executes, the proposal exists and no effect does. After it executes, the effect exists and the result is known. At the end of a step, the agent has stopped acting and is about to return control, so the full sequence of what it just did is visible at once. At the start and end of a run there is a natural place to establish and tear down. When a delegated child agent finishes, its output is available before the parent consumes it. When the runtime is about to alter the agent’s own context, by compacting a long history, the version about to be discarded still exists. And when the agent emits something outward that is not an action against a resource, an alert or a request for input or a completion signal, that emission can be routed somewhere.

The exact catalogue varies by runtime and any specific list of event names will date quickly, so the catalogue is not the thing to learn. The design question is which seam is the earliest one that already knows enough. Too early and the interceptor cannot see what it needs to judge. Too late and it can see everything and change nothing. The window where both conditions hold is narrower than people assume, and finding it is most of the work.

One practical caveat governs all of it. An interception point is worth only as much as the payload it hands you. An interceptor that receives the name of the action but not its arguments can enforce “never perform this operation” and nothing more. The rule you actually want is almost always “never perform this operation against that resource,” and that rule requires the arguments. Before designing policy at a seam, establish what the seam can actually see.

Timing fixes capability

Prevention is available at exactly one place: before the action executes. At that moment the proposal exists and the effect does not, and a refusal costs nothing but the work already spent generating the proposal. Every other interception point in the loop is downstream of the effect.

This sounds obvious and it is violated constantly, because the reactive interception point is the easier one to build and it produces the same reassuring artifacts. A post-action interceptor inspects the result, concludes it should not have happened, records the violation, raises an alert, and returns cleanly. Every signal the system emits says the guard is working. The only thing it did not do is the thing it was installed to do, and that gap stays invisible until the run where it was the whole point.

The tempting patch is to make the reactive interceptor undo the damage, but “undo” is a stronger word than the mechanism deserves. What you can write is a compensating action, and a compensating action is only as good as the inverse it approximates. Some effects have clean inverses, like a record written to a store you control. Many have none. A payment captured, a message delivered to a customer, a call to a partner system that started their workflow, an email in someone’s inbox: none of these come back. And the actions with no inverse are, with grim reliability, the exact set for which a guardrail was wanted in the first place. Compensation is a real technique with a real scope, and that scope is the reversible.

The inverse of this rule is just as sharp and is used less than it should be. Some constraints are undecidable from a proposal and only become evaluable from a result. Whether a write produced a record that violates an invariant, whether a generated output is structurally sound, whether a step’s result contradicts something established earlier in the run: none of that can be known from the intent. The split between the two interception points, therefore, is not “safety at the front, logging at the back.” It is decidable from the proposal at the front, decidable only from the effect at the back, and a system that needs both puts one at each.

A denial must be a decision, not a failure

The interceptor reports its verdict back to the runtime through a narrow channel, and that channel is a protocol. Designing it as a binary, worked or did not work, is the single most reliable way to build a guard that fails silently in both directions.

The channel has to distinguish three states. The interceptor evaluated the proposal and permits it. The interceptor evaluated the proposal and deliberately refuses it. The interceptor could not evaluate anything at all, because it crashed, timed out, could not read its policy, or hit a bug. Collapse the second and third and one of two failures is guaranteed.

If refusal is encoded as “anything other than success,” then every defect in the interceptor becomes a refusal. A missing dependency, a permissions error opening a config, a null in an unexpected place: each one silently converts into an unexplained denial of legitimate work. The symptom is an agent that can no longer act, and nothing in the failure points at the guard that caused it.

If instead refusal requires one specific signal, and everything else means “the guard errored, carry on,” then an enforcement path that emits the wrong signal fails open. The guard reports a problem, and the action executes anyway. Runtimes that encode a verdict in a process exit status typically work this way, reserving one particular value for refusal and treating other non-success values as non-blocking errors, precisely so that a crashed guard cannot take the whole system down with it. That is a defensible default, and it carries a consequence that has to be stated plainly: under it, an enforcement path that does not emit exactly the refusal signal enforces nothing at all.

Underneath sits a decision no runtime can make for you. What does an unresponsive guard mean? Failing open keeps the system available and leaves it unguarded for the duration. Failing closed keeps it safe and makes the guard a single point of failure for every action it inspects. Neither is right everywhere. A reactive interceptor that enriches an audit record should fail open without hesitation, because losing a log line is not worth an outage. A check standing in front of an irreversible action should fail closed, and you should accept that a broken guard halts the agent, because halting is precisely what you would ask for if someone woke you up and asked. The unacceptable option is to not decide, inherit whatever the runtime happens to do, and learn the answer during an incident. Timeouts are the same decision arriving more slowly: every interceptor needs a bound, and when the bound expires the runtime does one of those two things, on purpose.

The verdict has to travel back into the agent’s context

The layer is invisible to the agent by design, and there is exactly one thing that must cross back over: the reason for a refusal.

From inside the loop, a bare denial is indistinguishable from an action that failed for any other reason. The agent’s natural response to an opaque failure is to try again, often with a variant that is forbidden in the same way, and the run burns its budget rediscovering the same refusal in a dozen costumes. A denial that carries its reason back into context converts the boundary into information. The agent learns the constraint at the only moment the constraint is relevant, and can do something sensible with it: choose a permitted resource, escalate to a human, or report honestly that the task cannot be completed under the current policy. The rule stays non-negotiable. It simply stops being invisible at the point where invisibility does damage.

The same message is what keeps the humans out of the wrong layer. Silence at the boundary reaches an operator as a model that chose not to act, so the investigation begins where the reasoning lives instead of where the rule lives, and the reasoning is the one component behaving correctly. Every refusal path should name the rule that fired and the input it fired on. The cost is one string, and it redirects an entire class of investigation to the component that actually made the decision.

Every synchronous interceptor is on the critical path

Pre-action interception is synchronous by necessity. The runtime cannot execute the action until the verdict comes back, so the interceptor’s latency is added to every action it matches, and agents take a great many actions. A check costing a fraction of a second disappears on any single call and compounds into real time across a long-running run.

The consequences get worse when the check leaves the process. An interceptor that calls a policy service inherits that service’s tail latency and its availability, which means the reliability of the agent is now bounded below by the reliability of the policy service. When that service degrades, you get either a stalled agent or an unguarded one, and which one you get is the fail-open decision from earlier, arriving to collect.

So the operating rule is that in-band checks must be cheap and local. Evaluate against what you already hold: the proposal, a static policy loaded at startup, a set in memory. Everything expensive belongs where latency is free, which is anywhere off the blocking path. Normalizing a written record, indexing it, notifying a channel, aggregating usage, running a verification suite against a change: none of these need to hold the loop open, and all of them fit on a post-action or end-of-run event, or on an asynchronous consumer reading the audit stream.

Usage metering shows the split cleanly. Capturing a per-action cost record is a memory write and can happen synchronously without anyone noticing. Aggregating those records, attributing them, and reporting on them is batch work that belongs outside the loop entirely. But the ceiling itself, the rule that says this run does not get to spend past here, has to sit on the blocking path. A budget enforced by a report is not a budget; it is a receipt. Put the part that must stop something where it can stop something, and move the rest out of the way.

A policy layer is not an isolation boundary

Interception governs what the agent asks to do. It does not constrain what the executed action is capable of doing. That distinction sets a hard ceiling on what this layer can honestly promise, and the layer is routinely sold past it.

Consider the most common instance, a pre-action interceptor that scans a proposed command for destructive patterns and refuses the matches. It is worth having. It is also a filter and not a boundary, because it is matching surface forms produced by a system with unbounded ways to express the same effect, and the list of forms is finite while the ways to express them are not. Every check that reasons about the shape of a request rather than the authority behind it has this ceiling.

Containment lives one layer down, where the action actually executes: the credentials the runtime holds, the permissions on the resource, the network the process can reach, the sandbox it runs inside. An agent that cannot authenticate to the production datastore is contained. An agent that can authenticate, and is asked by an interceptor not to, is filtered. The order matters: design the containment first, and put interception on top of it, because interception’s real jobs are the ones containment cannot do. Containment cannot express a rule that depends on the semantics of a particular request rather than the identity of the caller, and it cannot produce the record that makes agent behavior auditable. Those are the jobs to hire this layer for.

Two things follow. Prefer default-deny wherever the stakes justify the effort, enumerating what is permitted and refusing everything else, because a denylist protects only against the harms someone already thought to name, and the space of harmful actions grows every time a tool is added. And treat the interception layer as privileged code, because it is exactly that: it runs on every action, with the runtime’s authority, holding the arguments of every call. It deserves the review, the versioning, and the change discipline of anything else in the security path, which is a good deal more than it usually gets, given that most of these layers begin life as a convenience script somebody wrote in an afternoon.

An audit trail needs the attempt as well as the effect

A record written after the fact tells you what happened. It is silent on everything that was proposed and refused, and that silence removes exactly the evidence that matters most.

An agent repeatedly reaching for a resource it is not permitted to touch, a manipulated input driving a sequence of denied destructive calls, a policy rule quietly blocking a legitimate workflow a hundred times a day: none of it appears in a log of successful actions. In that log, the system looks immaculate, because the boundary held every time. What the log cannot show you is that the boundary is being leaned on.

The audit surface therefore has two halves and needs both interception points. One record captures the proposal and the verdict it drew. The other captures the effect and what it returned. The first tells you what the agent tried and what the policy did about it; the second tells you what actually changed in the world. Together they let you reconstruct a run, which is the point, and they let you calibrate, which is the part nobody plans for. A rule that fires constantly against legitimate work is miscalibrated, and no log that records only what got through will ever tell you so.

Enforce only what does not require judgment

Once a layer exists that can refuse actions, it attracts every rule anyone has ever wanted enforced, and it will cheerfully accept rules it has no capacity to evaluate. The scope is narrower than the temptation.

What belongs here is the subset of constraints that are decidable without judgment: the ones where a proposal and a policy are enough to reach the same answer every single time. Is this resource in the permitted set. Does this argument fall within its allowed range. Is this operation on the prohibited list. Has this run exceeded its ceiling. These are exactly the rules that benefit from being mechanical, unbypassable, and boring.

Constraints that require weighing context, intent, or trade-offs are not enforceable at this layer, and cramming them in as brittle pattern matches produces the worst outcome on offer: a rule that fires on the wrong things, gets disabled by the first team it inconveniences, and in the meantime persuades everyone that the system is guarded. Those constraints belong to the model, which can reason about them and will sometimes be wrong, or to a human, who can reason about them and is expensive. Deciding which of those three tiers a rule belongs to is the actual design work, and the interception layer is the tier that should be hardest to get into.

For the rules that do belong here, observation comes before enforcement. A layer with the authority to refuse actions has the authority to break the system, and a mis-scoped rule breaks it quietly: the agent simply stops being able to do something, and nobody is watching the place where that fact is recorded. Running a new rule in a mode that records what it would have refused, against real traffic, before it is permitted to refuse anything, turns a production incident into a report somebody reads on a Tuesday. The rules that survive that filter are the ones worth enforcing.

Governance belongs at the boundary where intent becomes effect

This layer earns its place because it dissolves what looks like a contradiction at the center of agent design. You want the agent to be free, to reason its way to actions you did not anticipate, because that capacity is the entire reason to deploy a model instead of a script. You also want the system to hold invariants that no run, however creative, is permitted to break. Every attempt to reconcile those two demands inside the model, through stricter prompts and narrower instructions and more rules crowded into context, buys safety by spending freedom, and buys it unreliably, because the model is still the one deciding.

Moving the invariants out of the model and onto the boundary it must cross to affect anything resolves the tension instead of trading against it. Inside the boundary, the agent stays exactly as free as you designed it to be. At the boundary, the rules are code: they run every time, they are indifferent to what the context window has drifted into, and there is no argument that reaches them. Nondeterminism is preserved where it creates value, in the reasoning, and eliminated where it creates risk, at the point of effect.

That turns the design questions concrete. For each constraint that matters: which seam is the earliest one that already knows enough to judge it. What does the guard do when it cannot answer. What does the agent learn when it is refused. What does the guard cost on every action it inspects. And behind all of them, the question that measures the real strength of the system: what would still be true if the guard were removed entirely. Whatever survives that removal is what you actually guarantee. Everything else was a request.