












上下文窗口是有限的,忘记是不可避免的。关键不是"如何记住更多",而是"什么值得保留"。
一个 session 跑了大半天。上下文里塞满了几十次编译报错、五轮调试尝试、三个被否的方案。Agent 开始忽略自己几分钟前做的决定——你明明说了"用方案 B",它又回到了方案 A。
这不是 agent 变笨了。这是上下文窗口满了。
当窗口接近上限时,LLM 的 attention 分布会变得稀疏。最早的消息几乎不可见,最新的消息占据了所有注意力。agent 看起来"记不住"了——不是因为它没有上下文,而是因为有用的上下文被无用的信息淹没了。
问题不是"能不能设计一个无限的上下文窗口"(技术上可以,RAG 和 1M token 窗口已经在做了),问题是:你希望 agent 在有限容量里记住什么? 主动选择遗忘的内容,和让窗口满了随机丢消息,是完全不同的两种情况。
1 万个 token 的上下文、10 万个 token 的上下文、甚至 100 万个 token 的上下文——不管多大,它都有边界。一个持续运行数小时甚至数天的 agent session 可以把任何上下文窗口塞满。100 万 token 听上去很多,但如果 agent 每 10 分钟做一次工具调用(每条几百 token),一天就会产生数万 token 的"操作日志",加上每次调用的返回值(读文件可能几千 token),一周就到百万了。
所以这不是一个"够不够大"的问题,这是一个"在有限的容量里放什么"的问题。
更核心的是:把上下文当作"无限"来设计,比把它当作"有限"更危险。当系统假设上下文永远装得下时,它不会设计遗忘策略。而遗忘策略缺席的后果不是"永远不遗忘"——而是被动遗忘:最早的消息被新消息推出窗口,没有任何优先级判断,没有任何保留依据。最古老的但最重要的信息(用户的第一条需求约束)和最古老的最不重要的信息(第一次查目录返回的 200 条文件列表)被同等对待——一起被推出去。
主动遗忘 vs. 被动覆盖——这是选择放弃一些东西,和让东西自己消失,之间的区别。后者不是工程,是听天由命。
既然遗忘不可避免,那就设计它。
CodeCoder 的压缩策略把上下文内容分为三个优先级:
最高价值——必须保留:
这些在任何压缩场景下都不能丢弃。它们是 agent 在当前时刻"正在做什么"和"不能做什么"的核心上下文。
中等价值——可以压缩但不可删除:
这些内容有价值,但不需要保留原始全文。CodeCoder 的 tier-1 压缩对这类内容做占位化处理——把 ToolResult 的冗长正文替换为摘要 + 文件路径,保留"做了什么"但丢弃"返回了什么细节"。
最低价值——可以直接丢弃:
CodeCoder 的 tier-1 压缩首先丢弃所有 Reasoning token。这不是随意的选择——Reasoning 通常占据上下文的 30-50%,而且它的信息密度最低。如果 Agent 需要在之后复现自己的推理过程,它应该在 Memory 中显式保存推理结论,而不是依赖 Reasoning 原文。
tier-2 压缩在 tier-1 之后仍超阈值时触发:对最早的对话段落做结构化摘要,摘要是迭代式合并的——每次只摘要增量部分,并累积文件追踪信息附在摘要末尾。
有些东西不该靠上下文窗口来保留。如果 AGENTS.md 和 CONTEXT.md 的内容需要通过上下文来保持"记忆",那么每次 session 结束它们就丢失了。
但 CodeCoder 的做法是:这些文件在每次 session 开始时重新读入 system prompt。 它们根本不依赖上下文窗口。这就是"文件系统即自我"原则的另一个体现——核心知识不塞进上下文,而是放在磁盘上。上下文窗口里的东西可以丢,磁盘上的文件不会丢。
Memory 工具就是为此设计的。它不是压缩时顺便保存的副产品——是 agent 主动决定"这个信息值得跨 session 记住"时写入的。agent 调用 memory write key=value,系统持久化到 memory/ 目录。
对比三种机制的遗忘特性:
| 存储位置 | 遗忘机制 | 恢复方式 |
|---|---|---|
| 上下文窗口 | 压缩/丢弃 | 不可恢复 |
| Memory 文件 | 永不自动删除 | agent 主动调用 memory read |
| 配置文件(AGENTS.md 等) | 永不自动删除 | session 启动时重读 |
上下文窗口里的东西是最容易丢的——所以只放"当前"需要的东西。Memory 是持久的——放"以后"也需要的东西。配置文件是最稳固的——放"永远"需要的东西。这些配置文件(AGENTS.md、CONTEXT.md)构成 agent 的"自我基因组",见篇 1「文件系统即自我」。
遗忘策略不是免费的——无论选择什么级别的压缩,都意味着信息丢失。
第一个代价是 tier-2 摘要的信息损失。结构摘要把最早的对话段落压缩为"目标 / 约束 / 进展 / 关键决策 / 下一步"五个字段——但这五个字段不可能完美还原原始对话中所有的细微决策。如果某个关键决策的前提被压缩掉了,agent 在新一轮对话中可能做出与原始意图矛盾的判断。CodeCoder 的迭代式合并策略试图通过累积文件跟踪(在摘要末尾附上 read/modified 文件路径)来缓解这个问题,但路径列表不能替代完整的上下文。
第二个代价是 压缩触发时的性能开销。tier-2 压缩需要调用一次 LLM 来生成结构摘要——这是有成本和延迟的。在交互式 session 中,用户可能正在输入下一句话,而 agent 正在后台压缩上下文,导致响应卡顿。CodeCoder 将 compaction 放在独立线程中运行,但受限于 JSON 文件在压缩期间的一致性问题(agent 可能在压缩的同时写新的消息),实现中使用了写时复制和锁机制——额外的复杂度。
第三个代价是 压缩使得事后审计变得更困难。压缩前的原始上下文在压缩后被替换为摘要,原始细节丢失。如果需要审计员在数周后复现"当时 agent 为什么做了这个决定",他看到的只有 Tier-2 摘要,而不是原始推理过程。CodeCoder 的 Session 文件是追加写入不删除的(压缩用新文件而非原地覆盖),解决了这个问题——但磁盘占用因此翻倍。
第四,Memory 的写入依赖于 agent 的主动性。如果 agent 不知道某个信息应该存到 Memory,或者忘记调用 memory write,这个信息就丢失了。依赖 agent 自发地"决定记住什么"是一种被动记忆策略,与主动的上下文保留完全不同。理论上可以通过定期提示 agent"你有需要记住的东西吗"来缓解,但增加了 prompt 成本和行为的不确定性。
压缩就是遗忘。承认它,设计它。
如果你运行一个自主 agent,三个建议:
Agent 不需要记住一切才能高效工作。它需要知道什么该忘、什么该留、什么该写到磁盘上。这和人的记忆策略惊人地相似——我们不是记忆好的动物,我们是擅长遗忘的动物。区别在于:人的遗忘是无意识的,agent 的遗忘需要设计。
最后一篇,我们回到起点——"我是 CodeCoder"这句话,到底意味着什么。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。