数据截至 (上游 commit ac9c2a586ba7)
第 4 章:深入实现、巧思、边界与代码地图
本章讲什么: 前三章讲清了原理;这一章补齐"读源码要知道的落点"——数据怎么存、向量后端有哪几种、对外几种接口、哪些设计值得抄、它会在哪崩,最后给一张符号级跳转表。
1. 存储结构
核心是几张 SQLite 表(schema 见 migrations/001_initial.sql,但注意第 3 章提到的列名不一致):
| 表 | 装什么 | 关键列 |
|---|---|---|
memories | 记忆主体 | id、content、primary_sector、salience、decay_lambda、simhash、mean_vec、segment、user_id |
vectors | 每记忆每脑区一个向量 | (id, sector) 主键、v(BLOB)、dim |
waypoints | 联想边 | (src_id, dst_id)、weight |
users | 用户画像摘要 | user_id、summary |
temporal_facts | 时间事实 | subject、predicate、object、valid_from、valid_to、confidence |
temporal_edges | 事实间关系边 | source_id、target_id、relation_type、weight |
- 向量编码:
struct.pack("{n}f", *vec)存成紧凑 float32 BLOB(vector_store.py:40),读回用struct.unpack。 - 分段(segment): 记忆按
env.seg_size分批轮转(hsg.py:419-426),后台衰减按 segment 遍历,便于分片处理。
2. 向量后端:三选一
get_vector_store(vector_store.py:92)按环境变量 OPENMEMORY_VECTOR_STORE 选后端:
| 后端 | 实现 | 特点 |
|---|---|---|
| sqlite(默认) | SQLiteVectorStore(vector_store.py:35) | 零依赖、暴力全扫余弦 |
| postgres | core/vector/postgres.py | 多用户、可扩展 |
| valkey/redis | core/vector/valkey.py | 内存向量检索 |
三者都实现同一 VectorStore 抽象基类(vector_store.py:19),上层 hsg.py 只依赖接口。
3. 对外接口:一个门面、四种壳
所有接口最终都收敛到 Memory 门面(main.py)与 hsg.py:
- Python SDK:
from openmemory.client import Memory。 - Node SDK / CLI / HTTP 服务:
packages/openmemory-js,路由汇总在server/routes/index.ts(memory/temporal/sources/ide/dashboard 等)。 - MCP server:
ai/mcp.py(run_mcp_server)暴露openmemory_query/store/get/list/delete五个工具,store支持contextual(HSG)/factual(TKG)/both。 - VS Code 扩展:
apps/vscode-extension,把 IDE 事件写进记忆。
4. 巧妙之处(可借鉴的技术)
- 自适应联想扩散。 向量命中够自信就不扩散,不自信才沿图 BFS 拉更多候选,且扩张量随不自信程度增大(
hsg.py:560-568)。省算力又提召回。 - 冷记忆分级降级而非删除。 先压缩维度、再降级成 32 维指纹,查到再重嵌入还原(
decay.py:157-176、on_query_hit)。用精度换空间,且可逆。 - 召回即强化的闭环。 查询会回写 salience 并联想扩散(
hsg.py:638-656)——记忆系统"越用越顺手",这是它区别于只读 RAG 的关键。 - 分脑区差异化遗忘。 情绪快忘、反思慢忘(
SECTOR_CONFIGS),把"人类记忆的时间性"编码进 lambda。 - simhash + 汉明距离去重。 换句式的重复内容不再生成新记忆,只强化旧记忆(
hsg.py:390-403)。 - 无 API 也能跑。 synthetic 嵌入用哈希 + n-gram + 位置编码本地造向量(
ai/synthetic.py),开箱即用、离线友好。
5. 边界与局限(诚实清单)
- README 首行明说"项目正在重写,预期 breaking changes 和 bug"——本文所述以当前 commit 为准。
- 默认嵌入质量有限。 synthetic 是词面+n-gram 的伪语义,真语义相似度需配真实模型。
- 检索无 ANN 索引。 SQLite 后端暴力全扫,规模上去会慢(
vector_store.py:67)。 - 反思聚类用词面 Jaccard(
reflect.py:20),与检索用的向量语义是两套逻辑,可能聚不到"意思相近但用词不同"的记忆。 - 代码存在冗余与疑似 bug:
hsg.py重复查询/赋值;compute_simhash有死代码段;decay.py:206-207salience 增量被覆盖;MCP factual 查询参数名与query.py签名不符(第 3 章 §6);迁移 SQL 列名(obj/relation)与代码(object/relation_type)不一致。 - 无实体归一化。 TKG 主谓宾裸字符串精确匹配,大小写/别名不合并。
- 多用户隔离靠
user_id过滤,非强隔离——同库共享,靠查询条件区分。
6. 横向对比(memory-context 货架内)
OpenMemory 在"给 agent 的长期记忆"这一格的取舍:
| 维度 | OpenMemory 的选择 |
|---|---|
| 记忆模型 | 五脑区分类 + 复合打分,而非单一向量相似度 |
| 时间性 | salience 衰减/强化 + 独立的时间事实图谱(双时态) |
| 关联 | 显式 waypoint 图 + 联想扩散,而非纯 kNN |
| 部署 | 自托管、本地优先(SQLite),对标 Mem0/Zep/Supermemory 的云 API(且自带从它们迁移的工具) |
| 依赖 | 可零外部依赖跑(synthetic 嵌入 + SQLite) |
与纯 RAG 记忆库相比,它的差异化就一句:记忆有状态、会遗忘、会被想起、会互相联想。代价是逻辑更重、调参面更大、当前实现还在重写期。
7. 代码地图(导航索引)
以下符号名相对行号更抗漂移,agent 可直接 grep 定位(路径相对 packages/openmemory-py/src/openmemory/):
| 主题 | 文件 | 符号 |
|---|---|---|
| 对外门面 add/search | main.py | Memory、Memory.add、Memory.search |
| 大文档拆分 root-child | ops/ingest.py | ingest_document、split_text、mk_root、mk_child、link |
| 多格式抽取 | ops/extract.py | extract_text、extract_pdf、extract_url |
| 写入核心:去重/分区/嵌入/建链 | memory/hsg.py | add_hsg_memory |
| 脑区分类 | memory/hsg.py、core/constants.py | classify_content、SECTOR_CONFIGS |
| simhash 去重 | memory/hsg.py | compute_simhash、hamming_dist |
| 召回核心 | memory/hsg.py | hsg_query、compute_hybrid_score、boosted_sim |
| 联想扩散/建边 | memory/hsg.py | expand_via_waypoints、create_single_waypoint、reinforce_waypoints、prune_weak_waypoints |
| 复合分权重/超参 | memory/hsg.py | SCORING_WEIGHTS、HYBRID_PARAMS、SECTOR_RELATIONSHIPS |
| 衰减公式 | memory/hsg.py | calc_decay |
| 后台衰减 + 冷记忆降级 | memory/decay.py | apply_decay、pick_tier、compress_vector、fingerprint_mem、on_query_hit |
| 记忆动力学(共振/强化/扩散) | ops/dynamics.py | calculateCrossSectorResonanceScore、applyRetrievalTraceReinforcementToMemory、propagateAssociativeReinforcementToLinkedNodes |
| 反思聚类 | memory/reflect.py | run_reflection、cluster、sim_txt |