11. Plan Mode

先谋后动,在写作场景的「选项 C」

前面四个板块,我们把单个 Agent「怎么把一件件事做好」讲完了:它能转(引擎)、能记事(记忆)、有人设和方法论(驾驭)、能安全地碰真实世界(交互)。这些其实都默认了一件事:「一个 Agent 从头干到尾」。可一碰上结构复杂的写作任务,这个默认可能就兜不住了,得有更高一层的组织。这一篇就从「先谋后动」切入,聊聊我为什么最后选了一条自己动手定制的路

👆你在这里
👆你在这里

🪧 一篇复杂长文,最好想清楚再动笔

我之前试过偷懒,让 Agent 直接写一篇涉及大量调研与查证的技术深度长文。它二话不说,吭哧吭哧从第一段就往下写。写到快一半,我发现坏了:内容开始跑题,好些地方还在重复。这时候想调整,等于推倒重来,token、时间全搭进去了

换成一个人写复杂文章,不会这么干。他会先列个大纲,把立意、结构、几个关键论点和论据调研方向先想清楚,甚至跟约稿的编辑对一遍,确认方向没问题,才动笔。这就是先谋后动

辅助写作的 Agent 也该这样。对结构复杂、方向容易错的长文,「先出个计划、确认了再动手」,比「一上来就写、错了再改」划算得多。这个「先谋后动」的机制,就是这一篇的主角:Plan Mode

🧭 基础概念:Plan Mode = 先探索、出计划、你确认,再动手

Plan Mode 做的事,是把一次任务切成两段

先计划,后执行
先计划,后执行

放到更大的坐标系里看,这其实是 Agent 设计里一个经典的分野:一种是「边想边做」,走一步看一步,适合试错成本低、能随时纠偏的任务;另一种是「先想全再做」,把探索和执行拆成两个阶段,中间插一道关卡。写代码里有类似的路子,写作也不例外。像复杂长文这种「一步走错、后面全得推倒重来」的任务,天然更适合后一种

两个状态之间卡着一道「你来确认」的关口,这正是 Human-in-the-loop 落脚的地方:把「方向对不对」这个最关键、也最容易出错的判断,提前到「动笔之前」,而不是等写完一大篇才发现南辕北辙

就好比你家要搞装修:先出设计图、让业主拍板,再进场施工,而不是边砌墙边改图纸。砌错一堵墙的返工成本,可比改一版图纸大多了。写作场景里那「一大篇跑偏的稿子」,形同一堵砌错的墙

但也不是所有写作任务,都值得如此大动干戈。一条朋友圈文案、一段几十字的小改动,根本没必要先出计划再确认,多这一道手续反而是负担。Plan Mode 值得上场的,是那些方向容易错、错了代价又大的长文

顺带说一句,计划态特别适合搭配我们前面提过的 AskUserQuestion(第 5 篇聊过的澄清工具)。Agent 探索完、要定计划之前,正是它该回过头问你「这篇你想要犀利点还是温和点」的时机,把关键问题对清楚,落地才能靠谱

🔩 Claude Code:一个专为「写代码」设计的 plan 模式

这套「先谋后动」的思路不是我凭空想出来的,Claude Code 里就有原生实现,处理复杂的 Vibe Coding 任务时我自己也常用

按官方的描述,Claude Code 的 plan 模式里,「Claude 读文件、跑命令去探索,写出一份计划,但不改你的源码」。它的落地细节也挺讲究:探索阶段结束,Claude 不是自己悄悄切换状态,而是调用一个叫 ExitPlanMode 的工具,专门发起「我想退出计划模式了,请你批准」这个请求,你在终端里按个键确认,它才真正动手改代码;确认之后,那份计划还会接力交给 TodoWrite,拆成一条条可勾选的任务,跟着执行进度一路打勾。Learn Claude Code 的源码解读系列第五篇讲了 TodoWrite 与计划流程的关系,对理解这套机制很有帮助

听起来跟我想要的「只读探索、不落笔」很接近了,对吧?但魔鬼就在运作细节里。它的「计划」,实际上是一份代码变更计划,会被写进一个专用文件(~/.claude/plans/ 下的一个 markdown);它的「确认」,是终端里一个针对 ExitPlanMode 的按键动作;它的「产物」,是退出计划模式后直接去执行代码变更。这一整套体验,从骨子里就是为「一个人守着终端、边看边点头」这个场景量身定做的。 可就是这里面深厚的「coding 基因」,也正是后面踩坑的根源

🛠 SDK:plan 只是一种 permission_mode

到了 SDK 这层,情况变了。SDK 本质上是把 Claude Code 那套能力「去 UI 化」,让开发者可以在自己的应用里编排,而不是绑定在一个终端窗口里。终端里那道「按键确认」的仪式感没有了,ExitPlanMode 这个专属工具也没有了。plan 在这里被压缩成一个纯粹的枚举值:权限模式(permission_mode)的一种。还记得第 9 篇那六种模式吗,default / acceptEdits / plan / auto / dontAsk / bypassPermissions,plan 就是其中之一

把权限模式切到 plan,agent 就进入那个「只读探索、写计划、不改源码」的状态。它是 session 级的配置(创建 client 时绑定),但也支持运行期切换(比如用 /plan 前缀发一条 prompt)

这里可以 callback 第 7 篇:当时讲过,「命令的尽头都是 Skill」。可 /plan 偏偏是个例外,它不是 Skill。原因就在这个身份上:/polish 那类命令,本质是方法论注入(触发一本写法说明书 SKILL.md);而 /plan 本质是运行模式的切换(把整个 agent 切进「只读探索」的状态)。一个是「给它一套写法」,一个是「给它换一种干活模式」,性质截然不同。所以在项目落地时,别的命令都收敛成了 Skill,唯独 /plan 是权限层面的事,单独走一路

从 Claude Code 到 SDK发生了「降维」:界面被拿掉了,只剩下语义最核心的那个开关。可降维之后,原本属于终端 UI 的「审批」「落地」这些体验,需要有人补回来。看到这,要给写作 Agent 做 Plan Mode,怎么补这个缺口,很自然会想到把 permission_mode 切到 plan,对吧?我一开始确实也是这么干的,后面却踩到了大坑

🧨 SmartWriter的三次演进:从「用现成的」到「偏不用」

Plan Mode 前前后后改了三版,这段弯路值得完整讲一遍

第一版(选项 A):靠严格限制工具清单,再在提示词里模拟「现在是计划阶段」。理论上能跑,但问题不少:限制了工具,Agent 探索时缺东少西,产出的计划质量没保障;写保护又全靠 System Prompt 口头约束,不是 SDK 硬性拦截,模型一旦不听话就没有兜底

第二版(选项 B):直接用 SDK 原生的 plan mode。在小规模验证(spike)时它一路通过,我一度以为稳了。结果一上真实的端到端测试,就翻了车。因为 SDK 的 plan mode 骨子里还是那套「写计划到专用文件、确认后执行代码变更」的 coding 基因,强行移植到写作场景,处处别扭

🔧 避坑 · 借一套「写代码基因」的机制来干写作,水土不服

选项 B(SDK 原生 plan mode)在实测里暴露了三个问题,全都源于那套藏在骨子里的 coding 基因:

  1. 语义冲突,agent 自己都懵了。 plan mode 满脑子是「代码任务」,可我让它干的是写作。最离谱的是,我扒 agent 的思考过程,看到它自己在那儿纠结,原话是:「the plan-mode workflow is designed for code tasks, but this is a writing assignment, so I need to adapt it thoughtfully」(这套 plan 流程是给代码任务设计的,但这是个写作任务,我得动脑筋适配一下)。它把宝贵的 token 和时间,花在了「怎么把一套 coding 机制掰弯来适配写作」上,本末倒置了
  2. 退出 plan 模式的副作用不可控。 少了终端里那个 ExitPlanMode 的按键确认,plan mode 下 agent 探索完会自行判断「退出」,这个退出可能顺手往 ~/.claude/plans/ 写个文件、污染用户的磁盘,也可能不写,全看它临场发挥。这种「行为不可控」,对一个要分发给真实用户的产品,是不能接受的
  3. 那个 plan 文件根本指错了地方。 它默认要把计划写到一个 coding 场景的「代码变更计划」文件里,跟我要的「写作计划」八竿子打不着。agent 又一次在思考里纠结「running into constraint conflict」(撞上约束冲突了)

这个坑的根源是,借了一套「为写代码而生」的现成机制,来干写作的活,落地时处处水土不服。省事的想法,最后一点都不省事

此路不通,那就只能自己动手。说到底我要的东西并不复杂:在计划阶段,别让它碰我的作品就好

✂️ 选项 C:不动 SDK 的 mode,自己在应用层拦一道

经过以上反思,才演进到了选项 C,它的核心思路是根本不切 permission_mode,计划阶段全程保持 acceptEdits(就是平时写作那个模式)。至于「不许写作品」这道防护,我自己在应用层拦截

具体怎么做?还记得第 9 篇那个 canUseTool 审批回调吗(为高频联网、发布这些审批建的),正好可以复用:

  1. 给任务加一个「现在是不是计划阶段」的标志(in_plan_phase
  2. 计划阶段开始,把它置为「是」
  3. 那个 canUseTool 回调里加一句判断:只要「现在是计划阶段」且「模型想调的是 Write 或 Edit」,就直接拒绝(快速 deny,不弹卡、不等待)
  4. agent 碰了一鼻子灰,就自动降级成只读探索,ReadGlobGrep、还有读画像的 profile_reader,这些照样能用

这样,没有借用 SDK 那套 coding plan mode 的任何语义,只是复用了项目已有的一个回调,加了一句「计划阶段不许写」的判断。它跟选项 B 的差别,列个表能看得更清楚:

维度 选项 B(切 SDK plan mode) 选项 C(acceptEdits + 应用层拦截)
防护强度 强(SDK 物理层禁止) 等效(一调 Write/Edit 就立即拒)
语义冲突 有(plan mode 是 coding 基因) 无(压根不触发它)
退出副作用 有(可能乱写 plan 文件)
切换 mode 次数 2 次 0 次(不切,缓存友好)
可测 / 可控 黑盒,难单测 应用层代码,可单测、deny 理由可定制

那计划本身该怎么呈现?它是要给你「确认或修改」的,所以得是结构化的,而不是一段自由文本,这正好又落回开篇关于「两条通路」的约定:面向你阅读的走自由文本,供程序消费(前端渲染成可确认的卡片)的走结构化。我让计划以一个规整的结构产出,包含目标、大纲、步骤、风险几块,前端拿到就能渲染成一张「计划卡」给用户过目。点了确认,它就落成一份 todo list,驱动 agent 进入执行态,一步步写下去。整个过程,用户只感知到「计划 / 执行」两个状态的切换,至于底下 permission_mode 叫什么、切没切,他完全不用知道,也不该知道

机制设计上的巧思:计划态和执行态,其实是同一个 client 一路跑下来的,靠那个 in_plan_phase 标志翻来翻去,计划时置「是」(拦住写),出完计划、进入执行就置「否」(放开写)。不用为了切阶段而重启和传递一套上下文,探索时读到的素材、画像,执行时都还在,做到无缝切换

「落成一份 todo list」,我一开始想得太简单了。初版设计是,后端照着计划里的步骤数先预填几条占位待办,等 agent 执行时再对号更新。结果一测就碰壁:待办列表不是一次性铺满,而是从第一条开始逐条往外蹦;不少条目内容还是空白的;进度条持续卡在第一格不动

来回改了几版 id 映射逻辑,一次比一次绕,现象却纹丝不动。后来翻 agent 的真实执行记录才搞明白:待办其实是 SDK 自带的机制,agent 每调用一次都会返回一句确认。解析代码想当然地以为这句确认是个 JSON 结构,就照着这个假设来写,可翻记录一看,CLI 实际吐出来的是一句大白话(类似「Task #3 created successfully」),根本不是 JSON。解析函数每次都在这里失败、返回空,待办自然就对不上号

🔧 避坑 · 待办列表的坑,根子在「没查真实数据就下笔」

这个坑前后折腾了四轮方案才真正定位到根因,每一轮都在「调整 id 匹配逻辑」这个症状层面打转,却没有回头去验证 SDK 到底返回的是什么格式:官方文档里的示意结构,和 CLI 实际吐出来的文本,根本是两码事。测试用例也一直是绿的,因为写测试时用的是自己臆造的 JSON,验证的是一个不存在的东西

根因找到后,解法反而不难:改用正则从那句大白话里把任务 id 抠出来,JSON 解析降级成兜底路径。这里最大的教训是,遇到「SDK 到底应该返回什么」这种细节,别信文档的示意图,去翻一份真实的执行记录,那才是唯一的 ground truth

这坑填完,其它执行起来就顺了:agent 建的待办才是唯一真相源,后端老实做透传,不用去猜它会建几条,也不用抢跑预填占位。计划里写的是几步,agent 执行时未必原样照搬,允许它按实际情况合并或拆分

📐 深挖 · 流式模式里,怎么把「计划」稳稳变成一份 JSON

计划要渲染成一张可确认的卡片,就得是结构化的(目标 / 大纲 / 步骤 / 风险各就各位),这属于「两条通路」里「供程序消费」那一条。可这里撞上一个流式模式的坑:前台写作走的流式通道,压根没有「让你指定输出格式」那个开关(那是后台单发查询才有的能力)。可计划又必须跟探索共用同一个会话,不然探索时读到的画像、素材,等出计划时就不在了,总不能为了拿个 JSON 另起一摊、把上下文丢了吧?

我的解法是个两步的混合策略,全程在同一个会话里完成:第一步,只读探索(in_plan_phase=True,拦住写);第二步,把这个标志翻成 False,紧接着追一句「现在请把你的计划用 JSON 吐出来」,再从它的流式文本里把那段 JSON 抠出来解析

而「从流式文本里抠 JSON」这一步也不敢裸奔,因为底层 CLI 偶尔会抽风,把整个 JSON 包进一个奇怪的壳里(这个 bug 留到第 12 篇细说)。所以这段解析,复用了一套全局统一的「解包 + 校验 + 兜底」防御逻辑

⚖️ 垂直落点:有时候,用好一个机制的方式是「不去动它」

回到贯穿全专栏的对照:

⚖️ 取舍现场 · 做 Plan Mode,通用和垂直的两条路

通用 Agent SmartWriter 为什么
怎么实现「只读探索」 直接切 SDK 原生 plan mode 全程 acceptEdits + 应用层拦 Write/Edit 原生 plan mode 带 coding 基因,硬套水土不服
借力对象 借 SDK 的专用机制 复用自己已有的 canUseTool 回调 复用比新借更可控、可测、缓存友好
用户感知 可能露出 mode 名称 只感知「计划 / 执行」两态 内部术语不外露(呼应第 7 篇 Flow Design)

上一篇讲 Hooks,我说「用好这套机制的方式,是从一排插槽里挑几个对写作最有用的」。这一篇讲 Plan Mode,结论又不太一样:用好它的方式,是根本不去切它那个原生 mode。 看着有点矛盾,这也是做垂直产品时常有的权衡与取舍:一个机制看着现成、好用,但如果它的「基因」跟你的场景不合,硬套的成本,可能会远高于自己用手头更贴合的零件搭一个。大白话就是,要具体场景具体分析,能抓住老鼠的才是好猫

好,Plan Mode 就讲到这。它解决的是「主 Agent 自己先把这一篇想清楚」的问题,说到底还是一个 agent 在谋划。可有些活性质完全不同,比如「翻几十篇资料做一轮调研」「对定稿全文做一遍终检」,这些又重又独立、还容易把主写作的上下文搅乱的任务,与其让主 agent 自己憋着干,不如派个分身,去一个隔离的房间里干完,只把结论递回来。这就是下一篇的主角:Subagent,咱们接着聊