结论
OpenPI 当前对子代理最终结果采用“只保留头部”的截断策略。超过自动投递预算(约 24 KiB)后,报告结尾会被优先丢弃;父模型只能得到整个 Pi session JSONL 的路径,没有轻量、确定、低 token 的恢复方式。
这个问题真实存在,而且结尾通常承载 verdict、修改文件、风险、未完成项和下一步,信息价值往往高于报告中段。
本 Issue 收敛为一个运行时方案:
按上下文预算保留 head + tail,把完整 final answer 保存成只读语义上的纯文本 artifact,并复用 Pi 原生 read 分页读取。
不新增 OpenPI 模型工具,不重启子代理,不要求模型遵守脆弱的“摘要必须写在某个位置”提示词合同。
当前行为与根因
extensions/subagents/index.ts 的 truncatedOutput() 对 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
这带来四个问题:
- 确定性丢尾部:verdict、文件清单和下一步常在结尾。
- 恢复粒度错误:缺的是最终回答的一小段,指向的却是包含完整事件流、工具调用和 JSON 转义的 session JSONL。
- 恢复成本过高:直接读取 JSONL 需要定位 assistant message、处理一行 JSON/转义和 offset;重新
subagent_send 又会产生额外模型调用。
- 投影不一致:自动完成投递和
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 合同。
不采纳的方案
- 完整结果始终注入父上下文(Codex 路线):简单,但长报告与大 fan-out 会直接放大上下文和缓存成本。
- 只增加固定 tail summary prompt:无法由运行时保证,模型/provider 漂移后仍会丢信息。
- 新增
subagent_transcript 工具:现阶段没有必要;纯文本 artifact + Pi read 已提供更小、更通用的机制。
- 继续指向 session JSONL:恢复对象与用户真正需要的 final answer 不一致。
- 只把 head cap 调大:推迟而非解决问题,同时增加父上下文成本。
验收标准
结果正确性
生命周期一致性
安全与回归
价值
- 保留首尾高价值证据,减少父模型基于不完整报告做结论的概率;
- 无额外模型调用即可恢复完整内容;
- 正常短结果零新增上下文成本;
- 不扩大工具面,保持缓存 Schema 稳定;
- 使“完整执行事实、模型可见投影、TUI 展示”继续保持清晰分层。
结论
OpenPI 当前对子代理最终结果采用“只保留头部”的截断策略。超过自动投递预算(约 24 KiB)后,报告结尾会被优先丢弃;父模型只能得到整个 Pi session JSONL 的路径,没有轻量、确定、低 token 的恢复方式。
这个问题真实存在,而且结尾通常承载 verdict、修改文件、风险、未完成项和下一步,信息价值往往高于报告中段。
本 Issue 收敛为一个运行时方案:
不新增 OpenPI 模型工具,不重启子代理,不要求模型遵守脆弱的“摘要必须写在某个位置”提示词合同。
当前行为与根因
extensions/subagents/index.ts的truncatedOutput()对snap.finalText调用truncateHead():截断后只追加:
这带来四个问题:
subagent_send又会产生额外模型调用。subagent_wait都复用同一个 head-only 投影,因此显式等待也无法拿到尾部。subagent_check只提供约 2 KiB / 20 行最近活动预览,不是最终结果分页接口。同类实现调研
Codex CLI / Codex Core
Codex 将子代理最后一条 assistant message 保存在:
完成时把该 payload 作为 inter-agent completion message 发给父代理。当前成功结果路径未看到显式截断;
list_agents返回的AgentStatus也可能携带完整 completed message。源码:
优点:实现简单,结果尾部不会因为固定 head cap 被确定性丢弃。
代价:长报告会完整进入父上下文;状态查询还可能再次携带完整结果。它解决了“拿不到”,没有解决“上下文预算与按需恢复”。OpenPI 不应照搬完整注入。
Hermes Agent
Hermes 的 delegation summary 路径已经实现了更完整的预算合同:
max_summary_chars默认 24,000 字符;read_file offset=... limit=200的分页方式;output_schema用于真正需要机器消费的结构化委派,并允许一次有界纠正。源码与文档:
Hermes 的关键边界值得复用:上下文里放有界投影,磁盘上保留完整语义结果,父模型按需读取。
OpenPI 方案
1. 一个统一的结果投影
自动投递、
subagent_wait和其他最终结果消费者共用同一个函数:继续使用 Pi 已有
truncateHead/truncateTail,同时遵守 byte 与 line 两个上限,不截断 UTF-8 字符。2. 纯文本 artifact,而不是 session JSONL
只有确实发生截断时才保存:
合同:
finalText,没有工具事件、thinking 或 JSON 包装;这个目录是可再生 cache,不改变 Pi session 的 source of truth。
3. 复用 Pi 原生
readfooter 给父模型可执行的恢复方式,例如:
不新增
subagent_transcript:4. 暂不把结构化输出作为默认要求
提示模型“必须把总结写在末尾”不是运行时保证;强制所有子代理使用 schema 还会增加 prompt、重试和 provider 兼容复杂度。
真正需要机器消费的 structured delegation 可以后续单独设计。本 Issue 先用确定性的运行时投影保证普通文本报告不丢首尾。
安全与产品边界
read按需读取。不采纳的方案
subagent_transcript工具:现阶段没有必要;纯文本 artifact + Piread已提供更小、更通用的机制。验收标准
结果正确性
finalText逐字一致。生命周期一致性
subagent_wait的每个结果使用同一投影,并继续遵守总输出预算。安全与回归
bun run check通过。bun run test通过。价值