Migrating from Supermemory
Move from container tags and document ingestion to explicit end-user scopes and durable memory records. The mapping is mostly about splitting one string into a scope and a set of tags.
Key differences
| Supermemory | MemorySync | What it means for your code |
|---|---|---|
containerTag string carrying scope and labels | X-End-User-ID header plus tags | Isolation stops being a naming convention you have to keep consistent. |
| Documents, chunked on ingest | Memory records, stored as written | A retrieval result is the record you wrote, not a fragment of one. |
queued then done | Durable when add returns | No polling loop, and no window where a write is not yet retrievable. |
search.execute({ q, containerTag }) | POST /memory/query | Scope comes from the header; filters stays yours. |
| Chunks with scores | Whole records with metadata | Nothing to stitch back together before you show or use it. |
| URLs, PDFs, and files ingested directly | You extract, then store what matters | A deliberate tradeoff — see the note below. |
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
client.memories.add(content="User prefers dark mode",container_tags=["user_alice", "preferences"],)
results = client.search.execute(q="user preferences",container_tag="user_alice",)
Move your documents with one command
The CLI reads a Supermemory export, or pulls your documents straight from the API, and writes each one into the end-user scope its container tag identifies. Start with --dry-run: it reports the per-user breakdown, everything it would skip, and every document it refuses, without writing anything.
# See what would happen. Writes nothing.npx memorysync-cli migrate supermemory --file supermemory_export.json --dry-run# Do it.npx memorysync-cli migrate supermemory --file supermemory_export.json
| What it does | Why |
|---|---|
Treats the container tag starting with user_ as the end user, and every other tag as a label | A container tag is your isolation boundary *and* your labelling mechanism. Only one of those maps onto a scope. Pass --scope-tag-prefix if your convention differs. |
| Refuses a document matching two scope tags, naming both | A document tagged user_alice and user_bob has no single owner. Picking the first would file it under whichever happened to be listed first, and it would look like it worked. |
| Refuses a document matching no scope tag | Writing it into a fallback scope would put it where nobody looks, with no error to notice. Pass --source-user <id> to place the whole file under one scope. |
Never stores a document’s summary in place of its content | The list API omits content unless asked, and every document carries a summary. Substituting one would store a paraphrase you could not tell apart from your real data. |
| Skips documents that have not finished processing, and reports how many | A document Supermemory is still working through has no reliable text yet. A truncated memory looks complete. |
| Reports documents too large to be one memory instead of splitting them | A chunked PDF is one enormous record here, which retrieves badly. Where to divide it is a decision about its content, so it is yours. |
Sends every Supermemory id as client_ref | Re-running is safe. Documents 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.
This is the only decision that matters. A tag like user_alice is a scope and becomes X-End-User-ID. A tag like preferences is a label and becomes an entry in tags. A tag that mixed both, such as user_alice_preferences, has to be split.
Pull your documents out with their container tags and metadata, and keep the export on disk. It is your rollback.
import json, osfrom memorysync import MemorySyncClientwith open("supermemory_export.json") as handle:documents = json.load(handle)# map the tag that identified a user onto the scope headerdef split_tags(tags: list[str]) -> tuple[str, list[str]]:scope = next((t for t in tags if t.startswith("user_")), "unknown")return scope.removeprefix("user_"), [t for t in tags if t != scope]batches: dict[str, list[dict]] = {}for document in documents:end_user_id, labels = split_tags(document.get("containerTags", []))if not document.get("content"):continuebatches.setdefault(end_user_id, []).append({"text": document["content"],"source": "import","tags": labels,# the id makes re-running safe; without it a retry stores a second copy"client_ref": document.get("id"),"metadata": {"supermemory_id": document.get("id")},})for end_user_id, items in batches.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=end_user_id,)result = client.bulk_add(items)print(end_user_id, result.created, "created,", result.skipped, "skipped")
Query as one migrated end user and confirm only their memories return. If a container tag was ambiguous, this is where you find out, before it matters.