给 Claude Code 装上“长期记忆“:本地部署双重记忆引擎实战

写在前面

用 Claude Code 做项目开发有个痛点------它没有长期记忆。

每次 /clear 或开新会话,之前讨论过的技术决策、踩过的坑、约定好的规范,全部归零。虽然 Claude Code 自带了 Auto Memory(会往 MEMORY.md 里写一些东西),但说实话,这个机制的召回精度和容量都很有限。你在 A 项目里踩过的坑,切到 B 项目就完全不记得了。

这篇文章记录我实际跑通的一套方案:在本地搭建双重记忆引擎,让 Claude Code 拥有跨会话、跨项目的持久记忆能力。零云端依赖,全部跑在本地。


一、整体思路:三层记忆架构

先说结论,最终落地的是一个三层结构:

复制代码
┌─────────────────────────────────────────────┐
│           Claude Code 用户交互层              │
│     CLAUDE.md / Auto Memory / Skills         │
└────────┬────────────────────┬───────────────┘
         │ MCP 工具调用        │ ov CLI
┌────────▼──────────┐  ┌──────▼──────────────┐
│ memory-engine     │  │ OpenViking Server   │
│ :17823 (SSE)      │  │ :1933               │
│                   │  │                     │
│ 会话记忆           │  │ 知识库管理           │
│ LanceDB 向量存储   │  │ 目录递归检索         │
│ Weibull 衰减      │  │ L0/L1/L2 分层加载    │
│ 混合检索           │  │ 结构化记忆           │
└────────┬──────────┘  └──────┬──────────────┘
         └──────┬─────────────┘
         ┌──────▼──────┐
         │ Ollama      │
         │ bge-m3      │  ← Embedding 模型(两个系统共享)
         │ qwen2.5:14b │  ← VLM(按需加载)
         └─────────────┘

每一层干什么:

层级 系统 存什么 怎么找
L1 即时层 Claude Code 原生 Auto Memory MEMORY.md 里的项目速览 每次会话全量注入上下文
L2 会话层 memory-engine(自建 MCP Server) 偏好、决策、踩坑、规范 向量+BM25 混合检索 + 时间衰减
L3 知识层 OpenViking 文档、代码库、深度上下文 语义搜索 + 分层加载

为什么要分三层?因为 Claude Code 的上下文窗口是有限的。L1 每次全量加载(几百 token),L2 按需召回(省 token),L3 只在需要深度知识时才拉取。三层各有分工,不互相抢上下文。


二、memory-engine:核心会话记忆

这是整套方案里最有价值的部分。简单说就是一个本地跑的 MCP Server,底层用 LanceDB 做向量存储,提供四个工具:

  • memory_store --- 存记忆
  • memory_recall --- 搜记忆
  • memory_forget --- 删记忆
  • memory_stats --- 看统计

2.1 为什么不直接用 Auto Memory

Auto Memory 有三个硬伤:

  1. 容量限制MEMORY.md 超过 200 行就会被截断
  2. 无衰减机制:三个月前存的过时信息和昨天存的新决策权重一样
  3. 无项目隔离:切到不同项目,记忆是混在一起的

memory-engine 解决了这三个问题。

2.2 混合检索 + Weibull 衰减

记忆不是存了就完事,关键是怎么找回来

检索分两路并行:

  • 向量检索:把查询语句 embedding 成向量,在 LanceDB 里做 ANN 搜索
  • BM25 全文检索:传统关键词匹配,处理精确术语命中

两路结果融合后,再叠加 Weibull 衰减

javascript 复制代码
// 核心衰减公式
function weibullScore(ageDays, tier) {
  const halfLife = { core: 90, working: 30, peripheral: 7 }[tier];
  const beta = { core: 1.2, working: 1.0, peripheral: 0.8 }[tier];
  const lambda = halfLife / Math.pow(Math.LN2, 1 / beta);
  return Math.exp(-Math.pow(ageDays / lambda, beta));
}

什么意思呢?记忆按重要性分三档:

  • 核心记忆(importance ≥ 0.8):半衰期 90 天,比如"项目用 PostgreSQL 15"
  • 工作记忆(importance 0.5~0.8):半衰期 30 天,比如"上次部署时改了 nginx 配置"
  • 外围记忆(importance < 0.5):半衰期 7 天,比如"今天 debug 了一个 CSS 对齐问题"

最终得分还会乘以访问频次------被多次召回的记忆会越来越"牢固",类似间隔重复学习。

2.3 项目隔离

通过 scope 参数实现:

复制代码
memory_store({content: "偏好 dark mode", scope: "global"})
  → 跨项目共享

memory_store({content: "本项目用 4 空格缩进", scope: "project:my-api"})
  → 仅 my-api 项目可见

memory_recall({query: "缩进偏好", scope: "project:my-api"})
  → 搜 project:my-api + global 两个范围

在 A 项目里存的决策,不会污染 B 项目的记忆空间。但全局偏好(比如"我习惯用 vim 键位")在所有项目里都能找到。

2.4 实际效果

部署完成后,在 Claude Code 里直接测试:

复制代码
> 调用 memory_stats 看看记忆统计

memory_stats 返回:
{
  "total": 47,
  "by_scope": {
    "global": 12,
    "project:blue-erp": 28,
    "project:ai-tools": 7
  },
  "by_category": {
    "decision": 15,
    "lesson": 11,
    "convention": 8,
    "preference": 6,
    "fact": 4,
    "issue": 3
  }
}

存一条记忆:

复制代码
> 记住:部署测试环境时,jar 包超过 100MB 必须分包上传,否则宝塔面板会截断文件

memory_store 返回:
{
  "id": "a3f7...",
  "status": "stored",
  "scope": "project:blue-erp"
}

过了几天,在新会话里检索:

复制代码
> 我要部署到测试环境,有什么注意事项吗?

memory_recall 命中:
[
  {
    "content": "部署测试环境时,jar 包超过 100MB 必须分包上传,否则宝塔面板会截断文件",
    "score": 0.847,
    "category": "lesson",
    "created_at": "2026-03-16"
  },
  {
    "content": "测试环境 SQL 变更记录在 sql/update/update.sql,追加式写入",
    "score": 0.623,
    "category": "convention"
  }
]

这就是"长期记忆"的实际体验------上次踩的坑,这次自动提醒你。


三、OpenViking:知识库层

OpenViking 解决的是另一个问题:项目文档和代码库的深度检索

比如你有一个 ERP 项目,设计文档有 20 多份,会议纪要 10 份,数据库设计 4 份。每次开新会话都让 Claude 重新读一遍?不现实。

OpenViking 的做法是把这些文档预处理后存进向量库,需要时语义搜索拉取。它有个 L0/L1/L2 分层机制------搜索时先返回摘要(L1),需要详情时再加载全文(L2),省 token。

bash 复制代码
# 添加项目文档到知识库
ov add-resource ~/projects/blue-erp/docs --preserve-structure --wait

# 语义搜索
ov find "借货归还的业务流程" --limit 3

# 输出
Results:
  1. [0.89] viking://resources/blue-erp/docs/数据库设计/成品借货流程.md
     "借货归还支持三种模式:全部归还、部分归还、借转销..."
  2. [0.76] viking://resources/blue-erp/docs/会议纪要/0208会议.md
     "借货消耗按借出数量计算,包含在价格里..."
  3. [0.71] viking://resources/blue-erp/docs/问题确认.txt
     "借货归还单需增加明细表..."

四、部署要点(踩坑记录)

整套系统的部署链路是:Ollama → OpenViking → memory-engine → Claude Code 集成。

4.1 Ollama 是基座

两个系统都依赖 Ollama 提供 Embedding。我选的是 bge-m3(1024 维,中英文混合场景最强),VLM 用 qwen2.5:14b

踩坑:Ollama 必须配置开机自启。memory-engine 的 recall 操作需要调 Ollama 做 embedding,如果 Ollama 没运行,recall 会静默返回空结果------不报错,就是搜不到东西。排查了很久才发现。

4.2 OpenViking 的 Embedding 配置

OpenViking 的 embedding provider 不支持直接写 ollama,要写 openai,然后通过 api_base 指向 Ollama 的 OpenAI 兼容接口:

json 复制代码
{
  "embedding": {
    "dense": {
      "provider": "openai",
      "model": "bge-m3",
      "api_base": "http://localhost:11434/v1",
      "api_key": "no-key",
      "dimension": 1024
    }
  }
}

VLM 配置同理,api_key"no-key" 就行。

4.3 memory-engine 的 SSE 多会话并发

这个是最大的坑。

MCP SDK 的 McpServer 只能绑定一个 transport。如果用 stdio 模式,每开一个 Claude Code 工作空间就 fork 一个进程,10 个窗口就 10 个进程,每个 80MB。

改用 SSE 模式后,只需一个守护进程。但第二个会话连上来时,server.connect() 直接抛 "Already connected to a transport" 把进程搞崩了。

最终的解决方案是工厂模式:每个 SSE 连接创建独立的 McpServer 实例,但共享全局的 LanceDB 连接和 OpenAI client。McpServer 实例本身极轻量(几 KB 内存),重资源全局单例共享。

javascript 复制代码
// 每个连接创建独立 McpServer
function createMcpServer() {
  const srv = new McpServer({ name: "memory-engine", version: "1.0.0" });
  // 注册工具(共享全局 DB)
  srv.tool("memory_store", ..., async (args) => memoryStore(args));
  srv.tool("memory_recall", ..., async (args) => memoryRecall(args));
  return srv;
}

// SSE 连接处理
if (req.url === "/sse") {
  const transport = new SSEServerTransport("/messages", res);
  const srv = createMcpServer();  // 每连接独立实例
  await srv.connect(transport);
}

4.4 Zod 版本兼容

另一个坑:npm install zod 默认装 v4,但 MCP SDK 内部用的 zod-to-json-schema 不兼容 v4,tools/list 直接报 "Cannot read properties of undefined (reading '_zod')"

解决:锁定 Zod v3:

bash 复制代码
npm install "zod@^3.25"

4.5 L2 距离归一化

memory-engine 早期版本的 minScore 设了 0.3,导致 recall 几乎总是空结果。原因是 LanceDB 默认用 L2 距离(范围 0~2),归一化向量的 L2 距离大多在 0.5~1.5 之间,直接用 1 - distance 会产生负数。

修复:

javascript 复制代码
// 错误:1 - distance 可能为负
score = 1 - r._distance;

// 正确:L2 归一化到 [0, 1]
score = Math.max(0, 1 - (r._distance || 0) / 2);

同时 minScore 从 0.3 降到 0.05。


五、自动记忆捕获

记忆系统最怕的不是存不进去,是用户忘了存

实际使用中,90% 的有价值信息不是用户说"记住这个"产生的,而是隐含在对话过程中------纠错、追问、约束嵌入。

解决方案是在 CLAUDE.md 里写规则,让 Claude 自己判断什么时候该存:

用户说了什么 Claude 的动作
"不是 X 而是 Y" 存纠正后的正确信息,importance: 0.9
"零 X 依赖"、"不需要 Y" 提取偏好约束,importance: 0.8
"要求..."、"必须..." 提取约束条件,importance: 0.8
技术决策、架构选择 原文存储,importance: 0.7~0.9

再配合 /save-session 命令做会话结束时的兜底提取,基本不会漏重要信息。


六、资源占用

在 24GB 内存的 Mac 上实测:

组件 常驻内存 说明
Ollama bge-m3 ~1.2 GB Embedding,两个系统共享
Ollama qwen2.5:14b ~9.0 GB VLM,按需加载,空闲自动卸载
OpenViking Server ~300 MB Python HTTP 服务
memory-engine MCP ~80 MB Node.js + LanceDB
日常实际占用 ~6 GB VLM 空闲时自动卸载

16GB 内存的机器也能跑,把 VLM 换成 qwen2.5:7b,Embedding 换成 nomic-embed-text,总占用降到 ~8GB。


七、健康检查

部署完写了个一键检查脚本,日常开机跑一下确认所有服务正常:

bash 复制代码
$ bash ~/check-memory-stack.sh

====== Memory Stack Health Check ======
[Ollama]        OK  models: bge-m3:latest, qwen2.5:14b
[Embedding]     OK  bge-m3 dim=1024
[OpenViking]    OK  :1933
[memory-engine] OK  SSE :17823  sessions=2
=======================================

四个绿就说明一切正常。


八、总结

跑了两周下来,这套方案最明显的收益是上下文连续性

以前开新会话,得花 5~10 分钟把项目背景重新说一遍。现在 Claude 自动从记忆里拉取之前的决策和规范,直接进入干活状态。

特别是跨项目切换的场景------从前端项目切到后端项目,每个项目的记忆是隔离的,不会串。但全局偏好(代码风格、工具选择)又是共享的。

适合的场景

  • 长期维护的中大型项目(记忆越积越有价值)
  • 多项目并行开发(项目隔离避免混淆)
  • 团队有固定的编码规范和业务规则(存一次,每次自动加载)

不太适合的场景

  • 一次性脚本、临时任务(用完即走,不需要记忆)
  • 对隐私极度敏感的场景(记忆存在本地磁盘,需要自行管理)

整套方案全部开源组件,零云端依赖,数据不出本机。如果你也在用 Claude Code 做日常开发,可以试试。

相关推荐
智算菩萨2 小时前
基于多模态基础模型迈向通用人工智能:BriVL模型深度解析
论文阅读·人工智能·ai·语言模型·agi
程序员鱼皮2 小时前
万字干货 | OpenClaw 进阶玩法大全:技能 / 多 Agent / 省钱 / 安全,50+ 实战技巧一次学会
前端·后端·ai编程
AntonCook2 小时前
我打通了飞书→OpenClaw→Claude Code 的完整链路
ai编程·claude
CoderJia程序员甲2 小时前
GitHub 热榜项目 - 日榜(2026-03-19)
人工智能·ai·大模型·github·ai教程
小碗细面2 小时前
5 分钟上手 Claude 自定义 Subagents
前端·人工智能·ai编程
腾视科技TENSORTEC2 小时前
算力驱动智慧零售|腾视科技AI边缘算力盒子 —— 无人商超全场景解决方案重磅发布
人工智能·ai·零售·ainas·无人商超·ai边缘算力盒子·aibox
NikoAI编程3 小时前
Claude Code Skill 实战:从「能用」到「好用」
人工智能·ai编程·claude
小村儿3 小时前
觉醒的agent:AI为何抛弃React和Vue,自创Aether框架
前端·agent·ai编程