# Agent Note: Harness 层目标式执行

Status: implemented

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

## 问题

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

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

因此，本仓库需要位于轮次/步骤 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 → 全新子 agent 轮次 → 步骤**。一个 Ralph Round 创建一个子会话。父 transcript 和此前子 transcript 都不是种子上下文；共享工作区与一份有界结构化报告承载跨 Round 状态。

因此，「Round」是外层策略迭代，不是每个会话轮次的同义词。具体 `dsh-agent-loop` 仍是轮次/步骤引擎。同会话驱动器使用公开 agent 与会话事件；它对核心唯一的新增项是通用的取消前观察通知 `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 轮次中有一条人类直接发送的消息，并把自治 Round 权限收窄到带机器可路由原因代码的完成或阻塞报告。 |
| `@deepseek-ai/dsh-goal-round-driver` | `packages/goal/goal-round-driver/`，续行策略 | 在不导入具体 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? })`，验证全新结构化提供方与有界 `RalphRoundReport`，并返回 `complete`、`blocked` 或 `budget-limited`。 |

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

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

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

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

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

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

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

### 同会话续行

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

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

普通轮次完成后，只有目标仍活跃、已激活且低于上限时才会安排另一个 Round。取消会暂停。速率限制或配额耗尽以代码 `usage-limited` 阻塞；上限耗尽使用 `round-limit`；队列失败使用 `queue-failed`；轮次错误、max-token 停止、策略拒绝与未知终止结果使用各自对应的阻塞代码。独立组合的请求恢复插件可以在同一个轮次内重试暂时性提供方失败；目标驱动器绝不会在异常终止结果后凭空发起另一个 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 工作时，模型可以创建目标，并且可以从任何语言推断该意图。它不得把日常单轮次工作变成目标。代码要求当前实时根 agent 轮次中有一条人类直接发送的消息；语义解释仍是模型判断。自治目标 Round 可以为确切的当前 Goal Round 报告 `complete` 或 `blocked`，但不能编辑、暂停、恢复或替换人类目标。

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

### 全新 agent Ralph 执行

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

每个 Round 都使用显式 `WorkflowStartRequest.subagentProvider`，默认为 `spawn`。该提供方必须存在、支持结构化输出，并声明不继承父上下文。Ralph 还会把解析后的 Round 上限作为 `WorkflowStartRequest.maxTotalAgents` 传递；工作线程引擎会在发布工作前验证两项每次运行策略，因此提供方配置错误或低于所请求 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) 进一步验证了「前一轮次后启动另一轮次的目标」和定时 `/loop` 之间的区别。Claude Code 还会在每个轮次后使用独立小模型评估器。本实现采用策略区分，但有意不复制该评估器：评估器输入、工具访问、确定性检查、提供方选择、隔离与权限需要单独设计的插件约定，而不是隐式自我认证层。

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

### 验证

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

## 考虑过的替代方案

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

## 后果

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

## 已知限制与暂缓事项

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