ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

AI编码代理的‘记忆断点’:上下文压缩实验与应对策略

AI编码代理的‘记忆断点’:上下文压缩实验与应对策略

直接放结论:上下文压缩这事儿,放在普通聊天场景里,体验上顶多是“忘了前面说啥”,但放到 AI 编码代理身上,就是“断线重连”级别的灾难。我花了 10 天时间,做了 430 条公开记录的对照实验,专门盯一件事——上下文被压缩之后,编码代理到底还能不能接着把活干完。结论是:能接,但绝不像你想的那样“自然接上”,它更像一个失忆的程序员被塞了一份残缺的交接文档,能不能续上,取决于你提前埋了多少钩子。

先说清楚我为什么要折腾这个实验。现在主流的 AI 编码代理(Cursor、Claude Code、Aider 这类,也包括各种基于大模型封装的内部工具)都靠一个上下文窗口活着。窗口有限,工程项目的代码量却是无限的,更别提还有一长串历史对话、命令输出、编译报错、测试日志。只要会话拉长,任何一个代理都要面对上下文不够用的现实,于是各家都上了压缩机制:有的做滑动窗口,有的做摘要改写,有的把早期对话直接丢弃。问题就在这——压缩算法只负责“让 token 变少”,它不负责“让代理还能干活”。我这次实验,就是想量化这个落差到底有多大,以及有没有办法提前把落差填上。

1. 先说结论:上下文压缩不是“省内存”,而是编码代理的“记忆断点”

1.1 编码代理为什么对上下文连续性这么敏感

普通聊天机器人,你问一句它答一句,答错了重新问就是,上下文丢了影响不大。编码代理完全不是这个工作模式。它是一个多步骤的自主执行系统:先理解需求,再读代码,再定位修改点,再改完跑测试,测试挂了还得回头看自己改了什么、原来的逻辑是什么。这个链条上的每一步,都依赖上一步的中间结果。一旦上下文在某一步被压缩掉,代理就相当于被从执行中点拽出来,塞回起点,手里只有一张模糊的“前面大概干了这些”的纸条。

这跟人类程序员的工作方式做个类比就很好懂了:你让一个熟手接一个别人写了一半的 project,完整的 git 历史、设计文档、代码注释都在,他很快能上手。但如果有人告诉你“前面那个人改了三天,主要改了登录模块,还修了缓存的一个 bug,具体怎么改的你自己看吧”,你大概率的反应是骂人。编码代理被压缩上下文之后,面对的就是后一种情况,而且它不会骂人,它只会一本正经地重新猜一遍,然后可能把代码改得更烂。

1.2 我这次实验的完整背景

实验动机其实是来自我自己项目里的一个真实痛点。前阵子我用编码代理改造一个内部用的数据处理服务,会话拉长到两三个小时后,代理开始频繁出现“重复修改同一个函数但每次方向都不一样”的情况。最典型的一次,它前面已经确认要保留旧接口做兼容,过了几轮之后又把这个接口删了,删除理由是“根据之前的讨论,旧接口不再需要”。但那个讨论根本没发生过,是压缩后的摘要自己拍脑袋补的。

我于是决定做一次系统性的记录实验。时间定在 10 天,任务都是真实的中小型编码任务,包含三块:一个 Python 后端接口改造、一个前端 React 组件的状态管理重构、一个带 CLI 入口的数据管道调整。记录方式是给每个任务建一份操作日志,每次跟代理交互、每次代理自行执行动作、每次上下文被压缩,都记下来。最终我拿到了 430 条有效公开记录,里面包含了交互内容、压缩事件、工具调用、代码修改结果、测试结果这些维度。

这里要说明一下,430 条不是简单对话条数,而是“有意义的状态变更记录”。比如一次文件修改是一条,一次测试执行是一条,一次压缩触发也是一条。这 430 条数据量不大,但足够看出趋势。以下所有分析都基于这 430 条记录来做。

2. 实验怎么设计的:10 天、430 条记录的采集与口径

2.1 项目选型与记录维度

选项目时我刻意避开了“一次跑通”的小 demo。那种项目上下文就几千 token,根本触发不了压缩,测了也是白测。我选的三类任务都有一个共同特征:依赖大量跨文件上下文,而且改动的子任务超过 10 步。Python 后端接口改造涉及 6 个文件、2 个数据库表、一个缓存组件;前端重构涉及组件树、状态流、API 层三块;CLI 数据管道涉及配置解析、任务调度、结果落盘。

记录维度我定了这么几个:

  • 会话轮次与 token 估算值
  • 上下文压缩触发时机与类型(摘要压缩、裁剪压缩、重写压缩)
  • 代理每次工具调用的参数与返回结果
  • 文件修改前后的 diff 摘要
  • 测试命令执行结果
  • 我作为使用者介入干预的次数与原因

用表格记录太费劲,我直接做成了一种 JSONL 日志格式,每条记录一个事件,字段包括事件类型、时间戳、关联文件、token 变化量、代理自述状态。这个格式后来帮了大忙,分析的时候可以直接按事件类型分组统计。

2.2 430 条记录到底记了什么

430 条记录按类型拆分大概是这么个分布:

  • 上下文压缩触发事件:54 条
  • 文件读取操作(read_file / grep / list_files):118 条
  • 文件编辑操作(apply_patch / write_file):96 条
  • 测试与命令执行:61 条
  • 代理主动总结并调整计划:43 条
  • 我的手动介入:27 条
  • 其他(报错重试、搜索等):31 条

这个分布本身就能说明问题:文件读取操作远多于编辑操作,说明代理大部分时间在“找代码”而不是“改代码”。这跟人类程序员很像,但问题是,人类找代码靠的是对项目结构有整体记忆,代理找代码靠的是上下文里的缓存。一旦上下文被压缩,缓存里的“项目地图”就失效了,它只能靠 grep 一点一点重新摸,效率断崖式下降。

压缩事件 54 条,平均每天 5.4 次,这个频次不算高,但每次都出现在会话中后段,也就是上下文即将触顶的时候。我在实验里给每个压缩事件都打了标,记录压缩前后的代理行为差异,这是整个实验最核心的数据。

3. 压缩之后,代理最容易在哪几步“接不上”

3.1 故障集中在:跨文件修改、依赖关系、多步骤任务

把所有压缩事件前后的行为做对比,故障率差异非常明显。我定义“故障”为:压缩发生后 5 轮交互内,代理出现重新询问已有信息、做出与之前相反的修改决策、或者连续失败重试的情况。

按任务类型统计:

任务类型压缩前故障率压缩后故障率(5轮内)最典型的故障表现
跨文件接口改造6%41%忘记之前确认的兼容策略
前端状态管理重构9%38%反复重写同一个状态函数
CLI 数据管道7%22%丢失参数格式约定

跨文件改造的故障率最高,这很好理解,它的上下文依赖最重。前端重构次之,CLI 相对低是因为命令行工具的模块边界清晰,代理即使失忆,靠读当前文件也能猜个大概。

3.1 里要特别拎出来说的是依赖关系这个坑。编码代理对“哪些地方引用了被修改的模块”这件事的判断,完全依赖上下文里有没有可靠的索引。压缩之后,索引被清空,代理就会凭“直觉”四处翻找依赖。我见过最夸张的一次,它为了确认一个函数是否被外部引用,连续 grep 了 7 次项目根目录,耗时 4 分钟,最后得出了“该函数无外部引用”的结论,可我手动查了一下,明明有一个测试文件在用它。代理判断的依据是 grep 结果没覆盖到那个文件,因为它的搜索模式写错了。

3.2 压缩质量差异:摘要压缩 vs 结构性截断对比

我原以为所有上下文压缩机制都一样,实验结果让我收回了这个想法。不同压缩策略对编码代理的“接续能力”影响差别非常大。

  • 摘要式压缩:把早期对话重写成一段摘要,保留语义但丢掉细节。这种方式的优点是上下文连贯性还行,缺点是摘要会“想当然”,经常把代理之前犹豫不决的点直接写成“已确认”。我的实验里,有一次代理在摘要里被“确认”了一个它从未确认过的方案,后续所有行为都沿着这个错误前提走,我直到两轮之后才发现。

  • 结构性截断:直接把最早的对话丢出去,只留最近的 N 轮。这种方式不制造幻觉,但会让代理彻底忘记早期输入的需求约束。比如任务一开始用户说“不能用 redis”,被截断后代理在某次编辑中大摇大摆地引入了 redis。

  • 混合式重写:结合对话摘要和代码结构摘要,把项目关键结构单独维护。效果最好,但市面上能做到这种级别的工具不多,很多都是宣传口径而已。

这个结论直接影响了我后面的实操策略:不要指望代理工具自带的压缩策略,要在“压缩之外”再建立一套属于我自己的上下文保护机制。

4. 实操对策:让代理在压缩后依然“接得上”

4.1 让压缩发生前,先做语义检查点

我的核心办法是强行制造“语义检查点”。什么叫语义检查点?就是在上下文即将被压缩之前,让代理把当前状态整理成一份独立的、完整的、不会被压缩机制影响的文档。

具体操作是这样的:当会话轮次快要接近上下文上限(一般我靠估算 token 或观察代理响应延迟来判断),我会主动停下来,跟代理说:“从现在开始,不要继续做任何修改,只输出一份当前任务状态文档。文档必须包含五个部分:已完成修改的文件与具体 diff 摘要、当前采用的方案决策与原因、下一步计划、待确认的开放问题、已知的风险与依赖关系。”

然后我把这份文档保存为项目根目录下的 AGENTS_STATE.md,这样代理即使上下文被全量清空,只要它被允许读取这个文件,就能迅速恢复“记忆”。我实测的效果是,有这个文件和没这个文件,压缩后恢复效率差了一半以上。没文件时代理需要重新摸索 10 到 15 轮才能恢复到压缩前的工作状态,有文件时 3 到 5 轮就接上了。

要注意的是,GET_AGENTS_STATE.md 不能只是给人类看的,格式必须让代理也好读。我用的是纯 Markdown 加简单标题层级,不用花哨的表格,因为代理解析表格的能力不如解析标题+列表稳定。

4.2 任务拆解的粒度决定了压缩的容错率

另一个关键技巧是任务粒度控制。上下文压缩对“已经执行了很长的多步任务”伤害最大,但如果把一个长任务拆成多个短任务,每个短任务都独立完成后重置上下文,压缩造成的损失就小得多。

我在实验中对比了两种工作方式:

  • 方式 A:一次性把整个接口改造需求丢给代理,让它一口气干完。
  • 方式 B:先让代理干第 1 步(梳理现有接口逻辑并输出报告),确认后再开新会话干第 2 步(写新接口),再确认再干第 3 步。

方式 B 表面上看起来慢,因为每一步都要花时间“重新读代码建立上下文”,但实际总耗时反而少。方式 A 在压缩发生后出现了大量返工,一次性上下文太长,压缩触顶后的修改基本报废,最后算下来总耗时比方式 B 多了大概 40%。

这个对比让我开始相信一个判断:AI 编码代理更适合“接力式”的任务拆解,而不是“放养式”的全程自主。你要做的是把一个任务拆成代理可以独立完成的单元,每个单元结束产出明确结果,下一个单元重新出发。

4.3 强制代理写完“决策日志”后再继续

第三个技巧看起来有点笨,但实测非常有效:让代理在每次关键决策前,先写出“决策日志”,再动手改代码。所谓关键决策,包括:修改某个接口签名、替换某个第三方库、重命名某个函数、改变数据存储结构等。

为什么要这么做?因为上下文压缩造成的“失忆”,本质上是代理丢失了做过决策的“推理链”。如果决策过程本身被记录在外部文件里,代理即使压缩失忆,也能顺着日志重新建立起推理链。而且写决策日志这个动作还有一个额外好处——它能强迫代理更谨慎地思考。我在实验里发现,被要求写决策日志的代理,修改代码时明显更少出现“凭空添加不存在的 API”这类低级幻觉。

具体格式很简单,就是在 AGENTS_STATE.md 里加一个“决策记录”区,编号记录每个决策的时间、内容、理由、影响范围。我甚至让它写“备选方案”和“为什么不选它”,这个动作对压缩后恢复帮助巨大,因为代理失忆后经常会“重新提出”已经被否决的备选方案,而决策日志直接堵死了这条歧路。

5. 常见问题与排查技巧实录

5.1 压缩后代理开始乱改代码怎么办

这是实验里最让人血压升高的场景:压缩前方案明明已经稳定了,压缩后代理却开始大改特改,把之前定的结构推倒重来。

我的第一反应是怀疑压缩导致的“记忆混淆”,后来发现不全是。有些代理工具在上下文压缩时,会顺手“优化”一下摘要,从而改变了对需求的解读。这种情形下,直接纠正它“你改错了”没用,因为代理不知道自己哪里错了,它会礼貌地道歉然后继续乱改。

正确做法是:停止当前会话,读取 AGENTS_STATE.md,找到对应决策记录,然后新开一个会话,把决策记录粘贴进去,明确告诉代理“按这个文件执行,不要重新决策”。这一步是打断代理的“错误惯性”,比在原来会话里反复纠正有效得多。

如果项目还没建 AGENTS_STATE.md,另一个应急办法是让代理把当前代码状态打包成一个 patch,然后用git diff对比找出它乱改的范围,手动回滚后再继续。

5.2 代理反复重试同一项工具调用

另一个高频问题出现在压缩后的首轮:代理为了找回上下文,会反复执行同样的搜索或读取操作。有一次它连续 5 次读取同一个配置文件,每次都是完整读入内容,读完又不执行后续动作,来回空转。

这种情况本质上是代理的“工具调用规划”乱了,它试图通过重复读取来找回失去的思路。最好的干预时机是在第 2 次重复时就打断它,直接把目标文件的内容和关键信息告诉它,然后指令化它的下一步动作。用咱们人的经验说就是:“别找了,答案在我给你的纸条上,干活吧。”

如果你不想手动干预,也可以用包装层在中间做缓存,把重复的 read_file 调用结果直接拦截返回,让代理觉得“自己读到了”,只不过这需要较强的工程改造能力,我不推荐普通用户去搞。

5.3 430 条记录里最典型的 7 类失败模式

整理这 430 条记录时,我把压缩相关的失败模式归纳成了 7 类,按出现频次排序:

  1. 决策回退——压缩前确认的选择被推翻,占比最高,约 27%。
  2. 幻觉文件——代理引用或修改了不存在的文件,因为它记错了文件名。
  3. 工具死循环——压缩后反复调用相同工具但参数不变,无任何推进。
  4. 重复劳动——同一段代码被重复修改,且两次方向相反。
  5. 依赖遗漏——删了某个函数,却没同步更新引用它的文件。
  6. 需求漂移——从“做 A”慢慢漂移到“做 B”,还自觉逻辑自洽。
  7. 测试跳过——压缩后代理开始“假装”运行了测试,实际没有执行。

其中最危险的是“测试跳过”。我甚至看到代理在输出中声称“所有测试通过”,但实际根本没运行 test 命令。这个行为跟上下文压缩没有直接关系,压缩会增加这种情况发生的概率,因为代理想尽快结束任务,但“假装测试通过”这种行为性质比一般接续失败严重得多。我的应对办法是:给代理设置强制规则,任何测试结果必须附带原始运行日志的时间戳。没有时间戳就视为未执行。

给读者一个速查表,遇到失败模式的时候直接找对应的解法:

失败模式识别信号直接解法
决策回退修改方向与之前相反读决策日志,旧会话重开并粘入决策
幻觉文件提示找不到文件手动列出真实文件树,禁止代理自造路径
工具死循环相同调用参数重复 3 次+中断,直接给出目标信息,指定下一步
重复劳动diff 中出现同一函数多次变更git diff 指认,恢复最近一次稳定版本
依赖遗漏修改后测试报 ImportError先 grep 引用,再改源码
需求漂移代码功能与最初需求不符回读需求文件(建议单独保存 REQUIREMENTS.md)
测试跳过声称测试通过但无日志要求时间戳,验证日志存在

6. 一点个人经验:把上下文压缩当“产品缺陷”而非“模型短板”

写到最后,我想给同样在做 AI 编码代理提效的人一个心态上的建议:不要因为代理上下文压缩后变笨,就否定它的整体价值,也不要指望换个更大窗口的模型就能一劳永逸。窗口再大,工程项目的上下文总会触顶。压缩机制是编码代理走向长任务执行的必经之路,问题不在于压缩这个技术动作本身,而在于我们有没有为压缩后的“失忆”做好应对机制。

我左后的实操习惯已经固定下来了:凡是超过一小时的编码代理任务,强制建一个 AGENTS_STATE.md;凡是涉及多文件的修改,强制要求代理先写决策日志;凡是完成一个阶段任务,强制重开会话而不是继续延长同一会话。这套习惯帮我节省了大量返工时间,也让 430 条记录里的失败模式出现频率大幅下降。你可以先试一条,比如下次任务超过半小时,就让代理输出一次状态文档,你会发现它后续的执行稳定性明显不一样。

RELATED READING

延伸阅读

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