claude-mem 架构与设计思路分析
2026-05-12 · 来源:claude-mem-architecture-analysis.md
原文:
raw/claude-mem-architecture-analysis.md· 仓库:thedotmack/claude-mem · 分析版本 v13.1.0
一句话定位
claude-mem 是给 claude-code 装上"长期记忆"的开源插件——通过宿主的 6 个 Lifecycle Hook 无侵入采集工具调用,用 claude-agent-sdk 异步压缩成结构化"观察 (observation)",存进本地 SQLite + Chroma 双索引;下次开会话时自动检索相关历史并注入上下文。
核心架构(三层)
| 层 | 组件 | 职责 |
|---|---|---|
| 边缘层 | bun-runner.js + 6 个 hook handler |
轻量 stdin 接力,禁止做 AI 推理 |
| 后台层 | Worker Service (Express daemon, 端口 37700 + uid%100) + BullMQ |
异步压缩、入库、向量同步 |
| 存储层 | SQLite (FTS5 全文) + Chroma (向量) | 双索引 |
详见 event-driven-memory-pipeline。
完整架构图
flowchart TB
subgraph CC["CLAUDE CODE 运行时"]
direction TB
subgraph HOOKS["事件钩子 — plugin/hooks/hooks.json 注册的 6 个 Lifecycle 钩子"]
direction LR
H1["SessionStart"]
H2["UserPromptSub"]
H3["PreToolUse(Read)"]
H4["PostToolUse"]
end
Bun["bun-runner.js
轻量 stdin 接力 → 派发 worker-service 子命令"] HOOKS --> Bun end subgraph WS["Worker Service — Express daemon"] direction TB WSrv["src/services/worker-service.ts
端口: 37700 + (uid % 100) · PID: ~/.claude-mem/worker.pid"] Routes["路由层 (7 组 Routes)
/api/context/* · /api/sessions/* · /api/observations
/api/search/* · /api/memory/save · /api/chroma/status · /v1/*"] Prov["Providers
Claude / Gemini / OR
(Agent SDK 调用)"] RP["ResponseProcessor
XML → Observation
事务批量入库"] CS["ChromaSync
向量同步 + watermark 回填"] WSrv --> Routes Prov --> RP --> CS end SQ["SQLite — ~/.claude-mem/.db
observations / FTS5 · sessions / prompts"] Ch["Chroma 向量库 — ~/.claude-mem/chroma
语义检索"] subgraph CONS["消费者"] direction LR Sk["mem-search Skill
3 层搜索协议
search → timeline → get_observations"] UI["Viewer UI (React)
SSE 流式渲染
时间线 / 观察列表"] end CC -- "HTTP / 进程派发" --> WS RP --> SQ CS --> Ch SQ --> Sk SQ --> UI Ch --> Sk Ch --> UI classDef cc fill:#1e3a8a,stroke:#1e40af,color:#fff classDef ws fill:#7c2d12,stroke:#9a3412,color:#fff classDef st fill:#14532d,stroke:#166534,color:#fff classDef cn fill:#5b21b6,stroke:#6d28d9,color:#fff class CC,HOOKS,H1,H2,H3,H4,Bun cc class WS,WSrv,Routes,Prov,RP,CS ws class SQ,Ch st class CONS,Sk,UI cn
轻量 stdin 接力 → 派发 worker-service 子命令"] HOOKS --> Bun end subgraph WS["Worker Service — Express daemon"] direction TB WSrv["src/services/worker-service.ts
端口: 37700 + (uid % 100) · PID: ~/.claude-mem/worker.pid"] Routes["路由层 (7 组 Routes)
/api/context/* · /api/sessions/* · /api/observations
/api/search/* · /api/memory/save · /api/chroma/status · /v1/*"] Prov["Providers
Claude / Gemini / OR
(Agent SDK 调用)"] RP["ResponseProcessor
XML → Observation
事务批量入库"] CS["ChromaSync
向量同步 + watermark 回填"] WSrv --> Routes Prov --> RP --> CS end SQ["SQLite — ~/.claude-mem/.db
observations / FTS5 · sessions / prompts"] Ch["Chroma 向量库 — ~/.claude-mem/chroma
语义检索"] subgraph CONS["消费者"] direction LR Sk["mem-search Skill
3 层搜索协议
search → timeline → get_observations"] UI["Viewer UI (React)
SSE 流式渲染
时间线 / 观察列表"] end CC -- "HTTP / 进程派发" --> WS RP --> SQ CS --> Ch SQ --> Sk SQ --> UI Ch --> Sk Ch --> UI classDef cc fill:#1e3a8a,stroke:#1e40af,color:#fff classDef ws fill:#7c2d12,stroke:#9a3412,color:#fff classDef st fill:#14532d,stroke:#166534,color:#fff classDef cn fill:#5b21b6,stroke:#6d28d9,color:#fff class CC,HOOKS,H1,H2,H3,H4,Bun cc class WS,WSrv,Routes,Prov,RP,CS ws class SQ,Ch st class CONS,Sk,UI cn
原 ASCII 图
┌───────────────────────── CLAUDE CODE 运行时 ─────────────────────────┐
│ │
│ 事件钩子(plugin/hooks/hooks.json 注册的 6 个 Lifecycle 钩子) │
│ ┌────────────┐ ┌──────────────┐ ┌────────────────┐ ┌─────────────┐ │
│ │SessionStart│ │UserPromptSub │ │PreToolUse(Read)│ │PostToolUse │ │
│ └─────┬──────┘ └──────┬───────┘ └────────┬───────┘ └──────┬──────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ bun-runner.js(轻量 stdin 接力 → 派发 worker-service 子命令) │
└─────────────────────────────┬───────────────────────────────────────────┘
│ HTTP / 进程派发
▼
┌──────────────────── Worker Service (Express daemon) ────────────────────┐
│ src/services/worker-service.ts │
│ 端口: 37700 + (uid % 100) • PID: ~/.claude-mem/worker.pid │
│ │
│ 路由层(7 组 Routes) │
│ /api/context/* /api/sessions/* /api/observations │
│ /api/search/* /api/memory/save /api/chroma/status /v1/* │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ Providers │ │ ResponseProcessor│ │ ChromaSync │ │
│ │ Claude/Gemini/OR │→ │ XML→Observation │→ │ 向量同步 + watermark │ │
│ │ (Agent SDK 调用) │ │ 事务批量入库 │ │ 回填 │ │
│ └──────────────────┘ └────────┬─────────┘ └──────────┬───────────┘ │
└─────────────────────────────────┼────────────────────────┼────────────────┘
▼ ▼
┌────────────────────┐ ┌────────────────────┐
│ SQLite │ │ Chroma 向量库 │
│ ~/.claude-mem/.db │ │ ~/.claude-mem/chroma│
│ observations / FTS5│ │ 语义检索 │
│ sessions / prompts │ │ │
└─────────┬──────────┘ └─────────┬──────────┘
└─────────┬──────────────┘
│
┌────────────────┴────────────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ mem-search Skill │ │ Viewer UI (React)│
│ 3 层搜索协议 │ │ SSE 流式渲染 │
│ search→timeline │ │ 时间线/观察列表 │
│ →get_observations│ │ │
└──────────────────┘ └──────────────────┘
六个生命周期钩子
| 阶段 | 触发 | Handler | 作用 |
|---|---|---|---|
| Setup | 插件初始化 | version-check.js |
版本兼容性 |
| SessionStart | 启动/clear/compact | context.ts |
启 worker + 注入历史上下文 |
| UserPromptSubmit | 用户提交 prompt | session-init.ts |
创建 session + 触发语义搜索 |
| PreToolUse(Read) | 读文件前 | file-context.ts |
查当前文件相关观察 |
| PostToolUse | 任何工具调用后 | observation.ts |
入队等待压缩 |
| Stop | 会话结束 | summarize.ts |
生成会话摘要 |
五步闭环
- 捕获:Hook →
bun-runner.js→ worker handler → POST 给本地 worker - 入队:写 outbox(Postgres)或 SQLite 暂存,BullMQ 调度
- AI 压缩:
ProviderObservationGenerator调 Agent SDK,把 tool log 压成 XML 观察 - 解析存储:
sdk/parser.ts→MemoryItemschema → 事务写入 SQLite + 异步推 Chroma - 注入回流:
SessionStart预检索 → system 消息;或mem-searchSkill 按需查询
六大可移植设计模式
- 生命周期钩子采集 — 任何有 before/after tool_use 钩子的 runtime 都能挂
- 边缘轻量 + 后台 AI 重活 — 用户交互路径上绝不做 AI 推理
- 三层搜索协议 —
search → timeline → get_observations,详见 three-tier-search-protocol - AI 作为压缩器 — 详见 ai-as-compressor
- session_id 双轨制 —
contentSessionId(会话)vsmemorySessionId(记忆线) - Outbox + 内容哈希去重 — AI 生成非确定,必须去重;outbox 让重试不丢消息
MemoryItem Schema
{
kind: 'observation' | 'summary' | 'prompt' | 'manual',
type: string,
title: string,
narrative: string, // 自然语言叙述
facts: string[], // 离散事实点
concepts: string[], // 抽象概念标签
filesRead: string[],
filesModified: string[]
}
[!note] 关键洞察
别只存"对话历史",先压缩成"事实/概念/读写文件"等可索引字段;语义搜索叠在结构化层之上。
多 Profile 隔离
export CLAUDE_MEM_DATA_DIR="$HOME/.claude-mem-work"
export CLAUDE_MEM_WORKER_PORT=37800
所有路径(DB、Chroma、日志、PID)从 env 派生,shell 级隔离。
演进信号
近 30 天最活跃:worker-service.cjs(88 次)、mcp-server.cjs(75 次)。PR #2383 把 worker 重写为 server-beta:事件管道 + Postgres + MCP + Docker + 团队审计。趋势是从「单机插件」→「团队后台服务」,但 runtime-selector.ts 保留单机 SQLite 回退。
关键文件索引
| 关注点 | 路径 |
|---|---|
| 钩子注册 | plugin/hooks/hooks.json |
| Hook 派发 | plugin/scripts/bun-runner.js |
| Worker 入口 | src/services/worker-service.ts |
| Provider 抽象 | src/sdk/{Claude,Gemini,OpenRouter}Provider.ts |
| XML 解析 | src/sdk/parser.ts |
| 上下文构建 | src/services/context/ContextBuilder.ts |
| 压缩 Prompt | src/sdk/prompts.ts |
| 搜索 Skill | plugin/skills/mem-search/SKILL.md |
最小可行落地(抄作业 5 步)
给任何 Agent 加记忆的最小套路:
- 采集:tool callback 里
record_event(event)塞 SQLite outbox - 压缩:cron/worker 跑
compress_batch(),喂给便宜模型(Haiku / Gemini Flash)输出{title, narrative, facts[], concepts[]}JSON - 双索引:SQLite FTS5 + 向量库
- 注册搜索工具:
search_memory(query),描述写"用户提到过去/上次时调用" - 会话启动预检索:基于"项目路径 + 最近活跃概念"拼 system prompt 前缀