<?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>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;br&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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;agent reads the repo&lt;/li&gt;
&lt;li&gt;agent decides what to change&lt;/li&gt;
&lt;li&gt;agent has a GitHub token&lt;/li&gt;
&lt;li&gt;agent creates commits, branches, or PRs&lt;/li&gt;
&lt;/ul&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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;agent reads directly&lt;/li&gt;
&lt;li&gt;agent proposes intent&lt;/li&gt;
&lt;li&gt;a boundary decides&lt;/li&gt;
&lt;li&gt;an adapter materializes only admitted work&lt;/li&gt;
&lt;/ul&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 things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;is this actor allowed?&lt;/li&gt;
&lt;li&gt;is this repo allowed?&lt;/li&gt;
&lt;li&gt;is this path in scope?&lt;/li&gt;
&lt;li&gt;does the source state still match?&lt;/li&gt;
&lt;li&gt;is this operation allowed?&lt;/li&gt;
&lt;li&gt;was the same effect already created?&lt;/li&gt;
&lt;li&gt;should this become a reviewable PR?&lt;/li&gt;
&lt;/ul&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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;where can the agent run?&lt;/li&gt;
&lt;li&gt;can it use the network?&lt;/li&gt;
&lt;li&gt;can it execute commands?&lt;/li&gt;
&lt;li&gt;which files can it access?&lt;/li&gt;
&lt;li&gt;can it escape the environment?&lt;/li&gt;
&lt;/ul&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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It creates a branch.&lt;/li&gt;
&lt;li&gt;It creates commits.&lt;/li&gt;
&lt;li&gt;It creates review work.&lt;/li&gt;
&lt;li&gt;It changes the state of the repository.&lt;/li&gt;
&lt;/ul&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:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;agent reads repository&lt;/li&gt;
&lt;li&gt;agent submits structured intent&lt;/li&gt;
&lt;li&gt;gateway checks state, scope, policy, and idempotency&lt;/li&gt;
&lt;li&gt;GitHub adapter creates a reviewable PR only after admission&lt;/li&gt;
&lt;li&gt;PR contains evidence about the decision&lt;/li&gt;
&lt;/ol&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;ul&gt;
&lt;li&gt;A boundary like this does not prove that the generated code is good.&lt;/li&gt;
&lt;li&gt;It does not replace CI.&lt;/li&gt;
&lt;li&gt;It does not replace human review.&lt;/li&gt;
&lt;li&gt;It does not prove semantic correctness.&lt;/li&gt;
&lt;li&gt;It only controls the transition from proposed work to external impact.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That narrower claim is the whole point. I think many agent systems mix three things together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;reasoning&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;decision&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;impact&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But these should be separated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The agent owns reasoning.&lt;/li&gt;
&lt;li&gt;The boundary owns the decision.&lt;/li&gt;
&lt;li&gt;The adapter owns controlled materialization.&lt;/li&gt;
&lt;li&gt;The target system should only receive admitted impact.&lt;/li&gt;
&lt;/ul&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;br&gt;
For every real outcome, I want to be able to ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what did the agent propose?&lt;/li&gt;
&lt;li&gt;what state did it read?&lt;/li&gt;
&lt;li&gt;which rules were checked?&lt;/li&gt;
&lt;li&gt;why was it admitted or blocked?&lt;/li&gt;
&lt;li&gt;what outcome was created?&lt;/li&gt;
&lt;li&gt;can a human review it?&lt;/li&gt;
&lt;li&gt;can we audit it later?&lt;/li&gt;
&lt;/ul&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>devtools</category>
      <category>github</category>
      <category>security</category>
    </item>
  </channel>
</rss>
