Claude Code核心拆解:计划模式------怎么让模型"先对齐再动手"
这是「Claude Code 核心拆解」系列文章之一。本篇拆计划模式(Plan Mode):Agent 世界里最贵的浪费不是写错代码,是写的不是你想要的------计划模式把"对齐"变成一次硬性关卡。文中所有
file:line锚点均经源码实读核验。
从一个你天天会遇到的需求说起
假设你在 Claude Code 里说了一句:
"帮我给这个项目加上用户登录功能,用 JWT 认证。"
模型收到这句话,干了几件事:它先扫了一遍项目结构(发现是 Spring Boot 项目、已有 User 表、没有安全框架),然后判断:这活不小------要动 5 个文件以上,涉及技术选型(jjwt 还是 spring-security?token 存 Redis 还是无状态?),需求里还有一堆没说清的地方(token 有效期?刷新机制?)。
于是模型没有直接开干,而是调用了一个工具:EnterPlanMode。从这一刻起,事情变得有意思了------模型主动把自己的手绑了起来:它进入了一个"只读"状态,接下来它只做两件事:看代码、写一份计划。写完计划,它停下来等你审批。你批了,它才恢复动手能力,按计划执行。
这就是计划模式的一生。听起来简单,但把这个"简单"做成"可靠",Claude Code 动用了权限系统、文件系统白名单、提示词注入节流、断路器检查四套机制。本篇逐个拆。
一句话思想
Agent 世界最贵的浪费不是写错代码,是写的不是你想要的。计划模式把"对齐"变成一次硬性关卡:方案获批之前,模型连一个文件都改不了。
这个思想是怎么来的
第 03 章的权限系统,粒度太细了
权限系统是每次动手问一次 ------你点了几百次"允许",批准的是"这一刀能不能下"。但人真正在意的往往不是每一刀,而是整个手术方案对不对。每次动手都问,打断 100 次,问的都是细节;方案一次对齐,打断 1 次,问的是全局。
这背后的管理思想很古老:Checkpoint(检查点) 。工程项目开工前要图纸会审,医生开刀前要手术方案确认------共同点是把"确认"从过程粒度 (每刀都问)提到方案粒度(一次问清全部)。打断次数从 N 降到 1,但单次拦截的赌注变大。
为什么对 Agent 尤其值钱
第 01 章埋过伏笔:Agent 每一步都是 LLM 临场决定的 ,你无法预审它的执行序列。但"方案"是它自己的产出物------你审它的方案,等于提前审了它的全部执行意图。这是传统软件做不到的:传统程序的行为是程序员写死的,上线前审一次代码就够;Agent 的行为是临场生成的,审它的"计划"就是审它的"代码"。
业界把这个套路叫 Plan-and-Execute (先规划后执行),和 ReAct 的"走一步看一步"是两个流派的调和:ReAct 灵活但容易在长任务里迷路(每步只看眼前),纯 Plan 刻板但可控。Claude Code 的选择是按需切换------模型自己判断任务复杂度,简单任务直接干,复杂任务先进计划模式。
核心设计题:怎么保证"计划阶段模型真的不动手"?
现在你要实现计划模式了。关键难题:模型在计划阶段必须只读、只写一份计划文件------这条规矩怎么落实?
三个候选,先自己想再往下看:

方案 A:在系统提示词里写"计划阶段请勿修改任何文件"。 这是大多数人会犯的错。提示词是软约束 ------模型在长任务里会漂移,第 10 轮忘掉第 1 轮的叮嘱是常态(你自己读长文档也会忘了开头的约束)。约束不存在于模型心里,就等于不存在。
方案 B:在前端 UI 上隐藏写文件的按钮。 更天真------UI 是给人看的,模型调用工具根本不经过 UI。你藏按钮只能防住你自己,防不住模型。
方案 C:切换权限模式,让写操作在权限层被硬性拒绝。 这是 Claude Code 的答案。它的精妙在于复用 :第 03 章已经建好了一套完整的权限管道,计划模式不需要新造任何闸门,只需要把权限模式切到 plan,写文件自然被现有判序拦下。往下看它怎么落地。
源码解析:三幕剧
第 1 幕:进入计划模式 = 修改权限状态,不是发个提醒
模型自己决定调用 EnterPlanMode 工具。这个工具的 call() 干的事是真的改全局权限模式 (EnterPlanModeTool.ts:88-94):
ts
// src/tools/EnterPlanModeTool/EnterPlanModeTool.ts:88-94
context.setAppState(prev => ({
...prev,
toolPermissionContext: applyPermissionUpdate(
prepareContextForPlanMode(prev.toolPermissionContext),
{ type: 'setMode', mode: 'plan', destination: 'session' },
),
}))
注意:这不是"发一条提醒",是 setAppState------真的把全局权限上下文切到了 plan 模式 。模式切到 plan 后,写文件在权限层被掐死(第 03 章的规则闸门全部生效),白名单只放行计划文件和草稿区。
一个体现设计者思路的细节:远程模式下审批弹窗不可用时,连 EnterPlanMode 也一并禁用 ------源码注释原话:"plan mode isn't a trap the model can enter but never leave"(不能是个进得去出不来的陷阱)。如果进了计划模式却没人能批准退出,模型就被困死在里面了。设计一个模式时,要先把它的逃生通道设计好------这是很多系统翻车的地方。
硬约束怎么落地:写权限判序里的"白名单 + ask 兜底"
切了模式只是第一步,真正拦下写操作的是第 03 章那条判序在 plan 模式下的行为。看 filesystem.ts 判序的结尾(filesystem.ts:1395-1411):
ts
// src/utils/permissions/filesystem.ts:1395-1411
// 5. Default to asking for permission
return {
behavior: 'ask',
message: `Claude requested permissions to write to ${path}, but you haven't granted it yet.`,
suggestions: generateSuggestions(
path,
'write',
toolPermissionContext,
pathsToCheck,
),
// ...
}
有个反直觉的事实值得钉死:整条写权限判序里没有 plan 模式专属的 deny 分支 。判序是:显式 deny 规则 → deny;内部路径白名单(本会话计划文件、草稿区,含路径穿越防御)→ allow;各类安全检查与 ask/allow 规则;最后兜底 behavior: 'ask'------弹权限对话框把决定权交给你。
所以 plan 模式的准确语义不是"写操作被禁止",而是**"没有你点头,一次文件都写不进"**。为什么选 ask 而不是 deny?因为计划文件本身就要用 Write/Edit 工具写入------封死工具会误伤白名单需求;而 ask 保留了人的介入通道:你想批准某次写入,点一下就行,模式不用退出。人是裁决者,harness 只是执法者。
软层怎么防漂移:每 5 轮重钉一次
硬约束解决了"写不进",但还有个软问题:模型怎么持续记得自己在计划模式、应该专注探索而不是老撞墙?
答案是周期性重锚 (Context Re-anchoring 在模式层的应用):attachments.ts 里的注入节流配置:
ts
// src/utils/attachments.ts:259-262
export const PLAN_MODE_ATTACHMENT_CONFIG = {
TURNS_BETWEEN_ATTACHMENTS: 5, // 每 5 个用户轮次注入一次
FULL_REMINDER_EVERY_N_ATTACHMENTS: 5, // 每 5 次注入里第 1 次发完整版
} as const
- 每 5 轮注入一次提醒:完整版(含完整禁令与工作流)与一句话精简版交替;
- 压缩边界无条件强制重发完整版------压缩会摘要掉旧提醒,如果不在压缩后立刻重钉,模型就"忘了"自己在计划模式;
- 进入动作本身在 tool_result 里内嵌完整行为指令 (
EnterPlanModeTool.ts:96-101):模式切换瞬间,指令就贴着上下文出现,模型不用回忆系统提示词。

两层合起来的完整语义:软层让模型"不想写"(提示词重锚),硬层保证"写不进"(权限闸门 + ask 兜底)。软层失效的代价只是多一轮撞墙;硬层失效才会真出事故------所以硬层独立于软层,不赌模型自觉。
第 2 幕:计划写成文件,不是回复在对话里
这是本章最值得咀嚼的决定。计划文件路径由系统注入(模型连路径都不用传),模型把方案写进去。为什么必须是文件?

回到 JWT 登录的例子:模型花了几轮调研,写出计划:"用 spring-security + jjwt,token 无状态,有效期 2 小时,改动 6 个文件......"。如果这份计划只是对话里的一段文本:
- 压缩风险:任务执行到第 20 轮时,早期对话会被压缩摘要掉------模型可能已经忘了"用户同意的是哪个版本";文件在磁盘上,永不丢失、随时可 Read;
- 不可编辑 :对话里的计划你只能"同意/不同意",想改一个技术选型(比如"token 别用 Redis,无状态就行")只能口头描述让模型重新输出,来回多轮;文件可以直接改、改完批准,一次交互完成 (
ExitPlanModeV2Tool.ts:258-260:Web UI 送来的编辑版计划会被写回磁盘); - 不可追溯 :计划是可 diff、可引用的工作产物,而不是一段转瞬即逝的对话。
第 3 幕:退出计划模式 = 一次权限审批
ExitPlanMode 的 checkPermissions 天然返回 ask(ExitPlanModeV2Tool.ts:221-239):
ts
// src/tools/ExitPlanModeTool/ExitPlanModeV2Tool.ts:221-239
async checkPermissions(input, context) {
// For ALL teammates, bypass the permission UI to avoid sending
// permission_request; call() handles leader-approval instead
if (isTeammate()) {
return { behavior: 'allow' as const, updatedInput: input }
}
// For non-teammates, require user confirmation to exit plan mode
return {
behavior: 'ask' as const,
message: 'Exit plan mode?',
updatedInput: input,
}
}
"方案审批"被实现成了一次权限确认------复用了第 03 章的权限管道,没有新造任何审批机制。这是计划模式设计的第二条命根子:Checkpoint 不是一个新系统,是权限系统的一次用法升级。
批准后恢复的是什么模式?进计划模式之前的模式 (prePlanMode),不是硬编码 default------不能进一趟计划模式把用户原来的配置搞丢了。
还有一个决定成败的细节:退出时的断路器检查 (ExitPlanModeV2Tool.ts:320-350):
ts
// src/tools/ExitPlanModeTool/ExitPlanModeV2Tool.ts:322-327
// Circuit breaker defense: if prePlanMode was an auto-like mode but the
// gate is now off (circuit breaker or settings disable), restore to
// 'default' instead. Without this, ExitPlanMode would bypass the circuit
// breaker by calling setAutoModeActive(true) directly.
一句话:如果进计划模式前你在用"自动审批"实验模式,而计划期间它的断路器熔断了(分类器连续出错被暂停),退出时就降级恢复到 default------否则退出动作本身就成了绕过熔断的通道。第 03 章"谁都不能单独放宽"的公理,在计划模式的出口又出现了一次。
三幕剧回到 JWT 例子
把例子走完:模型调研完写了计划文件 → 你打开计划,把"token 存 Redis"改成"无状态 JWT"、把有效期改成 1 小时 → 批准 → 弹窗"Exit plan mode?"你点同意 → 权限模式恢复到进计划前的 default → 模型开始按计划改 6 个文件。
注意你全程只被打断了一次,但你审的是全部执行意图 ;你改的那两个字(Redis → 无状态)省下的是执行完发现架构不对再返工的 6 个文件重写。审批时改一个词,比执行完返工四个文件便宜百倍------这就是把 Checkpoint 提前到最便宜时刻的全部经济学。

四个坑的完整版(每个都是真实翻车现场)
坑 1:叮嘱了不遵守(软约束漂移)。 "请先说方案"写在 prompt 里,模型说到第 8 轮自己开始改文件了。对策就是第 1 幕的模式切换------约束从模型心里搬到权限系统里。这是本章与子代理章共用的公理:能用电闸解决的,不要贴告示。
坑 2:计划夹带"研究成果",用户看不懂。 早期计划容易写成调研笔记而不是施工方案------通篇是"我发现 A 用了 B 模式"的研究结论,没有一句"我准备怎么做"。对策:ExitPlanMode 的 prompt 里有明确的负向清单------研究型任务不许退出计划模式 (没有"实施步骤"可批的东西不该走审批),并禁止用"问问题"工具绕行("这个计划行吗"必须用 ExitPlanMode 问,那是它的专职)。审批通道只放行"可执行的承诺",不放行"有趣的发现"。
坑 3:退出计划模式本身被用来绕过安全限制。 就是上面断路器检查防的场景。它防的不是模型(模型没有权限改断路器状态),防的是流程自身的漏洞 :正常流程"进 → 出"本身携带一次模式放宽,如果放宽的终点已经坏了(自动审批熔断),这次放宽就是一次无意的越权。设计任何"恢复状态"的逻辑时都要问一句:恢复的目标状态还合法吗?
坑 4:多 Agent 场景没人可弹窗。 子代理也有计划,弹窗弹给谁?对策:子代理退出计划模式不走人审批,写 plan_approval_request 进领导代理的邮箱 (ExitPlanModeV2Tool.ts:264-295)------"人"被替换成"领导",checkpoint 机制原封不动。这是多 Agent 协作章的预告:权限原语被复用为代理间协议。
核心思想
计划模式 = 把权限检查点的粒度从"每刀"提到"整场手术"。它的可靠性不来自模型的自觉(软约束会漂移),而来自权限系统的强制(硬约束不漂移);它的价值不来自流程本身,而来自把"方案错误"拦截在最便宜的时刻。
三幕剧里最值得带走的三个工程判断:
- Checkpoint 不是新系统,是权限系统的一次用法升级------退出就是一次 ask 弹窗,复用第 03 章管道,零新机制;
- 约束的双层结构------软层重锚(每 5 轮 + 压缩边界)管"不想写",硬层闸门管"写不进",硬层从不依赖软层生效;
- 恢复状态要检查目标合法性------退出恢复 prePlanMode 但先查断路器;"回到之前"不是无条件的,"之前"可能已经坏了。
下一篇
本篇反复出现的"软约束 vs 硬约束"公理,在子代理章会迎来它的第三位主角:fork 子代理的缓存魔法------继承父对话全部上下文却不付全价,靠的是把"必然不同的部分"压到历史最后一块。那篇拆的是上下文经济学最精妙的一招。
本文基于 claude-code-main 源码逐行走读,文中所有 file:line 锚点均经实读核验。