# Agent Note: Harness 层目标式执行

Status: implemented

[English](2026-07-16-harness-level-loop.md) | 中文

## 问题

具体 agent loop（智能体循环）只拥有一个轮次：它排空已接纳输入，执行一个或多个模型与工具步骤，然后停止。大型目标通常需要一项外层策略来开始另一个轮次、保留进度、在达到预算上限时停止，并让人类能够理解其状态。定时提示词、同会话续行和全新 agent Ralph 尝试都会重复工作，但它们并不共享相同的状态、权限、记忆或生命周期。

若把每种重复动作都称为一个通用「loop」，就会掩盖这些差异。同会话工作必须在现有 transcript（文本记录）中持久化人类目标，同时保留对话上下文。Ralph 工作必须有意丢弃对话上下文，只使用工作区和一份有界交接。面向人类的状态不能暗示重新打开会话就会静默授权更多工作。完成与阻塞声明也需要显式信任边界，而不能被偷渡进调度器抽象。

因此，本仓库需要位于 Turn/Step loop 之上的目标式执行，但不需要一个把持久化、评估、预算、调度、交接、后台任务和 UI 组合在一起的推测性通用 loop 服务。

## 决策

构建在现有 seam 之上的两项显式插件策略：

1. **同会话目标**在当前会话中保留一个持久目标，并且只在实时激活态已激活时接纳带目标归属的续行轮次。
2. **全新 agent Ralph 运行**执行一个固定前台工作流，其中每个 Round 都生成一个不带对话种子的全新结构化子 agent。

系统中没有 `packages/loop/` 包族、`LoopDriver`、`LoopId`、通用 `StopCondition` 或面向模型的通用 `loop` 工具。两项策略共享本仓库普通的 agent、会话、工具、工作流、subagent 与 UI 扩展点，但不会假装一种生命周期可以同时适配两者。

### 词汇与策略边界

同会话层级是 **Goal → Goal Round → 轮次 → 步骤**。一个 Goal Round 是为当前目标接纳的一次续行周期，并实体化为一个带目标来源的轮次。同一会话中的人类轮次或无关轮次不会消耗 Goal Round 上限，而一个轮次仍可包含多个模型/工具步骤。

全新 agent 层级是 **Ralph Run → Ralph Round → fresh child 轮次 → 步骤**。一个 Ralph Round 创建一个子会话。父 transcript 和此前子 transcript 都不是种子上下文；共享工作区与一份有界结构化报告承载跨 Round 状态。

因此，“Round”是外层策略迭代，不是每个会话 Turn 的同义词。具体 `dsh-agent-loop` 仍是 Turn/Step 引擎。同会话驱动器使用公开 agent 与 session 事件；它对核心唯一的新增项是通用的取消前观察通知 `agent/cancel-requested`，任何需要安全收敛取消的生命周期策略都可以使用它。

基于时间的 `/loop` 或定时执行是第三种策略，本决策不实现它。它应归属于调度器，而不是任一目标包族。

### 包拓扑与所属动词

| 包 | 仓库类别 | 所属结构与动词 |
|---|---|---|
| `@deepseek-ai/dsh-goal` | `packages/goal/goal/`，领域服务 | 拥有 `GoalId`、比较并交换 `GoalRef`、`GoalSnapshot`、四状态 `GoalPhase`、结构化 `GoalBlockReason`、进程本地 `GoalActivation`、重放折叠，以及 `get`、`create`、`edit`、`pause`、`resume`、`complete`、`block`、`clear` 与 `disarm` 动词。 |
| `@deepseek-ai/dsh-tool-goal` | `packages/goal/tool-goal/`，面向模型消费方 | 注册互斥的 `get_goal`、`create_goal` 与 `update_goal`；要求实时根 agent Turn 中有一条人类直接发送的消息，并把自治 Round 权限收窄到带机器可路由原因代码的完成或阻塞报告。 |
| `@deepseek-ai/dsh-goal-session` | `packages/goal/goal-session/`，续行策略 | 在不导入具体 loop 的情况下，预留、设围栏、接纳、归属、结算、取消并排空同会话 Goal Round，直至完全停稳。 |
| `@deepseek-ai/dsh-commands` | `packages/interaction/commands/`，UI 注册表 | 拥有面向人类专用命令的 `CommandDefinition`、发现、作用域注册、直接分发、`CommandResult` 与请求取消。 |
| `@deepseek-ai/dsh-command-goal` | `packages/goal/command-goal/`，人类命令生产方 | 为 TUI 注册构建在目标领域之上的 `/goal` 状态、创建、编辑、暂停、恢复与清除。 |
| `@deepseek-ai/dsh-tool-ralph` | `packages/workflow/tool-ralph/`，固定工作流消费方 | 注册 `ralph({ objective, maxRounds? })`，验证全新结构化 provider 与有界 `RalphRoundReport`，并返回 `complete`、`blocked` 或 `budget-limited`。 |

详细约定分别由[目标领域](2026-07-19-persisted-same-session-goal-domain.md)、[目标自有事件](../architecture/2026-07-31-goal-owned-durable-events.md)、[模型目标工具](2026-07-19-model-facing-goal-tools.md)、[目标回合驱动器](2026-07-19-same-session-goal-round-driver.md)、[命令注册表](2026-07-19-plugin-command-registration.md)、[人类目标命令](2026-07-19-human-goal-command.md)与 [Ralph 工作流工具](2026-07-19-fresh-agent-ralph-workflow-tool.md) Agent Note 拥有。

### 持久目标状态与实时权限

一个会话至多有一个当前目标。每次变更都通过持久的 `goal/change` 事件提交；该事件携带带版本的完整快照或带修订号的清除墓碑，收件箱状态不参与其中。会话日志是唯一持久真源，因此普通持久化、恢复与 `SessionStore.fork()` 会携带目标，无需第二个数据库或人为取消记录。

持久阶段只有 `active`、`paused`、`blocked` 与 `complete`。阻塞目标必须携带 `GoalBlockReason`，其中包含稳定的小写 kebab-case `code` 与非空的人类可读 `message`；用量限制、Round 耗尽、模型失败与策略拒绝都是原因代码，而不是额外生命周期阶段。独立激活态是 `armed` 或 `disarmed`，且永不持久化。创建与显式恢复会激活目标；停止转换、会话启动、fork 回放、驱动器替换和驱动器拆卸都会让目标保持未激活。

这种分离让会话恢复可观察且符合直觉。重新打开会话绝不会自行开始目标工作。随后的人类提示词，例如“继续”、“恢复目标”或任何语言中的等价请求，会给运行时根 agent 的模型一个新 Turn；模型可在其中读取目标并调用 `update_goal(..., action: 'resume')`。`/goal resume` 是直接人类命令路径。运行时认证请求来自实时直接人类 Turn；提示策略让模型解释措辞在语义上是否授权创建或恢复。

fork 会话会继承持久目标前缀，因为这是自然的重放结果。fork 从未激活状态开始，因此继承不等于执行权限，历史中也不会插入合成目标取消。

`defaultMaxGoalRounds` 可配置且默认为 `256`。该上限只计算已接纳的 Goal Round。`blockedAfterConsecutiveRounds` 在模型工具策略中单独配置且默认为 `3`；它只是在自治 Round 报告重复阻塞前的机械下限，不是对语义相同性的评估器。

### 同会话续行

Goal Round 驱动器为每个特定的实时 agent 至多拥有一个待定预留。只有目标处于活跃且已激活状态、agent 空闲、不存在竞争性人类工作、最新变更已经通过持久性检查点、确切的目标 id／修订号／Round 仍匹配，并且下游 pre-step 策略接受时，它才会接纳预留。其 `agent/pre-step` 围栏会在下游监听器前后检查这些事实，防止编辑、暂停、人类消息或卸载竞争接纳过时工作。

只有已接纳、Round 为正数且带目标来源的 `user/message` 会计入一个 Round。陈旧预留会结束一个阻塞的零步骤轮次，不会消耗上限。并发目标修订会胜过旧 Round 的结算。

普通 Turn 完成后，只有目标仍活跃、已激活且低于上限时才会安排另一个 Round。取消会暂停。速率限制或配额耗尽以代码 `usage-limited` 阻塞；上限耗尽使用 `round-limit`；队列失败使用 `queue-failed`；Turn 错误、max-token 停止、策略拒绝与未知终止结果使用各自对应的阻塞代码。独立组合的请求恢复插件可以在同一个 Turn 内重试暂时性 provider 失败；目标驱动器绝不会在异常终止结果后凭空发起另一个 Round。人类随后可以通过普通语言或 `/goal resume` 授权恢复。

### 人类与模型交互

人类 UX 遵循 [OpenAI Codex 在提交 `678157a` 时的公开 TUI 分发器](https://github.com/openai/codex/blob/678157acaa819d5510adfe359abb5d0392cfe461/codex-rs/tui/src/chatwidget/slash_dispatch.rs#L750-L805)中的紧凑形态：`/goal` 显示状态，`/goal <objective>` 创建目标，而 `edit`、`pause`、`resume` 或 `clear` 执行直接生命周期操作。该提交永久链接让研究所得语法在 Codex 演进时仍可验证。状态包含持久阶段、已接纳/上限 Round 数以及实时已激活/未激活状态。直接状态与命令输出不会进入模型历史；已接受领域变更仍可重建，因为目标服务会记录它们。

模型只接收 `get_goal`、`create_goal` 和 `update_goal`。当直接人类请求清楚要求大量多 Round 工作时，模型可以创建目标，并且可以从任何语言推断该意图。它不得把日常单 Turn 工作变成目标。代码要求当前实时根 agent Turn 中有一条人类直接发送的消息；语义解释仍是模型判断。自治目标 Round 可以为确切的当前 Goal Round 报告 `complete` 或 `blocked`，但不能编辑、暂停、恢复或替换人类目标。

TUI 默认挂载共享命令注册表和完整目标栈，并通过一个生产方暴露 `/goal`。ACP（Agent Client Protocol）挂载目标领域、模型工具和同会话驱动器，但有意省略人类命令平面。每条有效已注册命令都能被每个已组合的命令适配器发现和调用；若插件与某应用不兼容，该应用组合会省略其命令生产方，而不是依赖注册表层面的表面掩码。无 UI 的 agent 主干要求显式选择加入，以免单次调用方静默变成多 Round 操作。无头 CLI（命令行界面）与 JSON-RPC 运行入口不消费命令平面；挂载目标栈后，普通人类文本仍可授权模型目标工具。

### 全新 agent Ralph 执行

Ralph 是位于自有插件中的一等模型工具，展示了复杂固定执行策略可以在没有新 loop 核心的情况下组合完成。该插件拥有构建在 `ctx.workflows` 与 `ctx.subagents` 之上的固定工作流脚本；它不会创建会话目标状态，也不会为 `dsh-agent-loop` 增加分支。

每个 Round 都使用显式 `WorkflowStartRequest.subagentProvider`，默认为 `spawn`。该提供方必须存在、支持结构化输出，并声明不继承父上下文。Ralph 还会把解析后的 Round 上限作为 `WorkflowStartRequest.maxTotalAgents` 传递；工作线程引擎会在发布工作前验证两项每次运行策略，因此 provider 配置错误或低于所请求 Ralph 规模的引擎上限会在运行创建前失败。子 agent 继承 cwd 与谱系，但只接收不可变目标、当前 Round/上限、以工作区为权威的指令和上一份规范化报告。

报告包含状态、摘要、证据、下一步与阻塞文本。固定脚本内部和消费方边界都会验证状态专用不变量与序列化大小。`maxRounds` 可配置，默认为 `256`，并作为调用覆盖值的上限。`maxHandoffChars` 默认为 `16384`；过大报告会失败，而不会被静默截断。`maxResultChars` 单独默认为 `16384`，并限制面向父级的完整成功文本，包括外层文本与截断标记。

普通子 agent 失败会结束运行且不重试。固定脚本会报告失败 Round，并在存在时带回上一份成功交接；工具会把该状态作为错误返回，而不会误判为畸形报告或预算耗尽。致命工作流基础设施错误可能在脚本返回该状态前结算；更丰富的原因传输与重试策略均予以延期。

该工具位于前台且只存在于进程内。父工具调用等待终止结果，把取消传播到工作线程引擎，并等待 `run.dispose()`，因此返回前子工作已完全停稳。模型只看到一次调用，以及一份有界成功终止结果或一个错误；完成与阻塞的外层文本会明确说明结果由工作者报告，而不会呈现为独立认证。中间子 agent 对话不会进入父 transcript。

### 外部设计谱系

Codex 提供了这里采用的最小可观察目标 UX：一个附着于聊天的持久目标，以及设置、查看、编辑、暂停、恢复与清除控制。本实现采用这种可发现性，但使用本仓库的事件溯源目标记录、插件作用域与运行时权限检查。

当前 [Claude Code goals](https://code.claude.com/docs/en/goal) 进一步验证了“前一 Turn 后启动另一 Turn 的目标”和定时 `/loop` 之间的区别。Claude Code 还会在每个 Turn 后使用独立小模型评估器。本实现采用策略区分，但有意不复制该评估器：评估器输入、工具访问、确定性检查、provider 选择、隔离与权限需要单独设计的插件约定，而不是隐式自我认证层。

外部产品只是比较对象，不是兼容目标。本地源码研究帮助确定边界，而交付接口遵循本仓库“一切皆插件”、模型可见内容均记入日志、显式解析默认值与完全停稳后拆卸规则。

### 验证

六份所属 Agent Note 记录了单元、集成、进程、快照、取消、重放与构建后运行时覆盖率。该栈验证严格目标记录折叠、比较并交换竞争、会话 fork 继承、恢复后未激活、自然语言直接人类权限、可配置上限与阻塞阈值、确切的 Goal Round 归属、适配器范围的命令发现与 transcript 隔离。已发布的无密钥快照覆盖通过无头应用创建/检查模型目标、通过 ACP 执行多 Round 同会话生命周期与取消，以及通过无头应用执行两个真实 Ralph Round；聚焦的命令测试固定了无需模型 Turn 的直接 `/goal` 状态。Ralph 快照会启动工作线程引擎、spawn provider、结构化输出运行时与 agent loop，随后检查互不相同且无种子的子日志和精确的单向有界交接，同时固定父级事件流。聚焦的真实栈测试还覆盖完成、阻塞与 Round 上限结果、畸形及过大报告、保留上一份有效交接的普通子 agent 失败、单个阶段事件，以及取消后子 agent 完全停稳。包源码继续受仓库逐文件 100% 覆盖率门禁约束，构建后二进制测试覆盖已安装产物解析。实现经验已记录进根测试策略：每项非平凡的模型或人类可见变更都必须在同一 PR（Pull Request）中携带真实示例无密钥快照，而不能依赖仅包级或仅 mock fixture（测试前置数据）的覆盖率。

## 考虑过的替代方案

- **实现原始通用 loop 能力**——不予采纳，因为 `Evaluator`、`BudgetPolicy`、`RoundHandoff`、`GoalReflector`、后台任务所有权、持久化与调度并不构成一项一致的必选抽象。在出现首个具体消费者前全部构建，会产生宽泛推测性表面，并重复现有 session、workflow、subagent 与 task 机制。
- **只实现同会话目标**——不予采纳，因为全新上下文迭代在实质上不同，也是插件架构的重要示范。Ralph 应作为带显式上下文重置的固定工作流消费者。
- **把 Ralph 放进目标回合驱动器**——不予采纳，因为同会话目标有意保留一段对话，而 Ralph 有意移除对话。合并两者会让激活、重放、交接与 UI 状态含糊不清。
- **把 fork 当成全新 Ralph 子 agent**——不予采纳，因为 fork 会携带对话前缀。全新子 agent 加工作区状态与一份显式报告更容易限制和重放，并且无需合成取消记录。
- **把 Claude Code 评估器复制进首个目标实现**——不予采纳，因为只读取转录的模型评估器是一项有用策略，但不是普遍可信的完成证书。系统必须仍能支持确定性评估与隔离，因此评估器延期到其权限与提供方约定完成设计之后。
- **会话恢复后自动续行**——不予采纳，因为打开会话是观察行为，不是花费资源的权限。系统恢复持久状态，而激活态等待新的人类提示词。
- **通过模型路由 `/goal`**——不予采纳，因为状态与显式生命周期控制应是确定性且零 token 的 UI 操作；普通自然语言提示词仍是语义模型路径。
- **为具体 agent loop 增加目标或 Ralph 模式**——不予采纳，因为公开队列、提示词、会话、取消、工作流与 subagent seam 已经支持两项策略。通用 cancel-requested 观察是唯一核心协调新增项。

## 后果

- 目标式执行在没有单个过载「loop」对象的情况下交付：同会话续行与全新 agent 迭代拥有显式、可独立测试的约定。
- 持久目标历史可以重放和 fork，而进程本地激活态会防止恢复时意外开始工作。
- 人类获得小型 Codex 形态 UX；模型获得一组紧凑工具，其中的修改操作要求当前实时根 agent Turn 中有一条人类直接发送的消息；部署可以独立移除任一能力。
- Ralph 展示了非平凡固定策略可以完全作为现有 workflow 与 subagent 原语之上的插件实现。
- Round 上限默认宽裕，但仍由部署控制。它限制迭代次数，不限制 token、价格、耗时或外部副作用。
- 原始提案中的评估器、预算、反思器、后台任务、CLI 与通用 loop-session 架构有意不进入已实现的公开 API。

## 已知限制与暂缓事项

- **独立评估**——同会话完成/阻塞和 Ralph 终止状态都是模型或工作者声明。独立评估器、评估器驱动反馈 Round、完成证书、确定性检查器、对抗式 verifier 与 criteria/executor/isolation 约定均予以延期。
- **聚合预算**——`maxGoalRounds` 与 Ralph `maxRounds` 是唯一聚合工作量限制。token、货币、耗时、provider 用量与逐 Round 价格准入策略均不存在。
- **没有持久自治运行器**——同会话目标事实会持久化，但激活与调度只存在于进程内，并且有意在恢复后等待人类输入。Ralph 位于前台，进程丢失后无法恢复。后台收集、重启恢复与无人值守常驻执行均予以延期。
- **没有时间调度器**——间隔 `/loop`、cron、主动维护以及云端或桌面调度不在本决策范围内。
- **没有通用 loop 日志或执行世界回退**——会话重放会重建目标历史，而不会恢复此前文件、进程、环境、凭据或外部副作用。Ralph 把当前工作区作为权威，并且没有跨运行日志。
- **没有目标反思器**——concern 事件、自动无进展启发式、由独立反思器执行的目标修订、卡住模式检测与 `loop_split` 均未实现。人类可以直接编辑、暂停、清除或恢复目标。
- **Ralph 策略仍然狭窄**——一个 Round 创建一个全新子 agent；Round 内扇出、评估器/工作者角色分离、动态 provider/模型选择与从结构上禁止递归调用 Ralph 工具都需要独立策略表面。提示词指导不是强制执行。
- **Ralph 不会重试失败的子 agent**——普通失败会保留失败 Round 与上一份有效交接，而致命工作流基础设施错误可能在该状态可用前结束。重试次数、退避与更丰富的失败传输需要独立的策略与边界设计。
- **可移植 UI 仍较朴素**——TUI 渲染纯文本目标状态和通用 Ralph 卡片。ACP 只承载已提交的助手文本；系统没有持续状态组件、可重连命令输出、模态目标编辑器，ACP、无头 CLI 与 JSON-RPC 也没有命令平面。
