Cloud Sync (cmem.ai Pro)
Cloud sync replicates your local memory database across your devices through a per-user sync hub — an ordered log of everything your devices write. It is built into the worker: there is no daemon — the worker syncs on write. The same process that records every observation also uploads it, and pulls the other devices’ writes back down.Two lanes, one source of truth. Everything durable travels over plain
HTTP: pushes append to the hub’s ordered log, pulls page through it with a
cursor. An optional Realtime channel rides alongside as a pure speed layer —
it only makes the next pull happen sooner. The channel can be dropped, disabled,
or wrong with zero data loss; the HTTP cursor is always the truth.
What syncs
Three kinds of rows are replicated between your devices:- Observations — the compressed memories claude-mem generates from your sessions.
- Session summaries — the per-session overview records.
- User prompts — the prompts you typed. A prompt over 200 KB uploads as its first 200 KB plus a truncation note; the full prompt stays on the device that captured it.
How the push lane works
The database is the queue. Every synced table carries asynced_at column
(NULL = not in the log yet). After each write, the worker nudges a debounced
flusher that drains WHERE synced_at IS NULL and stamps rows on success. That
one mechanism handles live sync, offline catch-up, and retry. Rows written
after the SyncHub launch boundary start as NULL; anything that fails to
upload stays NULL until a later flush picks it up. Pre-launch local rows are
not treated as a cloud migration corpus.
- Debounced: write bursts coalesce; the flusher runs ~1.5 s after the last write (250 ms while the speed layer is connected), and only one flush runs at a time.
- Batched: ops drain in requests of up to 500 ops / 4,000,000 encoded bytes. Each individual canonical body is capped at 256,000 encoded bytes.
- Timeboxed: every request has a 30 s timeout — a dead network can never hang the worker.
- Retrying: a failed upload leaves rows
NULLand retries on the next write plus a capped exponential backoff (30 s → 10 min). The write path is never blocked — sync failures cost nothing but delay. - Idempotent: the hub dedupes on origin identity, so a retried push can never create duplicates.
How the pull lane works
Each device keeps a cursor into the hub’s log and pulls everything after it — in pages, so a device that was offline for a week (or is brand new and starts from zero) catches up the same way an active one stays current:- Session start: the worker pulls immediately before injecting context (bounded to 1.5 s, so a dead network can’t stall your session), which means memory written on another machine is there the moment you start working.
- Steady state: a short poll every 30 s while a session is active, every 5 min when idle, suspended entirely after an hour of no activity. Every push response also reports the log head, so an active device learns about remote writes without waiting out the timer.
- Applied like native writes: pulled rows keep their original timestamps, carry their origin device, index into full-text and vector search through the same paths native writes use, and are stamped so they can never be echoed back up.
Live updates (optional)
When enabled (the default), the worker also subscribes to a private Realtime channel for your account. When another device pushes, the server broadcasts a tiny “the log moved forward” signal and this device pulls right away — multi-device convergence in about a second instead of a poll interval. The channel is strictly advisory: it never carries your data, and any anomaly (a dropped connection, a missed heartbeat, a malformed signal) simply closes it and falls back to an HTTP pull, which is always correct. SetCLAUDE_MEM_CLOUD_SYNC_WS to false to turn live updates off — sync stays
fully correct at poll latency. If the sync server does not offer live updates,
the worker stays on polling and checks again hourly.
Requirements and setup
Cloud sync is active when all three of the token, the user id, and the hub URL are non-empty — there is no separate enable flag; blank any one of them to turn sync off.- A cmem.ai Pro sync token and user id (from cmem.ai → Connect).
- A sync hub URL in
CLAUDE_MEM_CLOUD_SYNC_HUB_URL:https://sync.cmem.ai, which the installer writes for you (source inservices/sync-api/). A hub URL pinned to the older Cloudflare Workers hub is moved to it automatically.
npx claude-mem install in your terminal and choose CMEM
Pro. The installer signs you in through the browser, writes all three values
into ~/.claude-mem/settings.json (mode 0600), and restarts the worker. The
/cloud-sync skill in Claude Code checks status, points you to the installer
for setup, and verifies the installed client can reach SyncHub. It never asks
you to paste the token into the chat, because anything pasted there lands in the
session transcript. For CI or a remote box, see
headless setup.
Settings
A Hub accepts at most 64 distinct device ids per account. Existing devices
continue to sync normally at the limit; a new device receives
409 device_limit_exceeded. Status/metadata reads and renaming an unknown
device do not create phantom devices.
Status endpoint
The worker exposesGET /api/sync/status (on the same port as the rest of the
worker HTTP API). It is registered unconditionally, so an unconfigured install
answers 200 rather than a 404 — callers can tell “not set up” apart from
“worker down”.
GET /v1/sync/status directly to SyncHub, including when all pending counts
are zero. The probe does not append an operation or advance a pull cursor.
hub.reachable: false and hub.error therefore expose a bad token, wrong Hub
URL, malformed response, timeout, or network failure
that an empty queue would otherwise hide.
pending— rows (and queued mutation ops) still waiting to upload. Counts near 0 mean the hub’s log has everything this device wrote.lastFlushAt— epoch ms of the last successful flush,nullbefore the first.lastError— message from the most recent failed flush,nullwhen healthy. It never contains the token.hub— the most recent authenticated SyncHub probe. Treathub.reachable: trueas the connectivity check;lastError: nullalone is not enough. Sequence and epoch fields remain decimal strings.

