ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

AI编码代理上下文压缩后“失忆”?430条实验记录与状态外置方案

2026/10/7 17:34:04 拓冰建站 浏览量
AI编码代理上下文压缩后“失忆”?430条实验记录与状态外置方案 做AI编码代理最让人崩溃的瞬间不是它写不出代码而是它干到一半突然“失忆”了。前三十轮对话里它明明已经定位了问题根源上下文一压缩下一轮它就开始改完全不相关的文件你盯着diff看了十分钟感觉对面换了个实习生。过去十天我跑了一轮专门的实验攒了430条公开记录就是为了搞清楚一个问题上下文压缩之后AI编码代理到底怎么接得上这篇文章就是这次实验的完整复盘包括压缩断在哪些环节、怎么设计工作流让代理不丢状态以及我在实际操作里踩过的坑。做agent开发、重度使用AI编程工具或者单纯对LLM应用工程感兴趣的朋友这篇应该都能用得上。1. 先说结论AI编码代理的失忆本质是“软状态”丢了这次实验之前我一直把“上下文压缩后代理变蠢”归因于模型能力不够。跑了430条记录之后我改了想法多数时候不是模型变蠢了而是它赖以决策的状态结构被破坏了。1.1 硬状态仓库、进程、测试结果不会骗人AI编码代理在工作时有一部分信息是“硬状态”也就是仓库里的文件、Git历史、运行中的进程、测试输出。这些状态不在对话上下文里而是真实存在于环境中。代理随时可以重新读文件、执行命令、查看git diff硬状态基本不会因为压缩而丢失。例如代理改到一半上下文爆了压缩之后它不知道刚才改到哪个文件。但只要它愿意执行git status和git diff就能把现场重建出来。所以在硬状态层面“接不上”通常不是真接不上而是代理没有主动去接。1.2 软状态为什么这么改、下一步要干嘛、哪些方案被排除了真正容易丢的是“软状态”。软状态包括几个东西当前目标的优先级是什么之前为什么否决了方案A而选择方案B哪些假设已经被验证过哪些还在悬而未决的状态。举个例子。代理之前决定“不修改公共接口的签名改为在调用侧做兼容”理由是调用方有十几个全改风险太大。这条决策如果只存在于对话历史里压缩摘要很可能写成“已确认接口兼容方案”原因和权衡过程全部丢失。压缩之后代理面对同样的问题很可能会重新评估“直接改接口签名然后全仓替换”的路径因为它在摘要里已经看不到那条否决理由了。我习惯把上下文压缩比作一次仓促的交接班。硬状态是交接单上写着的“设备运行正常”软状态则是老员工脑子里记住的“3号阀门不能拧到底因为上周出过事故”。交接单可以靠翻记录重新获得但脑子里那个“为什么不能”丢了才是真正耽误事的。430条记录里绝大多数“接不上”案例断掉的全是软状态。2. 为什么AI编码代理绕不开上下文压缩这道坎有人可能会问既然压缩有风险那不压缩不就行了答案是不行。我在这10天实验里切身体会到代理工作时的上下文膨胀速度远比想象中激进。2.1 一次普通任务上下文膨胀得有多快我手工统计过其中一次任务修改一个中型仓库里的支付模块修复汇率换算精度问题。这个任务从开始到结束代理一共执行了42轮工具调用。每一轮调用都会把工具输入、工具输出、文件读取结果、命令输出全部追加到上下文里。其中一轮cat读了一个配置文件直接输出了8000多个字符一轮测试命令输出了3000多行日志。42轮下来上下文里的内容早就超过了模型能完整容纳的上限。更麻烦的是现在的编码代理为了稳定输出还需要在每轮追加“思考过程”和“工具使用记录”。这些内容的增长速度是平方级的。一个30分钟的任务对话历史很容易就冲到数十万token。不压缩后续每一轮的延迟和成本都不可接受而且模型在超长上下文里的注意力会发散自己开始忘事。2.2 三种压缩方式损失的是同一样东西市面上主流做法无非三种截断、摘要、状态外部化。我分别跑过损失的东西其实同源。压缩方式常见实现典型损失实验中的失败表现截断只保留最近N轮对话早期目标和关键决策整体消失代理忘掉任务范围开始自由发挥摘要式压缩用模型把旧对话总结成一段话细节被抹平约束被泛化代理给出模糊结论执行时与原始需求偏离状态外部化把关键状态写入外部结构对话内只保留轻量摘要外部状态设计不完整遗漏隐性约束形式上有状态文件实际根本没接上有意思的是三种方式里风险最高的反而是摘要式压缩。截断至少是诚实的——删掉了就是删掉了代理知道自己不知道有时候还会主动问用户。摘要式压缩的问题在于它表面上保留了信息但这些信息已经被摘要模型“再创作”了一遍代理以为自己知道实际用的是二手信息。2.3 压缩的隐形偏见它会把重要的东西磨成常规摘要式压缩有一个很难察觉的问题它在压缩时会自动给信息分配优先级。那些在原文里出现次数多、表述强烈的内容容易被保留而只出现一次、以否定句式存在的约束很容易被丢弃或被改写成“考虑环境限制”之类的模糊说法。用通俗话讲摘要就是一个传话游戏。第一轮传的时候“不要用PNG必须用SVG因为图标需要动态变色”传完几轮就变成了“图标文件格式有要求”。代理拿到这个摘要大概率会直接导出PNG然后整个改版全废。所以在设计实验时我没有简单记录“压缩后任务是否成功”而是把每一次“接不上”的具体表现都拆开来看这样430条记录才能说明问题。3. 10天实验430条公开记录里到底发生了什么3.1 实验设计与记录口径这轮实验的思路是搭建一套固定的编码代理任务集在受控环境里跑10天每天跑固定数量的任务并把每次会话的关键事件落成公开可查的记录。任务集中在三类修复已知bug、添加小型功能、做局部重构。仓库规模控制在几千行代码级别确保任务本身可以单次完成但过程中会触发多次工具调用让上下文自然膨胀。“430条公开记录”指的不是430次完整任务而是430条事件级记录。每条记录中包含任务ID、当前上下文水位、压缩触发方式、压缩前代理正在做什么、压缩后代理做了什么、结果判定以及失败时的归因类别。记录格式统一后续可以直接做统计。记录字段说明示例值task_id任务编号bug-014context_watermark触发压缩时的上下文占用级别highcompression_type压缩策略summarize / truncate / statefulaction_before压缩前代理正在执行的动作修改utils/currency.pyaction_after压缩后代理的第一个动作读取views/cart.pyresult任务结果pass / fail / degradedfailure_cause失败归因约束丢失 / 完成状态丢失 / 引用错位3.2 主要指标与整体结果实验统计了三个核心指标任务完成率、压缩后返工次数率、关键引用错误率。任务完成率很容易理解返工次数率是指同一任务内代理重复执行已完成步骤的比例关键引用错误率则统计代理引用错误的文件路径、函数名或提交版本。整体结果显示使用了被动摘要压缩的任务失败率明显高于不触发压缩的对照组。更值得关注的是失败案例的归因高度集中。压缩后失败的任务中大约一半归因于“关键约束在摘要中丢失”三成归因于“完成状态丢失”还有一成多是“文件或符号引用错位”。这说明“接不上”不是一个模糊的整体问题而是有明确规律可循的工程问题。3.3 三类失败模式的集中爆发失败案例看得多了会发现它们有固定剧本。约束丢失类代理在压缩前明确知道“不要动数据库迁移文件”摘要之后它开始重构迁移逻辑。完成状态丢失类代理已经跑通了测试A压缩后重新跑了一遍发现测试A“失败”然后开始修一个根本不需要修的代码。引用错位类代理摘要里写着“修改之前提到的那个文件”实际执行时选错了同名文件改完发现改的不是目标。这些案例的共性在于代理在压缩前后的行为之间缺少一个可验证的“接续锚点”。它只能依靠上下文里的残余印象而不是去环境里重新确认现状。这正是后面实操方案要解决的核心问题。4. 压缩之后接不上到底断在哪个环节4.1 决策因果链断裂代理忘了“为什么”最隐蔽也最危险的一种断裂是因果链断裂。代理在前期做了大量探索最终形成了一个决策。这个决策本身可能被摘要保留了但决策背后的原因被丢弃了。我记录过一条典型case。代理在压缩前判断某个依赖库版本需要升级因为旧版本存在一个安全问题。摘要压缩后代理依然记得“要升级依赖库”但忘记了“升级的原因是安全漏洞”结果它升级到了一个依然存在漏洞的中间版本并且因为接口变化引入了一堆新问题。这就是典型的知道“做了什么”、忘了“为什么做”。在编码场景里“为什么”往往比“做什么”更关键因为它决定了后续所有相关决策的优先级。一旦因果链断掉代理就只能基于表面的代码结构继续工作很容易做出和原始意图相悖的修改。4.2 关键约束被“磨平”否定句和边界条件是重灾区摘要模型在处理否定句时表现尤其不稳定。“不要使用全局变量”“不要修改公共接口”“不要引入新的运行时依赖”这类约束在对话里通常出现在某一次讨论中之后不再重复。摘要时它们很容易被压缩成“注意代码风格”“保持兼容性”这类无害而模糊的表述。边界条件也一样。某次任务要求“输入参数为空时必须抛异常而不是返回默认值”这个细节只出现过一次。压缩后代理实现了“返回默认值”的兜底逻辑完全反着来而它自己毫无察觉。我把这类问题定义成“约束磨平”。它比完全丢失更难排查因为代理的行为看起来是合理的只是对照原始需求时才发现方向错了。4.3 计划与执行状态脱节重复劳动和遗漏步骤代理在执行过程中通常会维护一份“计划清单”但它对清单的记忆和对环境实际状态的认知是两条线。压缩后这两条线很容易脱节。一种表现是重复执行已完成步骤。代理压缩前已经修改完文件并跑通了测试压缩后它忘了“已经做完”又把同一段测试跑了一遍看到输出不是预期结果误以为之前的修改有问题开始反向修复。另一种表现是跳过未完成步骤。代理压缩前处于“正在分析数据流还没有动手改代码”的中间状态压缩后摘要把它描述成“正在进行数据流分析”代理以为自己已经分析完了直接跳到写代码阶段写出来的内容建立在错误前提上。4.4 引用错位文件名、符号名、提交号失真第三类是纯技术层面的断裂。压缩摘要如果由LLM生成它在引用代码符号时很难保证精确。文件名可能被缩写或改写函数名可能被换成含义相近的名字commit hash则几乎不可能被准确保留。代理后续要执行文件修改操作时就会因为路径不准确而失败。更隐蔽的是它选错了同名文件。一次任务里仓库存在utils/helper.py和legacy/utils/helper.py摘要里写的是“修改utils/helper.py”代理直接选了前者其实任务目标是后者。这类问题在人工排查时非常浪费时间因为你看到代理确实“在改文件”改了之后结果全错一开始根本想不到是引用错位。5. 实操方案让代理“接得上”的三个工程手段基于430条记录的分析我在实验最后三天迭代出了一套工程方案。核心思路是把“让模型记住”改成“让环境兜底”。这也符合我前面说的硬状态靠环境、软状态靠结构。5.1 状态外置把记忆从对话里搬进仓库最基础的做法是让代理把工作状态写进仓库里的状态文件。我不建议用单个大文件那样读起来本身占上下文。我用的目录结构是.agent-state/ ├── manifest.json # 总体任务状态与完成度 ├── decisions.md # 关键决策与否决理由 ├── constraints.md # 必须保留的原始约束 └── next-actions.md # 下一步动作清单以decisions.md为例格式尽量固定# DECISIONS ## 2025-06-03 汇率精度问题修复方案 - 决策不修改 CurrencyConverter 类的公共接口签名 - 决策原因仓库内引用该类的文件超过 40 个全局修改回归成本过高 - 替代方案在调用侧做舍入逻辑兼容 - 否决理由调用侧统一处理会增加多处重复代码故仅在边界条件处兼容这个文件的价值在于它不依赖模型的记忆能力。上下文压缩后代理只要执行一次cat .agent-state/decisions.md就能把决策因果链完整恢复。代价是每次决策更新时多写几十行文本换回来的却是压缩后的确定性。5.2 压缩前交接协议压缩不是销毁而是存档这是我认为最实用的一招。与其被动等上下文快满时让系统随机压缩不如定义一套主动的“交接协议”当上下文使用率达到阈值比如80%时强制代理停止工作先补齐状态文件再允许压缩。我把这个动作叫checkpoint执行过程固定在三个步骤第一更新manifest.json中的完成度与当前所在步骤第二把所有尚未验证的假设写入decisions.md标注“待确认”第三清空next-actions.md只保留下一步、下两步必须做的事。next-actions.md的模板我实测下来这样最稳# NEXT ACTIONS 当前步骤修复汇率换算精度问题已完成边界条件分析 下一步在 utils/currency.py 中增加小数点后两位的显式舍入 再下一步运行测试命令 pytest tests/test_currency.py -k precision 危险动作不要做不要修改 db/migrations/ 下的任何文件注意最后一行“危险动作”。这是我实验里发现特别有效的设计把“不要做的事”直接写进状态文件不依赖摘要模型帮你保留。约束磨平问题靠这个字段就能大幅缓解。5.3 摘要模板化不要自由发挥只要字段补齐实验中期我发现摘要式压缩最大的问题在于摘要模型自由发挥。后来我改变了策略摘要不要求“总结”而是按固定字段抽取。缺哪个字段就明确写[未记录]而不是用模糊语言填补。固定的摘要模板字段包括当前任务的一行描述、已完成改动文件的完整路径列表、已验证的事实、未验证的假设、下一步的第一动作、用户最新一条明确指令。每条都必须原样保留关键名词特别是文件名、函数名、命令语句不允许改写。这个调整的效果很明显。自由摘要经常把“修改utils/currency.py的round()调用”抽象成“调整货币计算逻辑”字段化之后必须写完整路径和函数名引用错位类问题大幅减少。5.4 压缩后的再规划循环让“读取状态”成为强制第一动作有了状态文件还需要保证压缩后的代理真的会去读。我在实验里采用的方案是压缩完成后不直接让代理继续原任务而是插入一个固定的“再规划阶段”。这个阶段强制要求代理依次执行查看manifest.json、查看next-actions.md、执行git status对比当前仓库实际状态、执行git diff --stat确认文件改动范围然后重新输出一份“基于当前实际状态的下一步计划”再开始动手。表面上看多了几步工具调用实际每次也就多消耗几千token。但它把代理从“依靠残余记忆继续”切换成了“重新从环境里获取真实状态再继续”。这个切换直接决定了压缩后任务能否接上。实验后期凡是启用再规划循环的任务压缩后返工率下降了约六成。6. 实战速查430条记录里最高频的5个坑6.1 代理压缩后反复读同一个文件现象代理反复执行cat读取同一个文件读完之后没有明显动作。这通常是因为它无法从摘要中确认该文件是否已经被修改过或不清楚修改前后的差异。排查思路先看状态文件里是否记录了该文件的改动状态再看代理是否执行过git diff。如果两者都没有说明压缩后的代理没有可信的信息源。解决办法是把“已修改文件清单”写进manifest.json并让代理在每轮开始时查看一次。6.2 代理把已经通过的测试又跑了一遍然后说失败现象压缩前测试已通过压缩后代理重新运行失败然后开始“修”原本没有问题的代码。排查思路这不是测试真的失败而是代理忘记了测试结果本身。测试输出属于一次性信息一旦上下文压缩之前的通过记录就成了历史消息。解决方法是把“已验证事实”单独存到状态文件里并注明验证时间。代理压缩后再跑测试之前先看状态文件就能规避。6.3 代理把需求悄悄改了现象压缩后的行为和压缩前的计划明显不一致但代理自己认为是一致的。这通常是约束磨平导致的否定句约束被摘要成了模糊表述。排查思路检查摘要字段里是否保留了原始约束的原文。如果状态文件里没有constraints.md或其中只有泛泛而谈的“注意代码规范”那几乎可以断定是约束丢失。解决方法是把约束用“危险动作”字段原样保存不要允许摘要模型改写。6.4 代理找不到需要修改的函数现象代理在仓库里搜索某个函数名搜不到于是开始猜测并修改其他代码。排查思路这大概率是引用错位问题。摘要改写后的函数名和真实名称不一致。解决办法是摘要字段里强制使用引用符号如直接写CurrencyConverter#convert并用反引号包住禁止用自然语言描述代替符号名。6.5 压缩后代理变得特别保守不敢动代码现象压缩后代理不再修改代码只做分析反复输出“建议”。排查思路这是因为代理丢失了详细的执行上下文后对自己缺乏信心会退回“安全模式”。同时也说明状态文件里缺少一个明确的“已验证事实”列表。给它一份可信的当前状态并用next-actions.md给出明确的下一个动作通常就能让它重新进入执行状态。7. 一点个人体会跑完这次实验我最深的感受是做AI编码代理永远不要指望模型在压缩后“自然恢复记忆”。上下文压缩不是黑盒操作它是系统设计的一环压缩策略和代理工作流必须放在一起设计。如果你现在正在做类似方向我的建议是别急着调模型的温度或采样参数先检查自己有没有给代理提供一套可依赖的外部状态。最后分享一个最简单的小技巧如果你暂时不想搭完整的状态文件体系至少把你的核心约束写得足够具体具体到“不要修改db/migrations/下的任何文件”而不是“注意数据库安全”。约束越具体摘要模型越难磨平它。其它都能妥协这一条是我在430条记录里验证出来的性价比最高的改动。