Claude Code Memory 总体系核心代码逐行解析

上一篇讲的是"上下文快满时,怎样把旧对话压成摘要";本篇讲的是另一个更容易混淆的问题:

Claude Code 到底怎样把信息保存下来,并在未来的会话里重新想起来?

配合下面这个 demo 一起看:

复制代码
用户:记住,我看复杂源码时喜欢"角色地图 + 逐行中文注释 + 反事实表"。

阅读顺序:先看 第 0 章 建立地图,再沿着"开门 → 告知规则 → 加载索引 → 写入 → 召回"的主链路阅读。


目录

  • [第 0 章:先建立一张地图](#第 0 章:先建立一张地图 "#%E7%AC%AC-0-%E7%AB%A0%E5%85%88%E5%BB%BA%E7%AB%8B%E4%B8%80%E5%BC%A0%E5%9C%B0%E5%9B%BE")
  • [第 1 章:总开关和目录定位 ------ Memory 存到哪里](#第 1 章:总开关和目录定位 —— Memory 存到哪里 "#%E7%AC%AC-1-%E7%AB%A0%E6%80%BB%E5%BC%80%E5%85%B3%E5%92%8C%E7%9B%AE%E5%BD%95%E5%AE%9A%E4%BD%8D--memory-%E5%AD%98%E5%88%B0%E5%93%AA%E9%87%8C")
  • [第 2 章:先告诉模型怎么记 ------ loadMemoryPrompt](#第 2 章:先告诉模型怎么记 —— loadMemoryPrompt "#%E7%AC%AC-2-%E7%AB%A0%E5%85%88%E5%91%8A%E8%AF%89%E6%A8%A1%E5%9E%8B%E6%80%8E%E4%B9%88%E8%AE%B0--loadmemoryprompt")
  • [第 3 章:启动时加载什么 ------ getMemoryFilesgetUserContext](#第 3 章:启动时加载什么 —— getMemoryFiles 与 getUserContext "#%E7%AC%AC-3-%E7%AB%A0%E5%90%AF%E5%8A%A8%E6%97%B6%E5%8A%A0%E8%BD%BD%E4%BB%80%E4%B9%88--getmemoryfiles-%E4%B8%8E-getusercontext")
  • [第 4 章:模型亲自写记忆 ------ 主题文件 + MEMORY.md](#第 4 章:模型亲自写记忆 —— 主题文件 + MEMORY.md "#%E7%AC%AC-4-%E7%AB%A0%E6%A8%A1%E5%9E%8B%E4%BA%B2%E8%87%AA%E5%86%99%E8%AE%B0%E5%BF%86--%E4%B8%BB%E9%A2%98%E6%96%87%E4%BB%B6--memorymd")
  • [第 5 章:模型漏记了怎么办 ------ extractMemories](#第 5 章:模型漏记了怎么办 —— extractMemories "#%E7%AC%AC-5-%E7%AB%A0%E6%A8%A1%E5%9E%8B%E6%BC%8F%E8%AE%B0%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E--extractmemories")
  • [第 6 章:未来怎么想起来 ------ 相关记忆异步召回](#第 6 章:未来怎么想起来 —— 相关记忆异步召回 "#%E7%AC%AC-6-%E7%AB%A0%E6%9C%AA%E6%9D%A5%E6%80%8E%E4%B9%88%E6%83%B3%E8%B5%B7%E6%9D%A5--%E7%9B%B8%E5%85%B3%E8%AE%B0%E5%BF%86%E5%BC%82%E6%AD%A5%E5%8F%AC%E5%9B%9E")
  • [第 7 章:Session Memory ------ 当前会话的滚动会议纪要](#第 7 章:Session Memory —— 当前会话的滚动会议纪要 "#%E7%AC%AC-7-%E7%AB%A0session-memory--%E5%BD%93%E5%89%8D%E4%BC%9A%E8%AF%9D%E7%9A%84%E6%BB%9A%E5%8A%A8%E4%BC%9A%E8%AE%AE%E7%BA%AA%E8%A6%81")
  • [第 8 章:Agent Memory 与 Team Memory ------ 作用域扩展](#第 8 章:Agent Memory 与 Team Memory —— 作用域扩展 "#%E7%AC%AC-8-%E7%AB%A0agent-memory-%E4%B8%8E-team-memory--%E4%BD%9C%E7%94%A8%E5%9F%9F%E6%89%A9%E5%B1%95")
  • [第 9 章:Memory、Compact、Context Collapse 到底什么关系](#第 9 章:Memory、Compact、Context Collapse 到底什么关系 "#%E7%AC%AC-9-%E7%AB%A0memorycompactcontext-collapse-%E5%88%B0%E5%BA%95%E4%BB%80%E4%B9%88%E5%85%B3%E7%B3%BB")
  • [附:五套 Memory 对照表](#附:五套 Memory 对照表 "#%E9%99%84%E4%BA%94%E5%A5%97-memory-%E5%AF%B9%E7%85%A7%E8%A1%A8")
  • [附:反事实表 ------ 少一层会发生什么](#附:反事实表 —— 少一层会发生什么 "#%E9%99%84%E5%8F%8D%E4%BA%8B%E5%AE%9E%E8%A1%A8--%E5%B0%91%E4%B8%80%E5%B1%82%E4%BC%9A%E5%8F%91%E7%94%9F%E4%BB%80%E4%B9%88")
  • 一页纸总结

第 0 章:先建立一张地图

Claude Code 源码里的 memory 不是一个单独功能,而是五套名字相似、职责不同的机制。

角色 类比 保存时间 主要文件
Instructions Memory 公司规章 跨会话 CLAUDE.md.claude/rules/*.md
Auto Memory 个人知识库 跨会话 memory/MEMORY.md + 主题文件
Session Memory 本次会议纪要 当前会话 session-memory/.../summary.md
Agent Memory 某类专家自己的笔记 跨 Agent 调用 agent-memory/<agentType>/
Team Memory 团队共享 Wiki 跨用户、跨机器 memory/team/ + 服务端同步

本篇主角

本篇的主角是 Auto Memory。它的完整生命线是:

text 复制代码
启动 Claude Code
    │
    ├─ loadMemoryPrompt()
    │    告诉模型:什么值得记、写到哪里、格式是什么
    │
    ├─ getMemoryFiles()
    │    加载 CLAUDE.md 与 Auto Memory 的 MEMORY.md 索引
    │
    ▼
用户说:"记住,我喜欢逐行中文注释"
    │
    ├─ 路 A:主 Agent 当场 Write/Edit 记忆
    │
    └─ 路 B:本轮结束后 extractMemories 后台补记
    │
    ▼
主题文件 feedback_source_walkthrough.md
    +
MEMORY.md 中的一行目录
    │
    ▼
未来某次用户又问源码
    │
    ├─ 扫描最多 200 个主题文件的 frontmatter
    ├─ sideQuery 最多选择 5 个相关文件
    ├─ 异步读取正文
    └─ 作为 relevant_memories attachment 注入主对话

一句话主线

Claude Code Memory 不是"把整段聊天永久塞进模型",而是把值得长期保留的信息写成 Markdown 文件;平时只加载短索引,真正需要时再选择少量主题文件注入上下文。

先纠正三个常见误解

误解 实际情况
Memory 是向量数据库 不是。主体是本地 Markdown + frontmatter + LLM 相关性选择
MEMORY.md 存全部记忆 不是。它是目录,正文放在独立主题文件
Session Memory 等于长期记忆 不是。它主要服务当前长会话和 compact

第 1 章:总开关和目录定位 ------ Memory 存到哪里

📄 文件:src/memdir/paths.ts 第 21-55、79-89、198-258 行

🎬 对应 demo:Claude Code 启动,先判断这次是否允许使用长期记忆

1.1 总开关 isAutoMemoryEnabled

ts 复制代码
export function isAutoMemoryEnabled(): boolean {
  // ① 环境变量明确要求关闭:立即关闭
  const envVal = process.env.CLAUDE_CODE_DISABLE_AUTO_MEMORY
  if (isEnvTruthy(envVal)) {
    return false
  }

  // ② 环境变量明确写成 0/false:相当于明确要求打开
  if (isEnvDefinedFalsy(envVal)) {
    return true
  }

  // ③ --bare 简化模式:关闭自动发现和后台记忆行为
  if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
    return false
  }

  // ④ 远程环境没有持久盘:即使写了,下次也不存在,所以关闭
  if (
    isEnvTruthy(process.env.CLAUDE_CODE_REMOTE) &&
    !process.env.CLAUDE_CODE_REMOTE_MEMORY_DIR
  ) {
    return false
  }

  // ⑤ 用户 settings.json 显式配置优先
  const settings = getInitialSettings()
  if (settings.autoMemoryEnabled !== undefined) {
    return settings.autoMemoryEnabled
  }

  // ⑥ 默认开启
  return true
}

源码:paths.ts L21-L55

这是一条"第一个明确答案获胜"的优先级链:

text 复制代码
环境变量
  > --bare
  > 远程环境是否有持久目录
  > settings.json
  > 默认开启

1.2 基础目录 getMemoryBaseDir

ts 复制代码
export function getMemoryBaseDir(): string {
  // 远程/Cowork 挂载了持久盘时,使用挂载目录
  if (process.env.CLAUDE_CODE_REMOTE_MEMORY_DIR) {
    return process.env.CLAUDE_CODE_REMOTE_MEMORY_DIR
  }

  // 本机默认使用 ~/.claude
  return getClaudeConfigHomeDir()
}

源码:paths.ts L79-L89

1.3 项目级 Auto Memory 路径

ts 复制代码
function getAutoMemBase(): string {
  // 优先找 canonical git root
  // 所有 worktree 因此共享同一份项目记忆
  return findCanonicalGitRoot(getProjectRoot()) ?? getProjectRoot()
}

export const getAutoMemPath = memoize(
  (): string => {
    // ① Cowork/用户可信设置可以直接覆盖完整目录
    const override = getAutoMemPathOverride() ?? getAutoMemPathSetting()
    if (override) {
      return override
    }

    // ② 默认:~/.claude/projects/<规范化项目根>/memory/
    const projectsDir = join(getMemoryBaseDir(), 'projects')
    return (
      join(projectsDir, sanitizePath(getAutoMemBase()), 'memory') + sep
    ).normalize('NFC')
  },

  // ③ 按 projectRoot 做 memoize key
  () => getProjectRoot(),
)

export function getAutoMemEntrypoint(): string {
  // ④ 入口索引固定叫 MEMORY.md
  return join(getAutoMemPath(), 'MEMORY.md')
}

源码:paths.ts L198-L258

demo 假设项目是 /repo/shop,最终大致得到:

text 复制代码
~/.claude/projects/-repo-shop/memory/
├── MEMORY.md
└── ...

🔑 小白重点(第 1 章)

  • Memory 是项目隔离的:不同仓库默认进入不同目录。
  • worktree 又是共享的:通过 canonical git root,避免每个 worktree 各记一套、互相矛盾。
  • 没有持久盘就不开:远程临时容器里写"长期记忆"没有意义。
  • 路径覆盖有安全边界 :项目提交的 .claude/settings.json 不能把目录偷偷改成 ~/.ssh;只有可信设置来源可以覆盖。

第 2 章:先告诉模型怎么记 ------ loadMemoryPrompt

📄 文件:src/constants/prompts.ts 第 491-506 行

📄 文件:src/memdir/memdir.ts 第 187-315、409-506 行

🎬 对应 demo:模型还没有读任何记忆正文,先收到一份"记忆系统使用说明书"

Memory 要生效,第一步不是读文件,而是让模型知道:

  1. 哪些内容值得保存;
  2. 哪些内容绝对不要保存;
  3. 保存到哪个目录;
  4. 使用什么文件格式;
  5. 什么时候应该召回。

2.1 Memory Prompt 被放进系统提示

ts 复制代码
const dynamicSections = [
  // ...其他动态系统提示...

  // ① 把 Memory 规则作为一个独立的 system prompt section
  systemPromptSection('memory', () => loadMemoryPrompt()),

  // ...其他动态系统提示...
]

源码:prompts.ts L491-L506

systemPromptSection 会缓存这一段,所以不是每一轮都重新扫描目录、重新拼提示。

2.2 loadMemoryPrompt 选择运行模式

ts 复制代码
export async function loadMemoryPrompt(): Promise<string | null> {
  // ① 判断 Auto Memory 总开关
  const autoEnabled = isAutoMemoryEnabled()

  // ② 新召回模式开启时,不要求维护 MEMORY.md 索引
  const skipIndex = getFeatureValue_CACHED_MAY_BE_STALE(
    'tengu_moth_copse',
    false,
  )

  // ③ 长生命周期助手模式:新信息写每日 append-only 日志
  if (feature('KAIROS') && autoEnabled && getKairosActive()) {
    return buildAssistantDailyLogPrompt(skipIndex)
  }

  // ④ Team Memory 开启:生成"私有 + 团队"组合规则
  if (feature('TEAMMEM') && teamMemPaths!.isTeamMemoryEnabled()) {
    const autoDir = getAutoMemPath()
    const teamDir = teamMemPaths!.getTeamMemPath()
    await ensureMemoryDirExists(teamDir)
    return teamMemPrompts!.buildCombinedMemoryPrompt(
      extraGuidelines,
      skipIndex,
    )
  }

  // ⑤ 普通模式:只有个人 Auto Memory
  if (autoEnabled) {
    const autoDir = getAutoMemPath()
    await ensureMemoryDirExists(autoDir)
    return buildMemoryLines(
      'auto memory',
      autoDir,
      extraGuidelines,
      skipIndex,
    ).join('\n')
  }

  // ⑥ 总开关关闭:不向模型介绍 Memory
  return null
}

源码:memdir.ts L409-L506

2.3 buildMemoryLines 告诉模型什么

核心规则可以压缩成下面几段:

text 复制代码
# auto memory

你有一个文件型持久记忆系统,目录是 <memoryDir>

用户明确说"记住"时:
  立即保存

用户说"忘掉"时:
  找到对应文件并删除或更新

允许的类型:
  user / feedback / project / reference

保存步骤:
  1. 写独立主题文件
  2. 在 MEMORY.md 增加一行指针

不要保存:
  代码结构、Git 历史、临时任务、CLAUDE.md 已有内容

召回时:
  Memory 只是过去某时刻的观察
  涉及当前代码、文件、flag 时必须重新验证

生成逻辑见 memdir.ts L187-L265

2.4 四种长期记忆类型

ts 复制代码
export const MEMORY_TYPES = [
  'user',       // 用户是谁、目标是什么、知识背景如何
  'feedback',   // 用户纠正或确认过的协作方式
  'project',    // 无法从代码推导的项目背景、决策、期限
  'reference',  // 外部系统和资料的入口
] as const

源码:memoryTypes.ts L14-L21

我们的 demo:

text 复制代码
"我看复杂源码时喜欢角色地图 + 逐行注释 + 反事实表"

它既描述了用户偏好,也描述了希望助手如何协作。实际保存时最适合 feedback,正文可以写成:

markdown 复制代码
---
name: source_explanation_style
description: 用户阅读复杂源码时偏好角色地图、逐行中文注释和反事实表
type: feedback
---

解释复杂源码时,先给角色地图,再沿调用链逐行添加中文注释,最后补反事实表。

**Why:** 用户需要先建立全局心智模型,再进入复杂控制流。

**How to apply:** 面对跨文件调用链、状态机、压缩或调度逻辑时采用此结构。

🔑 小白重点(第 2 章)

  • loadMemoryPrompt() 注入的是使用规则,不是所有记忆正文。
  • Prompt 明确禁止保存可从代码重新推导的信息,避免 Memory 退化成过期的代码快照。
  • MEMORY.md 是目录,不是正文仓库。
  • "Memory 说某函数存在"不等于"这个函数现在仍存在",所以源码要求再次 grep 或读文件验证。

第 3 章:启动时加载什么 ------ getMemoryFilesgetUserContext

📄 文件:src/utils/claudemd.ts 第 790-1007、1136-1195 行

📄 文件:src/context.ts 第 152-188 行

🎬 对应 demo:未来开启一次新会话,CC 先加载规章和短索引

这一章要分清两个动作:

text 复制代码
loadMemoryPrompt()
  = 告诉模型"怎么使用记忆系统"

getMemoryFiles()
  = 真的去磁盘找本次需要注入的文件

3.1 getMemoryFiles 的加载顺序

ts 复制代码
export const getMemoryFiles = memoize(async () => {
  const result: MemoryFileInfo[] = []
  const processedPaths = new Set<string>()

  // ① 企业/管理员级指令
  result.push(...await processMemoryFile(
    getMemoryPath('Managed'),
    'Managed',
    processedPaths,
    includeExternal,
  ))

  // ② 用户全局指令 ~/.claude/CLAUDE.md
  if (isSettingSourceEnabled('userSettings')) {
    result.push(...await processMemoryFile(
      getMemoryPath('User'),
      'User',
      processedPaths,
      true,
    ))
  }

  // ③ 从文件系统根部向当前目录逐级加载项目规则
  for (const dir of dirs.reverse()) {
    result.push(...await processMemoryFile(
      join(dir, 'CLAUDE.md'),
      'Project',
      processedPaths,
      includeExternal,
    ))

    result.push(...await processMemoryFile(
      join(dir, '.claude', 'CLAUDE.md'),
      'Project',
      processedPaths,
      includeExternal,
    ))

    // 还会递归读取 .claude/rules/*.md
    // 以及私有的 CLAUDE.local.md
  }

  // ④ 最后加载 Auto Memory 的短索引 MEMORY.md
  if (isAutoMemoryEnabled()) {
    const { info: memdirEntry } = await safelyReadMemoryFileAsync(
      getAutoMemEntrypoint(),
      'AutoMem',
    )
    if (memdirEntry) {
      result.push(memdirEntry)
    }
  }

  // ⑤ Team Memory 开启时,再加载团队索引
  if (feature('TEAMMEM') && teamMemPaths!.isTeamMemoryEnabled()) {
    const { info: teamMemEntry } = await safelyReadMemoryFileAsync(
      teamMemPaths!.getTeamMemEntrypoint(),
      'TeamMem',
    )
    if (teamMemEntry) {
      result.push(teamMemEntry)
    }
  }

  return result
})

源码:claudemd.ts L790-L1007

3.2 为什么加载顺序很重要

源码从"范围大、优先级低"加载到"范围小、优先级高":

text 复制代码
Managed
  ↓
User
  ↓
仓库上层目录
  ↓
更靠近 CWD 的目录
  ↓
Local
  ↓
AutoMem / TeamMem 索引

越靠后的内容越接近当前工作环境,模型通常会给予更高注意力。

3.3 @include 怎么处理

processMemoryFile 会:

  1. 标准化路径;
  2. processedPaths 防止重复和循环;
  3. 检查 claudeMdExcludes
  4. 解析 symlink;
  5. 读取正文并找出 @path
  6. 递归处理引用文件;
  7. 达到最大深度时停止。

核心代码见 claudemd.ts L615-L685

3.4 真正注入 userContext

ts 复制代码
export const getUserContext = memoize(async () => {
  // ① --bare 或禁用变量可能关闭自动发现
  const shouldDisableClaudeMd =
    isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_CLAUDE_MDS) ||
    (isBareMode() && getAdditionalDirectoriesForClaudeMd().length === 0)

  // ② 找文件 → 按实验开关过滤 → 拼成一整段上下文
  const claudeMd = shouldDisableClaudeMd
    ? null
    : getClaudeMds(
        filterInjectedMemoryFiles(await getMemoryFiles()),
      )

  // ③ 作为名为 claudeMd 的 userContext 字段返回
  return {
    ...(claudeMd && { claudeMd }),
    currentDate: `Today's date is ${getLocalISODate()}.`,
  }
})

源码:context.ts L152-L188

这里有一个很容易讲错的细节:

text 复制代码
Memory 的"行为规则"
  → loadMemoryPrompt()
  → system prompt

CLAUDE.md / MEMORY.md 的"文件内容"
  → getMemoryFiles() + getClaudeMds()
  → userContext

不是所有 Memory 内容都直接塞进 system prompt。

3.5 新召回模式下为什么不再注入索引

ts 复制代码
export function filterInjectedMemoryFiles(files) {
  const skipMemoryIndex =
    getFeatureValue_CACHED_MAY_BE_STALE('tengu_moth_copse', false)

  if (!skipMemoryIndex) return files

  // 新模式下,AutoMem/TeamMem 由相关性预取按需注入
  return files.filter(
    f => f.type !== 'AutoMem' && f.type !== 'TeamMem',
  )
}

源码:claudemd.ts L1136-L1151

因此存在两种召回范式:

text 复制代码
旧模式:
  每次加载 MEMORY.md 短索引
  模型根据索引自行决定要不要 Read 主题文件

新模式 tengu_moth_copse:
  不注入 MEMORY.md
  后台 selector 直接挑相关主题文件并附加正文

🔑 小白重点(第 3 章)

  • CLAUDE.md 是"规章",Auto Memory 是"经验";两者共用部分加载基础设施,但语义不同。
  • getMemoryFiles 被 memoize,同一会话不会每轮都遍历磁盘。
  • compact、切换 worktree、设置同步等场景会清缓存,让下一次重新读取。
  • 新召回模式的核心变化是:从"总给目录"变成"直接给最多几个相关正文"。

第 4 章:模型亲自写记忆 ------ 主题文件 + MEMORY.md

📄 文件:src/memdir/memdir.ts 第 199-265 行

📄 文件:src/memdir/memoryTypes.ts 第 180-270 行

🎬 对应 demo:主 Agent 识别到用户明确要求"记住",当场保存

4.1 为什么必须拆成两层

假设所有内容都直接追加到 MEMORY.md

text 复制代码
MEMORY.md
├─ 用户偏好 100 行
├─ 项目背景 300 行
├─ 外部系统 200 行
└─ 历史反馈 500 行

每次新会话都会付出整本加载成本,而且越用越大。

所以 CC 使用"图书馆目录 + 独立书籍":

text 复制代码
memory/
├── MEMORY.md                         # 只放一行一条的目录
├── feedback_source_walkthrough.md    # 详细正文
├── user_role.md
└── reference_dashboard.md

4.2 两步保存协议

ts 复制代码
const howToSave = [
  'Saving a memory is a two-step process:',

  // ① 主题文件:保存完整内容和 frontmatter
  '**Step 1** --- write the memory to its own file ...',
  ...MEMORY_FRONTMATTER_EXAMPLE,

  // ② MEMORY.md:只添加一行短指针
  '**Step 2** --- add a pointer to that file in `MEMORY.md`...',

  // ③ 明确强调:索引不是正文
  '`MEMORY.md` is an index, not a memory...',
]

源码:memdir.ts L205-L234

demo 最终写成:

text 复制代码
memory/
├── MEMORY.md
└── feedback_source_walkthrough.md

MEMORY.md

markdown 复制代码
- [源码讲解偏好](feedback_source_walkthrough.md) --- 复杂源码先给角色地图,再逐行注释并补反事实表

主题文件保存完整的 WhyHow to apply

4.3 索引的硬上限

ts 复制代码
export const MAX_ENTRYPOINT_LINES = 200
export const MAX_ENTRYPOINT_BYTES = 25_000

export function truncateEntrypointContent(raw: string) {
  // ① 先按 200 行截断
  // ② 再按约 25 KB 截断
  // ③ 尽量在换行处切,避免切断半行
  // ④ 末尾追加 WARNING,提醒模型整理索引
}

源码:memdir.ts L34-L102

4.4 什么不应该写

源码明确排除:

text 复制代码
× 代码模式、架构、文件路径、项目结构
  原因:应读取当前代码

× Git 历史、谁改了什么
  原因:git log / git blame 才是事实源

× 调试过程和修复配方
  原因:最终代码和 commit 才是事实源

× CLAUDE.md 已经写过的规则
  原因:重复注入

× 当前任务进度、临时状态
  原因:只对当前会话有用

源码:memoryTypes.ts L180-L195

4.5 用户说"忘掉"怎么办

Prompt 没有设计单独数据库删除 API,而是让模型:

text 复制代码
找到对应主题文件
  ↓
删除错误条目或修改正文
  ↓
同步更新 MEMORY.md 指针

也就是说,Memory 的 CRUD 本质上就是受权限控制的文件 CRUD。

🔑 小白重点(第 4 章)

  • 主题文件是知识,MEMORY.md 是导航。
  • frontmatter 的 description 非常关键,未来的相关性选择器主要靠它判断是否值得召回。
  • 按"语义主题"组织,而不是按"2026-08-09 第 3 次对话"组织。
  • 显式说"记住"也不能突破"不保存临时噪声"的规则。

第 5 章:模型漏记了怎么办 ------ extractMemories

📄 文件:src/query/stopHooks.ts 第 133-156 行

📄 文件:src/services/extractMemories/extractMemories.ts 第 296-615 行

🎬 对应 demo:主 Agent 没有当场写文件,本轮结束后后台书记员补记

5.1 触发点:完整一轮结束

ts 复制代码
if (!isBareMode()) {
  if (
    feature('EXTRACT_MEMORIES') &&
    !toolUseContext.agentId &&
    isExtractModeActive()
  ) {
    // fire-and-forget:不挡住用户看到主回复
    void extractMemoriesModule!.executeExtractMemories(
      stopHookContext,
      toolUseContext.appendSystemMessage,
    )
  }
}

源码:stopHooks.ts L133-L156

这里的时机不是"每次工具调用后",而是 query loop 已经给出最终回答、没有继续调工具时。

5.2 initExtractMemories 保存哪些运行状态

ts 复制代码
export function initExtractMemories(): void {
  // 尚未结束的后台提取 Promise
  const inFlightExtractions = new Set<Promise<void>>()

  // 游标:上一次处理到哪条消息
  let lastMemoryMessageUuid: string | undefined

  // 防止并行写同一目录
  let inProgress = false

  // 距离上次提取经过了多少个合格回合
  let turnsSinceLastExtraction = 0

  // 忙碌期间又来一次触发:只保留最新上下文,稍后补跑
  let pendingContext
}

源码:extractMemories.ts L290-L325

这套状态可以想成快递分拣站:

text 复制代码
lastMemoryMessageUuid  = 上次包裹处理到哪
inProgress             = 当前传送带是否有人
pendingContext         = 忙碌时新来的最后一批包裹
inFlightExtractions    = 关机前仍需等待的任务

5.3 主 Agent 写过就不重复提取

ts 复制代码
if (hasMemoryWritesSince(messages, lastMemoryMessageUuid)) {
  // ① 主对话已经 Write/Edit 过 Auto Memory
  // ② 后台 Agent 再跑只会制造重复
  // ③ 所以跳过,但仍推进游标
  const lastMessage = messages.at(-1)
  if (lastMessage?.uuid) {
    lastMemoryMessageUuid = lastMessage.uuid
  }
  return
}

源码:extractMemories.ts L345-L360

于是两条写入路径是同一回合互斥的:

text 复制代码
主 Agent 已写
  → 后台不写

主 Agent 没写
  → 后台检查有没有值得保存的内容

5.4 正式启动后台 fork

ts 复制代码
// ① 扫描已有主题文件的 frontmatter
//    直接把清单给后台 Agent,省掉一次 ls
const existingMemories = formatMemoryManifest(
  await scanMemoryFiles(memoryDir, createAbortController().signal),
)

// ② 生成"只分析最近 N 条消息"的提取指令
const userPrompt = teamMemoryEnabled
  ? buildExtractCombinedPrompt(
      newMessageCount,
      existingMemories,
      skipIndex,
    )
  : buildExtractAutoOnlyPrompt(
      newMessageCount,
      existingMemories,
      skipIndex,
    )

// ③ 完美 fork 主会话,复用 prompt cache
const result = await runForkedAgent({
  promptMessages: [
    createUserMessage({ content: userPrompt }),
  ],
  cacheSafeParams,
  canUseTool: createAutoMemCanUseTool(memoryDir),
  querySource: 'extract_memories',
  forkLabel: 'extract_memories',
  skipTranscript: true,
  maxTurns: 5,
})

源码:extractMemories.ts L388-L427

5.5 权限沙箱

后台提取 Agent 可以:

text 复制代码
✓ Read / Grep / Glob
✓ 只读 Bash
✓ 在 Auto Memory 目录内 Write/Edit

不可以:

text 复制代码
× 修改项目源码
× 执行写操作 Bash
× 调 MCP
× 再启动 Agent
× 写到 Memory 目录之外

核心权限判断见 extractMemories.ts L166-L221

5.6 为什么还要限制 maxTurns: 5

正常提取只需要:

text 复制代码
Turn 1:并行 Read 可能要更新的已有文件
Turn 2:并行 Edit/Write
Turn 3:必要时修正

限制为 5 可以防止后台 Agent 因"验证得更彻底"进入搜索兔子洞,无限消耗 token。

5.7 并发触发怎么收敛

text 复制代码
提取 A 正在运行
    │
    ├─ 触发 B:不并行,保存 pendingContext = B
    ├─ 触发 C:覆盖 pendingContext = C
    │
提取 A 完成
    │
    └─ 用最新 C 补跑一次 trailing extraction

因为 C 的 messages 已经包含 B 的历史,所以保留最新上下文即可。

🔑 小白重点(第 5 章)

  • 后台提取是 best effort:失败只打日志,不影响主回复。
  • 它不是每轮无脑总结全部历史,而是通过 UUID 游标只处理增量。
  • 主 Agent 和后台 Agent 不会在同一段消息上重复写。
  • fork 复用主会话缓存,降低额外提取成本。
  • 退出前 drainPendingExtraction() 会软等待仍在运行的任务,避免刚写一半进程就结束。

第 6 章:未来怎么想起来 ------ 相关记忆异步召回

📄 文件:src/memdir/memoryScan.ts 第 21-94 行

📄 文件:src/memdir/findRelevantMemories.ts 第 18-140 行

📄 文件:src/utils/attachments.ts 第 2196-2415 行

📄 文件:src/query.ts 第 300-304、1592-1613 行

🎬 对应 demo:几天后,用户再次要求解析复杂源码

这是 Auto Memory 最有意思的一段:它没有向量数据库,而是使用"两级筛选"。

6.1 第一级:只扫描轻量 header

ts 复制代码
const MAX_MEMORY_FILES = 200
const FRONTMATTER_MAX_LINES = 30

export async function scanMemoryFiles(memoryDir, signal) {
  // ① 递归找所有 .md,但排除索引 MEMORY.md
  const entries = await readdir(memoryDir, { recursive: true })
  const mdFiles = entries.filter(
    f => f.endsWith('.md') && basename(f) !== 'MEMORY.md',
  )

  // ② 每个文件只读前 30 行,拿 frontmatter
  const headerResults = await Promise.allSettled(
    mdFiles.map(async relativePath => {
      const { content, mtimeMs } = await readFileInRange(
        join(memoryDir, relativePath),
        0,
        FRONTMATTER_MAX_LINES,
      )
      const { frontmatter } = parseFrontmatter(content)
      return {
        filename: relativePath,
        description: frontmatter.description || null,
        type: parseMemoryType(frontmatter.type),
        mtimeMs,
      }
    }),
  )

  // ③ 最新优先,最多保留 200 个候选
  return fulfilled(headerResults)
    .sort((a, b) => b.mtimeMs - a.mtimeMs)
    .slice(0, MAX_MEMORY_FILES)
}

源码:memoryScan.ts L21-L77

扫描得到的 manifest 类似:

text 复制代码
- [feedback] feedback_source_walkthrough.md (2026-08-09T...)
  用户阅读复杂源码时偏好角色地图、逐行中文注释和反事实表

- [reference] reference_dashboard.md (2026-07-20T...)
  线上延迟看板入口

6.2 第二级:让 sideQuery 最多选 5 个

ts 复制代码
export async function findRelevantMemories(
  query,
  memoryDir,
  signal,
  recentTools = [],
  alreadySurfaced = new Set(),
) {
  // ① 扫描 header,并排除本会话已经展示过的文件
  const memories = (await scanMemoryFiles(memoryDir, signal))
    .filter(m => !alreadySurfaced.has(m.filePath))

  if (memories.length === 0) return []

  // ② 把"用户问题 + 候选清单"交给一个短 sideQuery
  const selectedFilenames = await selectRelevantMemories(
    query,
    memories,
    signal,
    recentTools,
  )

  // ③ 只接受清单里真实存在的文件名
  return selectedFilenames
    .map(filename => byFilename.get(filename))
    .filter(Boolean)
    .map(m => ({ path: m.filePath, mtimeMs: m.mtimeMs }))
}

源码:findRelevantMemories.ts L39-L75

selector 的要求是:

text 复制代码
最多返回 5 个
只有"确定有帮助"才选择
不确定就不选
没有相关内容可以返回空数组

这不是传统 embedding Top-K,而是一个结构化 JSON side query。

6.3 读取正文时还有三层预算

text 复制代码
候选主题文件数:最多 5
每个文件:最多前 200 行
每个文件:最多 4096 bytes
整个会话已注入相关记忆:最多约 60 KB

对应常量和读取逻辑见 attachments.ts L269-L289attachments.ts L2279-L2321

超长文件不会整份丢弃,而是注入开头并告诉模型:

text 复制代码
这份记忆被截断了,如需完整内容请使用 Read 工具读取原文件。

6.4 为什么在 query() 一开始就启动

ts 复制代码
// query.ts 进入主循环前
using pendingMemoryPrefetch = startRelevantMemoryPrefetch(
  state.messages,
  state.toolUseContext,
)

源码:query.ts L300-L304

它会和主模型流式输出、工具执行并行运行。

6.5 为什么消费时"只看是否已完成,不等待"

ts 复制代码
if (
  pendingMemoryPrefetch &&
  pendingMemoryPrefetch.settledAt !== null &&
  pendingMemoryPrefetch.consumedOnIteration === -1
) {
  // ① 只在 promise 已经完成时读取
  const memoryAttachments = filterDuplicateMemoryAttachments(
    await pendingMemoryPrefetch.promise,
    toolUseContext.readFileState,
  )

  // ② 作为 attachment 加入当前消息流
  for (const memAttachment of memoryAttachments) {
    const msg = createAttachmentMessage(memAttachment)
    yield msg
    toolResults.push(msg)
  }

  // ③ 标记已消费,避免下一次循环重复注入
  pendingMemoryPrefetch.consumedOnIteration = turnCount - 1
}

源码:query.ts L1592-L1613

如果 selector 还没完成:

text 复制代码
本次 iteration 不等它
  ↓
主 Agent 继续工作
  ↓
下一次 query loop iteration 再检查

这叫 zero-wait consume:记忆召回不能成为首 token 延迟的硬依赖。

6.6 旧记忆的新鲜度警告

两天以上的记忆会带上类似提示:

text 复制代码
This memory is 47 days old.
Memories are point-in-time observations, not live state.
Verify against current code before asserting as fact.

源码:memoryAge.ts L22-L52

🔑 小白重点(第 6 章)

  • CC 用的是 Markdown header 检索 + LLM rerank,不是向量库。
  • description 写得越准确,未来召回质量越高。
  • 召回最多 5 个主题文件,且有行数、字节数、会话总量限制。
  • 召回异步预取,不让 Memory 搜索拖慢主循环。
  • "已经展示过"与"模型自己 Read 过"的文件都会去重。

第 7 章:Session Memory ------ 当前会话的滚动会议纪要

📄 文件:src/services/SessionMemory/sessionMemory.ts 第 134-180、272-375 行

📄 文件:src/services/SessionMemory/sessionMemoryUtils.ts 第 31-36 行

📄 文件:src/services/compact/sessionMemoryCompact.ts 第 514-620 行

🎬 对应 demo:当前对话很长,书记员定期更新 summary.md

Session Memory 和 Auto Memory 最大区别:

text 复制代码
Auto Memory:
  为未来其他会话保存长期事实

Session Memory:
  为当前这次长会话保存滚动工作状态

7.1 默认触发阈值

ts 复制代码
export const DEFAULT_SESSION_MEMORY_CONFIG = {
  // 上下文达到 10k token 才第一次创建会议纪要
  minimumMessageTokensToInit: 10_000,

  // 之后至少再增长 5k token 才更新
  minimumTokensBetweenUpdate: 5_000,

  // 通常还要求新增至少 3 次工具调用
  toolCallsBetweenUpdates: 3,
}

源码:sessionMemoryUtils.ts L31-L36

7.2 shouldExtractMemory

ts 复制代码
export function shouldExtractMemory(messages) {
  const currentTokenCount = tokenCountWithEstimation(messages)

  // ① 第一次没到 10k:不记
  if (!isSessionMemoryInitialized()) {
    if (!hasMetInitializationThreshold(currentTokenCount)) {
      return false
    }
    markSessionMemoryInitialized()
  }

  // ② 距上次是否增长至少 5k token
  const hasMetTokenThreshold =
    hasMetUpdateThreshold(currentTokenCount)

  // ③ 距上次是否至少发生 3 次工具调用
  const hasMetToolCallThreshold =
    countToolCallsSince(messages, lastMemoryMessageUuid) >=
    getToolCallsBetweenUpdates()

  // ④ 最后一轮没有工具调用,说明是自然对话断点
  const hasToolCallsInLastTurn =
    hasToolCallsInLastAssistantTurn(messages)

  // ⑤ token 门槛永远必须满足
  return (
    hasMetTokenThreshold &&
    (hasMetToolCallThreshold || !hasToolCallsInLastTurn)
  )
}

源码:sessionMemory.ts L134-L180

7.3 后台书记员

text 复制代码
post-sampling hook
  ↓
只允许主 REPL 线程
  ↓
sequential 防止两个书记员并发写
  ↓
读取/创建 summary.md
  ↓
fork session_memory Agent
  ↓
权限只允许 Edit 这一个 summary.md
  ↓
推进 lastSummarizedMessageId 书签

主流程见 sessionMemory.ts L272-L350

7.4 它为什么能帮助 compact

上下文快满时:

text 复制代码
trySessionMemoryCompaction()
  ↓
等待正在写的 summary.md,最多 15 秒
  ↓
读取 summary.md
  ↓
用 lastSummarizedMessageId 找到"纪要覆盖到哪"
  ↓
summary.md 代替旧消息
  +
保留书签之后的近期原始消息

源码:sessionMemoryCompact.ts L514-L620

拿不到有效 Session Memory 时,才降级到传统 LLM 现场压缩。

🔑 小白重点(第 7 章)

  • Session Memory 保存的内容可以包含当前任务进度;Auto Memory 反而明确禁止保存这种临时状态。
  • Session Memory 的目标是"压缩后还能无缝继续干活",不是建立用户长期画像。
  • lastSummarizedMessageId 是摘要覆盖边界,不是长期记忆索引。
  • 这就是为什么 Session Memory 和 Auto Memory 必须分成两个目录、两套 Prompt。

第 8 章:Agent Memory 与 Team Memory ------ 作用域扩展

8.1 Agent Memory:每类专家有自己的笔记

📄 文件:src/tools/AgentTool/agentMemory.ts 第 12-177 行

📄 文件:src/tools/AgentTool/loadAgentsDir.ts 第 593-674、713-746 行

自定义 Agent frontmatter 可以写:

yaml 复制代码
---
name: code-reviewer
memory: project
---

支持三种作用域:

scope 路径 含义
user ~/.claude/agent-memory/<agentType>/ 跨所有项目
project .claude/agent-memory/<agentType>/ 项目共享,可进版本控制
local .claude/agent-memory-local/<agentType>/ 当前项目、当前机器私有

路径分派:

ts 复制代码
export function getAgentMemoryDir(agentType, scope) {
  const dirName = sanitizeAgentTypeForPath(agentType)

  switch (scope) {
    case 'project':
      return join(getCwd(), '.claude', 'agent-memory', dirName) + sep
    case 'local':
      return getLocalAgentMemoryDir(dirName)
    case 'user':
      return join(getMemoryBaseDir(), 'agent-memory', dirName) + sep
  }
}

源码:agentMemory.ts L46-L65

Agent 声明 memory 后,加载器会做两件事:

ts 复制代码
// ① 即使 Agent 原 tools 没写,也补齐 Read/Edit/Write
if (isAutoMemoryEnabled() && memory && tools !== undefined) {
  tools = addMissingTools(tools, [
    FILE_WRITE_TOOL_NAME,
    FILE_EDIT_TOOL_NAME,
    FILE_READ_TOOL_NAME,
  ])
}

// ② 在 Agent 自己的 system prompt 后拼上专属 Memory Prompt
getSystemPrompt: () => {
  if (isAutoMemoryEnabled() && memory) {
    return systemPrompt + '\n\n' +
      loadAgentMemoryPrompt(agentType, memory)
  }
  return systemPrompt
}

源码:loadAgentsDir.ts L659-L674loadAgentsDir.ts L713-L746

它解决的问题是:

text 复制代码
主 Agent 的用户偏好
  不应该淹没
code-reviewer 对常见缺陷的专业积累

8.2 Team Memory:把主题文件同步给团队

📄 文件:src/memdir/teamMemPaths.ts 第 66-94 行

📄 文件:src/services/teamMemorySync/watcher.ts 第 231-352 行

📄 文件:src/services/teamMemorySync/index.ts 第 760-1188 行

Team Memory 位于 Auto Memory 子目录:

text 复制代码
<auto-memory>/
├── MEMORY.md             # 私有索引
├── feedback_user.md
└── team/
    ├── MEMORY.md         # 团队索引
    ├── project_release.md
    └── reference_dashboard.md

入口路径见 teamMemPaths.ts L66-L94

启动同步系统时:

ts 复制代码
export async function startTeamMemoryWatcher() {
  // ① build flag、功能开关、OAuth、GitHub repo 缺一不可
  if (!feature('TEAMMEM')) return
  if (!isTeamMemoryEnabled() || !isTeamMemorySyncAvailable()) return
  const repoSlug = await getGithubRepo()
  if (!repoSlug) return

  // ② 先从服务端拉取
  syncState = createSyncState()
  await pullTeamMemory(syncState)

  // ③ 再监听本地目录
  // 即使服务端当前为空也必须监听,避免首次写入无法上传
  await startFileWatcher(getTeamMemPath())
}

源码:watcher.ts L231-L305

上传采用:

text 复制代码
本地文件计算 hash
  ↓
只上传与 serverChecksums 不同的 delta
  ↓
服务端返回 412 冲突
  ↓
只拉最新 hashes,不拉全部正文
  ↓
重新计算更小的 delta
  ↓
重试

teamMemorySync/index.ts L869-L1138

安全措施包括:

  • 路径穿越校验;
  • symlink 逃逸校验;
  • 单文件大小限制;
  • 上传前 secret scan;
  • 检测到密钥的文件跳过同步;
  • OAuth 和 GitHub repo scope;
  • 冲突重试次数限制。

8.3 /memory 命令只是管理入口

/memory 不负责自动总结,它做的是:

text 复制代码
清除并重新预热 Memory 文件缓存
  ↓
展示 User / Project / Auto / Team / Agent Memory 选项
  ↓
必要时创建空文件
  ↓
用 $VISUAL / $EDITOR 打开

入口见 memory.tsx L21-L88,选项组装见 MemoryFileSelector.tsx L44-L152

🔑 小白重点(第 8 章)

  • Agent Memory 解决"不同专家各自积累什么"的隔离问题。
  • Team Memory 不是 Git 自动同步,而是本地目录 + 服务端 API + watcher。
  • Team Memory 冲突策略偏向保住当前本地编辑,不做复杂内容级自动合并。
  • /memory 是查看和编辑入口,不是 Memory 的核心运行时。

第 9 章:Memory、Compact、Context Collapse 到底什么关系

📄 文件:src/query.ts 第 365-467 行

你当前 IDE 打开的代码是:

ts 复制代码
if (feature('CONTEXT_COLLAPSE') && contextCollapse) {
  const collapseResult =
    await contextCollapse.applyCollapsesIfNeeded(
      messagesForQuery,
      toolUseContext,
      querySource,
    )
  messagesForQuery = collapseResult.messages
}

它处在 query 主循环的这个位置:

text 复制代码
原始 messages
  ↓
Tool Result Budget
  ↓
History Snip
  ↓
Microcompact
  ↓
Context Collapse        ← query.ts:440
  ↓
Auto Compact
  ├─ Session Memory Compact
  └─ 传统 LLM Compact
  ↓
调用主模型
  ↓
工具执行
  ↓
Relevant Memory 附件消费
  ↓
回合结束后 extractMemories

源码:query.ts L365-L467

四者的时间尺度

机制 处理什么 是否跨会话 是否改写 messages
Auto Memory 长期偏好和背景 通过 context/attachment 注入
Session Memory 当前会话工作纪要 通常服务当前会话 compact 时作为摘要
Context Collapse 当前历史中的局部阶段 是,做读时投影
Auto Compact 接近上下文上限的全局换代 是,产生新边界和摘要

为什么 Context Collapse 在 Auto Compact 前

源码注释已经给出答案:

text 复制代码
先尝试细粒度折叠
  ↓
如果已经降到 auto-compact 阈值以下
  ↓
就不必把整个历史压成一份大摘要

也就是:

能局部收纳就先局部收纳,实在装不下才整体换箱。

Memory 会不会被 compact 丢掉

分两种:

text 复制代码
CLAUDE.md / MEMORY.md:
  getUserContext 有独立加载与缓存
  compact 后会清理相关缓存并重新注入

relevant_memories attachment:
  属于消息历史的一部分
  compact 后旧 attachment 可能消失
  collectSurfacedMemories 因此自然重置,未来允许再次召回

这也是 attachments.ts L2244-L2265 特意扫描当前 messages,而不是维护永久全局 Set 的原因。

🔑 小白重点(第 9 章)

  • Auto Memory 解决"跨会话记得什么"。
  • Session Memory 解决"当前长会话压缩后如何继续"。
  • Context Collapse 解决"局部旧阶段如何折叠"。
  • Auto Compact 解决"整个上下文即将溢出"。
  • 它们不是竞争关系,而是不同时间尺度上的分层治理。

附:五套 Memory 对照表

维度 CLAUDE.md Auto Memory Session Memory Agent Memory Team Memory
核心语义 指令 长期经验 当前会话纪要 专家经验 团队知识
主要入口 getMemoryFiles loadMemoryPrompt post-sampling hook Agent frontmatter watcher + sync API
入口文件 CLAUDE.md MEMORY.md summary.md MEMORY.md team/MEMORY.md
详细正文 文件本身 独立主题文件 同一个结构化 summary 独立主题文件 独立主题文件
默认注入方式 eager 索引 eager / 正文按需 compact 时使用 Agent prompt 索引/正文按需
写入者 用户/仓库 主 Agent 或后台提取 Agent Session Memory fork 对应 Agent 主/后台 Agent
典型内容 测试命令、编码规范 用户偏好、项目背景 Current State、Worklog review 常见缺陷 发布约束、共享入口
是否允许临时任务状态 不建议 明确禁止 允许且重要 取决于 scope 规则 不建议
是否跨用户 项目文件可以 默认否 project scope 可以
过期风险 高,需验证 随会话更新 高,需同步

附:反事实表 ------ 少一层会发生什么

如果删掉...... 会发生什么 为什么现有设计要保留
MEMORY.md 索引 旧模式下模型不知道有哪些主题文件 用很小 token 成本提供导航
独立主题文件 索引无限膨胀,每次启动都加载全部正文 将"发现"和"读取"分离
frontmatter description selector 只能靠模糊文件名猜相关性 description 是轻量召回摘要
200 文件候选上限 目录越大,扫描和 selector prompt 越贵 对召回成本设硬上限
最多 5 个召回结果 一次普通问题可能注入几十份旧记忆 控制噪声和上下文占用
新鲜度提示 旧 file:line 会被模型当成当前事实 强迫模型回到代码验证
hasMemoryWritesSince 主 Agent 和后台 Agent 同时写重复记忆 两条写入路径按消息区间互斥
UUID 增量游标 后台每轮重新分析整个会话 只处理上次之后的新消息
pendingContext 合并 多轮快速结束会并发写同一目录 单写者 + 最新上下文补跑
权限沙箱 "整理记忆"的子 Agent 可能改项目代码 将副作用限制在 Memory 目录
Session Memory compact 时只能每次现场调用 LLM 总结 平时滚动记,临界点直接复用
canonical git root 每个 worktree 形成互相矛盾的记忆 同仓库共享同一知识空间
Team secret scan API key 可能随团队记忆上传 共享前阻断敏感信息

一页纸总结

text 复制代码
【启动】
isAutoMemoryEnabled
  ├─ env / --bare / remote persistent dir / settings
  └─ 默认开启

getAutoMemPath
  └─ ~/.claude/projects/<canonical-git-root>/memory/

【告诉模型规则】
constants/prompts.ts
  └─ systemPromptSection('memory', loadMemoryPrompt)
       ├─ 四种类型:user / feedback / project / reference
       ├─ 什么不保存
       ├─ 主题文件格式
       └─ MEMORY.md 只做索引

【加载】
getMemoryFiles
  ├─ Managed CLAUDE.md
  ├─ User CLAUDE.md
  ├─ Project CLAUDE.md / .claude/rules
  ├─ Local CLAUDE.local.md
  ├─ Auto Memory MEMORY.md
  └─ Team Memory MEMORY.md

getUserContext
  └─ getClaudeMds(filterInjectedMemoryFiles(...))

【写入】
用户明确说"记住"
  ├─ 路 A:主 Agent 直接 Write/Edit
  └─ 路 B:回合结束后 extractMemories 后台补记
       ├─ UUID 游标,只看增量
       ├─ 主 Agent 写过则跳过
       ├─ fork 复用 prompt cache
       ├─ 最多 5 turns
       └─ 写权限仅限 Memory 目录

【召回】
startRelevantMemoryPrefetch
  ├─ scanMemoryFiles:最多 200 文件,只读前 30 行 frontmatter
  ├─ sideQuery:最多选择 5 个
  ├─ 正文:每个最多 200 行 / 4096 bytes
  ├─ 会话总注入约 60 KB
  ├─ settled 才消费,不阻塞主循环
  └─ 旧记忆附带"请验证当前代码"提示

【当前会话连续性】
Session Memory
  ├─ 10k token 初始化
  ├─ 每增长 5k token 更新
  ├─ summary.md + lastSummarizedMessageId
  └─ compact 时优先拿它代替旧消息

【作用域扩展】
Agent Memory
  └─ user / project / local 三种 scope

Team Memory
  └─ team/ 目录 + pull/push + watcher + hash delta + secret scan

【和 query.ts:440 的关系】
History Snip
  → Microcompact
  → Context Collapse
  → Auto Compact
  → 主模型与工具
  → Relevant Memory 消费
  → 回合结束后长期记忆提取

一句话总结全篇

Claude Code Memory 是一套分层的文件型记忆系统:CLAUDE.md 管"必须遵守什么",Auto Memory 管"未来值得记住什么",Session Memory 管"本次长会话做到哪里",Agent Memory 管"某类专家学到了什么",Team Memory 管"团队共同知道什么";它通过短索引、frontmatter 召回、异步预取、权限沙箱和过期校验,把持久化能力控制在可解释、可编辑、可审计的 Markdown 文件上。

相关推荐
众人皆醒我独醉2 小时前
训练加速实战:Flash Attention、Gradient Checkpointing 与数据流水线
后端·面试·gpu
王林不想说话2 小时前
ES6 到 ES2026 全面进阶指南:新特性、原理、示例与工程落地
前端·javascript·面试
demo007x2 小时前
CoT(Chain-of-Thought)
程序员·llm·agent
黄敬峰2 小时前
TypeScript 必考题:type 和 interface 的区别一次搞懂
面试
京东云开发者2 小时前
【全栈实践】第一个 AI Agent 项目:从零搭建 AI 音频创作助手(入门篇)
typescript·agent·vuex
阿里云云原生3 小时前
智能体构建与进化——Agent 开源开发者沙龙·广州站精彩回顾 & PPT 下载
云原生·agent
Joy T3 小时前
Agent 开源项目全景解析(下):LlamaIndex、Dify、FastGPT 与真实工程选型
langchain·开源·框架·agent·springai·langgraph·mcp
Dr.kangder3 小时前
嵌入式面试总结(八)——大小端
嵌入式硬件·面试·职场和发展·架构·嵌入式
DigitalOcean4 小时前
Qwen 3.8 已上线 DigitalOcean 推理云平台
agent