# 记忆系统完全解析

> **源码版本**: Claude Code 2.1.88（社区公开流传源码）
> **分析深度**: 逐文件、逐函数级别
> **涉及文件**: 约 30 个核心文件，横跨 `src/memdir/`、`src/services/`、`src/utils/`、`src/components/memory/`

---

## 💡 通俗理解

想象你有一位极其认真的私人秘书。每次你们开会，秘书都会做三件事：

1. **会前翻看笔记本** — 开会之前，秘书会把以前的笔记翻出来看看，回忆你的偏好、上次说过的事、还没做完的项目。这就是**记忆加载**。
2. **会中默默记录** — 聊天进行时，秘书在旁边悄悄记要点：你纠正过的错误、你提到的新偏好、重要的项目决策。但秘书不会什么鸡毛蒜皮都记 — 只记那些"下次开会还有用"的事。这就是**记忆提取（extractMemories）**。
3. **周末整理归档** — 每隔一段时间，秘书会把散乱的速记整理成分类笔记：一份"老板喜好"、一份"项目进展"、一份"重要参考"。这就是**记忆整合（Dream consolidation）**。

Claude Code 的记忆系统就是这位秘书的完整工作流程。

### 🌍 行业背景

跨会话记忆是 AI 编程工具的差异化竞争点之一。各工具的记忆能力差距悬殊：

- **Cursor**：支持 `.cursorrules` 项目级指令文件（类似 CLAUDE.md），以及 Notepad 功能存储会话间的笔记。但没有自动记忆提取——用户必须手动维护上下文。2025 年推出的 Memory 功能支持自动记住用户偏好，但粒度远不及 Claude Code 以 Auto Memory 为核心的多层记忆体系。
- **Windsurf**：支持 `.windsurfrules` 指令文件和 Cascade Memory（自动学习用户偏好），但记忆结构较为扁平，没有类型分类学（user/feedback/project/reference）的区分。
- **GitHub Copilot**：通过 `.github/copilot-instructions.md` 支持项目级指令，但没有跨会话持久记忆或自动记忆提取能力。
- **Aider**：支持 `.aider.conf.yml` 配置和 `--read` 加载上下文文件，但不具备自动记忆提取或整合（Dream）能力。所有上下文管理需要用户手动操作。
- **Codex（OpenAI）**：支持 `AGENTS.md` 指令文件和开源技能库（Skills Library），类似 CLAUDE.md 的概念，但没有自动记忆或跨会话持久化。
- **OpenCode**：支持通过 YAML 格式深度定制子智能体的执行逻辑和系统提示词，声明式插件架构管理 MCP 与本地工具，但同样没有 Claude Code 级别的自动记忆提取能力。

Claude Code 的记忆系统在终端类 AI 编程工具中覆盖面较为完整：以 Auto Memory 为核心的多存储体系、自动后台提取、Dream 整合、相关性检索（Sonnet 侧查询）、团队共享记忆。这种设计的代价是系统复杂度高、API 调用成本增加，但换来了显著更强的上下文连续性（具体对比见 §10.4，仅基于公开资料未逐行核实各家源码）。

> 📚 **学术理论视角：Tulving 记忆框架**
>
> 认知心理学家 Endel Tulving 在 1972 年提出的三类记忆系统，作为**类比视角**可以与 Claude Code 的记忆架构做粗略对应（源码中没有直接引用 Tulving 的证据，此处仅为读者理解便利）：
>
> | Tulving 记忆类型 | Claude Code 对应实现 | 说明 |
> |---|---|---|
> | **情境记忆**（Episodic）| JSONL 对话存储 + SessionMemory 实时蒸馏 | 记录"发生了什么"——每次对话的完整上下文，以及 Session Memory 从中提取的关键摘要 |
> | **语义记忆**（Semantic）| fork 子 Agent (`extractMemories`) 提取 + autoDream 后台整合 | 从具体经历中抽象出一般性知识——后台代理从对话中提取可复用的事实和模式，Dream 系统进一步整合去重 |
> | **程序化记忆**（Procedural）| `feedback` 记忆类型（正负反馈兼顾）| 记录"怎么做"——用户的纠正和确认塑造行为模式，让 Agent 学会特定用户的工作方式 |
>
> 值得注意的是，多数 AI 记忆系统只考虑负反馈（纠错），而 Claude Code 的 `feedback` 类型明确要求同时记录正向确认（"对，就这样做"）。这防止了 Agent 在反复纠正中变得过于保守——一个在人类记忆研究中也被反复验证的现象。

---

## 1. 架构总览

Claude Code 的记忆系统并不是一个单一模块，而是由**一个核心系统加四个辅助/特化存储**组成的复合体系。

> **重要澄清**：以下五个子系统之间**不存在分层依赖关系**——它们各自独立工作，不是"第一层调用第二层、第三层依赖第四层"的堆叠结构，更像是同一个办公室里各管一摊的几位同事。**Auto Memory 是整个记忆系统的核心**——它拥有最精密的提取管道、类型分类学、Dream 整合和相关性检索。其余四个子系统各自独立运作，解决特定场景的存储需求：CLAUDE.md 是用户手动管理的配置层，Session Memory 是单会话压缩辅助机制，Agent Memory 是 AgentTool 的附属存储，Team Memory 只是 Auto Memory 目录下的 `team/` 子文件夹。

| 子系统 | 名称 | 存储位置 | 生命周期 | 核心文件 |
|:---:|:---:|:---:|:---:|:---:|
| **核心** | Auto Memory (memdir) | `~/.claude/projects/<slug>/memory/` | 永久（自动管理） | `src/memdir/memdir.ts` |
| 配置层 | CLAUDE.md 指令文件 | 项目根目录/`~/.claude/` | 永久（用户手动管理） | `src/utils/claudemd.ts` |
| 辅助 | Session Memory | `~/.claude/session-memory/` | 单次会话 | `src/services/SessionMemory/` |
| 辅助 | Agent Memory | `~/.claude/agent-memory/` 或 `.claude/agent-memory/` | 永久（Agent 级别） | `src/tools/AgentTool/agentMemory.ts` |
| 辅助 | Team Memory | `<memdir>/team/` | 永久（团队共享） | `src/memdir/teamMemPaths.ts` |

### 为什么必须分离？

五个子系统各自独立不是偶然——如果混在一起会出三类严重问题：

1. **规则与事实互相污染**。CLAUDE.md 里的硬约束（"绝不删除 .env 文件"）和 Auto Memory 里的可回忆背景（"上次删过一次 .env 导致事故"）混在同一个存储中，模型分不清哪些是**必须遵守的规则**，哪些是**仅供参考的历史**。CLAUDE.md 的本质是**指令记忆**（"应该怎样做"），Auto Memory 的本质是**事实记忆**（"曾经发生了什么"）——两者的语义完全不同，不应该混入同一个读取管道。

2. **会话续航与长期沉淀打架**。Session Memory 的工作笔记（"用户刚才提到想重构 auth 模块"）如果被写入长期记忆，会产生大量过时垃圾——每次重构完，这条笔记就变成错误信息。Session Memory 存在就是为了在当前会话中增强续航能力，会话结束就该丢弃。

3. **共享边界变危险**。个人 Auto Memory 中可能包含敏感信息（API key 片段、私有路径），如果不和 Team Memory 做物理隔离，`teamMemorySync` 上传时可能泄漏不该同步的内容——这也是为什么 Team Memory 有独立的 secret scanner（`secretScanner.ts`）的原因。

> 💡 **通俗理解**：这就像你不会把日记（私人事实）、公司规章（制度规则）、今天的 TODO（临时笔记）和团队共享文档（协作资料）全写在同一本笔记本上——混在一起你根本分不清哪些该给别人看、哪些过期了、哪些是必须遵守的。

### 1.1 加载优先级

CLAUDE.md 指令文件的加载顺序有严格规定（见 `src/utils/claudemd.ts` 顶部注释）：

> 💡 **路径怎么读？** 下面的 `~` 符号代表你电脑上的"家目录"——Mac 上就是 `/Users/你的用户名/`，Windows 上是 `C:\Users\你的用户名\`。所以 `~/.claude/` 就是你家目录下一个名为 `.claude` 的隐藏文件夹。

```
1. Managed memory (/etc/claude-code/CLAUDE.md) — 全局管理指令（由公司IT管理员统一配置）
2. User memory (~/.claude/CLAUDE.md) — 用户私有全局指令（你个人的全局偏好）
3. Project memory (CLAUDE.md, .claude/CLAUDE.md, .claude/rules/*.md) — 项目级指令（针对当前项目的规则）
4. Local memory (CLAUDE.local.md) — 私有项目指令（你个人针对当前项目的私有规则，不会提交到版本控制）
```

**后加载的优先级更高**。这遵循了软件配置的标准"最近原则"——本地配置覆盖全局配置，与 CSS 层叠规则、Git 配置的 `--local > --global > --system` 优先级链如出一辙。

---

## 2. 核心子系统：Auto Memory (memdir)

> 以下 §2–§8 的七个小节分别对应 §1 架构总览中列出的 "1 个核心 Auto Memory + 4 个辅助存储" 的结构细化；其中 §2–§4 专门拆解核心 Auto Memory 的路径 / 扫描 / 提取三条流水线，§5–§8 依次介绍四个辅助存储。标题编号不代表"七个并列核心子系统"。

Auto Memory 是 Claude Code 记忆系统的核心，实现了**跨会话的持久记忆**。

### 2.1 路径解析

路径计算逻辑位于 `src/memdir/paths.ts`。下面这段代码的作用是**决定记忆文件存放在电脑的哪个文件夹里**——你不需要逐行理解代码，只需要知道它按优先级依次查找三个可能的位置（代码后有中文说明）：

```typescript
// src/memdir/paths.ts (第 223-235 行)
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(),
)
```

解析优先级为：
1. `CLAUDE_COWORK_MEMORY_PATH_OVERRIDE` 环境变量（Cowork SDK 使用）
2. `settings.json` 中的 `autoMemoryDirectory`（仅信任 policy/local/user 来源，**不信任 project 来源** — 防止恶意仓库将记忆目录指向 `~/.ssh`）
3. 默认路径 `~/.claude/projects/<sanitized-git-root>/memory/`

**安全加固**：`validateMemoryPath()` 函数（第 109-150 行）对路径做了 7 项安全检查——这些检查的核心目的是**防止恶意项目把记忆文件偷偷存到敏感位置**（比如你的 SSH 密钥目录 `~/.ssh`，那里存着你登录远程服务器的"钥匙"）：
- 拒绝相对路径（必须写完整路径，比如 `/Users/xxx/...`，不允许写 `../../` 这种可以"往上跳"到其他目录的路径）
- 拒绝根目录/近根目录（长度 < 3）
- 拒绝 Windows 驱动器根目录
- 拒绝 UNC 路径（Windows 网络共享路径，如 `\\server\share`）
- 拒绝 null 字节（一种利用底层编程语言漏洞"截断"文件路径的攻击手段）
- 拒绝展开后指向 `$HOME` 或其父目录的 `~` 路径
- 最后 NFC 规范化 Unicode（统一字符编码格式，防止看起来相同但编码不同的路径绕过检查）

### 2.2 记忆启用判定

`isAutoMemoryEnabled()`（`src/memdir/paths.ts` 第 30-55 行）使用**六级判断链**（first-defined-wins）：

```typescript
// src/memdir/paths.ts (第 30-55 行)
export function isAutoMemoryEnabled(): boolean {
  const envVal = process.env.CLAUDE_CODE_DISABLE_AUTO_MEMORY
  if (isEnvTruthy(envVal)) {       // "1", "true" → 禁用
    return false
  }
  if (isEnvDefinedFalsy(envVal)) { // "0", "false" → 强制启用！
    return true
  }
  // ... 后续检查
}
```

1. `CLAUDE_CODE_DISABLE_AUTO_MEMORY` 为真值（`"1"`/`"true"`）→ 禁用
2. `CLAUDE_CODE_DISABLE_AUTO_MEMORY` 为假值（`"0"`/`"false"`）→ **强制启用**（短路后续所有检查）
3. `--bare`/`CLAUDE_CODE_SIMPLE` 标志 → 禁用
4. 远程模式检测（无 `CLAUDE_CODE_REMOTE_MEMORY_DIR`）→ 禁用
5. `settings.json` 的 `autoMemoryEnabled` → 按配置值
6. 默认启用

> **反直觉行为**：`CLAUDE_CODE_DISABLE_AUTO_MEMORY=0` 会**强制启用**记忆并短路后续所有检查——一个名为 "DISABLE" 的变量设为 `0` 等于强制 ENABLE。这是因为源码将环境变量处理拆分为 `isEnvTruthy`（禁用）和 `isEnvDefinedFalsy`（强制启用）两个分支，形成了一个**双向开关**而非单向门控。实际效果：当你在 `settings.json` 中禁用了记忆但又设置 `CLAUDE_CODE_DISABLE_AUTO_MEMORY=0`，环境变量会覆盖配置文件的设定，强行开启记忆。

### 2.3 记忆类型分类学

Auto Memory 将记忆严格约束为 **四种类型**，定义于 `src/memdir/memoryTypes.ts`：

```typescript
// src/memdir/memoryTypes.ts (第 14-21 行)
export const MEMORY_TYPES = [
  'user',       // 用户画像：角色、偏好、知识水平
  'feedback',   // 行为反馈：纠正 + 正面确认
  'project',    // 项目上下文：目标、决策、期限
  'reference',  // 外部引用：Linear 项目、Grafana 仪表盘
] as const
```

每种类型都有精确的 `<when_to_save>`、`<how_to_use>` 和 `<examples>` 指导。

**关键设计决策**：系统明确禁止保存以下内容（`WHAT_NOT_TO_SAVE_SECTION`，第 183-195 行）：
- 代码模式、架构、文件路径 — 可以通过 grep/git 获取
- Git 历史 — `git log` / `git blame` 是权威来源
- 调试方案 — 修复已在代码中，提交信息有上下文
- CLAUDE.md 已有内容
- 临时任务细节

> 即使用户明确要求保存这些内容，系统也会引导提取其中"令人意外或非显而易见"的部分。这防止了记忆系统退化为活动日志。

### 2.4 记忆文件格式

每个记忆是一个独立的 Markdown 文件，带有 YAML frontmatter：

```markdown
---
name: {{记忆名称}}
description: {{一行描述 — 用于未来相关性判断，要尽量具体}}
type: {{user, feedback, project, reference}}
---

{{记忆正文 — feedback/project 类型建议结构：规则，然后 **Why:** 和 **How to apply:** 行}}
```

**MEMORY.md 是索引，不是记忆本身** — 每条索引不超过约 150 字符，格式为 `- [Title](file.md) — one-line hook`。索引总量上限为 200 行或 25,000 字节（`MAX_ENTRYPOINT_LINES` = 200, `MAX_ENTRYPOINT_BYTES` = 25,000）。

### 2.5 记忆入口点截断

`truncateEntrypointContent()`（`src/memdir/memdir.ts` 第 57-103 行）实现**双重截断**：先按行截断（200 行），再按字节截断（25KB，在最近换行处切割不切断行）。字节上限的存在理由是：有些用户的 MEMORY.md 不到 200 行但达到 197KB（每行极长的索引条目），行数和字节数的双重检查覆盖了两种退化模式。

---

## 3. 核心子系统：记忆扫描与相关性检索（Auto Memory 流水线 2/3）

### 3.1 扫描引擎

`scanMemoryFiles()` 函数（`src/memdir/memoryScan.ts` 第 35-77 行）实现单遍扫描：

```typescript
// src/memdir/memoryScan.ts (第 35-77 行)
export async function scanMemoryFiles(
  memoryDir: string,
  signal: AbortSignal,
): Promise<MemoryHeader[]> {
  try {
    const entries = await readdir(memoryDir, { recursive: true })
    const mdFiles = entries.filter(
      f => f.endsWith('.md') && basename(f) !== 'MEMORY.md',
    )
    const headerResults = await Promise.allSettled(
      mdFiles.map(async (relativePath): Promise<MemoryHeader> => {
        const filePath = join(memoryDir, relativePath)
        const { content, mtimeMs } = await readFileInRange(
          filePath, 0, FRONTMATTER_MAX_LINES, undefined, signal,
        )
        const { frontmatter } = parseFrontmatter(content, filePath)
        return {
          filename: relativePath,
          filePath,
          mtimeMs,
          description: frontmatter.description || null,
          type: parseMemoryType(frontmatter.type),
        }
      }),
    )
    return headerResults
      .filter((r): r is PromiseFulfilledResult<MemoryHeader> =>
        r.status === 'fulfilled')
      .map(r => r.value)
      .sort((a, b) => b.mtimeMs - a.mtimeMs)  // 最新优先
      .slice(0, MAX_MEMORY_FILES)  // 上限 200 个文件
  } catch { return [] }
}
```

**设计选择**：单遍（read-then-sort）而非双遍（stat-sort-read）。对于常见情况（N <= 200），这将系统调用次数减半。代价是当 N > 200 时会多读几个小文件的 frontmatter。

> 📚 **课程关联**：记忆检索的"扫描全量 frontmatter → LLM 选择相关项"两阶段模型，对应《数据库》中的**两阶段查询优化**——先用廉价的全表扫描（frontmatter 解析，类似索引扫描）缩小候选集，再用昂贵的精确匹配（Sonnet 侧查询，类似回表查询）选出最终结果。`MAX_MEMORY_FILES = 200` 的上限类似于数据库优化器的**基数估计**上限，防止全表扫描的 I/O 成本失控。

每个记忆只读前 30 行（`FRONTMATTER_MAX_LINES = 30`），足以解析 YAML frontmatter。

### 3.2 智能相关性检索

`findRelevantMemories()` 函数（`src/memdir/findRelevantMemories.ts`）实现了**两阶段检索**：

**阶段 1**：扫描所有记忆文件的 frontmatter，生成摘要清单。

**阶段 2**：使用 Sonnet 模型（通过 `sideQuery` 通道——一个独立于主对话循环的轻量 API 调用，不进入对话 transcript、不占用主对话上下文、失败不影响主对话）从清单中选择最多 5 个相关记忆。选择器使用 JSON Schema 强制输出 `{ selected_memories: string[] }`，`max_tokens: 256`。

**关键细节**：
- 选择器是**保守的** — 提示要求"只有你确定会有帮助的"才选
- `alreadySurfaced` 参数过滤已展示过的记忆，避免浪费 5 个槽位
- `recentTools` 参数抑制对"正在使用中"工具的参考文档的推荐 — 但**仍然选择**包含警告和已知问题的记忆

### 3.3 记忆新鲜度感知

`memoryAge.ts` 提供了人类可读的年龄标签和过期警告：

```typescript
// src/memdir/memoryAge.ts (第 33-42 行)
export function memoryFreshnessText(mtimeMs: number): string {
  const d = memoryAgeDays(mtimeMs)
  if (d <= 1) return ''
  return (
    `This memory is ${d} days old. ` +
    `Memories are point-in-time observations, not live state — ` +
    `claims about code behavior or file:line citations may be outdated. ` +
    `Verify against current code before asserting as fact.`
  )
}
```

超过 1 天的记忆会附加过期警告。设计动机是：用户报告旧记忆中的 file:line 引用（已经改变）被当作事实断言，带有引用的过时声明反而比没有引用更有"权威感"。

---

## 4. 核心子系统：后台记忆提取 extractMemories（Auto Memory 流水线 3/3）

这是记忆系统最精密的部分 — 一个**后台子代理（forked subagent）**，在每轮对话结束后自动从对话中提取值得记住的信息。

**经济基础：prompt cache 共享**

提取代理使用 `runForkedAgent` 模式运行（`src/utils/forkedAgent.ts`），源码注释（`extractMemories.ts` 第 8-9 行）明确说明这是"a perfect fork of the main conversation that shares the parent's prompt cache"。这个设计是整个记忆系统能够承受"每轮一次 forked agent 调用"的**经济基础**。

具体机制：`forkedAgent.ts` 定义了 `CacheSafeParams` 类型（第 57-68 行），包含 system prompt、user context、system context、tool use context 和 fork context messages。这些参数必须与主对话**完全一致**，才能命中 Anthropic API 的 prompt cache：

```typescript
// src/utils/forkedAgent.ts (第 46-55 行)
/**
 * Parameters that must be identical between the fork and parent API requests
 * to share the parent's prompt cache. The Anthropic API cache key is composed of:
 * system prompt, tools, model, messages (prefix), and thinking config.
 */
```

每次主循环的 `handleStopHooks` 执行后，会通过 `saveCacheSafeParams()` 保存当前的 cache 安全参数，后续的 forked agent（包括记忆提取、prompt suggestion、post-turn summary 等）都复用这组参数。缓存命中率通过日志模板的 `hitPct` 字段被持续监控。

**成本影响**：没有 cache 共享，每轮提取需要重新发送完整的 system prompt + 工具定义 + 对话历史作为 input tokens，成本将与主对话本身相当（Sonnet 的 input token 费用 × 完整上下文长度）。有了 cache 共享，提取调用只需为 cache 未覆盖的增量消息付费（cache 命中部分按 10% 费率计算，见 Anthropic 公开的 prompt caching 定价）。这使得"每轮自动提取"的单位成本显著降低，从"每轮重付 full input"量级降到"只付增量消息"量级。

### 4.1 触发条件

提取在 `stopHooks.ts` 中被 fire-and-forget 调用。触发前需满足：
- 当前是主代理（非子代理）
- `tengu_passport_quail` 特性门控开启
- Auto Memory 启用
- 非远程模式

### 4.2 游标机制与优雅降级

提取器使用 `sinceUuid` 游标跟踪"上次处理到哪条消息"，`countModelVisibleMessagesSince()` 函数（第 82-110 行）计算游标之后的新消息数量，以此决定是否有足够的新内容值得提取。

```typescript
// src/services/extractMemories/extractMemories.ts (第 82-110 行)
function countModelVisibleMessagesSince(
  messages: Message[],
  sinceUuid: string | undefined,
): number {
  if (sinceUuid === null || sinceUuid === undefined) {
    return count(messages, isModelVisibleMessage)
  }
  let foundStart = false
  let n = 0
  for (const message of messages) {
    if (!foundStart) {
      if (message.uuid === sinceUuid) { foundStart = true }
      continue
    }
    if (isModelVisibleMessage(message)) { n++ }
  }
  // 游标丢失时的降级：回退到全量计数而非返回 0
  if (!foundStart) {
    return count(messages, isModelVisibleMessage)
  }
  return n
}
```

**优雅降级设计**：当 `sinceUuid` 因上下文压缩（compaction）被删除而找不到时，系统**不是返回 0（这会永久禁用当前会话剩余时间的提取）**，而是回退到计算所有可见消息的数量。源码注释明确说明了这个决策的理由——"防止永久禁用当前会话剩余时间的提取"。

这是一个处理状态丢失的经典权衡：在分布式系统中，游标失效是常见问题（Kafka 的 offset 过期、数据库 cursor 超时等），通常有三种应对策略——(a) 重新全量处理、(b) 直接跳过、(c) 返回错误。Claude Code 选择了 (a)，保证了记忆提取的**完备性**（不遗漏值得记住的内容），代价是可能触发对已处理内容的重复提取。但由于提取代理本身具有幂等性（相同内容不会重复写入已存在的记忆），这个代价在实践中是可接受的。

### 4.3 频率控制

`extractMemories.ts` 使用**可配置的节流**（`tengu_bramble_lintel`，默认每 1 轮触发一次）和**重叠防护**：

```typescript
// src/services/extractMemories/extractMemories.ts (第 376-386 行)
if (!isTrailingRun) {
  turnsSinceLastExtraction++
  if (
    turnsSinceLastExtraction <
    (getFeatureValue_CACHED_MAY_BE_STALE('tengu_bramble_lintel', null) ?? 1)
  ) {
    return
  }
}
turnsSinceLastExtraction = 0
```

### 4.4 与主代理的互斥

当主代理自己写了记忆时，后台提取器跳过该范围并前进游标：

```typescript
// src/services/extractMemories/extractMemories.ts (第 121-148 行)
function hasMemoryWritesSince(
  messages: Message[],
  sinceUuid: string | undefined,
): boolean {
  // ... 检查 sinceUuid 之后的所有 assistant 消息
  // 是否有 Write/Edit tool_use 指向 auto-memory 路径
  for (const block of content) {
    const filePath = getWrittenFilePath(block)
    if (filePath !== undefined && isAutoMemPath(filePath)) {
      return true
    }
  }
  return false
}
```

这确保了主代理和后台代理是**每轮互斥的** — 避免重复写入。

### 4.5 并发处理：Stash-and-Trail 模式

如果新的提取请求在上一次运行期间到达，不会被丢弃，而是暂存起来等上一次完成后执行 trailing run：

```
请求到来 --> inProgress? --> 是 --> stash context（覆盖之前的 stash）
                             否 --> 开始 runExtraction
runExtraction 完成 --> 有 pending? --> 是 --> runExtraction(trailing)
                                       否 --> 空闲
```

Trailing run 会基于已前进的游标计算 `newMessageCount`，所以只处理两次调用之间新增的消息。

> 📚 **课程关联**：Stash-and-Trail 模式是《操作系统》中**生产者-消费者问题**的一个变体——生产者（对话轮次）持续产生新消息，消费者（提取器）按节奏处理。与经典的有界缓冲区不同，这里的 stash 只保留最新一个请求（覆盖式写入），类似于《计算机体系结构》中**写合并缓冲区（Write Combining Buffer）**的思想——多次写入合并为一次处理。

### 4.6 工具权限沙箱

后台提取代理被严格限制（`createAutoMemCanUseTool()`，第 171-222 行）：

| 工具 | 权限 |
|:---:|:---:|
| FileRead, Grep, Glob | 无限制 |
| Bash | 仅只读命令（ls, find, grep, cat, stat, wc, head, tail 等） |
| FileEdit, FileWrite | **仅限** 记忆目录内的路径 |
| MCP, Agent, 可写 Bash | 全部拒绝 |

### 4.7 提取提示词

提取提示（`src/services/extractMemories/prompts.ts`）给代理明确的效率指导：

```
你的 turn 预算有限。FileEdit 需要先 FileRead 同一文件。
高效策略：
  turn 1 — 并行发出所有你可能需要的 FileRead
  turn 2 — 并行发出所有 FileWrite/FileEdit
不要在多个 turn 之间交替读写。
```

硬上限为 5 个 turn（`maxTurns: 5`），防止陷入验证的兔子洞。

### extractMemories vs autoDream：两种记忆机制的分工

读者容易把这两者混淆。下面用一张表明确它们的区别：

| 维度 | extractMemories（§4） | autoDream（§6） |
|------|----------------------|-----------------|
| **做什么** | 从当前 transcript 提取 durable signal | 对已有记忆做 consolidation/去重/归类 |
| **时机** | 每轮对话结束时（高频） | 后台定时（低频：24h + 5 会话） |
| **触发方式** | queryLoop 收尾阶段自动调用 | `isGateOpen()` 三门全开后才启动 |
| **操作对象** | 对话历史（messages） | 已有的 MEMORY.md + 记忆文件 |
| **产出** | 新的记忆文件（user/feedback/project/reference 类型） | 整合后的主题文件（合并/去重/修剪） |
| **比喻** | 秘书每次会议后做笔记 | 秘书周末整理本周所有笔记 |

> 💡 **通俗理解**：extractMemories 是"每天写日记"，autoDream 是"月底翻日记本，把重复的删掉、相关的合并、过时的划掉"。两者缺一不可——只写不整理会越来越乱，只整理不写则没有新素材。

---

## 5. 辅助存储 1/4：Session Memory

Session Memory 是**单会话级别的笔记**，在对话过程中自动维护一份 Markdown 文件，记录当前对话的关键信息。

### 5.1 触发阈值

定义于 `src/services/SessionMemory/sessionMemoryUtils.ts`：

```typescript
// src/services/SessionMemory/sessionMemoryUtils.ts (第 32-36 行)
export const DEFAULT_SESSION_MEMORY_CONFIG: SessionMemoryConfig = {
  minimumMessageTokensToInit: 10000,     // 首次触发：上下文达到 10K tokens
  minimumTokensBetweenUpdate: 5000,      // 更新间隔：上下文增长 5K tokens
  toolCallsBetweenUpdates: 3,            // 更新间隔：至少 3 次工具调用
}
```

触发条件（`shouldExtractMemory()`，`sessionMemory.ts` 第 134-181 行）：
1. 首次：上下文窗口 tokens >= 10,000
2. 之后每次更新需同时满足：
   - **token 阈值**（必要条件）：自上次提取以来上下文增长 >= 5,000 tokens
   - **以及**以下之一：工具调用次数 >= 3，或者最后一轮助手回复没有工具调用（自然对话断点）

### 5.2 与 Compaction 的集成

Session Memory 的一个核心用途是替代传统的上下文压缩。`sessionMemoryCompact.ts` 定义了压缩后的保留范围：最少 10K tokens / 5 条文本消息，最多 40K tokens（`DEFAULT_SM_COMPACT_CONFIG`，第 57-61 行）。

`calculateMessagesToKeepIndex()`（第 324-397 行）从 `lastSummarizedMessageId` 开始向后扩展直到满足最低要求。关键细节：`adjustIndexToPreserveAPIInvariants()`（第 232-314 行）确保不会拆分 tool_use/tool_result 对，避免 API 调用中出现孤立的 tool_result。

### 5.3 提取同步等待

压缩前通过 `waitForSessionMemoryExtraction()`（第 89-105 行）等待进行中的提取完成。双超时设计：15 秒等待超时 + 60 秒过期阈值（`sleep(1000)` 轮询），防止死锁。

---

## 6. 辅助流程：Dream 整合 autoDream（作用于 Auto Memory 之上）

Dream 是记忆系统的"整理归档"环节 — 定期将积累的会话记忆整合成结构化的主题文件。

### 6.1 触发门控

`autoDream.ts` 使用三级门控（按成本从低到高排序）：

```
1. 时间门控：距上次整合 >= minHours (默认 24h)  [一次 stat 调用]
2. 会话门控：自上次整合以来的会话数 >= minSessions (默认 5)  [目录扫描]
3. 锁定门控：没有其他进程正在整合  [文件锁]
```

配置来自 GrowthBook 远程配置（`tengu_onyx_plover`，第 73-93 行），带有防御性逐字段类型检查，确保 GB 缓存返回错误类型值时仍使用合理默认值。

### 6.2 KAIROS 模式：每日日志

当 `feature('KAIROS')` 启用且处于助手模式时，记忆切换到 **每日日志模式**：

```typescript
// src/memdir/memdir.ts (第 327-370 行)
function buildAssistantDailyLogPrompt(skipIndex = false): string {
  const memoryDir = getAutoMemPath()
  const logPathPattern = join(memoryDir, 'logs', 'YYYY', 'MM', 'YYYY-MM-DD.md')
  // ...
  // 代理只做 append 操作，不重写不重组
  // 独立的夜间进程将日志蒸馏为 MEMORY.md 和主题文件
}
```

日志路径格式：`<autoMemPath>/logs/YYYY/MM/YYYY-MM-DD.md`

这是一个有趣的范式切换：标准模式下代理维护 MEMORY.md 作为活跃索引，KAIROS 模式下代理只做追加写入，由 `/dream` 技能夜间处理索引维护。

> **关键互斥关系：KAIROS 模式与自动 Dream 是互斥的。** `autoDream.ts` 第 95-96 行的 `isGateOpen()` 显式检查了这个条件：
> ```typescript
> function isGateOpen(): boolean {
>   if (getKairosActive()) return false // KAIROS mode uses disk-skill dream
>   // ...
> }
> ```
> 当 KAIROS 模式激活时，自动 Dream 整合被完全禁用。这意味着 KAIROS 用户**不会**享受到 6.1 节描述的自动定时整合，而是依赖 `/dream` 手动技能（disk-skill dream）来触发整合。这个设计的合理性在于：KAIROS 模式的"追加写入 + 离线整合"范式与 autoDream 的"自动定时整合"在职责上完全重叠——如果两者同时运行，会导致日志文件和主题文件之间的状态冲突。

### 6.3 Dream Agent 的四阶段工作流

> **命名澄清**：Claude Code 里存在两条"Dream"执行路径——(a) 自动触发的 `autoDream`（§6.1 三门控通过后自动起一个后台 fork agent）和 (b) 用户在 KAIROS 模式下手动调用的 `/dream` 磁盘 skill（disk-skill dream，由独立的夜间/on-demand 进程执行）。下面的四阶段 prompt 是 `consolidationPrompt.ts` 里的 `buildConsolidationPrompt()` 输出，两条路径**共用同一份 consolidation prompt**（都走整合→修剪逻辑），但**触发机制、执行上下文、是否与 autoDream 互斥**完全不同，见 §6.2 的互斥说明。

通过所有门控后，Dream agent 被注入一个结构化的四阶段提示词，每个阶段有明确的操作指令：

| 阶段 | 名称 | 操作 |
|:---:|:---:|:---|
| Phase 1 | **Orient（定位）** | `ls` 记忆目录 + 读 MEMORY.md + 浏览现有主题文件，建立当前记忆状态全景图 |
| Phase 2 | **Gather Recent Signal（收集信号）** | 检查每日日志（如 KAIROS 模式下的 `logs/YYYY/MM/YYYY-MM-DD.md`），窄范围检索 session transcripts——**显式约束"不要穷举读 transcripts，只找你已经怀疑有价值的内容"** |
| Phase 3 | **Consolidate（整合）** | 写入/更新记忆文件，合并新信号到已有主题文件（避免近似重复），将相对日期转为绝对日期（"昨天"→"2026-04-04"），删除矛盾事实 |
| Phase 4 | **Prune and Index（修剪与索引）** | 更新 MEMORY.md 索引（保持 200 行/25KB 上限），删除过期指针，缩短冗长条目，解决文件间矛盾 |

💡 **通俗理解**：就像周末整理书桌——先看看桌上有什么（Orient），翻翻这周的便签找重要的（Gather），把重要的归档到对应文件夹（Consolidate），最后清理过期的便签、更新目录（Prune）。

Phase 2 的"只找你已经怀疑有价值的内容"是一个重要的**反常识约束**——它防止 Dream agent 因全量扫描 transcripts 而超出 turn 预算。Dream agent 的工具权限也严格受限：Bash 只能执行只读命令（`ls`、`find`、`grep`、`cat`、`stat`），文件写入只能针对记忆目录路径，不能使用 MCP 工具或 Agent 工具。

#### Dream 提示词的实际内容

以下引自 `consolidationPrompt.ts` 中 `buildConsolidationPrompt()` 的完整输出。这是 Dream agent 在每次整合时收到的全部指令：

**开场定调**（角色设定）：

> *"You are performing a dream — a reflective pass over your memory files. Synthesize what you've learned recently into durable, well-organized memories so that future sessions can orient quickly."*

"Dream"这个命名不是比喻——它是对人类记忆整合机制的刻意映射。人在睡眠期间，大脑会将短期记忆整合为长期记忆（海马体→新皮层转移）。Claude 的 Dream 做类似的事：将散落的会话记忆整合为结构化的主题文件。

**Phase 2 的优先级链**——信号收集有明确的来源优先级：

> 1. *"Daily logs (`logs/YYYY/MM/YYYY-MM-DD.md`) if present — these are the append-only stream"*
> 2. *"Existing memories that drifted — facts that contradict something you see in the codebase now"*
> 3. *"Transcript search — grep the JSONL transcripts for narrow terms"*

关键约束：*"Don't exhaustively read transcripts. Look only for things you already suspect matter."*

这说明 Dream 不是"全量重新理解"，而是"带着已知方向去找新信号"——一种**假设驱动的信息检索**模式。

**Phase 3 的三条整合规则**：

> *"Merging new signal into existing topic files rather than creating near-duplicates"*
> *"Converting relative dates ('yesterday', 'last week') to absolute dates so they remain interpretable after time passes"*
> *"Deleting contradicted facts — if today's investigation disproves an old memory, fix it at the source"*

第三条尤其值得关注——Dream agent 有权**主动删除错误记忆**。这是一种"遗忘即清理"的设计——如果一条旧记忆被新证据推翻，删除比保留更有价值。

**Phase 4 的索引约束**——MEMORY.md 的严格格式：

> *"Each entry should be one line under ~150 characters: `- [Title](file.md) — one-line hook`. Never write memory content directly into it."*

MEMORY.md 是**索引不是内容**——就像图书馆的目录卡，只记录书名和位置，不摘录内容。这防止了索引文件自身膨胀成不可维护的状态。

### 6.4 整合锁机制

使用文件锁 + mtime 回退实现安全的并发控制：
- `tryAcquireConsolidationLock()` — 获取锁
- `rollbackConsolidationLock(priorMtime)` — 失败时回退 mtime，让下次时间门控仍然通过
- 扫描节流（`SESSION_SCAN_INTERVAL_MS = 10 分钟`）防止时间门控通过但会话门控不通过时的重复扫描

---

## 7. 辅助存储 2/4：Agent Memory

Agent Memory 为每个自定义 Agent 提供独立的持久记忆空间。

### 7.1 三级作用域

定义于 `src/tools/AgentTool/agentMemory.ts`：

```typescript
// src/tools/AgentTool/agentMemory.ts (第 12 行)
export type AgentMemoryScope = 'user' | 'project' | 'local'
```

| 作用域 | 路径 | 说明 |
|:---:|:---:|:---:|
| `user` | `~/.claude/agent-memory/<agentType>/` | 跨项目全局 |
| `project` | `.claude/agent-memory/<agentType>/` | 项目级，可提交到 VCS |
| `local` | `.claude/agent-memory-local/<agentType>/` | 本地，不提交 |

### 7.2 快照同步

`agentMemorySnapshot.ts` 的 `checkAgentMemorySnapshot()`（第 98-144 行）实现快照驱动的初始化：检查 `.claude/agent-memory-snapshots/<agentType>/snapshot.json`，对比 `.snapshot-synced.json` 的同步时间，返回三种状态（`none`/`initialize`/`prompt-update`）。

---

## 8. 辅助存储 3/4：Team Memory（4/4 是 CLAUDE.md，见 §1.1）

Team Memory 是 Auto Memory 的子目录（`<memdir>/team/`），实现团队共享记忆。

### 8.1 路径安全

`teamMemPaths.ts` 的 `validateTeamMemWritePath()`（第 228-256 行）实现**双遍验证**防止路径遍历（PSR M22186）：
1. `path.resolve()` — 消除 `..` 段（快速拒绝明显的遍历）
2. `realpathDeepestExisting()`（第 109-171 行）— 解析最深存在祖先的真实路径（防止符号链接逃逸）

后者还用 `lstat()` 区分"真的不存在"和"悬空符号链接（链接存在但目标不存在）"— 悬空符号链接是攻击向量：`writeFile` 会跟随链接在 teamDir 外创建目标文件。

### 8.2 Combined 模式提示

当 Auto Memory + Team Memory 同时启用时，使用 `TYPES_SECTION_COMBINED` — 每种记忆类型都带有 `<scope>` 标签指导存储位置选择：

- `user` 类型 --> always private
- `feedback` 类型 --> default to private，只有明确的项目级约定才存 team
- `project` 类型 --> strongly bias toward team
- `reference` 类型 --> usually team

---

## 9. 记忆文件检测与提示词工程

`src/utils/memoryFileDetection.ts` 是记忆系统的"守门人"。`isAutoManagedMemoryFile()`（第 133-147 行）检查所有自动管理的记忆文件（排除用户手动管理的 CLAUDE.md），用于 UI 折叠/徽章逻辑。`isShellCommandTargetingMemory()`（第 215-271 行）检测 Shell 命令是否指向记忆文件，支持 Windows、MinGW 和 POSIX 三种路径格式。

### 9.1 回忆指导

`TRUSTING_RECALL_SECTION`（`memoryTypes.ts` 第 240-256 行）经过 eval 验证：

```
"记忆中说 X 存在" 不等于 "X 现在仍然存在"

- 如果记忆提到一个文件路径：检查文件是否存在
- 如果记忆提到一个函数或标志：grep 搜索
- 如果用户将基于你的推荐采取行动：先验证
```

评估结果：在 `appendSystemPrompt` 位置放置时 3/3 通过；作为其他部分的子弹点时 0/3。**位置很重要**。

### 9.2 忽略指令

```
如果用户说"忽略"或"不使用"记忆：像 MEMORY.md 为空一样行事。
不要应用记忆事实、引用、对比或提及记忆内容。
```

这解决了一个具体的失败模式（evals #22856, case 5）：用户说"忽略关于 X 的记忆" --> Claude 正确读取代码但补充"不是记忆中说的 Y" — 把"忽略"理解为"承认然后覆盖"而非"完全不引用"。

---

> 📖 **延伸阅读**：本章聚焦于单用户记忆系统。当多个团队成员需要共享 AI 记忆时（如项目级知识同步），Claude Code 还有一套独立的**团队记忆同步系统**（`src/services/teamMemorySync/` 目录下 5 个文件合计 **2,167 行**，以 `wc -l` 核实），通过 Anthropic API 实现 Pull/Push 同步、密钥扫描、ETag 乐观并发等。详见 **Part 3「团队记忆同步完全解析」**。

## 10. 批判性分析

### 10.1 设计优势

**1. 类型分类学的约束力**

四类型分类（user/feedback/project/reference）加上"什么不该保存"的反向约束，有效防止了记忆膨胀。禁止保存可派生信息（代码模式、git 历史）的决策遵循了数据库设计中的**派生数据不持久化**原则（类似于数据库范式化中避免存储可计算字段）— 这些信息有更权威的源头。

**2. 主代理-后台代理互斥**

`hasMemoryWritesSince()` 检查确保同一范围的消息不会被双重处理。这是简洁而有效的并发控制。

**3. 新鲜度感知**

记忆不是"真理" — 它是"某个时间点的观察"。这个设计哲学贯穿始终，从 `memoryFreshnessText()` 到 `TRUSTING_RECALL_SECTION`，有效缓解了"过期记忆被当作权威"的问题。

**4. 安全防御深度**

Team Memory 的双遍路径验证（resolve + realpath）+ 悬空符号链接检测 + Unicode 规范化攻击防御 + 设置来源信任链（排除 projectSettings 防止恶意仓库），体现了成熟的安全工程实践。这些防御手段在安全敏感的文件操作中属于业界标准做法（OWASP 路径遍历防御指南推荐类似的多层验证），Claude Code 的实现覆盖面较为全面。

### 10.2 设计权衡与潜在问题

**1. Sonnet 侧查询的成本与延迟**

每次用户提问，`findRelevantMemories()` 都会用 Sonnet 模型做一次侧查询来选择相关记忆。这增加了：
- 延迟：额外一次 API 往返（即使 `max_tokens: 256`）
- 成本：每轮对话多一次 API 调用
- 单点故障：如果侧查询超时或失败，所有深层记忆都不会被召回——源码中 `findRelevantMemories.ts` 对失败场景的处理是返回空数组（即"不召回"），没有实现"降级到全量 frontmatter manifest 注入"这种优雅退化路径

一个可能的改进是本地向量搜索或 embedding 缓存，或者补一条 graceful degrade 路径（在 Sonnet 侧查询失败时回退到按 mtime 排序的近似召回），但这会增加客户端复杂度。

**2. MEMORY.md 索引的脆弱性**

MEMORY.md 是一个手动维护（由 AI）的索引文件，存在格式漂移风险。当后台代理和主代理都在更新索引时，可能出现格式不一致。`tengu_moth_copse` 门控下的 `skipIndex` 模式表明团队已经在实验移除手动索引的路径。

**3. Session Memory 的 1 秒轮询**

`waitForSessionMemoryExtraction()` 使用 `sleep(1000)` 轮询。实现较简单；若对等待延迟敏感，可改为基于 Promise/EventEmitter 的事件驱动唤醒以减少最坏情况下的等待时间。

**4. 200 文件上限的隐含约束**

`MAX_MEMORY_FILES = 200` 是一个隐含的硬上限。对于长期使用的重度用户，这可能成为限制。系统除 Dream 整合（会在 Phase 3/4 对过期、冗余、矛盾记忆做合并与删除）之外，没有独立的自动老化/归档机制——`memoryAge.ts` 只计算年龄并给模型"记忆可能过时"的提示，不主动清理过期文件。

**5. 门控标志的不透明性**

系统中大量使用 GrowthBook 特性门控（如 `tengu_passport_quail`、`tengu_herring_clock`、`tengu_moth_copse`、`tengu_coral_fern` 等），这些代号对维护者来说完全不透明。虽然这是 A/B 测试的标准做法，但增加了代码理解难度。

**6. 文件系统 vs 数据库的权衡**

选择纯文件系统存储而非 SQLite 或向量数据库。优点是透明（用户可以直接编辑 .md 文件）、便携（git 可追踪）、无依赖。缺点是没有索引、搜索效率依赖外部 API 调用。

### 10.3 源码中的实验性信号

从源码中可以看到几个正在进行的实验性改动（均以 feature flag 或独立代码路径形式存在，非官方路线图）：
- `skipIndex` 模式（`tengu_moth_copse`）：MEMORY.md 索引可能被废弃，转向纯扫描
- `isExtractModeActive()` 与 `isAutoMemoryEnabled()` 的分离：提取逻辑正在解耦
- KAIROS 模式的日志范式：从"实时索引维护"转向"追加写入 + 离线整合"
- Team Memory 的高度安全性投入：预示着多人协作场景的重要性

### 10.4 竞品记忆系统深度对比（对 LangMem/Mem0/Zep 的描述按公开资料综合）

Claude Code 的记忆系统并非孤立存在。以下是与主流 AI 记忆框架的架构级对比（**非 Claude Code 列的描述基于各项目公开文档与博客综合，未逐行核实各家源码；随版本演进可能已有变化，请以各项目官方文档为准**）：

| 维度 | Claude Code | LangMem / Mem0 | Zep |
|------|------------|----------------|-----|
| **存储范式** | 纯文件系统（.md 文件 + YAML frontmatter） | 向量数据库 + 语义索引 | 知识图谱 + 向量混合 |
| **召回方式** | LLM 侧查询（模型扫描 metadata → 选择相关文件） | Embedding 语义搜索 | 混合检索（语义 + 图遍历） |
| **记忆类型** | 四类型分类学（user/feedback/project/reference） | 自由标签 | 实体关系模型 |
| **遗忘策略** | 粗粒度（24h/5 轮周期 + Dream 整合） | TTL + 重要性衰减 | 图修剪 + 节点权重递减 |
| **关联网络** | 无（扁平文件列表） | 有（向量空间近邻） | 有（知识图谱边） |
| **离线整合** | autoDream / KAIROS 夜间整合 | 无 | 后台图更新 |

> 注：原表中曾列有 "OpenClaw"（定位为"文件系统 · 类 CC 设计"）作为对比列，该条目与前文行业背景中的 OpenCode 命名易混淆、且记忆系统具体实现未独立核实，已从对比表中移除。读者如需了解 OpenClaw/OpenCode 的真实记忆策略，请以该项目的 GitHub README 与官方文档为准。

**Claude Code 的独特定位**：CC 选择了"偏执行"的路径——像工程师 debug，一边看一边改一边跑。相比向量数据库方案（LangMem/Mem0），CC 的文件系统方案牺牲了语义召回精度（200 文件上限 + LLM 扫描 vs 数十万条向量检索），但换来了完全的透明性（用户可直接编辑 .md 文件）和零外部依赖。这个取舍反映了 CC 作为终端工具"一切自包含"的设计哲学。

**CC 的短板**：不支持语义化召回（用 LLM 扫描 metadata，上限 200 文件）、遗忘策略粗（24h/5 轮周期）、无关联网络。随着用户记忆量增长，这些限制将逐渐显现。

---

## 11. 代码落点

以下列出本章涉及的所有关键文件及核心行号：

### 记忆目录（memdir）
| 文件 | 核心函数/常量 | 行号 |
|:---|:---|:---:|
| `src/memdir/paths.ts` | `isAutoMemoryEnabled()` | 30-55 |
| `src/memdir/paths.ts` | `getAutoMemPath()` | 223-235 |
| `src/memdir/paths.ts` | `validateMemoryPath()` | 109-150 |
| `src/memdir/paths.ts` | `isExtractModeActive()` | 69-77 |
| `src/memdir/paths.ts` | `getAutoMemDailyLogPath()` | 246-251 |
| `src/memdir/memdir.ts` | `truncateEntrypointContent()` | 57-103 |
| `src/memdir/memdir.ts` | `buildMemoryLines()` | 199-266 |
| `src/memdir/memdir.ts` | `buildMemoryPrompt()` | 272-316 |
| `src/memdir/memdir.ts` | `buildAssistantDailyLogPrompt()` | 327-370 |
| `src/memdir/memdir.ts` | `loadMemoryPrompt()` | 419-507 |
| `src/memdir/memdir.ts` | `ensureMemoryDirExists()` | 129-147 |
| `src/memdir/memoryScan.ts` | `scanMemoryFiles()` | 35-77 |
| `src/memdir/memoryScan.ts` | `formatMemoryManifest()` | 84-94 |
| `src/memdir/memoryTypes.ts` | `MEMORY_TYPES` | 14-19 |
| `src/memdir/memoryTypes.ts` | `TYPES_SECTION_COMBINED` | 37-106 |
| `src/memdir/memoryTypes.ts` | `TYPES_SECTION_INDIVIDUAL` | 113-178 |
| `src/memdir/memoryTypes.ts` | `WHAT_NOT_TO_SAVE_SECTION` | 183-195 |
| `src/memdir/memoryTypes.ts` | `TRUSTING_RECALL_SECTION` | 240-256 |
| `src/memdir/memoryTypes.ts` | `WHEN_TO_ACCESS_SECTION` | 216-222 |
| `src/memdir/memoryAge.ts` | `memoryFreshnessText()` | 33-42 |
| `src/memdir/findRelevantMemories.ts` | `findRelevantMemories()` | 39-75 |
| `src/memdir/findRelevantMemories.ts` | `selectRelevantMemories()` | 77-141 |
| `src/memdir/teamMemPaths.ts` | `isTeamMemoryEnabled()` | 73-78 |
| `src/memdir/teamMemPaths.ts` | `validateTeamMemWritePath()` | 228-256 |
| `src/memdir/teamMemPaths.ts` | `realpathDeepestExisting()` | 109-171 |

### 服务层（services）
| 文件 | 核心函数/常量 | 行号 |
|:---|:---|:---:|
| `src/services/extractMemories/extractMemories.ts` | `countModelVisibleMessagesSince()` | 82-110 |
| `src/services/extractMemories/extractMemories.ts` | `initExtractMemories()` | 296-587 |
| `src/services/extractMemories/extractMemories.ts` | `hasMemoryWritesSince()` | 121-148 |
| `src/services/extractMemories/extractMemories.ts` | `createAutoMemCanUseTool()` | 171-222 |
| `src/services/extractMemories/prompts.ts` | `buildExtractAutoOnlyPrompt()` | 50-94 |
| `src/services/extractMemories/prompts.ts` | `buildExtractCombinedPrompt()` | 101-154 |
| `src/services/SessionMemory/sessionMemory.ts` | `shouldExtractMemory()` | 134-181 |
| `src/services/SessionMemory/sessionMemory.ts` | `extractSessionMemory` | 272-350 |
| `src/services/SessionMemory/sessionMemory.ts` | `initSessionMemory()` | 357-375 |
| `src/services/SessionMemory/sessionMemory.ts` | `createMemoryFileCanUseTool()` | 460-482 |
| `src/services/SessionMemory/sessionMemoryUtils.ts` | `DEFAULT_SESSION_MEMORY_CONFIG` | 32-36 |
| `src/services/SessionMemory/sessionMemoryUtils.ts` | `waitForSessionMemoryExtraction()` | 89-105 |
| `src/services/SessionMemory/sessionMemoryUtils.ts` | `getSessionMemoryContent()` | 110-126 |
| `src/services/compact/sessionMemoryCompact.ts` | `DEFAULT_SM_COMPACT_CONFIG` | 57-61 |
| `src/services/compact/sessionMemoryCompact.ts` | `calculateMessagesToKeepIndex()` | 324-397 |
| `src/services/compact/sessionMemoryCompact.ts` | `adjustIndexToPreserveAPIInvariants()` | 232-314 |
| `src/services/compact/sessionMemoryCompact.ts` | `trySessionMemoryCompaction()` | 514-630 |
| `src/services/autoDream/autoDream.ts` | `isGateOpen()` | 95-100 |
| `src/services/autoDream/autoDream.ts` | `initAutoDream()` | 122-273 |

### Agent 记忆
| 文件 | 核心函数/常量 | 行号 |
|:---|:---|:---:|
| `src/tools/AgentTool/agentMemory.ts` | `AgentMemoryScope` | 12 |
| `src/tools/AgentTool/agentMemory.ts` | `getAgentMemoryDir()` | 52-65 |
| `src/tools/AgentTool/agentMemory.ts` | `loadAgentMemoryPrompt()` | 138-177 |
| `src/tools/AgentTool/agentMemorySnapshot.ts` | `checkAgentMemorySnapshot()` | 98-144 |

### 工具层与 UI
| 文件 | 核心函数/常量 | 行号 |
|:---|:---|:---:|
| `src/utils/forkedAgent.ts` | `CacheSafeParams` 类型 | 57-68 |
| `src/utils/forkedAgent.ts` | `saveCacheSafeParams()` / `getLastCacheSafeParams()` | 75-81 |
| `src/utils/forkedAgent.ts` | `ForkedAgentParams` 类型 | 83-113 |
| `src/utils/memoryFileDetection.ts` | `isAutoManagedMemoryFile()` | 133-147 |
| `src/utils/memoryFileDetection.ts` | `isShellCommandTargetingMemory()` | 215-271 |
| `src/utils/memoryFileDetection.ts` | `memoryScopeForPath()` | 106-114 |
| `src/utils/claudemd.ts` | 加载顺序注释 | 1-25 |
| `src/commands/memory/memory.tsx` | `MemoryCommand` | 14-82 |
| `src/components/memory/MemoryUpdateNotification.tsx` | `MemoryUpdateNotification` | 21-44 |
