<?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: Adewole Babatunde</title>
    <description>The latest articles on DEV Community by Adewole Babatunde (@llm_money).</description>
    <link>https://dev.to/llm_money</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3949951%2F61bd58c2-b489-4e86-9fb6-84ba83cd164c.jpeg</url>
      <title>DEV Community: Adewole Babatunde</title>
      <link>https://dev.to/llm_money</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/llm_money"/>
    <language>en</language>
    <item>
      <title>How 12 AI agent frameworks handle human approval (most badly)</title>
      <dc:creator>Adewole Babatunde</dc:creator>
      <pubDate>Mon, 25 May 2026 05:17:03 +0000</pubDate>
      <link>https://dev.to/llm_money/how-12-ai-agent-frameworks-handle-human-approval-most-badly-2g3l</link>
      <guid>https://dev.to/llm_money/how-12-ai-agent-frameworks-handle-human-approval-most-badly-2g3l</guid>
      <description>&lt;p&gt;I keep watching teams ship agent systems into production and then discover, on day three, that &lt;em&gt;"the agent needs to wait for a human sometimes"&lt;/em&gt; breaks every assumption in their stack. Not because they didn't see it coming, every team plans for HITL. Because every popular agent framework reduces "human in the loop" to &lt;em&gt;"block the Python process on &lt;code&gt;input()&lt;/code&gt; and hope for the best."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I spent a day auditing the twelve most popular AI-agent frameworks against a strict production rubric. The results aren't kind. Two frameworks pass. Ten are one production deploy away from breaking.&lt;/p&gt;

&lt;p&gt;This post is the receipts.&lt;/p&gt;




&lt;h2&gt;
  
  
  The rubric (and why it's strict)
&lt;/h2&gt;

&lt;p&gt;A production HITL primitive isn't &lt;em&gt;"the agent can pause for input."&lt;/em&gt; That's a 1980s primitive. A production HITL primitive needs six properties:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Axis&lt;/th&gt;
&lt;th&gt;What it measures&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Durability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Does the agent survive a worker restart during a pending await? Is the paused state stored in durable storage (Postgres), not in-process memory?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Idempotency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;If the agent retries after a crash, can the same approval resolve once without double-acting?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Typed I/O&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Is the &lt;em&gt;request payload&lt;/em&gt; AND the &lt;em&gt;human's response&lt;/em&gt; a typed schema (Pydantic / Zod)? Or just &lt;code&gt;str&lt;/code&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Channel abstraction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Can you swap channel (terminal → Slack → email → dashboard) without rewriting the agent?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Verifier hook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Is there a built-in slot for an AI quality check on the human's response &lt;em&gt;before&lt;/em&gt; resuming?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Default UI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Does the framework ship an admin UI to view, claim, resolve in-flight tasks?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Score 1 (absent / broken) to 5 (production-ready primitive in core). Max 30.&lt;/p&gt;

&lt;p&gt;If you think six axes is too strict: the alternative is your agent process dying because a worker rotated mid-await, your retry double-charging a customer, your "approve y/n" prompt happening on stdin in a Slack channel only you can see, and you discovering all of this two weeks after you shipped.&lt;/p&gt;




&lt;h2&gt;
  
  
  The scorecard
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rank&lt;/th&gt;
&lt;th&gt;Framework&lt;/th&gt;
&lt;th&gt;Durability&lt;/th&gt;
&lt;th&gt;Idempotency&lt;/th&gt;
&lt;th&gt;Typed I/O&lt;/th&gt;
&lt;th&gt;Channel&lt;/th&gt;
&lt;th&gt;Verifier&lt;/th&gt;
&lt;th&gt;UI&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;LangGraph&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Pydantic AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Mastra&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;OpenAI Agents SDK&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;LlamaIndex&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Haystack&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Semantic Kernel&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;CrewAI&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Claude Agent SDK&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;LangChain (legacy)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;AutoGen&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;smolagents&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Nobody scores above 15/30.&lt;/strong&gt; Four frameworks tie at the bottom on 5/30, each for a different reason, but the headline number is the same.&lt;/p&gt;




&lt;h2&gt;
  
  
  The top: LangGraph, Pydantic AI, Mastra
&lt;/h2&gt;

&lt;p&gt;These three are the closest things to production-ready. They still leave 50% of the rubric to you, but at least the &lt;em&gt;durable pause&lt;/em&gt; primitive works.&lt;/p&gt;

&lt;h3&gt;
  
  
  LangGraph — best-in-class durability, BYO everything else
&lt;/h3&gt;

&lt;p&gt;LangGraph's &lt;code&gt;interrupt()&lt;/code&gt; pauses a graph node; resuming is &lt;code&gt;graph.invoke(Command(resume=value), config={...})&lt;/code&gt;. Critically, and this is what separates it from the field, when paired with a &lt;code&gt;PostgresSaver&lt;/code&gt; checkpointer, the paused thread state lives in Postgres. Any worker can resume it. The docs explicitly walk through worker-restart resilience.&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;langgraph.types&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;interrupt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Command&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;approval_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;interrupt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;question&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;approve transfer?&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;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approved&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What you'll discover the hard way: &lt;strong&gt;the docs warn that code before &lt;code&gt;interrupt()&lt;/code&gt; runs twice on resume.&lt;/strong&gt; &lt;em&gt;"Place pure computation before, side effects after."&lt;/em&gt; That's the developer's idempotency burden, not the framework's. And there's no channel abstraction, the &lt;code&gt;interrupt()&lt;/code&gt; payload is just a dict, and how that becomes a Slack message is entirely on you.&lt;/p&gt;

&lt;p&gt;LangGraph Platform ships a task UI. The OSS package does not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Best durability story in the survey. Everything above the storage layer is BYO.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pydantic AI — best typed API, weakest UI story
&lt;/h3&gt;

&lt;p&gt;Pydantic AI's &lt;code&gt;Deferred Tools&lt;/code&gt; is the cleanest API I saw. A tool marked &lt;code&gt;requires_approval=True&lt;/code&gt; causes the run to end with a &lt;code&gt;DeferredToolRequests&lt;/code&gt; output. You collect approvals as &lt;code&gt;DeferredToolResults&lt;/code&gt; and resume by passing both into the next agent run.&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="nd"&gt;@agent.tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requires_approval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transfer_funds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&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;sent &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&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 $500 to Bob&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output_type&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;DeferredToolRequests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;approvals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_call_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ToolApproved&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;call&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&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="n"&gt;approvals&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&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;message_history&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all_messages&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;deferred_tool_results&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;DeferredToolResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;approvals&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;approvals&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;Everything is Pydantic-typed. Durability comes via integration with &lt;a href="https://restate.dev" rel="noopener noreferrer"&gt;Restate&lt;/a&gt; (or Temporal, Prefect), which journals every step including the await. That's powerful, but "we ship a primitive plus a separate runtime you also have to learn" is not zero-config.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; The typing is exactly right. The rest of the stack is a separate framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mastra — best TypeScript story, channels don't compose
&lt;/h3&gt;

&lt;p&gt;Mastra workflows expose &lt;code&gt;suspend()&lt;/code&gt; / &lt;code&gt;resume()&lt;/code&gt;. When a step suspends, the workflow snapshot is persisted (PostgreSQL, Upstash Redis, etc.), and &lt;code&gt;resume()&lt;/code&gt; can be called from any HTTP endpoint with the matching run ID. Both &lt;code&gt;suspend&lt;/code&gt; and &lt;code&gt;resume&lt;/code&gt; payloads are Zod-typed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;approvalStep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createStep&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;approval&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;resumeSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;approved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;resumeData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;suspend&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;resumeData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;suspend&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;()&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="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="na"&gt;approved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;resumeData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;approved&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;The catch: Mastra ships "Channels" (Slack, Discord, Telegram) — &lt;em&gt;but for agents, not workflows.&lt;/em&gt; You can't say &lt;em&gt;"suspend this step and route it to #ops on Slack"&lt;/em&gt; without writing the glue yourself. The community-maintained &lt;code&gt;assistant-ui/mastra-hitl&lt;/code&gt; repo exists precisely because there's no canonical built-in approval UI for production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; The best TS option. The last mile is still on you.&lt;/p&gt;




&lt;h2&gt;
  
  
  The middle: OpenAI Agents SDK, LlamaIndex, Haystack, Semantic Kernel
&lt;/h2&gt;

&lt;p&gt;These four ship a recognizable HITL primitive but trip over a single major axis each.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI Agents SDK (11/30)&lt;/strong&gt; has clean &lt;code&gt;needs_approval&lt;/code&gt; semantics and a &lt;code&gt;RunState&lt;/code&gt; you can serialize. But the SDK assumes you'll bring your own queue, storage, and UI. No channel. No idempotency guarantee beyond what serialize-and-resume gives you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LlamaIndex (10/30)&lt;/strong&gt; has elegant event-driven HITL via &lt;code&gt;wait_for_event&lt;/code&gt; — but the docs warn: &lt;em&gt;"the runtime pauses by throwing an internal control-flow exception and replays the entire step when the event arrives."&lt;/em&gt; Any side effects before the await run twice. Footgun.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Haystack (9/30)&lt;/strong&gt; has the best-designed confirmation taxonomy (&lt;code&gt;AlwaysAskPolicy&lt;/code&gt;, &lt;code&gt;BlockingConfirmationStrategy&lt;/code&gt;, &lt;code&gt;RichConsoleUI&lt;/code&gt;) — and ships &lt;em&gt;only&lt;/em&gt; console UIs. Both built-in implementations block the Python process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic Kernel (8/30)&lt;/strong&gt; has &lt;em&gt;two&lt;/em&gt; half-built HITL mechanisms (&lt;code&gt;IFunctionInvocationFilter&lt;/code&gt; and Process Framework &lt;code&gt;KernelFunction&lt;/code&gt; parameters). The community is openly confused about which to use — issue &lt;a href="https://github.com/microsoft/semantic-kernel/issues/10832" rel="noopener noreferrer"&gt;#10832&lt;/a&gt; literally asks &lt;em&gt;"How to support HITL in Agent and Process frameworks."&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The bottom: CrewAI, Claude Agent SDK, LangChain legacy, AutoGen, smolagents
&lt;/h2&gt;

&lt;p&gt;This is where it gets bleak. Five frameworks. Each ships &lt;em&gt;some&lt;/em&gt; pause-for-human primitive. Each one is &lt;code&gt;input()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  CrewAI: &lt;code&gt;human_input=True&lt;/code&gt; — and then what?
&lt;/h3&gt;

&lt;p&gt;The API is one boolean:&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="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&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;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Research the latest AI advancements...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;expected_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A comprehensive report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;researcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;human_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CrewAI community forum has an officially acknowledged thread titled &lt;em&gt;"Human in the loop - workaround"&lt;/em&gt; that opens:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"HITL input is handled through the terminal, which does not work for a production web environment."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The recommended workaround is Streamlit or Chainlit — wrap your CrewAI process behind a UI and hijack stdin. &lt;strong&gt;The entire production HITL story for CrewAI is "wrap stdin."&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  LangChain (legacy), AutoGen, smolagents: the &lt;code&gt;input()&lt;/code&gt; family
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;LangChain ships &lt;code&gt;HumanInputRun&lt;/code&gt; and &lt;code&gt;HumanApprovalCallbackHandler&lt;/code&gt;. Both wrap &lt;code&gt;input()&lt;/code&gt;. The maintainers' standing recommendation for production HITL is "move to LangGraph" — &lt;a href="https://github.com/langchain-ai/langchain/discussions/21524" rel="noopener noreferrer"&gt;Discussion #21524&lt;/a&gt;, &lt;a href="https://github.com/langchain-ai/langchain/discussions/28217" rel="noopener noreferrer"&gt;Discussion #28217&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;AutoGen's &lt;code&gt;human_input_mode="ALWAYS"&lt;/code&gt; is a 2023 primitive still shipping in 2026. Issues &lt;a href="https://github.com/microsoft/autogen/issues/2358" rel="noopener noreferrer"&gt;#2358&lt;/a&gt; (persistence roadmap) and &lt;a href="https://github.com/microsoft/autogen/discussions/5806" rel="noopener noreferrer"&gt;#5806&lt;/a&gt; (mid-run checkpointing) are both open.&lt;/li&gt;
&lt;li&gt;smolagents has &lt;code&gt;step_callbacks&lt;/code&gt; and &lt;code&gt;agent.interrupt()&lt;/code&gt;. Memory is in-process. Issue &lt;a href="https://github.com/huggingface/smolagents/issues/364" rel="noopener noreferrer"&gt;#364&lt;/a&gt; tracks a state-serialization request — open, unfilled.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Claude Agent SDK: not actually HITL
&lt;/h3&gt;

&lt;p&gt;The SDK's &lt;code&gt;canUseTool&lt;/code&gt; is permission gating, not human-in-the-loop. The callback is synchronous within the agent loop. If you want to pause for a human review, &lt;em&gt;you&lt;/em&gt; make &lt;code&gt;canUseTool&lt;/code&gt; block on a Promise that resolves when the human answers. There's no built-in story for "human two timezones away approves via Slack." The SDK is honest about this — it's designed for Claude Code's interactive CLI approval, not for distributed agent ops.&lt;/p&gt;




&lt;h2&gt;
  
  
  The pattern: three universal gaps
&lt;/h2&gt;

&lt;p&gt;Across all twelve frameworks, three axes are universally missing or near-missing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Channel abstraction.&lt;/strong&gt; Only Mastra ships &lt;em&gt;anything&lt;/em&gt; resembling a channel adapter system. Even there, channels live on agents, not on workflows. Every other framework hands you a payload and walks away.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Verifier hook.&lt;/strong&gt; Zero out of twelve frameworks ship a slot for &lt;em&gt;"the human's response goes through an AI quality check before resuming the agent."&lt;/em&gt; The idea that a junior approver clicking "approve" should be pre-validated by an LLM before the agent trusts it — fully absent from the field.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Default UI.&lt;/strong&gt; Zero OSS dashboards for in-flight HITL tasks. LangGraph Platform has one (paid). Mastra has a dev playground (not production). The rest assume you'll build it.&lt;/p&gt;

&lt;p&gt;The aggregate pattern: &lt;em&gt;the industry has reduced HITL to "block on stdin," and ten of twelve frameworks are one production deploy away from breaking.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What "good" actually looks like
&lt;/h2&gt;

&lt;p&gt;A production HITL primitive should hit all six axes of the rubric above:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Persist to durable storage by default&lt;/strong&gt; — Postgres or equivalent. No in-process state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emit a typed payload&lt;/strong&gt; describing what the human is being asked. Pydantic / Zod.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route through a pluggable channel adapter&lt;/strong&gt; — Slack, email, dashboard, SMS. One config knob. Audit row written on every delivery + response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accept a typed response from any channel&lt;/strong&gt;, idempotent on duplicate deliveries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optionally pipe the response through a verifier model&lt;/strong&gt; before resuming the agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expose an OSS admin UI&lt;/strong&gt; so ops teams can see what's in flight.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No framework in this audit ships all six. So I built one: &lt;strong&gt;&lt;a href="https://github.com/awaithumans/awaithumans" rel="noopener noreferrer"&gt;awaithumans&lt;/a&gt;&lt;/strong&gt;. One function call (&lt;code&gt;await_human&lt;/code&gt; / &lt;code&gt;awaitHuman&lt;/code&gt;), all six properties in the box, channel adapters for Slack and email shipping today, dashboard included, audit trail by default, optional Claude/OpenAI/Gemini/Azure verifier. Apache 2.0. Python + TypeScript. Adapters for the two frameworks that &lt;em&gt;got close&lt;/em&gt; — LangGraph and the broader Pydantic AI / Temporal combo — built in.&lt;/p&gt;

&lt;p&gt;It is not the final answer. It's an &lt;em&gt;honest&lt;/em&gt; answer to the gap this audit exposes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Methodology
&lt;/h2&gt;

&lt;p&gt;For each framework I read the official docs, the README, the examples folder, and grepped open GitHub issues for "human", "approval", "interrupt", "input", "pause". Code samples are copied from canonical docs; I didn't make any of them up. Scores are subjective on a 1-5 scale per axis but the scorecard is reproducible — pull the framework, grep for the same terms, you'll find the same gaps. The full per-framework deep dive (with source URLs for every claim) lives in the &lt;a href="https://github.com/awaithumans/awaithumans/blob/main/docs/research/twelve-frameworks-hitl-audit.md" rel="noopener noreferrer"&gt;research notes&lt;/a&gt;. Audit date: &lt;strong&gt;2026-05-24&lt;/strong&gt;; the field is moving fast and at least three of these frameworks have HITL work in flight — see linked issues per section. If your framework is on this list and you've shipped one of the missing axes since the audit date, I'd love to update the scorecard — open an issue at &lt;a href="https://github.com/awaithumans/awaithumans" rel="noopener noreferrer"&gt;github.com/awaithumans/awaithumans&lt;/a&gt; or DM &lt;a href="https://x.com/awaithumans" rel="noopener noreferrer"&gt;@awaithumans&lt;/a&gt; on X.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;p&gt;Selected, in order of relevance. The full list of ~50 source URLs is in the research repo.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LangGraph&lt;/strong&gt; — &lt;a href="https://docs.langchain.com/oss/python/langgraph/interrupts" rel="noopener noreferrer"&gt;Interrupts&lt;/a&gt; · &lt;a href="https://fast.io/resources/langgraph-persistence/" rel="noopener noreferrer"&gt;persistence&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pydantic AI&lt;/strong&gt; — &lt;a href="https://ai.pydantic.dev/deferred-tools/" rel="noopener noreferrer"&gt;Deferred Tools&lt;/a&gt; · &lt;a href="https://pydantic.dev/articles/restate-durable-execution-pydanticai" rel="noopener noreferrer"&gt;Restate integration&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mastra&lt;/strong&gt; — &lt;a href="https://mastra.ai/docs/workflows/human-in-the-loop" rel="noopener noreferrer"&gt;HITL workflows&lt;/a&gt; · &lt;a href="https://github.com/assistant-ui/mastra-hitl" rel="noopener noreferrer"&gt;community HITL repo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI Agents SDK&lt;/strong&gt; — &lt;a href="https://openai.github.io/openai-agents-python/human_in_the_loop/" rel="noopener noreferrer"&gt;HITL Python&lt;/a&gt; · &lt;a href="https://openai.github.io/openai-agents-js/guides/human-in-the-loop/" rel="noopener noreferrer"&gt;HITL JS&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LlamaIndex&lt;/strong&gt; — &lt;a href="https://developers.llamaindex.ai/python/framework/understanding/agent/human_in_the_loop/" rel="noopener noreferrer"&gt;HITL workflows&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Haystack&lt;/strong&gt; — &lt;a href="https://docs.haystack.deepset.ai/docs/human-in-the-loop" rel="noopener noreferrer"&gt;HITL docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic Kernel&lt;/strong&gt; — &lt;a href="https://learn.microsoft.com/en-us/semantic-kernel/frameworks/process/examples/example-human-in-loop" rel="noopener noreferrer"&gt;Process Framework HITL&lt;/a&gt; · &lt;a href="https://github.com/microsoft/semantic-kernel/issues/10832" rel="noopener noreferrer"&gt;issue #10832&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CrewAI&lt;/strong&gt; — &lt;a href="https://community.crewai.com/t/human-in-the-loop-workaround/7330" rel="noopener noreferrer"&gt;community workaround thread&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Agent SDK&lt;/strong&gt; — &lt;a href="https://platform.claude.com/docs/en/agent-sdk/permissions" rel="noopener noreferrer"&gt;permissions docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LangChain (legacy)&lt;/strong&gt; — &lt;a href="https://github.com/langchain-ai/langchain/discussions/21524" rel="noopener noreferrer"&gt;Discussion #21524&lt;/a&gt; · &lt;a href="https://github.com/langchain-ai/langchain/discussions/28217" rel="noopener noreferrer"&gt;Discussion #28217&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AutoGen&lt;/strong&gt; — &lt;a href="https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/tutorial/human-in-the-loop.html" rel="noopener noreferrer"&gt;HITL tutorial&lt;/a&gt; · &lt;a href="https://github.com/microsoft/autogen/issues/2358" rel="noopener noreferrer"&gt;Issue #2358&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;smolagents&lt;/strong&gt; — &lt;a href="https://huggingface.co/docs/smolagents/en/examples/plan_customization" rel="noopener noreferrer"&gt;Plan customization&lt;/a&gt; · &lt;a href="https://github.com/huggingface/smolagents/issues/364" rel="noopener noreferrer"&gt;Issue #364&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If you maintain one of these frameworks and want to argue for a higher score on any axis, please do, open an issue with evidence and I'll update the scorecard. The point of the audit isn't to dunk; it's to make the gap legible.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. — the &lt;a href="https://github.com/awaithumans/awaithumans-browser-agent" rel="noopener noreferrer"&gt;browser-agent + awaithumans template repo&lt;/a&gt; shows what this looks like wired up: a browser-use agent that calls &lt;code&gt;await_human()&lt;/code&gt; before clicking Place Order. Slack DM with cart screenshot, one-tap approve, agent resumes. ~90 lines.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>machinelearning</category>
      <category>langchain</category>
    </item>
  </channel>
</rss>
