<?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: veobix</title>
    <description>The latest articles on DEV Community by veobix (@veobix).</description>
    <link>https://dev.to/veobix</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%2F4034812%2Ff9db9182-f6be-4b19-b9ba-0264afe7fee2.png</url>
      <title>DEV Community: veobix</title>
      <link>https://dev.to/veobix</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/veobix"/>
    <language>en</language>
    <item>
      <title>The Action Boundary: A Practical Safety Pattern for AI Agents</title>
      <dc:creator>veobix</dc:creator>
      <pubDate>Sat, 18 Jul 2026 06:32:39 +0000</pubDate>
      <link>https://dev.to/veobix/the-action-boundary-a-practical-safety-pattern-for-ai-agents-2loh</link>
      <guid>https://dev.to/veobix/the-action-boundary-a-practical-safety-pattern-for-ai-agents-2loh</guid>
      <description>&lt;p&gt;The most important line in an AI agent is not the prompt. It is the line where&lt;br&gt;
the program turns a suggestion into an external effect.&lt;/p&gt;

&lt;p&gt;Before that line, the system can classify, summarize, compare, and propose.&lt;br&gt;
After it, the system may send a reply, modify a ticket, update a deployment,&lt;br&gt;
publish content, or delete data. Treating both sides as one uninterrupted&lt;br&gt;
"agent loop" makes reviews difficult and failures expensive.&lt;/p&gt;

&lt;p&gt;This tutorial builds a small action boundary with four explicit states:&lt;br&gt;
PROPOSED, APPROVED, EXECUTED, and STOPPED. The pattern is intentionally simple.&lt;br&gt;
It can sit between an LLM and almost any tool adapter.&lt;/p&gt;
&lt;h2&gt;
  
  
  Begin with the failure you want to prevent
&lt;/h2&gt;

&lt;p&gt;Imagine a deployment assistant reading an issue:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The staging health check is green. Please roll back the broken production&lt;br&gt;
release using the previous image.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The issue also contains an old log line naming a different service. The model&lt;br&gt;
selects the rollback tool and uses the service name from the log instead of the&lt;br&gt;
authenticated repository context. The command is syntactically valid. The&lt;br&gt;
wrong service rolls back successfully.&lt;/p&gt;

&lt;p&gt;Adding "be careful" to the prompt does not create a trustworthy control. The&lt;br&gt;
system needs a typed target, a policy check, an approval bound to the final&lt;br&gt;
proposal, and a tool that rejects mismatched context.&lt;/p&gt;
&lt;h2&gt;
  
  
  Model a proposal instead of an instruction
&lt;/h2&gt;

&lt;p&gt;Do not pass free-form model text directly into a write-capable tool. Convert it&lt;br&gt;
to a small structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asdict&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StrEnum&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sha256&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StrEnum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;PROPOSED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proposed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;APPROVED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approved&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;EXECUTED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;executed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;STOPPED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stopped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ActionProposal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;expected_current_version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;requested_version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;source_reference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;encoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;asdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;separators&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoded&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;approve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ActionProposal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shown_digest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;current_digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_digest&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;shown_digest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proposal changed while awaiting approval&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;current_digest&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ActionProposal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;approval_digest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;observed_version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;result_store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;approval_digest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;PermissionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approval does not match proposal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;observed_version&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expected_current_version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target changed after proposal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result_store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requested_version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result_store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The digest is not a security boundary by itself. Its purpose is to make one&lt;br&gt;
approval refer to one immutable proposal. Authentication and authorization&lt;br&gt;
still belong in the surrounding application and tool adapter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the first implementation read-only
&lt;/h2&gt;

&lt;p&gt;Before implementing execute(), build a view that renders ActionProposal.&lt;br&gt;
Connect it to a fixture or a read-only endpoint. Ask users to verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the target unambiguous?&lt;/li&gt;
&lt;li&gt;Is the current state correct?&lt;/li&gt;
&lt;li&gt;Is the proposed state exact?&lt;/li&gt;
&lt;li&gt;Is the source evidence relevant?&lt;/li&gt;
&lt;li&gt;What would make you reject this action?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This phase often reveals that the model lacks a required identifier or that the&lt;br&gt;
source data is ambiguous. Finding that problem during a preview is much cheaper&lt;br&gt;
than finding it after a successful API response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep untrusted text away from authority
&lt;/h2&gt;

&lt;p&gt;Tickets, documents, web pages, and tool responses may contain instructions that&lt;br&gt;
look official but are not. Put them in data fields and label their source.&lt;br&gt;
Allowed actions, target scopes, and permission rules should come from trusted&lt;br&gt;
configuration.&lt;/p&gt;

&lt;p&gt;The tool adapter should receive a typed target that has already passed&lt;br&gt;
authorization. It should obtain its credential separately. A model does not&lt;br&gt;
need to read a deployment key in order to propose a deployment.&lt;/p&gt;

&lt;p&gt;Log the credential alias or permission scope, never the credential value.&lt;br&gt;
Avoid dumping complete prompts into logs because they may contain copied&lt;br&gt;
private data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make approval precise
&lt;/h2&gt;

&lt;p&gt;A confirmation dialog should show the same proposal that execute() will use.&lt;br&gt;
Include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;target and environment;&lt;/li&gt;
&lt;li&gt;observed current version;&lt;/li&gt;
&lt;li&gt;requested version;&lt;/li&gt;
&lt;li&gt;source reference;&lt;/li&gt;
&lt;li&gt;reversibility;&lt;/li&gt;
&lt;li&gt;exact side effect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any field changes after the preview, compute a new digest and request a new&lt;br&gt;
approval. Do not allow an old approval to authorize a corrected target.&lt;/p&gt;

&lt;p&gt;For low-risk reads, approval is unnecessary. For a production write, the&lt;br&gt;
approval actor may need a specific role. Keep that policy outside the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design retries before the happy path
&lt;/h2&gt;

&lt;p&gt;The idempotency key in the example protects against a common uncertainty: the&lt;br&gt;
server may complete the action while the client times out. A retry can return&lt;br&gt;
the stored result instead of repeating the effect.&lt;/p&gt;

&lt;p&gt;Choose an idempotency key for the intended operation, not for a network attempt.&lt;br&gt;
Store the external reference returned by the platform. Include the operation&lt;br&gt;
key, proposal digest, approval actor, attempt count, and result status in&lt;br&gt;
structured logs.&lt;/p&gt;

&lt;p&gt;Do not log the whole input by default. Record enough to reconstruct the control&lt;br&gt;
flow while keeping private payloads and credentials out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a real stop state
&lt;/h2&gt;

&lt;p&gt;STOPPED should be reachable without deploying new code. A circuit breaker can&lt;br&gt;
disable write adapters while preserving read-only proposals.&lt;/p&gt;

&lt;p&gt;Useful triggers include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;target version mismatch;&lt;/li&gt;
&lt;li&gt;validation failures above a threshold;&lt;/li&gt;
&lt;li&gt;repeated authorization errors;&lt;/li&gt;
&lt;li&gt;platform warnings;&lt;/li&gt;
&lt;li&gt;unknown target types;&lt;/li&gt;
&lt;li&gt;too many retries;&lt;/li&gt;
&lt;li&gt;missing rollback information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test the stop path. A configuration flag that nobody has exercised is a hope,&lt;br&gt;
not a control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write recovery instructions
&lt;/h2&gt;

&lt;p&gt;Some actions can be reversed. Store the previous value and verify that the&lt;br&gt;
rollback target still matches before restoring it.&lt;/p&gt;

&lt;p&gt;Other actions are irreversible. You cannot unsend a message or erase the fact&lt;br&gt;
that a public post appeared. Recovery then means stopping the sequence,&lt;br&gt;
identifying affected targets, notifying an accountable person, and preparing a&lt;br&gt;
correction.&lt;/p&gt;

&lt;p&gt;Write this down next to the operation definition. The absence of a true&lt;br&gt;
rollback may justify a stronger approval rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review the whole chain
&lt;/h2&gt;

&lt;p&gt;A useful pre-production review follows the action from input to effect:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;untrusted content enters as labeled data;&lt;/li&gt;
&lt;li&gt;the model creates a typed proposal;&lt;/li&gt;
&lt;li&gt;validation checks required fields and target scope;&lt;/li&gt;
&lt;li&gt;the user sees the exact proposal;&lt;/li&gt;
&lt;li&gt;approval binds to the proposal digest;&lt;/li&gt;
&lt;li&gt;the adapter checks current state and authorization;&lt;/li&gt;
&lt;li&gt;an idempotency key prevents duplicates;&lt;/li&gt;
&lt;li&gt;structured logs record the decision;&lt;/li&gt;
&lt;li&gt;stop and recovery paths remain available.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is more work than calling a tool directly. It is also a design you can&lt;br&gt;
inspect, test, and improve when something fails.&lt;/p&gt;

&lt;p&gt;Disclosure: I maintain the free checklist below. It contains synthetic cases&lt;br&gt;
for deciding when an AI support assistant may answer, needs human review, or&lt;br&gt;
must escalate. The routing model is a useful starting point for testing your&lt;br&gt;
own action boundary.&lt;/p&gt;

&lt;p&gt;Free resource: &lt;a href="https://angonpl.gumroad.com/l/25-ai-support-safety-tests?utm_source=dev&amp;amp;utm_medium=organic&amp;amp;utm_campaign=hybrid-growth-2026-07-18" rel="noopener noreferrer"&gt;https://angonpl.gumroad.com/l/25-ai-support-safety-tests?utm_source=dev&amp;amp;utm_medium=organic&amp;amp;utm_campaign=hybrid-growth-2026-07-18&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>automation</category>
      <category>security</category>
    </item>
  </channel>
</rss>
