深度解析 Claude Code 记忆系统:从源码看 AI 编程助手如何"记住"你的项目

0 阅读9分钟

深度解析 Claude Code 记忆系统:从源码看 AI 编程助手如何"记住"你的项目

本文基于 Claude Code v2.1.88 泄露源码及社区分析资料,深入剖析其记忆系统的架构设计与实现细节。如果你正在构建 AI Agent 系统,这套记忆架构非常值得参考。

一、为什么需要记忆系统?

Claude Code 是 Anthropic 官方的 CLI 编程助手。每次启动一个新会话,模型面对的是一个全新的上下文窗口——它不知道你的项目用什么技术栈、你偏好什么代码风格、上次修了什么 bug。

没有记忆系统,你每次都要花几分钟"教育"AI:解释架构、重复约定、描述上下文。这在复杂项目上是巨大的效率损耗和 token 浪费。

Claude Code 的解决方案是一套 三层持久化记忆体系,配合会话内的 上下文压缩机制,让 AI 在跨会话和长会话两个维度上都能高效利用知识。

二、记忆系统全景架构

从源码结构看,Claude Code 的记忆相关代码分布在以下目录:

src/
├── memdir/              # Auto Memory 核心(记忆目录管理)
│   └── memdir.ts        # buildMemoryPrompt / loadMemoryPrompt / 截断逻辑
├── services/
│   ├── SessionMemory/   # 会话记忆提取(Session Memory)
│   │   └── sessionMemory.ts
│   └── compact/         # 上下文压缩(三层策略)
├── utils/
│   └── api.ts           # CLAUDE.md 注入逻辑(prependUserContext)
└── constants/           # System Prompt 各段落定义

整个记忆体系可以概括为 "手动 + 自动 + 会话"三层架构

three_layer_memory_architecture.svg

三、Layer 1:CLAUDE.md —— 手动记忆层

3.1 不在 System Prompt 里

这是最大的认知误区。很多人以为 CLAUDE.md 的内容被拼接到 system prompt 中,实际上并非如此。

从源码看,CLAUDE.md 通过 prependUserContext() 函数注入,插入位置是消息列表的最前面,作为一条特殊的 user message:

// src/utils/api.ts(简化)
export function prependUserContext(messages, context): Message[] {
  return [
    createUserMessage({
      content: `As you answer the user's questions, you can use...
        ${context.claudeMdContent}
        ${context.memoryContent}
        ...`
    }),
    ...messages
  ]
}

这意味着 CLAUDE.md 不享受 system prompt 级别的缓存优化。每次会话它作为 user message 的一部分被发送,会消耗实际的输入 token。

3.2 四级作用域

CLAUDE.md 支持四个作用域层级,按优先级从低到高:

层级路径场景
企业级MDM 管控策略注入企业统一管控
用户级~/.claude/CLAUDE.md个人全局偏好
项目级./CLAUDE.md./.claude/CLAUDE.md团队共享约定(提交到 git)
本地级./CLAUDE.local.md个人项目偏好(gitignore)

项目级的 CLAUDE.md 可以通过 /init 命令自动生成——Claude 会分析你的代码库,提取构建命令、测试指令和项目约定。

3.3 最佳实践

由于 CLAUDE.md 直接消耗 token,写法上需要注意效率:

  • 越短越好:简洁的指令比冗长的描述更容易被模型遵循
  • 结构化:用清晰的分类(构建命令、代码风格、架构约定)组织内容
  • 避免废话:不要写"请帮我"、"你应该"这类无效前缀
  • 可执行:写成可直接执行的命令或可直接遵循的规则

四、Layer 2:Auto Memory —— 自动记忆层(memdir)

这是 Claude Code 记忆系统中最有技术含量的部分,代码位于 src/memdir/memdir.ts

4.1 核心设计:基于文件的记忆目录

每个项目在 ~/.claude/projects/<project>/memory/ 下有一个专属的记忆目录。目录路径通过 git 仓库路径派生,所以同一仓库的所有 worktree 和子目录共享同一个记忆空间。

~/.claude/projects/
├── <project-hash-A>/
│   └── memory/
│       ├── MEMORY.md        # 索引文件(入口点)
│       ├── debugging.md     # 主题文件:调试经验
│       ├── patterns.md      # 主题文件:代码模式
│       └── architecture.md  # 主题文件:架构笔记
└── <project-hash-B>/
    └── memory/
        └── MEMORY.md

4.2 MEMORY.md:200 行硬截断的索引文件

源码中定义了明确的截断常量:

// src/memdir/memdir.ts
export const ENTRYPOINT_NAME = 'MEMORY.md'
export const MAX_ENTRYPOINT_LINES = 200
export const MAX_ENTRYPOINT_BYTES = 25_000  // 25KB

只有 MEMORY.md 的前 200 行或前 25KB 会在会话启动时自动加载。超出部分直接被截断,并且会产生警告提示:

WARNING: MEMORY.md is 350 lines (limit: 200). Only part of it was loaded.
Keep index entries to one line under ~200 chars; move detail into topic files.

这意味着 MEMORY.md 应该被当作索引使用,而不是存放大量细节。每条记忆控制在一行、200 字符以内,详细内容放到独立的 topic 文件中。

4.3 Topic 文件:按需加载

debugging.mdpatterns.md 这样的主题文件不会在启动时加载。Claude 在会话过程中需要相关信息时,会通过标准的文件读取工具按需读取它们。

这是一个典型的冷热分层设计:

  • 热数据(MEMORY.md 前 200 行)→ 每次启动加载,消耗固定 token
  • 温数据(topic 文件)→ 按需读取,用工具调用触发
  • 冷数据(历史会话)→ 不加载,依赖 Session Memory 提取

4.4 四类记忆分类法

buildMemoryLines() 函数的源码注释可以看到,Auto Memory 采用了一个封闭的四类分类体系:

// src/memdir/memdir.ts
/**
 * Constrains memories to a closed four-type taxonomy:
 * - user: 用户偏好和习惯
 * - feedback: 用户的修正和反馈
 * - project: 项目特定知识
 * - reference: 参考信息
 *
 * Content derivable from current project state (code patterns,
 * architecture, git history) is explicitly excluded.
 */

关键设计原则:可以从代码库当前状态推导出来的信息(代码模式、架构、git 历史)被显式排除在记忆之外。这避免了记忆与实际代码之间的不一致——代码变了但记忆没更新的问题。

4.5 记忆写入时机

Claude 并不是每次会话都写记忆。从源码和官方文档看,它的写入策略是:

  1. 用户纠正时:当用户纠正 Claude 的行为("不要用分号"、"我们用 pnpm 不用 npm"),Claude 会主动将这些偏好写入记忆
  2. 有价值的发现时:调试过程中发现的坑、构建系统的特殊配置等
  3. Claude 自主判断:基于"这条信息在未来的对话中是否有用"来决定是否记忆

写入操作在 UI 中会显示 "Writing memory" 提示,用户可以随时编辑或删除这些文件(它们就是普通的 Markdown 文件)。

4.6 记忆注入到 System Prompt

loadMemoryPrompt() 函数负责将记忆内容注入到 system prompt 的动态部分(SYSTEM_PROMPT_DYNAMIC_BOUNDARY 之后):

// src/memdir/memdir.ts(简化)
export function buildMemoryPrompt(params: {
  displayName: string
  memoryDir: string
  extraGuidelines?: string[]
}): string {
  const entrypoint = params.memoryDir + ENTRYPOINT_NAME
  const raw = fs.readFileSync(entrypoint, { encoding: 'utf-8' })
  const t = truncateEntrypointContent(raw)  // 硬截断

  const lines = buildMemoryLines(params.displayName, params.memoryDir, ...)
  lines.push(`## ${ENTRYPOINT_NAME}`, '', t.content)
  return lines.join('\n')
}

注意这里是同步读取fs.readFileSync),因为记忆加载发生在 system prompt 组装阶段,需要在 API 调用前完成。

五、Layer 3:Session Memory —— 会话内记忆管理

5.1 Session Memory 提取

src/services/SessionMemory/sessionMemory.ts 实现了从对话过程中自动提取值得记忆的内容。从架构分析中可以看到,它的工作方式是:

// 会话结束或 compact 触发时
shouldExtractMemory() -> runForkedAgent() -> Session Memory 更新

这里用了一个巧妙的设计——fork 出一个子 agent 来做记忆提取,不阻塞主会话的执行流程。

5.2 三层上下文压缩

长会话中上下文会逐渐填满,Claude Code 的 services/compact/ 实现了三层压缩策略:

策略机制触发时机
autoCompact将历史消息总结为摘要上下文接近限制时自动触发
snipCompact修剪冗长的工具输出单次工具输出过大时
contextCollapse折叠旧的消息段落保留关键信息,折叠细节

这套压缩机制确保了即使在超长会话中,关键的上下文信息不会因为窗口限制而丢失。

六、System Prompt 的缓存架构

Claude Code 的 system prompt 不是一整块文本,而是被精心分割为静态段动态段

system_prompt_cache_architecture.svg 静态段通过 Anthropic API 的 prompt caching 机制可以被缓存复用,大幅降低 token 消耗和延迟。Auto Memory 注入在动态段——这意味着每次记忆内容变化,都会破坏后续的缓存,所以记忆内容要尽量稳定和精简。

七、CLAUDE.md vs Auto Memory:注入位置的差异

这两套机制虽然都提供跨会话记忆,但注入位置完全不同:

维度CLAUDE.mdAuto Memory
注入位置User message(对话开头)System prompt(动态段)
缓存影响不影响 system prompt 缓存变化会破坏缓存
加载方式完整加载(无截断)MEMORY.md 前 200 行
写入者用户手动Claude 自动
版本控制可提交到 git本地存储,不共享
适合内容团队约定、构建命令个人偏好、调试经验

八、记忆系统的设计启示

如果你在构建自己的 AI Agent 记忆系统,Claude Code 的设计有几个值得借鉴的点:

memory_injection_data_flow.svg

8.1 分层而非全量

不是所有记忆都需要每次加载。热数据(MEMORY.md 索引)自动加载,温数据(topic 文件)按需读取,冷数据(历史会话)通过提取机制保留精华。

8.2 索引 + 详情分离

MEMORY.md 当索引用,详细内容放 topic 文件。这个模式和数据库的"索引-数据"分离思想一致——索引小且快,数据大但不常读。

8.3 排除可推导信息

不记忆可以从代码库当前状态推导出来的内容。这避免了记忆与现实不一致的维护负担。

8.4 记忆是纯文本

所有记忆都是 Markdown 文件,人类可读可编辑。没有私有数据库、没有向量存储、没有嵌入计算。简单可靠。

8.5 硬性截断保护

200 行 / 25KB 的硬截断不是建议,是强制执行的。这种"宁可丢信息也不爆上下文"的设计,在生产系统中非常重要。

九、局限性

当然,这套系统也有明显的局限:

  • 单 Agent 绑定:记忆存储在 ~/.claude/ 下,不能跨工具共享(比如 Cursor 或 OpenCode 无法读取)
  • 无语义检索:topic 文件的查找依赖 Claude 自己的文件读取工具,本质上是 grep 级别的搜索,没有向量相似度匹配
  • 无版本管理:记忆文件没有版本历史,误删除或错误修改无法回退
  • 上下文开销:即使是索引级别的记忆,200 行也是不小的 token 消耗

社区已经有一些项目在尝试解决这些问题,比如 memsearch(跨 Agent 语义记忆)和 memory-mcp(基于 MCP 的记忆服务),有兴趣的可以关注。

十、总结

Claude Code 的记忆系统是一个务实的工程设计——没有花哨的向量数据库,没有复杂的 RAG 管线,就是 Markdown 文件 + 分层加载 + 硬性截断。但这种简单性恰恰是它的优势:可预测、可调试、可人工干预。

对于 AI Agent 开发者来说,Claude Code 的记忆架构提供了一个很好的参考基线:先用最简单的方案解决 80% 的问题,再在必要时引入更复杂的机制。


参考资料:

如果这篇文章对你有帮助,欢迎点赞收藏。关于 AI Agent 架构设计的更多内容,可以关注我后续的分享。