子代理、fork 与上下文折叠:Agent 交接的三种做法

给 coding agent 派活时,最难的从来不是派出去,而是"派什么过去"。子代理拿到任务却没有背景,得重新发现 Node 版本、被 pin 的依赖和你说过的约束;而留在主上下文里的构建日志,又让窗口被 43K 的报错挤满。本文编译 Arthur 的 fork/fold 设计:已完成的工作折成一条 ~160 token 的 note,归档可回查;需要委派时让子代理继承父代理的当前视图。附三张成本账、缓存断点位置,以及作者自己承认"还没验证"的部分。

分享
子代理、fork 与上下文折叠:Agent 交接的三种做法

一句话结论:多 Agent 协作的瓶颈不在"派活",而在交接。让子代理重做一遍上下文(重新发现依赖、约束、已有决策)成本高;把日志留在主线程,43K token 的构建输出又会一直占着窗口参与每一轮推理。Arthur 提出第三条路——fold:让 Agent 把已经完成的片段压成一条带句柄(handle)的 note,归档保留原文、主上下文只留结论;以及 fork:在派活之前让子代理继承父代理的当前视图。两者都不是压缩对话,而是改变模型在后续轮次里看到什么。

本文完整编译 Arthur《My take on subagents, forks and collapsing context》(2026-10-12)的设计、成本模型与实验计划,并做中文整理。文中成本数字来自作者按缓存价格做的估算(假设运行),作者明确表示:折叠是否真的改善任务结果,尚未验证。


一、问题:构建循环的"切线"把上下文吃掉了

作者描述的场景非常具体:你让 Agent 加一个导出功能,它在实现过程中发现项目构建不过,于是花了好几轮读报错、试修复、重新构建。等构建通过、回到原任务时——"我本来要干什么来着?"

问题的结构是这样的(用作者的记号):

  • B:构建之前的上下文(系统提示、你的需求、探索代码、写好的查询);
  • E:构建片段(episode)——报错日志、失败的猜测、你的纠正、最终通过的命令;
  • A:构建之后的工作(继续写前端)。

今天主流的两种做法都有代价:

  1. 内联 + 普通 compaction:日志一直留在上下文里,直到窗口快满才触发总结。触发时机取决于上下文大小,而不是"构建"这件事早就结束了。作者举例的图上,一次构建产生了约 42K token,窗口 80K,典型的 compaction 点在 72K——上一轮构建的日志要跟后面所有工作一起挤在窗口里。
  2. 子代理(subagent):把构建外包给一个全新的子代理,日志不进主上下文,还保住了父代理的 prompt-cache 前缀。但代价是交接:新子代理只知道任务描述里写了什么,它得重新发现 Node 版本、被 pin 的依赖、你早先给过的约束。而且你必须在还不知道这段工作有多长的时候就决定是否委派——一个"快速构建"如果变成七轮,事后无法撤回到子代理里。

作者还点了一个容易被忽略的成本:即使 prompt caching 让旧日志便宜了,那些 token 仍然占着窗口,模型在之后的每一轮里仍然要对它们做注意力计算。

二、fold:做完再压,而不是等窗口满

fold 的思路是反过来的:构建照常内联跑(不预测、不提前委派),等它结束,Agent 再回头把这段"切线"折起来。

概念是这样的:

  • episode(片段):一段与主线任务有关、但可以整体结束的工作(比如"让它构建通过");
  • fold(折叠):Agent 识别出片段的起点和终点,写一条 note,把原始过程归档,从句柄(如 b2e4)引用。

原来的上下文变化是:

今天:  B + E + A
折叠后:B + fold(E) + A        # fold(E) 约 160 token,E 原文进归档

作者给的例子:一段 ~43K token 的构建片段,折成 ~160 token 的一条 note,缩小约 270 倍。主线程从"我本来要干什么来着"变成"构建过了,继续前端"。

2.1 note 模板:五类信息,缺一不可

作者用的模板固定五段:

字段 含义
changed 改了哪些文件、配置、外部状态
learned 学到了什么,包括前提条件(caveats)
decided / rejected 做了什么决定,否决了什么
open 未完成、未验证的开放项
where 如果不够再回头看哪里(句柄)

模板看起来和现有 compaction 提示词差不多(OpenCode 的 summarize 也要求 decisions、work state、next steps、文件路径与精确报错)。区别在于应用的片段更小、时机更早——而且 note 要独立成立。

2.2 最难的是保住"约束",而不只是结论

这是全文最值得记的一个例子。假设 Agent 写了一个任务领取查询:

SELECT ... FROM jobs WHERE ... FOR UPDATE SKIP LOCKED

关键约束是:选取和领取必须在同一个事务里,这样行锁持续到领取被记录为止。

如果 note 只写"领取查询能用了",那么后续任何一个把查询挪出事务的改动,都会让两个 worker 领取到同一个任务——而归档里虽然有解释,note 却没有给 Agent 去看的理由。

正确写法是:

领取查询可用;选取与领取必须保持在同一事务内(SKIP LOCKED 的行锁持续到 commit)。

作者还提醒 note 的原材料从哪来:片段里 Agent 在每次工具结果后的那些"这意味着什么、下一步试什么"的回复,是输入而不是成品——它们依赖上面已经看不见的输出,里面还混着后来被否决的猜测;note 要留下站得住的结论,并在需要时引用精确的报错字符串或路径。

2.3 选择的时机与风险

"直到构建通过"是一个容易事先定义好的折叠点,但"哪些文件会被读到、哪些早先决策会被用到、这段要多久"很难预测——fold 把选择权交给了事后的 Agent,因为它那时才有信息。

风险也在这里:选错片段会把无关工作总结进来,或者一半日志留在上下文里。归档虽然可以找回原文,但找回本身要花时间,而且未必发生在错误之前。作者对开放式工作(问题一直在变)的建议是:干脆不要折,把片段保持打开——再好的时机判断也无法预知二十轮之后需要什么。

三、fork:让子代理继承父代理的视图

fold 处理"内联执行完再收",fork 处理"一开始就派出去":对延续父任务的委派,与其让父代理把散落在上百条消息里的背景拼成一段交接提示词,不如把父代理当前的消息数组复制一份给子代理。

fork(task) -> result, handle

实现上:复制父代理的 message 数组,在副本里用 task 作为未完成工具调用的结果,让子代理跑到结束,返回最后一条消息和子代理 transcript 的句柄。父代理仍然要指定任务,但不必重建背景——而且未改动的前缀理论上还能命中缓存。

fork 的两个坑:

  1. 它继承父代理的错误猜测——这恰恰是子代理本来要避免的问题。作者的包因此让 fork 基于已折叠后的视图:早先查询工作如果已经折成 note,子代理拿到的是那条 note,而不是完整历史(被否决的猜测不会再传下去;但 note 里的结论如果是错的,依然会传下去;未折叠的开放片段仍然全量继承)。
  2. 多个 fork 共享同一个工作树,文件编辑必须协调。

四、一笔成本账:fork 比全新子代理贵多少

作者做了一个估算模型:父前缀 100K token、全新子代理的系统提示 15K;整段循环 30 轮共增加 150K token;缓存读 0.1x、缓存写 1.25x;全新子代理需要重读约 30K 父代理已经读过的材料;假设两边轮数与构建输出相同。

项目 倍率 fork 全新子代理
前缀,每轮从缓存读 0.1x 300K 45K
循环增量,写一次 1.25x 188K 188K
循环增量,随增长从缓存读 0.1x 225K 225K
重读父代理已读过的内容 写 1.25x + 读 0.1x 0 ~125K
合计(fresh-token 等价) ~713K ~583K
按 $3/百万 token 计 ~$2.14 ~$1.75

三个读法:

  1. fork 的输入成本约为全新子代理的 1.2 倍;只看前缀会得出"近 7 倍"的错觉,因为漏掉了两边都要承担的构建输出。
  2. 父上下文越长,fork 越贵;缓存读越便宜,差距越小;缓存过期会额外产生一次前缀写。
  3. 但如果父代理已经折过——假设前缀从 100K 降到 40K——fork 的总量会掉到 ~533K,低于全新子代理的 ~583K(还没算生成 note 的成本)。也就是说:fold 让 fork 变得划算。作者同时说明,对依赖很少的短查询,仍然应该用全新子代理。

五、评审者:给需求,不要给作者的解释

一个容易忽略的用法差异:如果你要找一个 Agent 评审代码,fork 会把"作者为什么是对的"整段推理一起交过去——这恰好污染评审独立性。

作者的主张是:评审用独立子代理,输入 = 用户的原始需求 + diff + 相关代码,不给实现者的结论。图里给了对照:拿到需求与 diff 的评审者找到了"worker 在事务外领取任务"的 bug;拿到父代理推理的评审者则"继承结论",复述了"SKIP LOCKED 防止重复领取"。

代价也要说清:同一个模型仍然有共同的盲区,独立输入只能降低"附和作者"的概率。

六、缓存断点:fold 与 fork 都靠它省钱

成本估算的前提是缓存前缀能被复用。作者逐项列了各种操作对缓存的影响:

操作 变化 缓存行为
append 追加工具结果等消息 复用已有前缀,只处理新增尾部
fork 复制父视图并追加 task 复用父前缀;每个子代理处理自己的尾部
fold 用 note 替换片段 复用变化点之前的条目,处理剩余上下文与 note
compaction 用摘要替换旧对话 重写部分要重新处理,更早内容可能仍命中
ask 加载归档上下文并提问 复用该上下文条目,或重算已过期部分

对工程实现更麻烦的是:Agent 事先并不知道自己会折哪一段。pi-episodes 的做法是在"最新的 fold note 或带时间戳的用户消息"处放一个额外断点;真实片段起点可能在这之后几轮,因此一次 fold 仍可能让中间那段重新处理。作者的测量计划包含这部分成本。

另外几个现实约束:Anthropic 缓存默认 5 分钟、可买 1 小时;较新的 OpenAI 模型默认 30 分钟;长工具调用或暂停会话都可能超出缓存存活时间。归档查询尤其容易触发全价读,需要单独计量。系统提示与工具定义保持稳定是共享缓存前缀的前提——Anthropic 把工具定义放在系统提示和消息之前,改一个工具定义会让其后所有内容的前缀失效。

七、数据模型与实现细节

包里的四个概念:

名称 内容
log 每条线程一条只追加的消息列表(主线 + 每个 fork)
view 模型实际看到的:主 log,其中被折叠的片段替换为 note
archive 被折叠片段与已结束 fork 的 log,附"碰过哪些文件"的索引
handle 归档片段的 ID(如 b2e4)

几个对重现对话保真度很关键的点:

  • 用户消息与工具结果在保存时打时间戳;模型用这些可见 ID 来指定 since。事后补打会改变已缓存文本。
  • fold 不能跨过更早的 note 或 compaction 摘要,且保持工具调用与结果成对。
  • 用户消息保留原始措辞与时间戳,这样构建过程中给你的纠正,回看时仍然是"那一轮里的一次纠正"。
  • 工作树快照提供 diff;没有快照时退化为列出文件工具碰过的路径。快照只在本会话内存活,重启后无法再检查"归档之后文件是否变了"。
  • 带句柄的 recall / ask 可以在归档里检索或追问(ask 把归档上下文临时挂上、答完即撤),成本按缓存是否命中差出数量级(warm ~$0.019 vs cold ~$0.19 的示例)。

八、怎么实测:pi-episodes

作者把它做成了 pi 的包:

pi install npm:pi-episodes

交互式里 /fold 会列出轮次让你选起点(/fold t13 直接指定),然后 Agent 写 note;没有交互选择器时,让 Agent 自己选。

作者建议的实测方法:在一次真实的构建循环之后执行 fold,然后给 Agent 一个依赖该循环细节的任务——看 note 是否保住了细节、或者 Agent 是否先去检索再行动。它正在验证的问题包括:

  1. 模型能否自己意识到一个片段结束了,并选对起点;
  2. note 是否留住了前提条件(还是犯"查询能用了"的错);
  3. 该回查时会不会回查,文件触碰提示是帮忙还是噪音;
  4. fork 是否比全新子代理更容易锚定父代理的错误猜测;
  5. 更小的上下文是否改善任务结果(对比保留完整日志)。

后续想做的实验还包括:索引符号而不只是文件;用最近上下文主动提示归档线程(并测量是帮助还是分心);用失败任务反推 note 里缺了哪些约束。

九、边界:作者自己承认不知道的部分

文章最诚实的两句话,值得原样保留:

  • "folding 是否改善任务结果,我还不知道。" 图表与成本数字来自假设运行;这是一个待验证的设计,不是已验证的实践。
  • "最有证据缺口的情况,是 Agent 根本没意识到自己需要回查。" 一条漏掉事务约束的 note,可能永远不会触发检索——除非后续文件读写恰好碰到同一个文件(shell 命令不会触发这个提示)。

此外,归档里的答案反映的是当时的文件状态;文件已改,解释就可能失效。作者说测量完成后会公布结果。

对工程团队的启示(中文整理者补充)

把这篇设计放在一起看,它其实给了三类可立即使用的动作,都不依赖作者那个实验包:

  1. 交接提示词写五段(changed / learned / rejected / open / where),并强制要求约束而不只是结论——尤其在事务、并发、权限这类"结论正确但前提会失效"的地方。
  2. 委派决策延后:不确定要多久的工作先内联跑,用"构建通过/测试通过"这类天然结束点作为折叠时机;开放式工作保持打开。
  3. 评审与实现分离:评审者只拿需求 + diff + 代码,不拿实现者的推理。

最后,任何自动折叠都要配人工复核:note 是摘要,摘要会丢信息;archive 是兜底,但"想不起来去查"是它救不了的失败模式。


来源

  • Arthur, My take on subagents, forks and collapsing context(2026-10-12):https://arthur.place/subagents-forks-collapsing-context
  • 事件分割理论(Event Segmentation Theory):Zacks & Swallow, Event Segmentation;Kurby & Zacks, Segmentation in the perception and memory of events
  • Altmann & Trafton, Memory for goals(关于"开放项需要在暂停前留下线索"的设计依据)
  • pi-episodes(实验包):pi install npm:pi-episodes

延伸阅读(本站)

本文由汇智网编译整理,转载请注明出处。

这是「Agent / MCP」专题的一部分。完整地图见:Agent / MCP / 自动化完全指南

阅读更多

28 个 Agent 六天重构:569 个单元与 250 次人工决策复盘

28 个 Agent 六天重构:569 个单元与 250 次人工决策复盘

把重构交给 Agent 跑六天,代码确实是 Agent 写的;但人的时间并没有消失——它变成了两周设计、88 个必须回答的问题和 185 次"先例裁决",周一还有 63 个决策挤在同一小时里做完。本文完整编译 GoodBarber CTO 的自主循环复盘:569 个工作单元、五道互相印证的检查、从 1 到 20 的爬坡条件、单集成者的瓶颈曲线,以及五个真实翻车点(测试台说谎、Agent 绕过阻力、没人检查的规则被破坏)。

By admin
自建开源 coding agent 平台:OpenHands vs Munder Difflin

自建开源 coding agent 平台:OpenHands vs Munder Difflin

买席位省事,但代码、模型和预算都不在自己手里。OpenHands 的自托管把模型生成的每一步关进 Docker 沙箱,代价是 Docker socket、动态端口、镜像固定与清理都得自己扛;Munder Difflin 走另一条路——复用已付费的 CLI 与小时额度,用 git 文件当消息总线,用审批队列和熔断器控制自治。两条路线的架构、成本与决策清单,外加自己实现时的五个最小层。

By admin
一台终端、多台机器、多个 Agent:并行 coding agent 的实战栈,以及它背后的分布式系统问题

一台终端、多台机器、多个 Agent:并行 coding agent 的实战栈,以及它背后的分布式系统问题

一个开发者同时养 3–5 个 coding agent,难点从来不是模型,而是隔离、状态和协调。本文编译一套终端原生并行栈(worktree 隔离 + 状态感知的 tmux + 远程/手机审批),并给出三个硬数据:任务形状决定多 Agent 成败、编排可以把错误放大从 17.2 倍降到 4.4 倍、以及为什么"状态要比 Agent 活得久"。

By admin