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
READS1次阅读

前几篇我们分别讨论了:

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 最核心的能力之一,就是不断回答一个问题:

下一次调用模型时,它到底应该看到什么?

参考资料