# AI 的记忆系统：四种记忆类型与语义召回


<!-- more -->


上一篇文章聊了上下文压缩——它解决的是"当前会话能记住多少"的问题。但编程助手还需要另一种记忆：**跨会话的长期记忆。**

你关闭 Claude Code，明天重新打开。它应该知道什么？
- 你偏好用 TypeScript 而不是 JavaScript
- 你上次说"不要用 underscore 库"
- 这个项目用 Vitest 而不是 Jest
- 你曾经纠正过它的一个理解错误

上下文压缩管不了这些。会话结束了，对话历史清空了，明天是全新的窗口。如果 AI 每次都要你重新解释一遍，它就不是助手，是负担。

Claude Code 的记忆系统要回答的问题是：**AI 应该记住什么、存在哪里、怎么找到、什么时候用。**

## 四种记忆类型

Claude Code 把记忆分成四类，每类对应不同的"记住什么"：

**用户记忆（User Memory）**——关于用户个人的偏好和习惯。"我喜欢简洁的代码风格""我用 macOS""我的邮箱是 xxx"。这类记忆跟随用户，不跟随项目。换一台电脑、换一个项目，这些记忆仍然有效。

**反馈记忆（Feedback Memory）**——用户纠正或表扬的记录。"上次你理解错了，我不是要说 A 而是 B""你上次写的测试很好，保持这种风格"。这类记忆是 Agent 的自我改进燃料——它让 AI 从历史互动中学习。

**项目记忆（Project Memory）**——关于当前项目的约定和状态。"这个项目用 Next.js App Router""错误处理统一用 toApiError()""部署用 Vercel"。这类记忆跟随项目，不跟随用户。同一个人换项目，记忆跟着变。

**引用记忆（Reference Memory）**——用户明确标记为"重要参考"的信息。"记住这个文档链接，以后经常要用""这个 API 的认证方式比较特殊，记下来"。这类记忆是用户主动创建的"书签"。

四分类的本质是**记忆的作用域**：用户记忆是全局的（所有项目共享），项目记忆是局部的（特定项目），反馈记忆是交叉的（用户+项目的组合），引用记忆是手动的（用户显式控制）。

## 记忆的存储格式

Claude Code 的记忆不是存在数据库里，而是存在**文件系统**上——每个记忆条目是一个带 YAML frontmatter 的 Markdown 文件。

一个记忆文件可能长这样：

```markdown
---
type: user
tags: [programming, preference]
created: 2026-06-15
lastAccessed: 2026-07-01
---

用户偏好使用函数式编程风格，避免可变状态。
```

用文件系统存记忆，看似原始，实际有几个好处：

- **可版本化。** Git 可以追踪记忆的变更历史，你知道这条记忆是什么时候创建的、谁创建的、改过几次。
- **可审查。** 用户可以随时打开文件看 AI 记住了什么、删掉不想被记住的。
- **可移植。** 记忆文件可以随项目一起 clone，不需要额外的数据库或云服务。
- **可搜索。** 用 `grep` 就能搜记忆内容，不需要专门的查询接口。

这是一种"文件即数据库"的设计哲学——和 CLAUDE.md 一脉相承。不引入额外的存储层，用现有文件系统解决一切。

## 语义召回：不是关键词匹配

记忆存了怎么找？如果靠关键词匹配，"我喜欢简洁代码"这条记忆在用户说"帮我写个函数"时可能匹配不上——因为没有共同的关键词。

Claude Code 用的是**语义召回（Semantic Recall）**——通过嵌入模型（Embedding）将记忆条目和当前查询都转化为向量，计算余弦相似度，找到语义上最相关的记忆。

具体流程：
1. 用户发起请求时，系统提取查询的语义向量
2. 在记忆库中计算每条记忆与查询的相似度
3. 取 Top-K 最相关的记忆
4. 将这些记忆注入 system prompt 的"记忆"层

这意味着即使用户说"写点代码"，系统也能召回"用户偏好简洁代码风格"这条记忆——因为语义上相关，即使关键词不匹配。

语义召回的精度取决于嵌入模型的质量。好的嵌入模型能捕捉细微的语义差异；差的嵌入模型会把不相关的记忆拉进来，或者漏掉相关的记忆。Claude Code 使用侧查询（sideQuery）机制——在主要任务之外，额外调用一次嵌入模型来查找相关记忆。

## 预加载机制

语义召回有一个性能问题：每次用户请求都要查一遍记忆库，如果记忆库有几百条，每次都要计算几百次向量相似度。

Claude Code 的优化策略是**预加载（Prefetch）**——在会话启动时，预先加载与当前项目最相关的记忆子集，缓存起来。后续请求先查缓存，只在缓存不够时才做完整的语义搜索。

预加载的逻辑是：项目记忆优先（当前项目相关的记忆最可能用到），其次是用户记忆（用户偏好总是相关），最后是反馈记忆和引用记忆。

这本质上是一个**缓存策略问题**——和 CPU 的 L1/L2 缓存、浏览器的 HTTP 缓存是同一类问题。热点数据提前加载，冷门数据按需获取。

## 新鲜度警告

记忆会过期。你半年前说"偏好用 Redux"，现在可能已经迁移到 Zustand 了。如果 AI 还在引用那条过期记忆，它会做出错误的判断。

Claude Code 的记忆系统有一个**新鲜度警告**机制——当注入的记忆条目超过一定年龄（比如 90 天），系统会在 prompt 中标注"这条记忆可能已过时"。模型看到这个标注后，会谨慎使用这条记忆，或者主动向用户确认。

这揭示了一个深层问题：**记忆的价值随时间衰减。** 不是所有记忆都 equally 持久——用户的基本偏好（"我用 macOS"）可能几年不变，但项目状态（"用 Redux"）可能几周就过时。

理想的方案是对不同记忆类型设置不同的 TTL（Time To Live），或者用置信度衰减函数代替硬阈值。但当前实现用的是简单的时间阈值——它不完美，但它让模型知道"这条信息可能不可靠"。

## 记忆系统的边界

Claude Code 的记忆系统不是万能的。它有几个明确的边界：

**它不记住代码内容。** 记忆系统存的是"元信息"（偏好、约定、反馈），不是代码本身。代码存在文件里，需要就读文件。

**它不记住完整的对话历史。** 对话历史是短期记忆，由上下文压缩管理。记忆系统是长期记忆，只存摘要级别的结论。

**它不跨用户共享。** 用户记忆是个人私有的，项目记忆是项目级别的。两个不同的人在同一项目上使用 Claude Code，有不同的用户记忆，共享项目记忆。

这些边界不是技术限制，是**设计选择**。记忆系统如果什么都记，就变成了一个混乱的日志文件。有边界的记忆才是有用的记忆。

## 记忆系统的哲学

Claude Code 的记忆系统背后有一条设计哲学：

**AI 的记忆不应该是黑盒。** 用户应该知道 AI 记住了什么、能查看、能修改、能删除。记忆存在文件系统的明文文件里，而不是某个不可见的云端数据库。

这和传统软件的"用户数据"设计一脉相承——你的文件在你的硬盘上，你的设置在可读取的配置文件中。AI 的记忆也不例外。

这也意味着记忆系统有一个天然的安全模型：**文件系统权限即记忆权限。** 谁能访问项目目录，谁就能看到项目记忆。谁能访问用户目录，谁就能看到用户记忆。不需要额外的访问控制层。

## 延伸阅读：BYF 的 Wire Records —— 记忆的另一种形态

[BYF](https://github.com/ByronFinn/byf) 没有采用 Claude Code 的文件系统记忆，而是选择了**事件溯源（Event Sourcing）** 作为跨会话持久化的核心手段。

BYF 的所有状态变更操作都以 JSONL 格式记录到 `wire.jsonl`——用 BYF 自己的话说，"Wire Records 是事件溯源持久化层"。每个 turn 的每一步（用户输入、模型回复、工具调用、工具结果）都是独立的记录。会话恢复时，回放这些记录即可重建内存状态。BYF 的 `AgentRecords` 模块就是"回放引擎"，支持协议版本迁移，确保旧会话在新版 BYF 上仍然可恢复。

这种设计的优势是**可调试性**：BYF 专门有一个 `vis` 工具（可视化调试器），读取 `$BYF_HOME/sessions` 下的 wire records，渲染为可浏览的时间线/树形视图。你可以回看每一次 tool call 的请求和响应、每一步的 token 消耗、每一轮压缩的触发点——这不是记忆，这是**审计线索**。

BYF 还利用 wire records 实现了**会话分叉（Fork）**——从现有会话创建新会话，原会话不变。实现为完整目录复制 + `state.json` 重写。与 Git 分支不同，BYF 操作的是会话记录而非工作树文件。这让用户可以在不丢失历史的前提下探索不同的修改路径——"如果我在这里换一种方案会怎样？"

BYF 的选择揭示了一个事实：记忆系统不只是"AI 记住了什么"，还有"人的哪些操作被记录、可回溯、可探索"。记忆服务于 AI，记录服务于人。

## 下一篇

记忆让 AI 记住"谁在用它"和"它在干什么项目"。但光有记忆还不够——AI 还需要**专业技能**来处理特定类型的任务。下一篇文章我们要拆解 Claude Code 的 **Skill 系统**：可复用的专业知识封装。为什么 Skill 不是插件而是"提示词模块"？按需加载和全量注入之间到底该怎么取舍？

---

> 本系列基于 [Claude Code 官方源码](https://github.com/anthropics/claude-code)项目进行架构分析，聚焦设计思想而非代码实现。

