- Published on
Agent 记忆系统:文件派 VS 数据库派!
- Authors

- Name
- Stone
在我看来,Agent 的记忆系统可以说是长期使用 Agent 体验是否足够好的最重要的一环了。比如你使用 Claude Code,在做某个项目的时候告诉它:这个项目的图片都得使用你封装的 img-box 组件。如果没有 memory 系统,等你下次新开一个聊天窗口的时候,它又直接使用 <img> 标签了。这不能怪它,因为它真没记忆。
什么需要记忆,什么不需要记忆?
能从代码推导的,不存。
项目用什么技术栈?文件结构长什么样?有没有用 TypeScript?这些东西读一下 package.json 和 tsconfig.json 就知道了。存到记忆里反而有害——万一项目迁移了技术栈,旧记忆就成了误导。
能从 Git 推导的,不存。
谁改了什么文件?最近一次部署是什么时候?这些用 git log 和 git blame 一查也知道了。
能从文档推导的,不存。
项目已经在 CLAUDE.md 里写了“用 pnpm 不要用 npm”?那记忆里就不需要再存一遍。重复存储不仅浪费空间,还会在两处信息不一致时造成混乱。
那什么需要存?
只存那些“只存在于对话中、无法从其他地方获取”的信息。
- 比如代码风格。之前我做项目的时候,AI 喜欢按照 Vue 官方的最佳实践,把用到的字段都拆开通过 props 进行传递,但是我们组内的开发习惯喜欢一个 data 直接放进去,等子组件自己去弄。这种只存在于对话里的就要存进去。
- 比如开发者本身的信息。我不太会后端,你要和我解释清楚你每次写的内容,这种也需要存在 memory 中。
Claude Code 的四种记忆类型就是这么来的:user(用户画像)、feedback(行为反馈)、project(项目动态)、reference(外部资源)。
这个背后的排除法思路很重要。记忆系统最常见的问题不是“记得太少”,而是“记得太多”——噪音把有用信息淹没了。
文件派代表:Claude Code
看见 Claude Code 源码的时候都有点震惊了,它的 memory 系统非常简单粗暴,就是纯文本文件。
双层架构
Claude Code 的记忆系统是一个“索引 + 正文”的双层结构:
第一层:MEMORY.md(索引文件)
这是一个不超过 200 行的 Markdown 文件,放在 ~/.claude/projects/<项目路径>/memory/ 目录下。它的角色是目录页——每一行是一条指针,指向一个具体的记忆文件:
- [用户偏好](user_preferences.md) — 后端出身,前端不熟,偏好简洁代码
- [项目规范](project_conventions.md) — pnpm,vitest,commit 用中文
- [部署拉闸](deploy_freeze.md) — 2026-04-15 前不合并非关键 PR
第二层:独立记忆文件
每个记忆是一个独立的 .md 文件,带 YAML frontmatter:
---
name: 项目规范
description: 包管理器、测试框架、commit 规范等项目约定
type: feedback
---
用 pnpm 不要用 npm。
**Why:** 用户明确要求,团队统一规范。
**How to apply:** 所有 install/add 命令用 pnpm。
测试用 vitest 不要用 jest。
**Why:** 项目已配好 vitest,jest 会引入额外依赖。
frontmatter 里的 description 字段很关键——后面精选注入的时候,就是靠这个字段来判断这条记忆跟当前任务有没有关系。
为什么 Claude Code 要弄两层?一层不可以吗?
简单说:一层全放一起会撑爆上下文窗口,两层就能做到“先看目录,需要再看详情”。
打个比方。你家里有 500 本书,如果全部摊在书桌上(单层),你根本没地方写字。双层结构就是:书桌上只放一张目录索引(MEMORY.md),告诉你每本书大概讲什么、放在哪个书架上。你需要哪本,再去书架上拿(独立的记忆文件)。
工程上的原因很直接:
MEMORY.md 每次会话都会被塞进 system prompt 里,相当于每次干活前先给你看一遍“项目情报总览”。AI 的上下文窗口是有 token 上限的,如果 500 条记忆全量加载,光记忆就吃掉几十万 token,还没开始干活上下文就爆了。所以 MEMORY.md 被限制在 200 行以内,一行一条索引,只占用极少的上下文空间。
而那些独立的记忆文件(比如 项目规范.md、部署拉闸.md),平时不加载,只有 AI 判断当前任务跟某条记忆相关时,才临时拽进来——这就是文章里说的“精选注入”,每轮最多注入 20KB,避免撑满上下文。
所以双层结构的本质是:用最小的上下文代价,让 AI 知道“我有哪些记忆可用”,然后按需取用具体内容。
200 行上限的暴力约束
MEMORY.md 有一个硬限制:200 行或 25KB,谁先到谁算数。 超了就截断,末尾追加一条警告。
你可能觉得 200 行太少了。但这恰恰是一个很聪明的设计决策。
物理上限倒逼 Agent 只保留最重要的记忆。就像你的桌面如果只能放 10 本书,你自然会精挑细选——反过来,如果桌面无限大,你就会把什么都往上堆,最后找什么都找不到。

Claude Code 的记忆存储除了你手动让它记住以外,还会“偷偷记忆”:
每次 query 结束后(模型的最终响应不再有工具调用时),系统会 fork 一个后台 Agent,去分析最近几轮对话,提取值得记住的信息。
这个后台 Agent 的行为被严格限制了:
- 只有 5 轮工具调用预算(防止它自己跑偏)
- 只能读不能写代码(Read/Grep/Glob 可以,但只对记忆目录有 Edit/Write 权限)
- 跟主 Agent 互斥——如果主 Agent 这一轮已经手动写了记忆,自动提取就跳过,避免重复
- 失败了不会阻塞用户——后台跑的,静默失败,下一轮重试
这个设计非常优雅。用户完全感知不到后台在帮它记东西,但下次会话开始的时候,那些重要的信息就已经在记忆里了。
数据库派:OpenClaw 的 SQLite + 向量方案
Claude Code 的文件方案简单、透明、人类可读。但它有一个天花板:记忆量上去之后,文件检索效率不够。
你有 20 条记忆,MEMORY.md 扫一眼就知道哪条相关。你有 500 条呢?靠 Sonnet 扫 500 个 frontmatter 来挑,成本和延迟都扛不住。
而且文件方案做不了语义搜索。你问“上次部署出了什么问题”,文件方案只能靠文件名和 description 里的关键词匹配。但如果记忆文件的标题是“3 月 15 日线上事故”,“部署”这个词根本没出现,文件方案就找不到了。
OpenClaw 的方案说白了就是:把记忆存进数据库,然后用“语义 + 关键词”两条腿一起搜,最后老记忆会自动降权。
具体拆开来看,核心就三个设计:
1. 三种索引同时工作
一条记忆存进 SQLite 的时候,OpenClaw 会同时给它建三份索引——原文存 chunks 表,向量存 chunks_vec 表(用 sqlite-vec 扩展),关键词存 chunks_fts 表(用 FTS5 全文搜索)。这就好比你不光把书放书架上了,还同时做了书摘卡片(向量)和关键词标签(FTS5),找的时候不用一本本翻。
2. 混合检索:70% 语义 + 30% 关键词
搜记忆的时候,向量搜和关键词搜各跑一遍,结果按 7:3 加权合并。为什么要混?因为纯向量搜索有个毛病——语义相似不代表任务相关(你问“怎么部署”,它可能给你“部署架构图”而不是“部署命令”)。关键词搜能兜住这些边缘情况,但关键词搜又找不到同义词表达。混起来正好互补。
3. 时间衰减 + 压缩前存档
默认 30 天半衰期,老记忆自动降权(不过 MEMORY.md 这种常驻文件不衰减)。另外,在上下文快满触发压缩之前,OpenClaw 会让 Agent 先把当前对话里值得长期记的信息写入记忆——这叫 Memory Flush。等于在清空工作台之前,先把重要的东西抄到本子上,不至于丢了再也找不回来。
这个方案跟 Claude Code 的文件派最大的区别就是:当记忆量大到几百条、需要语义搜索的时候,文件扫不过来了,数据库加向量的组合才能扛住。

文件派和数据库派怎么选择?
简单说,选文件还是数据库,核心看两件事:记忆量大小和要不要语义搜索。
文件派就像你桌面上摆几本常用笔记本——20 条记忆,翻一下目录(MEMORY.md)就能找到。简单、透明、人能直接改。Claude Code 每天几十万开发者这么用,够了。
数据库派就像图书馆检索系统——500 条记忆,靠翻目录得翻到啥时候?得靠搜索引擎。SQLite + 向量索引,你说“上次部署出啥问题”,它能找到标题叫“3 月 15 日线上事故”的那条记忆,因为语义相近,哪怕关键词完全不沾边。
给个粗暴的标准:单人用、记忆少于 200 条,文件够了。多人用、记忆超 500 条、需要语义搜,上数据库。 可以从文件起步,撑不住了再迁移,不用一开始就搞复杂架构。
