☰
AI编码代理上下文压缩后如何不丢状态:430条实验记录与结构化记忆方案
2026/10/28 14:25:23 网站建设 项目流程

1. 这个实验到底在折腾什么

先交代背景。我花了大概十天时间,跑了一组围绕 AI 编码代理的对照实验,前后留下了 430 条可公开复现的记录。核心想搞清楚一件事:当对话上下文被压缩之后,一个正在干活的编码代理,还能不能"接得上"之前的活儿。

做过 AI 编码代理的人应该都有体感。你让它改一个跨十几个文件的重构任务,聊到中途上下文窗口快满了,系统触发一次压缩,把前面的历史对话摘要成一小段。压缩完之后你继续让它干活,它经常就开始犯迷糊:忘了之前定的命名规范,忘了哪个文件已经改过,甚至把已经删掉的函数又给你加回来。这不是模型笨,是压缩把关键状态给丢了。

这个实验要解决的就是这个断层问题。我把它拆成三个可观测的指标:任务连续性、状态召回准确率、以及压缩后的返工率。适合谁来参考?如果你在用 Codex 这类编码代理跑长任务,或者自己在做 agent 的记忆层,这篇东西应该能帮你少走点弯路。关键词里提到的 long_mem、engram,本质上都是在解决"记忆怎么跨压缩存活"这件事,我会在后面的章节里逐个拆开讲。

先说结论方向,免得你看到一半才发现不是你要的:单纯靠加大摘要长度没用,真正有效的是把"结构化状态"从自然语言历史里剥离出来,单独存、单独取。下面我把整个实验的设计、踩的坑、以及最后跑通的方案,一条条摊开讲。

2. 实验整体设计与思路拆解

2.1 为什么选"压缩后接续"作为切入点

编码代理和普通聊天机器人最大的区别在于,它是有"工作状态"的。普通对话压缩了,最多是忘了你前面提过的偏好;编码代理压缩了,可能直接导致代码库进入一个不一致的状态——文件 A 改了、文件 B 没改,而它以为都改完了。

我一开始也想过绕开压缩,比如把上下文窗口撑到最大。实测下来这条路走不通,原因有两个。第一,窗口再大也有上限,长任务迟早会撞墙;第二,就算没撞墙,超长上下文里的注意力衰减是真实存在的,模型对中间部分的召回明显变差,这个现象在很多评测里都被反复验证过。所以压缩不是要不要做的问题,是怎么做才能不丢状态的问题。

提示:不要试图用"无限上下文"来回避压缩问题,注意力衰减会让长上下文的效果远低于你的预期,尤其是需要精确引用早期文件路径和函数名的编码任务。

2.2 三种记忆方案的取舍逻辑

实验里我对比了三种主流思路,这也是 long_mem 和 engram 这类方案背后的分野。

第一种是纯摘要压缩。把历史对话丢给模型,让它总结成一段话。优点是实现简单,缺点是信息损失不可控,模型总结时会自作主张地丢掉它认为不重要的细节,而这些细节往往正是编码任务的关键。

第二种是向量检索式记忆,也就是 long_mem 常见的做法。把历史切片存进向量库,需要时按相似度召回。它解决了"存不下"的问题,但没解决"召回不准"的问题——编码任务里,你需要的是精确的、结构化的状态,而不是语义相似的片段。

第三种是结构化状态外挂,我把它叫 engram 思路。核心是把代理的工作状态(改了哪些文件、定了哪些约定、当前任务进度)抽成结构化数据,独立于对话历史存储。压缩只压缩对话,状态不动。

我最终选的是第三种为主、第二种为辅的混合方案。理由很直接:编码任务的本质是状态机推进,状态必须是精确的、可枚举的,不能靠语义相似度去猜。

2.3 430 条记录是怎么攒出来的

这 430 条记录不是随便聊出来的。我设计了一套固定的任务模板,每条记录对应一次完整的"任务执行—触发压缩—继续执行"循环。任务类型覆盖了单文件修改、跨文件重构、依赖升级、bug 修复四类,每类都跑了足够多的次数来排除偶然性。

记录里我重点标注了三个字段:压缩前代理的最后动作、压缩后代理的第一个动作、以及两者是否逻辑连贯。这个"连贯性"判断我用了双重标准——既看它有没有重复已完成的工作,也看它有没有遗漏未完成的工作。十天下来,这套标注帮我定位到了很多反直觉的规律,后面会细讲。

3. 核心细节解析与实操要点

3.1 上下文压缩到底丢了什么

要解决问题,先得知道问题出在哪。我把压缩前后的信息做了逐条比对,发现丢失的信息可以归成四类,而且这四类的丢失严重程度完全不同。

信息类型压缩后保留率对编码任务的影响
用户偏好与风格约定较高中,影响代码风格一致性
已修改文件清单低高,直接导致重复修改或遗漏
任务进度与待办中高,导致任务跑偏或提前收工
关键决策与理由极低高,导致推翻已定的方案

最要命的是第四类。比如前面已经讨论过"这个模块不用异步,因为调用方是同步的",压缩之后这条决策基本消失,代理很可能又给你改成异步,然后引入一堆并发问题。这种返工是最耗时的,因为它推翻的是已经验证过的结论。

3.2 结构化状态该存哪些字段

基于上面的分析,我设计了一套最小状态集。这套字段是我反复删减后留下的,每一条都对应一类高频丢失信息。

  • files_touched:已修改文件的路径列表,附带每个文件的修改摘要。这是防止重复修改的第一道防线。
  • decisions:关键决策记录,格式是"决策内容 + 理由"。理由必须存,否则代理无法判断这个决策在新情况下是否还成立。
  • task_progress:当前任务的子步骤完成情况,用简单的 todo 结构就行。
  • conventions:本次任务中临时确立的约定,比如命名前缀、目录结构。
  • open_questions:还没解决的问题,防止代理假装任务已经完成。

这套结构我建议直接序列化成 JSON 存,别用自然语言。自然语言在压缩时又会被二次损失,结构化数据不会。

3.3 压缩触发时机的选择

这里有个容易被忽略的细节:压缩不是越晚越好。我试过等到窗口快满了才压缩,结果发现压缩本身也需要消耗上下文,留给压缩的空间不够,摘要质量直线下降。

实测下来,在窗口使用率达到 70% 到 75% 之间触发压缩比较稳。这个区间既留足了压缩所需的空间,又不会因为压缩太频繁而打断任务节奏。低于 60% 就压缩,纯属浪费,还会让代理频繁"失忆"。

注意:不同模型的窗口大小和注意力分布不一样,这个 70% 到 75% 是我在实验环境下的经验值,你换模型后需要重新标定。标定方法很简单,跑几个长任务,观察压缩后连贯性开始明显下降的临界点。

3.4 状态注入的位置很关键

状态存好了,怎么塞回去也有讲究。我试过三种位置:塞在系统提示里、塞在压缩摘要后面、塞在用户新消息前面。

结果是塞在压缩摘要后面效果最好。原因是这样代理读到的顺序是"摘要(发生了什么)→ 状态(现在是什么情况)→ 新指令(要干什么)",逻辑上是连贯的。塞在系统提示里容易被后续内容淹没,塞在用户消息前面又会让代理把状态和指令混在一起理解。

4. 实操过程与核心环节实现

4.1 环境准备与基础配置

先说环境。我用的是 Codex 这类编码代理的 CLI 形态,跑在本地开发机上。如果你用的是桌面版或者别的接入方式,核心逻辑是一样的,只是配置入口不同。

基础配置里有两个参数必须调对。一个是上下文窗口的阈值,另一个是压缩策略。很多默认配置里压缩策略是"激进摘要",也就是尽量压短,这在编码任务里是灾难。我建议改成"保留结构"模式,如果代理本身不支持,那就得自己在外面包一层。

{ "context_window_threshold": 0.72, "compression_strategy": "structured_preserve", "state_store": { "type": "json_file", "path": "./agent_state/session_state.json" }, "state_injection_position": "after_summary" }

这个配置是我实验里跑得最稳的一版。threshold 设成 0.72 对应前面说的 70% 到 75% 区间,state_store 用本地 JSON 文件就够,不需要上数据库,除非你要做多会话共享。

4.2 状态提取的实现步骤

状态提取是整个方案的核心环节,我把它拆成四步。

第一步,在每次代理完成一个动作后,拦截它的输出,解析出这次动作涉及的文件和操作类型。这一步不需要模型参与,用正则或者简单的解析就能搞定,因为代理的输出格式通常比较规整。

第二步,判断这次动作是否产生了新的决策或约定。这一步需要模型参与,但可以用一个很小的模型来做,成本很低。我用的提示词大概是这样的:

以下是一次编码代理的动作记录。请判断其中是否包含新的技术决策或约定。 如果有,提取成"决策内容 + 理由"的格式;如果没有,返回空。 只输出 JSON,不要任何解释。

第三步,把提取到的信息合并进状态文件。合并时要注意去重,同一个文件被多次修改只保留最新摘要,同一个决策被重复提及只保留一条。

第四步,在压缩触发时,把状态文件的内容序列化,准备注入。这一步要确保状态是"当前有效"的,过期的决策要标记出来而不是直接删,因为代理可能需要知道"这个决策曾经存在但现在不适用了"。

4.3 压缩与接续的完整流程

把上面的环节串起来,一次完整的压缩接续流程是这样的。

代理正常干活,上下文逐渐增长。当使用率达到阈值,系统触发压缩。压缩时,对话历史被摘要成一段文字,同时状态文件被读取出来。然后系统把"摘要 + 状态 + 新的用户指令"按顺序拼成新的上下文,替换掉原来的历史。代理从新上下文继续干活。

这里有个细节我要强调:压缩后不要立刻让代理执行新任务,先让它"复述"一遍当前状态。这个复述动作能显著提升后续执行的准确性,因为它强制代理把状态读进注意力里。我在实验里对比过,加了复述步骤的记录,返工率平均下降了大概三成。

4.4 实验数据的记录方式

430 条记录我是这么记的。每条记录包含时间戳、任务类型、压缩前后的上下文长度、状态文件快照、以及连贯性评分。评分用的是 1 到 5 分制,5 分是完全连贯,1 分是完全跑偏。

记录格式我建议用 JSONL,一行一条,方便后续分析。别用 Excel,字段一多就乱,而且版本管理麻烦。JSONL 可以直接进 git,每次实验的差异一目了然。

# 记录追加示例 echo '{"ts":"2024-xx-xx","task":"refactor","score":4,"files":["a.py","b.py"]}' >> experiment_log.jsonl

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

5.1 压缩后代理重复已完成的工作

这是最高频的问题,没有之一。表现是代理又去改一个已经改好的文件,或者重新执行一个已经完成的步骤。

排查思路:先看状态文件里的 files_touched 有没有正确记录。如果记录了但代理还是重复,那问题出在注入位置或者复述步骤缺失。如果没记录,那是状态提取环节漏了。

我的解决办法是在状态注入后加一句明确的指令:"以下文件已经修改完成,不要重复修改。"这句话看起来多余,但实测有效,因为它把状态从"背景信息"提升成了"约束条件"。

5.2 代理丢失关键决策导致方案反复

表现是前面定好的方案,压缩后被推翻重来。这个问题比重复工作更隐蔽,因为它不一定立刻暴露,可能过很久才引发问题。

根因通常是 decisions 字段没存理由。代理看到一条孤立的决策,无法判断它是否还适用,就容易推翻。解决办法就是前面说的,决策必须带理由,而且理由要写得足够具体,让代理能判断适用条件。

5.3 状态文件膨胀导致注入超限

状态文件如果只增不减,跑长任务时会越来越大,最后注入时又超了上下文。这是个典型的自噬问题。

我的处理方式是给状态文件设上限,超过一定条数就做一次"状态压缩"——把已经完成的、不再相关的条目归档到另一个文件,主状态文件只保留当前活跃的部分。归档文件不是删掉,需要时可以查,但不参与日常注入。

5.4 常见问题速查表

问题现象可能原因排查动作解决方向
重复修改文件状态未注入或注入位置错检查注入位置改为摘要后注入
方案反复决策缺理由检查 decisions 字段补全理由
状态注入超限状态文件膨胀查看文件大小归档旧条目
压缩后跑偏阈值设置不当观察压缩时机调整到 70% 附近
复述不准确状态格式混乱检查 JSON 结构统一字段命名

5.5 几个反直觉的发现

实验里有几个结果和我最初的预期相反,值得单独说。

第一个,压缩摘要写得越详细,接续效果不一定越好。摘要太长会挤占状态注入的空间,而且长摘要里的噪声更多。摘要控制在能说清"做了什么、为什么"就够了,细节交给结构化状态。

第二个,状态字段不是越多越好。我一开始设计了十几个字段,结果代理反而抓不住重点。精简到五个核心字段后,效果明显提升。这印证了一个道理:记忆的价值在于精准,不在于全面。

第三个,复述步骤对简单任务几乎没用,但对复杂任务效果显著。所以别一刀切,可以根据任务复杂度决定要不要复述。

6. 工具选型与方案对比

6.1 long_mem 与 engram 思路的适用边界

这两个词在热搜里出现频率很高,我把它们的适用边界说清楚。

long_mem 这类向量检索方案,适合"信息量大但精度要求不高"的场景,比如让代理回忆之前看过的文档内容。它的优势是容量几乎无限,劣势是召回不精确。

engram 这类结构化状态方案,适合"信息量小但精度要求极高"的场景,比如编码任务的状态跟踪。它的优势是精确可控,劣势是需要针对任务类型设计字段,通用性差一些。

实际项目里我建议两者结合:结构化状态管核心工作状态,向量检索管参考资料。别指望一个方案包打天下。

6.2 不同接入方式的差异

Codex 这类代理有 CLI、桌面版、以及各种接入方式。从记忆管理的角度看,CLI 形态最灵活,因为你可以自己包一层脚本来拦截和注入状态。桌面版和图形化接入方式通常不给你这个自由度,状态管理只能依赖代理自带的机制。

如果你用的是图形化接入,那能做的优化有限,重点应该放在压缩阈值的调整和提示词的优化上。如果你用 CLI,那强烈建议自己实现状态外挂,收益非常大。

6.3 成本与效果的权衡

状态提取那一步如果用大模型,成本会上去。我的做法是用小模型做提取,只在关键决策判断时才调用大模型。实测下来,这套混合调用方式能把额外成本控制在可接受范围内,而效果损失很小。

具体来说,文件解析和去重完全不用模型,纯代码搞定。决策提取用小模型,准确率够用。只有在决策理由需要深度理解时才上大模型。这个分层策略是我实验里成本控制的关键。

7. 我踩过的坑和最后想说的

这套方案不是一次成型的。我最早那版直接把整个状态文件塞进系统提示,结果代理经常忽略它,因为系统提示里的内容太多了,状态被淹没了。后来改成摘要后注入,效果立刻不一样。

还有一个坑是状态文件的并发问题。如果你同时跑多个代理会话,共享一个状态文件会互相覆盖。我的解决办法是按会话 ID 分文件,需要共享的部分单独存一个公共文件,只读不写。

最后分享一个我觉得最有用的小技巧:在状态文件里加一个 last_action 字段,记录代理压缩前的最后一个动作。压缩后第一件事就是让代理确认这个动作的结果。这个字段能极大降低"接不上"的概率,因为它给了代理一个明确的锚点。

这个方向后续还能扩展,比如把状态文件做成可查询的接口,让多个代理协作时共享工作状态。不过那是另一个话题了,等我把多代理协作的实验跑完再聊。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询