Claude Code 源码解析(二):Memory 体系全解

接续上一篇对主链路的梳理,本文聚焦 Claude Code 的 Memory 体系:它如何分层、如何读写、如何注入模型,以及 Memory 与 Message、UI 之间的边界。

设计思路

Claude Code 的 Memory 是 文件系统优先、Harness 编排 的持久化上下文体系,核心原则在 memdir/memoryTypes.tsbuildMemoryLines() 中写得很清楚:

  • 只存「无法从当前项目状态推导」的信息(用户偏好、项目决策、外部系统指针等)
  • 不存代码模式、架构、git 历史、已在 CLAUDE.md 里的内容、临时任务状态
  • 文件是持久化真相源;模型通过 Write/Edit 或后台 fork agent 读写文件
  • Memory ≠ Message:磁盘上的 memory 必须 注入 后才进入模型上下文;UI 主要渲染 Message,且会隐藏大部分注入内容

五层 Memory

Claude Code 并非单一「memory 模块」,而是 五层机制叠加,各有存储位置、生命周期与注入时机。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
flowchart TB
subgraph human["人写的指令"]
MD["CLAUDE.md 体系<br/>Managed / User / Project / Local / rules"]
end

subgraph auto["Auto Memory memdir"]
AM["MEMORY.md 索引 + topic 文件<br/>user / feedback / project / reference"]
end

subgraph team["Team Memory"]
TM["memory/team/MEMORY.md + topic"]
end

subgraph agent["Agent Memory"]
AG["agent-memory 按 agentType + scope"]
end

subgraph session["Session Memory"]
SM["summary.md 仅当前 session"]
end

MD --> Inject
AM --> Inject
TM --> Inject
AG --> SubSP["Subagent system prompt"]
SM --> Compact["Compact 时注入 messages"]

Inject["注入模型"]
路径示例生命周期主要用途
CLAUDE.mdCLAUDE.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仅本 sessionCompact 续接

并非每次对话都加载全部类型:基础层(CLAUDE.md + auto memory 行为指令)在多数 session 会尝试加载;topic 文件、nested CLAUDE.md、team/agent/session memory 均为 条件触发或按需 recall。空目录、关 feature、关 env,对应层就不参与。


Auto Memory 详解

存储结构

路径解析见 src/memdir/paths.ts

1
2
3
4
5
6
7
8
~/.claude/projects/<sanitized-git-root>/memory/
├── MEMORY.md # 索引(指针列表,不是正文仓库)
├── user_role.md # topic 文件
├── feedback_testing.md
├── project_release_freeze.md
├── logs/2026/06/2026-06-04.md # KAIROS 模式:日誌
└── team/ # Team Memory 子目录
└── MEMORY.md

同一 git repo 的多个 worktree 共享 一个 auto-memory 目录(findCanonicalGitRoot)。MEMORY.md 有上限:200 行 / 25KBtruncateEntrypointContent)。

四种类型

每个 topic 文件带 frontmatter(memoryTypes.ts):

1
2
3
4
5
6
7
---
name: 简短标题
description: 供 recall 时判断相关性(要具体)
type: user | feedback | project | reference
---

正文…
type存什么
user用户角色、偏好、背景
feedback用户对做法的纠正/确认
project进行中的决策、deadline(非代码可推导)
referenceLinear 项目、Grafana 面板等外部指针

标准写入是两步(buildMemoryLines):

  1. Write topic 文件(带 frontmatter)
  2. MEMORY.md 加一行:- [Title](file.md) — 一行 hook

Harness 会 ensureMemoryDirExists(),prompt 明确说「目录已存在,直接 Write,不要 mkdir」。

开关

isAutoMemoryEnabled() 优先级(memdir/paths.ts):

  1. CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 → 关
  2. --bare / CLAUDE_CODE_SIMPLE → 关
  3. CCR 且无 CLAUDE_CODE_REMOTE_MEMORY_DIR → 关
  4. settings.jsonautoMemoryEnabled
  5. 默认

读路径:Memory 如何进入模型

Memory 不会以「UI 直接读磁盘」的方式工作,而是 三条注入通道

通道 A:System Prompt — 行为指令

loadMemoryPrompt()memdir/memdir.ts)→ constants/prompts.tssystemPromptSection('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
2
3
getMemoryFiles()          # 扫描所有 memory 相关文件
→ getUserContext() # 组装 claudeMd + currentDate
→ prependUserContext() # 调 API 时 prepend 到 messages 最前

getMemoryFiles() 加载顺序(utils/claudemd.ts 文件头注释):

  1. Managed → User → 从 cwd 向上 Project/Local → rules
  2. 若 auto memory 开:读 MEMORY.md(AutoMem)
  3. 若 team 开:读 team MEMORY.md(TeamMem)

getClaudeMds() 拼成带说明的大字符串,注入形态(utils/api.ts):

1
2
3
4
createUserMessage({
content: `<system-reminder>…# claudeMd\n${claudeMd}…</system-reminder>`,
isMeta: true, // UI 隐藏,模型可见
})

Feature tengu_moth_copse 开时,filterInjectedMemoryFiles()从 claudeMd 里去掉 AutoMem/TeamMem 索引,改由 relevant recall 按需注入。

通道 C:Attachment — 按需、按文件

Nested memory(读/编辑某文件时):

  • 从该文件所在目录向上找 CLAUDE.md / conditional rules
  • 生成 type: 'attachment', nested_memory
  • API 前展开为 isMeta user
  • UI 只显示:Loaded ./CLAUDE.md

Relevant memories(每 user turn 预取,query.ts + attachments.ts):

1
2
3
4
5
6
7
8
用户 submit
→ startRelevantMemoryPrefetch(messages)
→ findRelevantMemories():
scanMemoryFiles() 读 frontmatter
Sonnet sideQuery 选最多 5 个相关文件
readMemoriesForSurfacing() 读正文
→ query 循环 consume → relevant_memories attachment
→ normalize 时展开为 isMeta user messages

去重:alreadySurfacedreadFileState、session 总 byte 上限。


写路径:Memory 如何落盘

主 Agent 主动写

System prompt 里有完整 save 说明 → 模型用 Write / Edit 写 topic 文件与 MEMORY.mdisAutoMemPath() 的路径有 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 MemoryAuto 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
T0  用户 submit → processUserInput → messages 写入 state

T1 onQueryImpl
getUserContext() / getSystemPrompt()(含 loadMemoryPrompt)
query() 启动

T2 queryLoop
startRelevantMemoryPrefetch(后台)
prependUserContext + normalizeMessagesForAPI → callModel

T3 tool 执行
读文件 → nested_memory attachment
循环末尾 consume prefetch → relevant_memories attachment

T4 turn 结束
extractMemories? / sessionMemory hook?

T5 上下文快满
session memory compact 或传统 compact

Memory 与 Message 的关系

这是理解整个体系的关键,需要区分 两个层级的「数据源」

层级数据源管什么
持久化层Memory(磁盘文件)跨 session 存什么
运行时层Message(messages state)本 session 里 UI 与 API 共用的 transcript
1
2
3
4
5
6
7
8
Memory(磁盘 / 配置)

│ 读取、注入(getUserContext、attachment、system prompt…)

Message(transcript state) ←── 运行时「枢纽」

├─→ UI:过滤后渲染
└─→ API:prepend + normalize + system
  • 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
2
3
messages state
├─→ UI:filter(shouldShowUserMessage) — 藏 isMeta、compact 等
└─→ API:prependUserContext + normalizeMessagesForAPI + systemPrompt

模型收到、UI 看不到(更常见)

内容原因
CLAUDE.md、MEMORY 索引isMeta: true → UI 过滤
System prompt(memory 指令、工具说明)走 system 参数,不是 Message
Git statussystemContext append 到 system
Compact 摘要prompt 屏隐藏,模型仍收到
大量 null-rendering attachmentUI 不画,API 前可能展开

UI 能看到、模型收不到

内容原因
progress(Bash 进度等)normalizeMessagesForAPI 过滤
多数 system message过滤;appendSystemMessage 标注为 UI-only
memory_saved、compact boundary给用户看的标记
流式 streamingText临时 UI;落库后是 assistant message

shouldShowUserMessageutils/messages.ts):

1
2
3
if (message.isMeta) {
return false // UI 不显示 CLAUDE.md、memory 注入等
}

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.mdAuto Memory
谁写模型(+ extractMemories)
内容项目规范、协作约定对话里提炼的偏好/决策/指针
注入getUserContext().claudeMdsystem 指令 + 索引 + relevant recall
版本控制Project/Local 可进 git~/.claude/projects/...

/remember skill:审查 auto-memory,提议 promote 到 CLAUDE.md / CLAUDE.local.md / team memory。


Agent Memory 与 Team Memory 简述

Agent Memorytools/AgentTool/agentMemory.ts):agent 定义里 memory: 'user' | 'project' | 'local' 时,loadAgentMemoryPrompt() 注入 subagent 的 system prompt,与主 thread 隔离。@mention agent 时,relevant recall 只搜该 agent 的 memory 目录。

Team Memoryservices/teamMemorySync/ + memdir/teamMemPaths.ts):需 TEAMMEM feature 且 isTeamMemoryEnabled()(依赖 auto memory 开启)。loadMemoryPrompt() 走 combined prompt;team MEMORY.mdgetMemoryFiles 进入 user context。


关键源码索引

模块文件
路径 / 开关memdir/paths.ts
类型 taxonomy / promptmemdir/memoryTypes.ts, memdir/memdir.ts
扫描 frontmattermemdir/memoryScan.ts
按需 recallmemdir/findRelevantMemories.ts
CLAUDE.md 加载utils/claudemd.ts
User contextcontext.ts
System memory sectionconstants/prompts.ts
Attachment 管道utils/attachments.ts
API prependutils/api.ts, query.ts
后台提取services/extractMemories/extractMemories.ts
蒸馏services/autoDream/autoDream.ts
Session memoryservices/SessionMemory/sessionMemory.ts
Agent memorytools/AgentTool/agentMemory.ts
Attachment → APIutils/messages.ts
UI 过滤shouldShowUserMessage, AttachmentMessage.tsx

小结

Claude Code 的 Memory 体系可以概括为:

  1. 五层并存CLAUDE.md(人写指令)、Auto/Team/Agent Memory(模型或团队持久化)、Session Memory(compact 续接)
  2. 三条读路径:system 教行为、user context 给索引/指令、attachment 按需给正文
  3. 双写:主 Agent Write + extractMemories 补漏 + autoDream 蒸馏
  4. 持久化 vs 运行时:Memory 文件是长期真相源;Message 是 session 内注入与展示的枢纽
  5. UI ≠ API:UI 是对 Message 的过滤视图;模型收到更多 hidden context 与 system 参数

下一篇可继续深入 compact 链路(session memory compact vs 传统摘要)、或 extractMemories fork agent 的完整执行流程。