第十二章 计划模式:enablePlanMode() 让 LLM 先写计划再执行,支持中途改计划
"用户让 agent 调研 5 件事。1.x 时代我们用
PlanNotebook让 agent 自己写计划;2.0 把这个能力下沉到HarnessAgent.enablePlanMode(),多了plan_enter/plan_write/plan_exit/todo_write一套内置工具——LLM 在 plan 模式下会强制先写计划再执行。"本章你将学到:如何开启 plan mode、4 个内置工具各自做什么、怎么让用户中途改计划、以及 plan 与 subagent 的协作。
12.1 Plan 模式解决了什么问题?
没有 plan 模式时,agent 收到一个复杂任务(比如"给我从零搭一个用户认证系统")后,ReAct 循环是:
思考 → 调工具 → 看结果 → 思考 → 调工具 → 看结果 → ...
每一步都是临场判断。这会导致三个问题:
| 风险 | 例子 |
|---|---|
| 跳过关键步骤 | 先写代码再想数据库表结构,改四五轮 |
| 做多余的事 | 用户只要登录,agent 把 OAuth2.0、SAML、FIDO 全实现了 |
| 人无法中途介入 | agent 一口气跑到底,发现方向错了已经晚了——之前的所有操作都是破坏性的 |
Plan 模式把一次复杂任务拆成两个阶段:
PLAN 阶段(只读) BUILD 阶段(执行)
─────────────────── ─────────────────
agent 看文件、调研项目现状 agent 按 plans/PLAN.md 一步步做
agent 把计划写入 plans/PLAN.md 每条做完勾掉 todo
⚠ 此时所有非只读工具被阻止 限制解除,可以写文件、调 API、部署
│
人看完 PLAN.md 说"行" ──────→ 放行 │
人看完 PLAN.md 说"不行,改" → agent 用 plan_write 修订
人看完 PLAN.md 说"别做了" ──→ 止损,零破坏
核心思想:让人在 agent 动手之前有"一票否决权"。 这就像装修队先出图纸给你审,审过了再砸墙布线——而不是一进门就开始砸。
对于"今天天气怎么样"这种单步任务,plan 模式毫无意义。对于"从零搭建一个项目"、"同时调研 5 件事"、"批量重构代码"这类多步任务,plan 模式是防止 agent 跑偏的最后一道防线。
12.2 1.x PlanNotebook → 2.0 plan mode
1.x 的 PlanNotebook 是一个"让 agent 写计划的笔记本"对象,业务方需要:
- 手动
notebook.createPlan(...) - 写自己的
ReActAgent适配层让 agent 知道有PlanNotebook - 计划改起来需要主动调用
2.0 把"写计划"做成 agent 运行模式之一:
enablePlanMode(true)一行开启- 内置 4 个工具:
plan_enter/plan_write/plan_exit/todo_write PlanModeMiddleware在 PLAN 阶段拦截所有非只读工具调用,返回DENIED- LLM 调了
plan_enter才进入只读阶段,调了plan_exit才恢复执行能力 - 计划文件落在
workspace/plans/PLAN.md,可读、可改、可被 git 管理
⚠️ 2.0 重大变更
1.x 的
io.agentscope.core.plan.PlanNotebook在 2.0 中被移除。本章 12.8 给出从PlanNotebook到 plan mode 的最小迁移。
12.3 第一个 plan mode 例子
import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.core.message.UserMessage;
import io.agentscope.core.model.DashScopeChatModel;
import io.agentscope.harness.HarnessAgent;
import java.nio.file.Path;
import java.util.List;
public class Chapter12_PlanMode {
public static void main(String[] args) {
HarnessAgent agent = HarnessAgent.builder()
.name("project_planner")
.sysPrompt("你是一个项目经理,会用 plan mode 写计划。")
.model(DashScopeChatModel.builder()
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.modelName("qwen-plus")
.build())
.workspace(Path.of("./workspace"))
.enablePlanMode(true) // 开启 plan mode
.build();
agent.call(
List.of(new UserMessage("user", """
我下周要办一场 200 人技术大会,请帮我做一份执行计划:
- 场地
- 议程
- 嘉宾
- 报名
- 现场
""")),
RuntimeContext.empty())
.block();
// 跑完看 workspace/plans/PLAN.md —— agent 把计划写在这里
}
}
跑完 workspace/plans/PLAN.md 大致是:
# 大会执行计划(2026-06-15)
## Step 1 — 锁定场地
- 目标:杭州某酒店宴会厅
- 负责:行政
- 完成标准:拿到合同 + 付款凭证
## Step 2 — 公布议程
- 目标:议程在官网公开
- 依赖:Step 1
... (后续 4 步)
12.4 四个内置 plan 工具
| 工具 | 何时被调用 | 副作用 |
|---|---|---|
plan_enter |
agent 决定"这是个多步任务" | 写入 workspace/plans/PLAN.md 头部,进入 plan 模式 |
plan_write |
写或修订 PLAN.md 里的步骤 |
修改 workspace/plans/PLAN.md |
plan_exit |
agent 完成所有计划步骤 | 写 PLAN.md 收尾,更新状态为 DONE |
todo_write |
任何时候记录子任务 | 写入 AgentState 的 todo 列表,配合 Session 持久化 |
todo_write严格说不是"plan 工具",而是"todo 工具"——但 plan mode 下两者是配对的:plan 管"长期大计划",todo 管"短期可勾掉的清单"。
12.5 用户中途改计划
workspace/plans/PLAN.md 是普通 Markdown,agent 在每轮推理时都会读一遍当前 PLAN.md。这意味着:
用户:把 step 3 的'嘉宾'改成都用远程连线的形式。
agent:
1. 读当前 PLAN.md
2. 调用 plan_write 改 step 3
3. 输出确认
人也可以直接编辑 PLAN.md——下一轮 agent 看到的是人编辑后的版本。
12.6 plan + subagent 协作
enablePlanMode() 不影响 subagent 调度——agent_spawn / agent_send / agent_list 仍然可用。一个常见模式:
主 agent 拿到任务:
1. plan_enter,写下 5 步
2. 第 1 步:spawn subagent A(同步)
3. 第 2 步:spawn subagent B(同步)
4. 第 3、4 步:async spawn C、D
5. todo_write 跟踪
6. 等 C、D 回来,todo 勾掉
7. plan_exit
HarnessAgent.builder().enablePlanMode(true) + 多个 subagent 一起用,是 2.0 推荐的中型工作流模式。
12.7 plan 模式的"自动门控"
Plan mode 下,prompt 里强制设定了“agent 在写完 plan 之前不会调用任何业务工具”的规则。这是 2.0 优于 1.x 的安全设计:
- 1.x:LLM 可能跳过 PlanNotebook 直接动手做事。
- 2.0:plan mode 下必须先
plan_enter,用户可以在 plan 阶段介入。
如果你想"先看 plan 再放行"——给 plan_enter 配一个 Permission 规则 ASK:
import io.agentscope.core.permission.*;
PermissionContextState perms = PermissionContextState.builder()
.mode(PermissionMode.ACCEPT_EDITS)
.addAskRule("plan_enter",
new PermissionRule("plan_enter", null, PermissionBehavior.ASK, "userSettings"))
.build();
这样每次 plan_enter 时前端会弹出"agent 写了如下 plan,是否放行?",体现了HITL 模式(Human In The Loop)。
12.8 最小迁移清单(1.x PlanNotebook → 2.0 plan mode)
| 1.x 用法 | 2.0 等价 |
|---|---|
PlanNotebook.createPlan(...) |
enablePlanMode(true) + LLM 自己调 plan_enter |
notebook.addStep(...) |
plan_write |
notebook.finishStep(idx) |
plan_write 改对应 step 状态 |
notebook.getCurrentPlan() |
读 workspace/plans/PLAN.md |
业务方主动调 notebook.xxx |
LLM 在 plan mode 下自动调 |
12.9 完整可运行示例
public class Chapter12_PlanWithSubagent {
public static void main(String[] args) {
DashScopeChatModel model = DashScopeChatModel.builder()
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.modelName("qwen-plus")
.build();
SubagentDeclaration research = SubagentDeclaration.builder()
.name("research")
.description("做单点调研;输入主题,输出 200 字摘要")
.inlineAgentsBody("你是一个研究员,每主题输出 200 字摘要。")
.build();
HarnessAgent agent = HarnessAgent.builder()
.name("research_lead")
.sysPrompt("""
你是一个研究主管。接到多主题调研任务时:
1. 先 plan_enter 写出计划
2. 对每个主题 async spawn research subagent
3. 用 todo_write 跟踪每个 subagent 的状态
4. 等所有 subagent 回来后 plan_exit
""")
.model(model)
.workspace(Path.of("./workspace"))
.subagent(research)
.enablePlanMode(true)
.build();
agent.call(
List.of(new UserMessage("user", """
请调研以下 3 个主题:
1. 杭州咖啡店数量
2. 上海咖啡店数量
3. 成都咖啡店数量
""")),
RuntimeContext.empty())
.block();
}
}
跑完你会看到:
workspace/
├── plans/
│ └── PLAN.md # 3 步计划
└── state/
└── session-*.json # 包含 todo 列表
12.10 本章小结
HarnessAgent.enablePlanMode(true)开启 plan mode,强制 agent 先 plan 再 act。- 4 个内置工具:
plan_enter/plan_write/plan_exit/todo_write。 workspace/plans/PLAN.md是普通 Markdown,可以被人编辑、git 管理。- plan mode 不影响 subagent:可以一边写计划一边 spawn。
- Permission 规则可以让用户在
plan_enter时介入。