Skip to main content

Cloud Sync (cmem.ai Pro)

Cloud sync replicates eligible memory records across your devices through a per-user sync hub — an ordered log of synchronized writes. 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

The core memory rows replicated between your devices are:
  • 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.
  • Images (on by default) — while Keep images (CLAUDE_MEM_MEDIA_CAPTURE_ENABLED) is on, the converted WebP copies of captured screenshots are uploaded privately to your cmem.ai account, separately from the text log. Images appear in My Work locally and at work.cmem.ai. Other devices and work.cmem.ai download an image the first time it is viewed and show it as downloading until then. Original image files and their paths are never uploaded. Turn Keep images off in the memory viewer’s Settings to stop keeping and uploading new images. See Screenshots and Images.
When the sync hub supports it, agent work state also syncs: the to-do lists and working state agents save, including each entry’s fields. Alongside the rows, three kinds of mutations travel through the same log so later edits converge everywhere: session title changes, prompt→session repairs (a prompt captured before its session registered is re-linked on every device), and project remaps (worktree adoption and cwd-based moves retarget the affected rows on every device).
Privacy: cloud sync uploads your observation narratives and your full prompt text to the sync hub under your cmem.ai account, and, if media capture is on, your converted screenshots. Don’t enable it if that content must stay on your machine.

How the push lane works

The database is the queue. Every synced table carries a synced_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: content pages contain 40 ops by default, adjustable up to 500 ops / 4,000,000 encoded bytes per request. Each individual canonical body is capped at 256,000 encoded bytes.
  • Timeboxed: content pushes have a 90 s timeout by default. Pulls and status probes also have deadlines, so a dead network cannot hang the worker.
  • Retrying: a failed upload leaves rows NULL and 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 makes recent remote memory available before context is assembled when the bounded pull can finish. A larger backlog continues catching up afterwards.
  • Steady state: a short poll every 30 s while a session or visible local viewer 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.
  • Viewer activity: a visible local viewer renews an expiring activity lease every 20 s. Hidden and closed tabs release it; a failed release expires after 45 s. This keeps downloads awake without bypassing network backoff or an authentication pause. A hosted viewer with CLAUDE_MEM_CLOUD_SYNC_KEEP_PULLING=true keeps polling every 30 s, even with no browser tabs or agent sessions, and does not enter idle suspension.
  • 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) and supported by the sync server, the worker 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, ahead of the regular 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. Set CLAUDE_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. The status display names the actual mode; CLAUDE_MEM_CLOUD_SYNC_WS=true alone does not establish a live channel. The client requests /v1/sync/realtime-token; the included sync-api service does not provide that grant endpoint, so it uses HTTP polling unless the deployment supplies a compatible grant service. The older /v1/sync/ws endpoint is not interchangeable with this protocol.

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.
  1. A cmem.ai Pro sync token and user id (from cmem.ai → Connect).
  2. A sync hub URL in CLAUDE_MEM_CLOUD_SYNC_HUB_URL: https://sync.cmem.ai, which the installer writes for you (source in services/sync-api/). A hub URL pinned to the older Cloudflare Workers hub is moved to it automatically.
To connect, run 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 exposes GET /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”.
Configured:
Configured status requests perform a bounded, observational GET /v1/sync/status probe, including when the outgoing queue is empty. Concurrent requests share the probe. The viewer receives cached status through its event stream; its one-second display clock does not make a hub request. Reading status never uploads records, advances the pull cursor, or resumes an authentication-paused upload. The worker’s normal retry/lifecycle path owns recovery. Alongside the existing fields, sync reports the download and projection evidence used by the viewer:
The feed connection and synchronization status are separate. A connected feed proves that this browser is receiving updates from its viewer worker. It does not prove that the worker has downloaded every remote change. current requires a completed download and recent matching hub evidence, matching epochs, no known lag or pending uploads, and no unresolved scope/conflict gap. Old evidence becomes stale even if browser heartbeats continue. Unknown or mismatched epochs report unknown lag rather than zero. Sequence values stay decimal strings.
  • pending — eligible rows and queued operations still waiting to upload. Zero does not prove that incoming changes have arrived or that excluded historical rows were ever uploaded.
  • lastFlushAt — epoch ms of the last successful flush, null before the first.
  • lastError — message from the most recent failed flush, null when healthy. It never contains the token.
  • hub — the most recent authenticated SyncHub probe. Treat hub.reachable: true as the connectivity check; lastError: null alone is not enough. Sequence and epoch fields remain decimal strings.
  • sync.download — last download attempt/success, cursor, retry/pause state, and actual polling or realtime mode. An upload can be healthy while this direction is unavailable.
  • sync.completeness — quarantined operations and historical scope. An old conflict is distinct from a current network failure. Pre-launch native rows remain local-only unless explicitly selected for a supported migration; their exclusion is not repaired by resetting the cursor or reloading a page.
Cloud sync does not mean a byte-for-byte copy of every local file. Compatible readers can share My Work grouping, progress, story, reviews, decisions and published output links when CLAUDE_MEM_WORK_VIEW_SHARED_STATE=true and the hub supports work_state. The setting defaults to false for a consumer-first rollout: update participating local and hosted readers before enabling writers. Local paths, review sidecars and arbitrary deliverable files stay on their origin device; the shared state carries bounded semantic records and published links. A scope or incomplete-generation warning is distinct from a stopped download lane, and neither an empty upload queue nor a connected feed proves full parity. Not configured: