公司动态
为 Hermes Agent 集成本地记忆系统:SQLite + MemOS 实战指南
1. 项目概述给 Hermes 装上“记忆外挂”不是加功能是重建认知闭环你有没有试过让一个 AI 工具连续用三天、一周、一个月刚开始它记得你上次说“别用缩写”第二天问它“昨天提的方案里第三点是什么”它一脸茫然你反复教它你的写作风格偏好它却在第五次回复时又回到模板腔你让它管理项目进度它连上周五你确认过的截止日期都查不到——这不是它笨是它根本没“记住”你。标题里说的“给 10 万 Star 的 Hermes 装个记忆外挂”表面看是个技术集成动作实则是一次底层能力跃迁。Hermes 本身是一个高活跃度、强交互性的本地 AI Agent 框架GitHub 星标已超 10 万它的强项在于实时响应、工具调用和轻量推理但原生设计中没有长期记忆模块——所有上下文都靠 LLM 自身的窗口长度硬扛对话一断、进程一关、重启一来前功尽弃。所谓“越用越聪明”从来不是模型参数变多而是它能从每一次交互中沉淀经验、修正偏差、复用模式。这恰恰是 MemOSMemory Operating System要解决的核心问题。MemOS 不是另一个 RAG 插件也不是简单的向量数据库封装。它是一套完整的、可演化的记忆操作系统把“记忆”这件事拆解成可存储、可检索、可编辑、可反馈、可分层的工程对象。它支持 L1原始对话痕迹、L2行为策略、L3用户世界观建模三层记忆结构还能把高频操作固化为“结晶化技能”Crystallized Skills这才是真正意义上的“越用越聪明”。而 MemTensor 团队推出的memos-local-plugin 2.0正是专为 Hermes Agent 和 OpenClaw 设计的本地优先记忆插件它不依赖云服务全部数据落盘 SQLite检索融合 FTS5 全文搜索 向量相似度整个流程完全可控、可审计、可调试。这个项目适合三类人一是 Hermes 的深度使用者厌倦了每次重聊都要“自我介绍”二是本地 AI 部署者追求数据主权与离线可靠性三是想理解现代 AI Agent 架构演进的技术实践者——你将看到的不是 API 调用文档而是一次真实、完整、带血丝的系统级缝合过程。接下来我会带你从零开始把 MemOS 的记忆能力像安装一块高性能内存条一样严丝合缝地嵌入 Hermes 的运行时中。2. 整体架构设计与选型逻辑为什么是 MemOS SQLite Node.js而不是别的组合在动手之前必须讲清楚一个关键判断为什么我们不选 LangChain Memory、不选 mem0、不选 Letta甚至不直接用 Qdrant 或 Chroma 做向量库答案不在技术参数表里而在 Hermes 的运行环境约束和实际使用场景中。2.1 Hermes 的真实运行边界轻量、本地、Node.js 原生Hermes 是一个基于 Node.js 构建的桌面级 AI Agent 框架其核心优势在于启动快、资源占用低、可打包为独立.exe/.dmg应用。这意味着它的扩展机制必须满足三个硬性条件无 Python 依赖Hermes 主进程是纯 JavaScript/TypeScript若引入 Python 后端如 LangChain 的典型部署就必须开子进程通信或 HTTP 中转延迟陡增且跨平台打包复杂度指数级上升单二进制可分发用户下载 Hermes Desktop 后双击即用不能要求先装 Python、再 pip install、再配置环境变量内存与磁盘友好Hermes 定位是个人助理不是企业知识库单用户日均记忆增量约 2–5 MB不需要分布式向量库的吞吐能力反而更看重单机读写稳定性与崩溃恢复能力。提示我曾试过用 mem0 的 Node.js SDK 接入 Hermes结果发现其底层仍依赖 SQLite 向量库双存储且 SDK 对 Node.js 20 的 ESM 模块支持不全import { Memory } from mem0直接报错ERR_REQUIRE_ESM。这不是 bug是架构基因决定的——mem0 本质是 Python 优先的框架JS SDK 只是薄层胶水。2.2 MemOS 的本地插件为何成为唯一解MemOS 的memos-local-plugin 2.0是目前唯一一个原生 Node.js 实现、纯 SQLite 存储、零外部服务依赖的记忆插件。它的设计哲学非常清晰存储层全部数据存于本地 SQLite 文件默认~/.memos/local.db用 WAL 模式保证高并发写入安全用PRAGMA journal_mode WAL和PRAGMA synchronous NORMAL平衡性能与可靠性检索层放弃纯向量检索的精度陷阱采用FTS5 向量混合检索——FTS5 处理关键词、命名实体、时间短语如“上周三”“Q3财报”向量处理语义模糊查询如“那个讲得很细的方案”两者结果加权合并召回率与准确率兼顾API 层提供标准 RESTful 接口/add,/search,/edit,/deleteHermes 只需用fetch()调用无需任何 SDK演化层通过feedback接口接收自然语言指令如“把这条记忆改成更正式的语气”“删除关于报销流程的所有记录”插件内部自动解析、定位、更新这是其他方案完全缺失的能力。注意MemOS Cloud 版本虽有更高性能但要求网络可达、API Key 管理、跨域配置对家庭 NAS、内网办公等场景不友好。而memos-local-plugin的 Docker 部署方式见后文完美适配“腾讯云访问家里的 NAS 中的 memos”这类需求——NAS 上跑 MemOS 容器Hermes Desktop 通过内网 IP 调用全程不走公网数据不出局域网。2.3 SQLite 的不可替代性不是妥协是精准匹配网络热词里反复出现sqlite、db browser for sqlite、sqlite 和 mysql 有什么区别说明大量开发者对 SQLite 仍有认知偏差。在这里必须明确SQLite 不是“简陋版 MySQL”而是嵌入式数据库的黄金标准。它的优势在 Hermes MemOS 场景中被放大到极致零配置启动npm install sqlite3后new sqlite3.Database(./local.db)即可写入无需守护进程、无需端口监听、无需用户密码ACID 事务保障即使 Hermes 异常崩溃SQLite 的 WAL 日志也能保证最后一次INSERT不丢失这对记忆数据的完整性至关重要单文件可移植local.db文件可直接拷贝到另一台电脑Hermes 重新连接即恢复全部历史这是任何客户端-服务器数据库都无法提供的体验FTS5 原生支持SQLite 3.7.4 内置 FTS5 模块无需额外扩展CREATE VIRTUAL TABLE memos_fts USING fts5(content, title, tags)一行命令搞定全文索引比 Elasticsearch 轻量百倍。我实测对比过同样 5 万条记忆记录模拟 3 个月高频使用SQLite FTS5 向量混合检索平均响应 86ms而纯向量库Qdrant 本地实例在相同硬件下平均 142ms且内存占用高出 3.2 倍。这不是性能妥协是工程理性选择。3. 核心细节解析与实操要点SQLite 数据库结构、混合检索原理与 Hermes 集成接口现在进入最硬核的部分MemOS 的本地插件到底在 SQLite 里存了什么它是如何把“一句话反馈”变成数据库里的精准更新Hermes 又该怎么调用才能不破坏原有工作流这些细节决定了你装上的到底是“外挂”还是“累赘”。3.1 MemOS SQLite 数据库的四张核心表结构即逻辑memos-local-plugin初始化后会在指定路径生成local.db文件。用 DB Browser for SQLite 打开你会看到四张关键表非全部但覆盖 95% 使用场景表名作用关键字段说明为什么这样设计memories存储原始记忆单元idUUID、user_id用户标识、content主体文本、created_atISO8601 时间戳、updated_at、tagsJSON 数组如[project, urgent]、embeddingBLOB向量二进制user_id支持多用户隔离tags为后续过滤提供结构化入口embedding直接存二进制而非 base64节省 33% 存储空间且避免编码开销memory_traces记录记忆的生命周期事件id、memory_id外键、event_typeadd/edit/delete/feedback、event_dataJSON含操作人、原始内容、修改后内容这是“自演化”的基础——所有变更都有迹可循feedback事件会触发 L2/L3 层的策略更新memories_ftsFTS5 全文索引虚拟表rowid对应memories.id、content、title可选摘要、tagsFTS5 支持MATCH查询和bm25排序SELECT * FROM memories_fts WHERE content MATCH 报销流程 ORDER BY bm25(memories_fts) LIMIT 5即可获得最相关结果skills存储结晶化技能id、name如 会议纪要生成、trigger_pattern正则如 /(会议meeting).*(纪要实操心得不要手动修改memories表所有操作必须通过 MemOS 的/add、/edit等 API。因为memories_fts是虚拟表直接INSERT INTO memories不会触发 FTS5 索引更新导致检索失效。我踩过这个坑——手动插入 200 条测试数据后/search接口完全查不到最后用INSERT INTO memories_fts(memories_fts) VALUES(rebuild)重建索引才恢复。3.2 混合检索的实现逻辑FTS5 与向量如何协同工作当你在 Hermes 里输入“帮我找上周五讨论的服务器迁移方案”MemOS 的/search接口会执行以下三步第一步FTS5 快速初筛SELECT id, content, bm25(memories_fts) as score FROM memories_fts WHERE content MATCH 上周五 服务器 迁移 方案 ORDER BY score DESC LIMIT 50;这一步利用 SQLite 的倒排索引在毫秒级内从数万条记录中筛选出包含所有关键词的候选集比如 32 条。bm25算法自动给“上周五”“服务器”“迁移”“方案”赋予不同权重避免简单AND匹配的僵硬性。第二步向量精排对上一步返回的 32 条id插件从memories表中批量读取embedding字段计算它们与查询句向量的余弦相似度。这里用的是 ONNX Runtime 加载的轻量级 Sentence-BERT 模型all-MiniLM-L6-v2在 CPU 上单次推理仅 12ms。最终得到 32 个相似度分数。第三步加权融合与去重将 FTS5 的bm25分数0–100 归一化与向量相似度0–1按 6:4 权重相加再对结果按总分降序排列。同时检查content字段的编辑距离若两条记忆相似度 0.92则只保留分数更高的那条避免重复返回。提示这个权重比6:4是我实测调整的结果。纯向量检索在“上周五”这种时间短语上表现极差模型无法理解相对时间而纯 FTS5 在“服务器迁移”这种专业术语上容易误召如匹配到“服务器重启”“迁移学习”。6:4 是平衡点你可在插件配置中修改fts_weight和vector_weight参数。3.3 Hermes 集成接口不改一行 Hermes 源码只加三处调用Hermes 的插件机制非常干净我们不需要 fork 仓库、不修改其核心逻辑只需在三个关键节点注入 MemOS 调用① 对话开始前自动加载上下文在 Hermes 的onConversationStart钩子中通常位于src/agents/hermesAgent.ts添加// 获取最近 5 条相关记忆按时间倒序 const recentMemories await fetch(http://localhost:8000/product/search, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ query: 用户 ${userId} 的近期重要事项, user_id: userId, limit: 5, filter: { tags: [important] } // 只查带 important 标签的 }) }).then(r r.json()); // 将 memory.content 拼接到 system prompt 末尾 systemPrompt \n\n---\n【用户近期重要记忆】\n${recentMemories.map(m - ${m.content}).join(\n)};② 对话结束后自动存档与打标在onConversationEnd钩子中将本次对话摘要存为记忆// 用 LLM 生成摘要调用 Hermes 自带的 summarizer const summary await this.summarize(conversationHistory); // 存入 MemOS打上 project 和 date 标签 await fetch(http://localhost:8000/product/add, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ user_id: userId, content: summary, tags: [project, date:${new Date().toISOString().split(T)[0]}], async_mode: sync }) });③ 用户主动反馈时触发记忆修正当用户输入类似“把刚才说的第三点改成更简洁的说法”时Hermes 的指令解析器捕获到feedback关键词调用await fetch(http://localhost:8000/product/feedback, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ user_id: userId, feedback: 把刚才说的第三点改成更简洁的说法, context: conversationHistory.slice(-3).map(m m.content).join(\n) // 提供上下文 }) });MemOS 插件会解析这句话定位到对应的memory_id调用内置的 LLM可配置为本地 Ollama 模型生成新内容并更新数据库。注意所有fetch调用必须设置mode: cors若 Hermes 是网页版或确保跨域配置正确。桌面版 Electron 应用默认允许 localhost 请求无需额外配置。4. 实操过程与核心环节实现从零部署 MemOS Local Plugin 到 Hermes Desktop 全流程现在我们把前面所有理论落地为可执行的步骤。整个过程分为四阶段环境准备 → MemOS 插件部署 → Hermes 配置 → 联调验证。全程在 macOS / Windows / Linux 通用不依赖 Docker但提供 Docker 方案备选。4.1 环境准备Node.js 与 SQLite 的最小可行安装Node.js 版本选择网络热词中频繁出现error installing 24.16.0: node.js v24.16.0 is not yet released说明很多人卡在版本管理上。MemOS Local Plugin 要求 Node.js ≥ 18.17.0因使用stream/webAPI但 ≤ 22.x因部分依赖未适配 Node.js 23 的实验性特性。强烈推荐 Node.js 20.15.1—— 这是 LTS 版本中最后一个稳定支持所有插件依赖的版本。安装步骤以 macOS 为例Windows 类似# 1. 卸载旧版 Node.js如有 brew uninstall node # 2. 安装 nvmNode Version Manager管理多版本 curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash source ~/.zshrc # 或 ~/.bash_profile # 3. 安装并切换到 Node.js 20.15.1 nvm install 20.15.1 nvm use 20.15.1 node -v # 应输出 v20.15.1 # 4. 验证 SQLite 可用性macOS 自带Windows/Linux 需额外安装 sqlite3 --version # 应输出 3.30.0实操心得不要用node.js官网下载的.pkg安装包它会把 Node.js 装到/usr/local/bin与 nvm 冲突导致nvm use失效。必须用 nvm 管理否则后续npm install会因权限问题失败。4.2 部署 MemOS Local Plugin两种方式推荐轻量 CLI 模式MemOS 提供两种本地部署方式Docker Compose适合 NAS/服务器和 CLI适合桌面端。鉴于 Hermes Desktop 是单机应用我们首选 CLI 模式它更轻量、调试更直观。CLI 部署步骤# 1. 克隆 MemOS 仓库注意不是 MemOS 主仓库而是 local plugin 专用分支 git clone https://github.com/MemTensor/MemOS.git cd MemOS # 2. 安装依赖使用 uv 替代 pip更快更稳 curl -LsSf https://astral.sh/uv/install.sh | sh source $HOME/.cargo/env uv sync --python 3.11 # 3. 创建配置文件关键 cp docker/.env.example .env # 编辑 .env重点修改以下几行 MEMOS_HOME/Users/yourname/.memos # 记忆数据存放路径 MOS_EMBEDDER_MODELall-MiniLM-L6-v2 # 向量模型轻量首选 MOS_CHAT_MODEL_PROVIDERollama # 本地 LLM用于 feedback 解析 OLLAMA_HOSThttp://localhost:11434 # Ollama 服务地址 # 其他 API KEY 如 OPENAI_API_KEY 可留空CLI 模式默认不调用云服务 # 4. 启动 MemOS 服务后台运行不阻塞终端 nohup uv run src/memos/api/server_api.py --host 0.0.0.0 --port 8000 memos.log 21 # 验证是否启动成功 curl http://localhost:8000/health # 应返回 {status: ok}Docker 部署适配 NAS 场景如果你的 NAS 支持 Docker如群晖、威联通且希望 Hermes Desktop 通过腾讯云内网穿透访问用此方案# 1. 在 NAS 的 Docker 中拉取官方镜像 docker pull memtensor/memos-local-plugin:2.0.0 # 2. 创建数据卷并运行容器 docker run -d \ --name memos-local \ -p 8000:8000 \ -v /volume1/docker/memos:/root/.memos \ -e MEMOS_HOME/root/.memos \ -e MOS_EMBEDDER_MODELall-MiniLM-L6-v2 \ -e MOS_CHAT_MODEL_PROVIDERollama \ --restartalways \ memtensor/memos-local-plugin:2.0.0 # 3. 在腾讯云控制台配置内网穿透将 NAS 的 8000 端口映射到云服务器公网 IP # Hermes Desktop 中 API 地址改为http://你的云服务器IP:8000提示db browser for sqlite是调试神器。下载安装后打开/Users/yourname/.memos/local.db在Browse Data标签页实时查看memories表新增记录比看日志高效十倍。4.3 Hermes Desktop 配置三处代码修改与一键打包Hermes Desktop 的源码结构清晰我们只需修改三个文件①package.json添加 MemOS 依赖可选仅用于类型提示devDependencies: { types/node: ^20.14.10, typescript: ^5.4.5 }, dependencies: { node-fetch: ^3.3.2 // Hermes 默认不带 fetch需显式安装 }然后运行npm install。②src/config/index.ts注入 MemOS 配置export const MEMOS_CONFIG { baseUrl: http://localhost:8000, // 若部署在 NAS改为云服务器 IP timeout: 10000, enabled: true // 开关方便调试时关闭 };③src/agents/hermesAgent.ts在class HermesAgent中添加记忆钩子在constructor()末尾添加// 初始化 MemOS 客户端 this.memosClient { add: (data: any) fetch(${MEMOS_CONFIG.baseUrl}/product/add, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(data) }), search: (data: any) fetch(${MEMOS_CONFIG.baseUrl}/product/search, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(data) }), feedback: (data: any) fetch(${MEMOS_CONFIG.baseUrl}/product/feedback, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(data) }) };并在onConversationStart、onConversationEnd、onUserMessage方法中插入前述的三处调用。打包 Hermes Desktop# 1. 安装 electron-builder若未安装 npm install --save-dev electron-builder # 2. 修改 package.json 的 build 配置 build: { appId: ai.hermes.desktop, productName: Hermes Desktop, directories: { output: dist }, win: { target: nsis }, mac: { target: dmg } } # 3. 执行打包 npm run dist # 输出文件在 dist/ 目录下双击即可安装4.4 联调验证从“Hello World”到真实场景压测部署完成后必须进行四级验证Level 1基础连通性在 Hermes Desktop 中输入!memos test应触发以下调用await this.memosClient.add({ user_id: test-user, content: MemOS 连通性测试, tags: [test] }); const res await this.memosClient.search({ query: 连通性测试, user_id: test-user }); console.log(res); // 应返回包含该记忆的数组若res为空检查memos.log是否有Connection refused错误确认 MemOS 服务是否在运行。Level 2混合检索有效性输入查询我的 NAS 密码是多少观察返回结果若只返回含“NAS”“密码”字眼的记录如“NAS 登录教程”说明 FTS5 生效若返回“公司 Wi-Fi 密码”“路由器后台密码”等语义相近但关键词不同的记录说明向量检索生效若两者都返回且按相关性排序说明混合检索成功。Level 3反馈闭环验证让 Hermes 记住“我的邮箱是 hermesexample.com”。然后输入“把邮箱改成 hermesmemos.dev”。检查memories表中对应记录的content字段是否已更新memory_traces表中是否新增一条event_type: feedback的记录。Level 4压力测试模拟 3 个月使用用脚本批量插入 10 万条记忆# 生成测试数据 for i in {1..100000}; do echo {\user_id\:\user1\,\content\:\测试记忆 $i描述一些关于项目管理、会议纪要、代码审查的内容\,\tags\:[\work\]} data.jsonl done # 调用 MemOS 批量导入 API需自行实现或用 curl -X POST --data-binary然后在 Hermes 中执行 100 次随机搜索统计平均响应时间。实测结果MacBook Pro M1 16GB10 万条记录下 P95 响应 120ms内存占用稳定在 480MB。5. 常见问题与排查技巧实录从 SQLite 错误到 Hermes 内存上限的实战解法在真实部署中90% 的问题集中在环境、配置、权限三方面。以下是我在 7 个不同硬件平台M1/M2/M3 Mac、Intel Win10/Win11、Linux Ubuntu上踩过的坑附带可复制的解决方案。5.1 SQLite 相关问题速查表问题现象根本原因一行解决命令原理解释Error: no such table: memories_ftsmemories_fts是虚拟表首次启动未初始化sqlite3 ~/.memos/local.db INSERT INTO memories_fts(memories_fts) VALUES(rebuild)FTS5 虚拟表需显式rebuild才能创建索引MemOS 启动时若检测到空库会自动执行但手动删库后需手动触发Error: database is locked多个 Hermes 实例或插件同时写入同一 SQLite 文件sqlite3 ~/.memos/local.db PRAGMA journal_mode WAL; PRAGMA synchronous NORMAL;WAL 模式允许多读一写synchronous NORMAL降低 fsync 频率牺牲极小数据安全性换取高并发写入性能DB Browser for SQLite 打开后 .tables 查不到表DB Browser 默认不显示系统表和虚拟表在 DB Browser 的Execute SQL标签页中输入.tables或勾选Show system tablesSQLite 的.tables命令默认隐藏以sqlite_开头的系统表memories_fts属于虚拟表需显式启用显示提示sqlite and mysql what is the difference这个热词背后是很多人试图用 MySQL 思维操作 SQLite。记住SQLite 没有用户、没有权限、没有端口它就是一个文件。所有“连接失败”问题99% 是路径错误或文件被其他进程占用。5.2 Hermes 集成问题排查问题现象排查路径终极解决方案经验总结Hermes 启动时报fetch is not defined检查hermesAgent.ts是否在 Node.js 环境下调用了浏览器 API在package.json中添加type: module并将fetch替换为node-fetchimport fetch from node-fetch;Hermes 的 TypeScript 配置可能未启用 ES Module导致global.fetch不可用。node-fetch是 Node.js 环境的标准替代/search返回空数组但memories表中有数据检查memories_fts虚拟表是否与memories表同步运行sqlite3 ~/.memos/local.db INSERT INTO memories_fts SELECT id, content, title, tags FROM memories;FTS5 虚拟表不会自动同步主表数据MemOS 的add接口会同时写入两者但手动插入主表时需手动同步Hermes Desktop 打包后无法连接 MemOS检查打包后的asar包是否包含node-fetch在electron-builder配置中添加extraResources: [{ from: node_modules/node-fetch, to: node-fetch, type: file }]Electron 打包会将node_modules压缩为app.asar部分原生模块需单独提取5.3 Hermes 的 memory 上限问题不是 Bug是设计哲学网络热词中反复出现hermes 的memory上限怎么解决这其实是个误解。Hermes 本身没有硬性内存上限它的“上限”来自两个地方LLM 上下文窗口如你用 GPT-4 Turbo窗口是 128KHermes 会把所有记忆拼接后喂给模型超过则截断SQLite 单文件大小理论上 SQLite 单文件可达 140TB但实际中 2GB 是 macOS APFS 文件系统的舒适区超过后 WAL 日志写入变慢。真正的解法不是“突破上限”而是“智能裁剪”MemOS 提供auto-prune策略在.env中配置MEMOS_AUTO_PRUNEtrue MEMOS_PRUNE_THRESHOLD50000 # 超过 5 万条触发 MEMOS_PRUNE_STRATEGYoldest_first # 删除最老的 10%启动时 MemOS 会自动执行DELETE FROM memories WHERE id IN ( SELECT id FROM memories ORDER BY created_at ASC LIMIT (SELECT COUNT(*) * 0.1 FROM memories) );我实测10 万条记录裁剪后检索性能提升 22%因为 FTS5 索引体积减小内存缓存命中率上升。最后分享一个小技巧在 Hermes 的 UI 中为每个记忆添加一个⭐图标按钮点击即调用/edit接口给该记忆打上[important]标签。这样下次onConversationStart时filter: { tags: [important] }就能精准加载核心记忆避免无关信息污染上下文。这才是“越用越聪明”的真实形态——不是记住一切而是学会分辨什么是真正重要的。