让 Agent 重构一个模块,一开始只需要知道目标和约束。随着任务推进,它会不断读取代码、运行测试、查看日志。对话越来越长,任务还没完成,上下文却已经接近上限。
这时,该留下什么?旧日志可以删,但里面可能藏着排查问题的线索;历史可以压成摘要,但摘要是否记得接口不能改、哪些测试已经通过、下一步应该做什么?
上下文管理要解决的,就是怎样在有限窗口里保留足够的任务信息,同时控制执行成本和时延。按需检索、压缩和外部笔记,是处理这类长任务的几种重要手段。
本文沿着两条主线展开:先减少不必要的信息进入,再对已经积累的历史进行分层清理与压缩。 在此基础上,结合 OpenCode、Codex 和 Claude Code,讨论压缩如何影响输入缓存、怎样保留任务接力所需的状态,以及摘要请求自身超限时如何恢复。
Token 是模型处理信息的计量单位,不等于字数。上下文窗口限制单次请求能够容纳的信息量,需要同时考虑输入、输出,以及部分模型的推理 token。
减少不必要信息进入上下文
上下文是有限的,如此珍贵,那要选择必要信息进入上下文:
- 预加载:提供任务必须知道的信息
- SystemPrompt:设置人格、回复风格、工具使用、skill描述和路径。
- 项目指令文件
AGENTS.md:一些agent会把完整加载到system prompt中的指令标签,如opencode;还有一些可能会作为user prompt默认发送,如codex。 - 初始化输入:把目标,约束,事实,验收结果要说清楚。例如,重构模块时是否保留旧接口、需要通过哪些测试,哪些模块不需要改动或只改哪些模块,应在开始时明确。
- 懒加载:先加索引,需要时再读取。
- Skill:几乎所有产品都支持Skill渐进式披露,只暴露名字、描述、文件路径的必要信息;使用时再读取内容、参考文件或脚本,按需加载。
- MCP工具:MCP Tool的参数schema不保留,只暴露Name和描述,需要再通过 Tool 查找;支持工具搜索的产品,如字节deerflow,可以先提供索引,需要时再加载具体定义,不同产品不同,不是 MCP 协议要求省略 schema。
- 项目指令文件
AGENTS.md:指令文件保留必要说明和资料入口,详细架构、接口与排障记录放在参考文件中,并说明什么时候需要读取。 - 加载方式:ls查看目录,glob找到具体文件、ripgrep搜索关键字,rag进行向量搜索;
懒加载能减少初始输入,但需要清楚的入口,否则模型可能不知道还有哪些资料可用。
- 外挂磁盘:保留入口,需要时取回原文。
- Plan模式、SSD驱动开发:进度、状态可追踪,可复用的中间产物写成文件,后续可随时按需查看,另外SSD产物还可复利,后续都可以懒加载查看当初决策。
- 长期记忆:形成文件并每次对话结束时维护事实,需要时获取。
- 原始证据: 保存日志、测试输出和阶段产物,便于重新核查;上下文里保留当前需要的结论与完整路径即可。
- 不加载:
- multi agent:Multica的Team形式,每个Agent负责不同事项,交接报告来隔离上下文;通过启动Subagent等方式隔离,如让其编码等工作,主 Agent 再接收结论,未解决问题和产物路径。
- 任务已完成重新启动一个窗口再接着;任务可接力,输出交接文档,让另外一个agent获取必要输入即可。
多级上下文压缩方案
在有限的上下文中,要保证任务运行的质量(足够决策的上下文)情况下,降低端到端成本和完成时延,有4个要素关注:
- 上下文占用:保留的信息占多少token。
- 上下文质量:在压缩、筛选、转存后,关键事实、决策依据等准确完整。
- 执行成本:完成任务所耗费的token。
- 执行时延:发起任务到任务完成耗时。
为了让上下文占用减少:
- 轻量级压缩:
- 操作:裁剪,转存磁盘等
- 优点:执行操作的成本和耗时几乎可忽略;上下文较为缓解但没根治。
- 缺点:要考虑对输入缓存的失效进而导致成本进一步增大,最好可以批量一次性完成,避免频繁改变前缀;转存也可能带来检索的步骤,直接裁剪可能带来上下文部分缺失;
- 重量级彻底改写:
- 操作:上下文压缩
- 优点:上下文占用大幅度减低。
- 缺点:操作耗时和成本较大,且上下文质量无法保证无损。
每个模型厂商,如Deepseek、Openai等,都会有输入缓存cache entity,但有过期时间如5min(不一定),输入前缀相同就可能会缓存命中,但因压缩上下文可能会导致部分前缀变化,进而导致部分前缀缓存失效,修改位置之前的稳定前缀可能仍可复用 。缓存命中的话,缓存输入会很便宜,避免重复计算已有前缀。
上下文压缩对输入缓存的影响例子:设已有请求为:稳定指令 P + 旧历史 H + 近期内容 R;如果改成 P + 摘要 S + R,P 仍有复用机会,而 S + R 通常不能直接沿用原来 H + R 的完整前缀。
如何减少上下文压缩对输入缓存的影响:
- 窗口已经放不下,缓存再便宜也不能继续携带原历史数据;
- 窗口尚且充足时,则应衡量后续轮数、历史增长速度和清理收益。只清理掉很少内容,又反复修改前缀,可能得不偿失;把信息压的太少,迫使每轮都要重新读取文件,同样也会很贵。
扩展:多级上下文压缩,类似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 时触发;
保留一定大小窗口,是因为本身压缩请求也会占用窗口。
- 处理方式:
- 保留不超15K的消息(包括助手消息,工具消息,user消息上下文)不压缩,如果超8K就放弃那条消息。
- 其他转换成模型可读文本,如用户消息变成
[User]: ...,工具调用和结果变成[Assistant tool call]、[Tool result],且工具结果保证不超过2000个字符,超过则截取,拼接成 2000个字符+[truncated]。 - 把 “较旧历史文本 + 要求模型为另一个 coding agent 生成交接摘要 + 固定模板(目标,约束和重要信息(如事实、假设)、已完成/进行中工作,阻塞项,下一步 和 相关文件)“让另外一个模型压缩。
- 压缩后塞入一个user消息:摘要 + 近期历史的序列化记录。
- 如果压缩失败,拒绝为超过上下文大小,则接着重试。
扩展:压缩后模型接着对话不一定任务正确延续:
| 状态 | 应保留的内容 | 丢失后的典型问题 |
|---|---|---|
| 目标与约束 | 最终交付物、验收条件、不能破坏的行为、用户修订 | 做错任务或恢复已废弃方案 |
| 工作状态 | 完成、进行中、未开始、阻塞分别是什么 | 重复做已完成工作,遗漏未完成部分 |
| 决策依据 | 选择了什么、为什么、替代方案为什么不适用 | 再次尝试已经排除的方向 |
| 原始证据 | 文件、commit、日志路径、时间、定位片段 | 只记得“通过”,无法确认测了什么 |
| 在途操作 | 后台进程/任务标识、外部动作是否已完成 | 重复启动、重复提交、误报完成 |
| 下一步 | 立即可执行的动作与必要输入 | 泛泛“继续工作”,没有推进依据 |
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
- 第一层:工具结果限制
- 触发条件:对工具命令执行结果,默认上限10K(可配置)。
- 做法:保留工具前半段 和 后半段(各一半),中间用
…N tokens truncated…代替,没有完整文件。 - 取舍: 适合同时保留启动信息与结果尾部,不增加摘要请求;如果错误恰好在中间,则可能丢掉关键证据。重要日志应显式保存,并针对关键词读取。
- 第二层:LLM压缩
- 触发条件:超过模型上下文窗口的90%(可配,不同模型可能还不同,简单认为90%)
- 处理方式:在会话前 和 会话中(工具、LLM响应后)
- 本地压缩:
- 把所有历史消息写入摘要中,按当前进展和已做的关键决策、重要上下文、约束和用户偏好、接下来的工作、继续工作需要的数据、示例和引用生成。
- 从后往前保留最近20K user消息(工具、AI消息忽略),直到超20K截断,再拼接摘要信息,当成最新的用户消息。
- 远端压缩: 取决于功能开关是否开着。
- 如果发现 摘要请求+历史记录 超模型上下文,会继续一次紧急压缩,会把最久的历史项清除,每替换一次就重新检查,包括工具输出,工具搜索结果等,会替换成“ 输出超出可用上下文,已截断”。
- 取舍: 摘要可读、容易检查,另外保留用户要求以降低遗漏概率;但助手已做的技术判断和工具证据依赖摘要,旧用户指令也可能被预算截掉。重复压缩还可能积累语义偏差。
- 本地压缩:
ClaudeCode
可以参考,不一定正确,因为ClaudeCode并没有公布很多细节,只是从网上泄露代码,各种博客中而来,非官方。
- 第一层:工具大小限制
- 触发时机:每次工具执行完。
- 处理方式:
- Bash、PowerShell返回0则30000个字符上限,超出部分成 开头预览(前2000个字符)和文件路径,如果返回非0失败结果,则10K字符以内直接返回,超出时给首尾摘录,并不附文件路径。
- Grep是20000个字符, Glob、WebFetch、WebSearch、Edit、Write、Agent、普通 MCP 工具等是 100,000。Read没有限制,内部默认最多25000 token。 把工具结果输出到磁盘,替换成 开头预览(前2000个字符)和文件路径,保存文件有64MB限制。
- 微压缩Micro Compact:
- 触发时机:请求LLM结束后,针对工具如Read、Bash、PowerShell、Grep、Glob、WebSearch、WebFetch、Edit、Write;非幂等的mcp tool、todo write则忽略。
- 处理方式:两个策略
- 缓存时间已过期:距上一条助手消息60min,则认为缓存失效,(客户端判断,非探测的服务端判断),保留近5个工具调用,其他符合条件的工具则清除,替换成
[Old tool result content cleared],不再额外存磁盘(服务端时间策略启动)。 - 缓存时间未过期:内部选择算法返回了待删除的旧结果,客户端请求上下文没有减少,只是API 层加多一个用户消息,但类型标记
cache_reference指定删除哪个工具调用,会返回cache_deleted_input_tokens计算实际。作用是:能减少上下文的同时避免输入缓存失效(服务端实现黑盒,也没有一定成功)(服务端可配置启动)。
- 缓存时间已过期:距上一条助手消息60min,则认为缓存失效,(客户端判断,非探测的服务端判断),保留近5个工具调用,其他符合条件的工具则清除,替换成
- 局部历史折叠:默认关闭,实验性功能。
- 触发时机:有效窗口90%开始执行,95%阻塞并替换。
- 处理方式:摘要后台异步,如下把某个区间的message,让LLM进行总结,然后替代。(黑盒,较多细节不确定)
保存的历史:[m1][m2][m3][m4][m5]
模型看到的:[m1][摘要:m2~m4][m5]
- LLM压缩:
- 触发时机:下一个模型请求或下一次会话,当 上下文超过
模型上下文窗口 - min(最大输出,20K) - 13K时开始执行。200K = 167K / 180K ≈ 92.8%,源码注释写的就是~93% of effective,这是 92% 的出处。 - 处理方式:
- 如果有局部历史折叠:使用已有的局部历史折叠,保留 至少约5条user 或 助手消息,包括其中工具;改成已有会话记忆摘要+保留的近期消息,缩短仍太差回退到LLM摘要。
- LLM摘要:要求保留任务、约束、关键决策、代码和文件信息、问题、未完成工作、下一步等;压缩后,最多恢复五个近期文件,超过 5K tokens 的文件只补路径引用。
- 取舍: 把恢复入口和运行状态纳入机制,减少“摘要还记得任务,但不知道怎么继续”的问题;不过恢复内容会占用新窗口,文件上限、技能裁剪和路径规则的加载条件都意味着恢复有边界。文件重读得到的是当前内容,不一定是压缩前的旧版本。
参考文档
- OpenAI:Conversation state——上下文预算
- OpenAI:Prompt caching——前缀复用与缓存条件
- Claude Code:How Claude Code works——加载、子任务与自动压缩
- MCP:Tools 规范——工具元数据与参数 schema
- Anthropic:Prompt caching——缓存寿命与配置
- OpenCode V2:Config——工具输出预算
- OpenCode V2:Compaction——摘要、保留量与超限恢复
- Codex:Configuration Reference——输出与自动压缩配置
- Codex:compact.rs——固定提交的客户端摘要实现
- OpenAI:Compact conversation——公开压缩接口
- Anthropic:Context editing——服务端内容清除
- Oracle:GC Ergonomics——暂停时间、吞吐量与内存占用的取舍
- Oracle:Factors Affecting GC Performance——堆大小与年轻代大小
- Oracle:G1 Garbage Collector——分层回收与 Full GC
- Anthropic Applied AI 团队,2025-09-29:Effective context engineering for AI agents
重点阅读 Context retrieval and agentic search、Compaction 和 Structured note-taking。支持:上下文是有限资源;可以通过按需检索、压缩和外部笔记管理信息;过度压缩可能遗漏后续需要的关键细节。 - Nelson F. Liu 等,2023:Lost in the Middle: How Language Models Use Long Contexts
研究多文档问答与键值检索任务,展示信息位置对长上下文利用效果的影响。支持“能容纳信息不等于能有效利用信息”;其结果有特定模型和实验范围,不应直接推断所有当前模型具有相同程度的问题。 - Oracle,Java SE 21 GC 调优指南:The Parallel Collector
重点阅读 Options to Specify Parallel Collector Behaviors 和 Priority of Parallel Collector Goals。明确列出最大停顿时间、吞吐量和堆占用三个目标,并说明它们之间的影响;其中具体的目标优先级属于 Parallel Collector,不代表所有 GC 实现。