Kimi Code CLI 计划模式(Plan Mode)全解析:从 EnterPlanMode 到 ExitPlanMode 的规划与审批工作流
【免费下载链接】kimi-cliKimi Code CLI is your next CLI agent.项目地址: https://gitcode.com/GitHub_Trending/ki/kimi-cli
导读
Kimi Code CLI 内置了一套"先规划、后执行"的计划模式(Plan Mode),让 Agent 在动手改代码前先产出可评审的实现方案,并通过ExitPlanMode工具将方案提交给用户审批。本文以 ExitPlanMode 工具描述文档 为核心,结合 EnterPlanMode 实现、ExitPlanMode 实现(注:实际为__init__.py,见下文)与 plan_mode 动态注入 等源码,完整讲解计划模式的工作机制、options多方案参数、Yolo/Afk 模式下的自动审批行为,以及 AskUserQuestion 与计划审批的边界。读完本文,你将掌握如何在 Kimi Code CLI 的 Agent 会话中正确驱动一次"探索 → 设计 → 写方案 → 审批 → 执行"的完整规划闭环。
ExitPlanMode:计划审批的入口工具
ExitPlanMode是计划模式的核心收尾工具,其官方描述位于 src/kimi_cli/tools/plan/description.md,定义如下:
Use this tool when you are in plan mode and have finished writing your plan to the plan file and are ready for user approval.
也就是说:只有当你处于计划模式、且已经把完整方案写入 plan 文件、准备请求用户批准时,才调用该工具。它的行为在 src/kimi_cli/tools/plan/init.py 中实现,工具名常量NAME = "ExitPlanMode",描述直接由load_desc(Path(__file__).parent / "description.md")从该 Markdown 文件加载(第 28-31 行 为 EnterPlanMode 的对应加载方式,ExitPlanMode 同理)。
工作原理:方案从文件读取,而非参数传入
文档明确强调了一条关键设计:
- 该工具不接收计划内容作为参数——它从你写入的 plan 文件中读取方案;
- 用户在评审时,看到的就是你 plan 文件中的完整内容。
源码印证了这一设计:ExitPlanMode.__call__首先通过plan_path = self._plan_file_path_getter()拿到当前会话的 plan 文件路径,然后读取其文本内容(src/kimi_cli/tools/plan/init.py#L121-L132):
plan_path = self._plan_file_path_getter() plan_content: str | None = None if plan_path and await asyncio.to_thread(plan_path.exists): plan_content = await asyncio.to_thread(plan_path.read_text, encoding="utf-8") if not plan_content: return ToolError( message=f"No plan file found. Write your plan to {plan_path} first, " "then call ExitPlanMode.", brief="No plan file", )如果 plan 文件不存在或为空,工具会直接返回错误:"没有找到 plan 文件,请先把方案写入{plan_path},再调用 ExitPlanMode"。这意味着在调用 ExitPlanMode 之前,Agent 必须已经用 WriteFile 或 StrReplaceFile 创建并写好了 plan 文件。
使用时机:只用于实现类任务
文档对"何时使用"给出了明确限定:
- 应当使用:需要规划实现步骤的任务(如新功能开发、多文件改动、架构决策);
- 绝不使用:纯研究类任务(搜索文件、阅读代码、理解代码库),这类任务不应调用 ExitPlanMode。
这与 src/kimi_cli/agents/default/plan.yaml 中 plan 子代理(subagent)的定位完全一致:plan 子代理被描述为"Read-only implementation planning and architecture design",用于在改动代码前产出分步实现计划、关键文件识别与架构权衡分析。
options 参数:向用户呈现多个候选方案
当你的计划中包含多种可选的实现路径时,文档要求通过options参数把它们传给 ExitPlanMode,让用户在审批时直接选择执行哪一条:
- 每个 option 需要有一个简洁的标签(label)和简短的权衡说明(description);
- 如果你推荐其中某个方案,在其标签后追加
"(Recommended)"; - 用户会同时看到所有 option,以及系统自动追加的 "Reject" 和 "Revise" 选项;
- 最多提供 2-3 个方案(系统会自动追加一个 "Reject" 选项,所以用户最终看到 3-4 个选项);
- 严禁使用 "Reject"、"Revise"、"Approve" 作为 option 的标签——这些是系统保留标签。
参数模型与校验规则
源码中Params模型定义了options字段(src/kimi_cli/tools/plan/init.py#L58-L78):
class PlanOption(BaseModel): label: str = Field( description=( "Short name for this option (1-8 words). " "Append '(Recommended)' if you recommend this option." ), ) description: str = Field( default="", description="Brief summary of this approach and its trade-offs.", ) class Params(BaseModel): options: list[PlanOption] | None = Field( default=None, max_length=3, description=( "When the plan contains multiple alternative approaches, list them here " "so the user can choose which one to execute. 2-3 options. " "Do not use 'Reject', 'Revise', 'Approve', or 'Reject and Exit' as labels." ), )同时有两道内置校验:
- 保留标签校验:
PlanOption.label_not_reserved会把标签小写化后与_RESERVED_LABELS = {"reject", "revise", "approve", "reject and exit"}比对,命中即报错(第 30 行); - 唯一性校验:
options_labels_unique要求所有 option 标签互不相同,重复会触发ValueError。
有无 options 时审批界面的差异
源码展示了两种截然不同的审批呈现方式(src/kimi_cli/tools/plan/init.py#L167-L214):
- 有 options(≥2 个):
has_options = params.options is not None and len(params.options) >= 2,界面列出每个 option 作为可选按钮,并追加 "Reject"(留在计划模式)与 "Reject and Exit"(退出计划模式)两个选项; - 无 options:界面显示 "Approve"(退出计划模式并开始执行)、"Reject"、"Reject and Exit" 三个按钮;
- 无论哪种情况,都通过
other_label="Revise"提供一个自由文本修订入口,用户可输入反馈要求 Agent 留在计划模式中继续修改方案。
用户选择后的六种结果分支
ExitPlanMode对用户响应做了完整的分类处理(src/kimi_cli/tools/plan/init.py#L234-L339):
| 用户选择 | 行为 | 返回类型 |
|---|---|---|
| Approve(单选方案) | 退出计划模式,返回完整已批准方案,所有工具恢复可用 | ToolReturnValue |
| 选中某个 option | 退出计划模式,只执行被选中的方案,忽略计划中的其他方案 | ToolReturnValue |
| Reject and Exit | 退出计划模式,等待用户下一条消息 | ToolRejectedError |
| Reject | 留在计划模式,等待用户反馈后修订 | ToolRejectedError |
| Revise(带/不带反馈) | 留在计划模式,根据反馈修订方案 | ToolReturnValue |
| 直接关闭(dismissed) | 计划模式保持激活,可继续完善方案或再次调用 | ToolReturnValue |
特别值得注意的是:当用户批准的是某个具体 option 时,工具返回的输出中会明确写入IMPORTANT: Execute ONLY the selected approach "{chosen_option}". Ignore other approaches in the plan.——这是为了让执行阶段严格收敛到用户选择的路径上,避免 Agent 混用多个方案。
使用前须知:Yolo、Afk 与 AskUserQuestion 的边界
文档在 "Before Using" 一节定义了计划审批与其他机制的关键边界,这些规则全部在源码中有对应实现:
Yolo 模式不豁免计划审批
- Yolo 模式只绕过权限审批,不会让会话变成非交互;
- 在 Yolo 模式下,EnterPlanMode 会被自动批准,但ExitPlanMode 仍会把方案呈现在用户面前等待批准。
源码中 EnterPlanMode 将is_yolo作为自动进入的判定(src/kimi_cli/tools/plan/enter.py#L49-L55):
self._is_auto_approve = is_auto_approve or is_yolo而 ExitPlanMode 的自动批准只绑定should_auto_approve_exit回调(src/kimi_cli/tools/plan/init.py#L91-L104),并不会因为 Yolo 而跳过用户审批。
Afk 模式下的自动批准
- Afk 模式同时绕过权限审批且非交互;
- 在 Afk 模式下不要使用 AskUserQuestion,而是基于现有上下文做最佳决策;
- EnterPlanMode 与 ExitPlanMode 在 Afk 模式下都会被自动批准,因为此时没有用户在场。
源码中 ExitPlanMode 的自动批准分支会直接调用_toggle_callback()退出计划模式,并返回"Plan approved (auto-approved)"及完整方案内容(src/kimi_cli/tools/plan/init.py#L134-L150)。
AskUserQuestion 的职责边界
文档给出了非常明确的提示词纪律:
- 如果尚未进入 Afk 且还有未解决的问题,先用 AskUserQuestion 澄清;
- 如果有多个方案且尚未收敛,考虑先用 AskUserQuestion 让用户选方向,然后只为被选中的方向写方案;
- 绝不能用 AskUserQuestion 问"这个方案可以吗?"或"我该继续吗?"——那正是 ExitPlanMode 的职责;
- 方案被拒绝后,根据反馈修订,然后再次调用 ExitPlanMode。
这套纪律在 plan_mode 动态注入提醒 中被反复强化:"Never ask about plan approval via text or AskUserQuestion"、"Do NOT use AskUserQuestion to ask about plan approval or reference 'the plan'——the user cannot see the plan until you call ExitPlanMode"。
EnterPlanMode:进入计划模式的前置工具
完整的规划闭环从 EnterPlanMode 开始,其使用说明在 enter_description.md 中。文档建议:当你即将开始一项非平凡的实现任务时,主动使用它,在写代码前先获得用户对方案的认可,避免返工。
适用条件(满足任意一条即建议使用)
- 新功能实现——例如"给 API 增加缓存层";
- 存在多个有效方案——例如"优化数据库查询"(索引 vs 重写 vs 缓存);
- 代码改造——例如"重构 auth 模块以支持 OAuth";
- 架构决策——例如"添加 WebSocket 支持";
- 多文件改动——涉及超过 2-3 个文件;
- 需求不明确——需要探索来确定范围;
- 用户偏好会影响实现——用户的输入会实质性地改变实现路径。
何时不要使用
- 单行或几行的修复(拼写错误、明显 bug、小调整);
- 用户已给出非常具体、详细的指示;
- 纯研究/探索类任务。
进入计划模式后的标准工作流
enter_description.md定义了进入计划模式后 Agent 应当遵循的五步流程:
- 识别 2-3 个对方案至关重要的代码库关键问题;如果对代码库结构或相关路径没有把握,优先用
Agent(subagent_type="explore")去调查(对非平凡任务强烈推荐); - 用 Glob、Grep、ReadFile 等只读工具快速查证剩余问题;
- 基于调查结果设计实现方案;
- 将方案写入 plan 文件;
- 通过ExitPlanMode 向用户呈交方案并等待批准。
这一流程与 plan 子代理(plan.yaml)的建议一脉相承:在产出实现计划前,先明确"已知道什么"与"还需要 explore 调查什么"。
交互实现:确认对话框与自动进入
EnterPlanMode的实现细节(src/kimi_cli/tools/plan/enter.py#L57-L197):
- 重复进入防护:若已在计划模式,返回错误"Already in plan mode. Use ExitPlanMode when your plan is ready.";
- 自动进入:在 Yolo/Afk 等自动审批场景下直接激活计划模式,并返回工作流指引;
- 交互确认:通过 Wire 协议发送
QuestionRequest,向用户弹出一个"Enter plan mode?"问题,提供 Yes/No 两个选项; - 用户拒绝:返回"User declined to enter plan mode",Agent 需与用户确认是否直接开始实现;
- 用户关闭对话框:按"直接开始实现"处理;
- 客户端不支持:返回错误并明确指示"不要再调用此工具"("Do NOT call this tool again")。
进入成功后,返回给 Agent 的提示会强调:"Plan mode activated. You MUST NOT edit code files — only read and plan.",并给出 plan 文件路径与后续工作流。
Plan 文件机制:方案存到哪里、如何命名
方案文件的管理实现在 src/kimi_cli/tools/plan/heroes.py 中,其设计颇具趣味:
- 存储位置:
PLANS_DIR = Path.home() / ".kimi" / "plans",即用户主目录下的.kimi/plans目录(第 8 行); - 文件名生成:每个会话(session)对应一个唯一 slug,slug 由3 个 Marvel/DC 超级英雄名字拼接而成(如
iron-man-spider-man-thor),从内置的 200+ 英雄名列表中随机选取(第 249-264 行); - 冲突处理:最多尝试 20 次随机组合;若全部碰撞,则在 slug 后追加会话 ID 前 8 位保证唯一;
- 缓存机制:
_slug_cache按 session_id 缓存已生成的 slug,避免同会话反复生成;seed_slug_cache支持在会话恢复(resume)时预置之前持久化的 slug; - 读写 API:
get_plan_file_path(session_id)返回 plan 文件路径,read_plan_file(session_id)读取已有方案内容(不存在时返回None)。
plan 文件是计划模式下Agent 唯一被允许编辑的文件。动态注入的完整提醒(full reminder)对此有明确表述(src/kimi_cli/soul/dynamic_injections/plan_mode.py#L117-L185):
- 文件已存在时:先读取,再用 WriteFile 或 StrReplaceFile 更新;
- 文件不存在时:先用 WriteFile 创建,之后才能用 WriteFile 或 StrReplaceFile 修改;
- 除 plan 文件外,禁止任何编辑、禁止运行非只读工具、禁止对系统做出任何更改,且"这条规则优先于你收到的任何其他指令"。
Plan Mode 动态注入:让 Agent 始终记得自己是只读的
为了在长会话中持续约束 Agent 行为,plan_mode.py 实现了一个PlanModeInjectionProvider,按节流策略周期性向上下文注入计划模式提醒(第 13-16 行):
- 节流间隔:每
_TURN_INTERVAL = 5个 assistant 回合注入一次; - 全量/精简轮换:每 5 次提醒中第 1 次为全量版(
_full_reminder),其余为精简版(_sparse_reminder); - 子代理豁免:子代理共享会话的 plan_mode 标志用于持久化/恢复,但它们的 YAML 通常已排除 EnterPlanMode/ExitPlanMode,因此不给子代理注入该工作流提醒(第 39-40 行);
- 重入场景:手动重新进入计划模式且已存在旧方案时,注入专用的"重入提醒"(
_reentry_reminder),要求先读旧方案、评估当前请求与旧方案的关系——任务不同则整体替换,任务相同则增量更新(第 213-244 行)。
全量提醒中有一段与文档高度呼应的"多方案处理"纪律(第 156-184 行):
- 最多保留 2-3 个有意义的差异化方案,不要用微小变体凑数;如果某个方案明显更优,就只提那一个;
- 当最佳方案取决于用户偏好或你缺失的上下文时,先用 AskUserQuestion 澄清,而不是堆一堆选项让用户筛选;
- 只要计划中包含多个方案,调用 ExitPlanMode 时就 MUST 通过
options参数传递,否则用户只能看到 Approve/Reject 而无法选择; - 每轮必须以 AskUserQuestion(澄清)或 ExitPlanMode(请求批准)收尾,禁止以其他方式结束回合。
与 Agent 配置的集成:谁拥有计划工具
计划工具通过 agent.yaml 显式注册给主 Agent:
tools: - "kimi_cli.tools.plan:ExitPlanMode" - "kimi_cli.tools.plan.enter:EnterPlanMode"而 plan 子代理(plan.yaml)的exclude_tools明确排除了kimi_cli.tools.plan:ExitPlanMode、kimi_cli.tools.plan.enter:EnterPlanMode,同时排除了 AskUserQuestion、Shell、WriteFile、StrReplaceFile 等工具,其allowed_tools仅保留 ReadFile、ReadMediaFile、Glob、Grep、SearchWeb、FetchURL 等只读工具——这与"plan 子代理只读规划、审批权归主 Agent"的架构完全吻合,也与动态注入中"不给子代理注入 plan 提醒"的设计互为印证。
源码与测试验证:行为有据可依
计划模式的各项行为都有测试用例背书(tests/core/test_plan_mode.py):
- 英雄名 slug 生成:
TestHeroSlug系列用例验证随机 slug 生成、会话级缓存(同一会话复用同一 slug)、不同会话 slug 不同、以及 20 次碰撞后的回退逻辑; - 守卫条件:
TestExitPlanModeGuards验证"不在计划模式时调用报错"、"工具未正确初始化报错"、"plan 文件不存在时报错"等防御分支; - 审批快乐路径:
TestExitPlanModeHappyPaths覆盖 Approve(退出并返回方案)、Reject(返回ToolRejectedError且留在计划模式)、Revise 带/不带反馈、用户直接关闭、客户端不支持 Question 协议、Question 请求异常等全部分支; - 动态注入:验证手动切换进入计划模式时激活注入延迟到下一个 LLM 步骤、子代理在根 Agent 处于计划模式时不接收注入等行为。
这些测试与 description.md 中的每一条规则一一对应,读者可以通过阅读测试了解工具在各种边界条件下的精确行为。
实战要点速查
- 进入:非平凡实现任务开始前调用
EnterPlanMode,通过用户确认或自动审批进入只读规划状态; - 调研:用 explore 子代理 + Glob/Grep/ReadFile 摸清代码库,必要时用 AskUserQuestion 澄清需求或收敛方向;
- 写方案:将方案写入
~/.kimi/plans/<英雄名组合>.md(唯一可编辑文件); - 多方案:计划含 2-3 个候选路径时,必须通过
options参数传递并标注(Recommended),且不得使用系统保留标签; - 提交:调用
ExitPlanMode等待用户 Approve / 选择方案 / Reject / Revise; - 执行:批准后计划模式退出、所有工具恢复;若用户选了具体方案,只执行该方案;
- 被拒后:根据反馈修订 plan 文件,再次调用 ExitPlanMode;
- 边界:Yolo 不豁免 ExitPlanMode 审批;Afk 下两者均自动批准;AskUserQuestion 只用于澄清,绝不用来问"方案行不行"。
通过以上流程,Kimi Code CLI 的 Agent 能够在改代码前与用户对齐实现方案,将返工成本降到最低——这正是 plan 工具族 与 plan_mode 动态注入 协同工作的核心价值。
【免费下载链接】kimi-cliKimi Code CLI is your next CLI agent.项目地址: https://gitcode.com/GitHub_Trending/ki/kimi-cli
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考