Open Interpreter 计划模式深度解析:plan.md 如何定义一套"决策完整"的协作规划协议
本文以 codex-rs/collaboration-mode-templates/templates/plan.md 这份计划模式(Plan Mode)协作模板为骨架,完整拆解其三阶段工作流、执行/变更行为边界、提问策略与 <proposed_plan> 定稿规则;并结合仓库中模式注入、模板加载、流式解析与 update_plan 工具拦截的源码实现,说明这套提示词协议在 openinterpreter(Codex 系 CLI/TUI)中是如何被编译进会话上下文并被客户端消费的。读完后你既能逐条掌握计划模式的运作规则,也能从源码层面理解其落地链路。
1. plan.md 的定位:一份被编译进二进制的开发者指令
plan.md 不是一篇普通文档,而是 Plan 模式生效时注入给模型的核心开发者指令。它的加载方式非常直接:
- src/lib.rs 中仅两行代码:
pub const PLAN: &str = include_str!("../templates/plan.md");与pub const DEFAULT: &str = include_str!("../templates/default.md");。模板在编译期被内联进 crate 二进制,运行时无需文件读取,保证了模板内容与发布版本严格一致。 - models-manager/src/collaboration_mode_presets.rs 中的
plan_preset()把COLLABORATION_MODE_PLAN作为developer_instructions装配进 Plan 模式预设,并固定了ReasoningEffort::Medium的推理档位;与之相对的是default_preset()加载 default.md,后者要求"优先做出合理假设并直接执行",且把模板中{{KNOWN_MODE_NAMES}}占位符渲染为当前可见模式名列表。
从源码结构看,模板的优先级还有一层细节:core/src/context/world_state/collaboration_mode.rs 的 from_collaboration_mode() 会优先取模型目录(catalog)下发的指令,仅在其缺失时才回退到预设中的 developer_instructions。也就是说,plan.md 是内置兜底版本,模型服务方仍可用 CollaborationModeMessages 覆盖它。
模式切换由 TUI 侧驱动:tui/src/collaboration_modes.rs 提供 next_mask() 按预设顺序循环切换模式、plan_mask() 直接取 Plan 预设;而模式枚举定义在 protocol/src/config_types.rs:
pub enum ModeKind {
Plan,
#[default]
#[serde(
alias = "code",
alias = "pair_programming",
alias = "execute",
alias = "custom"
)]
Default,
}
pub const TUI_VISIBLE_COLLABORATION_MODES: [ModeKind; 2] = [ModeKind::Default, ModeKind::Plan];
值得注意的是 Default 上挂着 code/pair_programming/execute 等 serde 别名——从代码结构看,这是为了兼容旧配置中把默认执行模式叫作"code/execute"的命名习惯。
2. 计划模式的总纲:聊天式收敛到"决策完整"的规划
plan.md 开宗明义(第 1-3 行):
You work in 3 phases, and you should chat your way to a great plan before finalizing it. A great plan is very detailed—intent- and implementation-wise—so that it can be handed to another engineer or agent to be implemented right away. It must be decision complete, where the implementer does not need to make any decisions.
即:Agent 分三阶段工作,在定稿前要通过对话把一个计划打磨到"决策完整"(decision complete)——细节详尽到可以直接交给另一位工程师或另一个 Agent 立即实现,实现者不需要再做任何决策。这是整份模板的北极星指标,后续所有规则(先探索后提问、多问问题、定稿门槛)都是为它服务的。
3. 模式规则:Plan Mode 只能由开发者消息显式结束
plan.md 的 "Mode rules (strict)" 一节确立了两条硬规则:
- 只有开发者消息(developer message)显式结束 Plan Mode 时才退出,例如切回 Default 模式时注入的 default.md 开发者指令:"Any previous instructions for other modes (e.g. Plan mode) are no longer active."
- 用户意图、语气或祈使句都不能改变模式。用户在 Plan Mode 里说"直接帮我做",应被理解为"规划这个执行过程",而不是执行它。
这与注入机制吻合:collaboration_mode.rs 中 CollaborationModeState 实现 WorldStateSection,render_diff() 只在模式或模型发生变化时向历史追加一段用 COLLABORATION_MODE_OPEN_TAG/CLOSE_TAG 包裹的 developer 角色片段。换言之,模式状态是通过世界状态的 diff 注入实现的,普通用户消息不会触发模式切换——模板中"模式由开发者消息控制"的措辞正是对这一运行时事实的镜像。
4. Plan Mode 与 update_plan 工具:两个极易混淆的概念
模板单列一节区分二者,这是理解该协议的关键:
- Plan Mode 是一种协作模式,流程上可能向用户发起输入请求,并最终产出一个
<proposed_plan>块。 update_plan是一个独立的清单/进度/TODO 工具,它不进入也不退出 Plan Mode,二者不可混用;在 Plan 模式下调用update_plan会直接报错。
仓库源码精确印证了最后一点。core/src/tools/handlers/plan.rs 的 PlanHandler 在处理调用时:
if turn.mode == ModeKind::Plan {
return Err(FunctionCallError::RespondToModel(
"update_plan is a TODO/checklist tool and is not allowed in Plan mode".to_string(),
));
}
即当当前 turn 处于 Plan 模式时,update_plan 不会执行、不会发 EventMsg::PlanUpdate,而是把一条"工具不允许在 Plan 模式使用"的错误回传给模型——模型据此自行改道,改为通过对话和 <proposed_plan> 块推进规划。非 Plan 模式下,update_plan 才正常解析 UpdatePlanArgs 并广播计划更新事件。
5. 行为边界:允许"探索执行",禁止"变更执行"
plan.md 的 "Execution vs. mutation in Plan Mode" 一节给出了一条清晰的分界线:可以执行**非变更性(non-mutating)且有利于改善计划的动作,但绝不能执行变更性(mutating)**动作。
5.1 允许的动作(探索类)
以"获取真相、消除歧义、验证可行性,且不改变仓库受跟踪状态"为判据,允许:
- 读取/搜索文件、配置、schema、类型、manifest 与文档;
- 静态分析、检视与仓库探索;
- 不编辑受跟踪文件的 dry-run 式命令;
- 允许写缓存/构建产物(如
target/、.cache/、快照)的测试、构建与检查,前提是不编辑仓库受跟踪的文件。
5.2 禁止的动作(执行类)
凡是"实现计划"或改变受跟踪状态的动作都禁止:
- 编辑或写入文件;
- 运行会重写文件的格式化器/linter;
- 应用会更新受跟踪文件的补丁、迁移、代码生成;
- 目的为"执行计划"而非"打磨计划"的带副作用命令。
模板给出了一个可操作的判定口诀(第 39 行):
When in doubt: if the action would reasonably be described as "doing the work" rather than "planning the work," do not do it.
(拿不准时:如果一个动作会被描述为"干活"而非"规划活",就别干。)
6. 三阶段工作流:先落地环境,再谈意图,最后谈实现
PHASE 1 — Ground in the environment(探索优先,提问其次)
- 先在实际环境中"扎根":用发现事实而非向用户提问的方式消除 prompt 中的未知项;只有环境推导不出的缺失/歧义才允许记录为待问问题;允许并鼓励回合之间的静默探索。
- 硬性前置条件:除非本地没有仓库/环境,在向用户提问前,至少完成一轮有针对性的非变更探索(搜索相关文件、检视可能的入口点/配置、确认当前实现形态)。
- 例外:仅当 prompt 本身存在明显歧义或矛盾时,才可以在探索前澄清;但只要歧义"可能通过探索解决",一律先探索。
- 禁止问"仓库或系统能回答的问题"(例如"这个 struct 在哪?""该用哪个 UI 组件?"——探索即可确认)。只有穷尽了合理的非变更探索,才开口提问。
PHASE 2 — Intent chat(搞清楚用户到底要什么)
持续追问,直到能够清楚陈述六要素:目标 + 成功标准、受众、范围内/范围外、约束、当前状态、关键偏好/权衡。
核心偏置是"提问优于猜测"(Bias toward questions over guessing):只要还剩下任何高影响的歧义,就不要开始规划,先问。
PHASE 3 — Implementation chat(我们用什么方式/如何构建)
意图稳定后,继续追问直到规格"决策完整",覆盖清单:技术方案、接口(API/schema/输入输出)、数据流、边界情况/失败模式、测试 + 验收标准、发布/监控,以及迁移/兼容性约束。
7. 提问协议:问题必须"改变计划"才值得问
7.1 工具优先与选项质量
- 强烈优先使用
request_user_input工具提问; - 只给出有意义的多选选项,不要放明显错误或无关的凑数选项;
- 极少数情况下,若问题极端模糊、无法用合理多选表达,可以直接不用工具提问。
每个问题必须满足(满足其一即可,且均不能通过非变更命令自行解答):
- 实质性地改变规格/计划;
- 确认/锁定某个假设;
- 在真实有意义的权衡之间做选择。
request_user_input 工具在 Plan 模式下的可用性由代码层面背书:config_types.rs 中 ModeKind::allows_request_user_input() 仅对 Plan 返回 true——这正是"该工具主要服务于 Plan 模式"的实现依据。
7.2 两类未知项,区别对待
- 可发现事实(仓库/系统事实):先探索。提问前先做定向搜索、检查可能的真相来源(配置/manifest/入口点/schema/类型/常量)。只有以下三种情况才允许提问:存在多个可信候选;什么也没找到但缺少关键标识符/上下文;歧义本身就是产品意图。且提问时要给出具体候选(路径/服务名)并推荐一个。绝不问"这个 struct 在哪"这类自己能答的问题。
- 偏好/权衡(不可探索发现):尽早问。给出 2–4 个互斥选项 + 一个推荐默认值;若用户未回答,就按推荐选项继续,并在最终计划中把它记录为假设。
8. 定稿规则与 <proposed_plan> 协议
8.1 何时允许输出最终计划
只有当计划"决策完整、不给实现者留下任何决策"时才输出。呈现正式计划时,必须用 <proposed_plan> 块包裹以便客户端特殊渲染,格式五条:
- 开标签独占一行;
- 计划内容从下一行开始(与开标签不同行);
- 闭标签独占一行;
- 块内使用 Markdown;
- 标签名严格保持
<proposed_plan>/</proposed_plan>,即使计划内容使用其他语言也不得翻译或改名。
8.2 计划正文的写作规范
plan.md 对最终计划的内容密度做了非常细粒度的规定,值得逐条继承:
- 内容必须对人和 Agent 都可消化;默认"仅计划、简洁",且必须包含:清晰的标题、简要摘要、公共 API/接口/类型的重要变更、测试用例与场景、显式假设与默认值;
- 优先采用 3–5 个短小节,通常是 Summary、Key Changes / Implementation Changes、Test Plan、Assumptions;除非范围边界对避免出错确有必要,不设独立 Scope 小节;
- 按子系统或行为分组列实现要点,避免逐文件清单;仅在必要时点名文件以消除歧义,非必要不超过 3 个路径;
- 行为级描述优于逐符号的删改清单;对 v1 新增功能计划,不要凭空发明详细的 schema、校验、优先级、回退或 wire-shape 策略,除非需求已建立或为防止具体实现错误所必需;
- 要点保持短小,避免解释性子弹点,除非确有必要消除歧义;压缩同类变更,省略分支逻辑、重复不变量、未受影响的长清单;
- 简单重构的计划应压缩为:紧凑摘要 + 关键编辑 + 测试 + 假设;用户要更多细节时再展开。
8.3 结尾与块数量约束
- 最终输出中不要问"是否继续?"——用户看到
<proposed_plan>块后,可自由退出 Plan 模式要求实现,或留在 Plan 模式继续打磨; - 每轮最多一个
<proposed_plan>块,且仅在呈现完整规格时产生; - 若用户在前一计划后要求修订:新块必须是完整替代;若用户表示不认可但未给出足够信息产出完整替代版,则先回应关切、继续规划、不产出
<proposed_plan>块;若后续消息既不需要修改也不质疑计划(如澄清性问题),则先作答,再原样复述此前的<proposed_plan>块。
9. 客户端侧:<proposed_plan> 块的流式解析实现
plan.md 要求"标签独占一行、内容从下一行开始"并非空穴来风——它精确匹配了仓库中解析器的设计。utils/stream-parser/src/proposed_plan.rs 中的 ProposedPlanParser:
- 基于
TaggedLineParser识别OPEN_TAG = "<proposed_plan>"与CLOSE_TAG = "</proposed_plan>",实现StreamTextParser接口,把模型输出流切分为ProposedPlanStart/ProposedPlanDelta(text)/ProposedPlanEnd事件序列,同时把标签外的文本收集为visible_text; - 行级标签识别意味着"开标签必须独占一行"是协议的一部分:测试
preserves_non_tag_lines明确验证了" <proposed_plan> extra"(缩进/行尾有其他内容)不会被识别为标签,会原样保留为普通文本——这正是模板第 98 行规则的底层原因; - 流结束时,
closes_unterminated_plan_block_on_finish测试验证未闭合的块会被自动闭合,避免渲染悬空状态; - 非流式场景提供
strip_proposed_plan_blocks()(剥离计划块,留下正文)与extract_proposed_plan_text()(提取最后一个块内的计划文本)两个工具函数,供历史记录、导出等复用。
这意味着:模型按 plan.md 输出规范块 → TUI/app-server 流式解析为独立事件 → 客户端把计划渲染为特殊卡片、正文只留说明文字。模板规范与解析器协议是一一对应的契约。
10. 端到端链路小结
把散落的源码证据串起来,plan.md 的完整生命周期是:
- 编译期:src/lib.rs 用
include_str!将 plan.md 内联为PLAN常量; - 模式装配:collaboration_mode_presets.rs 以该常量构造 Plan 预设(含 Medium 推理档位),TUI 通过 collaboration_modes.rs 在 Default/Plan 间切换;
- 上下文注入:collaboration_mode.rs 的世界状态 diff 机制在模式变化时以 developer 角色片段把指令注入会话历史(模型目录下发指令优先,plan.md 为内置回退);
- 行为约束:Plan 模式下
update_plan工具被 plan.rs 硬拦截;request_user_input则仅 Plan 模式可用(ModeKind::allows_request_user_input); - 产出消费:模型按模板规范输出
<proposed_plan>块,proposed_plan.rs 流式解析为事件供客户端渲染,用户随后可退出 Plan 模式执行或继续修订。
11. 适用前提与限制
- 本文所有行为描述以当前仓库代码为准:模板内容随 crate 编译固化,运行时修改仓库文件不会热生效;
- 模板中的工具名(
request_user_input、update_plan)依赖对应工具在当前会话可用——default.md 也明确要求"仅当本轮工具列表中包含request_user_input时才使用"; - Plan 模式预设固定了 Medium 推理档位,这是 collaboration_mode_presets.rs 中的内置默认,具体模型是否支持相应推理档位仍受模型目录约束;
- 若模型目录通过
CollaborationModeMessages下发了 plan 指令,内置 plan.md 仅作为回退,实际生效内容以注入上下文者为准(从from_collaboration_mode()的优先级逻辑推断)。
对维护协作式编码 Agent 的团队而言,plan.md 的价值在于它把"规划质量"从模型的自然倾向提升为可测试的协议:提问有判据(是否改变计划)、探索有门槛(先探索后提问)、行为有边界(非变更 vs 变更)、产出有格式(<proposed_plan> 块与解析器一一对应)。这种"提示词即契约、契约有解析器兜底"的设计,是开源仓库里一个值得参考的工程样本。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00