1. 这个实验到底在折腾什么
先交代背景。我过去大半年一直在用 AI 编码代理做日常开发,主力是 Codex 这类命令行工具,配合本地模型和云端模型混着跑。用久了就撞上一个绕不开的墙:上下文压缩。
你肯定遇到过这种情况——跟 AI 代理聊到几十轮,它突然开始"失忆",前面定好的接口约定、文件路径、变量命名规则全忘了,甚至把你刚纠正过的错误又犯一遍。这不是模型笨,是上下文窗口塞满了,系统自动做了压缩(compaction),把早期对话摘要成一小段,细节全丢了。
问题来了:压缩之后,AI 编码代理怎么接得上?
我花了 10 天时间,跑了 430 条公开可查的记录,专门测这件事。核心思路是用两个东西来兜底:long_mem(长期记忆层)和engram(记忆痕迹编码)。说白了,就是不让代理只靠当前上下文活着,而是给它一个外部记忆仓库,压缩发生后能快速"回忆"起关键信息。
这个实验适合谁看?如果你正在用 Codex、Claude Code 或者类似的编码代理,并且被上下文丢失折磨过,那这篇就是写给你的。如果你还没开始用,也可以先了解这个坑有多深,提前避雷。
先说结论方向:光靠加大上下文窗口是死路,必须做分层记忆。下面我把整个实验的设计、踩的坑、具体参数和排查方法全摊开讲。
2. 整体设计思路与方案选型
2.1 为什么盯上"压缩后接续"这个点
AI 编码代理的工作模式和普通聊天不一样。普通聊天丢点上下文无所谓,但编码代理丢上下文是致命的——它可能忘记你用的是 TypeScript 还是 Python,忘记数据库连接串的字段名,忘记你明确说过"不要动这个文件"。
我统计了自己前 3 个月的失败案例,超过 60% 的代理出错都发生在上下文压缩之后的前 5 轮对话内。这个比例高得离谱,说明压缩机制和代理的接续能力之间存在结构性矛盾。
压缩本身是为了省 token、省成本、避免超窗,这个逻辑没错。错的是压缩之后没有补偿机制。就像你把一本书缩写成目录,然后要求读者凭目录还原出每一页的细节,这不现实。
2.2 long_mem 和 engram 的分工
我选这两个组件不是拍脑袋,是各有各的活。
long_mem负责结构化存储。它把对话中的关键实体抽出来——文件路径、函数签名、接口定义、用户偏好、项目约束——存成一个可检索的记忆库。压缩发生时,原始对话可以丢,但这些结构化事实不能丢。
engram负责痕迹编码。这个词借自神经科学,指的是记忆在大脑中的物理痕迹。在这里,它做的事情是把每轮对话压缩成一个高维向量指纹,附带时间戳和重要性权重。当代理需要回忆时,不是去翻原始文本,而是用当前 query 去匹配最相关的 engram,快速唤醒。
两者配合的逻辑是:long_mem 管"事实",engram 管"情境"。事实告诉你userService.ts里有个getUserById方法,情境告诉你上次讨论这个方法时你正在修一个空指针 bug。
2.3 方案对比:为什么不用纯 RAG 或纯摘要
我试过三种方案,列个表对比一下。
| 方案 | 压缩后接续准确率 | 额外 token 开销 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|
| 纯摘要压缩 | 约 42% | 低 | 低 | 短对话、非关键任务 |
| 纯 RAG 检索 | 约 58% | 中 | 中 | 文档问答、知识库 |
| long_mem + engram | 约 81% | 中高 | 高 | 长周期编码代理 |
纯摘要的问题前面说了,细节丢失不可逆。纯 RAG 的问题是它只做被动检索,代理不知道"什么时候该去检索",经常该回忆的时候不回忆,不该回忆的时候瞎回忆。
long_mem + engram 的组合多了一层主动唤醒机制:当代理检测到当前上下文的信息密度下降(比如连续两轮没有新的文件操作),就自动触发 engram 匹配,把相关记忆拉回来。这个触发逻辑是整个方案的关键,后面会细讲。
注意:这套方案不是银弹。如果你的对话轮次很少(少于 20 轮),上这套东西属于杀鸡用牛刀,纯摘要就够了。
3. 核心细节解析与实操要点
3.1 long_mem 的存储结构设计
long_mem 不是简单地把对话存成文本块。我设计了一个三层结构:
- 实体层:文件、函数、类、变量、配置项。每个实体有唯一 ID、类型、首次出现时间、最后修改时间。
- 关系层:实体之间的依赖关系。比如
getUserById依赖dbConnection,dbConnection的配置来自.env文件。 - 约束层:用户明确说过的规则。比如"所有 API 返回必须用
{ code, data, message }格式"。
存储用 SQLite 就够了,别上向量数据库。为什么?因为编码场景的检索大部分是精确匹配或前缀匹配,不是语义模糊匹配。你搜getUserById,就是要找这个名字,不是找"类似意思的函数"。SQLite 的 FTS5 全文索引完全够用,还省得维护一套向量库。
建表语句大概长这样:
CREATE TABLE entities ( id TEXT PRIMARY KEY, type TEXT NOT NULL, name TEXT NOT NULL, file_path TEXT, first_seen INTEGER, last_modified INTEGER, metadata TEXT ); CREATE TABLE relations ( source_id TEXT, target_id TEXT, relation_type TEXT, weight REAL DEFAULT 1.0 ); CREATE TABLE constraints ( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT NOT NULL, source_turn INTEGER, active INTEGER DEFAULT 1 );3.2 engram 的编码与匹配策略
engram 的核心是把每轮对话变成一个向量。我用的是本地的小型嵌入模型,维度 384,够用且快。每轮对话结束后,做三件事:
- 提取对话中的关键动作(读文件、写文件、执行命令、用户纠正)。
- 生成嵌入向量。
- 计算重要性权重,权重公式是:
weight = 0.4 * action_score + 0.3 * user_correction_flag + 0.2 * recency + 0.1 * entity_countaction_score是动作类型的分值,写文件比读文件高,执行命令比闲聊高。user_correction_flag是布尔值,用户纠正过代理的轮次权重直接拉满。recency是时间衰减,越近的越高。entity_count是这轮涉及多少个实体。
匹配的时候,用当前 query 的向量去和所有 engram 算余弦相似度,取 top-k(我设的 k=5),再按权重排序,把权重最高的 2-3 条注入回上下文。
实操心得:k 不要设太大。我试过 k=10,结果注入了一堆不相关的记忆,反而干扰代理判断。k=5 是实测下来最稳的。
3.3 触发时机的判断逻辑
这是最容易做砸的地方。触发太频繁,代理每轮都在回忆,效率极低;触发太少,该回忆的时候不回忆。
我的触发条件是一个组合判断:
- 当前上下文 token 数超过窗口的 70%
- 或者连续 2 轮没有新的实体被创建
- 或者用户输入中包含"之前""上次""刚才"这类回溯词
- 或者代理主动请求回忆(我在系统提示里给了它这个能力)
四个条件满足任意一个就触发。实测下来,平均每 8-12 轮触发一次,频率比较合理。
3.4 与 Codex 的集成方式
Codex 这类工具通常支持自定义系统提示和工具调用。我的集成方式是:
- 在系统提示里注入 long_mem 的当前状态摘要(实体数量、活跃约束)。
- 提供一个
recall工具,代理可以主动调用,参数是查询字符串。 - 在每轮对话结束后,通过 hook 机制把新实体写入 long_mem,生成 engram。
如果你用的是 Codex CLI,可以在配置里加一个 post-turn hook。具体路径看你的安装方式,Windows 桌面版和 CLI 版的配置位置不一样,这个后面排查部分会讲。
注意:不要试图修改 Codex 的二进制文件。所有集成都应该通过官方支持的扩展点来做,否则升级一次就全废了。
4. 实操过程与核心环节实现
4.1 环境准备与依赖安装
我用的环境是 Windows 11 + WSL2,Python 3.11。为什么用 WSL2?因为 Codex 在 Linux 环境下的 hook 机制更稳定,Windows 原生环境下有些路径问题很烦人。
依赖清单:
pip install sqlite-utils sentence-transformers numpysentence-transformers用来跑嵌入模型,我选的是all-MiniLM-L6-v2,384 维,速度快,质量够用。别用太大的模型,编码代理的场景不需要那么高的语义精度,反而拖慢响应。
数据库初始化:
import sqlite3 conn = sqlite3.connect('long_mem.db') conn.execute('PRAGMA journal_mode=WAL') # 建表语句见 3.1 节 conn.commit()WAL 模式一定要开,不然并发读写会锁表。
4.2 实体抽取的实现细节
实体抽取我用的是规则 + 轻量模型混合。纯规则会漏,纯模型会慢。
规则部分处理明显的模式:
- 文件路径:正则匹配
[\w/\\]+\.\w+ - 函数定义:匹配
function\s+(\w+)、def\s+(\w+)、const\s+(\w+)\s*=\s*\( - 配置项:匹配
[A-Z_]{3,}\s*=这种全大写模式
模型部分处理自然语言里的实体,比如"那个用户服务的接口",需要模型判断指的是userService相关的实体。
抽取频率是每轮对话结束后跑一次,耗时控制在 200ms 以内。超过这个时间就会影响代理的响应速度,用户能感知到卡顿。
4.3 engram 生成与注入的完整流程
完整流程分四步:
- 对话结束:代理完成一轮响应,用户没有新的输入。
- 实体抽取:从这轮对话中抽取实体,更新 long_mem。
- engram 生成:对这轮对话生成嵌入向量,计算权重,存入 engram 表。
- 注入判断:检查触发条件,如果满足,检索 top-k engram,把对应的 long_mem 实体和约束注入下一轮的系统提示。
注入的格式我设计成结构化文本,不是自然语言。比如:
[RECALLED CONTEXT] Entities: - userService.ts::getUserById (function, last modified turn 45) - dbConnection (config, from .env) Constraints: - API response format: { code, data, message } Recent actions: - Fixed null pointer in getUserById at turn 47这种格式代理解析起来准确率高,比自然语言描述强。
4.4 参数调优的实测记录
我调了 10 天,记录了几个关键参数的变化对准确率的影响。
| 参数 | 初始值 | 调优后 | 准确率变化 |
|---|---|---|---|
| engram top-k | 10 | 5 | +12% |
| 触发阈值(token 占比) | 0.5 | 0.7 | +8% |
| 权重中 recency 系数 | 0.1 | 0.2 | +5% |
| 实体抽取模型 | base | small | -2%(但速度快 3 倍) |
最后我选了 small 模型,因为那 2% 的准确率损失换来 3 倍速度提升,整体体验更好。代理响应快比偶尔多回忆对一次更重要。
实操心得:参数调优不要一次改多个。我一开始贪心,同时改了 top-k 和阈值,结果准确率反而降了,因为不知道是哪个参数的问题。后来改成单变量调优,每次只动一个,才找到最优组合。
5. 常见问题与排查技巧实录
5.1 Codex 集成时的典型报错
报错一:cc switch local proxy failed while handling codex endpoint /responses
这个报错我遇到三次。第一次是本地代理端口被占用,换端口解决。第二次是代理配置里的 endpoint 路径写错了,Codex 的/responses端点在某些版本里是/v1/responses,差一个前缀就 404。第三次最坑,是本地模型服务没启动,代理转发失败。
排查顺序:先netstat -ano | findstr 端口号看端口占用,再检查 endpoint 路径,最后确认后端模型服务活着。
报错二:the 'gpt-5.6-sol' model is not supported when using codex with a...
这个报错说明你配置的模型名 Codex 不认。Codex 对模型名有白名单校验,不是随便填的。解决办法是查你用的 Codex 版本支持的模型列表,用列表里的名字。如果你接的是第三方模型,需要在配置里显式声明model_provider,不然 Codex 会按默认 provider 去校验,必然失败。
报错三:codex无法加载组织设置
这个通常出现在登录态失效或者网络配置有问题的时候。先检查登录状态,codex login重新走一遍。如果还不行,检查配置文件里的组织 ID 是否正确。Windows 桌面版的配置文件在%APPDATA%\codex\config.json,CLI 版在~/.codex/config.json,两个位置不一样,别搞混。
5.2 记忆注入后代理反而变笨
这个坑我踩得最深。有段时间代理频繁回忆,但回忆的内容不相关,导致它在错误的上下文里做决策。
根因是 engram 的权重计算有问题。我一开始没加user_correction_flag,导致用户纠正过的轮次和普通轮次权重一样,检索时经常把纠正前的错误信息拉回来。
修复方法:用户纠正过的轮次,权重直接乘以 2.0,并且把纠正后的正确信息作为独立实体存入 long_mem,覆盖旧的错误实体。
还有一个原因是 top-k 太大,注入了噪声。降到 5 之后明显好转。
5.3 常见问题速查表
| 现象 | 可能原因 | 排查方法 | 解决 |
|---|---|---|---|
| 代理忘记文件路径 | engram 未触发 | 检查触发条件日志 | 降低触发阈值 |
| 回忆内容不相关 | top-k 过大 | 打印检索结果 | 降到 5 |
| 响应变慢 | 嵌入模型太大 | 计时各环节 | 换 small 模型 |
| 约束被忽略 | 约束未激活 | 查 constraints 表 | 重新激活 |
| 重复回忆同一内容 | 去重逻辑缺失 | 查 engram 表 | 加时间窗口去重 |
| Codex 报模型不支持 | 模型名不在白名单 | 查版本支持列表 | 换支持的模型名 |
| 代理启动失败 | 端口占用 | netstat 查端口 | 换端口 |
| 登录态失效 | token 过期 | codex login | 重新登录 |
5.4 独家避坑技巧
技巧一:给 engram 加一个"遗忘"机制。超过 200 轮之前的 engram,如果权重低于阈值,直接归档不参与检索。不然记忆库越来越大,检索越来越慢,准确率还下降。
技巧二:约束要带来源。每条约束记录是哪一轮、哪个用户输入产生的。代理回忆时能看到来源,判断可信度。没有来源的约束,代理有时候会怀疑是不是自己编的。
技巧三:定期做记忆压缩。long_mem 里的实体也会膨胀。我每周跑一次压缩,把 30 天没被访问过的实体合并或归档。压缩后数据库大小能降 40% 左右,检索速度提升明显。
技巧四:日志要记全。每次触发回忆,把 query、检索到的 engram ID、注入的内容全记下来。出问题的时候,这些日志是唯一能还原现场的东西。我 430 条记录里,有价值的部分全靠日志。
注意:日志里不要记敏感信息。文件路径和函数名可以记,但配置值、密钥这类东西一定要脱敏。我吃过亏,日志文件不小心提交到仓库里了。
6. 这套方案还能怎么扩展
跑完这 10 天,我觉得这套东西的边界远不止编码代理。任何需要长周期记忆的 AI 工作流都能用——比如长篇小说写作助手、长期项目管理的 AI 助理、甚至个人知识库的问答系统。
扩展方向有几个。一是把 engram 的嵌入模型换成多模态的,这样截图、架构图也能作为记忆存储。二是加一个记忆冲突检测,当新实体和旧实体矛盾时主动提醒用户。三是做跨项目的记忆隔离,不同项目的 long_mem 分开存,避免串味。
我个人在实际操作中的体会是,记忆系统的价值不在于存了多少,而在于该用的时候能准确取出来。我见过太多人堆了一堆向量库,结果检索质量一塌糊涂。与其追求大而全,不如把触发时机和权重计算这两个点做扎实。
最后分享一个小技巧:如果你刚开始做,别急着上 engram,先把 long_mem 的实体抽取做准。实体抽取的准确率上不去,后面所有环节都是空中楼阁。我前 3 天全花在这上面,后面 7 天才顺起来。这个投入顺序,建议你照抄。