什么是目标
目标(goal)是附着于现有会话的一条持久化完成目标。它是状态,而非调度器——会话日志是其事实来源,而一个独立的目标轮驱动(goal-round driver)决定一个已武装目标何时真正续作。目标家族位于 packages/goal/ 下并干净地切成四个包:
| 包 | 角色 | ctx 键 |
|---|---|---|
@deepseek-ai/dsh-goal | 目标状态、生命周期与重放折叠 | ctx.goals |
@deepseek-ai/dsh-goal-round-driver | 同会话续作驱动 | — |
@deepseek-ai/dsh-tool-goal | 面向模型的 get_goal/create_goal/update_goal 工具 | 注册于 ctx.tools |
@deepseek-ai/dsh-command-goal | 面向人类的 /goal 命令 | 注册于 ctx.commands |
消费者依赖 @deepseek-ai/dsh-goal,从不依赖具体的 agent 循环。其子系统参考是 docs/subsystems/goal.md。
领域模型
宿主词汇表位于 packages/goal/goal/src/domain.ts(宿主侧),其类型位于 src/types.ts。关键类型:
GoalId —— 一个目标跨其持久修订的品牌化 id
GoalRef —— { id, revision } 比较并设置(CAS)围栏
GoalPhase —— 'active' | 'paused' | 'blocked' | 'complete'
GoalBlockReason — { code: string, message: string }(仅当 blocked 时出现)
GoalSnapshot —— objective、phase、blockedReason?、maxGoalRounds(= GoalRef + 这些)
GoalView —— GoalSnapshot + { roundsStarted, createdAt, updatedAt, activation }
GoalActivation — 'armed' | 'disarmed'(进程本地,永不持久化)
GoalOperation —— 'create' | 'edit' | 'pause' | 'resume' | 'complete' | 'block' | 'clear'GoalPhase 是持久阶段;GoalActivation 刻意独立且进程本地。roundsStarted 是已准入的最高续作轮;maxGoalRounds 是总上限。blockedReason.code 是由阻塞策略选定的稳定小写连字符分类(模型工具总用 model-reported);blockedReason.message 是给人类/模型的解释。
生命周期转换与权限
ctx.goals 只接受注册于其 id 下的精确活跃 Agent 实例。变更使用 GoalRef { id, revision } 比较并设置围栏,并拒绝陈旧 ref。每次变更都会追加一条持久化的 goal/change 会话事件,携带完整变更后快照(或修订版清除墓碑),因此目标状态从不依赖收件箱位置、认领、准入或丢弃。
转换及其执行人:
| 操作 | 效果 | 谁 |
|---|---|---|
create | 活跃的一版目标,已武装 | 模型工具 / /goal |
edit | 保留阶段、阻塞原因与激活;编辑已完成目标则创建全新的活跃目标 | 模型工具 / /goal |
pause | 已暂停 + 解除武装 | 模型工具 / /goal |
resume | 停止阶段 或 已解除武装的活跃 → 活跃 + 已武装,清除阻塞者,仅在余量够时 | 模型工具 / 人类授权 /goal |
complete | 已完成 + 解除武装 | 模型工具(轮次授权)/ 人类 |
block | 已阻塞 + 解除武装,记录码与解释 | 模型工具(轮次授权)/ /goal |
clear | 墓碑;保留历史 | /goal |
阻塞使用一个持久阶段,而非增殖生命周期状态。resume 是唯一的重新武装路径:会话恢复、fork 或驱动替换后,活跃目标会自动 disarmed;只有显式的人类授权 resume(通过 /goal 或模型工具)才能重新武装续作。disarm() 本身是仅生命周期例外——它移除进程本地的续作权限,不写修订、不发变更事件。
激活:armed 与 disarmed
激活从不持久化。全新缓存与每个 agent/session-start 边都会解除武装,哪怕重放发现一个活跃的持久阶段;续作驱动也会在卸载前或持久性不确定之后调用 disarm()。这正是激活不在持久重放中的原因:持久阶段说的是“此目标打开着”,但只有活跃、已武装的进程才有权准入下一轮。
目标轮次
目标轮(goal round)是为当前目标准入的一个续作循环。驱动把它具体化为一个目标来源的轮次(turn),其中可含零个或多个步骤。术语表做出两个关键澄清:
- 只有已准入、目标来源的
user/message才使roundsStarted前进; - 同一会话中无关的人类轮次不消耗目标轮上限。
goal-round 驱动
@deepseek-ai/dsh-goal-round-driver 是同会话续作驱动,无可调配置——maxGoalRounds 属于目标定义,而面向模型的阻塞阈值属于 dsh-tool-goal,因此驱动不复制二者。
轮契约(来自 packages/goal/goal-round-driver/src/index.ts):
- 当精确活跃 agent 空闲且持有活跃、已武装、有余量的目标时,它先检查点待决目标变更(等待
ctx.sessions.flush(),并在等待后复查修订与竞争输入); - 为当前
{ goalId, revision }预留roundsStarted + 1(陈旧预留不消耗轮号); - 排队一条
<goal_round>提示词,携带GoalMessageSource { kind: 'goal', goalId, revision, round }; agent/pre-step监听器在下游监听器前后都验证认领记录与当前目标;只有已进入的user/message才使roundsStarted前进。
保留的提示词命名 JSON 引用的目标与 round/maxGoalRounds,把当前工作区、工具结果与持久会话状态当作权威,并要求有证据才完成,且当仍有工作时告诉模型保持目标激活。经 agent/error 到达的冲刷失败会在另一轮开始前解除武装续作。若人类工作先进入收件箱,自动工作会让路直至 agent 空闲。在下一个空闲检查点,持有一个已被预留或已尝试工作、但其工作被取消的目标会被暂停,从而取消无法自动重启它。
面向模型的工具:dsh-tool-goal
@deepseek-ai/dsh-tool-goal 注册 get_goal、create_goal 与 update_goal:
get_goal()→ 当前目标或null,含 id/revision、持久阶段、已准入/上限轮次、阻塞原因与实时激活。create_goal(objective, max_goal_rounds?)—— 从直接顶层人类轮次创建一个目标;非人类轮次与子代理会在执行时被拒。update_goal(goal_id, revision, action, objective?, max_goal_rounds?, blocked_reason?)——edit/pause/resume/complete/blocked;blocked_reason仅对blocked必填,并以稳定码model-reported持久化。
权限。 执行要求精确活跃的 exec.agent,且 create/edit/pause/resume 另外要求运行时根 agent 当前轮次中有一条已接受的 { kind: 'user' } 消息或导航事件。{ kind: 'user' } 是宿主凭据:调用方省略来源时,Agent.followup() 与 steer() 会赋予它,因此插件/调度器/非人类生产者必须传递自己的来源,而非继承人类权限。Complete 与 blocked 也接受当前精确的目标轮(id + revision + round 都与折叠目标相等),但目标轮 blocked 调用会在 blockedAfterConsecutiveRounds(默认 3)前被机械拒绝。
一个自主目标轮成功上报 complete 或 blocked 时会调用 concludeTurn(),在该步骤之后停止物理轮次;直接人类变更从不会促成这种停止。配置:blockedAfterConsecutiveRounds(正整数)。这些工具把一条固定目标策略渲染进系统提示词。
/goal 人类命令
@deepseek-ai/dsh-command-goal 在 ctx.commands 上注册一个全局命令:
| 输入 | 结果 |
|---|---|
/goal | 显示目标、阶段、轮次计数/上限、激活、合法下级命令 |
/goal <目标> | 创建并武装一个目标 / 用全新 id 替换已完成目标 |
/goal edit <目标> | 不改阶段或激活地编辑目标 |
/goal pause | 暂停 + 解除武装 |
/goal resume | 在余量内恢复 / 重新武装 |
/goal clear | 清除指针,保留持久历史 + 墓碑 |
控制词仅在作为完整输入时大小写不敏感;其他任何非空后缀都是目标。由于命令平面没有模态编辑器,edit 接受内联替换。
目标在哪里持久化
会话日志是唯一持久权威。每次变更都追加 goal/change;清除追加修订版墓碑。严格重放只从这些事件派生生命周期状态,拒绝畸形形状、不连续修订、非法转换、非单调时间戳与非顺序的已准入轮次。goal/changed(scoped emit)在持久事件提交后触发。这里不涉及 @deepseek-ai/dsh-goal/checkpoint 风格的辅助——会话引用使用的检查点标记是另一个概念(dsh-compaction)。
重放、scoped 事件与错误码
严格重放纯粹从 goal/change 事件重算 FoldedGoal——即目标加 roundsStarted、createdAt、updatedAt 与最新 GoalRef。它拒绝畸形形状、不连续修订、非法生命周期转换、非单调时间戳与非顺序的已准入轮次;增量重放把游标停在首个损坏事件,因此坏记录会停止折叠而不污染内存。流程:
持久化 goal/change 事件 ──► 严格重放折叠
│ ─ 拒绝畸形 / 非法 / 非单调
▼
FoldedGoal ──► 实时 GoalView(加上激活、roundsStarted 计数)
│
▼
变更请求 ──► GoalRef { id, revision } CAS 围栏
└ 陈旧 ref 被拒(GOAL_STALE_REVISION)
│
▼
提交 goal/change ──► goal/changed(scoped emit)该服务暴露稳定的 GoalErrorCode 原因:GOAL_AGENT_NOT_LIVE、GOAL_NOT_FOUND、GOAL_ALREADY_EXISTS、GOAL_STALE_REVISION、GOAL_INVALID_OBJECTIVE、GOAL_INVALID_MAX_ROUNDS、GOAL_INVALID_BLOCK_REASON、GOAL_INVALID_EDIT、GOAL_INVALID_TRANSITION。disarm() 是唯一只变更进程本地权限的操作:它移除续作权限而不写 goal/change 或不推进修订,因此从不出现在重放中。
推荐组合
随附的 dsh 基底启用持久化目标栈 + /goal;ACP 自动化应用启用领域 + 模型工具而无需命令适配器;无 UI 的 agent-spine-demo 要求显式 goals: {},以免无头一次性调用方悄然从单物理轮次变成多轮操作。
- id: goal
name: '@deepseek-ai/dsh-goal'
- id: tool-goal
name: '@deepseek-ai/dsh-tool-goal'
- id: goal-round-driver
name: '@deepseek-ai/dsh-goal-round-driver'进一步阅读
- 上下文来源 — 目标轮提示词所在其中持久化消息。
- 上下文压缩(Compaction) — 累积的目标轮提示词最终如何被遮蔽。
- 交互:命令、提问与审批 —
/goal与目标工具所依赖的命令平面与工具权限。 packages/goal/goal/src/domain.ts与src/types.ts— 精确领域类型与goal/change载荷。docs/glossary.md— goal、goal round 与 goal activation 的定义。.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.md— 目标领域设计决策记录。