跳到主要内容

Subagent 采集协议 v1

本协议的目标是使不同 Harness 使用同一套调用、参与者、关系和完整性规则。Roller 记录执行事实,本协议不定义训练损失或奖励。

开启​

roller collect ... --capture-calls 目前支持七个 Harness:Claude Code、 OpenCode、Codex、Hermes、OpenClaw、Pi 和 DeepSeek。原有的 --claude-capture-calls 仍保留。该选项要求显式指定 API base、使用 host 网络并开启 wire trace。 默认不开启;OpenCode 仅在开启此选项时允许原生 task 子任务工具。 采集模式写入 run 配置,Resume 时不能混用开启前后采集的 sample。

三个对象​

对象协议含义
Participantmanifest 的 participants一个原生执行者;一个执行者可以有多次调用
Callroller.call/v1一次实际 HTTP 模型请求及响应
Relationroller.relation/v1有证据支持的启动、结果交付和上下文关联

participant_id 是不透明的引用标识。消费者通过 role 判断 root/child, 不得通过标识前缀解释角色。参与者身份在一次采集 attempt 内有效;跨 sample 或 attempt 引用时必须附带 sample/manifest 路径。call_id 和新生成的 relation_id 包含采集实例的随机作用域,以避免不同 sample 或不同重试之间的编号冲突。

Call 的 context 保留实际发送的完整请求体;output 是重组后的 provider 响应。protocol 区分 anthropic-messages、openai-chat-completions 以及 openai-responses,协议按实际 HTTP 请求端点识别,各协议保持各自的消息格式。原始响应文件同样保留,请求不依赖原生日志重建。 Roller 不保存 HTTP 认证头,也不使用父 Agent 的配置覆盖子 Agent 的模型和工具配置。

状态回答的问题
execution_status调用/任务是否执行完成?
response_capture_status响应是否完整落盘?
identity_status能否确定属于哪个参与者?
capture_status当前记录是否完整?
export_status这次调用是否能进入逐调用导出?

仅当模型响应正常结束、输入响应完整、身份明确、模型匹配且没有采集错误时, Call 才可导出。Chat Completions 仅有 tool_calls 结束标记、没有实际工具调用 ID/name 的异常响应也会标记为 partial 并禁止导出。未归属、失败、中断的请求仍保留在 attempt 的调用和原始 证据中。整体采集状态可以是 partial,同时仍包含可导出的完整调用。

关系语义​

type需要的证据
spawn原生工具调用及子任务身份
result_delivered父任务收到了子任务的最终工具结果
child_completed / child_failed原生子任务完成/失败证据
result_in_context后续父模型的实际输入里存在相同工具调用 ID 的结果
launch_acknowledged后台子任务启动回执
launch_ack_in_context启动回执进入父模型实际输入

每条关系保留原生文件及事件/part ID 引用。父模型调用通过响应 message ID 或其输出中的工具调用 ID 关联;无法唯一关联时标记为 unknown。OpenClaw 的 Responses SDK 使用 call_id|item_id 作为原生工具 ID;关系保留原生 ID,并用 model_tool_ref 同时核对 HTTP 输出中的 call_id 和 item_id,不能只匹配其中一个。结果进入多个 后续调用时,分别记录关联。后台启动回执不得视为任务完成。 通知方式的结果还必须绑定原生 announcement run ID(OpenClaw)或后续响应 ID (Claude Code),并核对实际请求中的通知文本;仅出现相同文本不足以证明接收。 Call 可选的 native_identity.run_id 保存本地原生调用标识;关联头在转发前移除。

公共层与适配层​

适配层只返回 capture_protocol.NativeCapture:参与者、身份映射、原生关系、 可观测输出 ID/数量和能力声明。公共 finalize_capture 负责关联 HTTP 调用、 判断状态、生成上下文关系、写文件和 manifest。model_protocols 负责 Anthropic/Chat Completions/Responses 响应解码和工具引用提取。

Harness身份证据原生输出覆盖检查
Claude Codeprovider 响应 ID 与原生日志 message ID 匹配逐 message ID
OpenCodechat.headers 提供的 session ID 与原生日志匹配各 session 完成的 assistant 数量
Codex每次 HTTP 请求原生 thread-id 与 rollout session_meta 匹配token_usage 更新数量;不是逐响应 ID
Hermes执行实例自己的 session ID 与原生日志匹配provider 响应 ID
OpenClaw原生 stream wrapper 的 session ID 与 session 文件匹配stream context 数量
PiSDK session ID完成的 assistant 数量
DeepSeekrunner session ID完成的 assistant 数量

OpenCode 的关联头仅在本地代理中使用,代理在向模型服务转发前将其移除。各请求携带 自己的 session ID,因此并行请求不依赖“当前 session”全局变量。Codex 的原生 thread-id 仍转发给模型服务,仅移除 Roller 新增的 x-roller-* 身份头。原生数量 检查比逐 ID 检查弱,manifest 的 capabilities.native_output_coverage 明确 记录这一差别;Roller 不根据时间、文本或请求次序推测身份。

主 ACTF/SFT 只转换主参与者的消息和请求配置;子任务内部消息留在自己的 Call 中。calls.jsonl 是逐调用的协议数据,不能直接用作主轨迹 SFT。

旧版 Claude Code sample 可能将子代理步骤混入主 ACTF/SFT。新版 Resume 会在执行模型前 识别并拒绝复用这类 sample,并给出明确错误,以避免静默重新执行或沿用错误的主 SFT。 可使用新的 run 目录重新采集;若需保留旧执行结果,应先在原始轨迹的副本上 离线重新投影并校验,不得直接修改旧 SFT 后继续 Resume。

文件与边界​

每个 capture/attempt-NNNN/ 默认仅包含 manifest.json、calls.jsonl 和 relations.jsonl。--capture-debug on-error(默认)仅在执行失败、采集不完整、 模型调用失败或子任务失败时保留 raw/;full 始终保留,off 始终清理。原生日志、原始响应、采集事件及来源映射集中保存在 raw/;事件的 body_ref 引用 calls.jsonl 中对应调用的 context,不重复保存 请求正文。参与者通过 manifest 的 call_ids 和 Call 的 participant_id 关联, 不再另存 participants/*/calls.jsonl 副本。

manifest 使用 roller.capture/v1,新布局以 storage_layout: compact/v2 标识, 并记录 debug_retention 与 raw_retained。可选 lifecycle 包含 status (complete/pending/unknown/unsupported)和 pending_participant_ids:子任务执行结束 与结果交付分别确认,状态为未知或未完成时,不得将整体标记为 complete。校验兼容没有 lifecycle 的旧记录。 完整性检查在清理前完成; 清理后调用的 response_path 为 null,关系证据的 source 标明原始来源但 retained=false,以避免留下失效的文件引用。采集状态不因调试材料清理而降级。 精简模式下,Resume 校验正式文件哈希、调用归属和关系引用,无法重新解析原始响应。 读取和完整性校验兼容旧布局及 compact/v1;采集关闭前仍使用工作文件,发布时整理为新布局。 可使用 PYTHONPATH=src python scripts/view_capture.py /path/to/attempt-0001 查看按代理展开的调用树,该脚本不生成额外的数据副本。 Roller 在关闭 recorder 后发布文件哈希;文件缺失、被修改或引用错误将阻止 Resume 复用。即使关闭辅助日志保留,也不会清理 capture 正式证据。

七个 Harness 的执行边界​

  • Claude Code:固定版本 -p 原生循环会继续处理后台完成通知;Roller 不追加用户消息驱动执行。 通过通知的 task/tool ID 和后续响应 ID 确认完成与结果进入上下文。自动通知在主 ACTF/SFT 中仍是 user 输入,保留实际请求中的系统通知包装,不视为真人发言或子代理 assistant 输出。 已验证一次 root → child → grandchild 前台委派;嵌套返回缺失 toolUseResult 时, 必须核对原生元数据 ID、启动事件 UUID 与已采集的 child 身份。恢复同一 child 的新委派 按新工具 ID 判断是否结束,不能复用此前完成状态。
  • OpenCode:已有原生子任务适配;固定 bundled 版本的 task schema 没有 background 参数。 不存在的后台调用场景不计为通过。当前原生 subagent_depth 默认为 1; 子代理可以看到 task,但创建孙代理会被原生深度限制拒绝。被拒绝的轨迹予以保留,不虚构孙代理身份。 若其他原生证据显示存在后台执行,由于目前没有新增 drain,采集将保持 partial。
  • Codex:保留 Responses 原生 output(包括加密 reasoning)及所有线程 rollout; spawn 返回的是启动回执,wait 返回完成结果。多轮执行通过已确认的根线程 ID resume, 以防止 resume --last 误选最近的子线程。根 ACTF 的工具快照仅取自根线程,完整快照仍作为证据保存。 tool_search_output 动态加载的定义保存在实际 Call.context 和工具快照中;此类轨迹的 主 SFT 暂不导出,以避免丢失加载顺序或虚构静态工具列表。逐调用导出不受此边界影响。
  • Hermes:在原生 delegate_task 创建子实例时记录父 session ID 和委派工具 ID; 一个批量工具调用可以对应多个子实例。使用原生的有限查询 session 设置等待子实例返回。
  • OpenClaw:固定镜像内的原生 stream wrapper 记录各 session 的实际上下文; Roller 等待原生 registry 中的子任务及完成通知清理结束后,再收集文件。启动回执与完成通知分开处理; 完成通知须同时匹配原生 run ID 和父模型实际输入,才会建立 result_in_context。 registry 缺失或身份不匹配时记为 unknown;子任务已结束但通知仍为 pending 时记为 partial。 drain 失败时记录采集错误,并保留已成功的主 turn 状态。原生 session 可能不保存完整通知输入; ACTF 使用同一 announcement run 的 stream:context、session:after 和 provider 响应 ID 补全实际输入,无法绑定时禁止导出线性 SFT。工具 ID 规范化映射来自 原生 resolver 的实际输出。支持按实际请求端点解码 Chat Completions、Responses 或 Messages, 实测结果分别注明模型与协议。固定镜像的 hook 不匹配时会报错。
  • Pi / DeepSeek:统一记录实际模型 I/O、根参与者和完整性。Roller 内置的 Pi 配置禁用 扩展,DeepSeek 使用 sdk-minimal profile;二者没有原生委派工具。manifest 明确声明 not_available_in_bundled_profile,子任务测试为 not_applicable,不计为通过。

共同验收覆盖单子任务、并行、相同 prompt、文件共享、工具错误、不同工具配置和普通对话。 跨进程恢复、七个 Harness 的全部嵌套/后台组合和 compaction 分段重建仍未完成完整验证。

共享验收入口:scripts/test_subagents_e2e.py --harness HARNESS ...(七个 Harness 均可选择)。 测试凭据仅从指定环境变量读取。

OpenCode 原生接口依据:固定版本的插件 hook 定义、 task 工具元数据。