第 26 讲:Goal 目标管理与 Plan 规划模式

DeepSeek Harness 源码解析 · 第 26 讲

DeepSeek Harness 源码解析系列 第 26 讲:Goal 目标管理与 Plan 规划模式 基于 DeepSeek Harness 源码 · 2026-09-09 💡 本讲一句话: "重构整个模块"这种活,一个 turn 干不完——Agent 需要 目标(Goal) :把目标和轮次预算写进会话日志,然后自动一轮一轮继续干,直到完成、被卡住或耗尽预算;它还需要 规划模式(Plan mode) :先只读地想清楚方案,经用户批准才动手。本讲拆这五块源码: dsh-goal (事件溯源的状态机)、 dsh-goal-round-driver (自动续跑的调度器)、 dsh-tool-goal (模型侧工具+权限闸门)、 dsh-command-goal (/goal 命令)、 dsh-plan-mode (日志化的协作状态)。读完你能带走三个设计判断:为什么激活态不持久化、为什么每轮消息必须过 pre-step 围栏、为什么 Plan 模式宁可用 pending 队列也不开活镜像。 一、长任务的两个痛点,两套不同的地基 前面几讲拆过 subagent(第 18 讲)、workflow(第 24 讲)和 jobs/schedule(第 25 讲),它们解决的是"委托"和"异步回来找我"。但还有一类需求更根本: 一个目标本身跨多个 turn ——模型干完一轮,系统要能自动把下一轮递回去;同时用户随时可以暂停、改目标、或者干脆清掉它。 仓库里这是 五个包 的分工,地基各不相同——先给一张全景表: 包 角色 状态存哪 dsh-goal Goal 领域服务(ctx.goals):CAS 变更 + 严格 replay fold 会话事件日志 goal/change(全量快照)+ 进程内 WeakMap 缓存 dsh-goal-round-driver 自动续跑调度器:闲了就把下一轮排进 inbox,pre-step 围栏把关 纯进程内(Map<Agent, DriverState>),重启即失忆——这是故意的 dsh-tool-goal 模型侧 get_goal / create_goal / update_goal + 权限闸门 无状态,每次执行都从日志 fold 出当前态再校验 dsh-command-goal 人类侧 /goal 命令(show/create/edit/pause/resume/clear) 无状态,透传给 dsh-goal dsh-plan-mode 规划模式:prompt section + /plan 命令 + exit_plan_mode 审批工具 会话事件日志 plan/mode(last-wins 布尔)+ pending 意图队列 注意一个贯穿全篇的纪律: 凡是"跨重启必须还在"的状态,一律写进会话日志、用纯函数 fold 出来;凡是"只属于当前进程"的东西(激活态、调度器状态),一律不持久化 。这条线是理解本讲所有代码的钥匙。 二、Goal 领域:一个"全量快照 + CAS"的事件溯源状态机 2.1 类型词汇表:phase 是持久的,activation 不是 先看 dsh-goal/src/types.ts 。这里有一个刻意的设计分裂: GoalPhase(active/paused/blocked/complete)是持久化的生命周期阶段,而 GoalActivation(armed/disarmed)只是"本进程是否允许自动续跑" ——它出现在 GoalView 里,却明确标注 never persisted: 📄 packages/goal/goal/src/types.ts (第 16-71 行) export type GoalId = Branded<'GoalId'> // 品牌化字符串:编译期把 goal id 和普通 string 隔开,防止跨域混用 // export interface GoalRef { // CAS 引用:id + revision 一起构成"精确版本",所有变更都要带上它 readonly id: GoalId // 目标身份,跨修订不变 readonly revision: number // 正整数修订号;每次持久化变更都 +1——这就是乐观锁的锁 } /…

阅读全文:DeepSeek Harness 源码解析 第 26 讲