# Blocked is not failed: agents need boundary feedback

> Source: <https://dev.to/davidloibner/blocked-is-not-failed-agents-need-boundary-feedback-bbg>
> Published: 2026-06-18 08:26:19+00:00

In part 2, I wrote about why tool access is not the same as impact permission.

The main point was that an agent request should not automatically become an external effect, only because a tool is visible and the call is technically valid. For tools that can change real systems, there should be an admission step before impact.

But this leads to a practical follow-up question.

What happens when the admission layer says no?

In many current agent setups, this situation is treated like a normal tool failure. The agent calls a tool, the request violates a rule, the runtime returns a generic error, and the tool call fails.

At first sight, this seems acceptable. The unsafe action was blocked, so the system did its job.

However, I think this is only half of the problem.

A blocked action should not change the external target state. That part is clear. But the response should also help the agent understand what kind of next step is still valid. Otherwise the boundary stops one action, but it does not help the agent work inside the boundary.

That is the distinction I want to look at here.

A blocked action is not just a failed tool call. It can also be a structured decision.

If a human developer hits a permission wall, the situation is usually manageable. The developer reads the error, understands the constraint, and changes the approach. Even if the error message is not perfect, the human can infer what probably happened.

An autonomous agent reacts differently.

If the agent receives a normal tool exception, such as `403 Forbidden`

, `permission denied`

, or `tool failed`

, it does not necessarily understand that a deliberate boundary was enforced. It may interpret the failure as a temporary tool problem, a formatting issue, or a prompt problem.

This matters because the reasoning loop is still active. The agent may try to repair the situation by guessing. It may reword the same request, call another tool, generate a slightly different payload, or repeat the previous attempt because it did not understand why the action was blocked.

In this case, the generic error did not really guide the workflow. It only converted a policy decision into noise inside the agent loop.

This is one of the reasons why I think agent boundaries should return more than ordinary execution errors.

An admission layer should treat a denial as a controlled outcome, not as an unexpected crash.

If a request is blocked, invalid, or conflicts with stale state, the external system should not change. But the response sent back to the agent should still be precise enough to explain the safe next step.

For example, imagine an agent proposes a write based on a file state that is no longer current. A human developer may have changed the file while the agent was planning. In a normal tool flow, this might appear as a failed write or a generic conflict.

A structured boundary response can express the situation more usefully:

```
{
  "decision_status": "conflict",
  "outcome_status": "no_impact",
  "reason_code": "stale_state_reference",
  "required_next_action": "re_read_target_state",
  "retryable": false
}
```

The important point is not the exact field names. The important point is that the agent does not have to guess.

The response says that the request did not fail because the goal is impossible. It failed because the state reference is stale. The next safe action is therefore not to retry the same request, but to read the target state again and submit a new request.

It should be noted that `retryable: false`

does not mean that the task is impossible. It means that this exact request should not simply be repeated. The agent has to update its state first.

This distinction is useful because it separates different kinds of blocked work.

A path outside the allowed scope is not the same as stale state. A request that needs human approval is not the same as a duplicate attempt. A malformed input is not the same as a policy violation.

If all of these cases look like a generic tool failure, the agent has no good signal for what to do next.

The useful part of boundary feedback is not that it explains every internal rule. It should not do that.

The useful part is that it can turn a blocked request into a safe next action.

If the state is stale, the next action may be to re-read state. If the path is outside the allowed scope, the next action may be to choose an allowed path. If the requested effect already exists, the next action may be to reuse or poll the existing outcome. If the blast radius is too high, the next action may be to wait for review.

This does not make the boundary softer. The blocked action still stays blocked. The external system still remains unchanged.

But the agent is not left with a dead end. It receives a constrained signal about how to continue in a compliant way.

This is especially relevant for autonomous workflows. A human can often recover from unclear errors. An agent may instead create more retries, more tool calls, and more confusion. Structured feedback reduces that unnecessary search space.

There is a real counterpoint here.

If every blocked request returns very precise feedback, the boundary can become a way to probe the policy. A compromised or misaligned agent could send many slightly different requests and use the returned reason codes to learn where the limits are.

So feedback has to be useful, but not unlimited.

For normal operational friction, clear reason codes are helpful. Stale state, malformed input, missing fields, or an already existing outcome are usually safe to explain. These are not secret policy details. They are workflow facts that help the agent recover.

But the situation changes if the system sees repeated violations, ignored next-action hints, or suspicious probing across many variants. In that case, the feedback should become less detailed. A generic rejection, a cooldown, or human review may be more appropriate than continuing to expose precise policy boundaries.

The general rule is simple:

Boundary feedback should guide compliant work.

It should not become a policy oracle.

There is another distinction that matters.

Feedback is agent-facing. It describes what the agent needs for the next step. It can say that state is stale, that a path is out of scope, or that approval is required.

Scoring or auditing is different. That layer can observe how the agent behaves over time. It can track whether the agent repeats the same blocked request, ignores required next actions, or keeps asking for broader access than the task seems to need.

I would not expose that full evaluation to the agent. If the agent sees the complete scoring logic, it may start optimizing for the score instead of solving the actual task.

Therefore, the agent-facing response should stay limited to what is needed for a compliant next action. The deeper behavioral evaluation can remain part of audit, monitoring, or later review.

This separation is important because the boundary has two jobs that should not be mixed. It should help the agent continue safely, but it should also allow the system to notice when the agent is not adapting.

We do not give agents boundaries because we assume they are useless or because they will always fail.

The opposite is closer to the point.

Agents need boundaries because they are becoming useful enough to act on real systems. Real work has limits, rules, current state, review paths, and consequences.

A boundary that only returns a generic failure is too rigid for useful agent workflows. It stops one action, but it does not tell the agent what a safe next step would be.

A better boundary should keep the external system unchanged while still giving the agent enough structured feedback to continue correctly.

That is the practical value I see here.

**Blocked should not mean that the workflow disappears into a generic error.**

Blocked should mean:

the requested impact did not happen,

the reason is known,

and the next safe action is constrained.

This is the difference between a wall and a working boundary.

Let agents explore solutions. Block them when the request crosses a boundary. But when something is blocked, make the next safe step explicit.

Project: [Impact Boundary Labs](https://impactboundarylabs.com)
