有句话在 AI 社区里流传很广:"信息越多,系统就越聪明。"
这是个谎言。
在 Agent 系统里,上下文不是图书馆,模型也不是藏书管理员。上下文首先是一笔昂贵、易膨胀、还会自我污染的预算。往里面塞的东西越多,系统就越难管,越容易出错,越容易因为超长而崩溃。
这不是危言耸听。这是 Claude Code 整个上下文治理系统的出发点。
CLAUDE.md:把规则分层
想象你是一个新来的工程师,加入了一个 Agent 工作流。你需要知道什么?
- 公司全局的 AI 协作规范(managed memory)
- 你个人的长期工作习惯(user memory)
- 这个项目特有的约定(project memory)
- 这一轮临时的上下文(local memory)
Claude Code 把 CLAUDE.md 这个文件分成了四个层级,按优先级和目录距离加载:
- <code>/etc/claude-code/CLAUDE.md</code> —— 系统级,最低优先级
- <code>~/.claude/CLAUDE.md</code> —— 用户级
- 项目根目录的 <code>CLAUDE.md</code>、<code>.claude/CLAUDE.md</code>、<code>.claude/rules/*.md</code> —— 项目级
- <code>CLAUDE.local.md</code> —— 本地临时级,最高优先级
越靠近当前工作目录,优先级越高。这是什么意思?意思是:"长期协作规则"和"本轮对话指令"被彻底分开了。
你不用每次对话都重复说"你是一个产品经理"、"我们的代码风格是这样的"。这些东西写进项目的 CLAUDE.md,系统会自动加载。这一轮有临时的特殊要求?写进 CLAUDE.local.md,只对这一轮有效。
关键在于:这是在上下文预算用完之前,就把规则做了结构化分层。不是笼统地往上下文里塞,而是明确地说"你需要这些规则,但不需要那些"。
MEMORY.md:索引,不是日记
有一个很关键的设计细节。Claude Code 有一个叫 <code>MEMORY.md</code> 的文件,它被定义为 ENTRYPOINT_NAME。但这个"入口"不是正文,而是索引。
具体怎么用呢?规则是这样的:
- 把具体的 memory 写进独立的文件(比如 <code>learned_patterns.md</code>、<code>project_context.md</code>)
- 然后在 MEMORY.md 里加一个一行的指针(比如 <code>[项目背景](./project_context.md)</code>)
为什么要这么麻烦?因为 MEMORY.md 有硬约束:
- 最多 200 行(MAX_ENTRYPOINT_LINES = 200)
- 最多 25,000 字节(MAX_ENTRYPOINT_BYTES = 25,000)
超过了就强制截断,并追加一个警告信息。这不是 bug,这是特性。系统在说:
"我知道你想存很多东西。但我不会让你把 MEMORY.md 写成一个无限增长的垃圾场。超过界限了,我就断你。这是为了保护你的上下文预算。"
Session Memory:这轮你究竟做了什么
除了长期记忆,Claude Code 还有一个叫 Session Memory 的机制。它专门用来记录"这一轮之前我们到底做过什么"。
有一个专门的模板,包括这些栏目:
- Current State —— 现在做到哪了
- Task Specification —— 任务原文
- Files and Functions —— 涉及了哪些文件和函数
- Workflow —— 工作流程梗概
- Errors & Corrections —— 踩过哪些坑、怎么改的
- Codebase and System Documentation —— 涉及的系统文档
- Learnings —— 这轮学到了什么
- Key Results —— 关键产出
- Worklog —— 详细工作日志
但这个 Session Memory 也有硬预算:
- 每个栏目最多 2,000 tokens(MAX_SECTION_LENGTH = 2000)
- 整个 Session Memory 最多 12,000 tokens(MAX_TOTAL_SESSION_MEMORY_TOKENS = 12000)
这意味着什么?意味着系统给这一轮的"工作记录"专款专用地预留了一笔预算,但绝不会让它无限膨胀。
关键观点:系统不是在"尽量多记",而是在"很聪明地记最有用的部分"。
自动压缩:提前为失败留出余量
最有意思的部分来了。Claude Code 的 <code>autoCompact</code> 机制有一个很精妙的设计。
系统先算出可用的上下文窗口大小,然后从里面扣掉三笔预算:
- Summary Output Budget —— 预留 20,000 tokens 给压缩摘要输出(MAX_OUTPUT_TOKENS_FOR_SUMMARY = 20,000)
- Autocompact Buffer —— 预留 13,000 tokens 作为压缩触发的缓冲(AUTOCOMPACT_BUFFER_TOKENS = 13,000)
- 最后剩下的 —— 才是真正可用的上下文大小
看这个设计逻辑:系统不是把所有可用空间都用上,而是提前为失败和恢复留出了足够的余量。为什么?
因为当上下文即将溢出时,系统需要触发自动压缩。而压缩本身是一个需要额外输出的操作——模型需要生成"摘要",这个摘要本身也需要上下文空间。如果没有提前留出余量,就会陷入"为了压缩而超长、超长了再压缩"的死循环。
这就叫为系统的恢复机制提前预算。
压缩的真正逻辑
当 <code>compactConversation()</code> 真正执行的时候,它做的不是简单的"删除旧消息"。而是一个复杂的语义重组:
- 把原有的上下文拆开、分析、摘要
- 清空旧的 readFileState(文件读取状态)
- 重新生成 post-compact file attachments(压缩后的文件附件)
- 把 plan attachment 补回来(任务计划)
- 把 invoked skills attachment 补回来(已调用的技能)
- 把 deferred tools / agent listing / MCP instructions 的 delta attachment 重新补回来(变化部分的增量说明)
- 执行 session start hooks 和 post-compact hooks(压缩前后的钩子)
- 写 compact boundary message(压缩边界标记)
这一整套流程的目的是什么?保留工作语义。即使上下文被压缩掉了,系统仍然能够理解"我们在做什么"、"做到哪了"、"下一步该怎么做"。
代码里有句话很有味道:"per-skill truncation beats dropping"。意思是,即使要裁剪,也要优先保住每个 skill 开头最关键的那段指令,而不是直接扔掉整个 skill。这反映了系统的核心价值观:牺牲细节,保留骨架。
上下文治理的终极目标
所有这些机制——分层的规则、有限的索引、有预算的 memory、有缓冲的压缩——都在指向同一个目标:
让系统能继续工作。
不是"让系统知道得更多",而是"让系统在有限的上下文里继续完成任务"。
当你往上下文里塞了再多的资料,如果系统没法用这些资料来继续工作,那就是浪费。相反,如果系统能在 12,000 tokens 的 Session Memory 里,清楚地记录下"我们的目标是什么、现在做到哪了、遇到什么问题、下一步是什么",那这 12,000 tokens 就值得。
核心观点
把越多内容塞进上下文,系统不会越来越聪明。
它只会越来越难管。Token 会花得更快,压缩会触发得更频繁,工作记忆会被噪音淹没。
真正聪明的上下文管理是什么?是承认资源有限,然后聪明地用。
分层规则,让不必要的东西别进来。有限的索引,防止记忆无限膨胀。为失败预留预算,确保系统能从崩溃边缘恢复。压缩时优先保留工作语义,而不是什么都舍不得。
这些约束听起来像是在限制系统的聪慧。其实反过来——这些约束是在释放系统的可靠性。一个在资源约束下仍能坚韧工作的系统,比一个在无限资源下偶尔崩溃的系统,聪明得多。
<!-- 改动说明:核心选题为上下文治理的预算制度理念。重点突出 CLAUDE.md 的四层分级设计、MEMORY.md 的索引机制而非日记、Session Memory 的有限预算、autoCompact 的提前预留逻辑,以及 compactConversation 保留工作语义的设计。用"预算"而非"容量"来重新定义上下文的性质,强调治理的目标是支持继续工作而非存储更多信息。最后反直觉地指出:资源约束反而是释放可靠性的关键。 -->