记忆系统

Agent 系统作者:Claude Code 的记忆在源码里是 6 种文件类型 × 4 种内容类型 × 3 种 agent 持久化 scope——拆开看每一层的职责和实现。

记忆不是一层,是两套分类叠加

三级记忆 (Three-Tier Memory) · MEMORY.md 索引模式 上:三层职责分离。下:auto memory 的常驻索引 + 按需加载。 三层对比 (TIER COMPARISON) 项目记忆 (Project Memory) <repo>/CLAUDE.md 写入者 项目开发者 · git 跟踪 作用域 per-project · 跨 user 稳定 权威 OVERRIDES 默认 示例 "用 pnpm,不用 npm" "NEVER commit to main" 用户记忆 (User Memory) ~/.claude/CLAUDE.md 写入者 用户 · 手写 作用域 跨 project · per user 权威 低于项目级 示例 "偏好 type 而非 interface" "commit 用祈使句" Auto Memory ~/.claude/projects/<p>/memory/ 写入者 Claude · 自维护 作用域 per-project · 动态 类型 user / feedback / project / reference 示例 "偏好简短回答" "pipeline bugs 在 Linear INGEST" 放大 AUTO MEMORY · 索引 + 按需加载模式 始终在 system prompt 中 MEMORY.md (索引) ≤ 200 行——每条记忆一行,始终加载 - [简短回答偏好](feedback_terse.md) — 不喜欢尾部总结 - [用户身份](user_role.md) — 10 年 Go 开发,React 新手 - [Merge freeze](project_freeze.md) — 2026-03-05 mobile 发版 - [Linear 项目](reference_linear.md) — pipeline bugs 在 INGEST · · · (每条 ≤ 150 字符) 按需加载 按需加载 (完整内容) feedback_terse.md 完整 markdown · why + how-to-apply user_role.md 完整 markdown project_freeze.md 完整 markdown · why + how-to-apply reference_linear.md 完整 markdown 渐进披露 (Progressive Disclosure):索引是热路径 · 完整内容仅在相关时展开

很多人把 “记忆” 理解成一个存储。Claude Code 的源码里是两套正交的分类体系叠加

分类源码位置维度
记忆文件类型 (Memory File Type)utils/memory/types.ts6 种 —— 按”谁写的、文件在哪”分
记忆内容类型 (Memory Content Type)memdir/memoryTypes.ts4 种 —— 按”记的是什么信息”分(仅用于 AutoMem 里的单条记忆)

两套分类各自回答不同问题,合起来描述一条记忆的完整身份。下面分别拆开。


第一套:6 种记忆文件类型

// utils/memory/types.ts
export const MEMORY_TYPE_VALUES = [
  'User',        // ~/.claude/CLAUDE.md —— 用户全局偏好
  'Project',     // <repo>/CLAUDE.md —— 项目规则,进 git
  'Local',       // CLAUDE.local.md —— 项目**私有**规则,不进 git
  'Managed',     // policy / 企业管理配置
  'AutoMem',     // auto-memory,跨对话持久化
  ...(feature('TEAMMEM') ? (['TeamMem'] as const) : []),  // 团队共享记忆
] as const

每种类型在 system prompt 里有明确的描述后缀utils/claudemd.ts 第 1169-1177 行),作为注入给模型的元信息:

类型描述后缀谁写作用域
User"(user's private global instructions for all projects)"用户手写跨 project,per user
Project"(project instructions, checked into the codebase)"项目开发者手写per project,跨用户
Local"(user's private project instructions, not checked in)"用户手写,.gitignoreper project,per user(不共享)
Managed(无特殊后缀——policy 层)企业 / 组织管理员机器 / 组织级
AutoMem"(user's auto-memory, persists across conversations)"Claude 自己维护per project
TeamMem"(shared team memory, synced across the organization)"团队共享(feature-flagged TEAMMEM组织级

为什么 6 种类型(不是随意分的):

  • 谁有权写:User / Local / Project 是人手写的;AutoMem 是 Claude 写的;Managed 是 IT 管的;TeamMem 是团队同步的
  • 是否进 git:Project 进,Local 不进——这是一条硬边界
  • 可见性:Local 对同事不可见;Project 对所有人可见;User / AutoMem 对你自己可见
  • 更新频率:User / Project 人手写慢;AutoMem 按轮次更新

给自研 agent 的启示记忆”谁写的 × 作用域”是正交维度,合并成一层会让特殊需求无处安放。“企业强制规则”不该和”用户偏好”混在同一个文件。


加载顺序

getMemoryFiles() (utils/claudemd.ts 第 790 行起) 的加载顺序:

1. Managed(policy 级,始终第一)
2. Managed .claude/rules/*.md
3. User(如果 userSettings 启用)
4. User ~/.claude/rules/*.md
5. 从根目录向 CWD 走,每一层检查:
   - CLAUDE.md (Project)
   - .claude/CLAUDE.md (Project)
   - .claude/rules/*.md (Project)
   - CLAUDE.local.md (Local)

注意 .claude/rules/*.md 这个额外的规则文件目录——每层(Managed / User / Project / Local)都可以有自己的 rules 子目录。这是比单一 CLAUDE.md 更细粒度的规则组织方式,让你可以把不同主题(比如测试规则、安全规则、风格规则)分文件写。

Nested worktree 的特殊处理

源码里有一段针对嵌套在主仓库里的 worktree 的特殊分支(utils/claudemd.ts 第 868-884 行):

When running from a git worktree nested inside its main repo… Skip Project-type files from directories above the worktree but within the main repo — the worktree already has its own checkout.

场景:你从一个嵌套在主仓库里的 worktree 跑 Claude Code,向上走会踩到主仓库根目录,那里的 CLAUDE.md 会被重复加载。源码显式 skip。

Issue 引用:github.com/anthropics/claude-code/issues/29599——这是真实 bug 修复留下的补丁。这种细节通常只有跑在多 worktree workflow 的大型团队才会碰到,但一旦碰到就是血泪的排查。

单个 memory 文件的硬上限

// utils/claudemd.ts
export const MAX_MEMORY_CHARACTER_COUNT = 40000

单个 CLAUDE.md 或 memory 文件超过 40k 字符就被截断。40k 字符约 6-8k tokens——一个跑了几年、1000 人 contribute 的 repo 的 CLAUDE.md 容易撞到。

Memory 加载的关闭选项

CLAUDE_CODE_DISABLE_CLAUDE_MDS        // 硬关所有 CLAUDE.md 加载
CLAUDE_CODE_DISABLE_AUTO_MEMORY       // 只关 auto memory(见下面)
--bare / CLAUDE_CODE_SIMPLE           // 跳过自动发现但保留 --add-dir
CLAUDE_CODE_REMOTE                    // 云端 resume,跳过 git status
autoMemoryEnabled (settings.json)     // 项目级 opt-out

第二套:4 种记忆内容类型(AutoMem 内部)

AutoMem 里的每一条记忆都有一个 type,来自这个严格的 4 元集合:

// memdir/memoryTypes.ts
export const MEMORY_TYPES = ['user', 'feedback', 'project', 'reference'] as const

源码注释把设计意图写得很清楚:

Memories are constrained to four types capturing context NOT derivable from the current project state. Code patterns, architecture, git history, and file structure are derivable (via grep/git/CLAUDE.md) and should NOT be saved as memories.

核心原则只记可派生之外的内容。代码结构能从 grep 得到、git 历史从 git log 得到、项目约定从 CLAUDE.md 得到——这些都不进记忆。记忆只存”不在其他地方,但未来有用”的信息。

4 种 type 各自的语义(源码 TYPES_SECTION_COMBINED 的完整描述):

类型记什么何时保存何时使用
user用户的角色、目标、职责、知识学到用户偏好 / 背景时工作需要贴合用户视角时
feedback用户对做法的纠正或确认用户纠正你、或肯定一个 non-obvious 做法避免同一个纠正来第二次
project谁在做什么、为什么、何时做了解到 ongoing work / 约束 / deadline理解请求的动机
reference外部系统的指针(Linear / Grafana / Slack 等)用户提到外部资源及其用途用户引用外部系统时

为什么强类型而不是 free-form

不同类型老化速度不同——feedback 几乎永不过期(偏好稳定),project 变化快(任务完成就过期),reference 中等,user 稳定。强类型让 Claude 在读取时可以针对性判断”这条还生效吗”。

project 类型的特别提示:“relative dates in user messages must be converted to absolute dates when saving (e.g., ‘Thursday’ → ‘2026-03-05’)“——相对日期写进记忆后时间就漂了,源码里的记忆规则显式要求转绝对日期。


MEMORY.md 索引:源码级常量

AutoMem 的存储结构:

~/.claude/projects/<sanitized-git-root>/memory/
  MEMORY.md                       # 索引文件(始终加载)
  feedback_tool_truncation.md
  user_role.md
  project_current_initiative.md
  ...

索引文件的硬约束(memdir/memdir.ts):

export const ENTRYPOINT_NAME = 'MEMORY.md'
export const MAX_ENTRYPOINT_LINES = 200
export const MAX_ENTRYPOINT_BYTES = 25_000

两个上限同时生效——谁先破谁先触发截断:

  • 200 行是第一道线——每条索引一行,意味着最多 200 条记忆摘要
  • 25000 字节是第二道线,专门治”长行”场景

源码注释解释为什么双上限:

~125 chars/line at 200 lines. At p97 today; catches long-line indexes that slip past the line cap (p100 observed: 197KB under 200 lines).

p100 数据:有人的 MEMORY.md 只有 200 行但 197KB——意味着每行将近 1000 字符。单纯的行数上限抓不住这种 case,所以加了 byte cap。“125 chars/line”是 p97,意味着大多数人的索引是紧凑的。

触发截断时会追加一条警告消息(模型看得见):

WARNING: MEMORY.md is {reason}. Only part of it was loaded. Keep index entries
to one line under ~200 chars; move detail into topic files.

截断发生在 line cap 先,再做 byte cap;byte cap 用 lastIndexOf('\n', MAX_ENTRYPOINT_BYTES)在最近的换行处切,不从中间撕断。

给自研 agent 的启示无界增长的索引必须配多维硬上限 + 用户可见的截断警告。单一上限(行数 OR 字节)都会漏 case。警告消息要教用户怎么修——“Move detail into topic files” 是 actionable 的建议。


两种 AutoMem 模式:Live Index vs. Daily Log(KAIROS)

默认模式下 Claude 维护 MEMORY.md 作为实时索引——每次要存记忆都读 MEMORY.md 看有没有相关条目、决定是更新还是新增。

但在 KAIROS feature flag 启用时(memdir/paths.ts 注释写的很清楚),运作模式完全不同:

Rather than maintaining MEMORY.md as a live index, the agent appends to a date-named log file as it works. A separate nightly /dream skill distills these logs into topic files + MEMORY.md.

KAIROS 模式的文件结构:

<autoMemPath>/logs/2026/04/2026-04-22.md   # 今天的 append-only 日志
<autoMemPath>/MEMORY.md                     # 由 /dream 定期更新的索引
<autoMemPath>/{topic}.md                    # /dream 整理出的主题文件

核心思路:写入时便宜(append-only),整理由后台 job 做。自主 agent 每天跑 N 小时,不该在线路上花时间维护索引。

这和 journaling 的模式接近——write-ahead log + 后台 compaction。对自主 agent 是非常合适的模式。


Canonical git root:多 worktree 共享一个 memory

// memdir/paths.ts
function getAutoMemBase(): string {
  return findCanonicalGitRoot(getProjectRoot()) ?? getProjectRoot()
}

findCanonicalGitRoot 的意思:同一个 repo 的所有 worktree 共享同一个 auto-memory 目录

源码注释引用的 issue:anthropics/claude-code#24382——如果不这么做,每个 worktree 都有自己的 memory,从 worktree A 学到的偏好在 worktree B 里用不到。

给自研 agent 的启示记忆的 project key 应该是 canonical 的。Git repo 的 “身份” 不是路径,是 canonical root(或者远端 URL)。早早把这个想清楚,后期不用做数据迁移。


AutoMem 的环境变量控制

Auto memory 有独立于 CLAUDE.md 的启用链(memdir/paths.ts 第 30-55 行):

Priority chain (first defined wins):
  1. CLAUDE_CODE_DISABLE_AUTO_MEMORY env var (1/true → OFF, 0/false → ON)
  2. CLAUDE_CODE_SIMPLE (--bare) → OFF
  3. CCR without persistent storage → OFF (no CLAUDE_CODE_REMOTE_MEMORY_DIR)
  4. autoMemoryEnabled in settings.json (supports project-level opt-out)
  5. Default: enabled

5 层优先级,每层有对应的关停途径。用户、组织、云端、项目级都可以独立 opt-out——关闭路径和启用路径一样细致


Subagent:上下文防火墙(源码视角)

Agent tool 的完整签名

源码 tools/AgentTool/AgentTool.tsx 第 82-102 行:

{
  description: z.string(),      // 3-5 词的任务描述
  prompt: z.string(),            // 实际任务
  subagent_type: z.string().optional(),
  model: z.enum(['sonnet', 'opus', 'haiku']).optional(),  // 模型覆盖
  run_in_background: z.boolean().optional(),               // 后台模式
  // 多 agent 参数:
  name: z.string().optional(),    // 可被 SendMessage 寻址的名字
  team_name: z.string().optional(),
  mode: permissionModeSchema().optional(),  // permission mode 覆盖
  // 隔离:
  isolation: z.enum(['worktree', 'remote']).optional(),  // worktree 临时仓库 / 远端 CCR
  cwd: z.string().optional(),     // 工作目录覆盖
}

比”subagent 就是隔离的执行”复杂得多——它是一个完整的 sub-Claude 配置系统

  • model:子 agent 可以用更便宜的模型(例如 haiku),主 agent 继续用 opus
  • isolation: 'worktree'在一个临时 git worktree 里跑——子 agent 的改动不污染主仓库,完成后可以 review 再 merge
  • isolation: 'remote'(ant-only):在云端 CCR 环境里跑——完全隔离的文件系统 / 网络
  • run_in_background:后台跑,不阻塞主 agent
  • mode:子 agent 可以有不同的 permission mode(例如子 agent 跑 plan mode,主 agent 在 default)
  • name + SendMessage:多 agent 间异步通信的原语

两种 subagent 路径

源码 AgentTool.tsx 第 622-633 行显示实际有两种执行路径

// 默认路径:fresh context,不继承父对话
override: isForkPath ? {
  systemPrompt: forkParentSystemPrompt   // fork 路径:用父系统提示词
} : enhancedSystemPrompt ? {
  systemPrompt: asSystemPrompt(enhancedSystemPrompt)  // 默认:专属系统提示词
} : undefined,

// 只有 fork 路径才继承父对话历史:
forkContextMessages: isForkPath ? toolUseContext.messages : undefined,

两种路径:

  1. 默认(非 fork):子 agent 起一条全新的对话,有自己的 system prompt,看不到父对话历史。这就是”上下文防火墙
  2. Fork 路径:子 agent 继承父对话的全部 messages + 父 system prompt——用于需要完整上下文的场景

大多数日常 Agent tool 调用走默认路径,fork 路径是特殊场景的安全阀。

Agent-level 持久化记忆(AgentMemoryScope

Subagent 还有自己的持久化记忆,独立于主 agent 的 AutoMem(tools/AgentTool/agentMemory.ts):

export type AgentMemoryScope = 'user' | 'project' | 'local'
// - 'user'    → ~/.claude/agent-memory/<agentType>/
// - 'project' → .claude/agent-memory/<agentType>/
// - 'local'   → .claude/agent-memory-local/<agentType>/

每个 subagent type(比如 code-reviewerdatabase-migrator)都有自己的 3-scope 记忆目录。agent 之间记忆不串——一个 subagent 学到的偏好不会污染另一个 subagent。

给自研 agent 的启示:subagent 不只是”执行隔离”——它也应该是”学习隔离”。不同 subagent 处理不同领域,学到的经验各自沉淀,互不串门。


记忆使用的三条硬规则

源码里的 memory guidance(注入到 system prompt 里,作为 MEMORY_INSTRUCTION 的一部分)对 Claude 读记忆时的行为有三条硬规则:

1. Trust-but-verify

A memory that names a specific function, file, or flag is a claim that it existed when the memory was written. It may have been renamed, removed, or never merged. Before recommending it [verify].

记忆是时间点的快照,不是当下真相。引用具体标识符前必须 grep / read 验证。

2. 明确排除清单

What NOT to save:

  • Code patterns, conventions, architecture (derivable from code)
  • Git history, recent changes (git log is authoritative)
  • Debugging solutions (the fix is in the code)
  • Anything already in CLAUDE.md (no duplicates)
  • Ephemeral conversation state (use TaskCreate instead)

告诉 agent “不要存什么” 和告诉它 “存什么” 同样重要——LLM 默认倾向于”能记就记”,不显式排除就会膨胀。

3. 不用就不读

When to access memories: When memories seem relevant, or the user references prior-conversation work. […] If the user says to ignore or not use memory: Do not apply remembered facts, cite, compare against, or mention memory content.

用户可以明确说”别用记忆”——agent 要能无视记忆层。这是用户主权的体现。


给自研 agent 的要点

  1. 记忆是两套分类叠加:记忆文件类型(谁写、在哪、作用域)+ 记忆内容类型(记的是什么)。合并会让特殊需求无处放
  2. User / Project / Local 是硬分层。“进不进 git”是硬边界,不同场景要分开存
  3. Managed / TeamMem 对企业场景是必需——policy 强制规则 vs 团队共享规则,个人偏好装不下
  4. MEMORY.md 索引要双上限:行数 + 字节。p100 数据会告诉你为什么 single cap 不够
  5. 强类型的内容分类(user / feedback / project / reference)让记忆可以按类型老化
  6. Project 类型必须保存为绝对日期——相对日期(“上周”、“下周四”)在记忆里会漂
  7. 记忆的 project key 是 canonical git root,不是路径——多 worktree / 软链接场景的必需设计
  8. AutoMem 的启用链要细致:env var / CLI flag / settings.json / 默认——5 层 opt-out 对生产环境是必须
  9. Subagent 是 “执行 + 学习” 双重隔离:默认隔离上下文,同时有自己的 scope 化持久记忆
  10. 读记忆前要 verify——把”先 grep 确认”做成 agent 记忆使用的硬规则,不然记忆变成幻觉源
  11. 显式排除清单What NOT to saveWhat to save 同等重要,不写清楚 LLM 会膨胀
  12. 两种 MEMORY 模式(live index vs. daily log + nightly distill)对不同 agent 工作模式适用——自主 / 长时 agent 倾向后者

延伸阅读

  • Claude Code 源码:utils/memory/types.tsutils/claudemd.tsmemdir/{memdir,paths,memoryTypes}.tstools/AgentTool/{AgentTool,agentMemory}.tsx
  • 记忆设计 (Memory Design)——跨产品的记忆理论背景
  • 系统提示词组装——记忆层在 prompt 里的位置
  • 上下文压缩——短期对话 vs 长期记忆的分层
这页有帮助吗?