记忆系统
Claude Code 的持久化记忆机制 —— memdir 目录扫描、AI 驱动的记忆检索、年龄衰减、自动做梦整合
记忆系统概述
持久化文件记忆
Claude Code 采用基于文件的持久化记忆系统,让 AI 在跨会话中保持连贯性。记忆存储在 ~/.claude/projects/<sanitized-git-root>/memory/ 目录下,每个记忆是一个独立的 Markdown 文件,通过 MEMORY.md 索引文件进行快速导航。
记忆系统包含两个核心能力:显式记忆(用户通过 /remember 命令或对话中主动要求保存)和 隐式记忆(后台 extract-memories agent 自动提取重要信息)。 更高级的 自动做梦(autoDream)机制在积累足够会话后, 自动整合、去重、修剪记忆文件。
记忆系统架构
memdir — 记忆目录路径
paths.ts 路径解析与安全验证
记忆目录路径解析遵循严格的优先级链,同时内置多层安全验证防止路径遍历攻击:
路径解析优先级
CLAUDE_COWORK_MEMORY_PATH_OVERRIDE— 环境变量覆盖(Cowork SDK)settings.json autoMemoryDirectory— 仅限 policy/local/user 信任源- 默认路径:
~/.claude/projects/<sanitized-git-root>/memory/
安全防护机制
- 拒绝相对路径、根路径、UNC 路径
- 拒绝 null 字节和 Windows 驱动器根
- projectSettings 不参与路径覆盖(防恶意仓库)
- 路径经过
normalize()+ NFC 规范化
KAIROS 助手模式布局
在 KAIROS 模式下,记忆目录使用按日期归档的日志布局:
memory/
├── MEMORY.md # 主索引(≤25行,≤25KB)
├── topic-1.md # 按主题组织的记忆文件
├── topic-2.md
├── logs/
│ └── 2026/
│ └── 04/
│ ├── 2026-04-01.md # 每日追加日志
│ ├── 2026-04-02.md
│ └── 2026-04-03.md
└── team/ # 团队共享记忆
├── MEMORY.md
└── *.md记忆类型分类
四类型封闭分类法 — memoryTypes.ts
记忆系统采用严格的四类型分类,每种类型都明确界定应该存什么和 不该存什么。核心原则:只存储无法从当前代码库状态推导的信息。
export const MEMORY_TYPES = [
'user', // 用户画像:角色、偏好、知识水平
'feedback', // 反馈指导:纠正与确认的行为准则
'project', // 项目上下文:目标、里程碑、事件
'reference', // 外部资源:Linear、Grafana 等系统指针
] as const用户角色、目标、偏好、知识水平
📌 了解用户角色、偏好、知识时保存
例:用户是资深 Go 工程师,首次接触 React
纠正与确认的行为准则,含 Why 和 How to apply
📌 用户纠正或确认方案时保存(含非显而易见的确认)
例:集成测试禁止 mock 数据库,因上次 mock 通过但生产迁移失败
项目目标、里程碑、事件(代码/git 不可推导的部分)
📌 了解谁做什么、为什么、什么时候时保存
例:3月5日起合并冻结,mobile 团队切 release 分支
外部系统指针(Linear、Grafana、Slack 等)
📌 了解外部资源及其用途时保存
例:Pipeline bug 追踪在 Linear INGEST 项目
⚠️ 不应保存的内容
- 代码模式、约定、架构、文件路径 — 可从代码库推导
- Git 历史 —
git log才是权威来源 - 调试方案 — 修复已在代码中,commit message 有上下文
- CLAUDE.md 中已记录的内容
- 临时状态:进行中的工作、当前对话上下文
记忆文件格式
Frontmatter 规范与索引结构
每个记忆文件使用 YAML Frontmatter 声明元数据,body 部分对于 feedback/project 类型要求包含 Why(原因)和 How to apply(适用场景)结构化信息:
---
name: integration-test-policy
description: 集成测试必须使用真实数据库,禁止 mock
type: feedback
---
# 集成测试数据库策略
## 规则
所有集成测试必须连接真实数据库进行验证。
## Why
上季度 mock 测试通过但生产迁移失败,
mock/真实环境的差异掩盖了关键 bug。
## How to apply
编写集成测试时直接连接测试数据库,
不使用 sqlite-memory 或 mock provider。MEMORY.md 索引中每个条目不超过 ~150 字符:- [Title](file.md) — one-line hook。 索引上限 25 行 / 25KB,超出会被截断。
记忆扫描与检索
memoryScan.ts + findRelevantMemories.ts
记忆检索分为两步:扫描(读取所有 .md 文件的 frontmatter 头部) 和 选择(用 Sonnet 模型从清单中选出最相关的 ≤5 条)。
scanMemoryFiles — 单次扫描
export async function scanMemoryFiles(
memoryDir: string,
signal: AbortSignal,
): Promise<MemoryHeader[]> {
const entries = await readdir(memoryDir, { recursive: true })
const mdFiles = entries.filter(
f => f.endsWith('.md') && basename(f) !== 'MEMORY.md'
)
// 读取 frontmatter,按 mtime 降序排列,最多 200 个
const headers = await Promise.allSettled(
mdFiles.map(async (relPath) => {
const filePath = join(memoryDir, relPath)
const { content, mtimeMs } = await readFileInRange(filePath, 0, 30)
const { frontmatter } = parseFrontmatter(content)
return {
filename: relPath, filePath, mtimeMs,
description: frontmatter.description || null,
type: parseMemoryType(frontmatter.type),
}
})
)
return headers
.filter(r => r.status === 'fulfilled')
.map(r => r.value)
.sort((a, b) => b.mtimeMs - a.mtimeMs)
.slice(0, 200)
}findRelevantMemories — AI 选择
export async function findRelevantMemories(
query: string,
memoryDir: string,
signal: AbortSignal,
): Promise<RelevantMemory[]> {
// 1. 扫描所有记忆文件头部
const memories = await scanMemoryFiles(memoryDir, signal)
// 2. 用 Sonnet 模型选择最相关的 ≤5 条
const selectedFilenames = await selectRelevantMemories(
query, memories, signal
)
// 3. 返回路径 + mtime(用于新鲜度检查)
return selected.map(m => ({
path: m.filePath,
mtimeMs: m.mtimeMs,
}))
}检索流程要点
- 单次扫描优化:先读后排序,避免双 stat 调用
- 已去重过滤:
alreadySurfaced避免重复选择前轮已展示的记忆 - 工具噪声抑制:当用户正在使用某工具时,不选择该工具的参考文档
- 最多 200 个文件,按 mtime 降序排列
年龄衰减与新鲜度
memoryAge.ts — 记忆过时检测
记忆是时间点的快照,不是实时状态。系统通过 年龄计算和 新鲜度警告帮助模型正确处理过时记忆:
export function memoryAgeDays(mtimeMs: number): number {
return Math.max(0, Math.floor((Date.now() - mtimeMs) / 86_400_000))
}
export function memoryAge(mtimeMs: number): string {
const d = memoryAgeDays(mtimeMs)
if (d === 0) return 'today'
if (d === 1) return 'yesterday'
return `${d} days ago`
}
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 — verify against current code.`
}⚠️ 记忆漂移防护
对于引用特定函数、文件路径的记忆,系统要求模型在使用前验证其仍然存在: 文件路径 → 检查文件是否存在;函数名 → grep 搜索;文件:行号引用 → 特别容易过时。 这是经过 eval 验证的防护机制(memory-prompt-iteration case study)。
自动做梦(autoDream)
离线记忆整合 — 四阶段合并流程
自动做梦是记忆系统的核心维护机制。当满足条件时,系统会 fork 一个后台 Agent 执行记忆整合: 浏览最近会话、合并新信息、修剪过期记忆、更新索引。
autoDream 触发流程
三重门控
- 时间门:距上次整合 ≥ 24 小时(一次 stat 调用)
- 会话门:有 ≥ 5 个新会话(扫描节流 10 分钟)
- 合并锁:PID-based 文件锁,防止并发整合
合并锁机制
- 锁文件:
.consolidate-lock - mtime = lastConsolidatedAt 时间戳
- body = holder PID,用于检测存活进程
- 过期阈值 1 小时(PID 复用防护)
- 失败时回滚 mtime(下次可重试)
整合提示词(四阶段)
# Dream: Memory Consolidation
## Phase 1 — Orient
- ls 记忆目录,读取 MEMORY.md 索引
- 浏览现有 topic files 避免重复
## Phase 2 — Gather recent signal
- 读取 daily logs (logs/YYYY/MM/YYYY-MM-DD.md)
- 检查与代码库矛盾的旧记忆
- 精准 grep JSONL transcripts
## Phase 3 — Consolidate
- 合并新信号到现有 topic files
- 转换相对日期为绝对日期
- 删除已被推翻的事实
## Phase 4 — Prune and index
- 更新 MEMORY.md(≤25 行,≤25KB)
- 移除过期指针,缩短冗长条目DreamTask — 做梦任务管理
UI 可见性与生命周期
DreamTask 是 autoDream 的 UI 抽象层,将不可见的 forked agent 暴露为底部任务栏可见的后台任务, 用户可以在 Shift+Down 对话框中查看进度、取消任务。
DreamTask 状态结构
type DreamTaskState = {
type: 'dream'
status: 'running' | 'completed' | 'failed' | 'killed'
phase: 'starting' | 'updating'
sessionsReviewing: number // 正在审查的会话数
filesTouched: string[] // Edit/Write 操作的文件路径
turns: DreamTurn[] // 助手回复(工具调用折叠为计数)
priorMtime: number // 锁的原始 mtime(用于 kill 回滚)
abortController?: AbortController
}生命周期事件
- registerDreamTask:创建任务,status=running, phase=starting
- addDreamTurn:每轮助手回复,phase 首次 Edit/Write 时切为 updating
- completeDreamTask:status=completed,inline 显示修改摘要
- failDreamTask:status=failed,回滚合并锁 mtime
- kill:abort 控制器 + 回滚锁 + status=killed
团队记忆(Team Memory)
跨用户共享记忆 — teamMemPaths.ts
团队记忆是个人记忆的扩展,存储在 memory/team/ 子目录中。 所有在同一项目目录工作的用户共享团队记忆,每个会话开始时自动同步。
安全验证(双层检查)
- 字符串级:path.resolve() 消除 .. 段
- 文件系统级:realpath() 解析符号链接
防御悬挂符号链接、符号链接循环、Unicode 规范化攻击
作用域规则
user→ always privatefeedback→ 默认 private,项目级惯例存 teamproject→ 偏向 teamreference→ usually team
extractMemories — 提取记忆 Agent
后台自动提取对话中的有价值信息
主 Agent 的 prompt 中始终包含完整的保存指令。后台 extract-memories agent 作为安全网: 当主 Agent 主动写入了记忆时后台跳过该范围,未写入时后台 agent 捕获遗漏。
启用条件
- 功能开关
feature('EXTRACT_MEMORIES')必须开启 - 交互式会话 OR GrowthBook flag
tengu_slate_thimble - autoDream 限制工具权限:Bash 仅允许只读命令(ls, find, grep, cat 等)