DeepSeek Harness 计划模式(Plan Mode)完全指南:per-agent 协作状态、配置与受审退出机制
【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness
导读:Plan Mode 是 DeepSeek Harness 提供的一种按 Agent(per-agent)持久化的协作状态——开启后,Agent 在"先探索与设计、后执行"的引导下工作,并把完整方案通过
exit_plan_mode工具提交给人类审批。本文以.agents/notes/archived/feature/2026-07-07-plan-mode.md这份设计文档为骨架,结合当前仓库中落地实现的@deepseek-ai/dsh-plan-mode包(位于 packages/plan/plan-mode)、docs/subsystems/plan.md 子系统参考、工具目录与配置目录,完整讲解其设计动机、当前简化后的契约、配置方式、命令行用法、底层事件折叠原理,以及它与沙箱、审批等强制轴的边界。
Plan Mode 的核心思想可以用一句话概括:模式是"软"的——它只通过提示词引导模型,从不自行执行任何限制;真正的强制力来自独立的沙箱模式与审批策略两个轴。理解这条"软引导 + 独立强制"的边界,是正确使用和部署 Plan Mode 的前提。
1. 背景与设计动机:为什么需要一种"具名的会话模式"
在设计 Plan Mode 之前,DeepSeek Harness 缺少一种持久化手段,让某个 Agent 进入一种可区分的"工作姿态"(working stance)。设计文档(2026-07-07-plan-mode.md)明确指出,Plan Mode 需要同时满足四个要求:
- 在规划引导下探索与设计:模型在执行前先产出可评审的产物(plan artifact);
- 跨越明确的审批边界:方案必须经过人类显式同意才能进入执行;
- 可复现(reconstructable):会话恢复(resume)或派生(fork)时,模式状态必须无额外机制地还原,且不能让模型可见的请求与会话日志发生偏离;
- 与既有扩展接缝协作:
system-prompt/assemble负责按步装配引导;ctx.userInteraction承载审批提问与纠错反馈;SessionEventMap承载持久的 per-agent 事实。
围绕这些接缝,原设计提出了一套泛化命名模式注册表(generic named-mode registry):mode/set事件、ctx.modes服务、ModeConfig.modes定义映射、dsh-mode包。但后续的简化决策(2026-07-22-plan-specific-collaboration-state.md)发现:产品只发货了plan这一个模式,泛化 API 的所有"未来可能"支持(模式名校验正则、保留名规则、ctx.modes.list()、已退休定义回退)都是无人消费的维护负担,而且 "mode" 一词横跨了多个不相关领域——沙箱模式是ctx.sandboxPolicy拥有的强制执行策略,plan 模式是贡献引导与受审退出的协作姿态,二者不应被塞进同一个命名模式抽象。
因此,当前仓库中的落地形态是plan 专属的产品包:
- 包名:
@deepseek-ai/dsh-plan-mode,位于 packages/plan/plan-mode; - 持久事实:
plan/mode: { active: boolean }(替代原设计的mode/set: { mode: string }); - 折叠函数:
foldPlanMode(events),空日志默认值为false; - 服务:
ctx.planMode.get(agent)返回{ active, pending? },set(agent, active)记录边界应用的选择; - 提示词区块:固定的
plan:policy(替代mode:policy); - 命令与工具:
/plan [message]、/plan off、exit_plan_mode。
设计历史说明:原文档中描述的泛化
mode/set、ctx.modes、ModeConfig.modes定义映射等 API 属于已被简化决策取代的历史设计,当前仓库不再提供;exit_plan_mode与plan/mode才是现行契约。阅读本指南时请以"当前实现"一节为准。
2. 当前实现:Plan Mode 的设计契约
2.1 状态是日志的纯函数,而非实时镜像
Plan Mode 的持久状态是一个 log-only、whole-value-replace(整值替换)的会话事件:
plan/mode: { active: boolean } // SessionEventMap 成员:仅写日志、非表面(non-surface)、 // 整值替换——日志中最后一个值即当前状态 DEFAULT = false // 空日志(无 plan/mode 事件)的折叠结果foldPlanMode(events, end?)返回前缀中最后记录的值,不存在时返回false。因为会话日志即事实通道,所以:
- 恢复(resume)与派生(fork):子代理的 fork 继承父代理日志中的
plan/mode,无额外机制;新 spawn 的代理从非活动状态开始(无创建期 mode 选项,见下文限制); - 压缩(compaction):
plan/mode不是表面节点,压缩无法遮蔽它; - UI 观察:通过
session/event读取提交的模式翻转,没有agent/*实时镜像可订阅。
这个设计正是从"命名模式注册表"简化后保留不动的部分——原文档强调"折叠状态是 per-agent 的、日志独有且非表面、压缩无法遮蔽",简化后由plan/mode与foldPlanMode继续承担。
2.2 待定选择与步边界(pre-step)追加
由于每个会话事件都被回合(turn)包围,一个用户选择不能立刻写入日志,而是保持pending状态,直到下一个被接受的 in-turnagent/pre-step在请求装配前追加它。具体行为(见 docs/subsystems/plan.md 与 src/index.ts):
set(agent, active)记录待定选择;若目标与"已记录或已待定"状态相同则为 no-op;get(agent)返回{ active: boolean; pending?: boolean }——当前步装配所用的记录状态 + 等待追加的选择状态;- 运行中 Agent 唯一的追加点是前置的
agent/pre-step监听器:它观察每一个提议的请求步(包括第 1 回合第 1 步与请求恢复重试),先调用下游监听器,只有步被接受后才追加plan/mode; - 追加失败不能阻塞回合,选择保持 pending 等待后续被接受的 in-turn pre-step;
- 用户选择追加时,仅当最后记录的
request/header描述了相反状态,才追加一条插件来源的user/message通知("用户将此会话切换到了 plan 模式")——净零翻转(plan 后又切回)不产生任何通知,首个请求前设置的模式不通知(区块本身就是状态陈述),工具驱动的退出通过其自身的工具结果叙述。
set的返回语义(来自 docs/subsystems/plan.md 的 Cordis API 目录)为:'committed'(已立即记录)、'queued'(等待下一个被接受的 in-turn pre-step)、'cancelled'(清除了相反的待定选择,记录状态已匹配)、'noop'(已在目标状态)。
2.3 软层:计算区块 + 稳定退出工具
Plan Mode 的整个"表面"都是软的:
- 提示词区块:注册
{ name: 'plan:policy', order: 500, text: context => … },从AssembleContext.agent读取调用代理的模式,解析为配置引导文本或空串。active 时精确渲染部署配置的section文本(first-party order 500),inactive 时贡献空文本; - 稳定工具目录:
exit_plan_mode通过ctx.tools注册一次,在 plan 模式 inactive 时也保持注册,因此进入/退出 plan 模式只改变request/header中的提示词部分,原生工具 schema 与 Code Mode 的 PTC SDK 保持逐字节不变(tool-catalog 目录明确记录:"exit_plan_mode stays in the model-facing schema while planning is inactive so transitions add no tool-catalog churn on top of the plan-policy change",见 docs/tool-catalog.md); - 不设执行门禁:没有任何
tools/pre-execute监听器,模式本身不拦截任何工具调用。
2.4 受审退出:exit_plan_mode
exit_plan_mode是 Plan Mode 与人类之间的结构化过渡,其完整契约:
- 参数:唯一必填参数
plan: string——方案以 markdown 书写且必须以#标题开头; - 前置校验:无调用代理(agent-less)时拒绝执行(沿
todo_write先例);折叠模式非 active 时拒绝;空方案或无标题方案在提问前拒绝; - 评审:通过 user-questions 接缝发起单选评审(
Approve/Keep planning+ 自由文本反馈),detail携带完整方案原文; - 通过条件:只有恰好一个、且无自定义文本的
Approve选择表示同意,其余任何形态都 fail closed——评审未提供或失败时调用同样失败,退化为人工/plan off,绝不出现未受审退出; - 批准后:记录一条 silent(不叙述)的 pending 退出选择,由下一个被接受的 in-turn pre-step 追加;plan 引导在当前工具批次的剩余部分仍然有效,工具结果本身报告状态转换;
- 保持规划(Keep planning):返回携带用户反馈原文的 corrective
isError,模式保持在 active,模型修订后重新提交。
渲染意图上,presentCall是一个以方案首个标题为卡题、方案 markdown 为内容的 generic 卡片,随后是 generic 结果卡片。关于视觉渲染、token 与 KV Cache 影响,packages/plan/plan-mode/README.md 的 "Model Experience" 章节有逐项说明:inactive 不增加 token;进入/退出改变 first-party order 500 之后的系统提示词,因此从该点开始的缓存路径会变化,但工具目录不再抖动。
3. 部署配置:一行 YAML 开启 Plan Mode
Plan Mode 的定义是经过校验的插件 Config,遵循仓库惯例,无需改代码即可从cordis.yml变更。当前唯一必填配置是引导文本section——任何多余字段都会在加载时失败:
- name: '@deepseek-ai/dsh-plan-mode' config: section: | You are in plan mode. Explore and design before presenting the complete plan through exit_plan_mode.| 字段 | 默认值 | 含义 |
|---|---|---|
section | 必填 | plan 模式激活时以plan:policy提示词区块渲染的引导文本 |
校验规则(docs/subsystems/plan.md 与 docs/config-catalog.md):
section缺失、空白或非字符串,以及任何未知键,都在插件加载时失败,而不是被静默忽略;- 包本身不内置任何模型指令——规划行为的全部引导都来自部署方提供的文本;
- 当前没有泛化的模式定义映射,也不支持通过配置添加第二个模式(原设计中"additional modes use the same config map"的泛化能力已随简化决策移除;未来若出现第二个协作姿态,将是一次显式设计决策而非配置项)。
/** Deployment-owned plan guidance. */ interface PlanModeConfig { /** Guidance rendered as the `plan:policy` prompt section while plan mode is active. */ section: string }4. 命令行与前端使用:/plan、/plan off 与评审
4.1 命令契约
当ctx.commands被组合时,插件注册/plan [off|message](详见 packages/interaction/commands/README.md 与 src/index.ts):
| 输入 | 行为 |
|---|---|
/plan | 选择 active(进入 plan 模式) |
/plan <message> | 先选择 active,再把去除首尾空白后的消息通过agent.steer()提交,使其成为受影响步中的一条普通已记录用户消息(在 plan 引导之下);图片附件随 steered 消息一并携带 |
/plan off | 选择 inactive,不经过模型输入直接退出;也能取消一个尚未生效的待定进入选择;带图片的/plan off被拒绝以免图片丢失 |
命令名与结果不进入模型历史;只有显式的消息体作为普通用户消息被记录。命令在任何支持斜杠命令的前端可用,如 Web 客户端(/plan可携带图片)。
4.2 完整端到端流程
- 用户在 Web / TUI 输入
/plan [message](或直接驱动ctx.planMode); - 从下一步起,每个请求都携带部署配置的
plan:policy引导区块; - 模型在引导下探索与设计,把变更推迟进方案;
- 模型调用
exit_plan_mode提交 markdown 方案——用户界面显示方案卡片并弹出评审; - 用户选择Approve:plan 模式在下一个步边界切换为非活动,下一步的请求头移除引导区块而工具 schema 不变,此后执行跟踪交给
todo_write; - 用户选择Keep planning(可附反馈):模型收到携带反馈的 corrective 错误,修订后重新提交。
4.3 观察模式状态
接口(如 Web UI)可以显示 plan 模式是否激活、以及请求的切换是否仍在等待生效(pending)。该状态在所有标签页一致,并在重启后保持(它来自日志折叠而非进程内变量)。
5. 依赖、接缝与优雅降级
dsh-plan-mode是一个产品包,而非 capability-seam 三件套(接口/实现/消费者)——模式的可变部分是配置值而非实现,拆分会制造空的实现包(这与审批接缝、todo/先例的"不预拆分"判断一致)。其依赖关系(见 docs/tool-catalog.md 与 packages/plan/plan-mode/package.json):
- 对
cordis、dsh-session、dsh-agent、dsh-tools、dsh-system-prompt为对等依赖; - 注入
['tools', 'systemPrompt']; - 执行期机会性地(
ctx.get)读取ctx.userQuestions(类型级对等依赖边); - 与前端相关的只有可选的类型级对等边(
dsh-commands用于注册/plan)。
包外的一切都通过监听器参与(defensive-patterns:策略插件不得阻塞提示或回合),因此移除该包即可优雅地去掉 Plan Mode,不会破坏消费者。终端前端不需要任何模式专属代码——插件自己把每个命令注册到命令注册表,退出评审复用组合后的 user-questions 提供者的提示队列(与ask_user_question同队列,无新机制)。
当部署没有组合任何 user-questions 提供者时,Plan Mode 保持安全但手动:ctx.userQuestions缺失时接缝无法解析,exit_plan_mode返回 correctiveisError,退出降级为人工/plan off,永远不会出现未受审退出。引导文本会告诉模型:通过exit_plan_mode呈现方案,若失败则用散文询问用户——模型保持呈现而非卡死。
6. 边界澄清:Plan Mode 与沙箱、审批是两个独立的轴
这是整个设计中最重要的一条边界,也是原文档 FAQ 反复强调的点:
- Plan Mode 是协作姿态(
plan/mode折叠),不读取也不写沙箱与审批旋钮; - 沙箱模式是强制旋钮(
sandbox/mode折叠,见 docs/subsystems/sandbox.md),负责真正的内核级只读等约束; - 二者互不干扰,与 Codex 将 Plan/Default 协作预设与其沙箱/审批设置分开的做法一致。
因此:
想要在规划期间获得内核强制的只读下限?同时设置两个旋钮:切换 Plan Mode并且把沙箱模式选项设为只读——顺序无关,每个开关只改变自己的折叠,无干扰、无需要退出的恢复步骤。
日志把每个轴归属到自己的事件:姿态记入plan/mode,约束记入bash/sandbox-mode。
同样地,不存在 per-mode 工具允许/拒绝列表:因为"哪些工具在规划模式下安全"是每个工具自身的属性(它的效果 effects),而不是模式的属性;手工维护的名字列表会在新工具(含 MCP 服务器)到来时悄然腐烂,还会造成"看起来像安全边界而实际不是"的过度承诺。直到工具定义声明其效果元数据(原文档 Deferred 中留待的readOnlyHint/destructiveHint方向)之前,Plan Mode 的约束方式就是:section引导 + 退出评审。这是被明确接受的成本,不是缺陷。
7. 已知限制与注意事项
依据 packages/plan/plan-mode/README.md 的 "Known Limitations" 与 docs/subsystems/plan.md:
- 引导而非强制:忽略引导的模型在规划期间仍可能执行变更;真正的防护面是评审时刻、会话日志,以及独立配置的沙箱、审批、文件系统策略;
- 待定选择是进程本地的:回合最后一次被接受的 pre-step 之后做出的选择,若进程在另一次被接受的 in-turn pre-step 前退出则丢失(UI 需重新应用);原设计预留的 idle-record 原语是逃生舱;
- 无创建期 plan 选项:fork 子代理继承日志中的 plan 状态;新 spawn 的子代理从 inactive 开始;
- 活的子代理无法打开评审:被另一活代理拥有的子代理调用
exit_plan_mode会失败,并被要求把未决决策写进最终结果;只有plan-review这一种专属评审渲染器(Web UI),其他交互提供者通过其通用选项流呈现同一请求; - 缓存影响:进入/退出 Plan Mode 会从 first-party order 500 起改变系统提示词,该点之后的缓存路径变化;但工具 schema 与 Code Mode SDK 不再抖动。
8. 测试与验证证据
当前实现由多层测试钉住(见 packages/plan/plan-mode/tests):
plan-mode.spec.ts:边界顺序、重试、追加失败、HMR 释放、提示词装配、原生与 PTC 模式 schema 稳定、评审结果、不变量覆盖(经由布尔服务);projection.spec.ts:plan会话投影单元——把已记录的/plan命令运行转为候选目标,在plan/mode上提交记录状态,并为view推导{ active, pending };integration.spec.ts:完整exit_plan_mode评审弧线的包级测试;invariant.spec.ts:plan/mode载荷形状校验。
仓库级测试(apps/web/tests/plan-control-row.e2e.ts)覆盖 Web 前端的 plan 控制行。原设计的录制场景(input.json中的setMode步操作与elicitationAnswers队列)随交互式 ACP 场景退役;当前 keyless TUI 场景覆盖/plan <message>进入与/plan off直接退出,并验证每个已提交的plan/mode先于其改变的request/header。
9. 进一步阅读
- 设计决策原文:2026-07-07-plan-mode.md(含 Alternatives considered 与 Consequences 的完整历史);
- 现行设计决策:2026-07-22-plan-specific-collaboration-state.md;
- 子系统参考:docs/subsystems/plan.md(含
ctx.planMode的 Cordis API 签名); - 包 README:packages/plan/plan-mode/README.md(Model Experience 与限制细节);
- 工具目录条目:docs/tool-catalog.md(
exit_plan_mode的精确 schema); - 配置目录条目:docs/config-catalog.md(每个可接受字段的 JSDoc);
- 源码:packages/plan/plan-mode/src/index.ts、src/types.ts、src/invariant.ts。
【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考