上下文满了,学会砍
「The agent can forget strategically and keep working forever.」策略性遗忘 = 工程能力。
为什么要 compact?
agent 跑久了,messages[] 会膨胀:每个 read_file 回几千 token、每个 bash 回几百、每轮对话还有模型的思考文字。跑 50 轮,context 能塞到 100K+。两个后果:
- 碰到模型上限:到了窗口大小就崩,或者每一次 API 调用价格线性上涨。
- 注意力稀释:现在手头的任务淹没在 30 轮前的无关 tool_result 里,模型开始走神。
s06 的思路:让 agent 主动遗忘不重要的内容,但保留关键状态。三层机制,从轻到重。
Layer 1 · micro_compact(每轮静默运行)
最便宜的一层。每次 LLM 调用前跑一遍,把超过 3 个的旧 tool_result 替换成占位符:
# 第 10 轮往前,大部分 tool_result 变成: { "type": "tool_result", "tool_use_id": "toolu_01A", "content": "[Previous: used bash]" # 从几千字符缩到几十 }
有个特例:read_file 的结果不压缩。为什么?因为 read 的输出是参考资料,压了模型就得重读一遍,反而更贵。
PRESERVE_RESULT_TOOLS = {"read_file"} # 永不压缩
看 micro_compact 按 turn 吃掉旧结果
下面按步骤模拟 10 轮交互,每一轮前让 micro_compact 跑一次。看 messages[] 里旧的 tool_result 变成 [Previous: ...],但最近 3 个保持原样。
Layer 2 · auto_compact(超过阈值触发)
即使 micro 一直跑,累到一定规模还是会爆。s06 设了个阈值(默认 50000 token):
- 估算 token 数
len(str(messages)) // 4(粗糙但够用)。 - 超过阈值 → 把完整 transcript 写盘到
.transcripts/transcript_TIMESTAMP.jsonl(留底)。 - 让 LLM 给整段对话写一个 summary。
- 把
messages整个替换成一条"[compressed] SUMMARY..."。
代价很明显——丢失了具体的工具输出、对话语气,只剩提纲。但 agent 能接着往下干,这是核心收益。
Layer 3 · 模型自己调 compact tool
auto_compact 是 harness 自动触发的,模型不知道。Layer 3 反过来:给模型一个 compact 工具,让它主动要求压缩——比如觉得前面的探索已经没用、要开新阶段。
模型调用:
tool_use("compact", focus="keep the API design decisions")
触发和 auto 一样,但能带一个 focus 参数告诉总结时重点保留什么。实战里非常实用——模型知道哪些是 "已结束的小任务",比 harness 的启发式更准。
哪层合适?判断题
下面几个场景,决定 micro / auto / manual 哪个触发更合理。