Memory 系统:让 AI 拥有跨会话的记忆
一句话理解
每次关闭 Claude Code,对话历史就没了。但如果你告诉它"我们项目用 pnpm 不用 npm",你当然不想每次都重复一遍。Memory 系统就是解决这个问题的——它让 Claude Code 能记住跨会话的信息。
比喻:如果每次对话是一节"课",那 Memory 就是"课堂笔记本"。上一节课学到的东西写在笔记本里,下一节课翻开就能看到。
四层记忆架构
Claude Code 的记忆分为四层,从"个人专属"到"全公司共享":
┌─────────────────────────────────────────────────────┐
│ 第4层:Managed Memory(管理员策略) │
│ /etc/claude-code/CLAUDE.md │
│ 全公司所有人都遵守的规则,由管理员维护 │
│ 例如:"所有提交必须通过 CI" │
├─────────────────────────────────────────────────────┤
│ 第3层:User Memory(用户个人) │
│ ~/.claude/CLAUDE.md │
│ 你个人的偏好,适用于所有项目 │
│ 例如:"我喜欢用 TypeScript,不要 JavaScript" │
├─────────────────────────────────────────────────────┤
│ 第2层:Project Memory(项目级) │
│ 项目根目录/CLAUDE.md 或 .claude/CLAUDE.md │
│ 项目团队共享的规则(可提交到 Git) │
│ 例如:"本项目使用 Prisma ORM" │
├─────────────────────────────────────────────────────┤
│ 第1层:Local Memory(本地) │
│ .claude/CLAUDE.local.md │
│ 你个人在这个项目里的笔记(gitignored) │
│ 例如:"我负责 auth 模块的重构" │
└─────────────────────────────────────────────────────┘
优先级从低到高:Managed → User → Project → Local
后加载的优先级更高。就像 CSS 一样,后面的规则会覆盖前面的。
CLAUDE.md:最直接的记忆方式
CLAUDE.md 是一个特殊的 Markdown 文件,Claude Code 启动时会自动读取。
加载顺序
// src/utils/claudemd.ts (lines 790-900)
function getMemoryFiles() {
const files = []
// 1. 管理员级别(最低优先级)
files.push(readIf('/etc/claude-code/CLAUDE.md'))
files.push(readIf('/etc/claude-code/.claude/rules/*.md'))
// 2. 用户级别
files.push(readIf('~/.claude/CLAUDE.md'))
files.push(readIf('~/.claude/rules/*.md'))
// 3. 项目级别(从根目录到当前目录逐层加载)
for (dir of walkUp(cwd, projectRoot)) {
files.push(readIf(dir + '/CLAUDE.md'))
files.push(readIf(dir + '/.claude/CLAUDE.md'))
files.push(readIf(dir + '/.claude/rules/*.md'))
}
// 4. 本地级别(最高优先级)
for (dir of walkUp(cwd, projectRoot)) {
files.push(readIf(dir + '/.claude/CLAUDE.local.md'))
}
return files
}
@include 指令
CLAUDE.md 支持引用其他文件:
<!-- CLAUDE.md -->
# 项目规范
基本规则见这里:
@./docs/coding-standards.md
API 定义在这里:
@./api/openapi.yaml
// 支持的路径格式
@path // 相对路径
@./relative/path // 相对路径
@~/home/path // 用户主目录
@/absolute/path // 绝对路径
系统会递归展开这些引用(有循环引用检测),就像 C 语言的 #include。
Auto Memory:自动提取的记忆
除了手动维护 CLAUDE.md,Claude Code 还有一个自动记忆系统。它会在对话结束后,自动提取值得记住的信息。
存储结构
~/.claude/projects/<项目路径的哈希>/memory/
├── MEMORY.md ← 索引文件(必须存在)
├── user_preferences.md ← 用户偏好
├── feedback_testing.md ← 反馈:测试相关
├── project_auth.md ← 项目:认证模块
└── reference_linear.md ← 参考:Linear 项目
记忆文件格式
每个记忆文件都有固定的前置元数据(frontmatter):
---
name: 测试策略偏好
description: 用户要求集成测试使用真实数据库
type: feedback
---
集成测试必须连接真实数据库,不要用 mock。
**Why:** 上季度 mock 测试全部通过,但生产环境的数据库迁移失败了。
**How to apply:** 在写测试代码时,使用项目的 test DB 配置,不要 mock 数据库层。
四种记忆类型
// src/memdir/memoryTypes.ts (lines 14-19)
type MemoryType = 'user' | 'feedback' | 'project' | 'reference'
| 类型 | 用途 | 例子 |
|---|
| user | 用户角色、偏好、知识水平 | "我是后端开发,不太熟 React" |
| feedback | 用户纠正过的做法 | "不要在这里用 mock" |
| project | 正在进行的工作、截止日期 | "3月5日开始代码冻结" |
| reference | 外部系统的地址 | "bug 跟踪在 Linear INGEST 项目" |
MEMORY.md 索引文件
MEMORY.md 是所有记忆的入口,每次对话开始时自动注入到上下文中:
- [测试策略](feedback_testing.md) — 集成测试用真实数据库,不要mock
- [Auth重构](project_auth.md) — 认证模块重写中,法务合规驱动
- [Linear项目](reference_linear.md) — pipeline bug 在 Linear INGEST 项目追踪
有严格的大小限制:
// src/memdir/memdir.ts (lines 35-38)
const MAX_LINES = 200 // 最多 200 行
const MAX_BYTES = 25_000 // 最多 25KB
超出限制时,多余的行会被截断,并附上警告信息。
记忆自动提取
对话结束后,一个后台 Agent 会分析对话内容,自动提取新的记忆:
对话结束
│
▼
┌──────────────────────────────┐
│ 检查门控条件: │
│ ✓ 是主 Agent(不是子 Agent) │
│ ✓ 功能开关开启 │
│ ✓ Auto Memory 已启用 │
│ ✓ 不在远程模式 │
└──────────────┬───────────────┘
│ 全部通过
▼
┌──────────────────────────────┐
│ 启动 Fork Agent(后台执行) │
│ │
│ 给这个 Agent 的工具限制: │
│ ✅ Read, Grep, Glob(随意用) │
│ ✅ Edit, Write(仅限记忆目录) │
│ ❌ 其他写入工具 │
│ ❌ Bash rm 等危险命令 │
│ │
│ 分析对话,提取新记忆 │
│ 最多 5 轮 │
└──────────────────────────────┘
// src/services/extractMemories/extractMemories.ts (lines 171-222)
function createAutoMemCanUseTool() {
return (tool, input) => {
// 读类工具:随意
if (['Read', 'Grep', 'Glob'].includes(tool.name)) return 'allow'
// 写类工具:只允许写入记忆目录
if (['Edit', 'Write'].includes(tool.name)) {
return isAutoMemPath(input.filePath) ? 'allow' : 'deny'
}
// Bash:只允许只读命令
if (tool.name === 'Bash') {
const readOnlyCommands = ['ls', 'find', 'grep', 'cat', 'stat', 'wc', 'head', 'tail']
return isReadOnly(input.command, readOnlyCommands) ? 'allow' : 'deny'
}
return 'deny'
}
}
防止重复提取
如果主 Agent 在对话中已经手动写了记忆(比如用户说"帮我记住这个"),后台提取会自动跳过:
// src/services/extractMemories/extractMemories.ts (lines 121-148)
function hasMemoryWritesSince(messages, lastCursor) {
// 检查是否有 Edit/Write 工具调用目标是记忆目录
// 如果有:跳过自动提取(避免冲突)
}
Session Memory:会话级记忆
除了持久化的 Auto Memory,还有一种只在当前会话内使用的记忆:
~/.claude/session/<session-id>/memory.md
它有 9 个固定区段:
## Session Title
_本次会话在做什么_
## Current State
_现在进展到哪了_
## Task Specification
_用户的原始需求是什么_
## Files and Functions
_涉及了哪些文件和函数_
## Workflow
_采用的工作流是什么_
## Errors & Corrections
_遇到了什么错误,怎么修的_
## Codebase Documentation
_代码库的结构笔记_
## Learnings
_学到了什么_
## Key Results
_关键成果_
触发条件
// src/services/SessionMemory/sessionMemory.ts (lines 134-181)
function shouldExtractMemory() {
// 条件1: 距上次提取已经累积了足够的 token
// 条件2: 工具调用次数达到阈值 OR 当前轮没有工具调用(自然对话断点)
}
Session Memory 的主要用途是为上下文压缩服务——当对话被压缩时,Session Memory 可以作为摘要的基础(见第6章)。
Team Memory:团队共享记忆
当启用团队功能后,记忆可以在团队成员间共享:
~/.claude/projects/<项目>/memory/
├── MEMORY.md ← 个人索引
├── personal_*.md ← 个人记忆
└── team/ ← 团队共享目录
├── MEMORY.md ← 团队索引
└── shared_*.md ← 团队记忆
什么记忆该分享
| 类型 | 个人 or 团队 | 原则 |
|---|
| user | 永远个人 | 个人偏好不该强加给团队 |
| feedback | 默认个人 | 除非是项目级规范(如测试策略) |
| project | 倾向团队 | 项目信息大家都该知道 |
| reference | 通常团队 | 外部系统地址大家都需要 |
安全防护
团队记忆有路径遍历防御,防止恶意文件名逃出记忆目录:
// src/memdir/teamMemPaths.ts (lines 265-284)
function validateTeamMemKey(key) {
// 防御手段:
// 1. 空字节截断检测
// 2. URL 编码遍历检测 (%2e%2e%2f = ../)
// 3. Unicode 归一化检测(全角 ../)
// 4. 反斜杠路径检测(Windows)
// 5. 符号链接逃逸检测(realpath() 校验)
// 6. 符号链接循环检测(ELOOP)
}
记忆的注入时机
会话开始
│
├─ 加载 CLAUDE.md 文件们 → 注入到 User Context
│ (Managed → User → Project → Local)
│
├─ 加载 MEMORY.md 索引 → 注入到 System Prompt
│ (auto memory 区段)
│
└─ 加载 Session Memory → 用于上下文压缩
(如果有上一次的会话记忆)
对话进行中
│
├─ Session Memory 持续更新
│ (每隔一定的 token 数和工具调用次数)
│
└─ 用户说"记住这个" → 立即写入 Auto Memory
对话结束后
│
└─ 后台 Agent 自动提取新记忆 → 写入 Auto Memory
记忆的验证原则
系统提示词中包含了一条重要规则:记忆可能过时,使用前要验证。
"记忆记录可能随时间变得陈旧。在使用记忆内容前,
请验证其是否仍然正确。如果记忆与当前观察冲突,
信任你现在看到的——并更新或删除过时的记忆。"
具体验证方式:
| 记忆内容 | 验证方法 |
|---|
| "某个文件在 src/auth/" | 检查文件是否存在 |
| "函数名叫 validateToken" | grep 搜索确认 |
| "项目用 Prisma" | 看 package.json |
小结
Memory 系统的设计围绕一个核心问题:如何在"记住有用信息"和"不被错误信息误导"之间取得平衡。
- 四层 CLAUDE.md:从管理员政策到个人笔记,层层递进
- Auto Memory:自动提取 + 手动维护,持久化到磁盘
- Session Memory:会话内持续更新的"工作笔记"
- Team Memory:团队共享 + 路径安全防护
- 验证优先:记忆不是权威来源,使用前要验证
比喻:Memory 系统就像一个经验丰富的员工的"工作习惯"。他记得"上次老板说用 pnpm",但如果看到 package-lock.json(npm 的文件),他不会盲目坚持记忆,而是会确认一下现在的情况。