规划与 ReAct:从「无计划」到「依赖图」的四种形态
前面七篇搭好了 Agent 的躯干
、对话循环、工具契约、上下文、记忆、状态。但有一件事一直没讲——面对一个任务,Agent 到底应该怎么组织它的行动?一步到位?列个清单逐条做?边做边改计划?还是拆成一张可以并行的任务图?
这就是规划(Planning) 层要回答的问题。它不是”永远多规划一点”,而是为任务匹配最合适的规划形态——并且知道什么时候该停下来重新规划。
本篇原理部分译自 agentic 课程 Ch.09,案例来自 agent开发系列的工作流编排横评。
为什么规划这件事值得单独讲
没有计划,Agent 会空转(thrash)
,第 4 步走错了却一直没发现,调一个工具去做上一个工具已经做完的事。但计划太多,同一个 Agent 会把一半 token 花在提出一个 20 步的蓝图上——而这个蓝图在第一个工具结果和它冲突的那一刻就作废了。
代价体现在 token、延迟,以及最糟的一种:自信地给出了解决错误问题的最终答案。
修复方法不是”多规划”,而是把规划形态匹配到任务,并知道何时重新规划。
四种形态,并排看
flowchart TD
subgraph s1["① 无计划 — 反应式"]
a1["用户提问"] --> a2["模型每轮<br/>挑下一个动作"]
end
subgraph s2["② 清单"]
b1["模型写有序列表"] --> b2["逐步执行<br/>做完就划掉"]
end
subgraph s3["③ 规划-执行-重规划"]
c1["模型提出计划"] --> c2["执行一步"] --> c3["观察结果"] --> c1
end
subgraph s4["④ 依赖图"]
d1["模型输出任务 DAG"] --> d2["可运行节点<br/>并行执行"] --> d3["汇聚点综合"]
end
判断一个任务需要哪种形态,问两个问题:目标有多明确? 和 第 1 步的结果有多大概率改变第 2 步的计划? 目标明确、发散度低的任务适合前两种;后两种是为路径真正不确定、或存在独立分支的任务准备的。
形态一 · 无计划(反应式)
Agent 挑一个工具、运行、然后要么回答、要么挑下一个工具。这是短问答、一次性查询、简单转换的默认形态。
“装的 node 是什么版本?” 不需要计划。
快、便宜、流畅。但在任何真正多步的任务上都容易空转。
形态二 · 清单
模型在动手前写一个简短的有序列表,边做边划掉。
type ChecklistPlan = {
objective: string;
steps: Array<{
id: string;
text: string;
status: "pending" | "in_progress" | "done" | "skipped";
}>;
};
这个列表本身就是记忆——提示词构建器每轮把当前列表注入,模型对照它自我纠正。适合 3-8 个有序步骤、模型大致能记住整个计划但进度追踪有帮助的场景。OpenCode 的 TodoWriteTool 是最清晰的参照。
形态三 · 规划-执行-重规划
模型提出计划,执行一步或几步,观察结果,然后在结果改变全局时重新提出计划。
stateDiagram-v2
[*] --> 规划
规划 --> 执行 : 跑下一步
执行 --> 观察 : 工具结果
观察 --> 重规划 : 新信息改变了计划
观察 --> 执行 : 计划仍有效
观察 --> 完成 : 目标已满足
重规划 --> 执行
完成 --> [*]
适合调查、调试、研究——任何你在第 3 步做完之前不知道第 5 步长什么样的任务。代价是每次重规划多一次模型调用,收益是 Agent 不会被一个过时的计划锁死。
这就是大多数人口中的 “ReAct”(Reason + Act)
→行动→观察的循环。它稳定、通用,是目前 Coding Agent 的主流形态。形态四 · 依赖图
对于有独立分支的任务(并行审查三个文件;从三个源拉取再合并),把计划表达成一张有向图,并行执行可运行节点。
// 可运行 = pending 且所有依赖已完成
function runnableNodes(nodes: PlanNode[]) {
const done = new Set(
nodes.filter(n => n.status === "done").map(n => n.id)
);
return nodes.filter(n =>
n.status === "pending" && n.dependsOn.every(id => done.has(id))
);
}
实践中这种形态住在比纯规划高一层的地方——委派(下一篇 Ch.09 多智能体编排),可运行节点通常变成子 Agent 调用。适合有明确并行度和汇聚点的工作流;不适合任何看起来线性的东西——那样图只是为复杂而复杂。
怎么选
| 任务形状 | 规划形态 |
|---|---|
| 一个明显的动作 | 无计划 |
| 3-8 个有序步骤 | 清单 |
| 路径不确定、结果改变下一步 | 规划-执行-重规划 |
| 独立分支带汇聚 | 依赖图 |
两个要警惕的反模式:
- 因为依赖图看起来高级就用它,而清单本可以搞定;
- 对一个明显需要结构的任务赖在”无计划”模式里。
模型对这两种都会顺着你——得靠你的设计推回去。
计划该放在哪
计划就是文本,但放在哪很重要。三个地方:
- 工具结果里
todo_write,结果是新列表,像任何工具结果一样出现在易变尾部。 - 工作记忆里(上篇的可变草稿纸)
WorkingMemory.currentPlan的一部分,每轮渲染进提示词。 - 一个用户可编辑的独立文件里(
plan.md) 和用户共享这个产物,谁都能改。
跨轮次把计划放在同一个地方,这样模型知道去哪找。别一会儿写文件、一会儿返回工具结果——挑一个。
规划型 Agent vs 构建型 Agent
真实系统做的一个有用区分:做规划的 Agent 和做构建的 Agent 用不同的工具集。
- 规划型 Agent,不编辑、不 shell,输出一个结构化计划。往往用便宜模型就够。
- 构建型 Agent,含写入、编辑、shell。贵的模型住在这里。
交接
(由用户或策略),然后构建 Agent 以这个计划为起始上下文运行。OpenCode 注册了独立的plan 和 build Agent 配置,正是这个形状。
这也呼应了 Claude Code 的 Plan 模式——先只读探索出计划,批准后再切到可写执行。“先想后做”是跨系统反复出现的模式。
隐式 vs 显式计划
有些 Agent 从不写计划——它们只是不断挑下一个工具。有些则把写计划当作第一个动作。隐式在小任务上更快;显式能早early catch”脆弱假设”这种失败模式。
一个有用的默认:如果任务描述里点了两个或更多交付物,就要求一个显式计划;否则让模型每轮自己决定。
最便宜的推动方式
,对看起来多步的任务加一句 “先写出你的计划”。模型通常会照做,而它写出的计划本身就是有用的信号——如果模型连计划都说不清,说明任务本身欠规约。什么时候该重新规划
四个”计划已失效”的信号:
- 一个工具结果和计划的前提矛盾——比如计划假设某文件存在,结果它不存在。
- 同一步反复失败——重试两三次仍不过,说明方案本身有问题,不是执行问题。
- 出现了计划没预见的新子任务——发现要先修一个历史遗留问题才能继续。
- 目标本身被用户修正了——中途用户补充或改变了需求。
任意一个触发,就该停下来重新规划,而不是硬着头皮把过时的计划执行完。
小结
规划的本质是为任务匹配最简单够用的形态,并识别任务什么时候在要求你升到上一级:
- 一步 → 无计划
- 几步有序 → 清单
- 路径不定 → 规划-执行-重规划(ReAct)
- 独立并行 → 依赖图(通向多智能体编排)
计划要放在固定的地方、当作记忆管理;规划和构建可以用不同工具集的 Agent 分离;显式计划的最大价值,是逼出”任务是否欠规约”这个信号。
下一篇进入多智能体编排——当依赖图里的节点变成一个个子 Agent 时,谁来决定并行度?这是整个专题最烧脑的一章。
本文主要参考:
- agentic-ai-system-course Ch.09 Planning Patterns(四形态、replan 触发、plan-only vs build)
- agent开发系列 / 工作流编排(真实项目的规划实现)
- Claude Code Plan 模式(先想后做的哲学,见本站《Claude Code 专题·工作流篇》)