Getting Started
Migrating from Zep
Zep organises memory around sessions holding messages. MemorySync separates who a memory belongs to from which conversation produced it, which is the one idea worth understanding before you start.
The one difference that matters
Key differences
| Zep | MemorySync | What it means for your code |
|---|---|---|
session.create() before writing | No setup call | Scope is a header on the write itself. |
memory.add(session_id, ...) | POST /memory/add | Send the statement worth keeping, not every turn. |
memory.search(session_id, {text, limit}) | POST /memory/query with query and k | Reads span the end user, and session_id narrows when you want that. |
memory.get(session_id) | GET /memory/export | Returns records readable in the current scope. |
| Messages with roles | Records with source and metadata | Role becomes descriptive context you can filter on. |
Install the client
python -m pip install memorysync==1.9.2
import osfrom memorysync import MemorySyncClientclient = 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
session = client.session.create(session_id="thread_42",user_id="user_123",)client.memory.add("thread_42", {"content": "I love Python","role": "user",})
results = client.memory.search("thread_42", {"text": "programming","limit": 3,})
Move your episodes with one command
The CLI reads a Zep export, or walks your account through the API — users first, then each user’s episodes — and writes every episode into that user’s scope, carrying the thread id through as session_id. Start with --dry-run: it reports the per-user breakdown, the user and assistant split, and everything it would skip, without writing anything.
# See what would happen. Writes nothing.npx memorysync-cli migrate zep --file zep_export.json --dry-run# Do it.npx memorysync-cli migrate zep --file zep_export.json
| What it does | Why |
|---|---|
| Migrates episodes, not the derived graph | Zep keeps episodes verbatim as the original source, alongside the nodes and edges it derives from them. The graph is Zep’s own representation and has no equivalent here, so porting it would mean holding a model nothing reads. |
Writes each episode under its own user, and carries thread_id into session_id | This is the split described above, applied. The user is the isolation boundary; the thread only narrows a query. |
| Keeps both sides of the conversation and reports the split | An assistant episode is what the model said, stored as the user’s memory — right for some products, wrong for others. Dropping it by default would be silent data loss, so you get the counts and --role user if you want only the human turns. |
Skips json and fact_triple episodes, and reports how many | A fact triple is a serialised graph edge and JSON is a business record. Stored as memory text they are strings retrieval can match but never usefully explain. --include-source json,fact_triple migrates them anyway. |
| Skips deleted users | Their episodes are data the customer asked you to remove. Migrating it would resurrect it somewhere new. |
Keeps role, role_type, source and the original timestamp in metadata | Who said it is context about a record, not part of its identity, so it stays filterable without becoming the scope. |
Sends every episode uuid as client_ref | Re-running is safe. Episodes that already landed are reported as already migrated rather than stored twice. |
Or move them yourself
The command above is these steps, done for you. Do it by hand when you are building it into your own pipeline rather than running a one-time migration.
- 1List the sessions you want to bring across, and read each session’s user id. That id becomes the end-user scope.
- 2Filter the messages down to statements worth remembering. A full transcript imported turn by turn retrieves poorly, because most turns are not durable facts.
- 3Import per session, carrying the role in
metadataand the session id insession_id. - 4Check the per-item results, then query as one migrated user to confirm the scope is right.
import osfrom memorysync import MemorySyncClient# messages and user_id come from your Zep exportdef import_session(session_id: str, user_id: str, messages: list[dict]) -> None: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,)items = [{"text": message["content"],"source": "import",# the uuid makes re-running safe; without it a retry stores a second copy"client_ref": message.get("uuid"),"metadata": {"role": message.get("role"), "zep_uuid": message.get("uuid")},}for message in messagesif message.get("content", "").strip()]result = client.bulk_add(items)print(session_id, result.created, "created,", result.skipped, "skipped")
Next steps
Was this page helpful?