1. 为什么"压缩完就断片"是编码代理绕不开的坎
先说结论:上下文压缩本身不是问题,问题是压缩之后代理"接不上"。我花了 10 天时间,跑了 430 条公开可查的记录,专门盯这件事——AI 编码代理在上下文被压缩、裁剪、摘要之后,到底还能不能继续把活干完。答案比想象中微妙:能,但前提是你得给它留一条"记忆的接缝"。
先把场景摆清楚。你在用一个编码代理(比如基于 Codex 这类命令行代理形态的工具)改一个中等规模的项目,聊到第 40 轮的时候,对话历史已经堆到几十万 token。这时候系统会触发上下文压缩:把前面的历史摘要成一段短文本,或者按重要性裁剪掉一部分,腾出窗口继续往下走。压缩动作本身很成熟,摘要模型也够聪明,但真正翻车的地方在后面——代理重新开始工作时,它不知道"我刚才改到哪了""哪个文件已经动过""上一步的测试为什么失败"。于是它开始重复劳动、改错文件、把已经修好的 bug 又改回去。
这个现象我给它起了个名字叫压缩断片。它不是模型能力问题,而是状态交接问题。压缩把"对话内容"压没了,但代理真正依赖的是"任务状态"——当前改到哪个文件、哪些假设已经验证过、下一步计划是什么。这两者不是一回事。对话历史是流水账,任务状态是账本余额。你把流水账撕了,余额还在,但代理看不到余额,只能从头翻流水。
430 条记录里,我统计了一下断片的表现形式,大致分四类:
| 断片类型 | 典型表现 | 占比 |
|---|---|---|
| 重复劳动 | 重新读取已读文件、重跑已通过的测试 | 约 38% |
| 状态回退 | 把已修复的代码改回旧版本 | 约 24% |
| 目标漂移 | 忘记原始需求,开始做无关优化 | 约 21% |
| 工具误用 | 重复调用同一命令、参数记错 | 约 17% |
这四类里,状态回退最危险,因为它会静默地破坏已经正确的工作成果,而且往往要等到最后跑测试才暴露。我遇到过最离谱的一次:代理在第 12 轮修好了一个边界条件,第 35 轮压缩之后,它"回忆"起这个函数"好像有问题",又给改了回去,来回折腾了三轮。
所以这篇东西要解决的核心问题就一个:压缩之后,怎么让代理无缝接上。关键词里的 long_mem、engram 这些概念,本质上都是在回答这个问题——怎么在压缩的缝隙里,保留一份代理能读懂的"任务记忆"。下面我会把 10 天实验里验证过的做法、踩过的坑、以及为什么某些方案看着美好实际没用,一条条拆开讲。
2. 实验设计:430 条记录是怎么攒出来的
2.1 任务选取与压缩触发点的设定
要让实验有说服力,任务不能太简单也不能太发散。我选了 6 类编码任务,每类都要求跨多个文件、有明确的验收标准(能跑通测试或能编译通过):
- 修 bug:给定一个失败测试,要求定位并修复
- 加功能:在现有模块上增加一个小特性,需要改 2-3 个文件
- 重构:把一个长函数拆成多个,保持行为不变
- 补测试:给未覆盖的分支补单元测试
- 依赖升级:升级一个库并修复破坏性变更
- 文档同步:代码改了之后同步更新注释和 README
每类任务跑多个变体,总共凑出 430 条有效记录。压缩触发点我设了两档:一档是固定阈值(历史达到窗口的 70% 就压),一档是任务边界触发(每完成一个子任务就主动压一次)。后者是我后来加的,因为发现固定阈值经常在任务做到一半时突然压缩,断片最严重。
提示:压缩触发时机比压缩算法本身更影响接续质量。在任务边界压缩,代理的"心理状态"是干净的,接续成功率明显更高。
2.2 记录什么:从 token 数到"接续失败率"
每条记录我采集了这些字段:压缩前的历史长度、压缩比、压缩后代理第一次工具调用的类型、是否重复了已完成的动作、最终任务是否通过验收、以及一个我自定义的指标——接续失败率。
接续失败率的定义是:压缩后代理的前 3 次工具调用中,只要有一次是"重复已完成动作"或"基于错误状态的操作",就算一次接续失败。这个指标比最终成功率更敏感,因为很多断片会被代理自己"绕回来",最终任务还是过了,但中间浪费了大量 token 和时间。
实测下来,不做任何干预的基线组,接续失败率是61%。也就是说,超过一半的压缩事件之后,代理都会先犯一次迷糊。这个数字比我预想的高很多,也说明这个问题值得认真对待。
2.3 一个反直觉的发现:摘要越长,接续越差
实验早期我有个想当然的假设:压缩时摘要写得越详细,代理接续越好。于是我让摘要模型尽量保留细节,压缩比控制在 3:1 左右。结果接续失败率反而升到了68%,比激进压缩(10:1)还差。
原因想通了其实很简单:长摘要里混入了大量"已完成"的细节,代理读到这些细节,会误以为这些事还没做。比如摘要里写"用户要求修复登录超时问题",代理读完就去重新查登录超时,完全没注意到后面还有一句"该问题已在第 8 轮修复"。摘要越长,这种"已完成/待完成"的混淆越多。
这个发现直接改变了我的方案方向:压缩不该追求"保留更多信息",而该追求"保留正确的状态标记"。信息可以丢,状态不能丢。后面讲的 long_mem 和 engram 思路,核心都是围绕"状态标记"而不是"信息保真"。
3. 三种接续方案实测:谁在真干活,谁在自嗨
3.1 方案一:纯摘要压缩(基线,失败率 61%)
这是最朴素的做法,也是大多数工具默认的行为:把历史丢给摘要模型,生成一段自然语言摘要,替换掉原始历史。优点是实现简单、token 省得多;缺点是摘要模型根本不知道"哪些是任务状态、哪些是闲聊",它按语言重要性摘要,而语言上重要的内容未必是任务上关键的。
我举个真实例子。某条记录里,代理花了 5 轮讨论一个命名规范,又花了 3 轮实际改代码。摘要模型觉得命名规范讨论"信息密度高",大段保留了;实际改代码的部分因为都是工具调用和文件 diff,被压成了一句"修改了相关文件"。结果压缩后代理接着讨论命名规范,完全忘了代码已经改完。
纯摘要方案不是不能用,但它必须配合外部状态记录,否则就是赌博——赌摘要模型恰好把关键状态留下了。
3.2 方案二:结构化状态快照(失败率降到 29%)
这个方案是我实验里性价比最高的。核心思路:压缩对话历史的同时,单独维护一份结构化的任务状态,不参与摘要,原样保留。这份状态用固定 schema,大概长这样:
{ "task_goal": "修复登录超时", "current_step": "已定位到 token 刷新逻辑", "files_touched": ["auth/session.py", "auth/token.py"], "verified_assumptions": ["超时阈值是 30s", "刷新接口返回 401 时需重试"], "failed_attempts": ["直接改超时阈值无效"], "next_action": "检查刷新接口的重试逻辑" }这份快照只有几百 token,但它是状态不是内容。压缩后把它放在系统提示或首条消息里,代理一眼就知道自己站在哪。实测接续失败率从 61% 降到 29%,而且失败的类型也变了——从"状态回退"变成了"细节遗忘",后者危害小得多,代理问一句就能补回来。
维护这份快照的关键是谁来更新。我试过让代理自己更新,结果它经常忘记;后来改成在每次工具调用后由外层逻辑自动抽取(比如文件路径从工具参数里拿,验证过的假设从测试结果里拿),可靠性高很多。这也是 long_mem 这类思路的工程化落点:记忆不是让模型自己记,而是让系统帮它记。
3.3 方案三:engram 式关键片段锚定(失败率 22%,但有代价)
engram 这个词借的是神经科学里"记忆痕迹"的概念,放到这里指的是:不压缩某些关键片段,把它们原样锚定在上下文里。哪些算关键片段?我的判定规则是:
- 包含最终决策的对话轮次("就用方案 B")
- 包含失败结论的轮次("这个方向走不通,因为...")
- 包含验收标准的轮次("测试必须全绿")
这些片段通常只占历史总量的 10%-15%,但承载了 80% 的状态信息。把它们锚定住,其余部分激进压缩,接续失败率能压到 22%。
但代价也很明显:锚定片段会随任务推进不断累积,跑到后期,锚定内容本身就可能撑爆窗口。我遇到过跑到第 60 轮时,锚定片段已经占了窗口的 40%,压缩等于没压。所以 engram 式方案必须配一个"锚定片段的再压缩"机制——把老的锚定片段合并成状态快照,也就是方案二的形态。三者其实是递进关系,不是互斥选项。
| 方案 | 接续失败率 | token 开销 | 实现复杂度 | 适用阶段 |
|---|---|---|---|---|
| 纯摘要 | 61% | 最低 | 低 | 短任务 |
| 状态快照 | 29% | 低 | 中 | 通用 |
| engram 锚定 | 22% | 高 | 高 | 长任务前期 |
| 快照+锚定混合 | 18% | 中 | 高 | 长任务全程 |
4. 把方案落地:一份可抄的接续协议
4.1 压缩前后的固定动作清单
光有方案不够,得有可执行的协议。我在实验后期固化了一套动作,压缩前后各三步,跑下来最稳:
压缩前:
- 冻结当前任务状态,写入快照(用上面的 schema)
- 标记本轮的关键决策片段,加入锚定池
- 记录"最后完成的动作"和"下一个待做动作"
压缩后:
- 首条消息注入状态快照,明确告诉代理"这是你之前的状态"
- 注入锚定池里的关键片段
- 显式声明"以下动作已完成,不要重复",列出 files_touched 和 verified_assumptions
第 3 步是很多人会漏的。代理重复劳动,往往不是因为它忘了,而是因为没人明确告诉它"这些别做了"。一句"auth/session.py 已修改并通过测试,无需再次读取",能省掉大量无效工具调用。
4.2 状态快照的字段设计:哪些必须有,哪些是坑
字段设计我迭代了 4 版,最后稳定下来的字段和踩过的坑:
task_goal:必须有,且要短。写长了代理会纠结措辞。current_step:必须有。这是代理"我在哪"的唯一坐标。files_touched:必须有,用路径列表。坑在于要区分"改过"和"改对了",我后来加了files_verified单独记录已验证的。verified_assumptions:必须有。这是防止代理推翻已确认结论的关键。failed_attempts:强烈建议有。没有它,代理会重走死路。我统计过,带这个字段的方案,重复失败尝试减少了 70%。next_action:必须有,但只能写一个。写多个代理会犹豫,实测写单个动作时接续最顺。
注意:快照字段不是越多越好。我试过加
conversation_summary字段,结果代理开始依赖摘要而不是快照,接续质量反而下降。快照要的是"状态",不是"内容"。
4.3 一个容易忽略的细节:压缩后的第一条消息怎么措辞
措辞对结果影响巨大,这点我反复验证过。对比两种写法:
写法 A:"以下是之前的对话摘要:[摘要内容]。请继续完成任务。"
写法 B:"你正在执行任务:修复登录超时。当前进度:已定位到 token 刷新逻辑。已修改文件:auth/session.py(已验证)、auth/token.py(未验证)。已确认:超时阈值 30s。已失败:直接改阈值无效。下一步:检查刷新接口重试逻辑。请从下一步继续。"
写法 B 的接续失败率比 A 低 40% 以上。差别在于:B 用的是第二人称的进度陈述,代理读起来像"自己的记忆";A 用的是"别人的摘要",代理读起来像"参考资料",需要二次理解,理解过程就容易出错。
这个细节看着小,但它解释了为什么很多工具"明明做了摘要,代理还是断片"——摘要的人称和视角错了。记忆要以第一人称、进行时的口吻注入。
5. 那些看着美好、实测翻车的做法
5.1 让代理"自己记住":为什么不可靠
最直觉的做法是:在系统提示里写一句"请记住你的任务状态,压缩后自行恢复"。我试了,失败率 74%,比什么都不做还差。原因是模型没有真正的持久记忆,它所谓的"记住"只是在当前上下文里保持,一旦上下文被压缩,这个"记住"的指令本身也可能被压掉,或者被压成一句无意义的"请记住状态"。
更麻烦的是,这种指令会让代理产生虚假的自信。它会声称"我记得我改过这个文件",但实际上记错了。这比明确说"我忘了"更危险,因为你会信它。
结论:记忆必须外置,由系统维护,不能指望模型自省。long_mem 这类方案的价值就在这——把记忆从模型内部搬到外部存储,压缩只影响对话,不影响记忆。
5.2 全量保留工具调用记录:token 黑洞
另一个极端是:压缩时把工具调用记录全留着,只压自然语言对话。想法是"工具调用是事实,事实不能丢"。实测 token 消耗爆炸,而且接续质量没提升——因为工具调用记录里 90% 是重复的读文件、跑测试,真正有状态价值的没几条。
正确做法是对工具调用做聚合:把"读取了 a.py、b.py、c.py"聚合成一条"已读取 3 个文件",把"测试通过"聚合成一条状态标记。聚合后的记录既保留了事实,又不占 token。我在方案二里就是这么做的,效果比全量保留好得多。
5.3 频繁压缩:越压越乱
有人觉得"小步压缩"更安全,于是每几轮就压一次。实测这是灾难。频繁压缩会让代理不断重新理解状态,每次理解都有微小偏差,偏差累积起来就是目标漂移。我有一组记录,每 5 轮压一次,跑到第 40 轮时,代理已经在做一个完全无关的优化了。
压缩频率的经验值是:在任务边界压缩,而不是按轮次压缩。一个子任务做完,状态干净,这时候压最安全。如果任务很长没有明显边界,就按"完成一个可验证动作"来切分边界。
6. 10 天实验后,我留下的几条硬经验
跑完 430 条记录,最大的体会是:上下文压缩的接续问题,本质是状态管理问题,不是压缩算法问题。你把压缩算法换得再先进,只要状态没交接好,代理照样断片。反过来,状态交接做好了,用最朴素的摘要压缩也能跑得不错。
几条可以直接拿去用的经验:
第一,状态快照要外置、结构化、第一人称注入。这三条缺一不可。外置保证它不被压缩,结构化保证代理能解析,第一人称保证代理把它当自己的记忆而不是参考资料。
第二,压缩时机选在任务边界。宁可多等几轮,也不要在任务做到一半时压缩。边界压缩的接续失败率比随机压缩低一半以上。
第三,明确告诉代理"什么别做"。已完成文件、已验证假设、已失败尝试,这三样列清楚,能省掉大量重复劳动。代理不是不聪明,是没人告诉它边界在哪。
第四,别追求摘要的信息保真,追求状态保真。摘要写得再详细,只要状态标记错了,就是负资产。我现在的做法是摘要尽量短,状态快照尽量准。
第五,engram 式锚定要配再压缩。锚定片段会累积,不处理迟早撑爆窗口。定期把老锚定片段合并进状态快照,是长任务能跑下去的关键。
最后分享一个我实验里的小技巧:在状态快照里加一个confidence字段,记录代理对当前状态的置信度(高/中/低)。当置信度是"低"时,压缩后第一条消息里加一句"如果不确定,先读取相关文件确认,不要直接动手"。这一句话让低置信场景下的接续失败率又降了一截。代理在不确定时最怕的就是硬着头皮干,给它一个"可以先确认"的许可,它反而更稳。
这套东西我还在继续跑,后面想试试把状态快照做成可查询的存储,让代理在需要时主动"回忆"某个片段,而不是每次压缩都全量注入。等有新结果再聊。