文章总结: 文档深度拆解ClaudeCode五层记忆系统架构,从指令记忆到上下文注入层层递进。核心创新包括ForkedAgent异步提取、Sonnet二阶检索、权限沙箱隔离和团队记忆同步机制。重点分析了路径穿越防护、Symlink安全、PromptCache优化等关键技术。通过eval实验数据验证设计决策,为构建安全可靠的AI记忆系统提供了完整的架构蓝图和工程实践参考。 综合评分: 95 文章分类: AI安全,代码审计,安全建设,安全开发,实战经验
Claude_Code_记忆系统架构深度拆解
原创
辞令 辞令
WhITECat安全团队
2026年4月6日 20:12 上海
———–后台回复“claude”获取源码,便于对照研究
Claude Code
记忆系统架构深度拆解
Memory System Architecture Analysis
基于 claude-code-main 源码深度分析
2026 · 安全架构研究
一、整体架构鸟瞰
Claude Code 的记忆系统是多层次、文件驱动的架构。其核心设计哲学是:
记忆 ≠ 对话历史。记忆是跨会话持久的结构化知识,对话历史只存活于当前 session。
整体分为 5 层,从底向上依次为:
| 层级 | 名称 | 核心机制 | 文件位置 | | — | — | — | — | | Layer 5 | 上下文注入层 | system prompt / user context block | context.ts / memdir.ts | | Layer 4 | 智能检索层 | findRelevantMemories (Sonnet 侧查询) | memdir/findRelevantMemories.ts | | Layer 3 | 自动提取层 | extractMemories (后台 forked agent) | services/extractMemories/ | | Layer 2 | 文件存储层 | memdir (~/.claude/projects//memory/) | memdir/paths.ts | | Layer 1 | 指令记忆层 | CLAUDE.md 体系 (Managed/User/Project/Local) | utils/claudemd.ts |
二、Layer 1:指令记忆层(CLAUDE.md 体系)
文件:src/utils/claudemd.ts
这是静态的「行为规范」层,由人工配置,不是 AI 自主写入的。用于定义 AI 在特定项目、用户或机构场景下的行为约束。
2.1 加载顺序(优先级从低到高)
越靠近当前工作目录(CWD)的文件优先级越高,后加载意味着更高权重,对应「局部覆盖全局」的直觉:
| 优先级 | 类型 | 路径 | 特点 | | — | — | — | — | | 最低 (1) | Managed | /etc/claude-code/CLAUDE.md | 全局策略,机构级管控 | | 低 (2) | User | ~/.claude/CLAUDE.md | 用户私有全局指令 | | 中 (3) | Project | ./CLAUDE.md / ./.claude/CLAUDE.md | 项目级,已提交到 repo | | 高 (4) | Local | ./CLAUDE.local.md | 项目私有,gitignored | | 最高 (5) | AutoMem | ~/.claude/projects/…/MEMORY.md | AI 自主写入的记忆索引 |
2.2 @include 指令
CLAUDE.md 文件支持引用其他文件,用 marked lexer 扫描 AST token,只在非代码块文本节点提取 @path:
@./shared/coding-standards.md
@~/my-global-rules.md
@/absolute/path/to/rules.md
• 最大嵌套深度:5 层(MAX_INCLUDE_DEPTH = 5)
• 防止循环引用:processedPaths Set 追踪已处理路径
• 支持 ~/、./、/absolute 三种路径前缀
• 非文本格式文件(图片、PDF)自动跳过
2.3 条件规则(.claude/rules/*.md)
带 paths: frontmatter 的规则文件仅在操作匹配文件时才注入上下文:
paths:
– “src/api/**”
– “*.go”
所有 Go 文件必须包含错误处理…
这意味着编辑 Go 文件时才加载 Go 规范,编辑 API 文件时才加载 API 规范,有效控制 context window 使用量。
三、Layer 2:文件存储层(memdir)
文件:src/memdir/
这是 AI 自主读写的持久记忆目录,是整个记忆系统的数据层基础。
3.1 目录结构
~/.claude/
projects/
/ ← 按 git 根目录隔离
memory/
MEMORY.md ← 索引文件 (entrypoint)
user_role.md ← 用户信息记忆
feedback_testing.md ← 行为反馈记忆
team/ ← 团队共享记忆 (需开启 TEAMMEM)
MEMORY.md
project_deadline.md
路径解析优先级(src/memdir/paths.ts):
• CLAUDE_COWORK_MEMORY_PATH_OVERRIDE 环境变量(Cowork/SDK 使用)
• settings.json 中的 autoMemoryDirectory(支持 ~/ 展开)
• 默认: /projects//memory/
注意:为了多 worktree 共享同一记忆,路径基于 findCanonicalGitRoot() 而非 CWD,保证同一 repo 的不同 worktree 共享记忆。
3.2 四种记忆类型
来自 src/memdir/memoryTypes.ts,每种类型有明确的 scope 和语义:
| 类型 | 默认 Scope | 保存时机 | 使用时机 | | — | — | — | — | | user | 始终私有 | 了解用户角色、偏好、知识背景时 | 工作需要基于用户画像时 | | feedback | 私有(项目规范→团队) | 用户纠错或确认非显式做法时 | 避免重复犯同类错误时 | | project | 强烈建议团队 | 了解进展、截止日期、决策背景时 | 理解请求背后动机时 | | reference | 通常团队 | 发现外部系统资源时 | 引用外部系统信息时 |
3.3 记忆文件格式
每个记忆文件使用 YAML frontmatter 标注元数据,内容遵循结构化模板:
name: 用户背景
description: 用户是数据科学家,关注可观测性
type: user
[记忆正文]
**Why:** 用户提到 logging 调研项目
**How to apply:** 回答时侧重可观测性角度
Why + How to apply 的结构化要求源于 eval 验证:知道”为什么”可以让模型判断边界情况,而不是盲目执行规则。
3.4 MEMORY.md 索引文件
MEMORY.md 是轻量索引,不是内容本身,每行一条指针:
-
用户背景 — 数据科学家,Go 专家,React 新手
-
测试规范 — 不允许 mock 数据库
硬性限制(避免撑爆 system prompt):
• 最大行数:200 行(MAX_ENTRYPOINT_LINES)
• 最大字节:25,000 字节(MAX_ENTRYPOINT_BYTES)
• 超限自动截断并附 WARNING 提示模型
3.5 不该保存的内容(明确排除)
代码中明确定义了记忆的「负边界」,即使用户明确要求也拒绝保存:
• 代码模式、约定、架构、文件路径 — 可以通过 grep/读代码获取
• Git 历史、最近变更 — git log / git blame 更权威
• 调试方案或修复配方 — 答案在代码里,背景在 commit message
• 已在 CLAUDE.md 文档化的内容 — 重复保存
• 临时任务细节、进行中的工作 — 只在当前会话有用
四、Layer 3:自动提取层(extractMemories)
文件:src/services/extractMemories/extractMemories.ts
这是整个架构最精妙的设计:AI 在每轮对话结束后自动从对话中提取值得记住的内容,写入持久记忆目录。
4.1 触发时机
每次主 agent 产出最终回复(无 tool call 的 stop 事件)时,fire-and-forget 异步触发提取流程。由 handleStopHooks 在 stopHooks.ts 中调用。
4.2 Forked Agent 模式
提取器使用 runForkedAgent 模式,关键设计:
| 特性 | 设计 | 原因 | | — | — | — | | Prompt Cache 共享 | 与主 agent 使用相同 cache key | 提取几乎不增加额外 token 成本 | | 工具权限沙箱 | 只允许读取 + 仅限 memdir 的写入 | 防止提取 agent 误改代码 | | 最大轮数限制 | maxTurns: 5 | 防止验证死循环消耗大量 turns | | 不记录 transcript | skipTranscript: true | 避免与主线程产生竞态条件 | | 互斥机制 | inProgress 标志 + pendingContext stash | 防止并发运行 |
4.3 游标机制
每次提取只处理增量消息,不重复处理已处理的历史:
let lastMemoryMessageUuid: string | undefined
// 只计算上次游标之后的新消息数
const newMessageCount = countModelVisibleMessagesSince(
messages, lastMemoryMessageUuid
)
// 成功后推进游标
lastMemoryMessageUuid = messages.at(-1)?.uuid
4.4 主 Agent 互斥
如果主 agent 已经自己写了记忆(更主动、更精确),跳过背景提取,推进游标:
if (hasMemoryWritesSince(messages, lastMemoryMessageUuid)) {
// 跳过背景提取,推进游标
lastMemoryMessageUuid = lastMessage.uuid
return
}
4.5 工具权限沙箱细节
createAutoMemCanUseTool() 定义了精确的权限边界:
• ✅ 允许:FileRead、Grep、Glob(无限制读取)
• ✅ 允许:Bash(仅 isReadOnly 的命令:ls/grep/cat/stat 等)
• ✅ 允许:FileWrite/FileEdit(仅限 memoryDir 路径内)
• ❌ 拒绝:所有其他工具
REPL 工具被允许,因为它内部的每个操作仍会经过 canUseTool 检查,保持相同的安全约束。
4.6 流量控制
• inProgress 标志:防止同一时间并发运行多个提取
• pendingContext stash:并发请求被 stash,extraction 结束后执行一次 trailing run
• turnsSinceLastExtraction:支持 N 轮节流(tengu_bramble_lintel 功能开关)
• drainPendingExtraction():进程关闭前等待所有提取完成(60s 超时)
五、Layer 4:智能检索层(findRelevantMemories)
文件:src/memdir/findRelevantMemories.ts
问题:记忆越来越多,全部加载会撑爆 context window。方案:用 Sonnet 做二阶检索,只加载当前查询真正相关的记忆。
5.1 检索流程
| 步骤 | 操作 | 说明 | | — | — | — | | 1 | scanMemoryFiles() | 扫描所有 .md 文件,只读 frontmatter 前 30 行 | | 2 | formatMemoryManifest() | 格式化为文本清单,含类型/文件名/时间戳/描述 | | 3 | sideQuery(Sonnet) | 让 Sonnet 判断哪些记忆和当前查询相关 | | 4 | 返回最多 5 个 | 过滤已展示过的记忆(alreadySurfaced),避免重复 | | 5 | 注入上下文 | 按需加载完整记忆文件内容到对话上下文 |
5.2 Sonnet 检索 Prompt 的设计细节
检索 prompt 经过精心设计,有几个关键约束:
• “up to 5” 上限:防止过度注入导致 context 膨胀
• “CERTAIN will be helpful”:不确定就不选,宁可遗漏不要误选
• recently used tools 排除:正在用的工具不需要加载它的使用文档
• warnings/gotchas 例外:工具相关的警告/坑点仍然加载,正在使用时最重要
5.3 新鲜度机制(memoryAge.ts)
记忆超过 1 天,自动附加 staleness warning 防止过时数据被当作事实:
export function memoryFreshnessText(mtimeMs: number): string {
const d = memoryAgeDays(mtimeMs)
if (d <= 1) return ” // 新鲜记忆:无警告
return This memory is ${d} days old. +
Claims about code behavior may be outdated. +
Verify against current code before asserting as fact.
}
5.4 扫描性能优化
scanMemoryFiles() 的单次扫描设计减少了 syscall 开销:
• readFileInRange() 内部 stat,一次调用同时获取 mtimeMs 和内容
• 只读前 30 行(FRONTMATTER_MAX_LINES):只需要 frontmatter,不读全文
• 并行处理所有文件(Promise.allSettled)
• 最多处理 200 个文件(MAX_MEMORY_FILES)
• 按 mtime 降序排序:最近修改的优先
六、Layer 5:上下文注入层
文件:src/memdir/memdir.ts, src/context.ts, src/utils/claudemd.ts
6.1 两种注入路径
| 注入路径 | 时机 | 内容 | 特点 | | — | — | — | — | | System Prompt | 会话开始时固定 | Git 状态快照 + 记忆系统行为指令 + MEMORY.md 索引 | 静态,整会话缓存 | | User Context | 每轮对话前动态注入 | CLAUDE.md 内容 + 相关记忆文件 + 当前日期 | 动态,按需更新 |
6.2 buildMemoryLines():行为指令注入
这个函数向模型注入记忆系统的完整「操作手册」,包括:
• 四种类型的定义,每种附带 when_to_save 和 how_to_use
• 明确的不该记的内容列表(代码模式、git 历史、临时状态等)
• 两步保存流程:先写 topic 文件,再更新 MEMORY.md 索引
• 记忆与 Plan、Tasks 的使用边界区分
• staleness 验证要求:推荐前先验证文件/函数是否仍然存在
6.3 Prompt 设计中的 A/B 测试证据
代码注释中包含大量 eval 实验结果,说明 prompt 经过严格验证:
| 实验 | 失败方案 | 成功方案 | 效果 | | — | — | — | — | | H1 验证指令位置 | 放在 ‘When to access’ 下的 bullet | 独立的 H2 section | 0/3 → 3/3 | | H5 read-side 噪声 | 作为 bullet 嵌套 | 独立 section | 0/2 → 3/3 | | H6 ignore 反模式 | 无说明 | 显式命名 anti-pattern | eval score 提升 | | Section 标题措辞 | ‘Trusting what you recall’ (抽象) | ‘Before recommending from memory’ (行动导向) | 0/3 → 3/3 |
6.4 TRUSTING_RECALL_SECTION 核心哲学
这个 section 体现了记忆系统设计的核心哲学:
“The memory says X exists” is not the same as “X exists now.”
具体规则:
• 记忆中提到特定函数/文件/flag → 在推荐前先验证它还存在
• 记忆中总结了 repo 状态 → 用 git log 或读代码替代,不直接引用
• 用户说「忽略记忆」→ 视 MEMORY.md 为空,不引用、不对比、不提及
七、特殊模式
7.1 KAIROS 模式(助手日志模式)
为持久运行的 assistant session 设计的长会话记忆策略:
| | 普通模式 | KAIROS 模式 | | — | — | — | | 触发条件 | 默认开启 | feature(‘KAIROS’) 且 getKairosActive() | | 记忆写入方式 | extractMemories 提取 → 写 topic 文件 | append-only 写日志文件 | | 日志路径 | 不适用 | logs/YYYY/MM/YYYY-MM-DD.md | | 索引维护 | 实时更新 MEMORY.md | 夜间 /dream skill 批量提炼 | | 适用场景 | 普通短对话 | 24h 常驻 assistant session |
日志文件 append-only 的原因:assistant session 太长,实时维护索引代价太高。/dream skill 是定时批处理,将日志提炼成结构化 topic 文件。
7.2 团队记忆(TEAMMEM)
需要 feature(‘TEAMMEM’) 功能开关,记忆目录变成 private/team 双层:
memory/
MEMORY.md ← 私有索引
user_role.md ← 私有记忆
team/
MEMORY.md ← 共享索引(组织内同步)
project_deadline.md ← 团队共享记忆
AI 根据记忆类型的字段自动决定写入私有还是团队目录。团队记忆的限制:
• API Key、用户凭证 — 绝对禁止进入团队目录(prompt 中显式声明)
• 个人通信偏好 — 应写私有,不是团队规范
7.3 路径安全机制(团队记忆写入)
teamMemPaths.ts 中实现了双阶段路径验证,明确枚举了攻击向量:
| 攻击向量 | 防护机制 | | — | — | | ../路径穿越 | path.resolve() 消除 .. 后检查前缀 | | Symlink 逃逸 | realpath() 解析符号链接后验证真实路径 | | 悬空 Symlink | lstat() 检测链接本身是否存在 | | Null Byte 注入 | 显式检查 \0 字符 | | URL 编码绕过 (%2e%2e%2f) | decodeURIComponent 后再检查 | | Unicode NFKC 归一化攻击 | normalize(‘NFKC’) 后检查全角字符 | | 反斜杠 Windows 路径 | 显式拒绝包含 \ 的路径 |
八、对话输入历史(history.ts)
文件:src/history.ts
这是用户 shell 输入的历史记录(类似 bash history),不是 AI 对话记忆。
| 属性 | 值 | 说明 | | — | — | — | | 存储路径 | ~/.claude/history.jsonl | 全局历史,append-only JSONL | | 最大条数 | 100 条(MAX_HISTORY_ITEMS) | 滚动窗口,最新优先 | | Paste 大小阈值 | 1024 字节(MAX_PASTED_CONTENT_LENGTH) | 小内容内联,大内容存 hash 引用 | | 并发保护 | 锁文件(retry 3 次) | 避免并发写入冲突 |
8.1 大 Paste 处理
超过 1024 字节的粘贴内容存独立的 paste store,history.jsonl 只存 hash 引用:
• 历史条目:{ contentHash: “sha256…”, type: “text” }
• 实际内容:单独的 paste store 文件,按 hash 寻址
• 读取时:lazy resolve,ctrl+r 选中时才加载完整内容
8.2 Esc 撤销机制
Esc 中断后可以撤销最后一条历史记录,避免「提交了 prompt 又 Esc 回滚后重复出现」的问题:
• 快路径:从 pending buffer 直接移除(还未写盘)
• 慢路径:已写盘 → 加入 skippedTimestamps Set,读取时过滤
九、核心设计总结
9.1 设计决策汇总
| 设计维度 | 决策 | 核心理由 | | — | — | — | | 存储格式 | Markdown + YAML frontmatter | 人类可读、可 grep、可用 git 管理 | | 索引结构 | MEMORY.md(轻量索引)+ 独立 topic 文件 | 平衡 context budget 与可扩展性 | | 提取时机 | 每轮对话末尾,后台 forked agent | 不阻塞主响应,共享 prompt cache 省 token | | 检索策略 | Sonnet 二阶检索(先扫 frontmatter,再 LLM 判断) | 只读少量元数据,按需加载完整内容 | | 新鲜度管理 | mtime 追踪,>1 天自动附警告 | 防止过时记忆被当作当前事实 | | 写入权限 | 专用工具沙箱,仅限 memdir 路径 | 提取 agent 不能写代码,防止副作用 | | 团队协作 | private/team 双目录 | 明确隐私边界,API key 禁止进团队 | | 路径安全 | realpath + resolve 双重验证 | PSR 安全评审驱动,symlink 逃逸为重点 |
9.2 设计哲学总结
整个记忆系统的本质是:
把 LLM 的 stateless 限制转化为有结构的文件系统操作,用工程手段模拟了人类记忆的核心特征。
| 人类记忆特征 | 工程实现方式 | | — | — | | 选择性遗忘(不重要的自然淡出) | 按需检索而非全量加载,findRelevantMemories 只返回相关记忆 | | 记住关键的(重要的长期保留) | 结构化提取,背景 forked agent 每轮自动识别 | | 随时间衰减(越旧越不可靠) | mtime 追踪 + 新鲜度警告机制 | | 验证后才相信(不是绝对真理) | TRUSTING_RECALL_SECTION 的核心哲学 | | 分工协作(个人与团队知识分离) | private/team 双目录,scope 明确区分 |
9.3 文件索引
本次分析涵盖的核心源码文件:
| 文件路径 | 模块职责 | | — | — | | src/memdir/memoryTypes.ts | 记忆类型定义(user/feedback/project/reference) | | src/memdir/memdir.ts | 核心:prompt 构建、目录管理、内容截断 | | src/memdir/paths.ts | 路径解析、安全验证、autoMemory 开关 | | src/memdir/memoryScan.ts | 记忆文件扫描、frontmatter 解析 | | src/memdir/findRelevantMemories.ts | 智能检索:Sonnet 二阶选择 | | src/memdir/memoryAge.ts | 新鲜度计算与 staleness 警告 | | src/memdir/teamMemPaths.ts | 团队记忆路径管理与安全验证 | | src/memdir/teamMemPrompts.ts | 团队+私有组合 prompt 构建 | | src/services/extractMemories/extractMemories.ts | 自动提取主逻辑,forked agent 模式 | | src/utils/claudemd.ts | CLAUDE.md 体系文件发现与加载 | | src/context.ts | 系统/用户上下文构建(git status + claude.md) | | src/history.ts | 用户输入历史管理(jsonl 持久化) |
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:WhITECat安全团队 辞令 辞令《ClaudeCode记忆系统架构深度拆解》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。












评论