ARTICLE / AI Agent · Context Engineering
Context 太长怎么办?从 Compression 到 Sub-Agent
Agent 跑几十轮后 Context 必然膨胀,而 Sliding Window 会截掉「不要改数据库 Schema」这类关键约束。本文给出 Delete → Externalize → Structure → Compress → Isolate 的分层策略:先删噪声再摘要、超大 Tool Result 外置存储、用 Status Bar 显式表达状态,以及为什么 Isolation 优于 Compression。
- 发布
- 阅读
- 约 5 分钟
- 作者
- Nicolas Leigh
前几篇我们分别讨论了:
Agent = Model + Harness
Context
= Stable Prefix
+ Dynamic Trajectory
+ Current State
也讨论了 Prompt、KV Cache 和 Skills。
但只要 Agent 持续运行,一个问题几乎一定会出现:Context 会越来越长。
因为每一轮都可能新增:
- User Message
- Assistant Message
- Tool Call
- Tool Result
- Error Log
- Search Result
- File Content
运行几十轮以后,Context 很容易增长到几万甚至更多 Token。
这时真正的问题已经不是"Context Window 装不装得下?",而是这么多信息,模型还能不能找到真正重要的内容?
这就是长任务 Agent 必须面对的 Context Management 问题。
Context 不是越长越好
直觉上,我们可能认为 Context 越多 = 模型知道得越多 = Agent 越聪明。但实际并不一定如此。
假设 Agent 的 Context 中包含:用户最初的目标、重要架构决策、20 次工具调用、10 个完整文件、大量搜索结果、测试日志、错误堆栈、已经失效的尝试。
真正影响下一步决策的信息,也许只有其中很小一部分。随着无关内容越来越多,有效信息 / 总 Token 的比例会持续下降。
所以 Context Engineering 真正追求的不是 Maximum Context,而是 Maximum Information Density——尽可能提高每个 Token 对当前任务的价值。
为什么不能直接使用 Sliding Window?
最简单的办法似乎是这样:
messages = messages[-20:]
只保留最近 20 条消息。这就是 Sliding Window。
它实现简单,但对于 Agent 来说风险很大。因为旧消息并不一定不重要。
例如第 2 轮用户说"不要修改数据库 Schema",第 30 轮 Agent 可能仍然需要遵守这个约束。如果 Sliding Window 把它截掉了,模型可能认为"当前最简单的解决办法是修改数据库结构"。
类似地,被删除的还可能包括:之前失败过的方法、关键架构决策、用户限制、已经完成的步骤、重要工具结果。
于是 Agent 可能开始:忘记 → 重复搜索 → 重复尝试 → 再次失败。
所以 Context 太长时,不能简单按照时间删除信息。应该按照信息价值决定保留什么。
先删除垃圾,再考虑摘要
很多 Agent 一遇到 Context 太长,就开始调用模型"请总结一下对话"。但摘要并不应该是第一步。
因为 Context 中往往存在大量根本没有必要保留的信息,比如:网页导航栏、重复搜索结果、HTML 模板、超长日志、已经失效的 Tool Output、重复报错。
对于这些内容,最好的压缩方式不是 Summary,而是 Delete。
可以把 Context Management 理解成三步:
第一步:删除噪声
第二步:缩短低价值内容
第三步:总结重要历史
一个很实用的原则是:不要给垃圾做摘要。
Tool Result 不一定要永久保留全文
Tool Result 是 Context 膨胀的重要来源。
例如 Agent 执行 read_file("large.log") 返回了 30,000 Tokens。如果之后每一轮都把这 30,000 Token 原样发送给模型,成本很快会失控。
更好的方式是:
完整结果 → 保存到外部文件 / 状态存储
Context → 只保留必要片段和引用
例如:
日志已保存:
/tmp/payment-error.log
关键发现:
- 14:32 开始出现大量 timeout
- Redis connection pool exhausted
- 错误集中在 payment worker
如果之后需要详细内容,再重新读取。
这其实是一种很重要的思想:Context 不应该同时承担长期存储的职责。完整数据可以存到外部,Context 只保留当前决策真正需要的信息。
Agent Status Bar:不要让模型自己回忆状态
还有一类信息虽然重要,却没有必要通过几十轮历史重新推导。例如:当前目录、已经完成了什么、还有什么没做、测试是否通过、工具调用了多少次、最近一次错误是什么。
这些信息可以直接由 Harness 计算出来,然后放进一个结构化状态区:
<agent_status>
Goal:
修复支付接口并发问题
Completed:
[x] 定位问题
[x] 修改代码
Remaining:
[ ] 执行测试
[ ] 检查回归风险
Current directory:
/workspace/payment
Recent error:
None
</agent_status>
这就是所谓的 Agent Status Bar。
它的价值在于:模型不需要重新阅读几十轮历史再自己推断"我现在做到哪一步了",而是直接得到 Current State。
需要注意的是:状态栏最好由 Harness 计算,而不是让模型自己统计。因为模型可能漏记,也可能错误总结。
什么时候需要 Context Compression?
当简单删除噪声仍然不够时,就需要真正的 Context Compression。
例如原始历史是:
读取 A 文件
读取 B 文件
搜索函数 X
发现实现位于 C
修改 C
测试失败
分析日志
发现测试环境缺少 Redis
重新启动 Redis
再次测试成功
后续模型也许并不需要知道所有具体过程。可以压缩成:
已完成:
- 定位问题到 C 文件中的函数 X
- 已完成代码修改
- 第一次测试因测试环境 Redis 未启动而失败
- 启动 Redis 后测试通过
关键是:Compression 不应该只是机械缩短文字,而应该保留未来决策需要的信息。
所以一个好的 Compression 应重点保留:用户目标、明确约束、关键事实、重要决策、失败原因、当前进度、未完成事项。
最好的压缩,是根本不让信息进入 Context
Context Compression 仍然有一个问题:信息已经进入主 Agent 的 Context 了。
有没有更好的方法?有。就是 Context Isolation。
例如主 Agent 想知道"这个代码库里,支付回调到底在哪里实现?"
如果主 Agent 自己搜索:grep → read_file → grep → read_file → read_file... 可能产生几十个文件、几万 Token 的中间信息。
但实际上主 Agent 最后真正需要的可能只有:
支付回调位于:
internal/payment/callback.go
核心函数:
HandlePaymentCallback()
于是可以让一个 Search Agent 去完成探索:
Main Agent
↓
Search Agent
↓
大量搜索与文件读取
↓
返回结论
中间产生的几万 Token 根本不进入 Main Agent 的 Context。
这比"50K Token → 压缩成 2K"更进一步,因为它直接变成"50K Token → 从未进入主 Context"。
所以有一个非常重要的原则:Isolation > Compression。
Sub-Agent 不只是为了"多智能体"
很多人一听 Sub-Agent,首先想到的是"多个 AI 分工合作"。这当然是一种用途。
但从 Context Engineering 的角度看,Sub-Agent 还有一个非常重要的价值:隔离 Context。
例如:
Main Agent
├── Search Agent
├── Research Agent
├── Test Agent
└── Code Review Agent
每个 Sub-Agent 都可以拥有自己的 Context。Search Agent 可以阅读几十个文件,Research Agent 可以浏览大量网页,Test Agent 可以处理几万行测试日志。最后它们只把结论、关键证据、建议返回给 Main Agent。
于是主 Agent 的 Context 始终保持相对干净。
成熟 Agent 的 Context 管理应该是分层的
一个比较合理的策略可以是:
Level 1: 删除明显噪声
Level 2: 超大 Tool Result 外部存储,Context 只保留关键片段
Level 3: 生成 Agent Status Bar
Level 4: 压缩旧 Trajectory
Level 5: 把复杂探索任务交给 Sub-Agent
也就是说,不要等 Context 满了以后才突然"Summarize everything",而应该从 Agent 设计之初,就持续控制 Context 的增长。
结语
长任务 Agent 最终一定会遇到 Context 越来越长的问题。但真正的解决方案并不是简单扩大 Context Window。
更成熟的思路是:
Delete
↓
Externalize
↓
Structure
↓
Compress
↓
Isolate
- 删除没有价值的信息
- 把完整数据存到 Context 之外
- 用 Status Bar 显式表达当前状态
- 对旧轨迹进行语义压缩
- 让 Sub-Agent 隔离大量探索过程
最终目标始终只有一个:让主 Agent 在每一轮都看到完成当前决策真正需要的信息。
从这个角度看,Context Engineering 并不是某一个独立模块,它贯穿了 Prompt、Tools、Memory、Skills、State、Compression、Sub-Agent。
这也解释了为什么 Agent 的能力不仅取决于模型有多聪明,还取决于 Harness 能否持续为模型构造高质量 Context。
至此,我们就可以把整个系列重新总结成一句话:
Agent = Model + Harness
而优秀 Harness 最核心的能力之一,就是不断回答一个问题:
下一次调用模型时,它到底应该看到什么?