当你使用AI Agent进行长时间编程会话时,一些不可避免的事情会发生:对话不断增长,工具结果不断累积,上下文接近模型的限制。Agent开始”遗忘”东西——系统提示、早期指令、关键决策。在最坏的情况下,API返回context_length_exceeded错误,会话就此终止。
解决方案称为上下文压缩(context compaction)。这不是要扩展模型的记忆,而是要学会精确地遗忘:保留关键内容,丢弃噪声,并在这一过程中花费尽可能少的令牌。
本文分析了四个AI Agent系统如何实现这一关键功能。Hermes Agent、Codex CLI、Claude Code和OpenCode采用了截然不同的方法——从Hermes的双层安全保障到Claude Code的三阶段渐进压缩——但它们都趋同于一个共同模式。
什么是上下文压缩,为什么它如此重要?
在与AI Agent对话的每一轮中,都会有令牌被添加到历史记录中。系统提示(包含工具定义、指令、持久记忆)、用户消息、助手回复、工具调用和工具结果——所有内容都在不断累积。
在一次典型的调试会话中,工具结果占总令牌数的约80%。一次grep搜索可能返回2,000个令牌;读取一个300行的文件会增加3,500个令牌;执行带有堆栈跟踪的测试又会增加3,000个令牌。这些数据在调试过程中至关重要,但一旦bug被解决,它们就成了负担。
没有上下文压缩,Agent有三种选择,但都不理想:
- 失败——因上下文超出限制而报错,丢失所有进度
- 忽略——早期指令因工具结果撑爆上下文而被丢弃
- 花费巨大——每一轮输入令牌的成本与累积的历史成正比
上下文压缩通过用结构化摘要替换中间部分的历史记录(旧的工具结果、已解决的问题、已完成的调试),同时保护头部(系统提示 + 首次交互)和尾部(近期轮次),解决了这个问题。这是一种投资:你花费几分钱用一个便宜的LLM生成摘要,然后在未来每一轮中节省这笔成本。
Hermes Agent:双层系统
Hermes Agent由Nous Research开发,拥有四个系统中文档最完善、配置最灵活的压缩系统。其架构使用两个独立层,分别在流程的不同节点运行。
第1层:网关会话卫生(85%上下文)
该层位于gateway/run.py中,在Agent处理传入消息之前执行。它是用于控制会话在轮次之间不受控增长的安全网——例如,当Agent不活跃时,Telegram会话累积的消息。
- 阈值:固定在模型上下文的85%
- 令牌来源:如果API可用则使用其报告的实付令牌数;否则回退到基于字符的估算
- 触发条件:仅在
len(history) >= 4且启用压缩时 - 目的:捕获那些逃脱了Agent压缩器的会话
网关的阈值故意设置得比Agent高。如果设为50%(与Agent相同),会导致长网关会话的每一轮都发生过早压缩。
第2层:Agent ContextCompressor(50%上下文,可配置)
这是主要层,位于agent/context_compressor.py中。它在Agent的工具循环内运行,可以访问API提供的精确令牌计数。
典型配置:
compression:
threshold: 0.50 # 触发压缩的上下文比例
target_ratio: 0.20 # 作为尾部保留的阈值比例
protect_last_n: 20 # 保护的最小最近消息数
对于一个200K上下文的模型,压缩在达到约100K令牌时触发。受保护的尾部获得约20K令牌,摘要的预算最多为10K令牌。
4阶段算法
关键在于ContextCompressor.compress()如何将45条消息和约95K令牌转换为25条消息和约45K令牌。
阶段1:修剪旧工具结果(无需LLM,成本约零)
超出受保护尾部且长度>200字符的工具结果会被替换为占位符:[Old tool output cleared to save context space]。这在不调用任何模型的情况下释放了大量令牌。
Agent记得它执行过某个命令,但不记得确切输出。如果需要详细信息,它可以重新执行该命令。
阶段2:确定边界
[0..2] ← 保护前3条消息(系统提示 + 首次交互)
[3..N] ← 中间轮次 → 将被摘要
[N..end] ← 受保护尾部(基于令牌预算或 protect_last_n)
尾部保护从末尾向前遍历,累积令牌直到预算用完。如果预算保护的消息数少于protect_last_n,则使用固定最小值。
边界经过对齐,不会拆分tool_call/tool_result组。_align_boundary_backward()方法向后遍历连续的工具结果,以找到父级的assistant消息。
阶段3:通过辅助LLM生成结构化摘要
中间部分被发送到辅助模型(可配置,通常比主模型更便宜),使用包含8个部分的模板:
## Goal
[用户试图实现什么]
## Constraints & Preferences
[偏好、代码风格、重要决策]
## Progress
### Done
[已完成的工作——具体路径、命令、结果]
### In Progress
[正在进行的工作]
### Blocked
[遇到的阻塞或问题]
## Key Decisions
[重要的技术决策及其原因]
## Relevant Files
[已读取、修改或创建的文件]
## Next Steps
[接下来应该做什么]
## Critical Context
[特定值、错误消息、配置详情]
摘要预算与压缩内容成正比:content_tokens × 0.20,最低2,000,最高min(context_length × 0.05, 12,000)。
阶段4:组装压缩后的消息
构建新的消息列表:
- 头部消息(首次压缩时在系统提示中添加一条注释:“部分之前的轮次已被压缩…”)
- 摘要消息(选择适当的角色以避免连续角色违规)
- 尾部(未修改的最近消息)
_sanitize_tool_pairs()清理孤立的配对:引用已删除调用的工具结果被移除,结果已被删除的工具调用获得一个占位桩。
迭代重新压缩
在后续压缩中,之前的摘要会与更新指令一起传递给LLM,而不是从头开始摘要。项目从”进行中”变为”已完成”,添加新的进展,删除过时的信息。
提示缓存(Anthropic)
Hermes还为Anthropic模型集成了提示缓存,位于agent/prompt_caching.py中。system_and_3策略放置了4个缓存断点:
- 断点1:系统提示(始终稳定)
- 断点2-4:最后3条非系统消息(滚动窗口)
这在不牺牲信息的情况下将多轮对话的输入成本降低了约75%。
Codex CLI:交接摘要
OpenAI Codex CLI采用了一种更简单但同样有效的方法:单层压缩,将所有内容替换为一个”交接”摘要。
双路径设计
Codex提供两条压缩路径:
- 本地路径(
compact.rs):客户端调用LLM生成摘要。适用于任何模型提供商。 - 远程路径(
compact_remote.rs):调用OpenAI内部端点responses/compact。OpenAI服务器处理压缩,可能使用专门的模型和内部缓存。
两种情况下,压缩都需要一次LLM调用。区别在于执行位置:本地路径在客户端编排一切;远程路径将”生成摘要”步骤外包给OpenAI。
压缩提示
You are performing a CONTEXT CHECKPOINT COMPRESSION. Create a handoff
summary for another LLM that will resume the task.
Include:
- Current progress and key decisions made
- Important context, constraints, or user preferences
- What remains to be done (clear next steps)
- Any critical data, examples, or references needed to continue
Be concise, structured, and focused on helping the next LLM seamlessly
continue the work.
关键词是handoff(交接)——这不是会议纪要,而是为了让下一个模型能够无缝继续工作的简报。
用户消息保留
Codex的一个独特特性:逐字保留用户消息。它只压缩助手回复和工具结果。这意味着Agent始终可以看到用户最初说了什么,尽管当用户消息很长时会降低压缩效率。
回退:头部裁剪
如果压缩后仍然空间不足,Codex会诉诸头部裁剪——从最旧的消息开始裁剪。这是破坏性的,被视为最后的手段。
Claude Code:三层精度
Anthropic的Claude Code拥有四个系统中最为复杂的架构,包含三层渐进式压缩,从最便宜到最昂贵逐步推进。
Claude Code并非开源。此分析基于社区逆向工程和公开资料。
第1层:工具结果修剪(LLM成本为零)
该层在每个请求之前自动运行。无需LLM调用——纯粹是一个本地规则引擎。
逻辑:
- 保护最新的工具调用结果
- 旧工具结果 → 替换为
[Old tool result content cleared]
Agent记得它执行过搜索,但不记得结果。如果需要详细信息,它可以重新执行命令。这是选择性遗忘,而非完全失忆。
第2层:缓存友好策略
这是Claude Code的独特优势。Anthropic支持提示缓存:如果你API消息的前缀与前一次请求匹配,服务器会重用之前的计算结果,从而大幅降低成本和延迟。
在清理消息时,Claude Code有意避免修改序列的前半部分。它倾向于从末尾裁剪,保持开头完全相同以最大化缓存命中率。
权衡:清理效率较低,但缓存命中率最高。对于长时间任务(如重构整个模块),这意味着显著的节省——你只需为末尾的新内容付费。
第3层:结构化LLM摘要(最后手段,9个部分)
当前两层不够用时,触发完整摘要。自动压缩的阈值为:有效窗口 - 13,000 令牌。
在调用LLM之前,系统会尝试会话记忆压缩——使用会话记忆中已有的结构化信息。只有当这不可能时,才退回到传统的LLM摘要,生成9个固定部分:
- 用户的原始意图
- 核心技术概念
- 相关文件和代码
- 遇到的错误及修复方式
- 解决问题的逻辑链条
- 所有用户消息的摘要
- 待办任务
- 当前正在进行的工作
- 建议的后续步骤
提示要求原文引用关键短语,而非释义。这可以防止”上下文漂移”——模型在复述时微妙地偏离原始含义。
压缩后处理
压缩后,Claude Code执行一系列状态重建步骤:
- 注入开头语:“This session continues from a previous conversation…”
- 自动重新读取最多5个最近编辑的文件(预算50K令牌,每个文件5K)
- 重新声明工具定义和技能定义
- CLAUDE.md中的规格说明(系统提示)保持不变
还有一个被动回退:如果API返回prompt_too_long,则启动响应式压缩并重试。连续3次失败后暂停,以避免无限循环。
OpenCode:带非破坏性隐藏的分步治理
OpenCode(已归档,原sst/opencode)提供了最为平衡的策略,在session/compaction.ts中使用Effect-TS实现。
步骤1:Prune(隐藏,而非删除)
第一步并非删除——而是标记。OpenCode给旧消息添加时间戳compacted = Date.now(),使其在后续请求中不可见。数据仍然保留在数据库中。
规则:
- 仅当能释放>20K令牌时才执行
- 始终保留最近40K令牌作为安全缓冲
skill类型的工具输出从不修剪- 保护用户最近2轮消息的完整内容
这是一个富有远见的设计决策:数据并未真正丢失。它为未来的审计、回滚或历史功能留下了空间。
步骤2:5部分LLM摘要
如果在prune之后仍然存在问题,OpenCode会使用一个专用的隐藏Agent(不打断用户)生成包含5个固定部分的摘要:
- 已完成的工作
- 当前正在进行的工作
- 已修改的文件
- 后续步骤
- 重要的技术决策
自动重放最后一条消息
OpenCode最智能的特性:压缩后,系统自动重新发送用户的最后一条消息。用户甚至不会注意到压缩发生过——他们的最后一条消息被重新处理,Agent回复,仿佛一切如常。
OpenCode还会跟随用户的语言:如果对话是西班牙语,摘要也用西班牙语生成。
对比表
| 维度 | Hermes Agent | Codex CLI | Claude Code | OpenCode |
|---|---|---|---|---|
| 层级 | 2(网关 + Agent) | 1(摘要) | 3(修剪 + 缓存 + 摘要) | 2(隐藏 + 摘要) |
| LLM调用 | 仅阶段3 | 始终需要 | 仅第3层 | 仅步骤2 |
| 阈值 | 50%(可配置) | ~180-244K令牌 | ~95% | 溢出 + 余量 |
| 用户消息 | 被摘要 | 逐字保留 | 被摘要 | 被摘要 + 重放 |
| 工具结果 | 占位符 | 物理删除 | 占位符 | 时间戳隐藏 |
| 缓存 | Anthropic Prompt Cache | 无特殊 | 深度集成 | 减少读取 |
| 压缩后处理 | 迭代重新压缩 | 被动等待 | 重新读取文件 | 自动重放最后消息 |
| 是否可逆 | 是 | 是 | 是 | 否(时间戳) |
| 开源 | 是(MIT) | 是(Apache 2.0) | 否 | 是(已归档) |
共同模式
尽管存在差异,四个系统都趋同于一个共享模式:
- 先低成本修剪:在调用LLM之前,所有系统都有一个机械清理阶段(工具结果→占位符/隐藏)。这在不花费一个令牌的情况下释放了50-80%的空间。
- 头部和尾部始终受保护:系统提示和最近的消息从不触动。被摘要的是中间部分,即已完成的工作所在区域。
- 结构化摘要:自由格式行不通。所有系统都使用固定部分(4到9个)的模板来引导摘要模型。
- 更便宜的辅助模型:没有人用主模型来做摘要。压缩被委托给一个辅助模型(通常便宜10-100倍)。
- 后处理:压缩后,所有系统都会做一些事情来防止Agent”迷失方向”——重新发送最后一条消息、重新读取文件、注入前缀。
值得吗?投资的数学
上下文压缩现在花费令牌,以后节省成本。这有意义吗?
对于DeepSeek V4 Flash这类模型,价格为$0.28/1M输出令牌:
- 一次压缩的成本:生成约2,000令牌的摘要 → 约$0.0006
- 后续每一轮的节省:压缩后的历史45K令牌 vs 95K令牌 → 每轮仅输入就节省$0.014
- 平衡点:约1轮后,压缩就已回本
- 20轮的回报:比没有压缩便宜20倍
对于Claude Opus 4.8这类高端模型,价格为$25/1M输出令牌:
- 压缩成本:约$0.05(更便宜的辅助模型)
- 每轮节省:约$1.25
- 20轮的回报:便宜25倍
上下文压缩不是奢侈品——它是AI Agent能够在长时间会话中工作而不因API成本破产的经济必要条件。
对Agent开发的启示
如果你正在构建自己的Agent或选择一个现有的Agent,以下是正确的问题:
- 你需要多少层压缩? 单层(如Codex)对于短会话已经足够。双层(如Hermes)提供了防止溢出的安全保障。三层(如Claude Code)在超长会话中优化了成本。
- 可逆性重要吗? OpenCode是唯一不破坏数据的系统。如果你需要审计或回滚,它的时间戳方法更优。
- 你使用Anthropic吗? Claude Code或Hermes的Prompt Cache集成可以在多轮会话中将你的成本降低约75%。
- 辅助模型重要吗? 非常重要。如果你的辅助模型上下文窗口小于主模型,压缩会静默失败(Hermes将其记录为”退化的最常见原因”)。
最终,这四个系统证明,最佳的上下文管理不是扩展模型的记忆,而是学会精确地遗忘。
主要来源: Hermes Agent Documentation — Context Compression and Caching | 源代码:Hermes Agent, Codex CLI, OpenCode (已归档)