
1. 这个实验到底在折腾什么先交代背景。我做 AI 编码代理的日常使用和调优差不多两年主力工具是 Codex 这一类命令行编码代理。用久了就会发现一个很尴尬的事长会话跑到一定轮次之后代理会突然“失忆”。前面聊过的架构决策、改过的文件路径、约定好的命名规范它全忘了然后开始重复问你已经回答过的问题或者更糟——按它自己脑补的假设去改代码把之前对的东西改坏。这不是模型笨是上下文窗口满了之后触发了压缩compaction。压缩本身是好事它把历史对话摘要成一段更短的文本塞回窗口让会话能继续。但问题在于压缩是有损的。摘要丢掉了大量细节尤其是那些“当时看起来不重要、后面却反复要用”的信息比如某个函数为什么这么命名、某个依赖为什么锁在特定版本、某个接口的字段顺序为什么不能动。所以我做了这个实验连续 10 天用同一个编码代理跑真实项目任务记录 430 条公开可查的交互记录专门观察上下文压缩之后代理的“接续能力”到底掉了多少以及用什么办法能把它接回来。关键词里提到的 long_mem、engram 就是我在实验里试的两条技术路线前者是外挂长期记忆后者偏向把关键信息编码成可检索的片段。这篇文章适合谁看如果你只是偶尔用 AI 写个脚本那压缩问题你基本碰不到。但如果你把编码代理当成日常主力、一个会话要跑几百轮、跨多个文件做重构那这篇里的坑你迟早会踩。我会把实验设计、观察到的现象、试过的补救方案、以及最后跑通的配置都摊开讲能直接抄的部分我会标出来。先说结论方向免得你看到一半觉得我在绕压缩本身不可怕可怕的是压缩之后代理不知道自己丢了什么。大部分“接不上”的案例根因不是信息真的没了而是代理没有意识到需要去把信息找回来。所以解法的大头不在“压缩算法”而在“压缩后的重建机制”。2. 实验设计与记录方法2.1 为什么选 10 天和 430 条这个量级10 天不是我随便定的。编码代理的上下文压缩通常不是一次性的而是随着会话推进反复触发。单次压缩的影响可能被模型自身的推理能力掩盖只有跨多天、多轮压缩叠加之后退化才会明显到可观测。我前 3 天基本在摸清压缩触发的节奏中间 4 天做对照实验最后 3 天验证补救方案10 天刚好覆盖一个完整的“发现问题—定位原因—验证解法”闭环。430 条记录是这么来的每天平均 43 条有效交互剔除掉纯闲聊和重复确认剩下的都是有明确任务意图的轮次。每条记录我固定记五个字段轮次编号、是否刚经历压缩、代理的输出是否偏离预期、偏离类型、我采取的干预手段。这个字段设计很关键后面做统计全靠它。提示如果你也想做类似观察别一上来就记几百条。先跑 20 条把字段定死否则中途改字段会导致前面数据作废。我第一天就吃了这个亏白记了 30 多条。2.2 记录字段的设计逻辑为什么是这五个字段而不是更多因为观察的目标是“压缩与接续失败的相关性”字段太多会稀释注意力太少又无法归因。我解释一下每个字段的取舍轮次编号用来定位压缩发生在第几轮以及失败集中在压缩后的第几轮。实测下来失败高发区是压缩后的第 2 到第 5 轮第 1 轮反而正常因为摘要内容还热乎。是否刚经历压缩布尔值但我会额外标注“距上次压缩的轮数”。这个细节后来证明非常有用退化程度和距压缩轮数呈明显的先升后降曲线。输出是否偏离预期主观判断但我给自己定了硬标准——只要代理需要我重复一遍已经说过的信息就算偏离。偏离类型我归了四类分别是信息丢失、信息错配、过度泛化、路径漂移。这个分类是实验中期才稳定下来的前面几天的数据我按新分类重新标注过一遍。干预手段记录我当时做了什么比如重述、贴文件、换会话、挂记忆。这是后面提炼解法的一手素材。2.3 任务类型的选择与配比如果 10 天全跑同一种任务结论会偏。我刻意让任务类型有配比大致是跨文件重构占 40%新功能开发占 30%bug 定位占 20%文档与注释维护占 10%。这个配比接近我真实的工作分布也让不同压缩敏感度的任务都能被覆盖到。跨文件重构占比最高是因为它对上下文连续性最敏感。你改一个函数的签名代理得记得所有调用点在哪、每个调用点的上下文约束是什么。压缩一旦丢掉调用点清单代理就会漏改或者乱改。新功能开发相对独立压缩影响小一些但也不是没有尤其是涉及既有代码风格约定的时候。3. 压缩之后到底丢了什么四类偏离的实录3.1 信息丢失最直白也最容易被发现信息丢失是最好识别的一类。典型表现是代理直接问你“你之前提到的那个配置文件路径是什么”或者“这个模块的入口函数叫什么”这类偏离的好处是它自己会暴露你补一句就能继续。坏处是它打断节奏而且如果它不问你、直接猜就会滑向第二类。我记录里信息丢失占了偏离总数的约 38%。有意思的是丢失的内容有明显偏好路径、命名、版本号、字段顺序这四类信息最容易丢而任务目标、大方向反而不容易丢。这符合直觉摘要倾向于保留“要做什么”牺牲“具体怎么做”。注意信息丢失本身不可怕可怕的是代理不承认自己丢了。我遇到过好几次它明明忘了路径却编了一个看起来很像的路径继续往下写结果文件根本不存在。这种“自信的幻觉”比直接提问危险得多。3.2 信息错配把 A 的约束套到 B 上信息错配是我觉得最隐蔽、也最费时间的一类。表现是代理记得某些信息但张冠李戴。比如它记得“这个项目要求所有对外接口用蛇形命名”然后把这个约束套到了内部工具函数上而内部函数其实用的是驼峰。它没丢信息但把信息的适用范围搞错了。这类偏离占约 27%。根因通常是压缩摘要把多个相似约束合并成了一条丢掉了“作用域”这个维度。摘要写“项目命名规范蛇形”但原文其实是“对外接口蛇形内部驼峰”。压缩一合并作用域就没了。3.3 过度泛化从具体案例跳到错误通则过度泛化占约 21%。代理会把某一次具体的修复经验当成通用规则套到所有类似场景。比如我在某轮里让它给一个特定函数加了空值检查压缩之后它开始给所有函数都加空值检查包括那些参数根本不可能为空的。这类偏离的麻烦在于它看起来“很勤奋”输出量还变大了你如果不仔细看 diff很容易放过。我后来养成了一个习惯压缩之后的头几轮diff 必须逐行看不能只看它说“已完成”。3.4 路径漂移目标悄悄变了路径漂移占约 14%数量最少但危害最大。代理在压缩之后对任务目标的理解发生了微妙偏移。比如原任务是“把 A 模块的日志级别从 debug 调到 info”压缩之后它理解成“优化 A 模块的日志”然后顺手把日志格式也改了、把日志内容也精简了。方向没错但范围超了。这类偏离往往要到你 review 的时候才发现因为代理自己觉得干得挺好。我记录里最严重的一次代理在压缩后把整个模块的错误处理策略都改了理由是“顺手优化”结果引入了一个新的边界 bug。偏离类型占比识别难度主要根因信息丢失38%低摘要丢弃具体细节信息错配27%中摘要合并丢失作用域过度泛化21%中具体经验被抽象成通则路径漂移14%高任务目标被重新解读4. 两条补救路线long_mem 与 engram 的实测对比4.1 long_mem 路线外挂长期记忆long_mem 的思路很直接既然压缩会丢信息那我就在压缩之外单独维护一份长期记忆把关键信息存起来压缩后按需检索回来。我试的实现方式是维护一个结构化的记忆文件按“项目—模块—约束”三级组织每次压缩触发后代理先读这个文件再继续。实测下来long_mem 对信息丢失的改善最明显丢失类偏离从 38% 降到了约 15%。因为路径、命名这些信息本来就是结构化的存进去、读出来都很顺。但对信息错配和路径漂移改善有限因为这两类问题的根因是“理解偏差”不是“信息缺失”你把信息摆它面前它照样可能理解错。long_mem 的另一个坑是维护成本。记忆文件会越来越大如果不做定期清理检索出来的内容里会混入过时信息反而制造新的错配。我后来加了个规则每条记忆带一个“最后验证轮次”超过 50 轮没被引用的记忆自动降权。4.2 engram 路线把关键信息编码成可检索片段engram 的思路更偏“编码”。它不存原始信息而是把关键决策和约束编码成短小的、带语义标签的片段压缩后通过语义相似度检索。好处是存储紧凑、检索快坏处是编码过程本身有损如果编码规则没定好检索出来的片段可能对不上。我试的编码规则是“约束—场景—反例”三段式。比如“对外接口用蛇形—场景是所有 HTTP handler—反例是内部工具函数用驼峰”。这个三段式对信息错配的改善很明显错配类偏离从 27% 降到了约 12%因为“反例”这一段专门用来界定作用域。但 engram 对信息丢失的改善不如 long_mem因为编码过程会主动丢弃它认为不重要的细节而“重要”的判断标准是我定的难免有偏差。实测丢失类偏离只降到约 22%。4.3 两条路线的组合与取舍单用哪条都不够。我最后的方案是组合long_mem 负责存结构化事实engram 负责存约束与作用域。压缩触发后先读 long_mem 补事实再用 engram 校准约束边界。组合之后四类偏离的总占比从基准的 100% 降到了约 35%其中路径漂移降幅最大从 14% 降到约 5%。方案信息丢失信息错配过度泛化路径漂移无补救基准38%27%21%14%仅 long_mem15%24%19%13%仅 engram22%12%16%9%组合方案12%10%8%5%提示组合方案不是简单叠加两条路线的检索结果需要去重和冲突消解。我的做法是 long_mem 的事实优先engram 的约束用来修正事实的适用范围冲突时以 engram 为准因为约束边界比具体事实更容易验证。5. 实操把补救机制接进编码代理的完整流程5.1 记忆文件的结构设计先说 long_mem 的文件结构。我用的是纯文本加固定分隔符不用数据库因为编码代理读文本最顺数据库还得走查询接口多一层就多一个出错点。结构是三级[PROJECT: 项目名] [MODULE: 模块名] [FACT: 事实类型] 具体内容 | 最后验证轮次: N事实类型我固定了几种PATH路径、NAME命名、VERSION版本、ORDER顺序、CONSTRAINT约束。固定类型的好处是检索时可以按类型过滤不用全文扫。最后验证轮次用来做老化清理前面提过。写记忆的时机很关键。我的做法是不主动写只在代理明确确认某个信息之后写。比如代理问“这个路径对吗”我回答“对”这时候才把路径写进记忆。如果代理没确认我不写避免把错误信息固化进去。5.2 engram 片段的编码规则engram 片段我用三段式中间用竖线分隔约束内容 | 适用场景 | 反例编码时机是在每次压缩触发之前。我会在压缩前扫一遍最近的对话把新出现的约束抽出来编码。这个动作我一开始手动做后来写了个简单的脚本辅助但脚本只做抽取建议最终编码还是我确认因为编码质量直接决定检索质量。注意engram 的反例段千万别省。我一开始觉得反例是冗余省了几天结果信息错配立刻回升。反例的作用是给约束划边界没有边界约束就会到处乱套。5.3 压缩触发后的重建流程压缩触发后我固定走四步读 long_mem按当前任务涉及的模块把相关事实读出来作为上下文前缀塞给代理。检索 engram用当前任务描述做语义检索取最相关的 3 到 5 条约束片段。显式声明在给代理的输入里明确写“以下是已确认的事实和约束请以此为准”而不是默默塞进去。显式声明能显著降低代理“自作主张”的概率。首轮验证压缩后的第一轮我不派发新任务而是让代理复述当前任务目标和关键约束确认它接上了再继续。这四步里第三步和第四步是我踩坑之后加的。一开始我只做前两步发现代理经常忽略塞进去的内容因为它不知道这些内容比它自己的记忆更权威。显式声明之后忽略率大幅下降。5.4 一个完整的重建示例假设当前任务是重构用户模块的接口。压缩触发后我的输入大概长这样[已确认事实] - 用户模块入口: src/user/index.ts | 验证轮次: 142 - 对外接口命名: 蛇形 | 验证轮次: 138 - 依赖版本: zod3.22.0 | 验证轮次: 140 [已确认约束] - 对外接口用蛇形 | 适用所有 HTTP handler | 反例: 内部工具函数用驼峰 - 错误码统一用 E_ 前缀 | 适用所有抛错点 | 反例: 第三方库透传的错误不改 请先复述当前任务目标和上述约束确认后再开始重构。这段输入不长但把最容易丢的信息都覆盖了。实测下来带这段输入的压缩后首轮偏离率比不带低了一半以上。6. 常见问题与排查速查6.1 代理忽略记忆内容怎么办这是最高频的问题。代理明明收到了记忆内容还是按自己的理解干。排查顺序是先确认记忆内容是不是放在了输入的最前面中间隔了太多内容会被稀释再确认有没有显式声明权威性最后确认记忆内容本身有没有冲突冲突的内容代理会倾向于忽略全部。我的经验是显式声明 放在最前 内容无冲突三条都满足忽略率能压到很低。如果还忽略那大概率是记忆内容太长代理的注意力被分散了需要精简。6.2 记忆文件越来越大怎么清理不要等它大了才清。我的做法是每次会话结束跑一次老化超过 50 轮没被引用的记忆降权超过 100 轮的直接归档到冷存储不参与日常检索。归档不是删除需要的时候还能捞回来。这样日常检索的记忆量能稳定在一个可控范围。6.3 压缩后代理反复问同样的问题这说明记忆检索没生效或者检索出来的内容和问题对不上。先检查检索的关键词是不是太泛泛关键词会召回一堆不相关记忆把真正相关的挤掉。我的做法是检索时带上模块名做过滤缩小范围再排序。6.4 组合方案下两条路线冲突怎么判前面提过以 engram 的约束为准。但有个例外如果 long_mem 的事实带“最后验证轮次”且比 engram 片段新那以 long_mem 为准。因为事实是可以验证的约束是推断的新验证的事实优先级更高。问题现象可能原因排查动作代理忽略记忆未显式声明/位置靠后/内容冲突前置声明去冲突记忆膨胀未做老化清理按轮次降权归档反复问同一问题检索关键词太泛加模块名过滤两路线冲突优先级未定约束优先新事实例外压缩后首轮就偏未做首轮验证强制复述确认6.5 几个我踩过的坑第一个坑是记忆写入太积极。我一开始代理说什么我都记结果记忆里混入了大量未确认的猜测检索出来反而误导。后来改成只记确认过的质量立刻上来了。第二个坑是engram 编码太细。我把每个小约束都编码片段数量爆炸检索精度反而下降。后来合并同类约束片段数控制在 20 条以内效果最好。第三个坑是首轮验证省略。有几天我赶进度压缩后直接派任务结果偏离率明显上升。首轮验证看着费时间其实省的是后面返工的时间。7. 这套机制还能怎么扩展跑完这 10 天我最大的体会是上下文压缩不是要解决的问题而是要共存的现实。与其想着怎么让压缩不丢信息不如想着怎么让代理在丢了信息之后能快速接回来。这个思路转变之后很多之前觉得棘手的问题都变得可管理了。后续我打算往两个方向扩展。一是把记忆的写入和检索做成半自动减少手动确认的负担但保留人工兜底因为全自动的记忆质量我还不放心。二是把 engram 的编码规则从三段式扩展到带时间维度的四段式加上“约束生效时间”用来处理那些会随时间变化的约束比如某个版本之后才生效的命名规范。如果你也在用编码代理跑长会话建议你先从记录开始跑个二三十轮看看你自己的场景里哪类偏离最多再针对性地上补救。别一上来就全套照搬我的配比是基于我的任务分布你的可能不一样。工具是死的场景是活的先观察再动手比什么都强。