<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: David Loibner</title>
    <description>The latest articles on DEV Community by David Loibner (@davidloibner).</description>
    <link>https://dev.to/davidloibner</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3959684%2F2ccf52da-9ae1-4e12-856b-7663bbfeb8f0.png</url>
      <title>DEV Community: David Loibner</title>
      <link>https://dev.to/davidloibner</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/davidloibner"/>
    <language>en</language>
    <item>
      <title>The reasoning was right, but the world shifted</title>
      <dc:creator>David Loibner</dc:creator>
      <pubDate>Wed, 08 Jul 2026 06:35:26 +0000</pubDate>
      <link>https://dev.to/davidloibner/the-reasoning-was-right-but-the-world-shifted-3ngk</link>
      <guid>https://dev.to/davidloibner/the-reasoning-was-right-but-the-world-shifted-3ngk</guid>
      <description>&lt;p&gt;While working on the GitHub adapter, a gateway that lets AI agents create pull requests on GitHub, the &lt;code&gt;source_state&lt;/code&gt; field first looked like a small technical detail.&lt;/p&gt;

&lt;p&gt;It was not the operation itself, or the target. It was only a reference to the state the agent had seen before proposing a change.&lt;/p&gt;

&lt;p&gt;But after working through the write path, this field started to look less like metadata and more like part of the safety model. A proposed change is not only defined by what it wants to do. It is also defined by the state in which that proposal made sense.&lt;/p&gt;

&lt;p&gt;This is easy to miss.&lt;/p&gt;

&lt;p&gt;An agent can read a repository, produce a reasonable change, and submit a clean intent. Nothing about that has to be wrong. But while the agent is planning, the repository can move. A human can push a fix. Another workflow can update the same file. A branch can advance.&lt;/p&gt;

&lt;p&gt;In that case, the agent may still be reasoning correctly over the state it saw.&lt;/p&gt;

&lt;p&gt;The problem is that this state no longer exists.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The reasoning was right, but the world shifted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the stale state problem in agent workflows. And it is why I think agent workflows need state-bound intent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The illusion of a static world
&lt;/h2&gt;

&lt;p&gt;From the outside, even from the boundary's point of view, a stale request can look just like any other: the operation has the same name, the target path is still allowed, the input is still well formed.&lt;/p&gt;

&lt;p&gt;But it is not. The proposal belonged to an older state of the repository, formed before the branch moved, before the file changed, before another workflow created a related result.&lt;/p&gt;

&lt;p&gt;This is why stale state is not only a data freshness problem. For agent workflows, it becomes an admission problem: a decision about whether a proposed change is allowed to become a real effect. We call that decision point an MCP Boundary: the same pattern behind the GitHub adapter and the wider work we do on MCP gateways. The boundary should not only ask whether the operation is allowed on the target. It should also know whether the target is still in the state that made the operation reasonable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The write happens now.&lt;br&gt;
The reasoning happened before.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That gap is where the problem appears.&lt;/p&gt;

&lt;h2&gt;
  
  
  State binding
&lt;/h2&gt;

&lt;p&gt;The intent should not float freely between the agent and the system that later creates impact. It should carry the state reference it depends on, a practice we call state binding: attaching a proposed effect to the exact state it was reasoned about.&lt;/p&gt;

&lt;p&gt;For a GitHub adapter, this can be the branch head or a file hash. In other systems, it may be a ticket status, a data version, or a configuration version. The exact field is less important than the relation it creates.&lt;/p&gt;

&lt;p&gt;The request is no longer only:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;apply this change to this target.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It becomes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;apply this change to this target, if the target is still in the state this change was based on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That small addition changes the meaning of the request. The boundary can now reject the drift instead of guessing through it. If the state still matches, the request continues through the normal checks. If the state moved, the request should stop before impact.&lt;/p&gt;

&lt;p&gt;This does not mean the agent gets to declare that the state is safe. The state reference has to be checked by the boundary, or by the trusted system behind it. Otherwise it would only be another claim from the agent.&lt;/p&gt;

&lt;p&gt;The agent can say what it saw.&lt;/p&gt;

&lt;p&gt;The boundary has to verify whether that is still true.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is not a new idea
&lt;/h2&gt;

&lt;p&gt;The underlying idea is old. Git has base commits. Databases have version checks. HTTP has ETags. Security people know the broader class of problems around time of check and time of use.&lt;/p&gt;

&lt;p&gt;So the point is not that agent systems discovered stale state. The point is that agents make the read-to-write gap wider and less predictable.&lt;/p&gt;

&lt;p&gt;An agent reads, plans, calls tools, revises, retries, and only later submits a proposed effect. During that gap, the target system keeps moving. A change that was safe five minutes ago may be wrong now, not because the goal changed, but because the state around the goal changed.&lt;/p&gt;

&lt;p&gt;That makes state-bound intent more than a backend detail. It becomes part of the boundary. Agent requests should not only be checked by operation, target, and policy. They should also be checked against the state that produced them.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the state moved
&lt;/h2&gt;

&lt;p&gt;When the agent submits an intent, the boundary compares the submitted state reference with the current target state.&lt;/p&gt;

&lt;p&gt;If they match, the request can continue. It may still need review, be outside the allowed scope or be blocked for other reasons. But at least it is still attached to the state the agent used.&lt;/p&gt;

&lt;p&gt;If they do not match, the request should stop before impact.&lt;/p&gt;

&lt;p&gt;A stale request is not a malformed request. It is not an unauthorized request. It is not necessarily a forbidden goal. It is a request whose basis expired.&lt;/p&gt;

&lt;p&gt;So the safe next action is not to retry the same write with slightly different arguments. It is to read the target again and submit a new intent based on the current state.&lt;/p&gt;

&lt;p&gt;This is where &lt;a href="https://dev.to/davidloibner/blocked-is-not-failed-agents-need-boundary-feedback-bbg"&gt;boundary feedback&lt;/a&gt; matters. The system should say, in a structured way, that no impact happened because the state reference was stale:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"conflict"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outcome_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"no_impact"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reason_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stale_state_reference"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha:abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"current_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha:def456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required_next_action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"re_read_target_state"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"retryable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is not the JSON format.&lt;/p&gt;

&lt;p&gt;The important part is the next action.&lt;/p&gt;

&lt;p&gt;The boundary is not saying that the goal is forbidden. It is saying that this request no longer belongs to the current state of the target.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the binding is less clear
&lt;/h2&gt;

&lt;p&gt;Not every operation has a clean state reference. Some targets are easy to bind: a Git commit, a file hash, a database row version. Others are harder. A draft email may depend on context that is difficult to reduce to one stable token.&lt;/p&gt;

&lt;p&gt;In those cases, the system should be honest. If the state cannot be bound well, the boundary may need to be stricter. That connects back to &lt;a href="https://dev.to/davidloibner/a-write-is-not-just-a-write-34b1"&gt;reach&lt;/a&gt;: the less precisely you can bind the state behind an effect, the more careful admission should become.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters for autonomous work
&lt;/h2&gt;

&lt;p&gt;A human often notices when the world has moved. A developer sees that the branch changed. A support worker sees that the ticket was already closed.&lt;/p&gt;

&lt;p&gt;Agents do not carry that situational judgement automatically. They can continue from an earlier observation with full confidence, even when it is already outdated, because the reasoning is still coherent relative to the old state.&lt;/p&gt;

&lt;p&gt;That is exactly the issue.&lt;/p&gt;

&lt;p&gt;Good reasoning over stale state can still produce bad impact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State binding&lt;/strong&gt; does not fix this by making the agent correct. It does not prove that the code is good, that an email is wise, or that a data update is meaningful. Tests, review, and engineering judgement remain necessary.&lt;/p&gt;

&lt;p&gt;The claim is narrower. Without the state it was based on, the boundary cannot know whether it is admitting the request the agent actually reasoned about.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A request is not only an operation against a target.&lt;br&gt;
It is an operation against a target, based on a state.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If that state changed, the request changed too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No state binding, no safe admission.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;Project: &lt;a href="https://impactboundarylabs.com" rel="noopener noreferrer"&gt;Impact Boundary Labs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>security</category>
      <category>architecture</category>
    </item>
    <item>
      <title>A write is not just a write</title>
      <dc:creator>David Loibner</dc:creator>
      <pubDate>Sat, 04 Jul 2026 22:00:18 +0000</pubDate>
      <link>https://dev.to/davidloibner/a-write-is-not-just-a-write-34b1</link>
      <guid>https://dev.to/davidloibner/a-write-is-not-just-a-write-34b1</guid>
      <description>&lt;p&gt;The point became clear to me while working on the GitHub adapter (a more or less simple GitHub gateway for AI agents creating PRs).&lt;/p&gt;

&lt;p&gt;At first, creating a pull request looks like one write. A branch is created, commits are pushed, and a PR appears. The API call is concrete, and the result is easy to name.&lt;/p&gt;

&lt;p&gt;But after building around it for a while, the word "write" started to feel too imprecise. A pull request is not only a technical object. It changes the state of a repository and creates something that another person may have to review. If it duplicates an existing proposal, it can already be noise before anyone reviews it. If it touches a deployment file instead of a README, the same operation suddenly has a very different weight.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The operation name did not change.&lt;br&gt;
&lt;strong&gt;The effect did.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the previous parts, I argued that the agent should not hold the write credentials, that tool access is not the same as impact permission, and that blocked requests should return boundary feedback instead of generic tool failures.&lt;/p&gt;

&lt;p&gt;This leads to the next point (or let's call it problem).&lt;/p&gt;

&lt;p&gt;Even if write authority is moved behind a boundary, the word "write" is still too coarse.&lt;/p&gt;

&lt;p&gt;A permission table can say that an action may write. That is useful, because it separates reading from changing. However, it does not say where the change lands, who sees it, which systems react to it, or whether another decision is still possible before the effect becomes larger.&lt;/p&gt;

&lt;p&gt;A draft and a sent email are both writes. A pull request and a merge are both writes. Updating test data and changing production data are both writes. Technically, all of them change state. Operationally, they are not the same event.&lt;/p&gt;

&lt;p&gt;This is the weakness of flat write access. A system can enforce the permission correctly and still allow the wrong kind of effect.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reach is the missing property
&lt;/h3&gt;

&lt;p&gt;The useful distinction is not only whether an action changes state.&lt;br&gt;
The useful distinction is &lt;strong&gt;reach&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By reach, I mean how far the effect travels before another decision is required. A change that stays inside an isolated workspace has low reach. A change that creates work for another person, becomes visible to users, changes production, or sends something outside the system has higher reach.&lt;/p&gt;

&lt;p&gt;This is why the same technical write may need different treatment. A draft email still stays inside a controlled workflow, while a sent email leaves it. A pull request is not a merge, but it already changes the repository workflow. A staging change can be acceptable with little friction, while the same change against production should pass a stronger gate.&lt;/p&gt;

&lt;p&gt;The boundary should therefore not only ask whether the verb is allowed. It should ask how far the requested effect is allowed to go.&lt;/p&gt;

&lt;p&gt;This is the point where "write" stops being a sufficient impact class.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reversibility is not enough
&lt;/h3&gt;

&lt;p&gt;A tempting classification is reversible vs. irreversible.&lt;/p&gt;

&lt;p&gt;This helps, but it is not stable enough as the main boundary.&lt;/p&gt;

&lt;p&gt;Real systems are rarely cleanly reversible. A draft may allocate an ID. A temporary object may leave logs, cache entries, metrics, notifications, or review noise. A staging change may start automatic work. A pull request may consume reviewer attention even if it is later closed.&lt;/p&gt;

&lt;p&gt;The primary object may be deleted, but the system has already changed.&lt;/p&gt;

&lt;p&gt;Therefore, reversibility should only be a secondary signal. A reversible action can still cross an important boundary. An action that cannot be fully undone can still be isolated and low impact. What matters for admission is not only whether the object can be removed, but what became visible, costly, dependent, or relevant while it existed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Reach captures that better than reversibility.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The boundary needs structure, not guesswork
&lt;/h3&gt;

&lt;p&gt;A boundary cannot reliably control reach if the agent only submits plain language.&lt;/p&gt;

&lt;p&gt;A request such as "update the customer record" is not enough. It may mean a harmless internal note, a billing change, a correction in production data, or a change that notifies someone. These are different effects, even if the sentence looks similar.&lt;/p&gt;

&lt;p&gt;A second model may help as an additional signal, but it should not be the main boundary. If the control layer has to read a paragraph and guess the effect, the hard part has only moved into another interpretation step.&lt;/p&gt;

&lt;p&gt;The cleaner design is that the agent submits a structured intent before an external effect is possible. The request should make the relevant parts explicit: the kind of action, the target, the expected state, the environment, and the intended result.&lt;/p&gt;

&lt;p&gt;This does not mean that the agent gets to declare its own safety.&lt;/p&gt;

&lt;p&gt;The agent can propose what it wants to do. But the system around the boundary has to make the relevant effect visible. A path, repository, branch, account, environment, URL, or table is not only a string in the request. It has to be resolved by the system that owns the boundary.&lt;/p&gt;

&lt;p&gt;This is important because a tool name does not tell us enough.&lt;/p&gt;

&lt;p&gt;A generic tool may be able to do harmless and risky work. Treating the whole tool as high impact creates review noise. Treating it as low impact misses dangerous use. The useful classification is therefore not "this tool is safe" or "this tool is dangerous". The useful classification depends on the resolved target and on the effect that the interface exposes.&lt;/p&gt;

&lt;p&gt;A write to an agent-private scratch folder is different from a write to a deployment file. A request to a status endpoint is different from a request that changes billing. A test data update is different from a production data update.&lt;/p&gt;

&lt;p&gt;This only works when the interface exposes enough detail. If the target cannot be resolved, the request should be treated as broad. If a tool hides what it may change, the boundary cannot magically recover that information from the tool name. In that case, the safer answer is to wrap the tool with a narrower interface, require review, or block the request.&lt;/p&gt;

&lt;p&gt;Narrow admission only works when narrow effects are visible.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The tool is not the boundary.&lt;br&gt;
The resolved effect is.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Small actions can add up
&lt;/h3&gt;

&lt;p&gt;Per-request admission does not mean memoryless admission.&lt;/p&gt;

&lt;p&gt;A single small write may be acceptable, while many small writes can become a larger operational effect. This is especially relevant for agents because retries are not always simple technical retries. The agent may rephrase the same goal, split it into smaller steps, or move toward the same blocked outcome through another route.&lt;/p&gt;

&lt;p&gt;The boundary should be able to notice that pattern.&lt;/p&gt;

&lt;p&gt;However, this memory must not depend on an identifier the agent can freely choose. If the agent can reset a trace ID, rename the task, or create a new parent label to escape accumulated context, the control is not real.&lt;/p&gt;

&lt;p&gt;Cross-request signals have to be tied to something the agent cannot invent on its own, such as a workflow record created by the boundary, the underlying user or service identity, a signed work order, or another trusted identity.&lt;/p&gt;

&lt;p&gt;With that constraint, workflow memory does not contradict per-request admission. Each requested effect is still evaluated separately. There is no broad write window. But the decision is not blind to recent denials, repeated similar intents, unusual volume, duplicate effects, or ignored boundary feedback.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;At some point, the next safe action is no longer another small admission.&lt;br&gt;
It is review.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The boundary is not the whole target system
&lt;/h3&gt;

&lt;p&gt;There is also a limit to what the boundary can claim.&lt;/p&gt;

&lt;p&gt;The boundary sees the request at the interface. It does not automatically know every automatic effect that happens inside the target system afterward. A narrow-looking write may start internal rules, background work, notifications, audit records, or other follow-up actions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The agent requested one write.&lt;br&gt;
The system may produce many effects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This does not make the boundary useless. It defines its responsibility. The boundary controls the handoff from agent request to admitted effect. The target system still has to own what happens internally through clear behavior, access control, data rules, and production safeguards.&lt;/p&gt;

&lt;p&gt;If an endpoint can create a stronger effect behind the scenes, that effect should be part of what the endpoint exposes to the boundary. If the boundary cannot know it, the request should be treated as broader or sent to a stronger gate inside the target system.&lt;/p&gt;

&lt;p&gt;The boundary is not a proof that everything behind the interface is harmless.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The boundary is a controlled handoff point.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The narrower claim
&lt;/h3&gt;

&lt;p&gt;This does not make the agent correct.&lt;/p&gt;

&lt;p&gt;It does not prove that generated code is good, that an email is wise, that a data update is meaningful, or that a deployment is safe. Tests, review, domain knowledge, and normal engineering judgement remain necessary.&lt;/p&gt;

&lt;p&gt;The claim is narrower.&lt;/p&gt;

&lt;p&gt;A boundary should control how far an agent request is allowed to reach before another decision is required.&lt;/p&gt;

&lt;p&gt;That reach is not defined by the verb alone. It depends on the resolved target, the surrounding workflow, and the effects exposed by the target system.&lt;/p&gt;

&lt;p&gt;Write access only says that state may change. It leaves open where the change lands, who sees it, and what other systems start doing because of it.&lt;/p&gt;

&lt;p&gt;That is why write access is too flat for agent work.&lt;/p&gt;

&lt;p&gt;A draft, a pull request, a staging update, a production write, an external message, and a payment may all be writes.&lt;/p&gt;

&lt;p&gt;They do not reach the same world.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The verb is not the boundary.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;The effect is.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Project: &lt;a href="https://impactboundarylabs.com" rel="noopener noreferrer"&gt;Impact Boundary Labs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>Blocked is not failed: agents need boundary feedback</title>
      <dc:creator>David Loibner</dc:creator>
      <pubDate>Thu, 18 Jun 2026 08:26:19 +0000</pubDate>
      <link>https://dev.to/davidloibner/blocked-is-not-failed-agents-need-boundary-feedback-bbg</link>
      <guid>https://dev.to/davidloibner/blocked-is-not-failed-agents-need-boundary-feedback-bbg</guid>
      <description>&lt;p&gt;In part 2, I wrote about why tool access is not the same as impact permission.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;But this leads to a practical follow-up question.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happens when the admission layer says no?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;At first sight, this seems acceptable. The unsafe action was blocked, so the system did its job.&lt;/p&gt;

&lt;p&gt;However, I think this is only half of the problem.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;That is the distinction I want to look at here.&lt;/p&gt;

&lt;p&gt;A blocked action is not just a failed tool call. It can also be a structured decision.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generic errors are not enough
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An autonomous agent reacts differently.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the agent receives a normal tool exception, such as &lt;code&gt;403 Forbidden&lt;/code&gt;, &lt;code&gt;permission denied&lt;/code&gt;, or &lt;code&gt;tool failed&lt;/code&gt;, 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

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

&lt;p&gt;This is one of the reasons why I think agent boundaries should return more than ordinary execution errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blocked is an outcome
&lt;/h3&gt;

&lt;p&gt;An admission layer should treat a denial as a controlled outcome, not as an unexpected crash.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;A structured boundary response can express the situation more usefully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"conflict"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outcome_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"no_impact"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reason_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stale_state_reference"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required_next_action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"re_read_target_state"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"retryable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important point is not the exact field names. The important point is that the agent does not have to guess.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;It should be noted that &lt;code&gt;retryable: false&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;This distinction is useful because it separates different kinds of blocked work.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Boundary feedback should guide the next step
&lt;/h3&gt;

&lt;p&gt;The useful part of boundary feedback is not that it explains every internal rule. It should not do that.&lt;/p&gt;

&lt;p&gt;The useful part is that it can turn a blocked request into a safe next action.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;This does not make the boundary softer. The blocked action still stays blocked. The external system still remains unchanged.&lt;/p&gt;

&lt;p&gt;But the agent is not left with a dead end. It receives a constrained signal about how to continue in a compliant way.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Policy Oracle problem
&lt;/h3&gt;

&lt;p&gt;There is a real counterpoint here.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;So feedback has to be useful, but not unlimited.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The general rule is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Boundary feedback should guide compliant work.&lt;br&gt;
It should not become a policy oracle.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Feedback is not scoring
&lt;/h3&gt;

&lt;p&gt;There is another distinction that matters.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Controlling the loop
&lt;/h3&gt;

&lt;p&gt;We do not give agents boundaries because we assume they are useless or because they will always fail.&lt;/p&gt;

&lt;p&gt;The opposite is closer to the point.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;A better boundary should keep the external system unchanged while still giving the agent enough structured feedback to continue correctly.&lt;/p&gt;

&lt;p&gt;That is the practical value I see here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blocked should not mean that the workflow disappears into a generic error.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Blocked should mean:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;the requested impact did not happen,&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;the reason is known,&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;and the next safe action is constrained.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the difference between a wall and a working boundary.&lt;/p&gt;

&lt;p&gt;Let agents explore solutions. Block them when the request crosses a boundary. But when something is blocked, make the next safe step explicit.&lt;/p&gt;




&lt;p&gt;Project: &lt;a href="https://impactboundarylabs.com" rel="noopener noreferrer"&gt;Impact Boundary Labs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>devtool</category>
      <category>agents</category>
    </item>
    <item>
      <title>Agent workflows need an impact boundary</title>
      <dc:creator>David Loibner</dc:creator>
      <pubDate>Wed, 10 Jun 2026 09:59:26 +0000</pubDate>
      <link>https://dev.to/davidloibner/agent-workflows-need-an-impact-boundary-4ih7</link>
      <guid>https://dev.to/davidloibner/agent-workflows-need-an-impact-boundary-4ih7</guid>
      <description>&lt;p&gt;In part 1, I wrote about why coding agents should not hold write credentials.&lt;/p&gt;

&lt;p&gt;GitHub was the example, because the problem is easy to see there. A coding agent can read a repository, reason about a change, and produce useful work. But if the same agent also owns the token that creates branches, commits, or pull requests, the proposal and the authority to create impact are too close together.&lt;/p&gt;

&lt;p&gt;The problem is not only GitHub.&lt;br&gt;
The problem is the moment where an agent request becomes an external effect.&lt;/p&gt;

&lt;p&gt;Agents are getting more useful because they can use tools. They can read files, call APIs, update tickets, prepare emails, run commands, inspect systems, and sometimes change state. That is exactly why the boundary matters more, not less.&lt;/p&gt;

&lt;p&gt;The question is not only:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can the agent use this tool?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The more important question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Should this specific request become impact now?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the missing layer I keep coming back to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tool access is not impact permission
&lt;/h3&gt;

&lt;p&gt;A tool can be visible to the agent. The call can be valid. The arguments can be well formed. The agent can even have a reasonable goal.&lt;/p&gt;

&lt;p&gt;Still, this does not automatically mean the requested effect should happen.&lt;/p&gt;

&lt;p&gt;A GitHub tool may create a pull request. A database tool may update or delete rows. A cloud tool may deploy a configuration. An email tool may send a message. In all of these cases, the tool is not only returning information. It can change a system that someone cares about.&lt;/p&gt;

&lt;p&gt;This is where I think many agent workflows are still too flat. They often treat tool access as if it already contained the whole decision. If the agent can call the tool, the tool executes. If the call succeeds, the system moves on.&lt;/p&gt;

&lt;p&gt;That may be acceptable for many read-only or low-risk operations. But for tools that create external effects, I think there should be another step between the agent request and the target system.&lt;/p&gt;

&lt;p&gt;The agent should be able to propose work. But the fact that a tool exists should not mean that every valid tool call becomes impact.&lt;/p&gt;

&lt;h3&gt;
  
  
  The missing layer is admission
&lt;/h3&gt;

&lt;p&gt;The distinction that helped me most is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Scope defines what is possible.&lt;br&gt;
Admission decides what is allowed now.&lt;br&gt;
Logs record what happened.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These are related, but they are not the same thing.&lt;/p&gt;

&lt;p&gt;Scope is mostly about design-time limits. Which tools are visible? Which paths are available? Which actions are impossible from the beginning? This is useful and necessary, because an agent should not even see tools or data it does not need.&lt;/p&gt;

&lt;p&gt;Admission is different. It is about the concrete request in the current situation. The question is not only whether the operation exists or whether the agent generally has access to it. The question is whether this requested effect is allowed now, under the current state, scope, and policy.&lt;/p&gt;

&lt;p&gt;An event log comes after that. It helps reconstruct what happened, which is important for audit and debugging. But a good history of what happened is not the same as a decision before impact.&lt;/p&gt;

&lt;p&gt;In a normal system this may sound obvious. In agent workflows it is easy to miss, because the agent often sits directly in front of powerful tools. The tool call becomes the action. The action becomes the outcome. The boundary is only visible afterward, when something needs to be explained, reverted, closed, or cleaned up.&lt;/p&gt;

&lt;p&gt;That is the part I think should move earlier.&lt;/p&gt;

&lt;h3&gt;
  
  
  State matters
&lt;/h3&gt;

&lt;p&gt;A request cannot be judged only by its name.&lt;/p&gt;

&lt;p&gt;The same operation may be fine in one state and wrong in another. A pull request against the expected branch head may be acceptable, while the same proposed change against stale repository state should be blocked or sent back. Updating test data may be harmless, while the same update against production may not be. Sending an email draft may be fine, while sending the message to real users may require review.&lt;/p&gt;

&lt;p&gt;The operation is the same in a rough sense, but the situation is not.&lt;/p&gt;

&lt;p&gt;That is why an agent request should be tied to the state it was based on. It should not be enough that the agent says it looked at the system. The decision layer should know, at least for the relevant parts, what state the agent was allowed to observe.&lt;/p&gt;

&lt;p&gt;If the state has changed, the right answer should not be to guess and continue. It should be a structured conflict. The agent can then re-read the state and submit a new request.&lt;/p&gt;

&lt;p&gt;This is not only a defensive mechanism. It also gives the agent useful feedback. The system does not have to say that the goal is forbidden. It can say that the proposal is stale.&lt;/p&gt;

&lt;p&gt;That distinction matters if we want agents to work better inside boundaries instead of simply failing at them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Per request, not per session
&lt;/h3&gt;

&lt;p&gt;I also think the unit of permission matters.&lt;/p&gt;

&lt;p&gt;A broad session permission is convenient. It can say that a certain agent session is allowed to write, deploy, send, or modify something for a limited time. For human workflows this kind of model is often acceptable, because the human user carries context and responsibility through the session.&lt;/p&gt;

&lt;p&gt;For an agent, a session can become a temporary impact window.&lt;/p&gt;

&lt;p&gt;The agent may retry. It may misunderstand a previous result. It may keep going from stale assumptions. It may call the same tool again with slightly changed arguments. If the session still has broad authority, the system may know which agent acted, but it did not decide each effect separately.&lt;/p&gt;

&lt;p&gt;This is why I prefer the request or intent as the unit of decision.&lt;/p&gt;

&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;this agent session may write for the next ten minutes&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;but rather:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;this requested effect is allowed under this state, scope, and policy&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a narrower form of authority. It does not prevent the agent from working. It only prevents broad access from silently turning into broad impact.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agent retries are different
&lt;/h3&gt;

&lt;p&gt;The same issue appears with idempotency.&lt;/p&gt;

&lt;p&gt;In distributed systems, idempotency often protects against technical retries. A request times out, a response is lost, or a client sends the same request twice. The system should not create the same effect twice just because the transport was unreliable.&lt;/p&gt;

&lt;p&gt;Agents retry for messier reasons.&lt;/p&gt;

&lt;p&gt;They may reword the same goal. They may generate a slightly different payload. They may call another tool. They may try again because they did not understand that the previous attempt already created a pending result.&lt;/p&gt;

&lt;p&gt;In that case, the prompt or payload may be different, while the intended effect is still the same.&lt;/p&gt;

&lt;p&gt;This does not mean the boundary should magically guess meaning from free text. That would be too weak. A better approach is to make the agent submit a more structured request before impact is possible. The system should decide on the target, operation class, expected state, and requested outcome, not only on the natural-language prompt that produced it.&lt;/p&gt;

&lt;p&gt;Then it becomes possible to ask better questions.&lt;/p&gt;

&lt;p&gt;Is this effect already pending? Was it already completed? Did a previous attempt partially create it? Should the existing outcome be reused? Should the agent re-read state first?&lt;/p&gt;

&lt;p&gt;Tool idempotency protects the request path.&lt;/p&gt;

&lt;p&gt;Intent-level idempotency protects the workflow from repeated attempts toward the same effect.&lt;/p&gt;

&lt;h3&gt;
  
  
  This is not a replacement for review
&lt;/h3&gt;

&lt;p&gt;An impact boundary does not prove that the agent is right.&lt;/p&gt;

&lt;p&gt;It does not prove that generated code is good. It does not prove that a database change is meaningful. It does not prove that a deployment is a good idea. Human review, tests, domain knowledge, and normal engineering judgement are still needed.&lt;/p&gt;

&lt;p&gt;The claim is narrower.&lt;/p&gt;

&lt;p&gt;The agent should not be the component that turns its own request into external state change.&lt;/p&gt;

&lt;p&gt;It can reason. It can propose. It can use tools. But when the requested action changes something outside the model, there should be a separate decision before impact.&lt;/p&gt;

&lt;p&gt;That decision may be simple in low-risk cases. It may only check scope and freshness. In higher-risk cases it may require approval, reuse an existing outcome, or block the request. The exact implementation can differ between systems.&lt;/p&gt;

&lt;p&gt;The architectural point stays the same.&lt;/p&gt;

&lt;p&gt;Tool access should not become impact by default.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why I care about this
&lt;/h3&gt;

&lt;p&gt;I do not think production agent systems will become trustworthy only because the models get better or the tool interfaces get cleaner.&lt;/p&gt;

&lt;p&gt;Better models help. Cleaner interfaces help. Sandboxes help. Logs help. Reviews help.&lt;/p&gt;

&lt;p&gt;But when agents start acting on real systems, there is still one question that needs its own place:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;who decides what becomes impact?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My answer is not that agents should be kept away from tools. The opposite is probably true. Agents become useful because they can interact with systems and do real work.&lt;/p&gt;

&lt;p&gt;But useful work needs boundaries.&lt;/p&gt;

&lt;p&gt;For human work, we already know this. We use roles, reviews, limits, approvals, and audit trails. We do not treat every possible action as automatically allowed just because someone can technically perform it.&lt;/p&gt;

&lt;p&gt;Agent workflows need the same idea in a machine-readable form.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Let agents request work.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Let them propose changes.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Let them use tools.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But before their work changes an external system, there should be an impact boundary.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the layer I think is missing in many agent workflows.&lt;/p&gt;




&lt;p&gt;Project: &lt;a href="https://impactboundarylabs.com" rel="noopener noreferrer"&gt;Impact Boundary Labs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>devtools</category>
      <category>agents</category>
    </item>
    <item>
      <title>Coding agents should not hold write credentials.</title>
      <dc:creator>David Loibner</dc:creator>
      <pubDate>Sat, 30 May 2026 12:50:22 +0000</pubDate>
      <link>https://dev.to/davidloibner/coding-agents-should-not-hold-write-credentials-3eod</link>
      <guid>https://dev.to/davidloibner/coding-agents-should-not-hold-write-credentials-3eod</guid>
      <description>&lt;p&gt;I have been thinking a lot about coding agents lately.&lt;/p&gt;

&lt;p&gt;Not really about whether they can write good code, because usually they can, sometimes they can't. That part is obvious. But the risk is shifting from wrong answers to wrong outcomes.&lt;/p&gt;

&lt;p&gt;The part that feels more important to me is this:&lt;br&gt;
should the agent actually own the write authority?&lt;/p&gt;

&lt;p&gt;We already don't trust humans without roles, limits, reviews, and accountability. Developers use PRs, pilots use checklists, bank clerks have transfer limits. Capable agents need the same structure, but machine-readable.&lt;/p&gt;

&lt;p&gt;Right now a lot of setups still look roughly like this: the agent reads the repo, decides what to change, holds a GitHub token, and then creates commits, branches, or PRs.&lt;/p&gt;

&lt;p&gt;I don't think this is the right default.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The agent can reason.&lt;br&gt;
The agent can inspect files.&lt;br&gt;
The agent can propose changes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But the moment it can directly create external impact, the problem changes.&lt;/p&gt;

&lt;p&gt;It is no longer just:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;did the agent say something wrong?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It becomes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;did the agent create the wrong outcome?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a much more expensive failure mode.&lt;/p&gt;
&lt;h2&gt;
  
  
  Intent is not authority
&lt;/h2&gt;

&lt;p&gt;The pattern I like more is simple: the agent reads directly, proposes intent, and a boundary decides before an adapter materializes admitted work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So the agent does not get the write credentials.&lt;/strong&gt;&lt;br&gt;
It submits a structured intent instead, which could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"operation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"write"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"repo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example/app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"branch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"docs/config/agent-policy.md"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"blob_sha"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8f31c2..."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requested_effect_hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is then not a command anymore, it is a suggestion, or an intent.&lt;br&gt;
The system still has to decide whether this proposed outcome should exist.&lt;/p&gt;

&lt;p&gt;That decision layer can check whether the actor, repo, path, source state, operation, and requested effect are valid for this situation, and whether the result should become a reviewable PR.&lt;/p&gt;

&lt;p&gt;Only after that should there be an outcome.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"admitted"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"checks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pass"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pass"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"policy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pass"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"idempotency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pass"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pull_request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reviewable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core rule is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No impact without admission.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The flow would look like this:&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftkqxyh8iwunln7x431on.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftkqxyh8iwunln7x431on.png" alt="Diagram showing how an agent reads a repository, submits a structured intent, passes boundary checks, and creates a pull request through a GitHub adapter" width="800" height="867"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  This is not the same as a sandbox
&lt;/h2&gt;

&lt;p&gt;A sandbox is useful.&lt;br&gt;
But I think it solves a different problem.&lt;/p&gt;

&lt;p&gt;A sandbox asks where the agent can run, whether it can use the network, whether it can execute commands, which files it can access, and whether it can escape the environment.&lt;/p&gt;

&lt;p&gt;A gateway asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;should this concrete proposed outcome exist?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That difference matters because a sandbox can stop escape, it does not decide whether a proposed outcome should exist.&lt;br&gt;
If the agent has a valid GitHub token inside the allowed environment, it can still use allowed tools to create an unwanted result.&lt;br&gt;
The action can be technically allowed and still be the wrong outcome.&lt;/p&gt;

&lt;p&gt;That is why I think the boundary should sit between intent and impact, not only around execution.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sandbox isolates execution.&lt;br&gt;
Gateway isolates impact.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why GitHub is a good first target
&lt;/h2&gt;

&lt;p&gt;GitHub already has a good human pattern:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;a change proposal is not a merge.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pull Requests are familiar because they are reviewable and they fit how developers already work.&lt;/p&gt;

&lt;p&gt;But with agents there is one step before the PR that also matters:&lt;/p&gt;

&lt;p&gt;An agent proposal should not automatically become PR impact.&lt;br&gt;
A PR is already a real side effect. It creates a branch, commits, review work, and changes the state of the repository.&lt;/p&gt;

&lt;p&gt;So the agent should not directly create it with its own write token.&lt;/p&gt;

&lt;p&gt;The flow I want is more like this: the agent reads the repository, submits structured intent, the gateway checks state, scope, policy, and idempotency, and the GitHub adapter creates a reviewable PR only after admission. The PR should contain evidence about the decision.&lt;/p&gt;

&lt;p&gt;The adapter is not the authority, it only materializes admitted work.&lt;br&gt;
And the agent never receives the GitHub write credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  This does not make the code correct
&lt;/h2&gt;

&lt;p&gt;This is important:&lt;/p&gt;

&lt;p&gt;A boundary like this does not prove that the generated code is good. It does not replace CI, human review, or semantic correctness. It only controls the transition from proposed work to external impact.&lt;/p&gt;

&lt;p&gt;That narrower claim is the whole point. I think many agent systems mix &lt;strong&gt;reasoning&lt;/strong&gt;, &lt;strong&gt;decision&lt;/strong&gt;, and &lt;strong&gt;impact&lt;/strong&gt; together.&lt;/p&gt;

&lt;p&gt;But these should be separated. The agent owns reasoning. The boundary owns the decision. The adapter owns controlled materialization. The target system should only receive admitted impact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I care about this
&lt;/h2&gt;

&lt;p&gt;I don't think production agent systems will be trusted just because the models get smarter. They will be trusted when the path from agent work to external change becomes explicit.&lt;/p&gt;

&lt;p&gt;For every real outcome, I want to know what the agent proposed, what state it read, which rules were checked, why it was admitted or blocked, what outcome was created, whether a human can review it, and whether we can audit it later.&lt;/p&gt;

&lt;p&gt;That is the layer I have been working on with Impact Boundary Labs.&lt;br&gt;
The first implementation is GitHub-first:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;agents can read repositories directly, but write intents go through a deterministic gateway that creates reviewable Pull Requests with evidence.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;GitHub is not the whole idea, it is just the first concrete place to prove the pattern, because repositories have clear state, branches, commits, PRs, and review.&lt;/p&gt;

&lt;p&gt;The broader principle is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Let agents reason.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Stop them at intent.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Control what becomes outcome.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Project: &lt;a href="https://impactboundarylabs.com/" rel="noopener noreferrer"&gt;Impact Boundary Labs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is my very first article here on dev.to! I’d love to hear your thoughts on this architecture. How are you currently securing your agent workflows?&lt;/p&gt;

&lt;p&gt;Since I'm new here, I'm highly open to feedback - let me know in the comments what I can improve or what we should talk about in Part 2!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>github</category>
      <category>security</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
