尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

MCP Memory:用SQLite FTS5与OKF为AI Agent构建长期记忆

MCP Memory:用SQLite FTS5与OKF为AI Agent构建长期记忆 MCP Memory 是给 AI Agent 补长期记忆用的 MCP Server。它最近在 Hacker News 上以 Show HN 的形式出现主打 Fast Agent Memory Using Googles OKF and SQLite FTS5核心思路不是接一个大而重的向量数据库而是用 SQLite FTS5 做全文检索配合 OKF 这种结构化对象格式把实体、关系和观察记录成可查询、可更新的记忆。这个方向最近很热原因很直接Agent 在单次会话里表现不错但一旦上下文断了它就像换了个人产品体验很割裂。这篇文章会从 MCP Memory 到底解决什么问题、本地怎么跑通、数据模型和索引怎么设计、批量导入和长期使用有哪些坑这几个角度拆一遍。如果你正在做 Agent 工具链、MCP Server或者想给 Claude、Codex、自研 Agent 加记忆层这篇对你有用。如果你想在本地先验证不需要 GPU也不需要申请额外的 embedding APISQLite FTS5 就足够起步。先说结论这个方案最值得关注的地方是它绕开了“只要做记忆就必须上向量库”的惯性思路。它用结构化对象 全文索引的方式解决了一部分真实问题尤其是实体名、项目名、函数名、用户偏好这类精确匹配场景。后面我会把 OKF 怎么理解、FTS5 为什么够快、什么时候该换向量检索都讲清楚。1. 先搞清楚 MCP Memory 到底在解决什么问题1.1 Agent 没有记忆对话会反复“失忆”大模型本身是无状态的。同一个模型你给它一段上下文它能回答上下文删掉它不会记住你是谁也不会记住刚才答应过什么。很多 Agent 产品看起来“有记忆”其实只是把聊天记录塞回上下文窗口。这种做法的边界很明显上下文窗口有限塞不下长期历史。无关历史会让回答变慢也容易干扰判断。用户偏好、项目决策、已完成的结论散落在大量对话里没法被稳定复用。不同会话之间无法共享信息换一个 session 就前功尽弃。MCP Memory 要解决的就是这个问题让 Agent 有一个独立于会话之外的长期存储能写能查能更新。它不追求把全部历史都塞进来而是只记录对后续行为有用的结构化事实。1.2 MCP、Memory、RAG、Skill 不是同一层的东西做 Agent 的同学很容易把 MCP、RAG、Memory、Skill 混在一起。这几个概念有关系但不是一回事。MCP 是模型上下文协议可以理解成 Agent 与外部工具之间的标准接入方式。它解决的是“Agent 怎么调用你的工具”不解决“Agent 怎么记忆”。RAG 是从外部知识库里检索相关内容再把检索结果放进提示词里。它更适合“问题答案在某篇文档里”的场景。Memory 偏向长期工作记忆记录的是 Agent 在多次任务中积累的实体、关系、用户偏好、历史决策。Skill 更像一个可复用的能力包比如“怎么用 MCP 控制某个软件”“怎么完成一轮代码审查”。表格对比一下概念解决什么问题典型实现MCPAgent 和工具之间的协议JSON-RPC over stdio / HTTPRAG从文档库检索知识向量库、ES、混合检索Memory保存长期工作记忆实体、关系、观察记录Skill打包可复用能力提示词模板、脚本、工具调用链MCP Memory 这个名字容易让人以为它只属于 MCP 层实际上它做了三层事用 MCP 暴露接口用 OKF 定义记忆数据结构用 SQLite FTS5 做检索。后面逐一展开。2. 这个方案的三块核心MCP、OKF、SQLite FTS52.1 MCP 只解决“接入方式”不解决“记多久”MCP 在项目里的角色是“门面”。MCP Memory Server 会把记忆能力包装成几个工具比如写入记忆、查询记忆、新增实体、新增关系、删除记录。Agent 通过 MCP 客户端调用这些工具不需要关心底层是 SQLite 还是别的存储。这种设计的好处是接入成本低。MCP 客户端配置好 server 地址或本地命令后Agent 就能直接调。你不用为了加记忆功能专门改模型也不用自己写一套 API。但要注意MCP 本身不保证记忆可靠性。它只保证协议能通真正决定记忆质量的是底层存储结构和写入策略。很多人配完 MCP 之后发现记忆“时灵时不灵”问题多半不在协议而在数据模型。2.2 OKF 的作用是把记忆变成一个可索引的对象OKF 在标题里看着像黑话但拆开看并不复杂。它更接近一种对象描述约定每条记忆有一个稳定 keyvalue 是结构化对象包含类型、内容、相关实体、时间戳等字段。这样 SQLite FTS5 索引的不是整段聊天记录而是经过清洗的记忆片段。我按通用语义理解 OKF 时会把它当成下面这种结构{ key: entity:user:10001, type: entity, name: 张三, aliases: [zhangsan, 三哥], facts: [ 偏好 Python 优先的稳定架构, 喜欢先看单测再讨论重构 ], updated_at: 2025-01-01T12:00:00Z }再比如一条关系{ key: relation:user-project:10001-42, type: relation, from: entity:user:10001, to: entity:project:42, relation: 负责, metadata: { start_at: 2025-03-01 }, updated_at: 2025-03-01T10:00:00Z }OKF 的核心价值不是格式复杂而是字段约束。它强制 Agent 在写入记忆前把事情拆成“谁、和谁、什么关系、观察到什么”。不然只会出现一种结果记忆表里存了一大堆“用户说他想先看看方案”这样没法查询的闲聊。实际项目的字段不一定和我写的完全一样我建议以仓库 README 为准。但设计思路上稳定 key、结构化 value、时间戳更新这三件事是不可省掉的。2.3 FTS5 为什么够快SQLite FTS5 是 SQLite 自带的全文检索引擎。它利用倒排索引把文本拆成词项记录每个词项出现在哪些行查询时直接定位不需要全表扫描。对这个项目来说FTS5 特别合适的地方是不引入额外服务。一个 SQLite 文件就能跑。事务能力强。写入、更新、删除和索引同步可以放在同一个事务里。查询语法简单。支持 AND、OR、短语、前缀匹配、BM25 排序。对实体名、文件名、命令、用户偏好这类精确词召回很好。在一两万条记忆的规模下FTS5 的检索通常能在几十毫秒内完成。如果索引和查询写法正确足够支撑日常 Agent 对话。不要拿它跟分布式搜索引擎比吞吐这不是它的目标。3. 本地跑通一个最小记忆服务3.1 环境准备先确认本地 SQLite 支持 FTS5。绝大多数新版 SQLite 默认带 FTS5但有些精简发行版会去掉。用一条查询验证SELECT sqlite_version();再跑一个测试CREATE VIRTUAL TABLE test_fts USING fts5(content); INSERT INTO test_fts(content) VALUES (hello memory); SELECT * FROM test_fts WHERE test_fts MATCH memory; DROP TABLE test_fts;如果第一次建 FTS5 表就报 no such module: fts5说明当前 SQLite 编译时没带 FTS5。解决办法是换一个完整的发行版或者确认项目使用的 SQLite 版本。不要跳过这步很多 MCP Memory 启动失败最后都查到 SQLite 版本头上。运行 MCP Memory Server 还需要看项目的技术栈。整个 MCP 生态里 Node.js 和 Python 实现都很常见下面给的是通用模板git clone 仓库地址 cd mcp-memory npm install npm run dev如果项目是 Python 实现的git clone 仓库地址 cd mcp-memory python -m venv .venv source .venv/bin/activate pip install -r requirements.txt python -m mcp_memory注意这里仓库地址和mcp_memory只是演示用实际以项目 README 为准。MCP Server 多数通过 stdio 和客户端通信启动后不会主动打印请求日志它会等 MCP 客户端发消息过来。3.2 在 MCP 客户端里注册常见 MCP 客户端支持 JSON 配置文件注册 Server。通常长这样{ mcpServers: { memory: { command: npx, args: [-y, 实际包名], env: { MEMORY_DB: ./memory.db } } } }这里最容易被忽略的是 env 变量。如果 MCP 服务端依赖某个环境变量指定数据库路径、日志级别、端口号而客户端没传服务可能起来却找不到数据库。我一般会先在终端手动跑一次命令确认能正常启动再放进 MCP 客户端。如果你用的客户端支持 MCP Inspector可以直接用 Inspector 做连通性测试。它相当于 MCP 调试面板可以手动调用工具看返回结果比反复对话试错更高效。3.3 用最小输入验证记忆写入和召回不要一上来就导入大批数据先跑一条最小任务。建议顺序是写入一条记忆比如“用户偏好 Python”。查询关键词“Python”确认能召回。修改这条记忆改成“用户偏好 Python FastAPI”。再查询“FastAPI”确认更新生效。删除这条记忆确认检索结果里不再出现。单条任务跑通说明 MCP 通道、SQLite 文件、FTS5 索引、基本增删改查都正常。如果单条都失败后面批量导入只会放大问题。成功判断标准很简单调用写入工具后数据库文件出现且内容正确调用查询工具后返回非空结果修改后查询结果能反映更新。不要只看“没报错”还要看数据有没有真的落盘。4. 写入结构化记忆时先设计数据模型和索引4.1 不要只存聊天记录最容易犯的错是把 MCP Memory 当成“聊天记录收藏夹”。如果写入的是原始对话查询时只能靠关键词硬搜很快会把不相关的记录也捞出来。更好的做法是维护实体、关系、观察三类数据实体用户、项目、文件、工具、组织等。关系谁负责什么、哪个模块依赖哪个服务、用户对某个方案的态度。观察某次任务中得到的结论、偏好、限制条件。一个简化的表结构可以是CREATE TABLE memories ( id INTEGER PRIMARY KEY AUTOINCREMENT, object_key TEXT UNIQUE, type TEXT NOT NULL, content TEXT NOT NULL, entity_id TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE VIRTUAL TABLE memories_fts USING fts5( object_key UNINDEXED, type UNINDEXED, content, contentmemories, content_rowidid );这里用contentmemories是把 FTS5 做成外部内容表避免把全文内容重复存一份。注意外部内容表需要自己维护索引同步。4.2 用触发器保证索引同步直接往 memories 表写入数据FTS5 表不会自动更新。必须同步写 FTS5或者用触发器。插入触发器CREATE TRIGGER memories_ai AFTER INSERT ON memories BEGIN INSERT INTO memories_fts(rowid, object_key, type, content) VALUES (new.id, new.object_key, new.type, new.content); END;更新触发器CREATE TRIGGER memories_au AFTER UPDATE ON memories BEGIN INSERT INTO memories_fts(memories_fts, rowid, object_key, type, content) VALUES (delete, old.id, old.object_key, old.type, old.content); INSERT INTO memories_fts(rowid, object_key, type, content) VALUES (new.id, new.object_key, new.type, new.content); END;删除触发器CREATE TRIGGER memories_ad AFTER DELETE ON memories BEGIN INSERT INTO memories_fts(memories_fts, rowid, object_key, type, content) VALUES (delete, old.id, old.object_key, old.type, old.content); END;这些代码是通用写法实际项目可能用程序层同步也可能用 SQLite 自带钩子。关键是你要知道外部内容 FTS5 表不会自动跟随业务表变化漏掉同步就会出现“明明数据在但查不到”的诡异现象。4.3 查询写法决定了召回质量FTS5 的查询不是普通的 LIKE是MATCH。一条常见查询SELECT m.*, bm25(memories_fts) AS score FROM memories_fts JOIN memories m ON m.id memories_fts.rowid WHERE memories_fts MATCH :query ORDER BY score LIMIT 10;这里用bm25()做相关性排序。FTS5 默认按行号返回不一定是最相关的结果加 BM25 排序更接近真实搜索体验。如果 Agent 记忆主要是英文、代码、文件名默认分词器足够。如果会有大量中文短句可以考虑trigram分词器CREATE VIRTUAL TABLE memories_fts USING fts5( content, tokenize trigram );trigram 支持子串匹配适合中文里“不用空格分词”的场景但索引体积会更大。取舍时看你的用户输入习惯。原始项目没有说明默认分词器时落地前建议先确认。5. 从单条记忆到批量导入和长期使用5.1 批量导入时不要一条条 INSERT 后立刻 SELECT单条任务能跑通之后很多人会直接遍历几千条历史记录逐条调用写入工具。这样做的结果通常不是更快而是更慢还会遇到奇怪的锁问题。批量导入建议用原生 SQL 事务而不是通过 MCP 工具一条条调。因为 MCP 工具调用本身有协议开销每条之间还可能触发查询和序列化逻辑非常拖速度。更合理的做法是把历史记忆整理成 JSON 文件。写一个独立的导入脚本。在脚本里循环解析 JSON用事务批量插入。插入完成后统一校验数据库总数。不要一上来就开多线程写 SQLite。SQLite 对写操作是串行化的多线程写不仅没法加速还会频繁触发SQLITE_BUSY。优先保证单线程事务再看是否需要批量合并写入。5.2 去重和过期策略必须提前想Agent 写入的记忆天然会重复。同一个用户可能今天叫“张三”明天在对话里叫“三哥”。同一条项目结论可能在三个 session 里被重复记录。去重不能只靠字符串相等。需要设计归一化规则实体名称统一成规范名。别名单独存不覆盖主名。相同 key 的关系更新时只更新 updated_at 和相关属性。观察类记忆如果内容相似合并而不是追加。过期策略同样重要。有些记忆是长期偏好有些只是临时状态。比如“用户当前正在跑一个 SQL 迁移任务”可能几小时后就没用了。如果所有记忆都永不过期检索结果会被过时信息污染。可以考虑加一个status或expires_at字段定期清理。也可以给每条记忆增加来源 session 和可信度后续查询时按可信度过滤。原始项目如果没提供这些自己扩展并不难。5.3 和向量检索组合使用OKF SQLite FTS5 这套组合适合精确词召回。但它的短板也很明显不懂语义。比如用户写的是“我不喜欢长时间等构建”FTS5 查“构建效率”不一定能召回。如果 Agent 需要处理大量模糊需求可以保留 FTS5 做第一层召回再用 embedding 做语义召回最后在应用层合并排序。具体做法是在 OKF 对象里加一个embedding字段写入时生成向量查询时用向量库或 sqlite 的向量扩展做相似度检索同时用 FTS5 做关键词检索。两者结果去重后再交给模型排序。这个方案比“只用向量库”多一层精确召回比“只用 FTS5”多一层语义理解。代价是引入 embedding 服务和高维向量存储明显更重。如果只是学习或内部工具先用 FTS5 就够了。6. 运行起来之后怎么判断它真的快且稳定6.1 先看资源占用再看请求耗时MCP Memory Server 本身不会占用太多资源因为它不跑模型。真正占资源的是底层 SQLite 文件大小、索引维护和查询复杂度。判断资源占用时我一般会关注进程内存观察长期运行后是否持续上涨。数据库文件大小正文和索引的比例是否失控。FTS5 索引大小是否明显超过正文。写延迟单次写入在数据量变大后是否越来越慢。如果只是本地学习这些指标不看也行。如果要长期接入 Agent 产品就必须记录基线值否则后面很难判断是哪一次改动引入的性能回退。6.2 给出可量化的验收标准这里给一个通用判断表实际数值以你的机器和项目为准。场景判断标准单条写入耗时稳定不随数据库变大而明显变长单条查询命中结果顺序合理重复查询结果一致批量导入 1 万条能在可控时间内完成不出 SQLITE_BUSY并发请求多个 session 同时写入时不丢数据长期运行持续使用几天后速度没有持续劣化注意“速度没有持续劣化”很关键。SQLite 在小数据量下永远很快但索引设计不好、触发器写错、查询计划变差时数据量大了会突然变慢。不要只看第一次测试结果。7. 排查顺序和常见坑7.1 报错先从这些地方查MCP Memory 的问题很多不是模型问题也不是协议问题而是环境问题。遇到问题我习惯按这个顺序排查先看 MCP Server 日志有没有起来。如果进程直接退出多半是命令、路径、包名写错。再看数据库文件是否创建路径是否有写权限。不要假设客户端传的环境变量一定生效。确认 SQLite 版本带 FTS5。很多稳定版本自带但精简安装会把 FTS5 去掉。检查输入格式。OKF 结构体字段不匹配会导致写入失败或索引漏更新。检查触发器和索引。如果数据在 memories 表里但 FTS5 查不到优先看触发器有没有创建。查看写入并发。多个客户端同时写 SQLite 时出现 SQLITE_BUSY 是正常现象需要加忙等待或排队。这里最容易踩的是“数据能插入但查不到”。数据库文件存在内容也有就是 MATCH 不出来。多数时候不是 FTS5 有问题而是 FTS5 表是外部内容表没有同步索引。先去跑一次SELECT * FROM memories_fts看索引表和业务表是否一致。7.2 “能跑”和“适合生产”不是一回事本地能跑通一个 MCP Memory Server只说明链路通。生产环境里还要回答几个问题数据库文件存在哪个目录备份怎么做。多个 Agent 实例共用一个数据库还是各用各的。写入冲突时怎么处理。历史记忆误写入后能否一键回滚。MCP Server 崩溃后已提交的数据会不会丢。这些都需要提前设计。OKF 和 SQLite FTS5 提供的是底层能力不会帮你解决运维问题。如果你的使用场景是个人工具、内部小团队、原型验证默认配置通常够用。如果是多租户产品每一个用户都独占一个 SQLite 文件或者共用一张大表是两种完全不同的数据模型必须拆开设计。8. 最后留几个判断点MCP Memory 这类方案真正落地时最该盯住的不是功能列表而是输入格式、索引同步和失败重试。如果你打算自己实现或改造一个 MCP Memory Server先回答这几个问题Agent 写入的记忆是结构化对象还是原始文本SQLite FTS5 索引是否和业务表实时同步同一个 key 重复写入时是覆盖、合并还是忽略查询时用 BM25 排序还是只按时间倒序超过一定时间或规模后数据怎么归档如果未来要加语义检索数据模型能不能平滑扩展我的建议是先把单条写入和查询跑稳再碰批量导入。先不要急着提高并发先把数据库文件、日志、备份方式理清楚。踩过几次之后就会发现很多问题不是工具能力不够而是前置环境和输入材料没有处理干净。SQLite FTS5 已经是一个相当稳的起点真正需要花时间的是让 Agent 的记忆写入变得结构化和可信。
返回列表