<?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: Kunwar Harshit</title>
    <description>The latest articles on DEV Community by Kunwar Harshit (@hrshitkunwartech).</description>
    <link>https://dev.to/hrshitkunwartech</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%2F4118165%2F9ae2f884-dc5f-4b17-88a2-1c6202005d17.png</url>
      <title>DEV Community: Kunwar Harshit</title>
      <link>https://dev.to/hrshitkunwartech</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hrshitkunwartech"/>
    <language>en</language>
    <item>
      <title>The approval gate: a pattern for AI agents that write to production systems</title>
      <dc:creator>Kunwar Harshit</dc:creator>
      <pubDate>Wed, 09 Sep 2026 20:34:38 +0000</pubDate>
      <link>https://dev.to/hrshitkunwartech/the-approval-gate-a-pattern-for-ai-agents-that-write-to-production-systems-471f</link>
      <guid>https://dev.to/hrshitkunwartech/the-approval-gate-a-pattern-for-ai-agents-that-write-to-production-systems-471f</guid>
      <description>&lt;p&gt;Most AI agent demos end at the moment the agent decides something. The interesting engineering starts right after that, when the agent writes to a system other people depend on.&lt;/p&gt;

&lt;p&gt;I build automation that writes to CRMs, ticketing systems and inboxes for revenue teams. Those are systems of record. A wrong write is not a bad answer you can regenerate. It is a support ticket, a corrupted forecast, or an email a customer has already read.&lt;/p&gt;

&lt;p&gt;Here is the pattern that survived contact with production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode
&lt;/h2&gt;

&lt;p&gt;The naive design gives the agent credentials and lets it call the API directly.&lt;/p&gt;

&lt;p&gt;This works most of the time. Most of the time is the problem. At 95 percent action accuracy, one in twenty writes is wrong, and those writes compound into a database humans use to make decisions. The cleanup cost per bad write is much higher than the time saved per good write, so the automation goes net negative while still looking successful in your metrics.&lt;/p&gt;

&lt;p&gt;The second order failure is worse. Once users find two or three wrong records they stop trusting the whole dataset and go back to their spreadsheets. Now you have added a system nobody trusts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern: propose, diff, approve, execute, log
&lt;/h2&gt;

&lt;p&gt;Split the decision from the side effect. The agent never touches the target API. It emits a proposed action, and a separate executor applies it only after approval.&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;"action_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"act_01H8Z..."&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;"crm.opportunity.update"&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;"system"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"salesforce"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Opportunity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0061..."&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;"diff"&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;"StageName"&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;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Discovery"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Proposal"&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;"CloseDate"&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;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-31"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-11-30"&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;span class="nl"&gt;"evidence"&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;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"call_2291"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"let us aim to get paperwork out next month"&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;"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;"pending_approval"&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_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"call_2291:opp_0061:stage"&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;Four things make this work in practice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A diff, not a payload.&lt;/strong&gt; Show the reviewer &lt;code&gt;from&lt;/code&gt; and &lt;code&gt;to&lt;/code&gt;. A human can approve or reject a diff in about two seconds. Nobody actually reviews a raw JSON payload, so if you show a payload you have built an approval step that everyone rubber stamps, which is the same as having no gate at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence attached to the claim.&lt;/strong&gt; Every proposed change carries the quote or source that produced it. That is what makes review fast, and what makes the audit log useful six weeks later when someone asks why a field changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idempotency keys.&lt;/strong&gt; Approval is asynchronous and humans double click. Derive the key from the source event plus the target field so a replay is a no-op instead of a duplicate ticket.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reversibility.&lt;/strong&gt; Store the &lt;code&gt;from&lt;/code&gt; values, not just the &lt;code&gt;to&lt;/code&gt;. Every applied action gets an inverse. Undo turns a scary automation into one people will actually leave switched on, and it is nearly free to implement once you have captured the diff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preflight validation, before a human ever sees it
&lt;/h2&gt;

&lt;p&gt;Do not spend human attention on actions that were going to fail anyway. Before an action enters the approval queue, validate it against the target system. Does the field exist, is the picklist value legal, does the record still exist, does this user have write permission on it.&lt;/p&gt;

&lt;p&gt;Most bad proposals are not judgment errors. They are schema errors, and schema errors are cheap to catch in code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to log
&lt;/h2&gt;

&lt;p&gt;Log the whole envelope, not just the outcome. Proposal, evidence, reviewer, decision, timestamp, applied result, and the inverse. If you can answer "who approved this, based on what, and how do I undo it" from a single table, you can pass a security review and you can debug production.&lt;/p&gt;

&lt;p&gt;Do not log the raw customer content that produced the action beyond the specific evidence quote. You want interaction patterns and decisions, not a shadow copy of your customers' data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tradeoff
&lt;/h2&gt;

&lt;p&gt;This is slower per action than full autonomy, and that is fine. Reviewing a diff is cheap. Recovering trust after a bad automated write is expensive, and often you do not get the chance.&lt;/p&gt;

&lt;p&gt;Autonomy is the part that demos well. The gate is the part that decides whether the system is still switched on in six months.&lt;/p&gt;

&lt;p&gt;I build this pattern for revenue teams at &lt;a href="https://mindlyft.in" rel="noopener noreferrer"&gt;Mindlyft&lt;/a&gt;, where agents draft the post-call CRM updates, tickets and follow ups, and a human approves before anything ships.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>softwareengineering</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
