计划模式(Plan Mode)详解
版本基准:本文档基于 AgentScope 2.0 GA(
v2.0.0)编写。具体版本号以 Release Notes 为准。
一句话概括
计划模式让 Agent 在动手干活之前先"写好施工图纸、交给监理审批",通过只读阶段强制思考、HITL 确认门控把关,有效降低"边想边改、改坏一片"的概率。
你能学到什么
- 理解计划模式的核心思想:先规划再执行,两阶段分离
- 掌握只读阶段的工具白名单机制(9 个白名单工具 + 只读工具)
- 理解 HITL(Human-in-the-Loop)退出确认的原理
- 学会使用 planFileDirectory 配置计划文件位置
- 了解可选的
allowShellInPlanMode()—— 在 plan 阶段放开 shell 做只读调查 - 掌握运行期切换 PermissionMode("危险开关"逃生口)
- 了解计划状态的持久化与恢复
- 掌握编程控制 API:enterPlanMode(ctx)、exitPlanMode(ctx)、isPlanModeActive(ctx)
- 学会判断一次运行到底落到哪种终态(四种终态)
- 理解计划模式与 todo_write 的配合方式
- 了解子 Agent 在计划模式下的已知限制
前置知识
详见 README.md 前置知识部分。本篇额外需要:
- Middleware 机制:了解 2.0 的洋葱模型和挂载点(见 01-overview)
- AgentState:了解运行时状态管理(见 03-context)
- 工作区目录结构:知道 workspace 下有哪些文件和文件夹(见 01-overview)
核心概念
先规划再执行 —— 就像建筑工地的图纸审核
想象你是一个建筑工地的项目经理。现在要盖一栋大楼,你不会拿到需求就抡起锤子开干——你会先让设计师画好图纸,然后拿着图纸去找监理审批。监理觉得没问题、签了字,施工队才开始动手。
如果设计师边画图边施工,可能出现"地基打了一半发现图纸错了"这种灾难。所以正确流程是:图纸阶段只能画图和看现场,不能动一砖一瓦。
计划模式的核心思想也是如此:把 Agent 的工作分成两个阶段——
| 阶段 | 建筑工地类比 | Agent 行为 |
|---|---|---|
| 只读阶段(Plan Phase) | 设计师画图纸、勘察现场 | 只能读文件、搜索代码、写计划文件,不能执行任何修改操作 |
| 执行阶段(Build Phase) | 施工队按图纸施工 | 所有工具解禁,可以写文件、执行命令等 |
这两个阶段之间有一个硬性门控:Agent 不能自己从只读阶段跳到执行阶段,必须经过人类确认(HITL),就像监理必须签字才能开工。
只读阶段的工具白名单 —— 就像阅览室的"只许看不许动"规则
生活类比:想象你在图书馆的古籍阅览室。规则很明确——你可以看、可以记笔记、可以翻目录,但绝对不能在书上写字、不能把书带走、不能撕页。如果你违反规则,管理员会立刻阻止你。
技术解释:当 Agent 进入计划模式后,它被限制在一个"阅览室"里。PlanModeMiddleware 在 onActing 阶段拦截工具调用,判定为允许的工具才放行,其余的一律生成一个 DENIED 的工具结果(不真正执行)。被允许的工具有三类,共 9 个白名单工具 + 所有只读工具:
| 类别 | 工具 | 说明 |
|---|---|---|
| 计划控制工具 | plan_enter、plan_write、plan_exit | 计划模式的进入 / 写计划文件 / 退出 |
| 协作工具 | todo_write、agent_spawn、agent_send、agent_list、task_output、task_list | 待办清单与子 agent 编排(非破坏性) |
| 所有只读工具 | read_file、grep_files、list_directory 等 | 由 ToolBase.isReadOnly() 判定,可以查看代码和文件 |
如果 Agent 在只读阶段尝试调用非白名单工具(比如 write_file 或默认情况下的 execute),会立刻被拒绝,并收到这样的提示:
Blocked: you are in PLAN mode (read-only). You may investigate and run read-only
tools, record your plan with plan_write, and call plan_exit when ready to execute.
Do not modify files or run mutating commands until the plan is approved.
Agent 看到这个拒绝信息后,会自然地回到"先写计划"的正确路径上。
实现要点(GA 源码):PlanModeMiddleware 的判定写在 isPermitted(toolName) 里——只要工具名命中 ALWAYS_ALLOWED 集合、或被额外放行(如 allowShellInPlanMode 注册的 execute)、或通过 readOnlyResolver 判定为只读,就放行。注意它没有复用 PermissionMode.EXPLORE:那个 mode 是权限引擎构建时快照、无法运行时切换,而 plan mode 必须能动态开关。
为什么要用 plan_write 而不是直接开放 write_file?
因为 write_file 可以写任意文件,如果把 write_file 加入白名单,Agent 在只读阶段就能偷偷修改代码文件,那"只读"就形同虚设了。plan_write 只能写计划文件,安全可控。
HITL 退出确认 —— 就像监理签字放行
生活类比:建筑师画完图纸后,不能自己宣布"开搞!"——他必须把图纸交给监理工程师审核。监理看完觉得方案可行,在图纸上签了字,施工队才能进场。如果监理觉得有问题,打回去重画。
技术解释:Agent 在只读阶段完成了计划编写,调用 plan_exit 退出时,不会直接进入执行阶段。PlanExitTool.checkPermissions(...) 恒定返回一个 ASK 决策,复用权限系统的 HITL 流程弹出人工确认请求:
Agent 调用 plan_exit(参数:summary,可选)
│
▼
┌─────────────────────────────┐
│ HITL 确认请求 │
│ │
│ "Agent 已完成计划编写, │
│ 是否允许进入执行阶段?" │
│ │
│ [确认] [拒绝] │
└──────────┬──────────┬────────┘
│ │
确认 拒绝
│ │
▼ ▼
进入执行阶段 留在只读阶段
这个 HITL 门控是计划模式最关键的安全设计——它防止了模型"自作主张"直接进入执行阶段,确保人类始终在重大操作前有最终决定权。即使没有配置任何 allow/deny 规则,轻量级权限路径也能 honor 这个 ASK 自检,所以开箱即用。
计划文件与 planFileDirectory —— 就像图纸专用档案柜
生活类比:建筑公司的图纸不会随便丢在办公桌上,而是存放在专门的图纸档案柜里。柜子的位置可以指定(比如"3 号档案室 B 柜"),但默认放在"2 号档案室"。
技术解释:计划文件默认存放在工作区的 plans/PLAN.md(PlanModeManager.DEFAULT_PLAN_DIR = "plans"),你可以通过 planFileDirectory() 自定义这个位置:
workspace/
├── plans/ ← 默认的计划文件目录
│ └── PLAN.md ← 计划内容写在这里
├── AGENTS.md
├── MEMORY.md
└── ...
配置方式:
| Builder 方法 | 默认值 | 说明 |
|---|---|---|
enablePlanMode() / enablePlanMode(boolean) | false | 是否开启计划模式 |
planFileDirectory(String) | "plans" | 计划文件根目录(相对于 workspace) |
allowShellInPlanMode() / allowShellInPlanMode(boolean) | false | 按需放开 plan 阶段的 shell(execute)——见下节 |
计划文件跟着你选择的文件系统模式走——本机、沙箱、远端 KV 都行,天然分布式可用。
在 plan 阶段放开 shell(可选)—— 就像"借工具但保证只读"
生活类比:古籍阅览室原则上不让带笔(怕你在书上写)。但有些调查任务确实需要用放大镜、紫外灯看细节。馆长说:"行,工具借你,但你要保证只用来看,不能改。"这就是 allowShellInPlanMode()。
为什么默认禁 shell? shell 是双用途工具:同一次调用既可能是读(cat / ls / grep / git log),也可能是写(rm / > / git commit / npm install)。计划模式完全靠工具名判定是否放行,无法区分这一次是读还是写。默认禁掉 shell,是为了保住"只读"这条保证。
为什么有时需要放开? 通过 shell 读取各种内容,往往是调查代码库、产出切实可行计划的最灵活方式。
放开方式:
HarnessAgent agent = HarnessAgent.builder()
.name("planner")
.model(model)
.workspace(workspace)
.enablePlanMode()
.allowShellInPlanMode() // 让模型在 plan 阶段以只读方式跑 shell
.build();
开启后(PlanModeMiddleware 的 additionalAllowed 集合会包含 execute):
execute被加入 plan 阶段放行名单,模型可以用 shell 做调查;- plan banner 会追加一条提示,要求模型把 shell 用法限制在只读(
cat/ls/grep/git log/diff/show/status),在计划获批前不要跑 mutating 命令; - 专用的文件编辑工具(
write_file/edit_file)仍然被拒——它们是主要的改动入口,所以对文件写入的只读意图依然被强制保证。
取舍提示:这条保证比默认情况更弱(模型仍可能通过 shell 改东西),建议配合沙箱文件系统一起开,把爆炸半径关进沙箱。
运行期切换权限模式 —— "危险开关"逃生口
计划模式只是一个具体的阶段开关。在它之下,每个 session 都带着一个 PermissionMode,由权限引擎在评估时使用。你可以在运行期翻转这个 mode——比如提供一个由用户显式触发的"跳过所有权限确认"开关(类似其它编码工具里的 YOLO / dangerous-skip 开关):
RuntimeContext ctx = RuntimeContext.builder().sessionId("my-session").build();
agent.setPermissionMode(ctx, PermissionMode.BYPASS); // 全部放开、不再弹确认
// ... 跑需要完整权限的操作 ...
agent.setPermissionMode(ctx, PermissionMode.DEFAULT); // 恢复正常管控
PermissionMode current = agent.getPermissionMode(userId, sessionId);
四种 PermissionMode:
| Mode | 含义 |
|---|---|
DEFAULT | 默认,所有操作需要显式 allow 规则 |
EXPLORE | 只读模式,修改工具被拒 |
BYPASS | 全部放开、不再做规则评估 |
DONT_ASK | ASK 决策降级为 DENY(适合无人值守,但仍保留管控) |
setPermissionMode(...) 只改 mode,会保留该 session 已配置的 allow/deny/ask 规则与工作目录,并重建该 session 缓存的权限引擎,使切换在下一次 call 生效;正在进行中的 call 仍沿用启动时的引擎。
⚠ BYPASS 会关闭所有规则评估,因此应当作为显式的、按 session 的主动操作,并建议配合沙箱使用。如果想要无人值守且不弹确认、但仍保留管控,请改用 PermissionMode.DONT_ASK(ASK 决策变成 DENY,而不是被自动放行)。
状态持久化 —— 就像建筑师下班前保存图纸进度
生活类比:建筑师下班时,会把画了一半的图纸存进保险柜。第二天上班,打开保险柜取出图纸,接着昨天的进度继续画。即使公司搬家了(进程重启),只要保险柜还在,工作就不会断。
技术解释:计划模式是运行时状态,会随 AgentState 自动持久化。具体来说,AgentState.getPlanModeContext() 保存了当前的计划模式上下文信息:
┌────────────────────────────────────────────────────┐
│ AgentState │
│ ├─ 对话上下文 │
│ ├─ 摘要 │
│ ├─ 权限上下文 │
│ ├─ PlanModeContext ← 计划模式状态在这里 │
│ │ ├─ 是否处于只读阶段(planActive) │
│ │ ├─ 当前计划文件路径 │
│ │ └─ 其他计划相关元数据 │
│ ├─ 任务上下文 │
│ └─ 工具上下文 │
└────────────────────────────────────────────────────┘
│
│ call() 结束,AgentState 自动持久化
▼
┌────────────────────────────────────────────────────┐
│ AgentStateStore │
│ (默认 JsonFileAgentStateStore,~/.agentscope/ │
│ state/<userId>/<sessionId>/agent_state.json) │
│ └─ 包含完整的 AgentState(含 PlanModeContext) │
└────────────────────────────────────────────────────┘
这意味着:进程重启、节点切换、跨副本恢复后,计划阶段会一起恢复。Agent 不会因为重启就从执行阶段退回只读阶段,也不会丢失已经写好的计划文件。
如何判断运行结果 —— 别只看 isPlanModeActive()
是否进入计划模式由模型自主决定,所以一次运行可能落到四种终态。只看 isPlanModeActive() == false 是有歧义的——别在没确认"是否真的规划过"之前就当成成功:
| 终态 | 含义 | 判断依据 |
|---|---|---|
| 从未进入 plan mode | 模型选择直接在 build 模式工作——合法决定,常因任务与 workspace 不匹配 | 没调用过 plan_enter |
进入 → plan_exit | 成功:规划完、获批,已进入 build 模式 | 调用过 plan_enter,当前 planActive == false |
仍在 plan mode + 有 PLAN.md | 起草了计划但没退出;在同一 session 发后续消息批准继续 | 当前 planActive == true,plans/PLAN.md 存在 |
仍在 plan mode + 无 PLAN.md | "只说不做":最终文本看着像计划但没真正写出 | 当前 planActive == true,plans/PLAN.md 不存在 |
要在代码里区分这几种,可在监听最终 isPlanModeActive() 和计划文件是否存在的同时,记录是否调用过 plan_enter / plan_write(例如从 ToolCallStartEvent 捕获)。
与 todo_write 的配合 —— 就像图纸 + 施工任务单
生活类比:图纸(PLAN.md)描述的是"要建什么、为什么这么建";施工任务单(todo list)描述的是"每天具体干什么"。先有图纸,再根据图纸拆出任务单,施工队按任务单逐条推进。
技术解释:计划模式和 todo_write 是两个独立但常常一起用的概念:
| 概念 | 职责 | 类比 |
|---|---|---|
| 计划模式(Plan Mode) | 阶段开关 + 计划文件 + HITL 退出确认 | 图纸 + 监理审批 |
todo_write | 维护"当前要做什么"的结构化清单(全量替换,恰好一个 in_progress) | 施工任务单 |
典型工作流:
1. Plan 阶段:
Agent 思考 → 读文件 → 写 PLAN.md → plan_exit → HITL 确认
2. 执行阶段:
Agent 根据 PLAN.md → 用 todo_write 拆成 5-8 条任务 → 逐条推进
↑
每轮推理前自动展示
帮助 Agent 保持聚焦
关键区别:不要把 todo_write 和子 Agent 的后台任务(task_output / task_list)混淆——那是完全不同的机制,详见 09-subagent。
关键代码解读
1. 开启计划模式
// 构建一个开启了计划模式的 Agent
HarnessAgent agent = HarnessAgent.builder()
.name("planner") // Agent 名字
.model("my-model-id") // 模型 ID
.workspace(Paths.get(".agentscope/workspace")) // 工作区路径
.enablePlanMode() // 开启计划模式三件套
.planFileDirectory("plans") // 可选;计划文件目录,默认 "plans"
.build();
enablePlanMode() 做了两件事:
- 注册
plan_enter、plan_write、plan_exit三个工具(plan_exit内部checkPermissions恒返回ASK,从而复用权限系统的 HITL 流程要求人类确认) - 安装
PlanModeMiddleware,在只读阶段拦截并拒绝非白名单工具
你也可以用 enablePlanMode(true) 或 enablePlanMode(false) 显式控制开关。
2. 三个计划工具的参数
// plan_enter:进入计划模式(无参数)
// Agent 调用后进入只读阶段,工具白名单生效
// plan_write:把计划写到 plans/PLAN.md
// 参数:content(String 类型,计划文件的完整内容)
// 这是专门为计划模式设计的写入入口——安全、可控
// plan_exit:退出计划模式(触发 HITL 确认)
// 参数:summary(可选,给人类确认用的计划摘要)
// 人类确认后才真正进入执行阶段
3. 编程控制 API
如果你想在业务代码里主动控制计划模式(比如管理台有个"开始规划"按钮),可以直接调用 Agent 的方法:
// 进入计划模式——等价于 LLM 调用 plan_enter
// 需要带上 RuntimeContext(或 userId/sessionId),按会话寻址
agent.enterPlanMode(runtimeContext);
// 退出计划模式——等价于 plan_exit
// 注意:通过程序接口退出不会触发 HITL 确认
agent.exitPlanMode(runtimeContext);
// 查询当前是否处于计划模式
boolean planning = agent.isPlanModeActive(runtimeContext);
注意:通过编程 API 调用 exitPlanMode() 不会触发 HITL 确认——因为人类已经在管理台点了按钮,相当于已经"签字"了。只有 LLM 自己调用 plan_exit 工具时才会触发 HITL 确认。
如果用了 agentscope-admin-spring-boot-starter,还可以通过 HTTP 接口操作:
# 进入计划模式
POST /v1/admin/sessions/{id}:enter-plan-mode
# 退出计划模式
POST /v1/admin/sessions/{id}:exit-plan-mode
# 查看当前计划
GET /v1/admin/sessions/{id}/plan
4. 与 todo_write 配合的完整示例
HarnessAgent agent = HarnessAgent.builder()
.name("planner")
.model("my-model-id")
.workspace(Paths.get(".agentscope/workspace"))
.enablePlanMode() // 开启计划模式
.enableTaskList() // 开启 todo 提示(可选但推荐)
.build();
// 用户发起一个复杂的重构任务
String reply = agent.call("帮我重构 X 模块,把所有硬编码的配置项提取到配置文件里");
// Agent 的工作流程:
// 1. 自动调用 plan_enter 进入只读阶段
// 2. 读代码、搜索相关文件(只读操作)
// 3. 调用 plan_write 把重构计划写到 plans/PLAN.md
// 4. 调用 plan_exit → 弹出 HITL 确认
// 5. 人类确认后进入执行阶段
// 6. 用 todo_write 把计划拆成多个任务(恰好一个 in_progress)
// 7. 逐条执行任务
整体流程图
┌──────────────────────────────────────────────────────────────────┐
│ 计划模式完整流程 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ 用户:"帮我重构 X 模块" │
│ │ │
│ ▼ │
│ ┌────────────────────┐ │
│ │ Agent 推理循环 │ │
│ │ 调用 plan_enter │ ← 进入只读阶段 │
│ └────────┬───────────┘ │
│ │ │
│ ▼ │
│ ╔══════════════════════════════════════════════════╗ │
│ ║ 只读阶段(Plan Phase) ║ │
│ ║ ║ │
│ ║ ┌──────────┐ ┌──────────┐ ┌──────────┐ ║ │
│ ║ │读文件 │ │搜索代码 │ │读计划 │ ║ │
│ ║ │read_file │ │grep_files│ │plan_write│ ║ │
│ ║ └──────────┘ └──────────┘ └──────────┘ ║ │
│ ║ ║ │
│ ║ ✅ 允许:9 个白名单工具(plan_* + todo_write ║ │
│ ║ + agent_* + task_*)+ 所有只读工具 ║ │
│ ║ (allowShellInPlanMode 开启后 + execute) ║ │
│ ║ ❌ 拒绝:write_file, edit_file, 默认 execute ║ │
│ ║ ║ │
│ ║ 工具被拒时返回: ║ │
│ ║ "Blocked: you are in PLAN mode (read-only)..." ║ │
│ ╚════════════╤═════════════════════════════════════╝ │
│ │ │
│ │ 调用 plan_exit │
│ ▼ │
│ ┌────────────────────────────────────────┐ │
│ │ HITL 确认门控 │ │
│ │ │ │
│ │ "Agent 已完成计划,是否允许执行?" │ │
│ │ [确认] [拒绝] │ │
│ └────┬───────────────────────┬───────────┘ │
│ │ │ │
│ 确认✅ 拒绝❌ │
│ │ │ │
│ ▼ ▼ │
│ ╔══════════════════╗ 留在只读阶段 │
│ ║ 执行阶段 ║ 继续修改计划 │
│ ║ (Build Phase) ║ │
│ ║ 所有工具解禁 ✅ ║ │
│ ║ ║ │
│ ║ ┌────────────┐ ║ │
│ ║ │ todo_write │ ║ ← 把计划拆成任务清单 │
│ ║ │ 拆分任务 │ ║ │
│ ║ └─────┬──────┘ ║ │
│ ║ │ ║ │
│ ║ ▼ ║ │
│ ║ 逐条执行任务 ║ │
│ ╚══════════════════╝ │
│ │
└──────────────────────────────────────────────────────────────────┘
模块关系与学习顺序
| 模块 | 与本篇的关联 |
|---|---|
| 上下文 | AgentState.getPlanModeContext() 持久化、(userId, sessionId) 寻址 |
| 文件系统 | plans/ 目录跟着文件系统模式走(本机、沙箱或远端 KV) |
| 权限系统 | plan_exit 复用 ASK HITL 流程;运行期可通过 setPermissionMode 切换权限模式 |
| 子 Agent | 子 Agent 的后台任务(task_*)不等于 todo_write,不要混淆 |
学习要点
必须记住
- 计划模式 = 两阶段分离:只读阶段(只能看和写计划)→ HITL 确认 → 执行阶段(全部解禁),不跳步
- 白名单不止计划三件套:9 个白名单工具(
plan_enter、plan_write、plan_exit、todo_write、agent_spawn、agent_send、agent_list、task_output、task_list)+ 所有只读工具,其余一律被拒 plan_write不是write_file:专门的安全写入入口,只能写计划文件,不会破坏其他文件- HITL 是硬门控:
plan_exit恒返回ASK,LLM 不能自己跳过确认,只有人类同意才能进入执行阶段 - 状态会持久化:计划模式状态存在
AgentState.getPlanModeContext()里,重启不丢失 allowShellInPlanMode()是可选的弱保证:放开 shell 便于调查,但保证比默认弱,建议配合沙箱setPermissionMode是逃生口:BYPASS全放开(慎用)、DONT_ASK把 ASK 降级 DENY(适合无人值守)
容易混淆
-
plan_writevswrite_file:plan_write:只写plans/PLAN.md,计划模式专用,安全可控write_file:可以写任意文件,计划模式下被禁用
-
计划模式 vs
todo_write:- 计划模式:阶段开关 + 计划文件 + HITL 门控("图纸 + 审批")
todo_write:执行阶段维护结构化任务清单("施工任务单",恰好一个in_progress)- 两者独立但经常配合使用
-
todo_writevs 子 Agent 后台任务:todo_write:Agent 自己的任务清单,全量替换模式- 子 Agent 任务(
task_output/task_list):委派给子 Agent 的异步任务 - 完全不同的概念,不要混淆
-
编程退出 vs 工具退出:
agent.exitPlanMode()(编程调用):不触发 HITL 确认plan_exit(LLM 调用工具):触发 HITL 确认
-
Plan Mode vs PermissionMode:
- Plan Mode:具体的阶段开关(只读 → 执行),由
PlanModeMiddleware拦截 PermissionMode:session 级别的权限评估模式(DEFAULT/EXPLORE/BYPASS/DONT_ASK),可在运行期切换- Plan Mode 没有复用
EXPLORE,因为后者构建时快照、无法动态切换
- Plan Mode:具体的阶段开关(只读 → 执行),由
-
四种终态:
isPlanModeActive() == false不等于"成功"——也可能是从未进入,或仍在起草但 session 未结束。需要结合是否调用过plan_enter/ 是否有PLAN.md一起判断
实践建议
- 复杂任务必开计划模式:涉及多文件修改、架构调整的任务,开启计划模式能有效降低出错概率
- 配合 todo_write 使用:在计划阶段写好 PLAN.md,执行阶段用 todo_write 拆成 5-8 条任务逐条推进
- 合理设置 planFileDirectory:如果多个 Agent 共用工作区,给每个 Agent 设置不同的计划目录避免冲突
- 调查型任务可考虑
allowShellInPlanMode():配合沙箱文件系统一起开,既保留调查灵活性又控制爆炸半径 - 无人值守用
DONT_ASK而非BYPASS:前者仍保留规则评估,后者全放开风险大 - 注意子 Agent 的限制:当前子 Agent 不会自动继承只读限制,需要手动过滤工具或单独开启计划模式
常见问题
Q:如果 Agent 在只读阶段被拒绝了很多次怎么办?
A:这是正常的。每次拒绝都会返回提示信息,Agent 看到后通常会调整策略,转而使用只读工具继续思考或完善计划。如果反复拒绝,说明 Agent 的工具选择策略需要优化。
Q:HITL 确认被拒绝后,Agent 会怎样?
A:Agent 会留在只读阶段,可以继续修改计划文件,再次调用 plan_exit 请求确认。流程可以循环多次,直到人类满意为止。
Q:计划模式状态下进程重启了,会怎样?
A:AgentState 会自动恢复,计划模式的状态(包括是否在只读阶段)也会一起恢复。Agent 会继续留在之前的阶段,不会丢失进度。
Q:子 Agent 在计划模式下有什么限制?
A:当前已知限制——通过 agent_spawn 启动的子 Agent 不会自动继承只读限制。如果需要子 Agent 也只读,需要在子 Agent 声明里过滤工具到只读集合,或者在子 Agent 的 builder 里也开启 enablePlanMode()。未来版本会自动传播这个限制。
Q:可以不开计划模式,只用 todo_write 吗?
A:可以。todo_write 是 core 提供的独立工具,不依赖计划模式。但建议对复杂任务两者配合使用——计划模式提供安全门控,todo_write 提供执行聚焦。
Q:我想让 Agent 在 plan 阶段用 git log / grep 调查代码,但默认 execute 被拒,怎么办?
A:两个办法。一是用专门的只读工具(read_file / grep_files / list_directory),它们本来就在白名单里;二是 .enablePlanMode().allowShellInPlanMode(),把 execute 加入放行名单(plan banner 会提示模型只用来做只读调查)。后者保证更弱,建议配合沙箱使用。
Q:一次 call 结束后 isPlanModeActive() 返回 false,能说明任务成功了吗?
A:不能。false 有两种合法情况——从未进入 plan mode,或进入并 plan_exit 成功。需要结合是否调用过 plan_enter / plan_write,以及 plans/PLAN.md 是否存在来综合判断(见"如何判断运行结果"四态表)。