Claude Code 源码解析(二):Memory 体系全解
接续上一篇对主链路的梳理,本文聚焦 Claude Code 的 Memory 体系:它如何分层、如何读写、如何注入模型,以及 Memory 与 Message、UI 之间的边界。
设计思路
Claude Code 的 Memory 是 文件系统优先、Harness 编排 的持久化上下文体系,核心原则在 memdir/memoryTypes.ts 与 buildMemoryLines() 中写得很清楚:
- 只存「无法从当前项目状态推导」的信息(用户偏好、项目决策、外部系统指针等)
- 不存代码模式、架构、git 历史、已在 CLAUDE.md 里的内容、临时任务状态
- 文件是持久化真相源;模型通过 Write/Edit 或后台 fork agent 读写文件
- Memory ≠ Message:磁盘上的 memory 必须 注入 后才进入模型上下文;UI 主要渲染 Message,且会隐藏大部分注入内容
五层 Memory
Claude Code 并非单一「memory 模块」,而是 五层机制叠加,各有存储位置、生命周期与注入时机。
1 | flowchart TB |
| 层 | 路径示例 | 生命周期 | 主要用途 |
|---|---|---|---|
| CLAUDE.md | CLAUDE.md、~/.claude/CLAUDE.md、.claude/rules/*.md | 人维护,可进 git | 指令:项目/用户规范 |
| Auto Memory | ~/.claude/projects/<repo>/memory/ | 模型 + 后台 agent | 跨 session 记忆 |
| Team Memory | .../memory/team/ | API 同步 | 团队共享 |
| Agent Memory | ~/.claude/agent-memory/<type>/ 等 | 按 agent 配置 | Subagent 专属 |
| Session Memory | .../<sessionId>/session-memory/summary.md | 仅本 session | Compact 续接 |
并非每次对话都加载全部类型:基础层(CLAUDE.md + auto memory 行为指令)在多数 session 会尝试加载;topic 文件、nested CLAUDE.md、team/agent/session memory 均为 条件触发或按需 recall。空目录、关 feature、关 env,对应层就不参与。
Auto Memory 详解
存储结构
路径解析见 src/memdir/paths.ts:
1 | ~/.claude/projects/<sanitized-git-root>/memory/ |
同一 git repo 的多个 worktree 共享 一个 auto-memory 目录(findCanonicalGitRoot)。MEMORY.md 有上限:200 行 / 25KB(truncateEntrypointContent)。
四种类型
每个 topic 文件带 frontmatter(memoryTypes.ts):
1 | --- |
| type | 存什么 |
|---|---|
user | 用户角色、偏好、背景 |
feedback | 用户对做法的纠正/确认 |
project | 进行中的决策、deadline(非代码可推导) |
reference | Linear 项目、Grafana 面板等外部指针 |
标准写入是两步(buildMemoryLines):
- Write topic 文件(带 frontmatter)
- 在
MEMORY.md加一行:- [Title](file.md) — 一行 hook
Harness 会 ensureMemoryDirExists(),prompt 明确说「目录已存在,直接 Write,不要 mkdir」。
开关
isAutoMemoryEnabled() 优先级(memdir/paths.ts):
CLAUDE_CODE_DISABLE_AUTO_MEMORY=1→ 关--bare/CLAUDE_CODE_SIMPLE→ 关- CCR 且无
CLAUDE_CODE_REMOTE_MEMORY_DIR→ 关 settings.json的autoMemoryEnabled- 默认 开
读路径:Memory 如何进入模型
Memory 不会以「UI 直接读磁盘」的方式工作,而是 三条注入通道:
通道 A:System Prompt — 行为指令
loadMemoryPrompt()(memdir/memdir.ts)→ constants/prompts.ts 的 systemPromptSection('memory', ...):
- 教模型:何时存、存什么、不存什么、如何 recall、如何验证 stale memory
- 通常 不包含 全部 topic 正文(feature
tengu_moth_copse开时,连MEMORY.md索引也可能从 system 里拿掉) - 不是 Message,走 API 的
systemPrompt参数
loadMemoryPrompt() 分支:
- KAIROS + assistant 模式 → 日誌 append 指令(写
logs/YYYY/MM/DD.md) - TEAMMEM 开 → combined prompt(private + team)
- 仅 auto →
buildMemoryLines('auto memory', autoDir)
通道 B:User Context — CLAUDE.md + MEMORY.md 索引
每 session 调用链:
1 | getMemoryFiles() # 扫描所有 memory 相关文件 |
getMemoryFiles() 加载顺序(utils/claudemd.ts 文件头注释):
- Managed → User → 从 cwd 向上 Project/Local → rules
- 若 auto memory 开:读
MEMORY.md(AutoMem) - 若 team 开:读 team
MEMORY.md(TeamMem)
getClaudeMds() 拼成带说明的大字符串,注入形态(utils/api.ts):
1 | createUserMessage({ |
Feature tengu_moth_copse 开时,filterInjectedMemoryFiles() 会 从 claudeMd 里去掉 AutoMem/TeamMem 索引,改由 relevant recall 按需注入。
通道 C:Attachment — 按需、按文件
Nested memory(读/编辑某文件时):
- 从该文件所在目录向上找 CLAUDE.md / conditional rules
- 生成
type: 'attachment', nested_memory - API 前展开为
isMetauser - UI 只显示:
Loaded ./CLAUDE.md
Relevant memories(每 user turn 预取,query.ts + attachments.ts):
1 | 用户 submit |
去重:alreadySurfaced、readFileState、session 总 byte 上限。
写路径:Memory 如何落盘
主 Agent 主动写
System prompt 里有完整 save 说明 → 模型用 Write / Edit 写 topic 文件与 MEMORY.md。isAutoMemPath() 的路径有 write carve-out。写完后可能 yield createMemorySavedMessage()(UI 显示「N memories saved」,不进 API)。
后台 extractMemories(补漏)
services/extractMemories/extractMemories.ts:每轮 query 自然结束时,fork subagent 从 transcript 提取 durable memory。
- 若 主 Agent 本轮已写过 auto-memory path → 跳过(二者互斥)
- 用
lastMemoryMessageUuid作 cursor,避免重复处理 scanMemoryFiles预注入 manifest,避免浪费 turn 去ls
autoDream(蒸馏)
services/autoDream/autoDream.ts:时间 + session 数量门槛满足时,后台 consolidation,把 daily log 蒸馏成 topic 文件 + 更新 MEMORY.md。
Team Memory 同步
services/teamMemorySync/:按 git remote 与 server API 同步;Pull 以 server 为准;删除不传播。
Session Memory 写
services/SessionMemory/sessionMemory.ts:后台 fork agent 定期 Edit summary.md(Current State、Errors、Worklog 等 section)。不每 turn 注入主对话;主要在 compact 时把内容变成 isCompactSummary user message。
Session Memory 是什么
Session Memory 是 当前会话的结构化工作笔记,与 auto memory 独立:
| 对比 | Session Memory | Auto Memory |
|---|---|---|
| 范围 | 仅当前 session | 跨 session、按 repo |
| 路径 | .../<sessionId>/session-memory/summary.md | .../memory/MEMORY.md + topic |
| 注入 | Compact 时 | 每 session user context + recall |
| 更新 | 后台 fork,有 token/tool 阈值 | 主 Agent + extractMemories |
Feature gate:tengu_session_memory;且需 isAutoCompactEnabled() 才注册 hook。默认多数外部用户可能感知不到。
一轮 User Turn 里的 Memory 时间线
1 | T0 用户 submit → processUserInput → messages 写入 state |
Memory 与 Message 的关系
这是理解整个体系的关键,需要区分 两个层级的「数据源」:
| 层级 | 数据源 | 管什么 |
|---|---|---|
| 持久化层 | Memory(磁盘文件) | 跨 session 存什么 |
| 运行时层 | Message(messages state) | 本 session 里 UI 与 API 共用的 transcript |
1 | Memory(磁盘 / 配置) |
- Memory 不是 UI 直接读的对象;UI 读 Message 列表
- Memory 内容往往 不以 Memory 对象进 UI,而是 转成 Message 或 system 参数
- 部分注入(如
prependUserContext)甚至在 调 API 时临时 prepend,不一定长期留在 state 最前面
Message 形态对照
| Memory 相关 content | 在 transcript 里的形态 |
|---|---|
| CLAUDE.md、MEMORY 索引 | user + isMeta: true |
| Relevant topic 全文 | attachment → 展开为 isMeta user |
| Compact / session 摘要 | user + isCompactSummary |
| Memory 写入成功 | system + memory_saved |
| Memory 行为指令 | 不在 Message,在 system prompt |
UI 看到什么 vs 模型收到什么
不是 WYSIWYG。 Message 是 UI 与 API 的 运行时共享数据源,但两条管道过滤不同:
1 | messages state |
模型收到、UI 看不到(更常见)
| 内容 | 原因 |
|---|---|
| CLAUDE.md、MEMORY 索引 | isMeta: true → UI 过滤 |
| System prompt(memory 指令、工具说明) | 走 system 参数,不是 Message |
| Git status | systemContext append 到 system |
| Compact 摘要 | prompt 屏隐藏,模型仍收到 |
| 大量 null-rendering attachment | UI 不画,API 前可能展开 |
UI 能看到、模型收不到
| 内容 | 原因 |
|---|---|
progress(Bash 进度等) | normalizeMessagesForAPI 过滤 |
多数 system message | 过滤;appendSystemMessage 标注为 UI-only |
memory_saved、compact boundary | 给用户看的标记 |
流式 streamingText | 临时 UI;落库后是 assistant message |
shouldShowUserMessage(utils/messages.ts):
1 | if (message.isMeta) { |
UI 里 memory 相关的 可见 提示通常是:
Loaded ./CLAUDE.md(nested_memory)Recalled N memories(relevant_memories;Ctrl+O transcript 模式可看全文)- 「N memories saved」(memory_saved)
/memory命令打开的独立编辑 UI(不属于主 transcript)
CLAUDE.md 与 Auto Memory 的分工
| CLAUDE.md | Auto Memory | |
|---|---|---|
| 谁写 | 人 | 模型(+ extractMemories) |
| 内容 | 项目规范、协作约定 | 对话里提炼的偏好/决策/指针 |
| 注入 | getUserContext().claudeMd | system 指令 + 索引 + relevant recall |
| 版本控制 | Project/Local 可进 git | 在 ~/.claude/projects/... |
/remember skill:审查 auto-memory,提议 promote 到 CLAUDE.md / CLAUDE.local.md / team memory。
Agent Memory 与 Team Memory 简述
Agent Memory(tools/AgentTool/agentMemory.ts):agent 定义里 memory: 'user' | 'project' | 'local' 时,loadAgentMemoryPrompt() 注入 subagent 的 system prompt,与主 thread 隔离。@mention agent 时,relevant recall 只搜该 agent 的 memory 目录。
Team Memory(services/teamMemorySync/ + memdir/teamMemPaths.ts):需 TEAMMEM feature 且 isTeamMemoryEnabled()(依赖 auto memory 开启)。loadMemoryPrompt() 走 combined prompt;team MEMORY.md 经 getMemoryFiles 进入 user context。
关键源码索引
| 模块 | 文件 |
|---|---|
| 路径 / 开关 | memdir/paths.ts |
| 类型 taxonomy / prompt | memdir/memoryTypes.ts, memdir/memdir.ts |
| 扫描 frontmatter | memdir/memoryScan.ts |
| 按需 recall | memdir/findRelevantMemories.ts |
| CLAUDE.md 加载 | utils/claudemd.ts |
| User context | context.ts |
| System memory section | constants/prompts.ts |
| Attachment 管道 | utils/attachments.ts |
| API prepend | utils/api.ts, query.ts |
| 后台提取 | services/extractMemories/extractMemories.ts |
| 蒸馏 | services/autoDream/autoDream.ts |
| Session memory | services/SessionMemory/sessionMemory.ts |
| Agent memory | tools/AgentTool/agentMemory.ts |
| Attachment → API | utils/messages.ts |
| UI 过滤 | shouldShowUserMessage, AttachmentMessage.tsx |
小结
Claude Code 的 Memory 体系可以概括为:
- 五层并存:CLAUDE.md(人写指令)、Auto/Team/Agent Memory(模型或团队持久化)、Session Memory(compact 续接)
- 三条读路径:system 教行为、user context 给索引/指令、attachment 按需给正文
- 双写:主 Agent Write + extractMemories 补漏 + autoDream 蒸馏
- 持久化 vs 运行时:Memory 文件是长期真相源;Message 是 session 内注入与展示的枢纽
- UI ≠ API:UI 是对 Message 的过滤视图;模型收到更多 hidden context 与 system 参数
下一篇可继续深入 compact 链路(session memory compact vs 传统摘要)、或 extractMemories fork agent 的完整执行流程。