ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI编码代理上下文压缩断片实验:long_mem与engram记忆机制

AI编码代理上下文压缩断片实验:long_mem与engram记忆机制

1. 为什么"压缩完就断片"是编码代理绕不开的坎

做过 AI 编码代理的人,大概率都经历过这个场景:一个任务跑到第三十分钟,对话历史堆到几万 token,模型开始变慢、变贵,于是你上了上下文压缩——把前面的历史摘要成一段短文本,继续往下跑。结果代理突然"失忆"了:它忘了自己刚才改了哪个文件、忘了用户明确说过的约束、甚至把已经修好的 bug 又改了回去。

这不是模型笨,而是压缩这件事本身在信息论上就是有损的。你不可能把 5 万 token 的历史无损塞进 2 千 token 的摘要里,关键在于丢什么、留什么、以及丢了之后怎么找回来。我花了 10 天时间,围绕这个核心问题做了一组实验,累计留下 430 条公开可查的记录,专门观察"上下文压缩之后,AI 编码代理到底靠什么接得上"。

先说结论方向,免得你看到一半才发现不是你要的:单纯靠"更好的摘要 prompt"解决不了这个问题,因为摘要再聪明也是单向的、一次性的。真正让代理接得上的,是把压缩从"一次性动作"变成"可检索的记忆层"——也就是 long_mem 这类长期记忆机制,以及 engram 这种把关键状态编码成可召回痕迹的思路。Codex 这类编码代理在长任务里翻车,八成不是推理能力问题,而是记忆的存取策略没设计好。

这篇文章适合三类人看:正在做 AI 编码代理、被长上下文成本和质量夹击的工程师;想给自己的 Codex 类工作流加上长期记忆的实践者;以及单纯好奇"代理的记忆到底怎么工作"的技术爱好者。我会把 10 天实验的完整链路摊开讲——包括我怎么设计对照、怎么定义"接得上"、踩了哪些坑、以及最后沉淀下来的可复现方案。全程说人话,参数和步骤都给到能直接抄的程度。

2. 先把"接得上"定义清楚,否则实验没法做

2.1 什么叫"接得上":三个可观测的判据

在动手之前,我必须先解决一个元问题:怎么判断代理"接上了"?如果判据模糊,430 条记录也只是一堆主观感受。我最终锁定三个可观测、可自动打分的判据:

  • 约束保持率:用户在早期明确说过的硬约束(比如"不要动数据库 schema""必须用 TypeScript""测试文件不要改"),在压缩之后代理是否还遵守。这个可以靠事后扫描代理的 diff 自动判定。
  • 任务连续性:压缩点前后,代理是否还在推进同一个子目标,而不是突然开始做一件无关的事。我用"压缩后前 3 步操作与压缩前最后 3 步操作的主题重合度"来近似。
  • 回滚率:压缩之后,代理是否重新修改了它自己已经改对的地方,或者重新引入已经修掉的 bug。这个最致命,也最好统计——直接看 diff 里有没有"改回去"的痕迹。

这三个判据的好处是都不依赖人工主观打分,能写成脚本批量跑。10 天里我每条记录都按这三项打分,最后汇总成一张表,后面会给你看。

2.2 为什么不用"摘要质量"当判据

很多人第一反应是:那我评估摘要写得好不好不就行了?我试过,结论是这条路走不通。原因是摘要质量和代理行为之间没有稳定映射——一段读起来很漂亮的摘要,可能恰好漏掉了那个唯一重要的约束;而一段啰嗦的摘要,反而因为把细节都留着而让代理接得上。

提示:评估压缩方案时,永远评估"下游行为",不要评估"摘要文本本身"。摘要只是手段,代理接不接得上才是目的。

这个认知转变很关键。它直接决定了我的实验设计:我不去比谁的摘要 prompt 更优雅,我只比不同记忆策略下,代理的三项判据得分。

2.3 实验的基本盘:10 天、430 条记录怎么来的

实验载体是一个自建的编码代理,跑在真实的小型代码库上(约 1.2 万行 TypeScript,包含前后端和一个测试套件)。任务是从一个待办列表里随机抽取的、需要多轮交互才能完成的编码任务,比如"给某个 API 加一个分页参数并补测试""重构某个模块的错误处理"。

每天我跑大约 40 到 50 个任务,每个任务在压缩点前后各记录一次状态,最终累计 430 条有效记录。对照组是"纯摘要压缩",实验组逐步叠加 long_mem 检索和 engram 式状态编码。所有记录都保留原始日志,可复查。

3. 纯摘要压缩为什么会断片:把机制拆开看

3.1 摘要压缩的本质是"有损投影"

你可以把上下文想象成一张高分辨率照片,摘要压缩就是把它压成一张缩略图。缩略图能让你一眼看出"这是张风景照",但你要找照片里某个人的鞋带颜色,缩略图里根本没有这个信息。

代理在长任务里需要的,恰恰经常是"鞋带颜色"级别的细节:某个变量的确切名字、某个文件的确切路径、用户某句看似随口但其实是硬约束的话。摘要模型在压缩时,会优先保留"看起来重要"的语义主干,而把这类细节当噪声丢掉。问题是,对代理来说,这些细节往往才是决定下一步操作对不对的关键。

3.2 单向压缩没有"回头路"

更麻烦的是,摘要压缩是单向的。一旦你把 5 万 token 压成 2 千 token,原始信息就没了(除非你另外存了)。代理在后续推理中如果突然需要某个被丢掉的细节,它没有任何办法找回来——它只能基于残缺的摘要继续猜。

我在实验里反复观察到一种典型失败:代理在压缩后需要确认"用户之前说的那个字段名到底是 userId 还是 user_id",摘要里没写,代理就凭概率猜了一个,结果猜错,导致后面一连串修改全错。这种错误在纯摘要方案里出现频率极高。

3.3 430 条记录里,纯摘要方案的得分长什么样

先给你看对照组的数据,这是理解后面所有优化的基线:

判据纯摘要压缩得分说明
约束保持率61%近四成硬约束在压缩后丢失
任务连续性68%约三分之一任务在压缩点后跑偏
回滚率(越低越好)27%超过四分之一任务出现"改回去"

这三个数字放在一起看,你就明白为什么很多人觉得"代理一压缩就变傻"——它不是变傻,是丢了将近四成的关键约束,还有超过四分之一的任务在做无用功甚至负功。

3.4 一个具体的翻车现场

举个我记录里的真实例子。任务要求是"给用户列表接口加分页,默认每页 20 条,不要改现有的排序逻辑"。跑到第 25 分钟触发压缩,摘要写成了"为列表接口添加分页功能"。压缩后代理接着干,它加了分页,但顺手把排序逻辑"优化"了一下——因为它忘了"不要改排序"这个约束。

这个例子里,摘要本身没写错,它只是"合理地"省略了一个否定约束。而否定约束恰恰是摘要模型最容易丢的东西,因为它不携带正向语义信息。这就是纯摘要方案的结构性缺陷,不是 prompt 能补的。

4. long_mem 检索层:让被压缩掉的信息还能被捞回来

4.1 核心思路:压缩不等于删除

long_mem 这类长期记忆机制的关键洞察是:压缩可以只压缩"当前工作集",但原始信息要留在可检索的存储里。代理在需要的时候,能主动去查"用户之前到底怎么说的"。

这跟人类的做法一模一样。你开会时不会记住每一句话,但你知道"这事我记在笔记本里了,需要时翻一下"。long_mem 就是代理的笔记本,而且是带索引、能语义检索的笔记本。

4.2 落地结构:三层记忆怎么分

我在实验里把记忆分成三层,这个分层是后面所有效果提升的基础:

  • 工作记忆:当前上下文窗口里的内容,就是代理正在"盯着看"的东西。这一层会被压缩。
  • 情景记忆:按时间顺序存的所有原始交互记录,包括用户每一句话、代理每一步操作。这一层不压缩,只追加。
  • 语义记忆:从情景记忆里抽取出来的结构化事实,比如"用户约束:不要改排序逻辑""当前任务:加分页"。这一层是压缩的产物,但可回溯。

压缩时,工作记忆被压成语义记忆;但情景记忆完整保留。代理一旦发现语义记忆不够用,就去情景记忆里检索原文。

4.3 检索触发:代理怎么知道该去查

这是最容易做砸的地方。如果代理每步都去检索,成本爆炸;如果从不检索,等于没做。我的做法是设两个触发条件:

  1. 不确定性触发:当代理在生成下一步操作时,对某个关键实体(文件名、变量名、约束)的置信度低于阈值,就触发检索。实现上可以用模型对候选 token 的概率分布来近似。
  2. 冲突触发:当代理准备做一个可能违反已知约束的操作时,强制检索相关约束的原文。

第二个触发条件在实验里贡献最大。它把"约束保持率"从 61% 拉到了 84%,因为代理在动手改排序逻辑之前,会先去查"用户到底说没说不能改"。

4.4 检索的粒度:别整段捞,要捞"证据"

一开始我让代理检索时直接捞回整段历史,结果上下文又被撑爆了。后来改成只捞"证据片段"——比如只捞回用户说那句话的那一行,加上前后各一句上下文。这样单次检索通常只增加 100 到 300 token,成本可控。

注意:检索回来的内容要标记来源和时间戳,否则代理会分不清"这是历史约束"还是"这是当前指令",反而制造混乱。

4.5 加上 long_mem 之后的得分变化

判据纯摘要纯摘要 + long_mem提升
约束保持率61%84%+23 个百分点
任务连续性68%79%+11 个百分点
回滚率27%14%降低近一半

这个提升幅度说明,光是把"被压缩掉的信息还能捞回来"这件事做对,就能解决大半问题。但还没到理想状态,因为检索有延迟、有遗漏,而且代理不一定每次都意识到自己该查。

5. engram 式状态编码:把关键痕迹主动"刻"进代理

5.1 从"被动检索"到"主动留痕"

long_mem 是"需要时去查",engram 的思路更进一步:在压缩发生的那一刻,就把最关键的少数信息主动编码成一种高优先级、几乎不会被丢的痕迹,直接塞进压缩后的上下文里。

"engram"这个词借自神经科学里的"记忆痕迹"概念——某些记忆会被特别强化,形成长期稳定的痕迹。放到代理里,就是挑出那些"一旦丢失就会导致任务失败"的信息,给它们开小灶。

5.2 哪些信息值得做成 engram

不是所有信息都值得留痕,留太多等于没压缩。我在实验里总结出一份"必留清单":

  • 否定约束:所有"不要""禁止""避免"开头的指令。这类信息摘要模型最爱丢,但对任务成败影响最大。
  • 精确标识符:文件名、函数名、变量名、API 路径。这些一旦记错,后面全错。
  • 用户显式强调:用户用了"务必""一定""记住"这类词的地方。
  • 当前子目标:压缩那一刻代理正在推进的具体目标,一句话说清。

这四类信息加起来通常不超过 200 token,但覆盖了绝大多数致命失败点。

5.3 编码格式:结构化比自然语言更抗丢

engram 不能写成一段自然语言,否则它又会被下一轮压缩当成普通文本处理掉。我的做法是把它编码成结构化字段,比如:

{ "hard_constraints": ["不要修改排序逻辑", "测试文件只读"], "identifiers": { "file": "src/api/userList.ts", "function": "getUserList", "param": "pageSize" }, "current_goal": "为 getUserList 添加 pageSize 分页参数", "user_emphasis": ["默认每页 20 条"] }

这种结构化痕迹有两个好处:一是压缩时可以被识别为"高优先级"而豁免;二是代理读取时不需要解析自然语言,直接拿到确定的事实,减少误解。

5.4 痕迹的更新与失效

engram 不是一劳永逸的。任务推进后,旧的子目标完成,新的子目标出现,痕迹要更新。我的策略是:每次压缩时重新生成 engram,但硬约束和标识符做增量合并——只增不减,除非用户明确撤销。

这个"只增不减"的规则很重要。我踩过一个坑:早期版本每次压缩都重新生成全部 engram,结果某次摘要模型"觉得"某个约束已经过时了,就把它删了,导致代理后面违反了它。改成增量合并后,这类问题基本消失。

5.5 engram 叠加后的最终得分

判据纯摘要+ long_mem+ long_mem + engram
约束保持率61%84%93%
任务连续性68%79%88%
回滚率27%14%7%

到这一步,回滚率从 27% 压到 7%,约束保持率到 93%。剩下的 7% 回滚主要来自模型本身的推理波动,不是记忆问题了。

6. 把方案接到 Codex 类工作流上:实操与坑

6.1 接入点选在哪里

如果你用的是 Codex 这类现成的编码代理,你没法改它的内部推理,但你能控制它的输入组装。接入点就在"每次把上下文喂给模型之前"这一层。你要做的是:

  1. 维护一份外部的 long_mem 存储(情景记忆 + 语义记忆)。
  2. 在组装上下文时,先判断当前是否接近压缩阈值。
  3. 接近阈值时,生成 engram 并执行压缩,把 engram 放在上下文最前面。
  4. 给代理暴露一个"检索"工具,让它能主动查情景记忆。

第 4 步是关键。很多 Codex 工作流翻车,就是因为代理没有检索能力,只能被动接受压缩后的残缺上下文。

6.2 压缩阈值怎么定

我实验里用的阈值是"上下文窗口的 70%"。低于这个值不压缩,高于就触发。为什么是 70% 而不是 90%?因为压缩本身要消耗 token(要调用模型生成摘要和 engram),留 30% 的余量给这个操作,避免压缩到一半窗口就满了。

这个值不是死的。如果你的任务普遍较短,可以调到 80%;如果任务超长且约束密集,调到 60% 更稳。

6.3 检索工具的描述怎么写

代理能不能用好检索工具,一半取决于工具描述。我踩过的坑是:描述写得太抽象("查询历史记忆"),代理根本不知道该查什么。后来改成带示例的具体描述:

查询历史交互记录。当你需要确认用户之前说过的约束、 某个文件或变量的确切名称、或某个已完成的子目标时使用。 示例查询:"用户对排序逻辑的约束"、"getUserList 的参数名"

加上示例之后,代理主动检索的准确率明显上升。

6.4 几个实测中的意外情况

  • 检索回来的旧约束和当前指令冲突:比如用户后来说"这次可以改排序了",但检索捞回了早期的"不要改排序"。解决办法是给每条记忆打时间戳,并在冲突时以最新为准,同时把冲突显式告诉代理。
  • engram 里的标识符拼写漂移:某次摘要模型把getUserList写成了getUserlist,大小写错了,代理照着错的找文件,找不到。后来我在 engram 生成后加了一步"标识符校验",对照代码库里的真实符号表纠正拼写。
  • 压缩太频繁导致代理"注意力涣散":如果每几轮就压缩一次,代理会频繁丢失短期上下文。我的经验是压缩间隔至少 8 到 10 轮,除非窗口真的快满了。

6.5 成本账:这套方案贵不贵

很多人担心加检索和 engram 会让成本飙升。实测下来,单任务平均 token 消耗比纯摘要方案高约 18%,但任务成功率提升带来的重试减少,把总成本反而拉低了约 12%。因为纯摘要方案里那些"改回去再改回来"的操作,本身就是巨大的浪费。

方案单任务平均 token任务成功率综合成本指数
纯摘要基准基准100
+ long_mem+12%+15%96
+ long_mem + engram+18%+27%88

综合成本指数越低越好。可以看到,虽然单任务 token 涨了,但因为成功率上去了,整体反而更省。

7. 430 条记录里最值得记住的几条经验

7.1 否定约束是头号杀手,必须特殊对待

整个实验里,导致失败最多的单一因素就是否定约束丢失。摘要模型对"不要做什么"的保留能力,远弱于"要做什么"。所以任何压缩方案,都要把否定约束单独拎出来做 engram,不要指望摘要模型帮你留。

7.2 记忆的价值在于"可回溯",不在于"记得多"

我一开始的误区是拼命让代理记住更多,结果上下文越来越臃肿,效果反而差。后来想通了:代理不需要记住所有事,它只需要知道"去哪查"和"什么绝对不能忘"。前者靠 long_mem,后者靠 engram,中间的大量细节让它按需检索就好。

7.3 结构化永远打败自然语言

无论是 engram 还是检索结果,只要条件允许就上结构化格式。自然语言在压缩和传递过程中会被反复"重新理解",每次理解都可能引入偏差。结构化字段是确定的,不会漂移。

7.4 别忽略"压缩时机"这个变量

同样的方案,在不同压缩时机下效果差异很大。我的经验是:在代理完成一个子目标、即将开始下一个子目标的间隙压缩,效果最好。因为这时候上下文里"已完成"的部分可以放心压掉,"待进行"的部分还没产生。如果在子目标进行到一半时压缩,代理很容易丢失当前进度。

7.5 这套方案不是终点

10 天 430 条记录给我的最大体会是:上下文压缩和记忆管理,本质上是同一个问题的两面。你不可能靠一个技巧一劳永逸,只能不断逼近"让代理在长任务里保持连贯"这个目标。long_mem 和 engram 是目前我验证下来性价比最高的组合,但它们的参数(检索阈值、engram 字段、压缩间隔)都需要针对你的具体任务调。

我个人的做法是:先把 engram 的"必留清单"固定下来,这是收益最大、最不依赖调参的一步;然后再慢慢调 long_mem 的检索触发条件。如果你刚开始做,别一上来就追求全套,先把否定约束和标识符这两类信息管住,你会发现代理的"断片"问题立刻缓解一大半。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进