Claude Code 源码解剖

Claude Code 的记忆系统

从 Memdir、Extract Memories、Session Memory、Transcript Persistence、Agent Memory、Team Memory 和 Auto-Dream 拆解 Claude Code 如何把会话信号转成可预算、可整理、可共享但受控的跨会话记忆。

第 17 章 28 分钟

Claude Code 的记忆系统

这一章要解决的问题是:Claude Code 如何从一次次临时对话里提取长期有用的信息,又如何避免“什么都记住”导致上下文污染?

源码里的答案不是单个 MEMORY.md。它是一套分层记忆系统:Memdir 负责长期索引和主题文件,Extract Memories 在每轮结束后用 Fork Agent 提取信号,Session Memory 为自动压缩维护会话摘要,Transcript Persistence 记录完整 JSONL 会话,Agent Memory 让子 Agent 有自己的长期知识,Auto-Dream 定期整理和修剪,Team Memory 则把共享知识放到独立边界里。

核心直觉

Claude Code 的 Memory 不是“给模型加一个数据库”,而是把记忆拆成不同频率、不同作用域、不同写入权限的文件系统层。高频提取负责不漏信号,低频 Dream 负责去噪和合并,注入上下文时再用预算限制防止记忆反噬。

先看源码入口

源码定位关键符号负责什么
memdir/paths.tsgetAutoMemPath()getAutoMemDailyLogPath()解析项目记忆目录和 KAIROS 日志路径
memdir/memdir.tsENTRYPOINT_NAMEMAX_ENTRYPOINT_LINESMAX_ENTRYPOINT_BYTESbuildMemoryPrompt()MEMORY.md 索引读取、截断、注入系统提示词
memdir/memoryScan.tsMAX_MEMORY_FILESFRONTMATTER_MAX_LINES扫描主题文件 frontmatter,控制文件数量和读取成本
query/stopHooks.tsexecuteExtractMemories()executeAutoDream()查询结束后触发自动提取和 Dream
services/extractMemories/extractMemories.tshasMemoryWritesSince()createAutoMemCanUseTool()自动提取记忆、互斥主 Agent 写入、限制 Fork Agent 工具权限
services/extractMemories/prompts.ts提取 prompt指示 Fork Agent 高效读写记忆文件
services/SessionMemory/sessionMemory.tsregisterPostSamplingHook()采样后维护滚动会话摘要
services/SessionMemory/sessionMemoryUtils.tsDEFAULT_SESSION_MEMORY_CONFIG10K 初始化、5K 更新、3 次工具调用阈值
utils/sessionStorage.tsJSONL 会话存储持久化完整消息、压缩边界、文件快照和恢复状态
tools/AgentTool/agentMemory.tsAgentMemoryScope子 Agent 的 user/project/local 三作用域记忆
tools/AgentTool/agentMemorySnapshot.tscheckAgentMemorySnapshot()project 作用域记忆的 VCS 快照同步
services/autoDream/autoDream.tsisGateOpen()runForkedAgent()自动记忆整合的门控和执行
services/autoDream/consolidationLock.ts.consolidate-locktryAcquireConsolidationLock()Dream 并发锁和 last consolidated 时间
tasks/DreamTask/DreamTask.tsDreamTaskStatekill()Dream 后台任务 UI、进度和终止回滚
memdir/teamMemPaths.tsteam memory path 验证团队共享记忆的路径安全

这组入口说明:Memory 是一套从写入、扫描、注入、压缩、整理到共享的完整链路。

总流程图

flowchart TD A["用户会话推进"] --> B["Transcript Persistence<br/>JSONL append"] A --> C["Post Sampling Hook<br/>Session Memory"] C --> D{"token/tool 阈值满足?"} D -->|是| E["更新 session-memory/summary.md"] D -->|否| F["跳过"] A --> G["Stop Hooks"] G --> H{"EXTRACT_MEMORIES<br/>主 Agent + 模式开启?"} H -->|是| I["executeExtractMemories()<br/>Fork Agent"] H -->|否| J["跳过提取"] I --> K{"主 Agent 本轮写过 memory?"} K -->|是| L["跳过,推进 cursor"] K -->|否| M["createAutoMemCanUseTool()<br/>只读 + memoryDir 写入"] M --> N["写 MEMORY.md / 主题文件<br/>或 KAIROS daily logs"] G --> O["executeAutoDream()"] O --> P{"四层门控<br/>master/time/session/lock"} P -->|通过| Q["runForkedAgent(querySource=auto_dream)"] Q --> R["Orient / Gather / Consolidate / Prune"] R --> S["更新主题文件和 MEMORY.md"] P -->|不通过| T["静默跳过或记录原因"] N --> U["下次会话 buildMemoryPrompt()"] S --> U E --> V["自动压缩时注入 session summary"]

这张图有三个重点:

  • 长期记忆和会话摘要是两条不同链路。
  • 自动提取是高频增量,Auto-Dream 是低频整理。
  • 所有写入都要经过目录和权限约束,不是任意文件编辑。

记忆目录:长期记忆的文件系统骨架

Memdir 是长期记忆的存储层。目录位置由 getAutoMemPath() 解析。

export const getAutoMemPath = memoize(
  (): string => {
    const override = getAutoMemPathOverride() ?? getAutoMemPathSetting()
    if (override) {
      return override
    }
    const projectsDir = join(getMemoryBaseDir(), 'projects')
    return (
      join(projectsDir, sanitizePath(getAutoMemBase()), AUTO_MEM_DIRNAME) + sep
    ).normalize('NFC')
  },
  () => getProjectRoot(),
)

这段代码证明:记忆路径有三级优先级。

优先级来源说明
1CLAUDE_COWORK_MEMORY_PATH_OVERRIDECowork 空间级覆盖
2autoMemoryDirectory 设置只接受受信任来源,排除 projectSettings
3默认项目路径~/.claude/projects/@@INLINE_0@@/memory/

排除 projectSettings 很关键。恶意仓库不能通过项目配置把自动记忆重定向到敏感目录。getAutoMemBase() 使用 findCanonicalGitRoot(),意味着同一个仓库的不同 worktree 共享项目记忆。这符合“记忆属于项目,而不是某个临时工作目录”的判断。

记忆索引是入口,不是所有记忆

MEMORY.md 是索引入口,主题内容分散在独立 Markdown 文件里。源码常量给它设置了预算。

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

这段代码证明:长期记忆注入上下文前会被截断。先按 200 行截,再按 25KB 截;如果字节截断落在行中间,会退回到最后一个换行符。截断后会追加 warning,提示模型把细节移入主题文件。

这个 warning 是一个自修复信号:下次 Dream 或手动整理时,模型知道索引已经过胖,需要压缩索引而不是继续追加。

主题文件用 frontmatter 分类

主题文件大致这样:

---
name: 记忆名称
description: 一行描述
type: user | feedback | project | reference
---

记忆内容...

四类记忆的作用不同:

类型内容对 Agent 行为的影响
user用户角色、偏好、知识水平调整沟通风格、解释深度和默认建议
feedback用户对 Agent 行为的纠正直接改变下次执行策略,价值最高
project项目背景、目标、进行中的工作帮助跨会话接续,但需要定期清理
reference外部系统指针作为快捷入口,不应写成长篇知识库

memoryScan.ts 只读每个文件前 30 行解析 frontmatter。

const MAX_MEMORY_FILES = 200
const FRONTMATTER_MAX_LINES = 30

这段代码证明:扫描阶段只做轻量索引,不把所有主题文件全文读进来。最多 200 个文件,按修改时间倒序保留,让长期未更新的记忆自然降权。

长期助手日志模式:高频写入先追加

在 KAIROS 长期助手模式下,记忆写入可以走每日日志。

export function getAutoMemDailyLogPath(date: Date = new Date()): string {
  const yyyy = date.getFullYear().toString()
  const mm = (date.getMonth() + 1).toString().padStart(2, '0')
  const dd = date.getDate().toString().padStart(2, '0')
  return join(getAutoMemPath(), 'logs', yyyy, mm, `${yyyy}-${mm}-${dd}.md`)
}

这段代码证明:KAIROS 里的原始记忆信号会落到 memory/logs/YYYY/MM/YYYY-MM-DD.md。append-only 日志避免长期运行时频繁重写同一个主题文件,后续再由 Dream 合并、去重和整理。

这是一种很实用的分层:高频阶段容忍粗糙,低频阶段追求质量。

自动提取:每轮结束后的后台提取

自动提取在 stopHooks.ts 触发。

if (
  feature('EXTRACT_MEMORIES') &&
  !toolUseContext.agentId &&
  isExtractModeActive()
) {
  void extractMemoriesModule!.executeExtractMemories(
    stopHookContext,
    toolUseContext.appendSystemMessage,
  )
}
if (!toolUseContext.agentId) {
  void executeAutoDream(stopHookContext, toolUseContext.appendSystemMessage)
}

这段代码证明两个关键约束:

  • !toolUseContext.agentId 排除了子 Agent,只让主 Agent 触发自动提取和 Dream。
  • void 表示 fire-and-forget,不阻塞下一轮用户交互。

频率由 feature flag 节流

不是每轮都必须提取。源码里有 turnsSinceLastExtractiontengu_bramble_lintel 控制。

if (!isTrailingRun) {
  turnsSinceLastExtraction++
  if (
    turnsSinceLastExtraction <
    (getFeatureValue_CACHED_MAY_BE_STALE('tengu_bramble_lintel', null) ?? 1)
  ) {
    return
  }
}
turnsSinceLastExtraction = 0

这段代码证明:自动提取有可调频率。默认 1 表示每轮都可运行;远程配置可以提高间隔,降低成本。

与主 Agent 写入互斥

如果用户明确要求“记住这个”,主 Agent 可能自己用 Edit/Write 写记忆文件。此时 Fork 提取器必须避让。

function hasMemoryWritesSince(
  messages: Message[],
  sinceUuid: string | undefined,
): boolean {
  // 检查 assistant 消息里是否有 Edit/Write 指向 autoMemPath
}

这段代码证明:自动提取不是盲目追加。它会检查 cursor 之后是否已有主 Agent 的记忆写入;如果有,就跳过本轮提取并推进 cursor,避免两个 Agent 同时改同一片记忆。

权限隔离:只允许写 memoryDir

提取器通过 Fork Agent 执行,但它的工具权限被 createAutoMemCanUseTool() 严格限制。

工具权限
Read / Grep / Glob允许,用于读记忆目录和必要上下文
Bash只允许只读命令,如 lsfindgrepcat
Edit / Write仅允许目标路径在 memoryDir 内,并通过 isAutoMemPath() 验证
MCP、Agent、写入式 Bash 等拒绝

这说明自动提取虽然使用模型推理,但副作用范围被压到记忆目录。它不能借机改项目源码,也不能调用外部 MCP。

提取 prompt 要求少调查、快写入

提取 prompt 明确告诉 Fork Agent:预算有限,Edit 前必须先 Read,高效策略是第一轮并行读取可能更新的文件,第二轮并行写入。

turn 1: issue all Read calls in parallel for every file you might update;
turn 2: issue all Write/Edit calls in parallel.

这段 prompt 证明:记忆提取不是一次“重新研究项目”的机会。原文还明确禁止调查或验证,因为 Fork Agent 已继承父对话上下文和 Prompt Cache;它要做的是提取,而不是重新探索。

maxTurns: 5 则是硬上限,防止记忆提取陷入长循环。

会话记忆:给压缩系统看的会话摘要

Session Memory 和 Extract Memories 容易混淆。前者不是跨会话长期记忆,而是当前会话的滚动摘要,主要消费者是自动压缩。

默认阈值在 sessionMemoryUtils.ts

export const DEFAULT_SESSION_MEMORY_CONFIG: SessionMemoryConfig = {
  minimumMessageTokensToInit: 10000,
  minimumTokensBetweenUpdate: 5000,
  toolCallsBetweenUpdates: 3,
}

这段代码证明:Session Memory 不会在短对话里启动。首次要到 10K token;之后每 5K token 才考虑更新,并且还要求至少 3 次工具调用,或者最后一轮 assistant 没有工具调用,形成自然对话断点。

摘要结构固定:

# Session Title
# Current State
# Task specification
# Files and Functions
# Workflow
# Errors & Corrections
# Codebase and System Documentation
# Learnings
# Key results
# Worklog

每个章节有长度限制,总文件不超过约 12,000 token。压缩系统触发时会把 summary.md 注入上下文,帮助压缩器知道哪些状态必须保留。

维度Session MemoryExtract Memories
范围当前会话跨会话
存储位置~/.claude/projects/@@INLINE_0@@/@@INLINE_1@@/session-memory/~/.claude/projects/@@INLINE_0@@/memory/
触发token 阈值 + 工具调用阈值Stop Hooks,每轮或每 N 轮
消费者自动压缩下次会话的系统提示词
内容结构固定章节摘要主题文件 + MEMORY.md 索引

容易误解

Session Memory 不是“Claude 记住用户偏好”的地方。它是会话内状态摘要,帮长任务压缩后继续执行。用户偏好、项目规则和反馈更应该进入 Memdir 的主题文件。

会话持久化:完整会话的记录

utils/sessionStorage.ts 负责把会话写成 JSONL。每条消息一行 JSON,追加到:

~/.claude/projects/<root>/<session-id>.jsonl

选择 JSONL 的工程含义很清楚:追加便宜,不需要读完整文件再重写。

除了普通消息,JSONL 还会记录特殊条目:

条目用途
file_history_snapshot保存文件历史快照,压缩后恢复文件状态
attribution_snapshot记录每个文件修改来源
context_collapse_snapshot标记压缩边界和保留消息
content_replacementREPL 输出截断和替换记录

恢复时,sessionStorage.ts 会解析 JSONL、按 uuid / parentUuid 重建消息树、应用压缩边界、恢复文件历史快照。这说明 Claude Code 的“继续会话”不是只把最后几条消息读回来,而是把消息链、压缩边界和文件状态一起恢复。

子代理记忆:子 Agent 也有记忆作用域

子 Agent 需要自己的长期知识。例如代码审查 Agent 可能要记住团队偏好的 review 风格,测试 Agent 可能要记住项目测试命令。

agentMemory.ts 定义了三个作用域:

export type AgentMemoryScope = 'user' | 'project' | 'local'
作用域路径是否适合提交用途
user~/.claude/agent-memory/@@INLINE_0@@/跨项目的用户级偏好
project@@INLINE_0@@/.claude/agent-memory/@@INLINE_1@@/团队共享的项目知识
local@@INLINE_0@@/.claude/agent-memory-local/@@INLINE_1@@/本机特定配置

每个作用域都有独立 MEMORY.md 和主题文件,并复用 Memdir 的 buildMemoryPrompt()。这证明子 Agent Memory 不是另一个格式,而是在不同作用域下复用同一套记忆注入机制。

版本控制快照:共享项目记忆但不覆盖本地

project 作用域想被团队共享,但 .claude/agent-memory/ 可能在 .gitignore 中。源码用快照目录解决。

export function getSnapshotDirForAgent(agentType: string): string {
  return join(getCwd(), '.claude', SNAPSHOT_BASE, agentType)
}

checkAgentMemorySnapshot() 返回三种动作:

export async function checkAgentMemorySnapshot(
  agentType: string,
  scope: AgentMemoryScope,
): Promise<{
  action: 'none' | 'initialize' | 'prompt-update'
  snapshotTimestamp?: string
}> {
  // 无快照 -> none
  // 无本地记忆 -> initialize
  // 快照更新 -> prompt-update
}

这段代码证明:如果本地没有记忆,可以直接 initialize;如果本地已有记忆而快照更新,系统不会自动覆盖,而是提示模型做合并。这保护了本地定制。

自动整理:低频全局整理

Auto-Dream 是记忆系统的“睡眠阶段”。它不是每轮运行,而是通过四层门控。

第一层:Master Gate

function isGateOpen(): boolean {
  if (getKairosActive()) return false
  if (getIsRemoteMode()) return false
  if (!isAutoMemoryEnabled()) return false
  return isAutoDreamEnabled()
}

这段代码证明:Auto-Dream 在 KAIROS 模式、远程模式、自动记忆关闭、Dream 功能关闭时都不运行。v2.1.88 中 KAIROS 有独立 dream skill,所以这里排除;后续版本又演进出定时 Kairos Dream。

第二层:Time Gate

const hoursSince = (Date.now() - lastAt) / 3_600_000
if (!force && hoursSince < cfg.minHours) return

默认至少 24 小时。lastAt 来自锁文件 mtime,一次 stat 就能读到上次整理时间。

第三层:Session Gate

sessionIds = sessionIds.filter(id => id !== currentSession)
if (!force && sessionIds.length < cfg.minSessions) return

默认至少 5 个新会话。当前会话被排除,因为它总是最新,不能用来证明有足够的新材料。扫描还有 10 分钟冷却,防止每轮重复扫会话目录。

第四层:Lock Gate

并发锁在 .consolidate-lock

const LOCK_FILE = '.consolidate-lock'
const HOLDER_STALE_MS = 60 * 60 * 1000

这个锁文件有双重语义:

文件属性语义
mtime上次成功整合时间
文件内容当前持有者 PID

获取锁流程是:读 mtime 和 PID,如果 PID 存活且未过期则放弃;如果 PID 死亡或过期则回收;写入自己的 PID;重新读取验证 PID 是否仍是自己。

export async function tryAcquireConsolidationLock(): Promise<number | null> {
  await writeFile(path, String(process.pid))
  let verify: string
  try {
    verify = await readFile(path, 'utf8')
  } catch { return null }
  if (parseInt(verify.trim(), 10) !== process.pid) return null
  return mtimeMs ?? 0
}

这段代码证明:锁获取考虑了两个进程同时回收陈旧锁的竞态。后写入者赢,前写入者验证失败后退出。

失败或用户 kill 时,rollbackConsolidationLock(priorMtime) 会恢复 mtime;如果之前没有锁文件则删除。这避免失败的 Dream 把“上次整理时间”推进,导致之后长时间不再触发。

整理任务的分叉 Agent 约束

Auto-Dream 通过 Fork Agent 执行。

const result = await runForkedAgent({
  promptMessages: [createUserMessage({ content: prompt })],
  cacheSafeParams: createCacheSafeParams(context),
  canUseTool: createAutoMemCanUseTool(memoryRoot),
  querySource: 'auto_dream',
  forkLabel: 'auto_dream',
  skipTranscript: true,
  overrides: { abortController },
  onMessage: makeDreamProgressWatcher(taskId, setAppState),
})

这段代码证明:

  • cacheSafeParams 继承父级 Prompt Cache,降低整理成本。
  • canUseTool 复用自动提取的权限函数,只能读必要内容并写 memory root。
  • querySource: &#39;auto_dream&#39;forkLabel 让事件和任务归属清晰。
  • skipTranscript: true 避免后台整理污染用户会话记录。
  • onMessage 把进度接入 DreamTask UI。

Dream prompt 分成四阶段:

阶段行为
Orientls 记忆目录、读 MEMORY.md、浏览主题文件
Gather搜索日志和会话记录,寻找新信号
Consolidate合并到现有文件、消除矛盾、相对日期转绝对日期
Prune & Index修剪重复和过时条目,保持 MEMORY.md 在 200 行 / 25KB 内

提示词强调“合并优于创建”和“修正优于保留”。这说明 Dream 的主要职责不是多写,而是降低熵。

整理任务状态:后台任务可以被用户杀掉

Dream 被暴露成任务 UI。状态结构大致如下:

export type DreamTaskState = TaskStateBase & {
  type: 'dream'
  phase: DreamPhase
  sessionsReviewing: number
  filesTouched: string[]
  turns: DreamTurn[]
  abortController?: AbortController
  priorMtime: number
}

这段结构证明 Dream 不是隐藏后台线程。UI 可以展示阶段、处理会话数、触碰文件、轮次和 abort controller。

kill() 会中止 Fork Agent,并回滚锁文件 mtime。

async kill(taskId, setAppState) {
  updateTaskState<DreamTaskState>(taskId, setAppState, task => {
    task.abortController?.abort()
    priorMtime = task.priorMtime
    return { ...task, status: 'killed' }
  })
  if (priorMtime !== undefined) {
    await rollbackConsolidationLock(priorMtime)
  }
}

这段代码证明:用户终止 Dream 不只是停任务,还要修复门控状态,确保下次有机会重试。

团队记忆:共享记忆的边界

Teams 章节里提到,团队记忆位于:

~/.claude/projects/{project}/memory/team/MEMORY.md

它和个人记忆独立。v2.1.91 的事件信号出现了 tengu_team_mem_* 系列,例如 sync_pullsync_pushpush_suppressedsecret_skippedentries_capped。这些事件说明团队记忆从实验走向使用,并且有同步、敏感内容跳过、写入抑制和容量上限。

团队记忆最重要的边界是:个人记忆不能被 Dream 自动提升成 team memory。v2.1.100 bundle 中的规则明确要求不要在 dream 期间把 personal memories promote 到 team/,这是用户通过 /remember 等动作才应明确选择的共享级别。

这条边界很重要。共享记忆一旦写错,影响的不只是当前用户,而是整个团队的 Agent 行为。

版本演进:Memory 在变得更可控

原文后续版本信号里有几个值得关注的变化。

版本信号推断行为工程意义
tengu_memory_toggled会话中动态启用或禁用跨会话记忆用户对 Memory 有运行时开关
tengu_extract_memories_skipped_no_prose无散文内容时跳过提取避免纯代码、JSON、工具结果触发低质量记忆
tengu_team_mem_*团队记忆同步、敏感过滤、容量控制Team Memory 从实验进入治理
tengu_auto_dream_skipped记录 sessions 或 lock 等跳过原因Dream 从静默门控走向可观测
tengu_kairos_dreamKAIROS 模式后台定时 Dream长期助手从会话触发扩展到 cron

v2.1.100 的 Kairos Dream 还用随机时间分散执行:

function P_A() {
  let q = Math.floor(Math.random() * 360)
  return `${q % 60} ${Math.floor(q / 60)} * * *`
}

这段代码证明:定时 Dream 随机落在 0:00 到 5:59 之间。夜间执行降低对用户活跃会话的影响,随机偏移避免大量用户同一时刻触发。

状态和数据结构

状态或结构关键字段影响
MEMORY.md200 行 / 25KB长期记忆入口,控制注入预算
主题文件 frontmatternamedescriptiontype让扫描器快速判断内容类型和相关性
getAutoMemPath()override / setting / default控制记忆根目录,排除不可信项目配置
Extract cursorsinceUuid避免重复提取已处理消息
createAutoMemCanUseTool()tool name + path check限制 Fork Agent 只能写记忆目录
Session Memory config10K / 5K / 3 calls避免短会话和密集工具调用中频繁摘要
JSONL transcriptuuid / parentUuid / snapshots支持恢复消息树、压缩边界和文件状态
AgentMemoryScopeuser / project / local子 Agent 记忆的共享范围
.consolidate-lockmtime + PID同时表示上次整理时间和当前锁持有者
DreamTaskStatephasefilesTouchedpriorMtime后台整理可观察、可终止、可回滚
Team Memorymemory/team/团队共享知识,独立于个人记忆

设计取舍

第一,长期记忆不是全文注入。 MEMORY.md 是入口索引,主题文件按需组织。200 行和 25KB 限制说明记忆必须服从上下文预算。

第二,高频提取和低频整理分离。 Extract Memories 容忍“先记下来”,Auto-Dream 负责合并、修剪和纠错。一个追求召回,一个追求质量。

第三,后台记忆用 Fork Agent,但权限极窄。 自动提取和 Dream 都需要模型推理,但只能读必要内容、写记忆目录,不能任意改项目文件。

第四,会话摘要和跨会话记忆分离。 Session Memory 是压缩辅助信号,Memdir 是长期行为信号。把二者混在一起会导致短期工作现场污染长期偏好。

第五,共享记忆必须显式。 Team Memory 与个人 Memory 分开,Dream 不能擅自把个人记忆提升到 team。共享范围是权限边界,不只是文件夹名。

第六,可观测性逐步增强。 从静默跳过到 tengu_auto_dream_skipped、从始终提取到 no-prose skip,Memory 系统正在从“自动学习”变成“可解释地学习”。

读源码抓手

读源码抓手

读 Memory 不要先问“记忆内容长什么样”,先追三条链:怎么确定路径、什么时候写入、什么时候注入。

建议路线:

  • 先看 memdir/paths.tsgetAutoMemPath(),确认记忆目录的信任边界。
  • 再看 memdir/memdir.ts 的入口截断和 buildMemoryPrompt(),理解长期记忆怎么进入系统提示词。
  • 接着读 query/stopHooks.tsservices/extractMemories/extractMemories.ts,看自动提取的触发、节流、互斥和权限函数。
  • 然后读 services/SessionMemory/sessionMemory.ts,对比它和 Extract Memories 的触发条件与消费者。
  • 再读 utils/sessionStorage.ts 的 JSONL 条目类型,理解恢复会话为什么能带回压缩边界和文件状态。
  • 接着读 tools/AgentTool/agentMemory.tsagentMemorySnapshot.ts,看子 Agent 的三作用域和 VCS 快照。
  • 最后读 services/autoDream/autoDream.tsconsolidationLock.tstasks/DreamTask/DreamTask.ts,把四层门控、Fork 执行和 UI 终止串起来。

小结

小结

- Claude Code 的 Memory 是分层文件系统,不是一个无限增长的向量库或单个 Markdown。 - MEMORY.md 只是索引入口,200 行和 25KB 限制保证长期记忆不会吞掉上下文。 - Extract Memories 用 Fork Agent 高频提取,但通过工具权限和主 Agent 写入互斥限制副作用。 - Session Memory 服务于压缩,Memdir 服务于跨会话行为,两者不能混为一谈。 - Auto-Dream 用时间、会话数和 PID 锁控制低频整理,并把失败或 kill 后的状态回滚。 - Team Memory 是共享边界,不能由后台 Dream 擅自从个人记忆提升。