MemorySync
Getting Started

Migrating from Mem0

Move your memories and your calling code from Mem0 to MemorySync. The import runs through one endpoint, and you can keep both providers live until your own queries look right.

Key differences

Mem0MemorySyncWhat it means for your code
user_id passed per callX-End-User-ID request headerIsolation is enforced by the API, so a read cannot be widened by a forgotten filter argument.
filters={"user_id": ...} on searchScope header, not a filterfilters is left free for your own tags and metadata.
messages=[...] extracted into factstext — one statement per recordYou decide what is worth storing, so what comes back is what you put in.
top_kkSame meaning, shorter name.
Org and project ids on the clientX-Project-ID request headerOne key can serve several projects without new client instances.
No update on the managed APIPATCH /memory/{memory_id}Tags, importance, metadata, and source can be corrected in place.

Install the client

python -m pip install memorysync==1.9.2
import os
from memorysync import MemorySyncClient
client = MemorySyncClient(
api_key=os.environ["MEMORYSYNC_API_KEY"],
base_url="https://api.memorysync.io",
project_id=os.environ["MEMORYSYNC_PROJECT_ID"],
end_user_id="user-123",
)

Operation by operation

client.add(
messages="User prefers dark mode",
user_id="alice",
)
results = client.search(
query="user preferences",
filters={"user_id": "alice"},
top_k=5,
)
memories = client.get_all(filters={"user_id": "alice"})
client.delete(memory_id="mem_123")

Where Mem0 returns an extracted fact it decided to keep, MemorySync returns the record you wrote. If you were reconciling extraction output against your own database, that reconciliation step disappears.

Move your memories with one command

The CLI reads a Mem0 export, or pulls the account straight from Mem0, and writes every record into the end-user scope it belongs to. Start with --dry-run: it reports exactly what would be written, per user, and writes nothing.

# See what would happen. Writes nothing.
npx memorysync-cli migrate mem0 --file mem0_export.json --dry-run
# Do it.
npx memorysync-cli migrate mem0 --file mem0_export.json
What it doesWhy
Groups records by their Mem0 user_id and writes each group under that end-user scopeOne export usually holds many users. Each has to land in its own scope or it is unreachable by the person it belongs to.
Sends every Mem0 id as client_refRe-running is safe. Records that already landed are reported as already migrated instead of being stored a second time, so a run that stopped halfway is finished by running it again.
Refuses any record whose user cannot be determinedWriting it into a fallback scope would file it where nobody looks, and there would be no error to notice. Pass --source-user <id> to state the scope, or re-export including user_id.
Maps categories to tags, and keeps id, created_at, agent_id, app_id and run_id in metadataNothing from the source is discarded, and the two systems stay reconcilable on metadata.mem0_id.
Batches at 50 records per requestThe ceiling bulk-add enforces. The command handles the chunking.

Or move them yourself

The command above is the same four steps, done for you. Do it by hand when you are building it into your own pipeline rather than running a one-time migration.

  1. 1Export from Mem0. The dashboard export is the least code; the export API works too, and returns records with id, content, metadata, and created_at.
  2. 2Group the exported records by their Mem0 user_id. Each group becomes one MemorySync end-user scope.
  3. 3Import each group with bulk add, keeping the original id in metadata so the two systems stay reconcilable.
  4. 4Read the per-item results. A 207 with skipped items is a normal outcome, not a failure.
import json, os
from memorysync import MemorySyncClient
with open("mem0_export.json") as handle:
export = json.load(handle)
# one MemorySync client per end user keeps the scope explicit
by_user: dict[str, list[dict]] = {}
for record in export["memories"]:
if record.get("content"):
by_user.setdefault(record.get("user_id", "unknown"), []).append(record)
for user_id, records in by_user.items():
client = MemorySyncClient(
api_key=os.environ["MEMORYSYNC_API_KEY"],
base_url="https://api.memorysync.io",
project_id=os.environ["MEMORYSYNC_PROJECT_ID"],
end_user_id=user_id,
)
result = client.bulk_add(
[
{
"text": record["content"],
"source": "import",
"client_ref": record.get("id"),
"metadata": {"mem0_id": record.get("id"), **(record.get("metadata") or {})},
}
for record in records
],
)
print(user_id, result.created, "created,", result.skipped, "skipped")

Worth knowing before you cut over

  • Writes are durable when add returns. There is no event id to poll and no queued state to wait through before a memory is retrievable.
  • Nothing is silently rewritten. A record changes only when you call update or forget, so your memory count reflects your own decisions.
  • A memory belongs to one end user. Cross-user reads are not a filter you have to remember to apply.
  • Retrieval returns whole records with their metadata, not fragments to reassemble.
  • Keep your Mem0 export until you have run a full usage cycle here. It is the cheapest rollback available.

Next steps

Was this page helpful?