上下文管理

6 阅读16分钟

让 Agent 重构一个模块,一开始只需要知道目标和约束。随着任务推进,它会不断读取代码、运行测试、查看日志。对话越来越长,任务还没完成,上下文却已经接近上限。

这时,该留下什么?旧日志可以删,但里面可能藏着排查问题的线索;历史可以压成摘要,但摘要是否记得接口不能改、哪些测试已经通过、下一步应该做什么?

上下文管理要解决的,就是怎样在有限窗口里保留足够的任务信息,同时控制执行成本和时延。按需检索、压缩和外部笔记,是处理这类长任务的几种重要手段。

本文沿着两条主线展开:先减少不必要的信息进入,再对已经积累的历史进行分层清理与压缩。 在此基础上,结合 OpenCode、Codex 和 Claude Code,讨论压缩如何影响输入缓存、怎样保留任务接力所需的状态,以及摘要请求自身超限时如何恢复。

Token 是模型处理信息的计量单位,不等于字数。上下文窗口限制单次请求能够容纳的信息量,需要同时考虑输入、输出,以及部分模型的推理 token。

减少不必要信息进入上下文

上下文是有限的,如此珍贵,那要选择必要信息进入上下文:

  1. 预加载:提供任务必须知道的信息
    • SystemPrompt:设置人格、回复风格、工具使用、skill描述和路径。
    • 项目指令文件AGENTS.md:一些agent会把完整加载到system prompt中的指令标签,如opencode;还有一些可能会作为user prompt默认发送,如codex。
    • 初始化输入:把目标,约束,事实,验收结果要说清楚。例如,重构模块时是否保留旧接口、需要通过哪些测试,哪些模块不需要改动或只改哪些模块,应在开始时明确。
  2. 懒加载:先加索引,需要时再读取。
    • Skill:几乎所有产品都支持Skill渐进式披露,只暴露名字、描述、文件路径的必要信息;使用时再读取内容、参考文件或脚本,按需加载。
    • MCP工具:MCP Tool的参数schema不保留,只暴露Name和描述,需要再通过 Tool 查找;支持工具搜索的产品,如字节deerflow,可以先提供索引,需要时再加载具体定义,不同产品不同,不是 MCP 协议要求省略 schema。
    • 项目指令文件AGENTS.md:指令文件保留必要说明和资料入口,详细架构、接口与排障记录放在参考文件中,并说明什么时候需要读取。
    • 加载方式:ls查看目录,glob找到具体文件、ripgrep搜索关键字,rag进行向量搜索;

懒加载能减少初始输入,但需要清楚的入口,否则模型可能不知道还有哪些资料可用。

  1. 外挂磁盘:保留入口,需要时取回原文。
    • Plan模式、SSD驱动开发:进度、状态可追踪,可复用的中间产物写成文件,后续可随时按需查看,另外SSD产物还可复利,后续都可以懒加载查看当初决策。
    • 长期记忆:形成文件并每次对话结束时维护事实,需要时获取。
    • 原始证据: 保存日志、测试输出和阶段产物,便于重新核查;上下文里保留当前需要的结论与完整路径即可。
  2. 不加载:
    • multi agent:Multica的Team形式,每个Agent负责不同事项,交接报告来隔离上下文;通过启动Subagent等方式隔离,如让其编码等工作,主 Agent 再接收结论,未解决问题和产物路径。
    • 任务已完成重新启动一个窗口再接着;任务可接力,输出交接文档,让另外一个agent获取必要输入即可。

图1-信息进入策略-v003.png

多级上下文压缩方案

在有限的上下文中,要保证任务运行的质量(足够决策的上下文)情况下,降低端到端成本和完成时延,有4个要素关注:

  • 上下文占用:保留的信息占多少token。
  • 上下文质量:在压缩、筛选、转存后,关键事实、决策依据等准确完整。
  • 执行成本:完成任务所耗费的token。
  • 执行时延:发起任务到任务完成耗时。

为了让上下文占用减少:

  • 轻量级压缩:
    • 操作:裁剪,转存磁盘等
    • 优点:执行操作的成本和耗时几乎可忽略;上下文较为缓解但没根治。
    • 缺点:要考虑对输入缓存的失效进而导致成本进一步增大,最好可以批量一次性完成,避免频繁改变前缀;转存也可能带来检索的步骤,直接裁剪可能带来上下文部分缺失;
  • 重量级彻底改写:
    • 操作:上下文压缩
    • 优点:上下文占用大幅度减低。
    • 缺点:操作耗时和成本较大,且上下文质量无法保证无损。

图2-分层压缩-v003.png

每个模型厂商,如Deepseek、Openai等,都会有输入缓存cache entity,但有过期时间如5min(不一定),输入前缀相同就可能会缓存命中,但因压缩上下文可能会导致部分前缀变化,进而导致部分前缀缓存失效,修改位置之前的稳定前缀可能仍可复用 。缓存命中的话,缓存输入会很便宜,避免重复计算已有前缀。

上下文压缩对输入缓存的影响例子:设已有请求为:稳定指令 P + 旧历史 H + 近期内容 R;如果改成 P + 摘要 S + R,P 仍有复用机会,而 S + R 通常不能直接沿用原来 H + R 的完整前缀。

图2-分层压缩-v003.png

如何减少上下文压缩对输入缓存的影响:

  1. 窗口已经放不下,缓存再便宜也不能继续携带原历史数据;
  2. 窗口尚且充足时,则应衡量后续轮数、历史增长速度和清理收益。只清理掉很少内容,又反复修改前缀,可能得不偿失;把信息压的太少,迫使每轮都要重新读取文件,同样也会很贵。

扩展:多级上下文压缩,类似Java的GC,是在有限的内存中,在吞吐量和停顿时间的取舍。

为了减少内存占用:

  • 轻量级压缩YGC:回收年轻代,利用“大多数对象很快就会死亡”的特点,部分存活会晋级老年代。速度快,较频繁,但压缩有限。
  • 重量级压缩Full GC:整个 Java 堆,包括年轻代和老年代。速度慢,可能会影响应用程序,压缩较大。

共同特点是何时整理,整理多大范围,如何确定没用的,以及付出的计算和成本,但GC是必须维护程序正常执行,但Agent上下文压缩中,需要判断哪些信息未来没用或有用,这个可能是会出错,Agent会删掉未来可能使用的信息,无法像 GC的可达性分析精确找到没有使用的对象。

OpenCode

V2

1、第一层:工具结果限制

  • 触发条件:工具结果超过配置的行数或字节预算。配置示例为 2000 行、50*1024 字节(50K)。
  • 处理方式:将工具结果全文外挂到磁盘,然后工具响应结果只留下预览、已截断提示和文件路径;部分特定工具额外限制:glob默认最多返回 100 个文件路径; grep默认最多返回 100 条匹配。
  • **继续执行:**使用保留的结果推进,必要时读取完整输出。
  • 取舍:不增加摘要调用,并保留取回可能。

2、第二层:历史摘要

  • 触发条件:在调用模型响应后 或 工具结果返回后,当上下文窗口超 C - max(O, B)(C是模型上下文窗口、O是本次请求的最大输出 token 数、B是上下文留下的安全余量)。默认O=16K、B=20K,假设 C = 200K,那么超过约 180K 输入 token 时触发;

保留一定大小窗口,是因为本身压缩请求也会占用窗口。

  • 处理方式:
    1. 保留不超15K的消息(包括助手消息,工具消息,user消息上下文)不压缩,如果超8K就放弃那条消息。
    2. 其他转换成模型可读文本,如用户消息变成 [User]: ...,工具调用和结果变成 [Assistant tool call]、[Tool result],且工具结果保证不超过2000个字符,超过则截取,拼接成 2000个字符+[truncated]。
    3. 把 “较旧历史文本 + 要求模型为另一个 coding agent 生成交接摘要 + 固定模板(目标,约束和重要信息(如事实、假设)、已完成/进行中工作,阻塞项,下一步 和 相关文件)“让另外一个模型压缩。
    4. 压缩后塞入一个user消息:摘要 + 近期历史的序列化记录。
    5. 如果压缩失败,拒绝为超过上下文大小,则接着重试。

扩展:压缩后模型接着对话不一定任务正确延续:

状态应保留的内容丢失后的典型问题
目标与约束最终交付物、验收条件、不能破坏的行为、用户修订做错任务或恢复已废弃方案
工作状态完成、进行中、未开始、阻塞分别是什么重复做已完成工作,遗漏未完成部分
决策依据选择了什么、为什么、替代方案为什么不适用再次尝试已经排除的方向
原始证据文件、commit、日志路径、时间、定位片段只记得“通过”,无法确认测了什么
在途操作后台进程/任务标识、外部动作是否已完成重复启动、重复提交、误报完成
下一步立即可执行的动作与必要输入泛泛“继续工作”,没有推进依据

图4-任务交接状态-v003.png

V1

1、第一层:一样

2、第二层:旧工具剪枝

  • 触发条件:客户端配置默认关闭,每次对话结束触发。
  • 处理方式:跳过近两轮对话,从后往前处理已完成的工具结果,跳过skill工具直到上一次摘要或上一次剪枝,然后40K的工具结果不压缩,其余部分如果超20K则开始清除工具内容,[Old tool result content cleared] 的占位文本替代,不再写磁盘。(超20K才压缩是为避免频繁破坏上下文缓存)

3、第三层:历史摘要请求

  • 触发时机:触发阈值 = max(0, context 窗口 − 模型最大输出 tokens),如果模型最大输出token是8K,那么剩下8K就开始压缩。
  • 处理方式:保留最近上下文(包括user、assistant、tool)最大输出token的1/4,最少2K,最多15K上下文,其他则按一定规格让LLM总结形成交接文档:目标,约束和重要信息(如事实、假设)、已完成/进行中工作,阻塞项,下一步和相关文件。

Codex

  1. 第一层:工具结果限制
  • 触发条件:对工具命令执行结果,默认上限10K(可配置)。
  • 做法:保留工具前半段 和 后半段(各一半),中间用 …N tokens truncated… 代替,没有完整文件。
  • 取舍: 适合同时保留启动信息与结果尾部,不增加摘要请求;如果错误恰好在中间,则可能丢掉关键证据。重要日志应显式保存,并针对关键词读取。
  1. 第二层:LLM压缩
  • 触发条件:超过模型上下文窗口的90%(可配,不同模型可能还不同,简单认为90%)
  • 处理方式:在会话前 和 会话中(工具、LLM响应后)
    • 本地压缩:
      1. 把所有历史消息写入摘要中,按当前进展和已做的关键决策、重要上下文、约束和用户偏好、接下来的工作、继续工作需要的数据、示例和引用生成。
      2. 从后往前保留最近20K user消息(工具、AI消息忽略),直到超20K截断,再拼接摘要信息,当成最新的用户消息。
    • 远端压缩: 取决于功能开关是否开着。
    • 如果发现 摘要请求+历史记录 超模型上下文,会继续一次紧急压缩,会把最久的历史项清除,每替换一次就重新检查,包括工具输出,工具搜索结果等,会替换成“ 输出超出可用上下文,已截断”。
    • 取舍: 摘要可读、容易检查,另外保留用户要求以降低遗漏概率;但助手已做的技术判断和工具证据依赖摘要,旧用户指令也可能被预算截掉。重复压缩还可能积累语义偏差。

图5-摘要超限恢复-v003.png

ClaudeCode

可以参考,不一定正确,因为ClaudeCode并没有公布很多细节,只是从网上泄露代码,各种博客中而来,非官方。

  1. 第一层:工具大小限制
  • 触发时机:每次工具执行完。
  • 处理方式:
    • Bash、PowerShell返回0则30000个字符上限,超出部分成 开头预览(前2000个字符)和文件路径,如果返回非0失败结果,则10K字符以内直接返回,超出时给首尾摘录,并不附文件路径。
    • Grep是20000个字符, Glob、WebFetch、WebSearch、Edit、Write、Agent、普通 MCP 工具等是 100,000。Read没有限制,内部默认最多25000 token。 把工具结果输出到磁盘,替换成 开头预览(前2000个字符)和文件路径,保存文件有64MB限制。
  1. 微压缩Micro Compact:
  • 触发时机:请求LLM结束后,针对工具如Read、Bash、PowerShell、Grep、Glob、WebSearch、WebFetch、Edit、Write;非幂等的mcp tool、todo write则忽略。
  • 处理方式:两个策略
    1. 缓存时间已过期:距上一条助手消息60min,则认为缓存失效,(客户端判断,非探测的服务端判断),保留近5个工具调用,其他符合条件的工具则清除,替换成[Old tool result content cleared],不再额外存磁盘(服务端时间策略启动)。
    2. 缓存时间未过期:内部选择算法返回了待删除的旧结果,客户端请求上下文没有减少,只是API 层加多一个用户消息,但类型标记 cache_reference指定删除哪个工具调用,会返回 cache_deleted_input_tokens 计算实际。作用是:能减少上下文的同时避免输入缓存失效(服务端实现黑盒,也没有一定成功)(服务端可配置启动)。
  1. 局部历史折叠:默认关闭,实验性功能。
  • 触发时机:有效窗口90%开始执行,95%阻塞并替换。
  • 处理方式:摘要后台异步,如下把某个区间的message,让LLM进行总结,然后替代。(黑盒,较多细节不确定)
保存的历史:[m1][m2][m3][m4][m5]
模型看到的:[m1][摘要:m2~m4][m5]
  1. LLM压缩:
  • 触发时机:下一个模型请求或下一次会话,当 上下文超过 模型上下文窗口 - min(最大输出,20K) - 13K时开始执行。200K = 167K / 180K ≈ 92.8%,源码注释写的就是 ~93% of effective,这是 92% 的出处。
  • 处理方式:
    1. 如果有局部历史折叠:使用已有的局部历史折叠,保留 至少约5条user 或 助手消息,包括其中工具;改成已有会话记忆摘要+保留的近期消息,缩短仍太差回退到LLM摘要。
    2. LLM摘要:要求保留任务、约束、关键决策、代码和文件信息、问题、未完成工作、下一步等;压缩后,最多恢复五个近期文件,超过 5K tokens 的文件只补路径引用。
  • 取舍: 把恢复入口和运行状态纳入机制,减少“摘要还记得任务,但不知道怎么继续”的问题;不过恢复内容会占用新窗口,文件上限、技能裁剪和路径规则的加载条件都意味着恢复有边界。文件重读得到的是当前内容,不一定是压缩前的旧版本。

参考文档

  1. OpenAI:Conversation state——上下文预算
  2. OpenAI:Prompt caching——前缀复用与缓存条件
  3. Claude Code:How Claude Code works——加载、子任务与自动压缩
  4. MCP:Tools 规范——工具元数据与参数 schema
  5. Anthropic:Prompt caching——缓存寿命与配置
  6. OpenCode V2:Config——工具输出预算
  7. OpenCode V2:Compaction——摘要、保留量与超限恢复
  8. Codex:Configuration Reference——输出与自动压缩配置
  9. Codex:compact.rs——固定提交的客户端摘要实现
  10. OpenAI:Compact conversation——公开压缩接口
  11. Anthropic:Context editing——服务端内容清除
  12. Oracle:GC Ergonomics——暂停时间、吞吐量与内存占用的取舍
  13. Oracle:Factors Affecting GC Performance——堆大小与年轻代大小
  14. Oracle:G1 Garbage Collector——分层回收与 Full GC
  15. Anthropic Applied AI 团队,2025-09-29:Effective context engineering for AI agents
    重点阅读 Context retrieval and agentic search、Compaction 和 Structured note-taking。支持:上下文是有限资源;可以通过按需检索、压缩和外部笔记管理信息;过度压缩可能遗漏后续需要的关键细节。
  16. Nelson F. Liu 等,2023:Lost in the Middle: How Language Models Use Long Contexts
    研究多文档问答与键值检索任务,展示信息位置对长上下文利用效果的影响。支持“能容纳信息不等于能有效利用信息”;其结果有特定模型和实验范围,不应直接推断所有当前模型具有相同程度的问题。
  17. Oracle,Java SE 21 GC 调优指南:The Parallel Collector
    重点阅读 Options to Specify Parallel Collector Behaviors 和 Priority of Parallel Collector Goals。明确列出最大停顿时间、吞吐量和堆占用三个目标,并说明它们之间的影响;其中具体的目标优先级属于 Parallel Collector,不代表所有 GC 实现。