压缩成摘要、整个重置,还是写交接文档?
本课一问:第 2 课的「先便宜后贵」里,最贵的一步是让模型把旧轮次写成摘要。摘要该怎么写?或者干脆不写,整个清空、换一个新的 agent 接手?
这一课先不碰:跨会话的记忆(A6);把活分给子 agent、让它在自己的窗口里读(A7)。
从已有出发
前三课定下了「什么时候压」和「先裁什么」。剩下的一块要花钱:把旧轮次换成一段摘要。本课项目 miniagent 的 L2 就是这么做的,它在真实运行 pd-v2-layered-s1(预算 1,800,21 轮,最后成功)里一共写了 17 份摘要,全部录了下来。
先自己设计:让模型「总结一下上面的对话」
miniagent 给摘要模型的指令只有两句话,已经比「总结一下」讲究:要写任务、看过的文件、测试结果与确切的报错、做过的修改、还剩什么,不许编造。
- ① 朴素方案
- ② 它在哪失败
- ③ 改进方案
指令原文(miniagent/context.py:176-184):
第 10 轮请求里带着的那份摘要(第 10 轮请求之前写成,原文):
后面三节看三种做法:原地压缩但写成交接文档(Codex、Pi、OpenCode),整个清空换人接手(context reset),以及让模型自己决定什么时候开新窗口(Codex 的 new_context)。先把这 17 份摘要逐份拆开,看失败出在哪。
拆开看:摘要之后还剩什么
口径:本课项目 miniagent 用 Qwen3-8B 本机录制(2026-09-26),预算 1,800,L1 / L2 / L3 阈值为预算的 50 / 70 / 90%。每轮发出的消息由重放得到,与录制的 26 次压缩事件逐条一致。「摘要模型看得到什么」= 在它收到的那段输入文本里查关键原文(项目的 render_for_summary 会把每条消息截到 800 字符)。清单的判定是浏览器里现跑的正则,点「展开」可以看命中处。四份提示词只比较「要求了什么」,不代表用它们写出来的摘要会更好——那需要用同一模型重跑,本章没有这份录制。
试三件事:① 默认选中第 10 轮(时间轴上的 ↺):对照左右两栏,找出 dateparse.py 的源码在不在右边;再读模型这一轮的思考原句;② 点第 4 轮:这一轮之前先做了 L2、又做了 L3——右栏里有没有摘要?③ 往后点几轮带 L2 的,看清单里的 ⚠ timezone 从哪一轮开始出现,再看「摘要模型收到的输入里」那一行:它编出来的,还是从上一份抄来的?
拆开之后,失败不是一个原因,而是三个叠在一起:
- 垃圾进。17 次摘要,摘要模型收到的输入里含
AssertionError那一行的:0 次;含第二处 bug 那一行(MONTHS.index(name.lower()))的:0 次。测试输出在进入摘要之前已经被 L1 裁成了头尾各 3 行(第 2 课),源码则被项目的render_for_summary按每条 800 字符截断,那一行在第 800 个字符之后。指令要它写「确切的报错」,它根本没见过。 - 空白处编造,然后传下去。第一份提到 timezone 的摘要,输入里没有这个词;后面的摘要把上一份摘要当输入,就把它当成事实继承了(岔路 B1 讲的「上下文中毒」)。
- 刚写好的摘要被当成最旧的一轮丢掉。第 4 轮之前,L2 把 1 轮写成摘要(1,580 → 1,727,反而更长),紧接着 L3 从头丢了 2 轮——摘要正是被丢的那一轮。这次摘要花了 523 token,什么也没留下。
第 10 轮的上下文里有「日月对调」的 diff 和失败的测试,却没有源码,摘要只说「之前的修改集中在调整格式」。模型在思考里说得很直白:
But without seeing the regex pattern, it's difficult to proceed.
So, the solution is to revert the code back to the original assignment of month, day, year.—— pd-v2-layered-s1 第 10 轮,模型的思考(英文原文)
于是它把改对的一处改了回去。直到第 13 轮重新读了文件,第 15 轮才再改对。
数据:本课项目 miniagent 的真实运行(src/data/project/agent-traces.json,2026-09-26 导出;模型 Qwen/Qwen3-8B-MLX-4bit,本机 mlx_lm.server)。token 数取自服务端 usage;思考截取约 600 字、正文与工具输出约 900 字(保留头尾)。耗时受本机同时运行的其他任务影响,只能粗看;token 与轮数才可比。
改进一:把压缩写成交接文档
三家生产级 harness 的压缩提示词,出发点都是「接手的是另一个模型,它只看得到你写下的东西」。
Codex:全文 9 行,开宗明义是交接。
光有提示词还不够。Codex 压缩后重建的历史里,用户说过的原话是原样放回去的(从最新往前,最多 20,000 token),摘要只替换其余部分:
Pi:结构化模板,六节固定标题,并要求「Preserve exact file paths, function names, and error messages」。另外两个机制正对着我们的失败:序列化给摘要模型时,每条工具结果保留前 2,000 字符(我们的测试输出不到 900 字符、源码 1,302 字符,都会完整进入);读过和改过的文件清单跨多次压缩累积,不靠摘要模型记。
Pi 的切点规则上一课已经见过:从最新往回累加到 keepRecentTokens,切点只落在 user 或 assistant 消息上。像我们这种「一句任务跑到底」的会话,整个会话只有一个 turn,任何切点都落在 turn 中间:在 pin 源码上跑 findCutPoint,keepRecentTokens 取 1,000 时切点在第 7 条、isSplitTurn = true。Pi 对这种情况单独处理:为切点之前的那半个 turn 另写一份摘要,再与历史摘要合并(docs/compaction.md:81-107)。默认的 20,000 下,我们的整段会话都在保留范围内,切点在第 0 条——什么都不用摘。
OpenCode:五节模板(Objective / Important Details / Work State / Next Move / Relevant Files),规则里写着要保留确切的文件路径、符号、命令、报错字符串;再次压缩时,把上一份摘要与新对话合并,冲突时以新对话为准。四份提示词各自要求了什么,在上面组件的最下方逐项对照。
被压掉的只是「下一轮发给模型的那一份」。本课项目的 trace 里每条消息、每份摘要都在;Pi 把压缩写成会话文件里的一条记录,Codex 的 CompactedItem 可以回溯。完整历史在磁盘上,只是不在窗口里——A6 会用它来讲会话树与「回到三轮前换条路」。
改进二:清空重来,换一个 agent 接手(context reset)
另一条路线不在原地压缩:到了某个点,把上下文整个清空,启动一个新的 agent,只交给它一份交接文档。Anthropic 在《Harness design for long-running application development》(2026-03-24)里讲过它的动机:有些模型在自以为快到上限时会提前收尾(「context anxiety」),原地压缩给不了它一个干净的开始。
同一篇文章紧接着写道:换到 Opus 4.5 之后,这种行为基本消失,作者把 context reset 从 harness 里整个拿掉了。并由此给出一条通则:harness 里的每个组件都编码了一个「模型自己做不到」的假设,这些假设值得反复检验,因为模型一进步它们就可能过期。
它的教学价值恰恰在于被弃用:它解决的是某一代模型的某个行为。加这类组件之前先确认你用的模型还有这个问题;否则就是在为不存在的问题付成本。
改进三:让模型自己决定开新窗口
Codex 有一个 new_context 工具,描述只有一句:开一个新的上下文窗口,不清除、不重置环境状态。模型调用它,harness 就开新窗口,不写摘要。它挂在实验特性 token_budget 下(features/src/lib.rs:1396-1400,开发中、默认关闭;注册处 spec_plan.rs:1060-1063):
三种做法的共同点:压缩写的不是「发生过什么」,而是「接手的人要继续干,需要知道什么」。
证据三问
| 问 | 本课的回答 |
|---|---|
| ① 它解决什么问题? | 旧轮次必须让出位置时,把「继续干活需要的东西」留下来 |
| ② 为什么交接文档式更可能有效? | 模板点名要留的项(目标、约束、进度、确切报错、下一步、文件),正是我们的摘要漏掉的;用户原话与文件清单不经过摘要模型 证据等级 · 有理论动机 有数学或机制上的理由说明它可能有效,但没有证明它一定更好。最后是否采用,仍然要看实验。 本条设计动机,源码直证其做法;本章没有用这些模板重跑同一个任务来比较效果——那需要项目线补录。 出处待补 |
| ③ 实验上呢? | 两条路线(原地压缩、context reset + 交接)有官方一手材料,且 reset 已被作者弃用 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条调研档案 DR §11 两条声明均 3-0 满票。 出处Anthropic · 满票核查 ↗需联网 ;本课项目的 17 份摘要是单次运行的实测 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条项目实测:Qwen3-8B、预算人为调低到 1,800、一次运行;说明失败模式存在,不说明频率。 出处待补 |
演进地图:你在这里
- A1 的循环(所有 harness 的底层事实:模型 API 不记得上一轮)解决了实现最简单,模型每轮都看得到全部经过。卡在pd-v2-cap20-s1 从第 1 轮的 947 token 涨到第 11 轮的 2,618;第 1 轮里工具定义就占 72%,到最后一轮工具结果占 44%。只增不减,总有撞墙的一轮。
- 朴素 超了就截掉最早的本课项目 miniagent 的 truncate(整轮丢,保留 system 与任务)、learn-claude-code s08 的 snip_compact(中段存档)解决了零成本,还能保证 tool 调用与结果不被拆散(按整轮丢)。卡在pd-v2-truncate-s1 截断 2 次共丢 3 轮,读文件那一轮也在其中;第 7 轮模型凭记忆改,那一行变成
+ 1 + 1,验收测试退出码 1。 - 先便宜 先裁工具输出,不花一次推理CoreCoder Layer 1、learn-claude-code micro_compact、OpenCode prune(默认关)、Anthropic tool result clearing解决了工具结果是最占地方、也最可再生的一块;裁它不用调模型。卡在按位置裁,不懂内容:分层运行里被裁过的 0 份测试输出,0 份丢了唯一关键的 AssertionError 行。另一头,本任务最长的工具输出 1,302 字符,CoreCoder 的 1,500 字符门槛一次都没碰到。
- CoreCoder 50 / 70 / 90%、Pi 窗口 − 16,384、OpenCode 窗口 − min(20,000, 最大输出)、Codex 90%解决了阈值定在撞墙之前,给压缩本身和下一次回复留出余地。卡在本地估算在中文上偏低:一段真实中文 118 token(录制模型的 tokenizer),CoreCoder v0.4.0 估成 60,按「字符数 ÷ 4」估成 45。只靠本地估算触发,就会晚。
- CoreCoder Layer 2、本课项目 miniagent L2解决了比截断保留更多:旧轮次不是消失,而是被压成一段话。卡在pd-v2-layered-s1 的 17 份摘要里,写下确切报错的 0 份,编出不存在的「timezone」的 16 份;第 10 轮模型把改对的一处改了回去。21 轮、73,168 token(摘要另花 11,832)才完成,是同组不压缩成功运行最少的 4.3 倍。
- Codex(交接摘要 + 保留用户原话)、Pi(结构化模板 + 文件清单)、OpenCode(五节模板)解决了把「必须留下什么」写进模板:目标、约束、进度、确切报错、下一步、相关文件;完整历史留在磁盘上。卡在仍然有损。Codex 每次压缩后提醒用户:「Heads up: Long threads and multiple compactions can cause the model to be less accurate. Start a new thread when possible to keep threads small and targeted.」→ 下一章:会话存在哪、明天还记得吗。
数字口径:本课项目 miniagent 用 Qwen/Qwen3-8B-MLX-4bit 本机录制的真实运行(2026-09-26),每轮发出的消息由重放得到并与录制的压缩事件、服务端 usage 逐轮核对一致;估算器的输出由 CoreCoder、Pi 的原实现在 pin 源码上跑出(a05-context.json,2026-09-26)。耗时不作比较(本机有其他任务同时占用)。
一句话
先停在这里:记住三个失败原因(输入已被截断、空白处编造并被继承、摘要被 L3 丢掉)和三种做法(交接文档式压缩、context reset、让模型开新窗口)。
再深一层:去实验课,在六条真实运行上把本章所有组件串起来。
下一个缺口
会话压完了,明天开新会话,它还记得这个项目的规矩吗? → 下一章 A6:记忆与会话。
一个会话里,压缩让它能接着干;可「不许改测试文件」这种项目规矩,如果只活在第一轮的任务说明里,压几次就可能没了(本课的摘要只有 2 份写到它)。A6 讲把规矩放进每轮都会重新注入的地方,以及磁盘上那份完整历史怎么用。
你可能会问
- 「为什么不直接用 Pi 的模板重跑一遍,看摘要会不会变好?」 应该跑,本章没有这份录制。它需要项目线用同一模型、同一预算、把 L2 的指令换成 Pi 或 OpenCode 的模板再录一次——已列入本章需要补录的数据。
- 「摘要能不能不让模型写,用规则抽?」 可以做兜底:CoreCoder 没有模型可用时用
_extract_key_info抽出文件路径和含 error 的行(context.py:188-210)。它不会编造,但也写不出「下一步做什么」。