研究日期:2026-07-23(Asia/Shanghai)
仓库:mem0ai/mem0
源码快照:ca2abca2b884e038d3e525070e79d3057ef2012c
Python 包版本 / 最新正式版本:2.0.13 / v2.0.13(2026-07-22)
TypeScript OSS 包版本 / 最新正式版本:3.1.1 / ts-v3.1.1(2026-07-22)

版本与实时校准

本文研究 2026-07-23 抓取的 main。当日 GitHub API 快照为 61,505 stars、7,158 forks、683 个 open issues + PRs;数字只用于固定研究时点,不作为质量结论。

当前快照最后一个提交为:

ca2abca2b884e038d3e525070e79d3057ef2012c
fix(ts-oss/milvus): skip '*' wildcard filter values instead of matching literally (#6508)
2026-07-23T00:41:45+05:30

v2.0.13 的 Python release 同一天发布,主要修复 local Qdrant reset、Pinecone namespace reset、Mochow L2 分数方向、structured OpenAI 环境变量,以及 update metadata 改写 identity 等问题。托管 client 中一个“已转发但从未影响检索”的 retrieval_criteria 参数也被移除。这些 release 细节说明 mem0 现在最值得观察的不只是模型 prompt,而是 provider contract、身份边界和跨存储一致性。

对原 HTML / 旧稿最重要的更新

旧稿的标题“为什么新版记忆算法不再修改旧事实”过度概括,必须纠正:

  • 只有自动抽取路径是 ADD-onlyMemory.add(..., infer=True) 不再让 LLM 输出 UPDATE / DELETE;
  • Python OSS 仍公开提供 update()delete()delete_all()reset()history()
  • expiration_date 可以让结果在默认 search / get_all 中隐藏,但不会物理删除记录;
  • Platform v3 还有 Temporal Reasoning、Memory Decay、latest_only / delete_linked 等托管能力,不能反推到 OSS;
  • 2025 年的 Mem0 论文和旧的 ADD / UPDATE / DELETE 决策架构,不再与 2026 年 4 月合并的 OSS v3 主路径一一对应。

更准确的中心论点是:

ADD-only 是自动写入链的一次降复杂度重构。它把“本轮该不该覆盖旧事实”的第二次 LLM 决策移出热路径,却把冲突、增长、时间、删除和跨存储一致性推给了检索与显式治理。

研究提纲

核心论点

mem0 v3 的关键变化不是“选择了 append-only 哲学”,而是重新分配了不确定性:

  1. 模型只负责从新消息中抽取可复用事实;
  2. 确定性代码负责 embedding、精确 hash 去重、写入与实体索引;
  3. semantic、BM25、entity 三路信号在读取时决定哪些事实更值得出现;
  4. 应用负责显式 update、delete、expiration 与合规治理;
  5. vector store、SQLite history、recent messages、entity collection 之间没有统一事务。

模型少做了一次 diff,系统没有因此变简单;复杂度从“写入时改哪一条”迁到了“读到矛盾时相信哪一条,以及怎样真正清掉它”。

主要资料来源

  1. Python OSS 当前默认分支源码与测试;
  2. OSS v2 → v3 migration guide
  3. PR #4805:v3 ADD-only、混合检索、内建实体关联与 8 阶段 batch pipeline;
  4. Issue #4970:prompt 输出的 linked_memory_ids 在 Python / TypeScript OSS 中未被消费;
  5. Issue #4863:新进程中的 delete / update 可能跳过 entity cleanup;
  6. Issue #4988:entity cleanup 每次最多全扫 10,000 条;
  7. Issue #6512:Python OSS 的 user-scoped delete 不清除 SQLite history 中的 PII;
  8. memory-benchmarks:当前 benchmark 的 ingest → search → answer / judge 方法;
  9. Mem0 论文:历史研究背景,不作为当前 v3 代码说明书;
  10. v2.0.13 / ts-v3.1.1 release 与 GitHub API 元数据。

关键源码入口

  1. mem0/memory/main.py::Memory.add
  2. mem0/memory/main.py::Memory._add_to_vector_store
  3. mem0/configs/prompts.py::ADDITIVE_EXTRACTION_PROMPT
  4. mem0/memory/main.py::Memory.search
  5. mem0/memory/main.py::Memory._search_vector_store
  6. mem0/utils/scoring.py::score_and_rank
  7. mem0/utils/entity_extraction.py
  8. mem0/utils/lemmatization.py
  9. mem0/utils/spacy_models.py
  10. mem0/memory/storage.py::SQLiteManager
  11. mem0/memory/main.py::Memory._update_memory
  12. mem0/memory/main.py::Memory._delete_memory
  13. mem0/vector_stores/base.py::VectorStoreBase
  14. mem0/vector_stores/qdrant.py
  15. mem0/configs/base.py::MemoryConfig

结论先行

mem0 是一层可嵌入的 agent memory infrastructure,不是完整 agent,也不是一张自动保持“当前真相”的 profile 表。

当前 Python OSS 最准确的三层结构是:

  • 事实与召回层:主 vector collection,payload 内保存 memory text、hash、identity、时间和 metadata;
  • 辅助排序层:同一 vector provider 中的 {collection}_entities,以及 provider 可选的 BM25 / full-text 能力;
  • 审计与短期上下文层:本地 SQLite 的 history 与每个 session scope 最近 10 条 messages

官方 how-it-works 页面仍把 SQL 写成“事实 source of truth”,platform-vs-oss 页面还写 OSS 使用 external graph store;这两处与当前 Python OSS 代码和 v2 → v3 migration guide 不一致。本文以固定源码与 migration 为准,并把 Platform、Python OSS、TypeScript OSS 分开。

产品层、架构层、代码层

产品层

mem0 对应用暴露的核心工作流很小:

一次有价值的交互之后:add(messages, user_id / agent_id / run_id)
下一次模型调用之前:search(query, filters={...})
需要治理时:update / delete / delete_all / reset / history

开发者可以用默认 OpenAI LLM + embedding、embedded Qdrant 快速起步,也可以替换 LLM、embedder、vector store 和 reranker。默认配置并不等于本地离线:默认 LLM 与 embedder provider 都是 OpenAI,默认 vector store 才是本地 /tmp/qdrant

架构层

application
  │
  ├─ add()
  │    ├─ LLM extractor
  │    ├─ embedder
  │    ├─ main vector collection
  │    ├─ entity vector collection
  │    └─ SQLite history + recent messages
  │
  ├─ search()
  │    ├─ semantic candidates
  │    ├─ provider keyword / BM25 scores
  │    ├─ entity boosts
  │    └─ optional reranker
  │
  └─ explicit governance
       ├─ update / delete / expiration
       └─ reset / history

这些部件由 Python 进程顺序协调,不共享一个跨后端 transaction。所谓“memory record”其实同时投影到多个存储;任何生产部署都要自己回答重试、对账、备份与彻底删除的问题。

一条真实 add 请求

1. 身份先于事实

Memory.add() 要求 user_idagent_idrun_id 至少有一个。identity 同时进入 metadata 与 vector filters,用来隔离召回范围。当前 timestamp 明确是 Platform-only,OSS 传入会报错;expiration_date 则由 OSS 规范成 YYYY-MM-DD

输入可以是字符串、单条 message 或 message list。vision message 会先经过专门解析;若指定 memory_type="procedural_memory" 且有 agent_id,则走 procedural summary 的旁路。普通 infer=True 才进入 v3 pipeline。

2. Phase 0–1:只拿一小圈上下文

系统从 SQLite 取当前 session scope 最近 10 条消息,然后把本轮 message list 解析成文本。它用整段文本生成 query embedding,从主 vector store 取 top-10 既有 memories。

这 10 条同时承担两件事:

  • 给 LLM 做语义去重与关联参考;
  • 给后续 MD5 精确去重提供 existing hash 集合。

因此精确去重不是全库唯一约束。若一条完全相同的旧 memory 没进 top-10,它仍可能再次写入。

3. Phase 2:一次 LLM 调用,只做抽取

system prompt 的角色非常明确:

Your sole operation is ADD

它要求从 user 与 assistant message 中抽取可复用事实,并避免把已有 memory 本身当成新证据。LLM transport error 会重新包装为 LLMError 抛给调用方,方便上层 retry;但空响应、坏 JSON 或解析异常会记录错误、保存 recent messages,然后返回空结果。两类失败有不同语义。

代码把旧 memory UUID 临时映射为 "0""1" 等短 ID,注释称其目的为 anti-hallucination:

uuid_mapping[str(idx)] = mem.id
existing_memories.append({"id": str(idx), "text": ...})

但当前主路径之后从未读取 uuid_mapping。prompt 又要求 LLM 输出 linked_memory_ids,而 Phase 4 构造 payload 时只消费 textattributed_to。Issue #4970 对这个断层的描述与固定快照代码一致:模型生成的 memory-to-memory link 是未落盘的幽灵字段。

真正生效的“linking”发生在 Phase 7,是另一套基于 spaCy entity 的 entity-to-memory 索引。

4. Phase 3–5:batch embedding 与有限精确去重

抽取出的 memory texts 先走 embed_batch();batch 失败后逐条 embed,某条失败就跳过该 memory。

随后确定性代码:

  1. 对 text 计算 MD5;
  2. 与 top-10 existing hashes 比较;
  3. 与本批次 seen_hashes 比较;
  4. 生成新 UUID;
  5. 保存原文、lemmatized text、hash、created / updated time、identity 与 attributed_to

MD5 在这里不是安全签名,只是精确内容指纹。语义近似事实能否不重复,主要依赖前面的 top-10 召回和 LLM 判断。

5. Phase 6:batch persist 没有提交清单

主 vector batch insert 失败后,代码逐条重试;每条重试失败只记 error,不从 records 中移除。接下来的 history、entity linking 与返回值仍遍历原始 records

records = 两条候选
batch insert 失败
  ├─ memory A 单条重试成功
  └─ memory B 单条重试失败
history = A + B
return  = A + B

本次最小 mock 探针在固定快照上复现了这一行为:第二条 vector 最终未写入,_add_to_vector_store 仍返回两条 ADD,batch_add_history 也收到两条。反方向同样可能发生:vector 已写成功,history 写失败后逐条重试仍可能失败,但请求继续返回成功。

这是典型的多存储一致性问题,不是“batch 性能优化”的小尾巴。调用方若需要 exactly-once 或可审计写入,必须在 mem0 外层增加 idempotency、读后确认或 reconciliation。

6. Phase 7:所谓 graph 是扁平实体倒排

若安装 spaCy 与 en_core_web_sm,mem0 会从每条 memory 中抽取:

  • named entities;
  • proper names;
  • quoted text;
  • technical identifiers;
  • multi-word topic phrases。

全批次先按规范化 text 去重,entity text 再 batch embed。系统在 {collection}_entities 中做 exact lookup 与 top-1 semantic lookup;相似度至少 0.95 时合并,否则新增 entity record:

{
  "data": "Shopify",
  "entity_type": "PROPER",
  "linked_memory_ids": ["memory-a", "memory-b"],
  "user_id": "alice"
}

它没有 entity-to-entity edge、relation type 或图遍历 API。旧 relations 返回字段已经删除。因此“built-in graph memory”更准确的工程描述是:一个实体倒排 collection,为共享实体的 memories 提供排序 boost

7. Phase 8:保存最近消息

SQLite messages 表按 session_scope 保存 role、content、name、created_at,每次只保留最近 10 条。它是下一次抽取的短期上下文,不是用户可见的完整 transcript。

history 表保存 ADD / UPDATE / DELETE 的 old / new value、时间、actor 与 role;它没有 user / agent / run identity 列。这一 schema 直接解释了为什么 delete_all(user_id=...) 无法按用户物理清除历史。

一条真实 search 请求

1. search filter 是租户边界的一部分

search() 要求 filters 中至少有 user_idagent_idrun_id 之一;旧式 top-level identity kwargs 会报错。高级 metadata operators 会交给不同 vector store adapter 转换,因此兼容程度受 provider 影响。

默认参数是:

top_k = 20
threshold = 0.1
rerank = False
explain = False

2. 三路信号不是三个候选集

_search_vector_store() 的步骤是:

lemmatize query + extract at most 8 entities
→ query embedding
→ semantic search,internal_limit = max(top_k × 4, 60)
→ provider.keyword_search()
→ BM25 sigmoid normalization
→ entity boost
→ 只从 semantic results 构造 candidates
→ score_and_rank
→ optional reranker

最容易误读的点是:BM25 与 entity 都只是 boost signals,不是 recall expanders。一个只被精确关键词命中、却没进 semantic over-fetch 的 record,无法进入最终结果。Migration guide 已明确写出这条边界。

3. 分数公式里的三个细节

score_and_rank() 使用:

combined = (semantic + normalized_bm25 + entity_boost) / max_possible

其中:

  • semantic threshold 在融合前生效;
  • BM25 通过与 query term count 相关的 sigmoid 归一;
  • entity boost 最大权重为 0.5
  • max_possible 根据当前是否存在任意 BM25 / entity score,在 1.01.52.02.5 之间变化。

最后一点意味着,只要本轮候选集中有一条拿到 BM25 或 entity signal,所有候选都会使用更大的分母。没有获得该 signal 的 candidate 会被相对压低。这是一种简单的全局归一,不是学习得到的 calibrated probability;版本迁移后不能沿用旧 cosine threshold。

4. entity boost 会惩罚 hub entity

查询最多取 8 个去重 entity,每个 entity 在 entity store 中取最多 500 个匹配。相似度低于 0.5 不加分。每条 memory 的多个 entity boost 取最大值,不累加:

boost = similarity × 0.5 × 1 / (1 + 0.001 × (linked_count - 1)²)

linked_count 越大,权重越低,避免 “Google”“project” 一类 hub entity 把数百条 memory 一起抬高。

5. graceful degradation 依赖 provider 组合

如果 vector store 没实现 keyword_search(),初始化会 warning,search 退到 semantic。Qdrant 还需要 fastembed 创建 BM25 sparse vector slot。

若 spaCy 不可用:

  • entity extraction 返回空;
  • lemmatizer 返回原始 text;
  • entity boost 消失;
  • 某些支持 native keyword search 的 store 仍可能在原始 text 上运行 BM25。

所以 migration guide 中“没有 spaCy 就 semantic-only”的表述对所有 provider 组合来说过宽;代码更精确的行为是“无 entity、无词形归一,BM25 是否存在取决于 vector adapter 与其依赖”。

自动 ADD-only 与显式治理

update

update(memory_id, text, metadata, expiration_date)

  1. 先从 vector store 读取旧 record;
  2. identity keys 不能改写或注入;
  3. text 改变时重新 embedding、hash 与 lemmatize;
  4. vector store update;
  5. SQLite 写 UPDATE history;
  6. text 改变时清理旧 entity links,再对新 text 重新关联。

它不是跨存储 transaction。若 vector update 成功、history 或 entity cleanup 失败,状态会分裂。

delete 与 delete_all

delete() 先删除主 vector,再写 DELETE history,最后尽力移除 entity record 中的 memory id。Entity cleanup 被刻意设计为 non-fatal,不能破坏主删除路径。

两个当前开放 issue 与源码相符:

  • _remove_memory_from_entity_store()_entity_store is None 时直接返回,因此一个刚启动、还没触碰 entity store 的进程可能留下 stale link(#4863);
  • cleanup 会 list(..., top_k=10000) 再在 Python 扫描,超过 10,000 entities 时既慢又可能不完整(#4988)。

delete_all(user_id=...) 会列出该 scope 的 memories,逐个调用 delete。它不会清除 SQLite history 的原文。Issue #6512 把这点与 GDPR / PII 风险联系起来,并请求 Python 增加类似 Node disableHistory 的开关。

reset() 才会丢弃全部 history / messages 与 vector collections,但它不是按用户治理工具。

expiration

expiration_date 只是读取时的软过滤。过期 memory 默认不出现在 get_all / search,show_expired=True 可以重新看到,底层 vector、history 与 entity link 不会自动删除。

同步与异步

MemoryAsyncMemory 各自复制了完整 8 阶段 pipeline。异步版把 blocking provider calls 放进 asyncio.to_thread(),entity searches 使用 semaphore / gather;主逻辑基本同构。

测试覆盖了两边的 entity embedding count guard、identity immutability、entity boost 并行、history 与 delete_all race 修复。但“复制两份大函数”仍是一项维护成本:未来某个 fallback、metadata 字段或 error path 很容易只修一边。

代表性代码片段

1. ADD-only 的边界在自动抽取,不在整个 API

response = self.llm.generate_response(
    messages=[
        {"role": "system", "content": ADDITIVE_EXTRACTION_PROMPT},
        {"role": "user", "content": user_prompt},
    ],
    response_format={"type": "json_object"},
)

后面只把抽取结果变成新 UUID;显式 update() / delete() 是另一条 API 路径。

2. BM25 与 entity 不能把候选“救回来”

candidates = []
for mem in semantic_results:
    candidates.append(...)

scored_results = score_and_rank(
    semantic_results=candidates,
    bm25_scores=bm25_scores,
    entity_boosts=entity_boosts,
    ...
)

semantic 是 candidate generator;另外两路只是 ranker features。

3. 部分写入失败没有 reflected commit set

try:
    vector_store.insert(all_records)
except Exception:
    for record in all_records:
        try:
            vector_store.insert(record)
        except Exception:
            log_error()

write_history(all_records)
return all_records

稳健实现应该维护 committed_records,让 history、entity linking 与 response 只消费确认写入的集合,或用 outbox / reconciliation 明确 eventual consistency。

与相邻方案的区别

| 方案 | 主要 memory 单位 | 写入治理 | 检索结构 | 最独特的边界 | |---|---|---|---|---| | mem0 | 扁平事实 record | 自动 ADD-only + 显式 CRUD | semantic candidates + BM25 / entity boost | 可嵌入、多 provider | | OpenHuman | provenance leaf + 多层 summary | admission / compaction | Source Tree + entity / dense | 可下钻回 source_ref | | Hermes Agent | prompt-prefix memory + sidecar | lifecycle hook | provider-specific recall | 稳定 prompt prefix | | 传统向量记忆 | text chunk | 应用自行维护 | top-k similarity | 结构最少、运维最简单 |

mem0 的优势是 API 面积小、接入容易、provider 选择多。它不提供 OpenHuman 那样的来源树,也不把 memory 绑定在完整 agent loop 里。代价是应用必须自己定义“当前真相”、删除承诺和跨 store 对账。

适合与不适合

适合

  • 客服、销售辅助、个性化助手;
  • 需要跨 session 记住偏好、计划、账号或项目事实的 SaaS;
  • 想快速在现有 agent 前后插入 memory layer 的开发者;
  • 需要自选 LLM、embedder、vector database 的团队;
  • 能自己建立 tenant isolation、审计和数据生命周期的工程组织。

不适合直接照搬

  • 把 memory 当医疗、法律、财务“当前事实源”的高风险系统;
  • 需要严格 transaction、exactly-once 或强合规删除证明的场景;
  • 需要关系类型、图遍历、复杂 temporal validity 的知识图谱;
  • 只靠默认配置就期待完全本地、完全离线的部署;
  • 不愿为不同 vector adapters 做兼容测试和阈值重调的团队。

社会、市场与个人判断

mem0 把“记住用户”从产品功能抽成基础设施,会让越来越多应用共享一种新默认:每次对话不再是一次性 session,而是对用户模型的持续写入。

这会首先打动三类人:

  1. 开发个性化产品、却不想自己造 memory pipeline 的小团队;
  2. 已有大量用户交互,希望用历史提高留存与转化的 SaaS;
  3. 对 agent continuity 有强烈需求的重度 AI 用户。

市场机会不只在“memory API”,还会在 memory observability 与 governance:为什么留下这条、它来自哪里、哪一次检索用过它、删除是否传播、冲突由谁裁决。

最大的社会风险也来自同一个位置。应用一旦把“用户说过什么”持续抽成结构化事实,聊天就从沟通变成隐形 profile 写入。ADD-only 降低了模型静默覆盖历史的风险,却可能放大“永远累计”的风险。真正尊重用户的系统不能只提供一个 delete 按钮;它要证明 vector、entity、history、backup、analytics 与托管衍生数据都遵守同一个删除契约。

局限与未证实项

  1. 没有运行真实 OpenAI、Anthropic 或本地模型的 extraction quality benchmark;
  2. 没有启动真实 Qdrant / Pinecone / Milvus 做跨 provider integration;
  3. 没有独立复现 README 中 LoCoMo、LongMemEval、BEAM 分数;
  4. README benchmark 是官方 production-representative stack 的报告,不等于默认 OSS 配置成绩;
  5. Issue #4863、#4988、#6512 是开放社区报告;本文只对其中能由固定源码直接解释的机制做了静态核对;
  6. 当前代码的 linked_memory_ids 未消费是固定快照事实,未来可能通过移除 prompt 字段或真正落盘而改变;
  7. Platform backend 不在本仓库,本文不对其内部 transaction、temporal 或 deletion 实现做推断。

验证记录

当前事实

Python snapshot: ca2abca2b884e038d3e525070e79d3057ef2012c
Python package: 2.0.13
Python release: v2.0.13
TypeScript OSS package: 3.1.1
TypeScript release: ts-v3.1.1
License: Apache-2.0

聚焦测试

项目指导要求使用 Hatch。机器初始没有 hatch,先通过 uvx --from hatch 创建 dev_py_3_12;该环境会安装全部 vector-store / LLM extras,下载 litellmmaturin build dependency 时网络中断,未进入测试。为了不让无关 provider 阻塞核心验证,后续使用项目声明的最小 test extra:

uv run --extra test pytest \
  tests/utils/test_scoring.py \
  tests/memory/test_main.py -q

结果:

69 passed in 17.92s

扩展运行:

uv run pytest \
  tests/test_memory.py \
  tests/memory/test_storage.py \
  tests/utils/test_entity_extraction.py \
  tests/utils/test_lemmatization.py -q

初次结果:

73 passed, 19 skipped, 1 failed

唯一失败是最小环境没有可选 langchain-core,测试却显式传入 custom LangChain LLM。补齐该可选依赖后单测通过:

uv run --with 'langchain-core>=0.3.85,<1.0.0' pytest \
  tests/test_memory.py::test_async_procedural_memory_langchain_strips_code_blocks -q
1 passed

19 项 skip 来自未安装 spaCy / en_core_web_sm 的 NLP 路径,不计作通过。

一致性探针

另写了一个临时 mock test,完成后已删除。它让:

  1. LLM 抽取两条 memories,并为两条都返回 linked_memory_ids
  2. vector batch insert 失败;
  3. 第一条单独重试成功;
  4. 第二条单独重试失败;
  5. entity extraction 为空。

断言确认:

  • 方法仍返回两条 ADD;
  • SQLite batch history 仍收到两条;
  • vector payload 不包含 LLM 输出的 linked_memory_ids

结果:

1 passed in 0.60s

去重后的验证总计为 144 passed、19 skipped、0 个未解释的代码断言失败。其中 1 项是本文临时一致性探针。

验证边界

测试证明固定快照的单元契约和 mock failure behavior,不证明:

  • 真实 provider 的原子性;
  • 多进程并发;
  • 大规模 entity cleanup 性能;
  • extraction / retrieval 的线上准确率;
  • Platform 的内部实现;
  • GDPR 删除合规。

中文文章结构

  1. 从“它不再改旧事实”的误解开场;
  2. 把 ADD-only 定义为自动路径边界;
  3. 走通 8 阶段 add pipeline;
  4. 解释幽灵 linked_memory_ids 与真正的 entity linking;
  5. 走通 semantic candidate + BM25 / entity boost;
  6. 展开 update / delete / expiration;
  7. 用部分写入与 SQLite PII 说明治理成本;
  8. 比较 OpenHuman、Hermes 与普通向量库;
  9. 谁适合 / 不适合;
  10. 社会、市场与个人思考;
  11. 三个继续追问的问题。

英文文章结构

  1. Thesis: ADD-only is a write-path simplification, not an immutable-history guarantee;
  2. public API and product-layer separation;
  3. an end-to-end add() request;
  4. the unused LLM link contract versus entity-backed links;
  5. hybrid retrieval as candidate generation plus feature fusion;
  6. sync / async and provider degradation;
  7. explicit CRUD and soft expiration;
  8. partial-commit and erasure semantics;
  9. comparison with provenance trees and agent-native memory;
  10. operational guidance and production checklist;
  11. further questions.

三个继续追问的问题

  1. ADD-only 下的矛盾事实,应该由 time-aware ranking、显式 update,还是用户确认来裁决?
  2. 怎样把 vector、entity、history、backup 与 analytics 放进一个可证明的删除协议?
  3. 混合检索应继续使用固定公式,还是按租户、语料与 query type 学习校准权重?

主要外部链接