Brainby arc-labs/docs
Migration

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

mem0Brain
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= argumentthe (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 with act_as(namespace, agentId) (see Identity from a JWT).
  • Per-org isolation → one namespace per org (the isolation boundary).
  • Per-app isolation → one agent per 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:

  1. Export each mem0 memory (text + timestamp) with m.get_all(user_id=...).
  2. For each, brain.encode(text, occurred_at=<unix_nanos>) under that user's scope.
  3. 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?

On this page