1. 这个实验到底在折腾什么
先把这个标题拆开看。上下文压缩,指的是 AI 编码代理在长会话里,因为上下文窗口有上限,不得不把历史对话、代码片段、工具调用记录做摘要或截断,腾出空间给新内容。AI 编码代理,就是那种能自己读文件、改代码、跑命令、看报错的自动化编程助手,比如 Codex 这类 CLI 工具。long_mem和engram是两种记忆机制的代称,前者偏向长期记忆存储,后者偏向痕迹式记忆召回。整个实验的核心问题就一句话:当上下文被压缩之后,代理还能不能接得上之前的工作,不至于“失忆”或者“跑偏”。
我之所以对这个题目感兴趣,是因为我自己用 Codex 这类工具做项目时,最头疼的不是它不会写代码,而是它在长任务里会“断片”。你让它改一个跨十几个文件的模块,前半小时它还记得你定的命名规范、接口约定、测试策略,等到上下文一压缩,它就开始自由发挥,变量名风格变了,错误处理方式变了,甚至把你之前明确否掉的方案又捡回来。这不是模型能力问题,是记忆衔接问题。
这个实验用了 10 天、430 条公开记录来做验证,说明它不是拍脑袋写个 demo,而是有持续观察和记录的系统性尝试。适合谁来读?如果你正在用 Codex、Cursor、Claude Code 这类编码代理做真实项目,尤其是那种单次会话超过几十分钟、涉及多文件改动的任务,那这篇内容就是给你写的。如果你只是偶尔让 AI 补个函数,可能感受不深,但只要你开始把编码代理当“半个同事”用,上下文压缩后的衔接问题迟早会撞上你。
我下面会从设计思路、核心机制、实操过程、问题排查几个角度,把这个实验的逻辑和我自己的经验揉在一起讲。不是复述那 430 条记录,而是把里面最值得抄作业的部分拆出来。
2. 整体设计与思路拆解
2.1 为什么上下文压缩一定会导致“接不上”
先讲清楚一个底层事实:所有大模型的上下文窗口都是有限的。你不可能把整个项目的所有文件、所有历史对话、所有工具输出都塞进去。当会话长度逼近上限时,系统必须做取舍。常见的做法有三种:滑动窗口截断、摘要压缩、关键信息抽取。滑动窗口最粗暴,直接把旧内容扔掉;摘要压缩是用模型把旧对话浓缩成一段话;关键信息抽取则是挑出文件路径、函数签名、决策记录这类结构化信息保留。
问题在于,这三种方式都会丢信息。滑动窗口丢的是连续性,摘要压缩丢的是细节,关键信息抽取丢的是语境。编码代理最怕的就是语境丢失。比如你之前说过“这个模块不要用全局状态,因为后面要支持多实例”,这句话在摘要里可能被压成“避免全局状态”,听起来差不多,但代理再看到某个具体函数时,它不一定能把这个约束和当前代码关联起来。
这个实验的设计思路,我理解下来是:不试图阻止压缩,而是让压缩后的代理能通过外部记忆机制重新“接上”。long_mem 负责存长期稳定的项目约定和决策,engram 负责存短期可召回的操作痕迹。两者配合,相当于给代理配了一个笔记本和一个便签墙。笔记本记大方向,便签墙记刚才干了什么。
2.2 long_mem 和 engram 的分工逻辑
long_mem 的核心是持久化。它不依赖当前上下文窗口,而是把关键信息写到外部存储里,比如一个本地文件、一个向量库、或者一个结构化的 JSON。每次代理启动新会话或者上下文被压缩后,它先去读 long_mem,把项目级的约束加载回来。这些约束包括:代码风格、目录结构约定、依赖版本、测试命令、禁止使用的 API、必须遵循的接口规范。
engram 的核心是痕迹召回。它记录的是操作层面的东西:刚才改了哪个文件、跑了什么命令、得到了什么报错、下一步打算做什么。engram 不需要长期保存,但它需要在压缩后的短时间内能被快速检索到。你可以把它理解成代理的“工作记忆缓存”。
这两者的分工很关键。如果只做 long_mem,代理知道大方向,但不知道刚才干到哪了,容易重复劳动或者漏掉未完成的步骤。如果只做 engram,代理知道刚才干了什么,但不知道为什么要这么干,容易在遇到冲突时做出错误决策。两者结合,才能让压缩后的代理既记得“规矩”,又记得“进度”。
2.3 为什么选择 10 天、430 条记录这个规模
10 天不算长,但对于验证一个记忆衔接机制来说,足够覆盖多种任务类型:新功能开发、bug 修复、重构、测试补充、依赖升级。430 条公开记录,平均每天 43 条,说明实验者不是偶尔试一下,而是高频使用并持续记录。这种规模的好处是,能暴露出偶发问题,比如某个特定压缩阈值下 engram 召回失败,或者 long_mem 写入冲突。
我自己做类似实验时,前三天往往很顺,因为任务简单、上下文没满。到第五天开始,长任务多了,压缩频繁了,问题才集中出现。所以 10 天的跨度是有意义的,它跨过了“蜜月期”,进入了真实使用的疲劳区。430 条记录也说明实验者把每次压缩后的表现都记下来了,这样才能做归因分析。
2.4 方案选型的取舍:为什么不直接换更大窗口的模型
有人可能会问,既然上下文不够,换个窗口更大的模型不就行了。这个思路在理论上成立,但实际中有三个问题。第一,窗口再大也有上限,项目大到一定程度照样会满。第二,窗口越大,推理成本和延迟越高,编码代理需要频繁调用工具,成本会迅速膨胀。第三,也是最关键的,窗口大不代表模型能有效利用所有信息,中间部分的信息容易被忽略,这就是所谓的“迷失在中间”现象。
所以这个实验选择做记忆机制,而不是单纯堆窗口,是更务实的路线。它承认压缩不可避免,然后在这个前提下想办法。这个思路我觉得是对的,因为真实项目里你不可能无限扩窗口,但你可以设计一套让代理自己管理记忆的流程。
3. 核心细节解析与实操要点
3.1 long_mem 的存储结构怎么设计
long_mem 不能随便存,否则读的时候找不到重点。我建议用分层结构,至少分三层:项目级、模块级、任务级。项目级存全局约定,比如语言版本、包管理器、代码格式化规则、提交信息规范。模块级存该模块的特定约束,比如“支付模块所有金额用整数分表示”“用户模块的 ID 是 UUID v4”。任务级存当前任务的临时约定,比如“这次重构不改公共接口”“测试覆盖率必须达到 80%”。
存储格式上,JSON 比纯文本好,因为代理可以直接解析字段,不需要用自然语言理解去猜。但 JSON 的问题是写起来麻烦,所以可以先用 Markdown 写,再用一个简单的转换脚本转成 JSON。或者直接用 YAML,兼顾可读性和结构化。
注意:long_mem 里不要存大段代码。代码会变,存了也容易过期。存的是约束和决策,不是实现。
3.2 engram 的召回时机和触发条件
engram 的关键是“什么时候去读”。如果每次操作都读,开销太大;如果压缩后才读,可能已经晚了。我的经验是设三个触发点:第一,上下文使用率达到 70% 时,主动写一次 engram 快照;第二,压缩事件发生后,立即读一次 engram;第三,代理连续两次工具调用没有进展时,读 engram 检查是不是忘了之前的步骤。
engram 的内容要精简,每条记录不超过三行。格式可以是:时间戳、操作类型、目标文件、结果状态、下一步。比如“14:32 修改 src/auth.ts 添加 token 刷新逻辑 测试通过 待补充边界用例”。这样代理读的时候能快速定位。
3.3 压缩策略的选择:摘要还是抽取
实验里应该试过多种压缩策略。我的建议是混合使用。对于对话历史,用摘要压缩,因为对话的细节不重要,重要的是决策和结论。对于代码和工具输出,用关键信息抽取,因为代码的细节很重要,摘要会丢关键行。对于错误日志,保留原始文本,因为错误信息里的每个字都可能有用。
具体操作上,可以在压缩前先做一次“标记”,把重要段落打上标签,压缩时优先保留标签内容。这需要代理在生成内容时就意识到哪些是重要的,比如它可以在写决策时加一个[KEEP]前缀。这个习惯需要训练,但一旦养成,压缩质量会大幅提升。
3.4 代理如何知道“自己失忆了”
这是一个容易被忽略的点。代理如果不知道自己丢了信息,它就不会去读 long_mem 和 engram。所以需要设计一个自检机制。比如在每次压缩后,代理先问自己三个问题:当前任务的目标是什么?有哪些约束不能违反?上一步操作是什么?如果任何一个答不上来,就去读记忆。
这个自检可以用一个简单的提示词实现,比如在系统提示里加一段:“每次上下文压缩后,先复述当前任务目标、关键约束和上一步操作,再继续工作。” 这样代理会强制自己回忆,回忆不出来就会触发记忆读取。
3.5 工具选型:Codex 这类 CLI 代理的适配要点
Codex 这类 CLI 代理的特点是,它通过命令行与文件系统和工具交互,没有图形界面,所有状态都在文本里。这对记忆机制其实是好事,因为文本容易截取和存储。但要注意,Codex 的工具调用输出可能很长,比如跑一次测试输出几百行,这些内容会迅速占满上下文。
我的做法是,在工具调用后立即做一次过滤,只保留关键行。比如测试输出只保留失败用例和汇总行,编译输出只保留错误和警告。这个过滤可以由代理自己完成,也可以用一个包装脚本完成。过滤后的内容再进入上下文,能显著延缓压缩的到来。
4. 实操过程与核心环节实现
4.1 环境准备与基础配置
先假设你用的是 Codex CLI 这类工具。第一步是确认版本和配置。运行codex --version看版本,然后检查配置文件,通常在~/.codex/config或项目根目录的.codex文件里。你需要配置模型端点、API 密钥、默认工作目录。如果遇到codex无法加载组织设置这类报错,通常是配置文件里的组织字段和实际账号不匹配,删掉该字段或改成正确的值即可。
第二步是准备 long_mem 的存储位置。我建议放在项目根目录的.agent/memory/下,分long_mem.json和engram.jsonl两个文件。long_mem.json存结构化约束,engram.jsonl每行一条操作记录。这样代理读写都方便,也容易用 git 管理。
第三步是写一个初始化脚本,在代理启动时自动加载 long_mem。脚本可以用 bash 或 python 写,核心逻辑就是读 JSON 文件,把内容拼接到系统提示里。如果你用的是支持自定义提示词的代理,直接把 long_mem 内容塞进提示词就行。
4.2 第一次压缩前的准备动作
在上下文还没满的时候,就要开始为压缩做准备。具体做法是,每完成一个阶段性任务,就让代理写一次 long_mem 更新。比如完成登录模块后,让它记录:“登录模块使用 JWT,token 有效期 2 小时,刷新接口为 /auth/refresh,禁止在客户端存储 refresh token。” 这些信息写进 long_mem 后,即使后续上下文被压缩,代理也能通过读取 long_mem 恢复这些约束。
同时,engram 要开始记录操作流水。每次文件修改、命令执行、测试运行后,追加一条记录。记录不需要很详细,但要有足够的信息让代理回忆起上下文。比如“修改 user.service.ts 的 findById 方法,改为软删除过滤,测试通过”。
4.3 压缩发生时的处理流程
当上下文使用率达到阈值,压缩触发。这时候代理应该按以下顺序操作:
- 先把当前 engram 刷盘,确保最新操作记录已写入。
- 然后执行压缩,压缩策略按前面说的混合方式。
- 压缩完成后,立即读取 long_mem 和 engram。
- 复述当前任务目标、关键约束、上一步操作。
- 如果复述内容与 engram 记录一致,继续工作;如果不一致,以 long_mem 和 engram 为准,修正理解。
这个流程看起来简单,但实际执行时,代理可能会跳过第 3 步,因为它觉得自己还记得。所以需要在系统提示里强制要求,并且用工具调用日志来验证它确实读了文件。
4.4 一个完整的任务示例
假设任务是“给用户模块添加软删除功能”。代理的完整流程如下:
第一步,读 long_mem,确认项目约定:TypeScript 严格模式、使用 Prisma ORM、测试用 Jest、禁止直接写 SQL。
第二步,读 engram,看之前有没有相关操作。发现昨天改过 user.service.ts,但没涉及删除逻辑。
第三步,开始工作。先改 Prisma schema,加deletedAt字段。然后改 service,把findById改成过滤deletedAt: null。再改 controller,加删除接口。每改一个文件,写一条 engram。
第四步,跑测试。测试输出过滤后保留失败用例。发现有两个测试因为软删除过滤而失败,需要更新测试数据。
第五步,上下文达到 70%,写 engram 快照,记录当前进度:schema 已改、service 已改、controller 已改、测试待修。
第六步,压缩触发。压缩后读 long_mem 和 engram,复述任务目标和进度,继续修测试。
第七步,测试通过,更新 long_mem,记录“用户模块已支持软删除,删除接口为 DELETE /users/:id,软删除字段为 deletedAt”。
这个示例里,关键点是每次阶段性完成后都写记忆,压缩后强制读取。这样即使中间压缩了多次,代理也不会跑偏。
4.5 参数计算:压缩阈值怎么定
压缩阈值不能拍脑袋。我的经验公式是:阈值 = 窗口大小 × 0.7。比如窗口是 128k token,阈值设在 90k 左右。为什么是 0.7?因为要留出空间给压缩后的恢复操作和后续工作。如果设到 0.9,压缩后只剩 10% 空间,代理读个 long_mem 就满了,又得压缩,陷入死循环。
另外,engram 的刷盘频率也要算。如果每次工具调用都刷,开销大但安全;如果每 5 次刷一次,开销小但可能丢最近几步。折中方案是每 3 次工具调用刷一次,或者在关键操作(文件写入、测试运行)后立即刷。
5. 常见问题与排查技巧实录
5.1 代理压缩后重复劳动怎么办
这是最常见的问题。代理压缩后忘了刚才已经改过某个文件,又改了一遍。排查思路是看 engram 记录里有没有该文件的操作。如果有,但代理还是重复了,说明它没读 engram,或者读了但没理解。解决办法是在 engram 记录里加一个状态字段,比如status: done或status: in_progress,代理读的时候优先看状态,避免重复。
5.2 long_mem 写入冲突怎么处理
多个任务并行时,long_mem 可能被同时写入,导致内容覆盖。解决办法是加锁或者用追加模式。追加模式下,每次写入带时间戳和任务 ID,读取时按时间排序,取最新值。如果冲突严重,可以按任务分文件,每个任务一个 long_mem 文件,最后合并。
5.3 压缩后代理风格突变怎么修
有时候压缩后代理的代码风格会变,比如之前用驼峰命名,突然变成下划线。这通常是 long_mem 里的风格约束没被读到。检查 long_mem 是否包含风格规则,以及代理是否在压缩后读了 long_mem。如果都正常,可能是压缩时把风格相关的对话摘要丢了,需要在 long_mem 里显式写死风格规则。
5.4 engram 召回失败怎么排查
召回失败的表现是代理问“我们刚才在干什么”。排查步骤:先看 engram 文件是否存在且非空;再看代理是否有读取权限;然后看读取路径是否正确;最后看 engram 内容是否被压缩逻辑误删。如果都没问题,可能是代理的读取工具调用被跳过了,需要在提示词里强制要求。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查动作 | 解决方式 |
|---|---|---|---|
| 压缩后重复改同一文件 | engram 未读或状态不明 | 检查 engram 记录和读取日志 | 加状态字段,强制读取 |
| 代码风格突变 | long_mem 风格约束丢失 | 检查 long_mem 内容和读取时机 | 显式写死风格规则 |
| 代理问“刚才在干什么” | engram 召回失败 | 检查文件、权限、路径 | 修复读取流程,加提示词 |
| 压缩后立即又压缩 | 阈值设太高 | 计算阈值和窗口比例 | 降到 0.7 以下 |
| long_mem 内容过期 | 未及时更新 | 对比当前代码和记忆 | 阶段性任务后强制更新 |
5.6 独家避坑技巧
第一个技巧:在 long_mem 里加一个“最后更新时间”字段,代理读取时先看这个时间,如果超过一天,就提醒自己可能过期,需要验证。第二个技巧:engram 记录用 JSONL 格式,每行一条,方便追加和解析,不要用一个大 JSON 数组,否则每次写入都要重写整个文件。第三个技巧:压缩前让代理自己总结“如果我是新来的,需要知道什么才能继续”,这个总结直接写入 long_mem,比事后补更准确。
6. 记忆机制之外,代理本身该怎么配
6.1 Codex 这类工具的安装和基础设置
如果你还没装 Codex,先确认系统环境。Windows 桌面版和 CLI 版安装方式不同,CLI 版通常用 npm 或 brew 安装。安装后运行codex login登录,如果登录不上,检查网络和账号状态。配置文件里要设置默认模型和端点,如果遇到codex无法加载组织设置,把组织字段留空或填正确值。codex windows设置未完成通常是路径里有空格或权限不足,换个目录即可。
6.2 模型选择与端点配置
Codex 支持多种模型端点。如果你用第三方端点,注意模型名称要匹配,比如gpt-5.6-sol这类名称如果端点不支持,会报model is not supported。解决办法是查端点文档,用支持的模型名。如果遇到cc switch local proxy failed while handling codex endpoint /responses,通常是本地代理配置和 Codex 的请求格式不匹配,检查代理的路径和请求头设置。
6.3 中文支持和提示词优化
Codex 对中文的支持取决于模型。如果模型中文能力强,直接用中文提示词没问题。如果中文能力弱,建议用英文写系统提示,但 long_mem 和 engram 可以用中文,因为它们是给代理读的,代理能理解就行。提示词里要明确要求代理在压缩后读记忆,并且用中文复述,这样你能快速判断它有没有接上。
6.4 成本控制与调用频率
编码代理的调用成本主要来自模型推理和工具执行。记忆机制会增加一些读写开销,但相比重复劳动和错误修改,这个开销是值得的。控制成本的关键是减少无效调用。比如工具输出过滤后,上下文增长慢,压缩频率低,调用次数自然少。另外,engram 刷盘可以用本地脚本完成,不消耗模型调用。
7. 这套机制还能怎么扩展
7.1 从单代理到多代理协作
如果多个代理同时工作,long_mem 可以做成共享的,每个代理读写同一份项目约束。engram 则按代理分文件,避免互相干扰。协作时,一个代理完成的任务要写入共享 long_mem,其他代理读取后就知道进度。这比让代理之间直接对话更高效,因为对话会迅速占满上下文。
7.2 记忆的版本管理
long_mem 和 engram 都可以用 git 管理。每次更新后提交,这样能追溯记忆的变化。如果发现某次压缩后代理行为异常,可以回滚到之前的记忆版本,对比差异。这个做法在调试记忆机制时特别有用。
7.3 自动化的记忆质量检查
可以写一个脚本,定期检查 long_mem 里的约束是否和当前代码一致。比如 long_mem 说“使用 Prisma”,但 package.json 里已经换成 TypeORM,脚本就报警。这样能防止记忆过期导致代理做出错误决策。
7.4 从编码代理扩展到其他长任务场景
这套记忆机制不限于编码。任何需要长会话、多步骤、上下文会压缩的场景都能用,比如长文档写作、数据分析、自动化运维。核心逻辑是一样的:长期记忆存约束,短期记忆存进度,压缩后强制召回。我在写长报告时就用类似方法,让 AI 先读大纲和风格要求,再写每一节,写完一节更新进度,效果比一次性生成好很多。
7.5 未来可以尝试的方向
一个方向是用向量检索做 engram 召回,而不是简单的文件读取。这样代理可以根据当前任务语义,召回最相关的历史操作,而不是按时间顺序读。另一个方向是让代理自己决定什么时候写记忆,而不是按固定频率。这需要代理有更好的元认知能力,知道哪些信息未来会用到。目前来看,固定频率加关键操作触发,是更稳妥的方案。
我个人在实际操作中的体会是,记忆机制的关键不在于技术多复杂,而在于执行是否严格。代理很容易“自信地忘记”,所以必须用提示词和工具调用日志双重约束。踩过几次坑之后,我现在每次压缩后都会手动检查代理有没有读记忆文件,确认它复述的任务目标和我理解的一致,再让它继续。这个习惯虽然多花几秒钟,但能避免后面几十分钟的跑偏。