# Agent Note: 面向模型的同会话目标工具

Status: implemented

[English](2026-07-19-model-facing-goal-tools.md) | 中文

## 问题

持久目标领域有意把生命周期动词提供给插件，而不直接提供给模型。模型仍然需要一个小型控制 API，用于发现当前目标、根据人类意图创建目标并改变其生命周期。仅靠提示词指导无法确定是谁授权了一次变更：subagent、注入的插件消息、陈旧的模型轮次或恢复后的会话都可能产生相同的工具参数。

该工具 API 还需要保持持久状态与实时执行权限之间的分离。恢复或 fork 后的会话可以回放活跃目标，但初始处于未激活状态；后续人类提出「继续」之类的请求时，模型应能重新激活目标，而无需用户使用字面命令。相反，已接纳的自主 Goal Round 必须能够报告完成或持续阻塞，却不能因此获得编辑、暂停、恢复或替换人类目标的权限。

## 决策

位于 `packages/goal/tool-goal/` 的 `@deepseek-ai/dsh-tool-goal` 在 `ctx.goals` 之上贡献三个独占工具和一个系统提示词策略段：`get_goal`、`create_goal` 与 `update_goal`。工具名称和读取—创建—更新形态遵循 Codex 的紧凑目标工具表面，而权限规则使用本仓库公共的 agent（智能体）、会话、工具与目标服务。

### 工具与模型约定

`get_goal()` 返回当前目标或 `null`。非空结果包含用于比较并交换的 id 与修订号、目标描述、持久阶段、已接纳和最大 Goal Round 数、可能存在的阻塞原因，以及进程本地激活态观察。`create_goal(objective, max_goal_rounds?)` 创建一个长时间运行的同会话目标。`update_goal(goal_id, revision, action, objective?, max_goal_rounds?, blocked_reason?)` 支持 `edit`、`pause`、`resume`、`complete` 和 `blocked`；替换字段仅对 `edit` 有效，非空的 `blocked_reason` 仅在 `blocked` 时必填，并以稳定代码 `model-reported` 持久化。执行器把值恰好为空字符串的可选字段和值为 0 的 `max_goal_rounds` 视为严格 schema 占位值：这些值等同于省略；编辑时仍必须提供至少一个有实际意义的替换字段；所有非占位值仍受对应操作的限制。

提示词告诉模型：它可以从任何措辞或语言的直接人类请求中推断目标意图，但不应把常规单轮工作转换为目标。更新前必须读取当前目标，并复制准确的 id 和修订号。对于恢复或 fork 后处于活跃但未激活状态的目标，人类在语义上要求继续即可成为执行 `resume` 的依据。只有目标已经实现时才能标记完成，困难或不确定性本身不构成阻塞；阻塞报告必须说明具体条件。

三个工具都采用独占执行，使模型排序的批次可以观察此前变更及其新修订号。结果为紧凑 JSON。UI 展示是参数的纯函数，使用通用读取或变更卡片；变更卡片选择输入时，先取有实际意义的操作值，再取目标 id，因此允许的占位值不会使卡片输入留空。激活态仅作为实时观察返回，绝不会写入回放状态。

自主 Goal Round 成功报告完成或阻塞后，其工具结果会附带一条收尾指令，模型仍会在轮次经由常规无工具调用停止路径结束前向用户发言；原先在结果处终结轮次的做法已被 [Goal Round 收尾决策](../bug-fix/2026-08-02-goal-round-wrapup-message.zh.md)取代。直接人类发起的变更不会收到指令：agent 可以确认该变更，并且并发的人类 steering（中途引导）仍可参与普通的停止检查。

### 执行权限

每次调用都要求存在 `exec.agent`，且它必须是 `AgentRegistry` 中完全相同的运行中对象、当前继承的驱动发起者，并处于开放轮次内。这些检查在执行时进行，不能通过提示词注入或手写工具参数绕过。

创建、编辑、暂停与恢复还要求运行时根 agent 的当前轮次已经接纳一条用户消息或用户 steering 事件。根所有权来自实时 agent 图，而非持久的 fork 祖先关系：恢复后的 fork 会话可以接收新的直接人类权限，实时子级则仍是 subagent，不能改变这些状态。用户来源是宿主的证明：每个 `Agent.followup()` 或 `steer()` 输入都必须显式提供来源，因此宿主把直接人类内容标为 `{ kind: 'user' }`，非人类生产者则在来源字段中注明自己。运行时证明当前轮次包含人类直接发送的消息，而不判断人类措辞在语义上是否足以创建或恢复目标；该解释仍由模型完成。

完成与阻塞既接受直接人类权限，也接受准确的当前 Goal Round。Goal Round 权限要求存在一条来源为目标的 `user/message`，其中目标 id、修订号和 Round 都与折叠后的当前目标相等。它只授予这两种终止报告权限。直接人类权限可以立即停止目标。

### 阻塞阈值

`blockedAfterConsecutiveRounds` 是经过校验的正安全整数配置，默认值为 `3`。自主 Goal Round 调用 `blocked` 时，插件会机械地要求至少已经接纳该数量的 Round 并提供非空说明；配置值也会出现在模型指导中。运行时无法判断这些 Round 是否遇到了语义上相同的阻塞条件，因此语义等价性仍由模型判断。该计数特意与目标的宽裕继续执行上限分离。

## 测试

单元测试固定注册与 dispose（资源释放）、独占调度、生成的提示词策略、可安全处理占位值的通用展示、非英语轮次中的直接人类创建、精确/陈旧/非运行中智能体与驱动检查、实时子智能体拒绝、恢复后 fork 根的权限、steering、发起者不匹配、读取/创建/部分字段编辑/暂停/恢复行为（包括严格 schema 占位值）、条件式阻塞说明、会话启动边沿后的重新激活、权限检查先于条件参数检查的失败行为、准确 Goal Round 的完成、仅自主 Round 触发终止、已配置的阻塞阈值，以及人类立即阻塞。无密钥回放快照把目标领域和工具挂载到真实的 headless 单次运行应用中，通过随附循环与持久化栈驱动一次携带严格 schema 占位值的 `update_goal` 探测，以及对 `create_goal` 和 `get_goal` 的调用，固定 stream-json transcript（文本记录），并检查外部持久化的目标变更。这里有意不把 echo-agent fixture（测试前置数据）当作应用 UX 的替代品。

## 考虑过的替代方案

- **依赖提示词指令实施权限**——不予采纳，因为文本可以指导模型判断，却不能认证实时调用者、轮次或来源事件。
- **把每个目标服务动词分别暴露为工具**——不予采纳，因为紧凑的读取/创建/更新 API 可以降低 schema 成本，并保持统一的比较并交换行为。
- **要求精确命令短语**——不予采纳，因为自然语言意图（包括英语以外的语言）应由模型解释；执行权限取决于当前轮次中人类直接发送的消息，而不是拼写。
- **根据持久的根或 fork 元数据授权**——不予采纳，因为作为顶层会话独立恢复的 fork 应接受新的人类权限，而当前仍受所有权约束的 subagent 则不应接受。
- **允许自主 Round 编辑或恢复目标**——不予采纳，因为继续执行权限比重新定义或重启人类目标的权限更窄。
- **把阻塞阈值当作评估器**——不予采纳，因为事件计数无法证明障碍在语义上未改变或确实不可继续。
- **拒绝所有已提供的特定操作字段**——不予采纳，因为采用严格 schema 的提供方可能为每个可选字段序列化零值占位符；只有有实际意义的字段值才能表示与指定操作相冲突的另一项操作。

## 后果

- 模型获得稳定而紧凑的生命周期 API，无需直接访问目标服务。
- 改变状态的调用要求实时运行时根 agent、当前轮次中人类直接发送的消息，以及持久比较并交换引用。
- 人类可以通过普通自然语言请求创建和重新激活目标，而恢复后的会话在收到此类输入前保持静止。
- Goal Round 可以完成或报告重复阻塞，但不能自行扩大任务权限。
- 部署策略选择阻塞下限；同一个解析后的值同时控制执行与提示词指导。
- 系统可兼容采用严格 schema 的提供方所填入的占位值，同时不会放行有实际意义的跨操作更新。

## 已知限制与暂缓事项

- 是否属于重大目标、是否要求继续、目标是否完成以及阻塞条件是否相同，仍由模型进行语义分类。独立评估器或完成证书予以延期。
- 这些工具会改变目标状态，但不调度 Goal Round、不分类异常驱动停止，也不取消活跃轮次；这些行为由同会话驱动器负责。
- 除非另行挂载的继续执行驱动器接纳了目标来源的用户轮次，否则 Goal Round 权限路径处于休眠状态；本工具包本身不会制造这种权限。
- 面向人类的斜杠命令发现与渲染由独立的 [`dsh-command-goal`](../../../../packages/goal/command-goal/README.zh.md) 插件负责。
- 若部署没有同时设定两个注册项的作用域，某个作用域可能隐藏工具注册，却保留独立注册的提示词段。
