Skip to content

子代理超长结果:保留首尾、持久化完整 final artifact,并支持按需恢复 #64

Description

@tt-a1i

结论

OpenPI 当前对子代理最终结果采用“只保留头部”的截断策略。超过自动投递预算(约 24 KiB)后,报告结尾会被优先丢弃;父模型只能得到整个 Pi session JSONL 的路径,没有轻量、确定、低 token 的恢复方式。

这个问题真实存在,而且结尾通常承载 verdict、修改文件、风险、未完成项和下一步,信息价值往往高于报告中段。

本 Issue 收敛为一个运行时方案:

按上下文预算保留 head + tail,把完整 final answer 保存成只读语义上的纯文本 artifact,并复用 Pi 原生 read 分页读取。

不新增 OpenPI 模型工具,不重启子代理,不要求模型遵守脆弱的“摘要必须写在某个位置”提示词合同。

当前行为与根因

extensions/subagents/index.tstruncatedOutput()snap.finalText 调用 truncateHead()

const truncation = truncateHead(output, {
  maxBytes: Math.min(maxBytes, DEFAULT_MAX_BYTES),
  maxLines: Math.min(600, DEFAULT_MAX_LINES),
});

截断后只追加:

Full transcript in session file: ...jsonl

这带来四个问题:

  1. 确定性丢尾部:verdict、文件清单和下一步常在结尾。
  2. 恢复粒度错误:缺的是最终回答的一小段,指向的却是包含完整事件流、工具调用和 JSON 转义的 session JSONL。
  3. 恢复成本过高:直接读取 JSONL 需要定位 assistant message、处理一行 JSON/转义和 offset;重新 subagent_send 又会产生额外模型调用。
  4. 投影不一致:自动完成投递和 subagent_wait 都复用同一个 head-only 投影,因此显式等待也无法拿到尾部。

subagent_check 只提供约 2 KiB / 20 行最近活动预览,不是最终结果分页接口。

同类实现调研

Codex CLI / Codex Core

Codex 将子代理最后一条 assistant message 保存在:

AgentStatus::Completed(Option<String>)

完成时把该 payload 作为 inter-agent completion message 发给父代理。当前成功结果路径未看到显式截断;list_agents 返回的 AgentStatus 也可能携带完整 completed message。

源码:

优点:实现简单,结果尾部不会因为固定 head cap 被确定性丢弃。

代价:长报告会完整进入父上下文;状态查询还可能再次携带完整结果。它解决了“拿不到”,没有解决“上下文预算与按需恢复”。OpenPI 不应照搬完整注入。

Hermes Agent

Hermes 的 delegation summary 路径已经实现了更完整的预算合同:

  • 静态 max_summary_chars 默认 24,000 字符;
  • 根据父上下文剩余 headroom 和同批子代理数量动态缩小单份预算;
  • 超限后保留约 75% head + 25% tail,并按换行边界裁切;
  • 完整 summary 保存为单独纯文本文件;
  • footer 明确给出完整长度、保存路径,以及 read_file offset=... limit=200 的分页方式;
  • 可选 output_schema 用于真正需要机器消费的结构化委派,并允许一次有界纠正。

源码与文档:

Hermes 的关键边界值得复用:上下文里放有界投影,磁盘上保留完整语义结果,父模型按需读取。

OpenPI 方案

1. 一个统一的结果投影

自动投递、subagent_wait 和其他最终结果消费者共用同一个函数:

finalText
  ├─ 未超 byte/line budget → 原样返回
  └─ 超预算
       ├─ head(约 75%)
       ├─ [... middle omitted ...]
       ├─ tail(约 25%)
       └─ footer(总量、artifact、读取指引)

继续使用 Pi 已有 truncateHead / truncateTail,同时遵守 byte 与 line 两个上限,不截断 UTF-8 字符。

2. 纯文本 artifact,而不是 session JSONL

只有确实发生截断时才保存:

<agentDir>/cache/openpi/subagent-results/<content-hash>.txt

合同:

  • 内容只包含该次 finalText,没有工具事件、thinking 或 JSON 包装;
  • 文件名由内容 hash 得出,不使用模型生成的 title/path;
  • 创建目录与文件采用受限权限和不覆盖写入;
  • 相同结果可复用同一 artifact;
  • 如果写入失败,投影仍返回 head + tail,但必须明确说明全文未保存,不能给出虚假路径。

这个目录是可再生 cache,不改变 Pi session 的 source of truth。

3. 复用 Pi 原生 read

footer 给父模型可执行的恢复方式,例如:

[Output truncated: showing head and tail of 31.2KB total.
Full final answer: /.../cache/openpi/subagent-results/<hash>.txt
Use read with this path and a narrow offset/limit to inspect omitted sections.]

不新增 subagent_transcript

  • 避免增大和改变 delegate 工具 Schema;
  • 避免重复实现 Pi 已有文件分页能力;
  • artifact 是 final answer,不再要求模型解析 JSONL;
  • 子代理权限和生命周期不变。

4. 暂不把结构化输出作为默认要求

提示模型“必须把总结写在末尾”不是运行时保证;强制所有子代理使用 schema 还会增加 prompt、重试和 provider 兼容复杂度。

真正需要机器消费的 structured delegation 可以后续单独设计。本 Issue 先用确定性的运行时投影保证普通文本报告不丢首尾。

安全与产品边界

  • 不写项目工作区;artifact 只进入 Pi agent cache。
  • 不从模型文本生成文件路径。
  • 不跟随或覆盖已有文件。
  • artifact 失败不会导致子代理成功结果变成失败;但必须诚实报告恢复能力缺失。
  • 不把 artifact 注入子代理工具面;只有父会话通过已有 Pi read 按需读取。
  • 不改变 spawn、wait、cancel、settle、自动唤醒或 child authority 合同。

不采纳的方案

  1. 完整结果始终注入父上下文(Codex 路线):简单,但长报告与大 fan-out 会直接放大上下文和缓存成本。
  2. 只增加固定 tail summary prompt:无法由运行时保证,模型/provider 漂移后仍会丢信息。
  3. 新增 subagent_transcript 工具:现阶段没有必要;纯文本 artifact + Pi read 已提供更小、更通用的机制。
  4. 继续指向 session JSONL:恢复对象与用户真正需要的 final answer 不一致。
  5. 只把 head cap 调大:推迟而非解决问题,同时增加父上下文成本。

验收标准

结果正确性

  • 未超预算的结果逐字不变,不创建 artifact。
  • 超 byte 上限时同时保留可识别的开头和结尾,中段明确标记省略。
  • 超 line 上限时行为一致。
  • UTF-8 / 中文边界不产生替换字符或半字符。
  • footer 报告完整总量、artifact 路径和按需读取方式。
  • artifact 内容与原始 finalText 逐字一致。

生命周期一致性

  • 自动完成投递使用新投影。
  • subagent_wait 的每个结果使用同一投影,并继续遵守总输出预算。
  • 相同 finalText 多次投影不会生成冲突或错误内容。
  • artifact 写入失败时仍交付 head + tail,并明确 fail-closed。

安全与回归

  • 不使用 title、prompt、cwd 等不可信字符串构造 artifact 文件名。
  • 不覆盖既有路径,不通过 symlink 写出 cache 目录。
  • 不新增模型工具,不改变 OpenPI 能力披露与 child tool 分类。
  • bun run check 通过。
  • bun run test 通过。

价值

  • 保留首尾高价值证据,减少父模型基于不完整报告做结论的概率;
  • 无额外模型调用即可恢复完整内容;
  • 正常短结果零新增上下文成本;
  • 不扩大工具面,保持缓存 Schema 稳定;
  • 使“完整执行事实、模型可见投影、TUI 展示”继续保持清晰分层。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions