概述

在使用 Claude Code 时,我们最有直观感受的两个关于上下文的事情就是:

  • 项目的根目录下有一个 CLAUDE.md 文件
  • 我们和 Claude Code 对话多了之后,context 占用会越来越高

不知道你是不是,对于我来说,在使用 Claude Code 的很长一段时间里面都是这个感受。但是,Claude Code 是不是就只有这两种方式呢?这两种方式他的具体执行策略是怎么样的呢?刚好最近 Claude Code 被动开源,让我能够从源码级去了解一下这些内容。

另外,上下文管理在很多时候也叫记忆管理,所以后面我们会混淆这两者,不做具体的区分。

Claude Code 的记忆管理

Claude Code 的记忆管理可以分为两种:CLAUDE.md 和自动记忆,CLAUDE.md 是我们自己写的(可能大多数情况下都是通过 /init 命令生成的),然后自动记忆是 CLAUDE CODE 自己生成的。

CLAUDE.md

CLAUDE.md 一般是位于项目的根目录下,当然,你还可以放在其他位置,比如:

  • .claude/CLAUDE.md

还有一个特殊的配置叫做 CLAUDE.local.md,这个和 CLAUDE.md 的区别就是你不将他推送到远端 repo,只在本地使用。

这里就不对 CLAUDE.md 的内容做过多的介绍了,但是我们知道,每次在这个项目的目录启动一个会话的时候,CLAUDE.md 中的内容都会被放入上下文中传递给 CLAUDE 后台模型,如果你抓包看一下内容就会发现 CLAUDE.md 的内容被自动放入了请求中:

所以这也是为什么不建议在 CLAUDE.md 中写入一堆内容的原因。

CLAUDE.md 文件如何加载

Claude Code 通过从当前工作目录向上遍历目录树来读取 CLAUDE.md 文件,检查沿途的每个目录。这意味着如果你在 foo/bar/ 中运行 Claude Code,它会从 foo/bar/CLAUDE.md 和 foo/CLAUDE.md 加载指令。

Claude 还在当前工作目录下的子目录中发现 CLAUDE.md 文件。它们不是在启动时加载,而是在 Claude 读取这些子目录中的文件时包含。

自动记忆

除了我们预定义的内容之外,在每次会话时,CLAUDE CODE 也会自动记录我们的对话记录,并且会将会话的重要内容(CLAUDE CODE 认为必要的内容沉淀下来),然后保存起来。

自动记忆让 Claude 跨会话积累知识,无需你编写任何内容。Claude 在工作时为自己保存笔记:构建命令、调试见解、架构笔记、代码样式偏好和工作流习惯。Claude 不会每个会话都保存内容。它根据信息在未来对话中是否有用来决定什么值得记住。

默认情况下自动记忆就是打开的,当然你可以选择关闭它。

CLAUDE CODE 默认情况下会将记忆放在每个 project 的目录下:~/.claude/projects/<project>/memory/,在这个目录下我们可以看到这样的目录结构:

~/.claude/projects/<project>/memory/
├── MEMORY.md          # 简洁索引,加载到每个会话
├── debugging.md       # 关于调试模式的详细笔记
├── api-conventions.md # API 设计决策
└── ...                # Claude 创建的任何其他主题文件

很遗憾,我在我的本地没有找到 memory 文件(难道都不值得 CLAUDE CODE 记忆吗?)

MEMORY.md 的前 200 行或前 25KB(以先到者为准)在每次对话开始时加载。第 200 行之外的内容在会话开始时不加载。Claude 通过将详细笔记移到单独的主题文件中来保持 MEMORY.md 简洁。

此限制仅适用于 MEMORY.md。CLAUDE.md 文件无论长度如何都完整加载,尽管较短的文件产生更好的遵守度。

主题文件如 debugging.md 或 patterns.md 在启动时不加载。Claude 在需要信息时使用其标准文件工具按需读取它们。

Claude 在你的会话中读取和写入记忆文件。当你在 Claude Code 界面中看到”Writing memory”或”Recalled memory”时,Claude 正在主动更新或读取 ~/.claude/projects//memory/。

默认上下文

还有一个容易被忽视的内容就是我们在每个会话中,每次的对话内容都会被放入上下文中发送给 CLAUDE 模型,所以随着我们对话轮次的增加,我们的上下文会越来越长,而 CLAUDE CODE 有一个默认的上下文压缩机制,就是当上下文使用超过 85% 时,他会自动压缩上下文,自动压缩上下文是指它会重新构造上下文内容后,使用新的上下文再加上 CLAUDE.md 重启一个对话(虽然还是在同一个 session),但是请求的内容和压缩前已经有了非常大的变化了。

源码级解读

以下是我通过 CODEX 对 CLAUDE CODE 的源码进行解读后的结果,仅供参考。

首先是整体的模块调用:

从 Claude Code 的代码看,Claude Code 的“上下文管理 / 记忆系统”不是单体模块,而是分成 3 层,分别解决不同时间尺度的问题。

1. 会话内上下文层

核心是 QueryEngine,它持有一整个 conversation 的 messages、文件读取缓存、usage、已加载的 memory 路径等状态;每次 submitMessage() 都是在同一个会话状态上追加一轮,而不是重新构建上下文。QueryEngine.ts

这一层还会把“长期记忆机制”接进系统提示词和附件系统里: - 系统提示词里会注入 memory 的使用规则 loadMemoryPrompt()QueryEngine.ts - 文件类记忆以 nested_memory / relevant_memories attachment 的形式按需注入,而不是每轮把整个 memory 目录全塞进去。utils/attachments.ts utils/attachments.ts

2. 持久化记忆层 这是文件系统上的 memory dir,核心在 memdir/。它不是“数据库记忆”,而是“文件型记忆”: - 入口索引是 MEMORY.md - 具体记忆拆成独立 markdown 文件 - 记忆类型被严格限制为 user / feedback / project / reference - 明确禁止把代码结构、架构、git 历史这类“可从仓库重新推导”的东西存进去。memdir/memoryTypes.ts

loadMemoryPrompt() 会根据开关决定加载哪种模式: - 只有 auto memory - auto + team memory - 长生命周期 assistant 模式下改成 append-only daily log,再由 nightly 过程蒸馏回 MEMORY.mdmemdir/memdir.ts

检索不是全文都注入,而是两段式: 1. 先扫每个 memory 文件的 header/frontmatter 2. 再用一个 side query 挑最多 5 个“对当前 query 明显有帮助”的 memories
实现就在 findRelevantMemories()memdir/findRelevantMemories.ts

如果启用了 team memory,还会有单独的 team 子目录,并且对路径穿越、symlink escape 做了比较严的校验。memdir/teamMemPaths.ts

3. 上下文压缩层 这层处理“上下文窗口快满了怎么办”,有两套机制。

一套是传统 compact / auto-compact: - 到阈值后,把旧消息总结成 compact summary - 插入 compact boundary marker - 保留后续需要继续工作的少量附件/工具状态 - 会话恢复时再基于 transcript 恢复这些 compact/collapse 状态。services/compact/autoCompact.ts services/compact/compact.ts utils/sessionRestore.ts

另一套是 Session Memory: - 它不是用户长期记忆,而是“当前会话摘要笔记” - 在主 REPL 线程里按阈值后台触发 - 用 forked subagent 更新一个 markdown memory file - 后续 compaction 可以利用这份会话笔记,降低直接总结整段聊天的损耗。services/SessionMemory/sessionMemory.ts services/SessionMemory/sessionMemory.ts services/SessionMemory/sessionMemoryUtils.ts

可以把整体架构理解成:

用户输入
  -> QueryEngine 组织本轮上下文
    -> 系统提示词注入 memory 规则
    -> 按需注入相关 memory attachments
    -> 模型执行
    -> 会话变长后触发 Session Memory / Compact / Context Collapse
  -> transcript + compact boundary + memory files 持久化
  -> 下次继续会话时恢复