MemorySync
Getting Started

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

SupermemoryMemorySyncWhat it means for your code
containerTag string carrying scope and labelsX-End-User-ID header plus tagsIsolation stops being a naming convention you have to keep consistent.
Documents, chunked on ingestMemory records, stored as writtenA retrieval result is the record you wrote, not a fragment of one.
queued then doneDurable when add returnsNo polling loop, and no window where a write is not yet retrievable.
search.execute({ q, containerTag })POST /memory/queryScope comes from the header; filters stays yours.
Chunks with scoresWhole records with metadataNothing to stitch back together before you show or use it.
URLs, PDFs, and files ingested directlyYou extract, then store what mattersA deliberate tradeoff — see the note below.

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.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 doesWhy
Treats the container tag starting with user_ as the end user, and every other tag as a labelA 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 bothA 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 tagWriting 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 contentThe 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 manyA 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 themA 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_refRe-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.

1
Decide what each container tag meant

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.

2
Export your documents

Pull your documents out with their container tags and metadata, and keep the export on disk. It is your rollback.

3
Import one scope at a time
import json, os
from memorysync import MemorySyncClient
with open("supermemory_export.json") as handle:
documents = json.load(handle)
# map the tag that identified a user onto the scope header
def 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"):
continue
batches.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")
4
Verify the boundary

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.

Next steps

Was this page helpful?