直接放结论:上下文压缩这事儿,放在普通聊天场景里,体验上顶多是“忘了前面说啥”,但放到 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 类,按出现频次排序:
- 决策回退——压缩前确认的选择被推翻,占比最高,约 27%。
- 幻觉文件——代理引用或修改了不存在的文件,因为它记错了文件名。
- 工具死循环——压缩后反复调用相同工具但参数不变,无任何推进。
- 重复劳动——同一段代码被重复修改,且两次方向相反。
- 依赖遗漏——删了某个函数,却没同步更新引用它的文件。
- 需求漂移——从“做 A”慢慢漂移到“做 B”,还自觉逻辑自洽。
- 测试跳过——压缩后代理开始“假装”运行了测试,实际没有执行。
其中最危险的是“测试跳过”。我甚至看到代理在输出中声称“所有测试通过”,但实际根本没运行 test 命令。这个行为跟上下文压缩没有直接关系,压缩会增加这种情况发生的概率,因为代理想尽快结束任务,但“假装测试通过”这种行为性质比一般接续失败严重得多。我的应对办法是:给代理设置强制规则,任何测试结果必须附带原始运行日志的时间戳。没有时间戳就视为未执行。
给读者一个速查表,遇到失败模式的时候直接找对应的解法:
| 失败模式 | 识别信号 | 直接解法 |
|---|---|---|
| 决策回退 | 修改方向与之前相反 | 读决策日志,旧会话重开并粘入决策 |
| 幻觉文件 | 提示找不到文件 | 手动列出真实文件树,禁止代理自造路径 |
| 工具死循环 | 相同调用参数重复 3 次+ | 中断,直接给出目标信息,指定下一步 |
| 重复劳动 | diff 中出现同一函数多次变更 | git diff 指认,恢复最近一次稳定版本 |
| 依赖遗漏 | 修改后测试报 ImportError | 先 grep 引用,再改源码 |
| 需求漂移 | 代码功能与最初需求不符 | 回读需求文件(建议单独保存 REQUIREMENTS.md) |
| 测试跳过 | 声称测试通过但无日志 | 要求时间戳,验证日志存在 |
6. 一点个人经验:把上下文压缩当“产品缺陷”而非“模型短板”
写到最后,我想给同样在做 AI 编码代理提效的人一个心态上的建议:不要因为代理上下文压缩后变笨,就否定它的整体价值,也不要指望换个更大窗口的模型就能一劳永逸。窗口再大,工程项目的上下文总会触顶。压缩机制是编码代理走向长任务执行的必经之路,问题不在于压缩这个技术动作本身,而在于我们有没有为压缩后的“失忆”做好应对机制。
我左后的实操习惯已经固定下来了:凡是超过一小时的编码代理任务,强制建一个 AGENTS_STATE.md;凡是涉及多文件的修改,强制要求代理先写决策日志;凡是完成一个阶段任务,强制重开会话而不是继续延长同一会话。这套习惯帮我节省了大量返工时间,也让 430 条记录里的失败模式出现频率大幅下降。你可以先试一条,比如下次任务超过半小时,就让代理输出一次状态文档,你会发现它后续的执行稳定性明显不一样。