<?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: Tej Pandya</title>
    <description>The latest articles on DEV Community by Tej Pandya (@tej_pandya_a973bc2256ba93).</description>
    <link>https://dev.to/tej_pandya_a973bc2256ba93</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%2F4141519%2Fa63b18cd-6839-4c3b-b662-4e30eac5927e.png</url>
      <title>DEV Community: Tej Pandya</title>
      <link>https://dev.to/tej_pandya_a973bc2256ba93</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tej_pandya_a973bc2256ba93"/>
    <language>en</language>
    <item>
      <title>A Brief, a Gate and a Log: What an Agent-Era Agency Sells, in Code</title>
      <dc:creator>Tej Pandya</dc:creator>
      <pubDate>Mon, 19 Oct 2026 05:05:42 +0000</pubDate>
      <link>https://dev.to/tej_pandya_a973bc2256ba93/a-brief-a-gate-and-a-log-what-an-agent-era-agency-sells-in-code-5bg2</link>
      <guid>https://dev.to/tej_pandya_a973bc2256ba93/a-brief-a-gate-and-a-log-what-an-agent-era-agency-sells-in-code-5bg2</guid>
      <description>&lt;p&gt;If agencies sell judgment and a working system instead of hours, the product is a small "pod" setup: a brief an agent can follow, a gate a human must pass, and a log. This post sketches that in Python. It is an illustration, not a customer story, and I have no results to share.&lt;/p&gt;

&lt;p&gt;The idea comes from public writing by BCG and Google on agent-native marketing. Both say human marketers move toward direction and oversight, and that work is checked before it goes out.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. A brief the agent can follow
&lt;/h2&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;field&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Brief&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;goal&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;audience&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;must_say&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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;must_not_say&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;                 &lt;span class="c1"&gt;# the accountable human
&lt;/span&gt;    &lt;span class="n"&gt;learning_question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;     &lt;span class="c1"&gt;# what we want to learn from this run
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. A review gate
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Draft&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;text&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;brief&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Brief&lt;/span&gt;
    &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;list&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;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Draft&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Draft&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;phrase&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brief&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;must_not_say&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;phrase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;banned phrase: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;phrase&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;phrase&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brief&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;must_say&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;phrase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;phrase&lt;/span&gt;&lt;span class="si"&gt;}&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;draft&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Human sign-off and a log
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Draft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;approved_by&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;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;release_log.jsonl&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;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flags&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fix flags first: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;entry&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;ts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brief&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approved_by&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;approved_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;goal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brief&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;goal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;learning_question&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brief&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;learning_question&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&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="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&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;entry&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What this shows
&lt;/h2&gt;

&lt;p&gt;The agent writes the draft. The code checks it. A named person approves it. The log says who. The four things an agency can still sell sit in these 40 lines: strategy (the brief), system design (the gate), oversight (the approval) and accountability (the owner field).&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;This catches only phrase rules. It does not judge tone or truth. A real setup needs a human reader and your own legal review. This is a sketch.&lt;/p&gt;

&lt;p&gt;Tej Pandya, founder of GrowEasy.ai.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>python</category>
    </item>
    <item>
      <title>A Control Plane for an AI-Run Firm: Workflow, Agent and Human Lanes</title>
      <dc:creator>Tej Pandya</dc:creator>
      <pubDate>Sun, 18 Oct 2026 05:04:02 +0000</pubDate>
      <link>https://dev.to/tej_pandya_a973bc2256ba93/a-control-plane-for-an-ai-run-firm-workflow-agent-and-human-lanes-5f9d</link>
      <guid>https://dev.to/tej_pandya_a973bc2256ba93/a-control-plane-for-an-ai-run-firm-workflow-agent-and-human-lanes-5f9d</guid>
      <description>&lt;p&gt;"Autonomous company" sounds like a model choice. In practice it is a control-plane design. This post sketches one for a small service firm doing lead intake. The firm is illustrative, not a customer story, and I have no results to share.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;Split every task into one of three lanes and enforce the lane in code.&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;enum&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Lane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;WORKFLOW&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fixed steps, no model decisions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;AGENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model picks steps, inside limits&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;HUMAN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;person decides&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&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;lane&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Lane&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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;needs_approval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="n"&gt;TASKS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;capture_lead&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="n"&gt;Lane&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WORKFLOW&lt;/span&gt;&lt;span class="p"&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;crm.create&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;send_reminder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;Lane&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WORKFLOW&lt;/span&gt;&lt;span class="p"&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;email.send_template&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draft_reply&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="n"&gt;Lane&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AGENT&lt;/span&gt;&lt;span class="p"&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;crm.read&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;email.draft&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quote_price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="n"&gt;Lane&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HUMAN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="p"&gt;[]),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anthropic's guide says coded workflows give predictability for well-defined tasks, and that agents bring higher cost and compounding errors, so test them in sandboxes with guardrails. Salesforce describes "hybrid reasoning" that combines deterministic logic and adaptive AI. The lanes above are one simple way to do that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The router
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&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;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lane&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Lane&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HUMAN&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;needs_approval&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;queue_for_human&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&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;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lane&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Lane&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WORKFLOW&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;run_steps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&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;run_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allowed_tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things live here and not in a prompt: which tools a task may touch, which tasks need approval, and what gets logged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Incident handling
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;PAUSED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&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;pause&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task_name&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;who&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;PAUSED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;paused&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;who&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check &lt;code&gt;PAUSED&lt;/code&gt; at the start of &lt;code&gt;route&lt;/code&gt;. Someone must own the pause, and the log must say who used it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Red lines to code first
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Money, contracts and customer promises always go to a person.&lt;/li&gt;
&lt;li&gt;Every agent action has a log entry with inputs and outputs.&lt;/li&gt;
&lt;li&gt;Every task has a named owner and an off switch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this is not
&lt;/h2&gt;

&lt;p&gt;It is not proof that a company can run itself. Microsoft's 2025 Work Trend Index describes humans setting direction and "checking in as needed". The control plane is how that check-in becomes real.&lt;/p&gt;

&lt;p&gt;Tej Pandya, founder of GrowEasy.ai.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Make Your Store Callable by AI Assistants: A Feed and Tool Contract Sketch</title>
      <dc:creator>Tej Pandya</dc:creator>
      <pubDate>Sat, 17 Oct 2026 05:09:54 +0000</pubDate>
      <link>https://dev.to/tej_pandya_a973bc2256ba93/make-your-store-callable-by-ai-assistants-a-feed-and-tool-contract-sketch-225</link>
      <guid>https://dev.to/tej_pandya_a973bc2256ba93/make-your-store-callable-by-ai-assistants-a-feed-and-tool-contract-sketch-225</guid>
      <description>&lt;p&gt;If an assistant is going to buy from you, two things must be machine-readable: what you sell (the feed) and what the assistant may do (the tool). This post sketches both. It is a design sketch, not a copy of any platform spec.&lt;/p&gt;

&lt;p&gt;The seller here is an illustrative shoe retailer, not a customer story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: the feed
&lt;/h2&gt;

&lt;p&gt;OpenAI's product feed docs describe structured catalog data that lets ChatGPT show products with accurate pricing, availability and seller context. Google's UCP guide says it builds on existing Merchant Center shopping feeds. So start with clean rows:&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;"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;"shoe-123-42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Trail runner, size 42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&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="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"89.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"availability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"in_stock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"seller"&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="nl"&gt;"name"&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 Shoes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"return_days"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/p/shoe-123"&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;Check your platform's own schema for the real field names. Treat the sketch above as a shape, not a spec.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: the tool contract
&lt;/h2&gt;

&lt;p&gt;Apps in ChatGPT are built on the open Model Context Protocol, so a tool is a name, a typed input and a typed result.&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;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BuyShoe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;product_id&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;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;     &lt;span class="c1"&gt;# same key, same order, no double charge
&lt;/span&gt;    &lt;span class="n"&gt;buyer_approved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;     &lt;span class="c1"&gt;# a person said yes
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;buy_shoe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BuyShoe&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buyer_approved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&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;needs_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="nf"&gt;create_order_once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two choices matter. The idempotency key stops a retry from ordering twice. The approval flag keeps a human in the loop, which matters because most purchases will still need approval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: who stays responsible
&lt;/h2&gt;

&lt;p&gt;Google states that under UCP the merchant stays the Merchant of Record. Your order, refund and customer records stay yours, so your tool should write them the same way as a website order.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to verify before building
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Is your region, plan and industry supported? Announcements name specific surfaces and markets.&lt;/li&gt;
&lt;li&gt;What are the fees and review steps? I did not verify them.&lt;/li&gt;
&lt;li&gt;Does the platform's ranking favor you? Unknown.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next step
&lt;/h2&gt;

&lt;p&gt;Ship the feed first. It helps in any channel. Then add one tool, with a key and an approval step, and watch the logs.&lt;/p&gt;

&lt;p&gt;Tej Pandya, founder of GrowEasy.ai.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ecommerce</category>
      <category>api</category>
    </item>
    <item>
      <title>Turn a Job Role Into an Agent Task Spec: Scope, Limits and Escalation in Code</title>
      <dc:creator>Tej Pandya</dc:creator>
      <pubDate>Sat, 17 Oct 2026 05:04:47 +0000</pubDate>
      <link>https://dev.to/tej_pandya_a973bc2256ba93/turn-a-job-role-into-an-agent-task-spec-scope-limits-and-escalation-in-code-1ca7</link>
      <guid>https://dev.to/tej_pandya_a973bc2256ba93/turn-a-job-role-into-an-agent-task-spec-scope-limits-and-escalation-in-code-1ca7</guid>
      <description>&lt;p&gt;When a team says "we want an AI employee", the first job is not picking a model. It is cutting a role into tasks you can specify, limit and check. Here is one way to write that down as code.&lt;/p&gt;

&lt;p&gt;This is an illustrative role (sales operations analyst), not a customer story. I have no performance numbers to share.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: List tasks, not the role
&lt;/h2&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;field&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TaskSpec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&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;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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;allowed_tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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;output&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;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;                      &lt;span class="c1"&gt;# a named human
&lt;/span&gt;    &lt;span class="n"&gt;escalate_when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;workflow&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;agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;workflow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;TASKS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;TaskSpec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;clean_lead_list&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;crm.leads&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;crm.read&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;crm.update_field&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;list of fixes with reasons&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ops_lead&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="n"&gt;escalate_when&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;merge two records&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="nc"&gt;TaskSpec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draft_followup&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lead&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;last_call_notes&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;crm.read&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;email.draft&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;draft only, never sent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sales_rep&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="n"&gt;escalate_when&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;price mentioned&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;refund mentioned&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice &lt;code&gt;mode&lt;/code&gt;. Anthropic's guide separates workflows (fixed code paths) from agents (the model picks the steps) and says agents bring higher cost and compounding errors, so test them in a sandbox with guardrails. Start every task as a workflow. Move one to &lt;code&gt;agent&lt;/code&gt; only when the steps truly cannot be fixed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Enforce the limits outside the model
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TaskSpec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&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;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allowed_tools&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;escalate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool not allowed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="si"&gt;}&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="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;escalate_when&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;escalate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rule hit: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;60&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="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;audit_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step&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="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The allowed list and the stop words live in your code, not in a prompt. A prompt can be argued with. A check in code cannot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Make a person own each task
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;owner&lt;/code&gt; is a required field. If a task has no owner, it does not ship. The owner reviews a weekly sample from the audit log and can switch the task off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Keep the claims small
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Draft, do not send, until the owner trusts a task.&lt;/li&gt;
&lt;li&gt;Log every tool call with inputs and outputs.&lt;/li&gt;
&lt;li&gt;Write the stop rules from real incidents, not guesses.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I would not claim
&lt;/h2&gt;

&lt;p&gt;An agent here is a bundle of tasks with limits. It is not an employee in any legal sense, and this spec does not replace a role. Microsoft's 2025 Work Trend Index describes agents as "digital colleagues" taking tasks at human direction, and calls the person who manages them an "agent boss". That matches the owner field above.&lt;/p&gt;

&lt;p&gt;The next step is a review loop: sample the log, tighten the rules, then widen the task.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tej Pandya, founder of GrowEasy.ai.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Design an Agent-Callable Action: Schema, Idempotency, Scoped Auth and Receipts</title>
      <dc:creator>Tej Pandya</dc:creator>
      <pubDate>Fri, 16 Oct 2026 05:19:39 +0000</pubDate>
      <link>https://dev.to/tej_pandya_a973bc2256ba93/design-an-agent-callable-action-schema-idempotency-scoped-auth-and-receipts-cad</link>
      <guid>https://dev.to/tej_pandya_a973bc2256ba93/design-an-agent-callable-action-schema-idempotency-scoped-auth-and-receipts-cad</guid>
      <description>&lt;p&gt;If you want an AI agent to act on your service without driving your web UI, the useful unit is one well-defined action. This is a design note, not a claim about any vendor's product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just let the agent use the page
&lt;/h2&gt;

&lt;p&gt;A screen has no contract. A change in a button label or a modal can break the flow, and every page the agent reads can contain text written to mislead it. Anthropic's write-up on browser use says prompt injection is a real, unsolved risk for agents that read web pages. A narrow action reduces what an agent reads and what it can do.&lt;/p&gt;

&lt;p&gt;Keep a browser path as a fallback for things you cannot expose. Do not pretend it does not exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Describe the action as a schema
&lt;/h2&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"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"book_appointment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&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;"service_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;"string, from /services"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ISO 8601, must be an open slot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"customer_ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string, issued by your auth"&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;"result"&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;"booking_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;"string"&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;"confirmed | rejected"&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"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string, only if rejected"&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;Fixed fields beat free text. The caller cannot invent a service that does not exist, and you can reject bad input with a clear error.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Make retries safe with an idempotency key
&lt;/h2&gt;

&lt;p&gt;Agents retry. Networks fail. Ask for a key on every action that changes something.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /book
Idempotency-Key: 7f3c-example
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the same key arrives twice, return the first result and do not book twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Scope the permission
&lt;/h2&gt;

&lt;p&gt;Give the agent a credential that can do only this action, for this customer, for a limited time. Do not hand over a full account login. If the action can spend money or send a message, require the customer to confirm it.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Return a receipt
&lt;/h2&gt;

&lt;p&gt;Every successful action returns an ID and a plain summary the agent can show the customer: what was done, when, and how to undo it.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Log the call
&lt;/h2&gt;

&lt;p&gt;Record who asked, what was asked, and what you returned. When an agent and a customer disagree, this is the record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it is heading
&lt;/h2&gt;

&lt;p&gt;Google's Universal Commerce Protocol guide describes an open standard for agentic actions, starting with direct buying in AI Mode in Search and Gemini. Whether you use that or your own endpoint, the same four pieces apply.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;Many services have no API today, and for them a browser is the only route. An API with a broad key is also not safe. The design above is a starting point I think is worth testing.&lt;/p&gt;

&lt;p&gt;I'm Tej Pandya, founder of GrowEasy.ai.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Build a Per-Customer Product List Without Letting the Model Invent Products</title>
      <dc:creator>Tej Pandya</dc:creator>
      <pubDate>Fri, 16 Oct 2026 05:05:22 +0000</pubDate>
      <link>https://dev.to/tej_pandya_a973bc2256ba93/build-a-per-customer-product-list-without-letting-the-model-invent-products-9h3</link>
      <guid>https://dev.to/tej_pandya_a973bc2256ba93/build-a-per-customer-product-list-without-letting-the-model-invent-products-9h3</guid>
      <description>&lt;p&gt;If an assistant builds a product list for one customer at the moment they ask, the hard part is not the language model. The hard part is making sure every item on the list is real, in stock and priced right. This is a design sketch for that, written as a forecast of one pattern, not as a description of a shipped product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the rule: the model never creates inventory
&lt;/h2&gt;

&lt;p&gt;Public examples show the right split. Zomato's MCP server exposes restaurant discovery, menu browsing with prices, cart creation and ordering. The AI tool calls those functions and reads real menus. It does not make up dishes.&lt;/p&gt;

&lt;p&gt;Use the same rule in your own system. The assistant can choose, filter, rank and explain. It cannot add an item that is not in your product records.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Intent in.&lt;/strong&gt; Customer message arrives in chat, for example from a click-to-WhatsApp ad.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Classify.&lt;/strong&gt; A small classification step turns free text into a typed decision: category, budget band, urgency, confidence. TypeSafe describes its Jev model this way: unstructured input in, typed probabilistic decisions out, and no free text generation. A classifier is a good fit because the next code step needs a value from a fixed list, not a paragraph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Branch on confidence.&lt;/strong&gt; Below a threshold, ask one clarifying question or hand over to a person.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieve.&lt;/strong&gt; Query your own product store with the typed fields. Pull from approved documents for product facts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check live state.&lt;/strong&gt; Re-read price and stock right before showing the list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rank and explain.&lt;/strong&gt; The language model writes a one-line reason per item, using only fields it was given.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review.&lt;/strong&gt; The customer confirms. Nothing is bought without that step.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A schema that keeps the model honest
&lt;/h2&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;"intent"&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="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string (enum)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"budget_max"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0..1"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items"&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="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"must exist in product store"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"from live read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"in_stock"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"from live read"&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"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"max 20 words, uses only retrieved fields"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"sponsored"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"boolean"&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="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;Reject the whole response if any sku is not found in the store. Do not try to repair it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guardrails worth writing as tests
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unknown sku:&lt;/strong&gt; response is dropped and logged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale price:&lt;/strong&gt; if the live read differs from the retrieved value, show the live value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sponsored items:&lt;/strong&gt; always flagged, never hidden inside "recommended".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personal data:&lt;/strong&gt; only use data the customer agreed to share. The FTC's January 2025 surveillance pricing study press release is a good reason to keep price rules the same for everyone unless you can explain a difference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reorder and compare:&lt;/strong&gt; keep a stable category view. People still want it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Keep the feed clean
&lt;/h2&gt;

&lt;p&gt;Everything above depends on clean records: title, plain description, price, availability. Google Search Central tells merchants to share product data through structured data and Merchant Center feeds, and says the Content API suits quick stock updates. If your data is wrong, a per-customer list only gets the wrong answer faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I am not claiming
&lt;/h2&gt;

&lt;p&gt;I am not claiming any vendor ships this whole flow today. Amazon's Interests and Rufus, and Spotify's Prompted Playlist beta, show pieces: prompt-driven lists, memory, refresh. The full pipeline above is a design I think is worth testing.&lt;/p&gt;

&lt;p&gt;I'm Tej Pandya, founder of GrowEasy.ai.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>Define a Lead Handoff Record Before You Report Lead Counts</title>
      <dc:creator>Tej Pandya</dc:creator>
      <pubDate>Tue, 13 Oct 2026 05:06:02 +0000</pubDate>
      <link>https://dev.to/tej_pandya_a973bc2256ba93/define-a-lead-handoff-record-before-you-report-lead-counts-3bj9</link>
      <guid>https://dev.to/tej_pandya_a973bc2256ba93/define-a-lead-handoff-record-before-you-report-lead-counts-3bj9</guid>
      <description>&lt;p&gt;By Tej Pandya, founder of GrowEasy.ai&lt;/p&gt;

&lt;p&gt;A lead count is easy to report. A handoff is harder to define. This post sketches a small record that makes the handoff checkable. It is an illustrative design, not a deployed GrowEasy.ai feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Name the fields the next person needs
&lt;/h2&gt;

&lt;p&gt;A record the sales team can act on might hold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;lead&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;source&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;campaign&lt;/span&gt;
&lt;span class="k"&gt;stated&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;intent&lt;/span&gt;
&lt;span class="k"&gt;budget&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;signal&lt;/span&gt;
&lt;span class="k"&gt;timing&lt;/span&gt;
&lt;span class="k"&gt;question&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;asked&lt;/span&gt;
&lt;span class="k"&gt;contact&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;consent&lt;/span&gt;
&lt;span class="k"&gt;next&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;owner&lt;/span&gt;
&lt;span class="k"&gt;handoff&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;status&lt;/span&gt;        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;offered&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;accepted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;rejected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;expired&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;rejection&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;reason&lt;/span&gt;
&lt;span class="k"&gt;downstream&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;stage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a starting point. Each team should agree its own fields with the vendor sending the leads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Count status changes, not rows
&lt;/h2&gt;

&lt;p&gt;A raw lead is a row. A handoff has states. Track offered, accepted and rejected, and keep the reason when someone rejects it. A rejection reason is data a form fill never gives you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feed stages back to the ad platform
&lt;/h2&gt;

&lt;p&gt;Google Ads documents qualified lead and converted lead goals so stages tracked offline in a CRM can feed back into optimization. It also provides a lead funnel report from interactions to converted leads. See &lt;a href="https://support.google.com/google-ads/answer/11459091?hl=en" rel="noopener noreferrer"&gt;https://support.google.com/google-ads/answer/11459091?hl=en&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enhanced conversions for leads adds user-provided data, such as an email, to imported offline results to improve accuracy and bidding. Google notes the upload route is moving to the Data Manager API, so check the current method before you build. See &lt;a href="https://support.google.com/google-ads/answer/15713840?hl=en" rel="noopener noreferrer"&gt;https://support.google.com/google-ads/answer/15713840?hl=en&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For Meta, a third-party Zapier guide advises picking a middle funnel stage that happens often enough to learn from and inside the platform's attribution window, which that guide gives as 28 days. This is not Meta documentation, so check Meta's own docs. See &lt;a href="https://zapier.com/blog/meta-guide-to-better-lead-quality/" rel="noopener noreferrer"&gt;https://zapier.com/blog/meta-guide-to-better-lead-quality/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the stage on purpose
&lt;/h2&gt;

&lt;p&gt;Do not send only the sale if your sales cycle is long. A stage like "discovery call completed" may arrive in time and often enough to matter. Pick it with your sales team, not with the ad vendor alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check consent and data quality first
&lt;/h2&gt;

&lt;p&gt;Do not send contact data back to an ad platform unless your consent terms allow it. Dirty CRM stages send a dirty signal, so fix stage definitions before you automate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the record
&lt;/h2&gt;

&lt;p&gt;Try a lead with no consent, a rejected handoff with a reason, a handoff accepted after the window closes, and a duplicate lead. Check the record and the owner in each case.&lt;/p&gt;

&lt;p&gt;Sources:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://support.google.com/google-ads/answer/11459091?hl=en" rel="noopener noreferrer"&gt;https://support.google.com/google-ads/answer/11459091?hl=en&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://support.google.com/google-ads/answer/15713840?hl=en" rel="noopener noreferrer"&gt;https://support.google.com/google-ads/answer/15713840?hl=en&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://zapier.com/blog/meta-guide-to-better-lead-quality/" rel="noopener noreferrer"&gt;https://zapier.com/blog/meta-guide-to-better-lead-quality/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>Model a Call-to-CRM Handoff as Linked Events, Not One Summary</title>
      <dc:creator>Tej Pandya</dc:creator>
      <pubDate>Mon, 12 Oct 2026 05:05:49 +0000</pubDate>
      <link>https://dev.to/tej_pandya_a973bc2256ba93/model-a-call-to-crm-handoff-as-linked-events-not-one-summary-554p</link>
      <guid>https://dev.to/tej_pandya_a973bc2256ba93/model-a-call-to-crm-handoff-as-linked-events-not-one-summary-554p</guid>
      <description>&lt;p&gt;By Tej Pandya, founder of GrowEasy.ai&lt;/p&gt;

&lt;p&gt;A call-first sales workflow crosses several boundaries: the call provider, a transcript, messaging history and the accepted customer record. A single "latest summary" field hides those boundaries.&lt;/p&gt;

&lt;p&gt;The design below is illustrative, not a deployed GrowEasy.ai feature. Its goal is a handoff that survives delayed sources and uncertain identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the source event and the customer identity separate
&lt;/h2&gt;

&lt;p&gt;Store the provider's event identifier alongside an internal customer identifier and the basis for linking them. Do not make a display name the customer key.&lt;/p&gt;

&lt;p&gt;Twilio Flex documents a concrete reason for this caution: inbound WhatsApp identity can be a Business-Scoped User ID rather than a phone number. Flex also does not display the customer's WhatsApp profile name. Check the identifiers the actual integration returns before joining its messages to a CRM contact.&lt;/p&gt;

&lt;p&gt;A possible event shape is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider_event_id
channel_identity
customer_id
identity_link_basis
source_reference
occurred_at
received_at
proposed_change
accepted_change_reference
review_owner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a starting point, not a universal schema. An unresolved match should remain unresolved rather than create a confident but wrong history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate a completed call from a completed record update
&lt;/h2&gt;

&lt;p&gt;A call ending does not prove that its transcript is ready or that the CRM accepted the next action. Track those outcomes separately.&lt;/p&gt;

&lt;p&gt;DILR's integration analysis describes delayed transcript events, field validation failures and write-back retries. Treat these as cases to test with your provider, not a guarantee that every connector behaves the same way.&lt;/p&gt;

&lt;p&gt;A useful state model can distinguish source received, identity unresolved, proposal prepared, write accepted and review needed. Keep failure ownership visible instead of inferring success from the agent's reply.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recover from current state before accepting a proposed appointment
&lt;/h2&gt;

&lt;p&gt;Suppose a rep changes a visit time while the transcript is being processed. The older conversation should not overwrite the newer accepted appointment without a conflict check.&lt;/p&gt;

&lt;p&gt;Validate the target record, allowed change and current revision. If they disagree with the proposal, keep the current record and route the conflict. Source history should explain a proposed change; it should not automatically outrank a later accepted decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat recording references as access-controlled inputs
&lt;/h2&gt;

&lt;p&gt;Twilio's Recording resource supports fetching and deleting recordings and downloading completed media stored at Twilio. Its recording documentation warns that applicable consent laws must be followed.&lt;/p&gt;

&lt;p&gt;Store the reference and the access decision deliberately. Do not expose an entire recording merely because a rep needs one accepted next step. Recording consent is not a blanket permission for unrelated future use.&lt;/p&gt;

&lt;p&gt;Define how a deleted source affects cached summaries and source links. An export that contains broken media references is not a successful portability test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the handoff instead of testing only the conversation
&lt;/h2&gt;

&lt;p&gt;Try a delayed transcript, an ambiguous channel identity, a repeated event, a failed CRM write and a changed appointment. Verify the accepted record and responsible owner for each case.&lt;/p&gt;

&lt;p&gt;A reliable integration can make the next call easier without replacing the CRM. The architectural goal is continuity with visible uncertainty, not a larger field of prose.&lt;/p&gt;

&lt;p&gt;Sources:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.twilio.com/docs/flex/developer/conversations/receive-inbound-messages-from-sms-and-whatsapp" rel="noopener noreferrer"&gt;https://www.twilio.com/docs/flex/developer/conversations/receive-inbound-messages-from-sms-and-whatsapp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.twilio.com/docs/voice/api/recording" rel="noopener noreferrer"&gt;https://www.twilio.com/docs/voice/api/recording&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.twilio.com/docs/voice/twiml/record" rel="noopener noreferrer"&gt;https://www.twilio.com/docs/voice/twiml/record&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.dilr.ai/blog/voice-ai-crm-telephony-integration-architecture-enterprise" rel="noopener noreferrer"&gt;https://www.dilr.ai/blog/voice-ai-crm-telephony-integration-architecture-enterprise&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>Build an Agent-Friendly Customer Record Before Replacing the CRM Screen</title>
      <dc:creator>Tej Pandya</dc:creator>
      <pubDate>Sun, 11 Oct 2026 05:08:27 +0000</pubDate>
      <link>https://dev.to/tej_pandya_a973bc2256ba93/build-an-agent-friendly-customer-record-before-replacing-the-crm-screen-30hp</link>
      <guid>https://dev.to/tej_pandya_a973bc2256ba93/build-an-agent-friendly-customer-record-before-replacing-the-crm-screen-30hp</guid>
      <description>&lt;p&gt;By Tej Pandya, founder of GrowEasy.ai&lt;/p&gt;

&lt;p&gt;A conversational sales interface is easy to demonstrate. A customer record that survives conflicting updates is harder to build.&lt;/p&gt;

&lt;p&gt;If an agent can read a lead, propose a visit and write a follow-up, the application needs more than a text field for the latest summary. It needs identity, access rules and a way to distinguish proposed changes from accepted state.&lt;/p&gt;

&lt;p&gt;The design below is illustrative. It is not a deployed GrowEasy.ai feature or a customer result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give every customer a stable identifier
&lt;/h2&gt;

&lt;p&gt;Do not use a name as the record key. Two buyers can share a name; one buyer can use several contact routes. Keep external identifiers and source references alongside the internal customer identifier.&lt;/p&gt;

&lt;p&gt;A possible record sketch is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customer_id
source_record_refs
record_revision
record_owner
permitted_contact_state
current_appointment
pending_changes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a starting point, not a universal schema. The fields should follow the business process and its privacy requirements. A model's confidence should not decide whether two customers are merged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate proposed writes against current state
&lt;/h2&gt;

&lt;p&gt;Let the model propose a structured change. Validate the target record, allowed fields, permissions and revision before accepting it.&lt;/p&gt;

&lt;p&gt;If a rep changes the appointment while the agent is preparing a reminder, the older proposal should not silently overwrite the newer time. Return a conflict and recover using the current record.&lt;/p&gt;

&lt;p&gt;The accepted record change and a completed external action are separate outcomes. Saving a callback request is not proof that someone made the call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope access to the actual person and action
&lt;/h2&gt;

&lt;p&gt;Salesforce's custom-action documentation distinguishes employee-facing agents from service agents. It calls for record and field access checks, and verified customer identity scoped to private service-agent actions.&lt;/p&gt;

&lt;p&gt;That is a useful design warning: an agent's execution identity may have broader access than the customer it serves. The application must enforce the customer's boundary. Do not accept an arbitrary record ID from a prompt as proof of ownership.&lt;/p&gt;

&lt;p&gt;These requirements belong to Salesforce's documented product context. They are not a claim about every platform's implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check what the connector really exposes
&lt;/h2&gt;

&lt;p&gt;HubSpot's remote MCP guide describes supported reads and writes with existing user permissions. Availability varies by subscription, permissions and configuration. Sensitive Data settings block activities and conversations through MCP even though standard CRM APIs are a different route.&lt;/p&gt;

&lt;p&gt;Treat the connector's documented coverage as an integration boundary. A natural-language interface cannot make an unavailable object available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design audit coverage instead of assuming it
&lt;/h2&gt;

&lt;p&gt;Microsoft's Dataverse guidance says auditing must be configured at environment, table and column levels. Logs can be delayed and follow retention settings. Record auditing does not cover retrieve and export operations without additional activity logging.&lt;/p&gt;

&lt;p&gt;Before launch, test which changes are recorded, whose identity appears and whether the history can explain a disputed update. Keep only the data the business needs, with an explicit retention policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep a correction screen for unresolved changes
&lt;/h2&gt;

&lt;p&gt;A useful human view shows the current value, proposed value, relevant source and responsible owner. Test two similar customers, stale updates, revoked access and missing source history.&lt;/p&gt;

&lt;p&gt;The aim is not to prove that screens are obsolete. It is to make routine work easier without weakening the record people depend on.&lt;/p&gt;

&lt;p&gt;Sources:&lt;br&gt;
&lt;a href="https://developer.salesforce.com/docs/platform/isvforce/guide/secure-agentforce-actions.html" rel="noopener noreferrer"&gt;https://developer.salesforce.com/docs/platform/isvforce/guide/secure-agentforce-actions.html&lt;/a&gt;&lt;br&gt;
&lt;a href="https://developers.hubspot.com/docs/apps/developer-platform/build-apps/integrate-with-the-remote-hubspot-mcp-server" rel="noopener noreferrer"&gt;https://developers.hubspot.com/docs/apps/developer-platform/build-apps/integrate-with-the-remote-hubspot-mcp-server&lt;/a&gt;&lt;br&gt;
&lt;a href="https://learn.microsoft.com/en-us/power-platform/admin/manage-dataverse-auditing" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/power-platform/admin/manage-dataverse-auditing&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>Model a Sales Reminder as a State Machine, Not a Prompt</title>
      <dc:creator>Tej Pandya</dc:creator>
      <pubDate>Sat, 10 Oct 2026 12:39:13 +0000</pubDate>
      <link>https://dev.to/tej_pandya_a973bc2256ba93/model-a-sales-reminder-as-a-state-machine-not-a-prompt-5ck8</link>
      <guid>https://dev.to/tej_pandya_a973bc2256ba93/model-a-sales-reminder-as-a-state-machine-not-a-prompt-5ck8</guid>
      <description>&lt;p&gt;By Tej Pandya, founder of GrowEasy.ai&lt;/p&gt;

&lt;p&gt;A reminder agent needs somewhere to store what it owes. If the only record is the conversation, developers cannot reliably tell whether an action was planned, attempted or completed.&lt;/p&gt;

&lt;p&gt;This example is a design sketch for an appointment-led sales workflow. It is not a deployed customer result or a GrowEasy.ai feature claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep scheduling state separate from message text
&lt;/h2&gt;

&lt;p&gt;Store an appointment revision, confirmed time, consent state, contact route and reminder status. A change to the appointment should invalidate pending work tied to an older revision.&lt;/p&gt;

&lt;p&gt;The model can extract a proposed time from a message. Application logic validates it before updating the record. Unknown consent stays unknown; it is not filled from the model's confidence.&lt;/p&gt;

&lt;p&gt;An illustrative transition sketch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requested -&amp;gt; confirmed -&amp;gt; reminder_eligible -&amp;gt; attempted
attempted -&amp;gt; delivered | retryable_error | permanent_error
retryable_error -&amp;gt; reconciled | retry_pending | needs_human
needs_human -&amp;gt; assigned -&amp;gt; accepted -&amp;gt; completed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These states are not a universal standard. Choose them around the real work and the provider's actual receipts. Do not interpret "delivered" as "attended".&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconcile ambiguous tool results before retrying
&lt;/h2&gt;

&lt;p&gt;When a request times out, it may already have succeeded. Look up the provider result or reuse a supported idempotency key before repeating the side effect.&lt;/p&gt;

&lt;p&gt;A key might identify the appointment revision and reminder type. It must not suppress a genuinely new reminder after the buyer changes the appointment.&lt;/p&gt;

&lt;p&gt;Temporal's documentation recommends idempotent Activities because retry behavior can repeat execution. Durable state and safe side effects solve different problems; you need both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put eligibility checks immediately before the effect
&lt;/h2&gt;

&lt;p&gt;Before a reminder sends, re-check current appointment state, consent, permitted time and destination. A job that was eligible when queued may no longer be eligible when it runs.&lt;/p&gt;

&lt;p&gt;Treat refusal and revoked consent as stop conditions. Distinguish temporary transport errors from permanent failures. A retry budget is an application decision, not something the model should improvise indefinitely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give the human fallback a real lifecycle
&lt;/h2&gt;

&lt;p&gt;An exception record needs a question, current evidence, requested time and owner. Assignment is not acceptance. An overdue unaccepted task should remain visible to the team.&lt;/p&gt;

&lt;p&gt;Do not tell the buyer that a callback is confirmed if the application only emitted a notification. Gate that sentence on the state that proves a responsible person and valid time exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test changed state and repeated execution
&lt;/h2&gt;

&lt;p&gt;Test a cancellation after queueing, duplicate webhook delivery, two workers claiming the same job, a provider success followed by a lost response, an unavailable owner and an explicit request for a person.&lt;/p&gt;

&lt;p&gt;Check the resulting records and external actions, not only the agent's wording. Compare the design with a simpler deterministic baseline. Anthropic's workflow guidance is useful here: add model-driven flexibility where it is needed rather than turning every predictable step into an autonomous decision.&lt;/p&gt;

&lt;p&gt;A clean state machine cannot fix bad rules. It can make those rules, and their failures, visible enough to test.&lt;/p&gt;

&lt;p&gt;Sources:&lt;br&gt;
&lt;a href="https://www.anthropic.com/engineering/building-effective-agents" rel="noopener noreferrer"&gt;https://www.anthropic.com/engineering/building-effective-agents&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.temporal.io/activity-definition" rel="noopener noreferrer"&gt;https://docs.temporal.io/activity-definition&lt;/a&gt;&lt;br&gt;
&lt;a href="https://temporal.io/blog/idempotency-and-durable-execution" rel="noopener noreferrer"&gt;https://temporal.io/blog/idempotency-and-durable-execution&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>Build an AI Lead Handoff That a Sales Rep Can Actually Use</title>
      <dc:creator>Tej Pandya</dc:creator>
      <pubDate>Fri, 09 Oct 2026 05:10:51 +0000</pubDate>
      <link>https://dev.to/tej_pandya_a973bc2256ba93/build-an-ai-lead-handoff-that-a-sales-rep-can-actually-use-3180</link>
      <guid>https://dev.to/tej_pandya_a973bc2256ba93/build-an-ai-lead-handoff-that-a-sales-rep-can-actually-use-3180</guid>
      <description>&lt;p&gt;The most important output of a sales qualification agent may be the handoff record, not the last message it sends.&lt;/p&gt;

&lt;p&gt;My prediction is that AI will take more of intake and follow-up while humans handle complex selling. For developers, that means the boundary between the two jobs needs a data contract. A long generated summary is not enough.&lt;/p&gt;

&lt;p&gt;The example below is illustrative. It is not a claim about a deployed customer or a GrowEasy.ai feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep confirmed facts separate from model guesses
&lt;/h2&gt;

&lt;p&gt;Imagine an enquiry for a property visit. The buyer gives an area and a rough budget, but has not decided when to buy. Store the timing as unknown. Do not turn "just looking" into "purchase within thirty days" because your qualification schema expects a value.&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;"buyer_answers"&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;"area"&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="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"requested area"&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_message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"message-12"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"budget"&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="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"buyer stated range"&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_message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"message-15"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"purchase_timing"&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="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&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;"not_confirmed"&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;"open_questions"&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="s2"&gt;"Buyer asks about financing terms"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"handoff_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Needs an authorised human answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"callback_window"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&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;"awaiting_assignment"&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 fields are an example contract, not a required standard. What matters is that evidence and uncertainty remain visible. A lead score can help sort work, but it should not silently replace the actual answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make rep acceptance a state, not a hopeful sentence
&lt;/h2&gt;

&lt;p&gt;Use separate states for awaiting assignment, assigned, accepted, contacted and closed. Do not mark a handoff complete just because a message entered a queue.&lt;/p&gt;

&lt;p&gt;If nobody accepts, trigger a team-visible fallback. If the buyer's requested time passes, record that the callback was missed. Do not let a summary or a notification stand in for ownership.&lt;/p&gt;

&lt;p&gt;The buyer-facing agent should only say that a callback is confirmed when a real owner and a valid time exist. That rule belongs in application logic, not only in a prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Route commitments away from the qualification tool
&lt;/h2&gt;

&lt;p&gt;An intake agent may collect preferences or explain approved information. It should not invent an exception to contract terms, approve credit, promise legal clearance or negotiate a concession.&lt;/p&gt;

&lt;p&gt;Use restricted tools and approved knowledge. Escalate conflicting answers, unsupported questions and explicit requests for a person. The model should be able to say what it does not know.&lt;/p&gt;

&lt;p&gt;Salesforce's Agentforce sales page separates qualification, routing, follow-up and sales insights. That supports a modular design. It does not prove that unrestricted autonomy improves conversion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test duplicate messages and failed handoffs before conversion
&lt;/h2&gt;

&lt;p&gt;Start with failure cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The buyer changes the budget after the initial summary.&lt;/li&gt;
&lt;li&gt;The buyer requests a human before answering every question.&lt;/li&gt;
&lt;li&gt;Two reps try to accept the same enquiry.&lt;/li&gt;
&lt;li&gt;A reminder arrives after the appointment has been cancelled.&lt;/li&gt;
&lt;li&gt;A model claims the buyer agreed to a callback without a supporting message.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tests should inspect stored state and the buyer-facing response. Include a repeat-message test so a retry does not create two callback requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure downstream quality, not chatbot activity
&lt;/h2&gt;

&lt;p&gt;Response time and completed fields are useful, but incomplete. Check rep acceptance, callback completion, meeting attendance, qualified opportunity rate and eventual close rate. Add false rejection, incorrect promises and complaints.&lt;/p&gt;

&lt;p&gt;AI-assistance research in customer support has found productivity gains, with differences by worker experience. That is not a promised sales uplift. Compare this handoff with your current process on similar leads before making a performance claim.&lt;/p&gt;

&lt;p&gt;A qualification agent has done its job when the next person can act correctly. A fluent conversation that leaves nobody responsible is still a failed workflow.&lt;/p&gt;

&lt;p&gt;Tej Pandya, founder of GrowEasy.ai&lt;/p&gt;

&lt;p&gt;Sources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.salesforce.com/agentforce/use-cases/sales/" rel="noopener noreferrer"&gt;Salesforce sales-agent functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://academic.oup.com/qje/article/140/2/889/7990658" rel="noopener noreferrer"&gt;Generative AI at Work, published study&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nist.gov/publications/artificial-intelligence-risk-management-framework-generative-artificial-intelligence" rel="noopener noreferrer"&gt;NIST Generative AI Profile&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI disclosure: Written with autonomous AI assistance and checked against the linked sources.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Build an Industry-Onboarding Preview, Not a Prompt That Rewrites Your CRM</title>
      <dc:creator>Tej Pandya</dc:creator>
      <pubDate>Thu, 08 Oct 2026 09:27:31 +0000</pubDate>
      <link>https://dev.to/tej_pandya_a973bc2256ba93/build-an-industry-onboarding-preview-not-a-prompt-that-rewrites-your-crm-427m</link>
      <guid>https://dev.to/tej_pandya_a973bc2256ba93/build-an-industry-onboarding-preview-not-a-prompt-that-rewrites-your-crm-427m</guid>
      <description>&lt;p&gt;&lt;em&gt;Separate connector access, proposed configuration and approved changes before adapting one CRM to different industries.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;By Tej Pandya, founder of GrowEasy.ai&lt;/p&gt;

&lt;p&gt;I expect more vertical software to become a setup choice on a shared core. For builders, the important question is what happens between "tell us your industry" and "save these changes".&lt;/p&gt;

&lt;p&gt;A model can suggest qualification questions. That does not make it safe to rewrite fields, permissions or live records. Treat the suggestion as a proposed configuration, with a review step before it touches the system of record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put a typed configuration between the model and the CRM
&lt;/h2&gt;

&lt;p&gt;For an illustrative training-company setup, the proposal might contain a course field, preferred start date, an intake question and a human-handoff rule. An interior-design setup might propose project location and room type instead.&lt;/p&gt;

&lt;p&gt;Keep each proposal explicit: field name, type, purpose, allowed values, required status and which workflow uses it. A free-text prompt is not a migration plan. Check the proposal against the CRM's supported objects and the customer's existing fields.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP access does not grant permission to change the schema
&lt;/h2&gt;

&lt;p&gt;HubSpot's remote MCP server is generally available. Its April 2026 changelog lists read access and create/update access separately. Supported writes include contacts, companies, deals, tickets, line items and products; some other objects remain read-only.&lt;/p&gt;

&lt;p&gt;The connection uses OAuth 2.1 with PKCE and respects existing HubSpot user permissions. When sensitive data is enabled, Activity objects are blocked through this MCP server. None of that release establishes automatic industry-specific schema rewriting.&lt;/p&gt;

&lt;p&gt;Build against the tools and permissions actually available to the connected account. If a required operation is unavailable, show that limit instead of generating a plausible tool call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preview the difference and test it with fictional records
&lt;/h2&gt;

&lt;p&gt;A useful preview compares the current configuration with the proposal. Show added fields, renamed fields, changed validation and affected reports. Do not hide a destructive change among harmless labels.&lt;/p&gt;

&lt;p&gt;Run fictional leads through both versions. Check that qualification, handoff and reporting still mean what the owner expects. Keep the old configuration available, and define how to recover if a change is wrong. These are design recommendations, not a claim that we have shipped this system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep specialist logic outside the generic setup promise
&lt;/h2&gt;

&lt;p&gt;SAP's generative-UI article points toward task-specific interfaces, but also stresses live system-of-record data, familiar structures and complex business logic. A generated form does not replace those foundations.&lt;/p&gt;

&lt;p&gt;The goal is a shared core that can express different workflows, not a model pretending every industry's rules are interchangeable. Keep unsupported industries and untested integrations visibly untested.&lt;/p&gt;

&lt;p&gt;Sources: &lt;a href="https://developers.hubspot.com/changelog/remote-hubspot-mcp-server-is-now-generally-available" rel="noopener noreferrer"&gt;https://developers.hubspot.com/changelog/remote-hubspot-mcp-server-is-now-generally-available&lt;/a&gt; ; &lt;a href="https://news.sap.com/2026/03/why-is-generative-ui-the-new-frontier-for-business-software/" rel="noopener noreferrer"&gt;https://news.sap.com/2026/03/why-is-generative-ui-the-new-frontier-for-business-software/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
  </channel>
</rss>
