Agent 的内存管理:Claude Code 与 Codex 如何决定"忘掉什么"

深度拆解 Claude Code / Codex 的上下文淘汰策略:分层压缩管道、compaction 幸存规则、KV cache 淘汰的同构问题,以及 arXiv 论文给出的反直觉证据——简单丢弃常常不输昂贵的摘要。

Claude Code 跑在 200K token 的窗口上,但逆向分析显示它大约用到 167K 就开始”扔东西”了;Codex CLI 的阈值几乎一模一样。两个互为对手的编码智能体,在”什么时候忘、忘掉什么”这件事上收敛到了同一个数字——这不是巧合,而是同一个根本约束逼出来的同一类答案。

上一篇(Claude Code 和 Codex 还在用 RAG 吗?)讲的是 agent 怎么把信息装进上下文;这一篇讲反面:窗口装满之后,agent 怎么决定把什么扔出去。这个问题有个更熟悉的名字——内存管理

主线一句话:Agent 的上下文淘汰,本质是把操作系统的内存层级搬进了对话历史:所有策略都是三个动词(丢弃、压缩、外置)的组合,外加一张”什么必须活下来”的根集表;而最新的学术证据正在提醒行业——最便宜的那个动词,常常不输最贵的那个。

1. 根本约束:窗口不是装不下,是装多了会变笨

先回答一个自然的反问:为什么不直接把窗口做大? Claude 系列已经有 1M token 的窗口变体了,把 200K 换成 1M,问题不就消失了?

不会消失,因为限制上下文使用的从来不只是容量,还有注意力质量。两条证据:

“Lost in the Middle”(arXiv:2307.03172)系统测量了模型在长上下文中取信息的能力,发现了著名的 U 型曲线:关键信息放在开头或结尾时表现最好,放在中间时显著下滑——即使是专为长上下文设计的模型也一样。窗口越长,“中间”越大,被吞掉的信息越多。

Anthropic 的工程博客《Effective context engineering for AI agents》把这个现象命名为 context rot(上下文腐烂),并给出了一个值得记住的心智模型:模型有一个注意力预算(attention budget),每多一个 token 都在消耗它,收益边际递减。他们的设计准则是一句话:找到”最小的高信号 token 集合”,使期望结果的概率最大化。

所以问题的正确形式不是”怎么装下更多”,而是”怎么让留在窗口里的每个 token 都值得留”。这就是淘汰策略存在的理由——它不是容量不够时的无奈之举,而是维持智能水位的主动手段。

2. 统一框架:三个动词,一张根集表

把所有工具的所有机制摆在一起看,你会发现它们都是三个原子操作的组合:

动词操作信息损失成本
丢弃(Evict)直接删掉旧消息/旧工具输出完全丢失零(不调用模型)
压缩(Compress)用 LLM 把历史摘要成短文本有损,细节没了一次昂贵的 LLM 调用
外置(Externalize)把内容写到窗口外(磁盘/记忆文件),留个指针零损失,但要重新读取才可见便宜,代价延迟到未来

以及一个所有策略都绕不开的问题:什么必须活下来? 系统提示词、用户的核心意图、未完成的任务、刚改过的文件——这些是不能被淘汰的”根”。如果你写过垃圾回收器(GC),会发现这个结构完全同构:三个动词是回收策略,根集(root set)决定什么对象永远不被回收。

这个类比不是我发明的。MemGPT(arXiv:2310.08560)在 2023 年就把它推到了极致:论文标题就叫 Towards LLMs as Operating Systems,提出”虚拟上下文管理”——像 OS 用分页在快慢存储间搬数据、给进程制造”内存无限大”的错觉一样,在有限窗口和外部存储之间分层调度信息,给 agent 制造”上下文无限长”的错觉。今天 Claude Code 和 Codex 的所有机制,都可以读作这篇论文思想的工程化变体。

下面逐个解剖。

3. Claude Code:一条”能不调模型就不调”的分层管道

Claude Code 的上下文管理不是单一机制,而是一条按成本递增排列的管道。根据社区对其源码的逆向分析(Decode Claude另一份深挖),每轮迭代开始时依次执行:

graph LR
    A[Snip<br/>丢弃整条消息] --> B[Microcompact<br/>掩码可重算的工具结果]
    B --> C[Context Collapse<br/>LLM 摘要选定片段]
    C --> D[Autocompact<br/>LLM 摘要全部历史]
    style A fill:#d4edda
    style B fill:#d4edda
    style C fill:#fff3cd
    style D fill:#f8d7da

绿色的两级不调用模型,黄色调一次(局部),红色调一次(全局)。整条管道的设计哲学一句话:能用零成本的丢弃解决,绝不动用昂贵的摘要。

3.1 Microcompact:赌”可重算”的丢弃

管道里最有味道的一级是 microcompact。它盯着一类特殊消息:工具输出——Read 读进来的文件、Bash 跑出来的日志、Grep 的搜索结果。这类内容有个共同特征:随时可以重新算出来。文件还在磁盘上,命令还能再跑一遍,把 5000 token 的原文留在窗口里纯属浪费。

于是 Claude Code 把较老的工具输出掩码掉,只留一个”内容已存盘,路径在此”的引用;最近若干条工具结果保持完整(所谓 hot tail),保证模型手头的推理不断档。适用的工具覆盖 ReadBashGrepGlobWebSearchWebFetchEditWrite(逆向分析)。

这一步没有任何模型调用,却常常回收掉窗口里最大的一块——在编码任务里,工具输出通常占上下文的大头,而且是信息密度最低的大头

3.2 Autocompact:最后手段与触发数字

当便宜手段都不够用,才轮到全量摘要(autocompact):让模型读完整段历史,产出一份结构化总结,替换掉原文。触发阈值不是拍脑袋的,对源码的逆向分析给出了公式:

effectiveWindow=contextWindowmax(maxOutputTokens, 20000)\text{effectiveWindow} = \text{contextWindow} - \max(\text{maxOutputTokens},\ 20000) threshold=effectiveWindow13000\text{threshold} = \text{effectiveWindow} - 13000

200K 窗口代入,大约 167K 触发。两个减项各有含义:20K 是给”生成摘要本身”留的输出空间——如果窗口塞到只剩 500 token 才想起摘要,模型连总结都写不下;13K 是提前量,因为检查发生在每次 API 调用之前,要为下一轮的增量留余地。还有一个防御性常量:连续 3 次 autocompact 失败就停手(MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3),防止死循环烧 API。

需要说明:这些数字来自社区逆向,不同版本会漂移(另一份对比研究记录的触发点是约 95% 容量)。数字会变,结构不变:预留输出空间 + 提前量,在窗口八九成满时动手。

3.3 根集表:什么在 compaction 后活下来

摘要发生后,窗口里剩什么?官方文档给了一张精确的幸存者名单——这就是前面说的”根集”:

机制compaction 之后
系统提示词、output style不动(本来就不在消息历史里)
项目根 CLAUDE.md、无路径限定的规则从磁盘重新注入
自动记忆(auto memory)从磁盘重新注入
paths: 的路径规则丢失,直到再次读到匹配文件
子目录里的嵌套 CLAUDE.md丢失,直到再次读到该目录文件
已调用的 skill 正文重新注入,单个上限 5000 token、总量 25000,最旧的先丢
对话本身被一份结构化摘要替换

注意”从磁盘重新注入”这四个字——这是外置动词的用法:CLAUDE.md 和记忆文件的真身在窗口外的文件系统里,窗口里的只是投影,所以摘要摧毁不了它们。想让一条信息在 agent 的记忆里永生,不要把它说在对话里,要把它写在磁盘上。 这是使用任何带 compaction 的 agent 时最实用的一条推论。

除了被动等待,用户还有三个主动手段:/compact 加聚焦指令(如 /compact focus on the auth bug)控制摘要保留什么;/clear 在切换任务时直接清空;把大规模检索派给子智能体——它在独立窗口里烧掉几万 token,只把几百 token 的结论带回来。子智能体本质上是淘汰策略的第四种形态:隔离,从一开始就不让垃圾进主窗口。

4. Codex CLI:同样的骨架,不同的取舍

OpenAI Codex CLI 面对同一个问题,给出了结构相同、细节分岔的答案(依据 Codex Knowledge Base 的架构分析openai/codex 源码):

触发公式几乎一字不差。 effective_window = context_window − min(max_output_tokens, 20000),再减 13000 得阈值——200K 窗口同样落在约 167K。两个没有互相抄源码的团队算出同一个数,说明这不是设计品味,是约束的解:输出预留 + 摘要空间 + 提前量,给定同样的窗口就得出同样的答案。

两条摘要路径,其中一条是黑盒。 用 OpenAI 官方模型时,Codex 调用服务端接口 POST /v1/responses/compact,返回一个 AES 加密的不透明压缩块——客户端看不到摘要内容,服务端解密后直接喂给下一轮模型。用第三方模型时走本地路径:抽取最近的用户消息(约 20K token 上限),用一段摘要提示词让模型总结。这个”不透明 blob”设计与 OpenAI Responses API 的 compaction 一脉相承:官方文档明说 compaction item “不供人类解读”。对比之下,Claude Code 的摘要是明文,用户可以审查甚至用 /compact 指令干预——透明可控 vs 黑盒省心,是两家在同一骨架上最大的哲学分岔。

摘要后的修复动作。 compaction 之后 Codex 会注入一段引导消息,并自动重读最近编辑过的至多 5 个文件(每个文件有 token 上限)——这是对”摘要丢失了文件细节”的主动补偿:与其祈祷摘要写全了,不如把最关键的状态(刚改过的代码)从磁盘上原样拉回来。又是外置动词:磁盘是比摘要更可靠的记忆。

兜底是纯丢弃。 如果摘要之后空间仍不够,Codex 退化为 head trimming——从最老的消息开始硬砍。配置上只能把触发阈值调低不能调高(超过窗口 90% 的设置会被静默忽略),且目前无法完全关闭自动 compaction

把两家放进一张表:

维度Claude CodeCodex CLI
便宜的前置手段Snip + microcompact(掩码工具输出)无独立层,依赖摘要
摘要触发~167K / 200K(公式推导)~167K / 200K(公式几乎相同)
摘要形式明文结构化总结,可审查可干预官方模型走加密黑盒,本地模型走明文
摘要后修复从磁盘重注入 CLAUDE.md/记忆/skill自动重读最近编辑的文件
兜底连续失败 3 次即停head trimming 硬砍
用户控制/compact 可加聚焦指令、/clear阈值只能调低,不能关闭

顺带一提生态里的另两种性格,坐标系就完整了:OpenCode 把”修剪”(prune)做成与 compaction 分离的独立机制——保护最近 40K token 的工具输出,只删更老的;而 Sourcegraph Amp 干脆拒绝自动化,只提供手动的 handoff/fork 工具,哲学是”对话就该短而聚焦”。从全自动黑盒(Codex)到全手动(Amp),中间是分层自动化(Claude Code)——没有谁对谁错,是对”用户该不该感知遗忘”这个问题的不同回答。

5. API 层:淘汰策略正在变成基础设施

上面讲的都是 CLI 工具内置的策略。2025 年之后,两家把这套能力下沉到了 API,变成任何 agent 开发者都能直接调用的原语——这个动向值得单独一节,因为它说明上下文淘汰已经从”应用技巧”变成了”平台基础设施”

Anthropic 的 context editing 提供了声明式的淘汰配置。clear_tool_uses_20250919 策略就是 API 化的 microcompact:超过 trigger 阈值后,自动清掉最老的工具结果,保留最近 keep 条,单次至少清出 clear_at_least token,exclude_tools 列表里的工具豁免。被清掉的内容替换为占位文本,而发起调用的 tool_use 块保留——模型知道”我调过这个工具、参数是什么”,只是结果不在了。

两个设计细节暴露了真实的工程约束。第一,清理发生在 prompt cache 查找之后——随意从历史中间删内容会打碎缓存前缀,让后续每次调用都全价重算;服务端在缓存命中后再清理,鱼与熊掌兼得。第二,文档建议 clear_at_least 不要低于 5000 token,原因同样是缓存:为几百 token 破坏一次缓存前缀,经济上是亏的。淘汰策略不能只看信息论,还得看账单。

配套的 memory tool(memory_20250818)补上外置动词:给模型一个 /memories 目录的文件操作接口,而且当 context editing 快要清理时,模型会收到系统警告,可以抢在遗忘前把重要信息写进记忆文件——这是 MemGPT”模型自主管理自己的内存分页”思想的第一个大厂官方实现。Anthropic 报告的内部评测数字:100 轮网页搜索任务上,单用 context editing 提升 29%,加上 memory tool 提升 39%,token 消耗降低 84%(注意是厂商自测,非独立复现)。

OpenAI 这边,Responses API 的 compactioncontext_management + compact_threshold 一个参数开启服务端自动摘要。哲学分岔延续到了 API 层:Anthropic 给你一堆旋钮(trigger/keep/exclude),OpenAI 给你一个开关和一个黑盒。

6. 学术界的冷水:最贵的动词,常常不是最好的

到这里,一个自然的推断是:摘要比丢弃”聪明”,所以效果应该更好,丢弃只是穷人的替代品。学术证据恰恰打脸了这个直觉。

《The Complexity Trap》(arXiv:2508.21433,NeurIPS 2025 DL4Code workshop,JetBrains Research)做了一个行业早该做的对照实验:在 SWE-bench Verified 上,用同一个 SWE-agent 框架,比较”LLM 摘要”和”简单观察掩码”(把老的工具输出替换成占位符,只保留最近 M 条)两种策略,跨五种模型配置。结果:

  • 观察掩码把成本砍半,解题率不输、有时还略胜 LLM 摘要。 最好的配置(Qwen3-Coder 480B)下,掩码策略每实例 0.61,对比原始agent0.61,对比原始 agent 的 1.29(降 52.7%),也低于 LLM 摘要的 $0.64;
  • 摘要不仅要花一次模型调用,还可能引入错误——摘要写偏了,agent 就带着被污染的记忆继续干活;
  • 混合策略(掩码为主、偶尔摘要)能再挤出 7-11% 的成本。

论文标题就是结论:迷信复杂机制是个陷阱。回头看 Claude Code 的管道设计——“能不调模型就不调模型”、把 microcompact 排在 autocompact 前面——工程直觉和论文结论指向了同一个地方。 这也解释了为什么观察掩码有效的前提值得记住:工具输出可重算(文件还在磁盘上),所以丢弃的信息损失是虚的;而对话里用户说过的意图不可重算,该摘要还得摘要。动词的选择跟着信息的可恢复性走。

那摘要这个动词还有没有救?ACON(arXiv:2510.00615,ICML 2026)给出的方向是:别用一段固定的摘要提示词打天下,而是把”压缩指南”本身当成可优化对象——分析 agent 在哪些任务上因为压缩丢了关键信息而失败,用自然语言迭代修订压缩指南(全程不动模型权重)。结果:峰值 token 降 26-54%,而且小模型在长任务上的表现最多提升 46%——压缩不仅省钱,还通过去掉干扰信息让模型变准了。这与 context rot 的机制严丝合缝:淘汰做得好,本身就是性能优化。

站远一点看,这个领域已经成了正式学科。《A Survey of Context Engineering for Large Language Models》系统分析了 1400+ 篇论文,把上下文工程拆成检索/生成、处理、管理三大组件——本文讲的一切属于第三格;《Context Engineering 2.0》则把这件事的历史拉长到 1990 年代的人机交互研究,论证”让机器理解我们的处境”是个演化了三十年的老问题,LLM 只是它最新的载体。

7. 往下看一层:KV cache 淘汰,同一道题的另一次出现

如果你读过我之前写的 KV cache 内存那篇,现在可以把两层接起来了——因为同一道淘汰题,在栈的更深一层早就被解过一遍

推理引擎里,KV cache 随序列长度线性膨胀,显存装不下,就得决定扔掉哪些 token 的 KV。两篇代表作的答案:

StreamingLLM(arXiv:2309.17453,ICLR 2024)发现了 attention sink 现象:模型把不成比例的注意力压在最初几个 token 上,跟语义无关;只要保住开头几个 token 的 KV + 一个近期滑动窗口,4M token 的流式生成都不崩。H2O(arXiv:2306.14048,NeurIPS 2023)则发现注意力分数高度集中在一小撮 heavy hitter token 上,动态保留”重要的 + 最近的”,显存大降而精度几乎不掉。

现在把两层的策略并排放:

KV cache 层(token 级)Agent 层(消息级)
永远保住开头attention sink(前几个 token)系统提示词、CLAUDE.md 重注入
永远保住最近滑动窗口microcompact 的 hot tail、OpenCode 保护最近 40K
按重要性保中间H2O 的 heavy hitters摘要提取的”关键决策/未完任务”

同构不是巧合。两层面对的是同一个数学结构:一个只增不减的序列、一个固定预算、一个”未来会用到什么”的预测问题——和 CPU cache 的 LRU、操作系统的页面置换是同一族。所有解都收敛到”头 + 近期 + 重要性采样”这个形状,因为这就是该问题在信息不完备下的通解形状。区别只在操作粒度(token vs 消息)和重要性信号(注意力分数 vs LLM 判断/启发式规则)。

这层同构给了我们一个预判:KV cache 淘汰这几年从静态规则(StreamingLLM)进化到动态学习(各种可学习淘汰策略),agent 层的上下文淘汰大概率会走同一条路——今天的固定阈值和手写规则,会被 ACON 那样”从失败中学习淘汰什么”的自适应机制逐步替代。

8. 带走的判断模型:设计淘汰策略的四个问题

如果你在构建自己的 agent,不要从”抄哪家的 compaction”开始,从这四个问题开始:

  1. 这条信息可重算吗? 可以(文件、命令输出、搜索结果)→ 用丢弃/掩码,零成本,信息损失是虚的。不可以(用户意图、中间决策)→ 才考虑摘要。这是《The Complexity Trap》的核心教训。
  2. 这条信息需要跨越多少轮? 几轮内 → 留在窗口;整个会话 → 摘要携带;跨会话 → 必须外置到磁盘/记忆文件,窗口里的一切都是易失的。
  3. 你的淘汰动作对 prompt cache 友好吗? 从历史中间随意删改会打碎缓存前缀;要么像 Anthropic 那样在缓存命中后清理,要么攒够量再动手(≥5000 token)。先算账,再淘汰。
  4. 用户需要感知遗忘吗? 需要审查与干预 → 明文摘要 + 手动命令(Claude Code/Amp 路线);追求无感 → 服务端黑盒(Codex 路线)。这是产品问题,不是技术问题。

回到开头那个 167K。两家竞争对手在触发阈值上算出同一个数字,在管道结构上收敛到同样的”便宜手段优先”,这不是谁抄谁——是窗口有限 + 注意力衰减 + 缓存经济学这三条约束共同决定的解空间本来就窄。三十年前操作系统研究者在页面置换上、五年前推理引擎工程师在 KV cache 上、今天 agent 框架作者在对话历史上,解的都是这道题。上下文淘汰没有银弹,但有通解的形状:根集不动,近期全留,可重算的丢弃,不可重算的压缩,要长活的外置。 下次你看到任何一个新 agent 框架的”创新记忆机制”,拿这个形状去套——十有八九,它只是给这五个短语换了名字。


参考来源

工程实践(官方文档与源码分析)

arXiv 论文