深度讲解 Claude Code 的 PlanMode 如何改变 AI 协作流程,涉及读写分离、工作流切换等设计。这是优化 AI 辅助编程交互的关键理解。
这是我的 Claude Code Tools Deep Dive 系列的第二篇文章。上一篇深入探讨了 AskUserQuestion,这是一个让用户从具体选项中选择的结构化工具。这一次,我们看它的"兄弟"工具:EnterPlanMode。
在阅读本文之前,阅读该系列的序言可能会有所帮助,序言介绍了 Claude Code 工具机制的运作原理。本文也遵循那里引入的相同四层框架。
与 AskUserQuestion 一样,EnterPlanMode 是你几乎每天都可能遇到的工具。但它的设计要重得多。它不仅仅是提出一个问题;它把 Claude 切换到了完全不同的操作模式。
EnterPlanMode 是 Claude Code 进入规划模式的内置入口。它的工作简单却强有力:把 Claude 从默认的"边思考边编写"工作流转换到围绕只读探索和解决方案设计构建的规划工作流。只有在用户显式批准该计划后,Claude 才会回到实现阶段。
它解决的核心问题是 AI 与用户之间的对齐:
防止工作朝错误方向漂移 —— Claude 在修改任何文件之前就调整好方法。
强制执行只读探索 —— 一旦规划模式激活,Edit、Write 和 NotebookEdit 就会被禁用。Claude 无法在探索时悄悄做出改动。
创建明确的决策边界 —— 用户可以批准、拒绝或要求修改一份完整的提议,而不是在看 pull request 后才发现走错了方向。
生成可追踪的规划工件 —— 规划模式创建一份书面计划,可以被引用和修订,而不是一段在聊天历史中消失的段落。
场景:用户告诉 Claude,"重构这个身份验证模块,用会话 cookie 替换 JWT。"
这个请求听起来很清晰,但实际上涉及登录路由、令牌生成中间件、前端存储、会话过期策略、数据库架构决策以及对现有 API 消费者的向后兼容性。这是一个多文件、多决策、依赖重的改动。
没有规划边界,Claude 必须从现有上下文推断一个方法并立即开始编辑:
用户看着 diff 说:"我只想要 web 应用的会话。后端服务仍然需要 JWT。你为什么替换了整个 API 的身份验证?"
有几个地方出错了:
方向错误在第五步才浮现 —— 四个文件已经改变,所以回滚很痛苦。
决策边界不清楚 —— "替换所有身份验证还是仅仅 web 流?"这是一个关键分岔,但 Claude 是在没有询问的情况下猜测的。
用户从不见过全貌 —— 他们收到一堆 diff,必须逆向工程出预期的设计。
重要的附加影响从未浮现 —— 系统应该创建一张会话表吗?会话状态应该保存在内存、Redis 还是数据库?Claude 可能考虑了这些问题,但从未将其转变为明确的提议。
回滚很昂贵 —— 每次编辑都消耗令牌和关注力;丢弃实现会浪费两者。
核心问题:"边思考边编写"让 Claude 在方法还不稳定时就生成 diff,而用户直到最后才能看到完整的设计。
Claude 首先声明它想进入规划模式并寻求用户的批准。该转换本身就是一个交互式确认。如果用户拒绝,Claude 保持在默认模式。
Claude 的工具集变得更狭窄:
✅ 可用:Read、Glob、Grep、Agent、AskUserQuestion 和 ExitPlanMode
❌ 禁用:Edit、Write 和 NotebookEdit
Claude 无法修改项目文件。每一个探索行为都是只读的。
澄清关键问题:
这正是上一篇文章讨论的澄清模式。AskUserQuestion 和 EnterPlanMode 是天然的伙伴。
Claude 将完整的提议写入一份计划文件:范围、受影响的文件、迁移步骤、风险和回滚策略。这是一份可修改、可引用的工件——而不是一个短暂的聊天消息。
用户看到完整的计划并选择接下来会发生什么:
✅ 批准 → Claude 返回到实现模式并执行该计划。
✏️ 要求更改 → Claude 根据反馈修改计划。
❌ 拒绝 → Claude 改变方向。
批准前不会修改任何项目文件。用户的令牌、时间和关注力不会被花在实现错误方法上。
该工具的官方描述包含了一条有趣的规则:非平凡的实现任务应默认规划。这是一个故意保守的偏好。
实现新功能 —— 即使小功能也隐藏着决策:代码应该存放在哪里,按钮应该做什么,应该如何处理错误?
有多个合理的方法 —— "添加缓存"可能意味着 Redis、内存或文件;"实时更新"可能意味着 WebSocket、SSE 或轮询。选择本身就是设计工作。
改变现有行为 —— "更新登录流"是模糊的。在触碰代码之前定义改动。
做出架构决策 —— 模式、依赖和数据流方向应该被同意。
改动跨越两个或三个以上文件 —— 影响足够大,diff 不再能传达整个设计。
需求不清楚 —— "让应用更快"需要先做性能分析和讨论优化优先级。
实现受到用户偏好影响 —— 如果你需要 AskUserQuestion 来澄清方法,你可能需要 EnterPlanMode 来开发它。
一行代码的修复 —— 修正一个拼写错误或明显的差一错误。
添加一个明确指定的函数 —— 直接实现它;没有必要兴师动众。
用户已提供精确、详细的指示 —— 用户已经做好规划,所以重复这样做只会增加摩擦。
纯研究或探索 —— 当没有实现跟随时,使用 Agent 工具配合 explore agent。
原始描述中有一句特别有启发:"在规划的一侧出错。"如果不确定,先规划。这个默认设置本身暴露了设计者的偏好:倾向于对齐而不是速度。
AskUserQuestion 在所有四个设计层中传递信号。EnterPlanMode 的分布非常不同:名称执行了 schema 可能需要做的工作。
Enter 是一个动词,暗示进入一种状态——而不是获取数据或执行一次性操作。
PlanMode 命名该状态,并与 ExitPlanMode 形成自然对。
考虑一个反事实的设计:SetMode(mode: "plan")。该模型可能会将其解释为设置属性并随意切换模式。当前的名称编码了一个仪式化的状态转换:进入是显式的,退出也是显式的。它的语义比参数化的 SetMode 强得多。
这就是为什么 schema 可以是空的。名称已经锁定了意义,所以 schema 不需要去拯救它。
EnterPlanMode 的描述围绕四个问题展开:何时使用、何时不使用、如何与相邻工具分工,以及运行时会发生什么。
对于实现任务倾向于使用 EnterPlanMode,除非它们很简单。
一句话重塑了 Claude 的行为。如果不确定,规划而不是立即行动。该工具通过将默认设置向谨慎倾斜来启动。
"何时使用此工具"部分列出了七个编号的情况,每个都带有具体的信号。一个代表性的例子是:
多文件改动:该任务可能会接触超过 2-3 个文件
这提供了一个量化的阈值,而不是要求 Claude 相信一个主观的感受。直觉变成了一条操作规则,减少了关于是否需要规划模式的不一致。
如果你会使用 AskUserQuestion 来澄清方法,就改用 EnterPlanMode
这把一个模糊的边界变成了直接的规则:AskUserQuestion 处理孤立的澄清,而方法级的分岔则需要规划模式。它防止了反复提出断开的问题并尝试将答案汇总成计划的反模式。
纯研究/探索任务(改用 Agent 工具配合 explore agent)
这定义了另一个边界:不要为不会导致实现的研究使用 EnterPlanMode。规划模式存在于实现前规划。如果实现不是目标,进入它就是浪费动作;将调查委托给 explore agent。
此工具需要用户批准 - 他们必须同意进入规划模式
AI 不能单方面改变工作流。用户守护着这个转换。这也解释了为什么该工具不接受任何参数:调用本身是一个请求,而不是参数化操作的执行。
如果不确定是否使用它,倾向于规划 - 事先获得对齐比重做工作更便宜
这是描述的价值陈述:一轮对齐比错误的实现和回滚更便宜。同样的哲学出现在 AskUserQuestion 中。Claude Code 的工具生态系统一贯青睐对齐。
用户欣赏在对其代码库进行重大改动之前被征询意见
这句话训练了 Claude 的社交直觉。规划不仅仅是一个效率机制;它尊重用户对代码库的所有权。这种框架鼓励 Claude 将咨询视为良好协作,而不是中断。
EnterPlanMode 没有输入字段。它的 schema 是空对象 {},所以这一层不存在。每个行为信号都上升到工具级描述。
input_schema 是空的:没有字段、没有类型、没有约束。调用工具本身就是改变状态的意图;没有数据要传递。
这种缺失本身就是一个设计信号:权限在工具和运行时层强制,而不是在参数层。Claude 不需要请求个别权限或指定目标模式。Claude 调用 EnterPlanMode 后,运行时自动:
与在答案到达后结束的 AskUserQuestion 不同,规划模式是一个持久状态。
AskUserQuestion —— 澄清一个决策:"A 还是 B?"
EnterPlanMode —— 开发完整的提议;AskUserQuestion 在规划时保持可用。
ExitPlanMode —— 提交提议以供用户批准。
三个工具共同形成了一个完整的决策管道:
遇到一个不清楚的分岔
↓
AskUserQuestion: 澄清 A vs. B
↓
EnterPlanMode: 进入规划模式
├─ 用 Grep / Read / Glob / Agent 探索
├─ 根据需要用 AskUserQuestion 澄清子决策
└─ 写计划文件
↓
ExitPlanMode: 提交计划
├─ 用户批准 → 返回默认模式并实现
├─ 用户要求更改 → 在规划模式中修改
└─ 用户拒绝 → 停止或改变方向
如 AskUserQuestion 文章所讨论的,Claude 不应该在规划模式内使用 AskUserQuestion 来回答元问题"这个计划可以吗?"原因是时间:用户在 ExitPlanMode 提出批准前看不到计划。问一个看不见的计划是否可以接受是没有意义的。
EnterPlanMode 的优雅不仅来自于"让 AI 在行动前思考"。它来自于设计信号的极其不均衡的分布:
这揭示了一个更深层的原则:空 schema 本身就是一个设计选择。当工具的含义是"转换到一种状态"时,参数化它会削弱意义。SetMode 邀请随意切换。一个零参数的 EnterPlanMode 是一个慎重的、仪式化的请求。
下一篇文章将检查 ExitPlanMode,这个三工具决策管道的最后阶段,并解析"提交计划以供批准"是如何设计的。