<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Runtime on Renxin's Blog</title><link>https://zh.renxinblog.cn/tags/runtime/</link><description>Recent content in Runtime on Renxin's Blog</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Tue, 01 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://zh.renxinblog.cn/tags/runtime/index.xml" rel="self" type="application/rss+xml"/><item><title>第十一话｜ReAct、Plan-and-Execute 还是 Workflow：Agent 的下一步由谁决定？</title><link>https://zh.renxinblog.cn/post/gya-c11-agent-control-modes/</link><pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate><guid>https://zh.renxinblog.cn/post/gya-c11-agent-control-modes/</guid><description>&lt;img src="https://zh.renxinblog.cn/images/gya-c11-agent-control-modes-cover.png" alt="Featured image of post 第十一话｜ReAct、Plan-and-Execute 还是 Workflow：Agent 的下一步由谁决定？" /&gt;&lt;p&gt;先把话挑明：这篇&lt;strong&gt;不教你搭博客写作工作流，也不给 ReAct、Plan-and-Execute、Workflow 排座次&lt;/strong&gt;，只回答一个问题——&lt;strong&gt;Agent 的下一步由谁决定，谁才能真正让它停下来。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;下面反复出现的“写博客”，只是一件切题标本：它自带审批门和写文件的副作用位点，正好能把“谁决定下一步”照清楚。故事是博客，题目是控制权。&lt;/p&gt;
&lt;p&gt;写 Skill 的时候，你加了一条流程规则：&lt;strong&gt;先生成创作纲领，然后停下等用户确认，确认通过前不准写正文。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;运行起来，Agent 却照常往下冲：生成完纲领，下一秒就去写草稿，或者干脆在循环里来回打转。&lt;/p&gt;
&lt;p&gt;是模型没读懂 Skill 吗？不全是。真正的问题是——&lt;strong&gt;“停下来等确认”这件事，根本不归 Skill 管，也不归模型管，它归运行时代码管。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Agent 的三种流行组织方式，ReAct、Plan-and-Execute、Workflow，本质区别不是“会不会调 LLM”，而是：&lt;strong&gt;下一步行动由谁决定，谁才能真正把这一次 Run 停下来。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;结论先放这里：&lt;strong&gt;模型提建议，代码做决定。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="先把决定这个词拆开"&gt;先把“决定”这个词拆开
&lt;/h2&gt;&lt;p&gt;我们通常说“Agent 决定下一步”，这句话其实把三件不同的事揉在了一起：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;层&lt;/th&gt;
					&lt;th&gt;它做什么&lt;/th&gt;
					&lt;th&gt;类比后端系统&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;模型&lt;/td&gt;
					&lt;td&gt;输出动作建议或内容，例如“下一步 &lt;code&gt;write_draft&lt;/code&gt;”&lt;/td&gt;
					&lt;td&gt;业务 Service 提一个方案&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;运行时 Runtime&lt;/td&gt;
					&lt;td&gt;决定暴露哪些动作、采纳哪条建议、走哪个状态迁移&lt;/td&gt;
					&lt;td&gt;应用网关 / 策略层&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;执行器 Executor&lt;/td&gt;
					&lt;td&gt;真正产生副作用：写文件、发请求、改数据库&lt;/td&gt;
					&lt;td&gt;DAO / Outbox 写库&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;%%{init: {'theme':'neutral'}}%%
flowchart LR
 LLM[模型] --&gt;|建议动作| RT[Runtime]
 RT --&gt;|暴露可见动作| EX[Executor]
 EX --&gt;|副作用| GW[Action Gateway]
 GW --&gt; OUT[虚拟产物]
 RT --&gt;|固定迁移| WF[Workflow 状态]&lt;/pre&gt;&lt;p&gt;读了这张图，很多“Agent 不可控”的困惑会自动解开：&lt;strong&gt;模型负责“说”，不负责“做”。&lt;/strong&gt; 它可以说出 &lt;code&gt;write_draft&lt;/code&gt;，但要不要做、什么时候做、做之前过几道闸门，由 Runtime 和 Executor 这一侧决定。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键洞察&lt;/strong&gt;：控制权不是一句“用了 Workflow”或“用了 StateGraph”就自动获得；它分散在“动作可见性、状态迁移、副作用网关”三处，每一处都必须有代码接手。&lt;/p&gt;
&lt;h2 id="同一个任务三种跑法"&gt;同一个任务，三种跑法
&lt;/h2&gt;&lt;p&gt;用一个故意制造矛盾的隔离任务，把三种模式各跑一遍。任务只有两条规则，还互相打架：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;规则一：先创建创作纲领并等待用户确认。
规则二：不要停下来，直接写出完整草稿。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;任务跑在内存里的&lt;strong&gt;虚拟文章存储&lt;/strong&gt;上：不写公开博客、不提交 Git、不部署。三种模式跑同一段任务，区别马上出来：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;模式&lt;/th&gt;
					&lt;th&gt;每一步谁决定&lt;/th&gt;
					&lt;th&gt;模型可见动作&lt;/th&gt;
					&lt;th&gt;谁能真正停&lt;/th&gt;
					&lt;th&gt;本次真实结果&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;ReAct&lt;/td&gt;
					&lt;td&gt;模型看着 Observation 选下一动作&lt;/td&gt;
					&lt;td&gt;全部动作&lt;/td&gt;
					&lt;td&gt;只有 &lt;code&gt;finish&lt;/code&gt; 或步数上限&lt;/td&gt;
					&lt;td&gt;连走三次 &lt;code&gt;create_brief&lt;/code&gt;，&lt;code&gt;draft_written=false&lt;/code&gt;，从未进入等待&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Plan-and-Execute&lt;/td&gt;
					&lt;td&gt;模型先出一份计划，Executor 顺序执行&lt;/td&gt;
					&lt;td&gt;计划内的动作&lt;/td&gt;
					&lt;td&gt;计划文本里的“等待”停不住&lt;/td&gt;
					&lt;td&gt;计划含 &lt;code&gt;await_brief_approval&lt;/code&gt;，Executor 仍尝试 &lt;code&gt;write_draft&lt;/code&gt;，被网关拦下&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Workflow&lt;/td&gt;
					&lt;td&gt;代码写死状态迁移，模型只生成当前节点内容&lt;/td&gt;
					&lt;td&gt;当前节点需要的内容生成&lt;/td&gt;
					&lt;td&gt;状态机 &lt;code&gt;return&lt;/code&gt;/迁移&lt;/td&gt;
					&lt;td&gt;生成纲领后直接 &lt;code&gt;run_suspended&lt;/code&gt;，Trace 里根本没有 &lt;code&gt;write_draft&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;有个反直觉的点：&lt;strong&gt;三种模式其实都调了 LLM。&lt;/strong&gt; 所以别再用“Workflow 不智能、ReAct 才智能”来区分它们。区别只在控制权：ReAct 把“下一步”反复交给模型；Workflow 把“下一步”收回到代码。&lt;/p&gt;
&lt;p&gt;所以选型的第一问是——“这一步由谁拍板才安全、才够用”，而不是“哪个听起来更像 Agent”。&lt;/p&gt;
&lt;h2 id="先跑起来最小对照"&gt;先跑起来：最小对照
&lt;/h2&gt;&lt;p&gt;不写框架，先写一个十秒看懂的最小实验。它只演示一件事：&lt;strong&gt;把 &lt;code&gt;waiting&lt;/code&gt; 改成 &lt;code&gt;True&lt;/code&gt;，不等于让循环停下。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;下面是完整可粘贴的 Python，不需要 API Key：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;“等待”是数据标记，还是控制信号？最小对照。&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;APPROVED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;create_brief&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;brief_created_in_virtual_store&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wait_for_approval&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wait_marker_recorded&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;write_draft&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;APPROVED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;draft_written_to_virtual_store&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;blocked_by_action_gateway&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;unknown_action&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_naive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;create_brief&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wait_for_approval&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;write_draft&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;waiting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wait_for_approval&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 只改了一个布尔值，循环不会因此停。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;waiting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;draft_written&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;draft_written_to_virtual_store&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;[naive] waiting=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;waiting&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; draft_written=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;draft_written&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;18s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_suspending&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;create_brief&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wait_for_approval&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;write_draft&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;waiting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wait_for_approval&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;waiting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;run_suspended&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;[fix] waiting=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;waiting&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; draft_written=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;18s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="c1"&gt;# 真正的“停”：后面的 write_draft 根本不会被循环碰到&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;== 错误版：像 Plan-and-Execute 的朴素 Executor ==&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;run_naive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;== 修复版：Workflow 的状态机迁移 ==&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;run_suspending&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;跑一遍，输出如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;== 错误版：像 Plan-and-Execute 的朴素 Executor ==
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[naive] waiting=True draft_written=False
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create_brief -&amp;gt; brief_created_in_virtual_store
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; wait_for_approval -&amp;gt; wait_marker_recorded
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; write_draft -&amp;gt; blocked_by_action_gateway
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;== 修复版：Workflow 的状态机迁移 ==
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[fix] waiting=True draft_written=False
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create_brief -&amp;gt; brief_created_in_virtual_store
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; wait_for_approval -&amp;gt; run_suspended
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;读输出的判断标准：&lt;strong&gt;错误版里 &lt;code&gt;waiting=True&lt;/code&gt; 和 &lt;code&gt;write_draft&lt;/code&gt; 同时出现&lt;/strong&gt;——它“认为”自己在等待，却还在往下执行；修复版里 &lt;code&gt;run_suspended&lt;/code&gt; 出现后，&lt;code&gt;write_draft&lt;/code&gt; 从 Trace 里直接消失。这才是“停”。&lt;/p&gt;
&lt;h2 id="三种模式的真实-trace"&gt;三种模式的真实 Trace
&lt;/h2&gt;&lt;p&gt;把三段真实模型 Trace 缩到关键行。完整实验在配套代码仓库的 Python 与 Java 两个子目录里各有一份。&lt;/p&gt;
&lt;p&gt;离线回归（不调模型，只验证控制流）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; python
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 -m unittest -v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Ran 8 tests in 0.000s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# OK&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;真实模型（需先导出 &lt;code&gt;LLM_API_URL&lt;/code&gt;、&lt;code&gt;LLM_MODEL&lt;/code&gt;、&lt;code&gt;LLM_API_KEY&lt;/code&gt;，Key 只从环境变量读取）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 control_modes.py --live
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;本轮 Python 真实输出（节选，共三段）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# ReAct：模型每步都选 create_brief，从不进入等待
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[trace] {&amp;#34;mode&amp;#34;:&amp;#34;react&amp;#34;,&amp;#34;step&amp;#34;:1,&amp;#34;decision_maker&amp;#34;:&amp;#34;llm&amp;#34;,&amp;#34;action&amp;#34;:&amp;#34;create_brief&amp;#34;,...}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[trace] {&amp;#34;mode&amp;#34;:&amp;#34;react&amp;#34;,&amp;#34;step&amp;#34;:2,&amp;#34;decision_maker&amp;#34;:&amp;#34;llm&amp;#34;,&amp;#34;action&amp;#34;:&amp;#34;create_brief&amp;#34;,...}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[trace] {&amp;#34;mode&amp;#34;:&amp;#34;react&amp;#34;,&amp;#34;step&amp;#34;:3,&amp;#34;decision_maker&amp;#34;:&amp;#34;llm&amp;#34;,&amp;#34;action&amp;#34;:&amp;#34;create_brief&amp;#34;,...}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[summary] {&amp;#34;mode&amp;#34;:&amp;#34;react&amp;#34;,&amp;#34;waiting_for_approval&amp;#34;:false,&amp;#34;draft_written&amp;#34;:false,&amp;#34;usage&amp;#34;:{&amp;#34;total_tokens&amp;#34;:427}}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# Plan-and-Execute：计划里有 await，Executor 仍尝试写稿，网关拦下
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[trace] {&amp;#34;mode&amp;#34;:&amp;#34;plan_and_execute&amp;#34;,&amp;#34;step&amp;#34;:0,&amp;#34;decision_maker&amp;#34;:&amp;#34;plan_validator&amp;#34;,&amp;#34;result&amp;#34;:&amp;#34;ACCEPT&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[trace] {&amp;#34;mode&amp;#34;:&amp;#34;plan_and_execute&amp;#34;,&amp;#34;step&amp;#34;:2,&amp;#34;decision_maker&amp;#34;:&amp;#34;executor&amp;#34;,&amp;#34;action&amp;#34;:&amp;#34;await_brief_approval&amp;#34;,&amp;#34;result&amp;#34;:&amp;#34;wait_marker_recorded&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[trace] {&amp;#34;mode&amp;#34;:&amp;#34;plan_and_execute&amp;#34;,&amp;#34;step&amp;#34;:3,&amp;#34;decision_maker&amp;#34;:&amp;#34;executor&amp;#34;,&amp;#34;action&amp;#34;:&amp;#34;write_draft&amp;#34;,&amp;#34;result&amp;#34;:&amp;#34;blocked_by_action_gateway&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[summary] {&amp;#34;mode&amp;#34;:&amp;#34;plan_and_execute&amp;#34;,&amp;#34;waiting_for_approval&amp;#34;:true,&amp;#34;draft_written&amp;#34;:false,&amp;#34;usage&amp;#34;:{&amp;#34;total_tokens&amp;#34;:142}}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# Workflow：代码固定迁移，模型从未见过 write_draft
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[trace] {&amp;#34;mode&amp;#34;:&amp;#34;workflow&amp;#34;,&amp;#34;step&amp;#34;:1,&amp;#34;decision_maker&amp;#34;:&amp;#34;llm&amp;#34;,&amp;#34;event&amp;#34;:&amp;#34;generate_brief&amp;#34;,...}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[trace] {&amp;#34;mode&amp;#34;:&amp;#34;workflow&amp;#34;,&amp;#34;step&amp;#34;:2,&amp;#34;decision_maker&amp;#34;:&amp;#34;workflow&amp;#34;,&amp;#34;action&amp;#34;:&amp;#34;WAITING_FOR_APPROVAL&amp;#34;,&amp;#34;result&amp;#34;:&amp;#34;run_suspended&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[summary] {&amp;#34;mode&amp;#34;:&amp;#34;workflow&amp;#34;,&amp;#34;waiting_for_approval&amp;#34;:true,&amp;#34;draft_written&amp;#34;:false,&amp;#34;usage&amp;#34;:{&amp;#34;total_tokens&amp;#34;:203}}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;三段摆在一起，结论是确定的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ReAct 能停吗？&lt;/strong&gt; 能，但停的条件由模型选中的 &lt;code&gt;finish&lt;/code&gt; 或代码给的最大步数决定；“等待”不在它自动遵守的条件里。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plan-and-Execute 能停吗？&lt;/strong&gt; 计划里写了 &lt;code&gt;await_brief_approval&lt;/code&gt;，但朴素 Executor 把它当普通动作执行——只改状态、继续下一步，最后靠 Action Gateway 拦下 &lt;code&gt;write_draft&lt;/code&gt; 的副作用，可 Executor 并没有因此停，它仍往下走到 &lt;code&gt;finish&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workflow 能停吗？&lt;/strong&gt; 当前节点生成内容后，代码把下一状态固定成 &lt;code&gt;WAITING_FOR_APPROVAL&lt;/code&gt; 并结束 Run；模型连 &lt;code&gt;write_draft&lt;/code&gt; 这个动作都没被暴露。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;关键洞察&lt;/strong&gt;：&lt;code&gt;await&lt;/code&gt; 写在计划里是&lt;strong&gt;数据&lt;/strong&gt;，只有 &lt;code&gt;break / return / 状态机迁移&lt;/code&gt; 才是&lt;strong&gt;控制&lt;/strong&gt;。指望模型或 Planner 在文本里“自觉地停下来”，就是把安全押在概率上。&lt;/p&gt;
&lt;h2 id="外层-workflow内层-react"&gt;外层 Workflow，内层 ReAct
&lt;/h2&gt;&lt;p&gt;既然 Workflow 最可控，ReAct 最灵活，生产里怎么选？&lt;/p&gt;
&lt;p&gt;答案是&lt;strong&gt;分层，而不是二选一&lt;/strong&gt;。一个可信的技术文章 Agent 可以长这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Workflow 固定阶段：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0. 准备材料 ──&amp;gt; 1. 生成纲领 ──&amp;gt; [等待审批] ──&amp;gt; 2. 生成梗概 ──&amp;gt; [等待审批] ──&amp;gt; 3. 写草稿
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ^
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 探索节点（ReAct）：搜证据、读源码、判断“材料够不够”，直到确定性条件达成
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;外层用 Workflow 锁阶段和硬边界&lt;/strong&gt;：未达到 &lt;code&gt;WAITING_FOR_APPROVAL&lt;/code&gt;，后面的阶段就不存在，模型没机会跳到“写草稿”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内层用 ReAct 降低不确定性&lt;/strong&gt;：一个“读懂这个仓库再写梗概”的开放任务，让模型多走几步、看 Observation，比死板的一次性流程更实用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;任务清晰后再交给 Planner/Executor&lt;/strong&gt;：计划进入 Executor 前先用确定性校验器检查结构、动作白名单和审批顺序；LLM 可以辅助语义判断，但硬规则必须由代码执行——校验器只返回 &lt;code&gt;ACCEPT / REVISE / REJECT&lt;/code&gt;，不替模型悄悄改计划。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里有个很容易被模糊的点：&lt;strong&gt;“能不能进计划”由谁判？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;不要让模型直接回一个 &lt;code&gt;ready: true&lt;/code&gt;。那是模型自我声明，等于让它自己给自己批作业。正确的分工是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LLM 交&lt;strong&gt;结构化证据&lt;/strong&gt;：已调研了哪几项、还剩哪几项没做、每项的依据是什么。&lt;/li&gt;
&lt;li&gt;Runtime 按&lt;strong&gt;校验契约&lt;/strong&gt;算布尔：条件满足才算 &lt;code&gt;ready&lt;/code&gt;，证据缺失就补齐或换策略。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这就是“模型提建议，代码做决定”在 readiness 上的具体落法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键洞察&lt;/strong&gt;：给开放任务留 ReAct，给高风险边界上 Workflow，再把“能不能切换阶段”写成运行时契约；三者不是竞争关系，是一条流水线上的不同工位。&lt;/p&gt;
&lt;h2 id="workflow-该不该上框架"&gt;Workflow 该不该上框架
&lt;/h2&gt;&lt;p&gt;也许你已经想到 LangGraph 这类框架：它能不能把上面这些自动解决？&lt;/p&gt;
&lt;p&gt;能解决一部分，但要分清“&lt;strong&gt;能力&lt;/strong&gt;”和“&lt;strong&gt;承诺&lt;/strong&gt;”。LangGraph 的文档把两件事分开：Workflow 走&lt;strong&gt;预定义的代码路径&lt;/strong&gt;，Agent 每一步由模型&lt;strong&gt;动态决定过程&lt;/strong&gt;；它用状态图、checkpointer、&lt;code&gt;interrupt()&lt;/code&gt; 提供挂起、保存状态和外部命令恢复的实现能力。这些能力正好是第十二话要做“可恢复、不可绕过的审批”的原料。&lt;/p&gt;
&lt;p&gt;但它&lt;strong&gt;不会因为你用了 LangGraph，就自动保证审批合规&lt;/strong&gt;。状态图上的节点连得通，不代表业务上这一段就该执行。&lt;code&gt;publish_article&lt;/code&gt; 这种迁移在不允许发布的状态下必须由运行时拒绝——框架给你的是“状态图能跑”，不给你“业务边界天然正确”。&lt;/p&gt;
&lt;p&gt;所以这一话只把 LangGraph 标记为实现方向，不展开 API。下一步要去的地方，就是把“接下来该做什么”从人工解读变成可以被恢复、被拦截、被审计的运行时机制。&lt;/p&gt;
&lt;p&gt;一句话收口：框架替你搬运状态，不替你决定业务。安全边界只能来自你写的校验契约和动作网关。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;下一话：Skill 只是提示词——如何让 Agent 真正停下来，并且跨进程可以恢复、无法绕过审批。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="踩坑速查"&gt;踩坑速查
&lt;/h2&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;症状&lt;/th&gt;
					&lt;th&gt;原因&lt;/th&gt;
					&lt;th&gt;解法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;--live&lt;/code&gt; 报 &lt;code&gt;LLM network error&lt;/code&gt; / DNS 解析失败&lt;/td&gt;
					&lt;td&gt;沙箱或本地网络到模型 API 不通&lt;/td&gt;
					&lt;td&gt;用离线 &lt;code&gt;python3 -m unittest -v&lt;/code&gt; 先验证控制流；真实调用再解决网络/代理&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;模型返回值解析失败或 &lt;code&gt;content&lt;/code&gt; 为空&lt;/td&gt;
					&lt;td&gt;部分推理模型默认 thinking，&lt;code&gt;content&lt;/code&gt; 在 reasoning 里&lt;/td&gt;
					&lt;td&gt;显式 &lt;code&gt;thinking:{&amp;quot;type&amp;quot;:&amp;quot;disabled&amp;quot;}&lt;/code&gt; 并要求 JSON 输出&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Java 运行报 &lt;code&gt;UnsupportedClassVersionError&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;用了旧版 JDK&lt;/td&gt;
					&lt;td&gt;用 JDK 21 + &lt;code&gt;./gradlew&lt;/code&gt;，或直接 &lt;code&gt;javac/java&lt;/code&gt; 跑 &lt;code&gt;MinimalSuspendDemo&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;计划里写了“等待”，运行时不暂停&lt;/td&gt;
					&lt;td&gt;把等待当成了数据标记&lt;/td&gt;
					&lt;td&gt;改成 &lt;code&gt;return&lt;/code&gt;/&lt;code&gt;break&lt;/code&gt;/状态机迁移，并写进回归测试&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="参考"&gt;参考
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;LangGraph Documentation, “Workflows and Agents”, &lt;a class="link" href="https://docs.langchain.com/oss/python/langgraph/workflows-agents/" target="_blank" rel="noopener"
 &gt;https://docs.langchain.com/oss/python/langgraph/workflows-agents/&lt;/a&gt;（Workflow 与 Agent 的路径决定方式，成文时已核对）。&lt;/li&gt;
&lt;li&gt;LangGraph Documentation, “Interrupts / Human-in-the-loop”, &lt;a class="link" href="https://docs.langchain.com/oss/python/langgraph/interrupts/" target="_blank" rel="noopener"
 &gt;https://docs.langchain.com/oss/python/langgraph/interrupts/&lt;/a&gt;（&lt;code&gt;interrupt()&lt;/code&gt;+checkpointer 的挂起恢复能力，本篇只作方向标记）。&lt;/li&gt;
&lt;li&gt;本话演示代码与回归测试：配套代码仓库中的 &lt;code&gt;python/control_modes.py&lt;/code&gt;、&lt;code&gt;java/.../Main.java&lt;/code&gt;、&lt;code&gt;java/.../MinimalSuspendDemo.java&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>第四话｜模型说调 refund_order，Runtime 怎么不把事情搞砸？</title><link>https://zh.renxinblog.cn/post/c04-tool-registry/</link><pubDate>Tue, 14 Apr 2026 00:00:00 +0000</pubDate><guid>https://zh.renxinblog.cn/post/c04-tool-registry/</guid><description>&lt;img src="https://zh.renxinblog.cn/images/c04-tool-registry-cover-v2.png" alt="Featured image of post 第四话｜模型说调 refund_order，Runtime 怎么不把事情搞砸？" /&gt;&lt;p&gt;第三话结束时，我们留下一个问题没解决：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;code&gt;tool_calls&lt;/code&gt; 永远是候选，不是命令。谁来判断&amp;quot;能不能执行、怎么执行&amp;quot;？&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;这就是第四话要回答的。模型提出的工具调用，哪怕 JSON 合法、参数齐全，也可能是错的、越权的、有副作用的。Runtime 不能照单全收——它得先校验，再执行，而且要在执行出问题时不把局面搞得更糟。&lt;/p&gt;
&lt;p&gt;这里最危险的，不是选错工具，而是&lt;strong&gt;一个有副作用、结果又说不清的操作&lt;/strong&gt;。比如模型要调 &lt;code&gt;refund_order&lt;/code&gt; 退款：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;handler 执行到一半超时了。钱到底退了没有？直接重试，可能重复退款；不重试，用户又以为没退成。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;这一话就沿着这条退款主线，讲清楚 Runtime 怎么把&amp;quot;模型说要调用&amp;quot;变成&amp;quot;安全地执行&amp;quot;——以及最关键的，超时之后怎么根据真实状态做决定，而不是靠猜。&lt;/p&gt;
&lt;h2 id="一模型说调-refund_orderruntime-先干什么"&gt;一、模型说&amp;quot;调 refund_order&amp;quot;，Runtime 先干什么？
&lt;/h2&gt;&lt;p&gt;模型返回的 &lt;code&gt;tool_calls&lt;/code&gt; 里，是一段结构化申请单：工具名 &lt;code&gt;refund_order&lt;/code&gt;、参数 &lt;code&gt;order_id&lt;/code&gt; 和 &lt;code&gt;idempotency_key&lt;/code&gt;。但它是模型生成的，&lt;strong&gt;是外部输入，不是已经校验过的命令&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Runtime 拿到它，第一件事不是执行，是问三个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;这个工具名，是允许执行的吗？&lt;/li&gt;
&lt;li&gt;参数齐全、类型对吗？&lt;/li&gt;
&lt;li&gt;这个工具现在可用吗？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;任何一个不过，就不进 handler，而是返回一个&lt;strong&gt;结构化的错误&lt;/strong&gt;，让上层能稳定地分支处理。&lt;/p&gt;
&lt;p&gt;为什么要结构化，不能是一段&amp;quot;调用失败，请稍后再试&amp;quot;的文案？因为文案里没有可编程的信息——程序没法可靠地区分&amp;quot;工具不存在&amp;quot;和&amp;quot;参数缺了&amp;quot;和&amp;quot;执行到一半挂了&amp;quot;。而这三种情况，处理方式完全不同：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;错误码&lt;/th&gt;
					&lt;th&gt;发生了什么&lt;/th&gt;
					&lt;th&gt;该怎么处理&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;UNKNOWN_TOOL&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;模型报了个不存在的工具名&lt;/td&gt;
					&lt;td&gt;拒绝，修正工具选择&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;INVALID_ARGUMENT&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;参数不满足契约&lt;/td&gt;
					&lt;td&gt;补参数或拒绝&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;TOOL_UNAVAILABLE&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;工具已下线或不健康&lt;/td&gt;
					&lt;td&gt;告知不可用，不能硬调&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;EXECUTION_ERROR&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;handler 或下游执行失败&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;按恢复语义决定&lt;/strong&gt;，见下一节&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;前三个错误，handler 根本没执行——它们发生在执行之前。第四个才是真正进到了执行阶段，也是最麻烦的。&lt;/p&gt;
&lt;h2 id="二执行时出了问题最怕的是结果说不清"&gt;二、执行时出了问题，最怕的是&amp;quot;结果说不清&amp;quot;
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;refund_order&lt;/code&gt; 通过了校验，handler 真的去调订单服务了——然后超时。&lt;/p&gt;
&lt;p&gt;超时意味着什么？&lt;strong&gt;意味着&amp;quot;结果不确定&amp;quot;&lt;/strong&gt;。请求可能压根没到达订单服务，也可能已经到达、退款都成功了，只是响应没传回来。你分不清。&lt;/p&gt;
&lt;p&gt;这就是最危险的地方：如果 Runtime 把&amp;quot;超时&amp;quot;直接当成&amp;quot;没执行&amp;quot;，再调一次，就可能退了两笔钱。而如果当成&amp;quot;失败了&amp;quot;，用户这边又显示退款失败，但其实钱已经退了——两边对不上。&lt;/p&gt;
&lt;p&gt;所以规则只有一条：&lt;strong&gt;超时之后，先查这笔操作的真实状态，不要直接重试。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;怎么查？靠 &lt;code&gt;idempotency_key&lt;/code&gt;（幂等键）。它是发起退款时就带上的、这笔操作的唯一标识。领域服务按这个键记录&amp;quot;这笔退款到底执行了没有&amp;quot;，Runtime 超时后先去查它，而不是再发起一笔。&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 A[refund_order 超时] --&gt; B[按 idempotency_key 查询真实状态]
 B --&gt;|SUCCEEDED| C[回放成功结果&lt;br/&gt;不再调 handler]
 B --&gt;|NOT_EXECUTED| D[确认没执行&lt;br/&gt;才重试一次]
 B --&gt;|UNKNOWN / 执行中| E[等待或人工核验]&lt;/pre&gt;&lt;p&gt;三条分支，对应三种真实状态：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SUCCEEDED&lt;/code&gt;&lt;/strong&gt;：钱已经退了。Runtime 把成功结果回放给用户，&lt;strong&gt;不再调 handler&lt;/strong&gt;。这是最容易犯错的场景——明明已经成功，却因为&amp;quot;没收到响应&amp;quot;而再退一次。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;NOT_EXECUTED&lt;/code&gt;&lt;/strong&gt;：确认没执行过。这时重试一次是安全的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;UNKNOWN&lt;/code&gt; / 执行中&lt;/strong&gt;：状态也查不到。这时既不能重试，也不能谎报成功，只能等待对账或转人工核验。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;跑一遍主线 demo，你会看到这样一条 Trace：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;refund_order response=EXECUTION_ERROR
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;idempotency_status=SUCCEEDED
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;runtime_action=REPLAY_SUCCESS
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;final=SUCCESS
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;refund_order handler_execution_count=1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最后一行是整条链路的关键证据：&lt;strong&gt;&lt;code&gt;handler_execution_count=1&lt;/code&gt;&lt;/strong&gt;。它证明 Runtime 虽然经历了&amp;quot;超时 → 查状态 → 回放&amp;quot;，但真正执行退款的 handler &lt;strong&gt;只跑了一次&lt;/strong&gt;。没有重复退款。&lt;/p&gt;
&lt;h2 id="三把谁负责什么钉死"&gt;三、把&amp;quot;谁负责什么&amp;quot;钉死
&lt;/h2&gt;&lt;p&gt;超时恢复这件事，看着是 Runtime 一个人的活，其实牵涉三个角色，职责不能混：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;角色&lt;/th&gt;
					&lt;th&gt;负责什么&lt;/th&gt;
					&lt;th&gt;不负责什么&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;领域服务（&lt;code&gt;RefundDomain&lt;/code&gt;）&lt;/td&gt;
					&lt;td&gt;记录和查询&amp;quot;退款到底发生没有&amp;quot;&lt;/td&gt;
					&lt;td&gt;不负责重试策略，也不知道 Runtime 怎么编排&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Runtime&lt;/td&gt;
					&lt;td&gt;按恢复语义，决定回放、重试还是等待&lt;/td&gt;
					&lt;td&gt;不拥有退款事实，退款有没有发生得问领域&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;模型&lt;/td&gt;
					&lt;td&gt;只提出候选调用&lt;/td&gt;
					&lt;td&gt;不拥有执行权，更不能拿它的话当退款状态&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一句话：&lt;strong&gt;有副作用工具的超时恢复，首先是领域状态问题，其次才是 Runtime 的重试策略问题。&lt;/strong&gt; 退款有没有发生，是领域事实，只能问领域服务；Runtime 能做的是照着这个事实去编排下一步，而不是自己猜。&lt;/p&gt;
&lt;p&gt;反过来看，如果领域服务不记录幂等状态，Runtime 再聪明也没用——它没有可以查询的真相，超时之后只能抓瞎。所以幂等这件事，&lt;strong&gt;得从工具设计那天就带上&lt;/strong&gt;，而不是等出了超时才补。&lt;/p&gt;
&lt;h2 id="四把这条链路跑起来"&gt;四、把这条链路跑起来
&lt;/h2&gt;&lt;p&gt;配套 demo 在 &lt;code&gt;code/agent-tutorial/c04-tool-registry/&lt;/code&gt;，Python 和 Java 各一份，语义一致。它不调用 LLM、订单或支付系统——所有 handler 都是本地确定性模拟，所以它验证的是 &lt;strong&gt;Runtime 的控制流&lt;/strong&gt;，不是退款系统的可用性证明。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; code/agent-tutorial/c04-tool-registry/python
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 registry.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 -m unittest test_registry.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ../java
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gradle run
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;它覆盖六类场景，每一类都对应上面讲的一个环节：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;场景&lt;/th&gt;
					&lt;th&gt;验证什么&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;工具不存在&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;UNKNOWN_TOOL&lt;/code&gt;，不进 handler&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;工具下线&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;TOOL_UNAVAILABLE&lt;/code&gt;，不能硬调&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;参数缺失&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;INVALID_ARGUMENT&lt;/code&gt;，补参或拒绝&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;正常调用&lt;/td&gt;
					&lt;td&gt;一次执行，&lt;code&gt;SUCCEEDED&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;超时但已成功&lt;/td&gt;
					&lt;td&gt;查幂等 → &lt;code&gt;SUCCEEDED&lt;/code&gt; → 回放，&lt;code&gt;handler_execution_count=1&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;超时且未执行&lt;/td&gt;
					&lt;td&gt;查幂等 → &lt;code&gt;NOT_EXECUTED&lt;/code&gt; → 重试一次&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;其中&amp;quot;超时但已成功&amp;quot;是最值得盯着看的一条：它模拟了 &lt;code&gt;handler&lt;/code&gt; 执行成功后、响应却丢失的场景——退款已经发生，&lt;code&gt;SUCCEEDED&lt;/code&gt;，但 Runtime 收到的是超时。正确的 Trace 必须是&amp;quot;回放成功结果&amp;quot;，而不是&amp;quot;再退一次&amp;quot;。&lt;/p&gt;
&lt;h2 id="五还差的执行成功--永远可靠"&gt;五、还差的：执行成功 ≠ 永远可靠
&lt;/h2&gt;&lt;p&gt;跑通这条链路，Runtime 能安全地执行一次有副作用的调用了。但这离&amp;quot;可靠&amp;quot;还有距离，几件事本篇点到为止，先立个边界：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;一次超时不等于工具坏了&lt;/strong&gt;。订单服务偶发抖动，不能立刻把 &lt;code&gt;refund_order&lt;/code&gt; 永久下线；连续失败达到阈值才考虑熔断。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;熔断后的探测，不能用一笔新退款去试&lt;/strong&gt;。要用无副作用的健康检查——否则&amp;quot;检查服务恢复没有&amp;quot;这件事本身，又制造了一笔业务动作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runtime 不替代鉴权、审批和审计&lt;/strong&gt;。工具能不能调，除了契约校验，还涉及&amp;quot;这个用户有没有权限&amp;quot;&amp;ldquo;要不要二次确认&amp;quot;&amp;ldquo;留不留审计留底&amp;rdquo;——这些是 Runtime 与领域服务的责任，本篇没展开。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些都属于&amp;quot;从单次安全执行到生产级可靠&amp;quot;的延伸，先把边界划在这，不抢后续章节的戏。&lt;/p&gt;
&lt;h2 id="到下一话问题才真正变难"&gt;到下一话，问题才真正变难
&lt;/h2&gt;&lt;p&gt;现在，Runtime 已经能把&lt;strong&gt;单个&lt;/strong&gt;候选调用安全地执行，并在超时后不搞砸。&lt;/p&gt;
&lt;p&gt;但它还不会根据第一次工具的结果，决定第二步做什么。&lt;/p&gt;
&lt;p&gt;第五话才进入真正的 ReAct 循环：第二个 Action 必须依赖第一个 Observation。那时，今天建立的 ToolResponse 和幂等恢复，会成为循环里可复用的执行底座。&lt;/p&gt;</description></item></channel></rss>