Migrating from mem0
Data shape, identity model, write verb (add → encode), and retrieval (search → recall) differences when moving from mem0 to Brain.
mem0 and Brain both ingest conversation, extract memories from it, and serve retrieval — so the mapping is closer than most vendor migrations. The main differences are the verb surface, the identity model, and the shape of a read.
Verb mapping
| mem0 | Brain |
|---|---|
m.add(messages, user_id=...) | client.encode({ text }) under the user's scope |
m.search(query, user_id=...) | client.recall({ query }) |
m.delete(memory_id=...) | client.forget({ memory_id }) |
m.get_all(user_id=...) | list memories (GET /v1/memories, wire/edge) |
user_id= argument | the (namespace, agent) scope carried by the key |
Both add and encode run LLM-backed extraction — you send text, the system
infers the structured memories. The biggest shift is on the read: mem0's
search returns a ranked list of hits; Brain's recall returns a
membership verdict (Single / Many / None) with the supporting
memories, and a None is an explicit "don't know" rather than a low-score hit.
Data shape
mem0 stores extracted facts with a vector embedding and metadata; retrieval is vector search (plus an optional graph store).
Brain stores typed memories — fact, preference, event, entity,
relation — each with a confidence score and provenance, and (for entities and
relations) a place in a typed graph. Retrieval fuses three retrievers
(semantic, lexical, and entity-graph) with reciprocal-rank fusion and reranks
the result. There is no separate graph store to stand up — the graph is part of
the same single binary.
Identity model
mem0 scopes memory by user_id (and optional agent_id / run_id) passed on
every call.
Brain's scope is a (namespace, agent) pair carried by the API key — the
server derives it; clients never send a scope. Translation:
- mem0
user_id→ a(namespace, agent)scope. Give each tenant its own key, or hold one pooled key and stamp each request withact_as(namespace, agentId)(see Identity from a JWT). - Per-org isolation → one
namespaceper org (the isolation boundary). - Per-app isolation → one
agentper app within the namespace.
There is no client-side scope= argument on any Brain SDK — the constraint is
enforced, not advisory.
Write
from mem0 import Memory
m = Memory()
m.add("I prefer dark mode", user_id="alice")from brain_db_sdk import BrainHttpClient
# The key already carries alice's (namespace, agent) scope.
brain = BrainHttpClient(os.environ["BRAIN_API_KEY"])
brain.encode("I prefer dark mode")Brain deduplicates byte-identical text per (agent, context) automatically —
re-encoding the same fact returns the existing memory with
was_deduplicated: true rather than writing a second copy. You don't manage
client-side idempotency keys for the common case.
Read
results = m.search("user preferences", user_id="alice")
for r in results["results"]:
print(r["memory"], r["score"])answer = brain.recall("user preferences", max_results=10)
if answer.answer_kind == "none":
print("don't know") # explicit absence, not an empty ranked list
else:
for m in answer.memories:
print(m.text, m.similarity_score)The deeper difference: because Brain fuses a lexical and an entity-graph retriever alongside the vector one, a cue that returns nothing in a pure-vector system will often still resolve in Brain — the extractor pulled the names out, and the graph retriever finds them even when embedding similarity is weak.
Where Brain is stricter
- Typed schema — every memory has a type; the classifier assigns it. The
seeded
brain:schema is always active, and you can declare your own types. - Scope is the key — no client-side scope construction, ever.
- Recall is membership, not ranking — you get
Single/Many/None, not a page you threshold yourself.
Where Brain is more flexible
- Provenance and bi-temporality — every memory records what produced it and when it was valid; corrections supersede rather than overwrite; history is queryable.
- Typed graph, built in — entities, statements, and relations are extracted and traversable (Entities, Graph) without a second datastore.
- Single binary — one process (arena + WAL + redb + HNSW + tantivy + bundled embeddings). No external vector DB or graph DB to run.
Migrating data
Feed your mem0 history back through encode and let Brain re-extract:
- Export each mem0 memory (text + timestamp) with
m.get_all(user_id=...). - For each,
brain.encode(text, occurred_at=<unix_nanos>)under that user's scope. - Re-running is safe — content dedup collapses byte-identical re-encodes.
Don't try to rebuild the entity graph by hand — the write pipeline extracts it as you encode.
Was this page helpful?