子代理、fork 与上下文折叠:Agent 交接的三种做法
给 coding agent 派活时,最难的从来不是派出去,而是"派什么过去"。子代理拿到任务却没有背景,得重新发现 Node 版本、被 pin 的依赖和你说过的约束;而留在主上下文里的构建日志,又让窗口被 43K 的报错挤满。本文编译 Arthur 的 fork/fold 设计:已完成的工作折成一条 ~160 token 的 note,归档可回查;需要委派时让子代理继承父代理的当前视图。附三张成本账、缓存断点位置,以及作者自己承认"还没验证"的部分。
一句话结论:多 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:构建之后的工作(继续写前端)。
今天主流的两种做法都有代价:
- 内联 + 普通 compaction:日志一直留在上下文里,直到窗口快满才触发总结。触发时机取决于上下文大小,而不是"构建"这件事早就结束了。作者举例的图上,一次构建产生了约 42K token,窗口 80K,典型的 compaction 点在 72K——上一轮构建的日志要跟后面所有工作一起挤在窗口里。
- 子代理(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 的两个坑:
- 它继承父代理的错误猜测——这恰恰是子代理本来要避免的问题。作者的包因此让 fork 基于已折叠后的视图:早先查询工作如果已经折成 note,子代理拿到的是那条 note,而不是完整历史(被否决的猜测不会再传下去;但 note 里的结论如果是错的,依然会传下去;未折叠的开放片段仍然全量继承)。
- 多个 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 |
三个读法:
- fork 的输入成本约为全新子代理的 1.2 倍;只看前缀会得出"近 7 倍"的错觉,因为漏掉了两边都要承担的构建输出。
- 父上下文越长,fork 越贵;缓存读越便宜,差距越小;缓存过期会额外产生一次前缀写。
- 但如果父代理已经折过——假设前缀从 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 是否先去检索再行动。它正在验证的问题包括:
- 模型能否自己意识到一个片段结束了,并选对起点;
- note 是否留住了前提条件(还是犯"查询能用了"的错);
- 该回查时会不会回查,文件触碰提示是帮忙还是噪音;
- fork 是否比全新子代理更容易锚定父代理的错误猜测;
- 更小的上下文是否改善任务结果(对比保留完整日志)。
后续想做的实验还包括:索引符号而不只是文件;用最近上下文主动提示归档线程(并测量是帮助还是分心);用失败任务反推 note 里缺了哪些约束。
九、边界:作者自己承认不知道的部分
文章最诚实的两句话,值得原样保留:
- "folding 是否改善任务结果,我还不知道。" 图表与成本数字来自假设运行;这是一个待验证的设计,不是已验证的实践。
- "最有证据缺口的情况,是 Agent 根本没意识到自己需要回查。" 一条漏掉事务约束的 note,可能永远不会触发检索——除非后续文件读写恰好碰到同一个文件(shell 命令不会触发这个提示)。
此外,归档里的答案反映的是当时的文件状态;文件已改,解释就可能失效。作者说测量完成后会公布结果。
对工程团队的启示(中文整理者补充)
把这篇设计放在一起看,它其实给了三类可立即使用的动作,都不依赖作者那个实验包:
- 交接提示词写五段(changed / learned / rejected / open / where),并强制要求约束而不只是结论——尤其在事务、并发、权限这类"结论正确但前提会失效"的地方。
- 委派决策延后:不确定要多久的工作先内联跑,用"构建通过/测试通过"这类天然结束点作为折叠时机;开放式工作保持打开。
- 评审与实现分离:评审者只拿需求 + 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 一份"自己的记忆":本地、可移植、可溯源的 funes
- 一台终端、多台机器、多个 Agent:并行 coding agent 的实战栈
- 多 Agent 团队到底值不值?50 个全栈应用实测
- 自建开源 coding agent 平台:OpenHands vs Munder Difflin
- 专题:Agent / MCP / 自动化完全指南
本文由汇智网编译整理,转载请注明出处。
这是「Agent / MCP」专题的一部分。完整地图见:Agent / MCP / 自动化完全指南