Agent 上下文压缩:如何缩短历史,又让长程任务继续执行

记忆篇讨论了 Agent 如何记录和使用记忆。随着对话、工具调用和任务执行不断推进,当前上下文会持续增长,而模型能够接收的内容始终有限。记忆解决的是哪些信息值得长期保留;压缩解决的是,怎样整理已经进入上下文的历史,让当前任务能够继续执行。

读者收获:

  • 分清压缩、原始历史和长期记忆的职责。
  • 理解压缩的触发、执行以及压缩后的输入结构。
  • 判断压缩是否保住了长程任务的关键状态。

压缩真正被追问的是什么?

1. 长时间迭代一个项目,上下文不断增长,怎么做压缩?

Q: 你们项目是怎么做压缩的?

主要分三部分,从压缩前/中/后,介绍压缩的处理方案: 前:

  1. 什么时机触发压缩:一般情况下根据不同模型的 context, 会设置一个最大阈值,到达阈值之后,就会触发压缩执行,而这个阈值大小根据业务做调控,如果在对话中经常触发压缩,不但会消耗 token,也会增加用户等待时间;我们的业务阈值定在 20%-30% 波动调整;
  2. 什么时候会去判断阈值:会在每一轮(run)会话的一次模型调用前(step),去进行阈值判断,如果触发压缩,则进行压缩处理。
  3. 对于停止会话并休眠的会话,到达一个固定节点,也会触发压缩 中:
  4. 压缩什么: 先区分压缩信息,简单而言就是即将传入 messages 的数组信息,这个时候需要分析哪些信息需要全量保存(最新信息),哪些信息可以进行压缩处理;一般情况下,会根据最新的 n 条信息完全保存,然后剩余的信息进行压缩;
  5. 怎么压:message 都是结构化信息,我们会根据需要,先将所有 message 分类,比方说 skill catalog,memory 信息,用户信息,工具 output, 错误信息等,然后根据一定策略组合成结构话数据,写入到一条 user message 中。 后:
  6. 压缩的最终结构就是一条新的 message,这个 message 作为压缩信息,重新进入 messages 中,同时有一些持久化信息需要补充,如 memory,skill catalog 等信息,需要重新组装成 message, 放在messages 最顶端等候被 model 调用

所以,整个压缩过程,就是在会话执行到模型模型调用前,判断到达阈值了,将历史信息按策略压缩成一条(多条)信息,然后补充不被压缩的信息,继续进行后续对话的过程。

图 1:每个 step 的压缩判断与执行流程

图中展示对话执行中的同步压缩路径。只要本轮还要继续执行新的 step,就会再次经过阈值检查。

2. 什么时候会触发压缩

**Q:Token 使用到什么程度触发压缩,阈值怎么定?

  1. 这个主要是根据使用的模型不同动态调整的。如果发现近段时间触发压缩频率异常,则继续调整阈值。
  2. 这个可以根据我们自己 vibe coding 的习惯做测试,什么时候我们会手动进行 compact,我一般是超出 50%就会触发,或者我准备做一个超大的任务时,会去触发;对应于业务而言,最重要的策略就是:不能突破100%,有空的时候

3. 压缩在什么时候发生?

**Q:压缩发生在工具调用之前、之后,还是一轮对话结束之后?

压缩发生在用户发起对话后,模型执行前。对于非一问一答的agent,模型在每一 step 调用模型前都会去进行阈值判断。

4. 压缩还没完成,下一条请求已经到了,怎么办?

这个和业务有关,不同的 agent 可能允许使用多 agent 处理,本问题只根据经验回答

  1. 会话级:如果一轮对话没有完成,用户继续发起请求,这个请求并不会被接入到正在执行的 session 中,而是放入队列中等待执行(没有做多模型),因此不会出现当前会话执行时,被其他请求打断的情况
  2. step 级:对于 subagent 执行完成,请求返回到主 agent,发现主agent 正在压缩,这个时候其实也是做了队列,等待当前 step 执行完成后,才会获取 subagent 的信息,在下一个 step 才会消费这个请求;

用户其实想要问的是,如果有请求过来时,怎么保证压缩过程的稳定,以及这个请求的信息,是否会被压缩消费,这一块重点还是看 agent 是怎么判断的。

图 2:压缩期间到达的新信息,何时被消费?

按上文的排队方案,用户新请求与 subagent 结果分别等待不同的执行边界;压缩完成不代表两类信息都会立刻被接入。

5. 历史被压缩后,最初的技术文档和用户约束会不会丢失?

  1. 信息是否会丢失,并不是单靠【压缩】这个模块处理,更多的应该是【记忆】是怎么处理的;压缩触发后,确实会将会话信息做了处理,压缩后的信息会丢失一些细节,但是重点信息都保存了;
  2. 会话级别的重要信息,都被 memory 存储,压缩后会重新进入到会话中,而不是仅靠压缩处理;
  3. 对于整个会话/项目的整体流程/约束,本身就不应该依靠会话信息做约束,而应该靠整套记忆存储系统协助,压缩改动的仅仅只是会话记忆,并不会对整个会话/项目有破坏性的改动。

简单介绍一下我知道的记忆

图 3:压缩后的 messages 由哪些部分组成?

较早历史进入摘要,近期消息保留原文,memory 与 skill catalog 等信息重新加载。下图按来源分组,具体消息角色和拼接顺序以实现为准。

What:到底压缩了什么?

压缩改变的是下一次模型调用使用的消息视图,不等于删除原始历史,也不等于修改长期记忆。

展开三类内容:

  • 适合归纳的历史:已经完成的讨论、重复解释、过时的工具输出。
  • 需要保留的执行现场:近期用户要求、当前操作以及必要的工具调用和结果。
  • 能够重新加载的信息:项目规格、文件内容、记忆目录、当前能力配置。

Why:为什么不能等窗口用满再压缩?

第一,容量是硬约束。 输入不仅包含聊天,还有系统指令、工具定义、工具结果和读取的文件;还要为后续输出及执行增长留出余量。

第二,能装下,不代表能同样有效地利用。 《Lost in the Middle》在所测试的模型与任务中发现,相关信息位于中间位置时,利用效果可能弱于开头或结尾。文章应把它写成需要关注的风险,而不是“所有模型只关注两端”的固定规律。论文

第三,压缩与不压缩都有代价。 长输入会增加处理负担;压缩本身也有模型调用、延迟和信息遗漏风险。因此目标不是越早越好、越短越好,而是找到任务质量与资源消耗之间的平衡。

When + Where:什么时候触发,在哪里生效?

触发:看什么水位,在哪个边界判断?

  • 水位依据的是实际已经调用的 usage,还是下一次输入的估算。
  • 阈值表示已用占比还是剩余空间。
  • 单条工具结果很大、压缩耗时较长时,需要预留多少增长空间。
  • 一次用户请求内部也可能执行很多步,不能只在新用户消息到来时检查。

30%–50% 可以作为待验证的候选区间,但不能写成普遍经验。 现有代码中既有 75% 的比例配置,也有绝对 Token 阈值;具体取值应与对应模型、场景和验证结果绑定。

20%-30% 属于业务需要,不同业务需要有不同的考量。阈值拉太高还需要解决下一次任务 context 炸了要怎么处理

生效:下一次模型到底看到了什么?

  • 同步压缩:先等待摘要,再继续,流程清楚,但增加等待时间。
  • 后台压缩:执行期间生成摘要,但必须管理固定压缩窗口、新增消息以及增长上界。

不同类型的压缩各有优劣,重点还是看业务,同步压缩属于 chain 调用,简单明了,会耗时,但是由于压缩引发的问题也少,处理起来更方便,属于 mvp 阶段优选;等后续迭代优化时,可以进行其他选型处理。

图 4:同步压缩与后台压缩,等待发生在哪里?

这是两类方案的执行示意。后台路径中的继续执行受上下文余量约束,摘要就绪后,还需要合并窗口外新增的消息,才能在后续模型调用中使用新视图。

小结:把记忆与压缩重新合起来

记忆负责让重要信息能够长期保存和重新获得;压缩负责整理当前上下文。两者共同服务的目标,是让 Agent 在有限窗口中持续、准确地推进任务。