核心问题:Agent长对话中上下文窗口token耗尽 ;暴力截断是最差方案,会丢失任务目标、进度、技术决策,造成模型失忆。
Hermes 采用渐进式压缩策略,对用户透明:裁剪工具输出 → LLM结构化摘要 → 严格保护头尾上下文。
一、压缩器初始化与阈值设计
关键初始化参数(ContextCompressor)
| 参数 | 默认值 | 说明 |
|---|---|---|
| threshold_percent | 0.50 | 用到50%窗口就触发压缩,预留空间给摘要API;不选0.85/0.9,避免临近打满时摘要超时直接溢出 |
| protect_first_n | 3 | 头部固定保护消息条数 |
| protect_last_n | 20 | 尾部消息保底条数 |
| summary_target_ratio | 0.20 | 尾部token预算 = 阈值tokens × 0.2 |
| max_summary_tokens | 窗口5%,上限12000 | 摘要最大token上限 |
以 Claude‑200K窗口举例:
- context_length=200K
- threshold_tokens=100K(50%触发压缩)
- tail_token_budget=20K(尾部token预算)
- max_summary_tokens=10K
设计思想:半满即压,从容不迫,不要等到窗口快爆才处理。
二、头尾保护策略:头尾不动,中间开刀
消息切分为三部分:HEAD保护区 | MIDDLE可压缩区 | TAIL保护区
-
头部HEAD保护(固定3条)
一般对应:system提示词、第一条用户消息、第一条assistant回复。
包含人格设定、工具列表、原始任务描述,绝对不压缩。
-
尾部TAIL保护(v2核心升级:Token预算制 + 保底条数双重保护)
- v1老版本:仅固定保留20条消息;缺陷:消息token波动巨大,短消息浪费窗口、长消息直接压不动。
- v2逻辑:从后往前累加token,最多消耗
tail_token_budget(20K);同时保底至少保留20条消息。 - 两种场景对比:
- 最近20条很短:预算没用完,会多保留更多历史消息。
- 最近消息tool输出巨大:最多消耗20K token,但保底仍保留20条。
- 边界对齐:不会把tool_call/tool_result消息对半切开。
核心:数量是表象,token容量才是本质。
三、中间段两阶段压缩(成本由低到高)
Phase1:零成本裁剪工具输出 _prune_old_tool_results
- 逻辑:在尾部保护边界之前,tool角色消息,内容>200字符,替换为占位符
[Old tool output cleared to save context space];短tool result保留。 - 不调用LLM,纯字符串替换,速度快;大文件读取等巨型tool result可以大幅削减token。
Phase2:LLM结构化摘要(裁剪后仍超阈值时执行)
- 摘要预算:
max(最小2000token, min(被压缩内容×20%, max_summary_tokens)) - 摘要使用独立
auxiliary client,不占用主模型额度,可以配置便宜小模型做摘要。 - 增量迭代更新摘要:不是每次从零生成;第二次压缩时,基于上一轮摘要,合并新增对话,更新项目状态,避免矛盾片段拼接。
📝七段式结构化摘要模板(关键)
避免自由文本摘要丢失关键信息;强制分类填充,降低信息遗漏。
## Goal 用户任务目标
## Constraints & Preferences 用户约束、偏好
## Progress
### Done 已完成工作(文件、命令、结果)
### In Progress 正在进行
### Blocked 阻塞卡点
## Key Decisions 技术决策与理由
## Relevant Files 读写修改过的文件
## Next Steps 下一步计划
## Critical Context 关键参数、报错、配置
四、工具调用配对完整性修复(很重要)
OpenAI/Anthropic API强制约束:tool_call_id必须和tool返回消息一一对应。压缩容易产生孤儿消息。
_sanitize_tool_pairs做两件修复:
- 有tool结果、没有对应的tool_call:删除这条tool result。
- 有tool_call、没有tool结果:插入占位stub结果。
配套边界对齐函数:
_align_boundary_forward:起始边界跳过连续tool消息_align_boundary_backward:结束边界保证assistant+tool组完整,不会切断工具调用上下文。
五、Session分裂:parent_session_id链式压缩
每次触发压缩时做会话分裂:
- 将旧session标记结束,end_reason=
compression; - 创建全新session,
parent_session_id指向旧会话id,形成会话链:A→B→C...当前活跃为最新会话。
收益:
- 历史可追溯:工具可以回溯读取被压缩丢弃的早期对话;
- 标题自动继承编号,用户感知是连续对话;
- token消耗按会话分段记录,支持链上聚合统计成本。
额外收尾:快照todo list、重建system prompt、清空文件读取缓存。
六、两套压缩器区分:在线context_compressor vs 离线trajectory_compressor
| 维度 | context_compressor.py(在线Agent对话) | trajectory_compressor.py(RL训练离线脚本) |
|---|---|---|
| 运行时机 | 对话运行时,在线 | 训练前批处理离线 |
| 输入格式 | OpenAI messages | ShareGPT对话格式 |
| token计数 | 字符粗估 | huggingface tokenizer精确计数 |
| 保护策略 | 头3条 + 尾部20Ktoken+保底20条 | 保护首条system/human/gpt/tool,最后4轮 |
| 并发 | 单次同步 | asyncio高并发50 |
理念:在线求快,离线求准。RL训练轨迹压缩追求严格控制单条样本token长度,保障batch训练效率。
七、横向对比其他方案
1)对比 Claude Code三层压缩
- Claude Code:微压缩→SM工具摘要→Full Compact;阈值80%/90%/95%晚触发;每次摘要从零生成。
- Hermes:两层(裁剪tool+全量结构化摘要);50%早触发;增量迭代摘要;支持session链式分裂。
- Hermes短板:缺少SM中间层,保护区内超长tool result要等到全量压缩才处理。
Hermes优势:早压缩、每次压缩内容少,摘要质量更高。
2)对比 OpenClaw compaction
- Hermes:LLM结构化摘要,信息留存高;有LLM调用延迟;适合长复杂编码任务。
- OpenClaw:固定窗口直接裁剪;零成本零延迟;丢弃被裁掉信息;适合短对话高吞吐场景。
没有绝对好坏,按业务场景选型。
✅完整压缩全流程
- Agent循环检测token≥压缩阈值,触发;
- Step0:预压缩,关键信息写入memory;
- Step1:
_prune_old_tool_results裁剪超长tool输出; - Step2:计算HEAD、MIDDLE、TAIL压缩边界;
- Step3:生成/增量更新七段式结构化摘要;
- Step4:组装消息:HEAD + 摘要 + TAIL;执行
_sanitize_tool_pairs修复tool配对; - Step5:Session会话分裂,继承标题,重建状态;
容错:摘要调用失败有冷却,降级直接丢弃中间消息,不炸整个对话。
参考