You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(sync) + chore: final verification, docs, and stale-bundle fix (Phase 6)
Full-matrix e2e harness (scripts/sync-matrix-e2e.ts, 39/39): fresh-device
bootstrap, >500-op offline catch-up, concurrent writes, all four mutation
types in both arrival orders, epoch reset with native-corpus re-push, and
kill-switch degradation — plus a two-real-daemon run proving the
session-start context-inject pull end-to-end.
docs/public/cloud-sync.mdx rewritten for the two-lane architecture
(durable HTTP lanes, advisory WebSocket, poll-mode fallback, mutation
convergence, settings reference) with availability framed honestly —
no live hub URLs. Canary header comment scoped to what it measures.
plugin/scripts/worker-service.cjs regenerated — the Phase 5 commit had
shipped it stale (missing pollModeOnly/onSyncModeHint/X-Sync-Mode).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
description: "Worker-native sync of your memory database to your cmem.ai account — no daemon, the worker syncs on write."
3
+
description: "Two-lane multi-device sync of your memory database — durable HTTP push/pull through a per-user sync hub, with an optional WebSocket speed layer."
4
4
---
5
5
6
6
# Cloud Sync (cmem.ai Pro)
7
7
8
-
Cloud sync backs up your local memory database to your [cmem.ai](https://cmem.ai)
9
-
account. It is built into the worker: **there is no daemon — the worker syncs on
10
-
write**. The same process that records every observation also uploads it, so
11
-
there is no second process to install, monitor, or restart.
8
+
Cloud sync replicates your local memory database across your devices through a
9
+
**per-user sync hub** — an ordered log of everything your devices write. It is
10
+
built into the worker: **there is no daemon — the worker syncs on write**. The
11
+
same process that records every observation also uploads it, and pulls the
12
+
other devices' writes back down.
12
13
13
14
<Note>
14
-
**The database is the queue.** Every synced table carries a `synced_at` column
15
-
(`NULL` = not in the cloud yet). After each write, the worker nudges a debounced
16
-
flusher that drains `WHERE synced_at IS NULL` in batches to cmem.ai and stamps
17
-
rows on success. That one mechanism **is** live sync, backfill, offline
18
-
catch-up, and retry — a never-synced install simply has everything `NULL`, and
19
-
anything that fails to upload stays `NULL` until a later flush picks it up.
15
+
**Two lanes, one source of truth.** Everything durable travels over plain
16
+
HTTP: pushes append to the hub's ordered log, pulls page through it with a
17
+
cursor. An optional WebSocket rides alongside as a pure *speed layer* — it
18
+
only makes the next pull happen sooner. The socket can be dropped, disabled,
19
+
or wrong with **zero data loss**; the HTTP cursor is always the truth.
20
20
</Note>
21
21
22
22
## What syncs
23
23
24
-
Three kinds of rows are uploaded to your cmem.ai account:
24
+
Three kinds of rows are replicated between your devices:
25
25
26
26
-**Observations** — the compressed memories claude-mem generates from your sessions.
27
27
-**Session summaries** — the per-session overview records.
28
-
-**User prompts** — the prompts you typed (clamped to 200 KB each).
28
+
-**User prompts** — the prompts you typed (clamped to 200 KB per field).
29
+
30
+
Alongside the rows, three kinds of **mutations** travel through the same log
31
+
so later edits converge everywhere: session **title** changes, **prompt→session
32
+
repairs** (a prompt captured before its session registered is re-linked on
33
+
every device), and **project remaps** (worktree adoption and cwd-based moves
34
+
retarget the affected rows on every device).
29
35
30
36
<Warning>
31
-
**Privacy:** cloud sync uploads your observation narratives and your full prompt
32
-
text to your cmem.ai account. Don't enable it if that content must stay on your
33
-
machine.
37
+
**Privacy:** cloud sync uploads your observation narratives and your full
38
+
prompt text to the sync hub under your cmem.ai account. Don't enable it if
39
+
that content must stay on your machine.
34
40
</Warning>
35
41
36
-
## Quick start
37
-
38
-
Run the `/cloud-sync` skill in Claude Code. It checks sync status and, on first
39
-
run, walks you through setup:
42
+
## How the push lane works
40
43
41
-
1. Grab your **sync token** and **user id** from **cmem.ai → Connect** and paste
42
-
them when asked. The skill writes them into `~/.claude-mem/settings.json`
43
-
(mode `0600`) without ever echoing the token.
44
-
2. The skill restarts the worker, which immediately starts draining unsynced
45
-
rows, and polls status until the pending counts fall.
46
-
47
-
If you previously used the **legacy standalone sync client**, the skill migrates
48
-
it automatically: your token is read from the old `.cloud-sync.env`, your device
49
-
identity carries over, and **nothing re-uploads** — rows the old client already
50
-
pushed are stamped as synced during migration. See
51
-
[Migrating from the standalone client](#migrating-from-the-standalone-client).
52
-
53
-
## How the flusher behaves
44
+
**The database is the queue.** Every synced table carries a `synced_at` column
45
+
(`NULL` = not in the log yet). After each write, the worker nudges a debounced
46
+
flusher that drains `WHERE synced_at IS NULL` and stamps rows on success. That
47
+
one mechanism **is** live sync, backfill, offline catch-up, and retry — a
48
+
never-synced install simply has everything `NULL`, and anything that fails to
49
+
upload stays `NULL` until a later flush picks it up.
54
50
55
51
-**Debounced:** write bursts coalesce; the flusher runs ~1.5 s after the
56
-
last write, and only one flush runs at a time.
57
-
-**Batched:** rows drain in batches of up to 200 per kind, packed into request
58
-
bodies of at most 2 MB.
52
+
last write (250 ms while the speed layer is connected), and only one
53
+
flush runs at a time.
54
+
-**Batched:** ops drain in requests of up to 500 ops / 2 MB.
59
55
-**Timeboxed:** every request has a 30 s timeout — a dead network can
60
56
never hang the worker.
61
-
-**Retrying:** a failed upload leaves rows `NULL` and retries on the next write
62
-
plus a capped exponential backoff (30 s → 10 min). The write path is
63
-
never blocked — sync failures cost nothing but delay.
64
-
-**Startup drain:** the worker kicks one flush at startup, which doubles as the
65
-
initial backfill of your existing database.
57
+
-**Retrying:** a failed upload leaves rows `NULL` and retries on the next
58
+
write plus a capped exponential backoff (30 s → 10 min). The write
59
+
path is never blocked — sync failures cost nothing but delay.
60
+
-**Idempotent:** the hub dedupes on origin identity, so a retried push can
61
+
never create duplicates.
62
+
63
+
## How the pull lane works
64
+
65
+
Each device keeps a **cursor** into the hub's log and pulls everything after
66
+
it — in pages, so a device that was offline for a week (or is brand new and
67
+
starts from zero) catches up the same way an active one stays current:
68
+
69
+
-**Session start:** the worker pulls immediately before injecting context
70
+
(bounded to 1.5 s, so a dead network can't stall your session), which
71
+
means memory written on another machine is there the moment you start
72
+
working.
73
+
-**Steady state:** a short poll every 30 s while a session is active,
74
+
every 5 min when idle, suspended entirely after an hour of no activity.
75
+
Every push response also reports the log head, so an active device learns
76
+
about remote writes without waiting out the timer.
77
+
-**Applied like native writes:** pulled rows keep their original timestamps,
78
+
carry their origin device, index into full-text and vector search through
79
+
the same paths native writes use, and are stamped so they can never be
80
+
echoed back up.
81
+
82
+
## The speed layer (optional WebSocket)
83
+
84
+
When enabled (the default), the worker also holds one WebSocket to the hub and
85
+
receives new ops the moment another device pushes them — multi-device
86
+
convergence in well under a second instead of a poll interval. The socket is
87
+
strictly **advisory**: any anomaly (a gap, a parse error, a dropped
88
+
connection) simply closes it and runs one HTTP pull, which is always correct.
89
+
Set `CLAUDE_MEM_CLOUD_SYNC_WS` to `false` to turn it off — sync stays fully
90
+
correct at poll latency.
91
+
92
+
The hub can also ask every client to fall back to polling by stamping
93
+
responses with an `X-Sync-Mode: poll` header (an operational brake on the
94
+
server side). Clients close their sockets and keep syncing over HTTP —
95
+
degraded means *slower*, never *stopped* — and resume the socket automatically
96
+
when the header disappears.
97
+
98
+
## Requirements and setup
99
+
100
+
Cloud sync is active when **all three** of the token, the user id, and the hub
101
+
URL are non-empty — there is no separate enable flag; blank any one of them to
102
+
turn sync off.
103
+
104
+
1. A **cmem.ai Pro sync token and user id** (from **cmem.ai → Connect**).
105
+
2. A **sync hub URL** in `CLAUDE_MEM_CLOUD_SYNC_HUB_URL`. The hub is a
106
+
Cloudflare Workers service (source in `workers/sync-hub/` with its own
107
+
deploy runbook); use the hub URL your cmem.ai account documentation
108
+
provides for it.
109
+
110
+
Run the `/cloud-sync` skill in Claude Code to check status and walk through
111
+
setup. It writes the settings into `~/.claude-mem/settings.json` (mode
112
+
`0600`) without ever echoing the token, restarts the worker, and polls status
113
+
until the pending counts fall. If you previously used the legacy standalone
114
+
sync client, the skill retires it and carries your device identity over — see
115
+
[Migrating from the standalone client](#migrating-from-the-standalone-client).
66
116
67
117
## Settings
68
118
69
-
Cloud sync is configured in `~/.claude-mem/settings.json`. It is **active when
70
-
both the token and the user id are non-empty** — there is no separate enable
71
-
flag; blank the token to turn sync off.
72
-
73
119
| Key | Default | Meaning |
74
120
|-----|---------|---------|
75
121
|`CLAUDE_MEM_CLOUD_SYNC_TOKEN`|`''`| Your cmem.ai sync token (from **cmem.ai → Connect**). Sent as `Authorization: Bearer`. |
76
-
|`CLAUDE_MEM_CLOUD_SYNC_USER_ID`|`''`| Your cmem.ai user id (from **cmem.ai → Connect**). |
77
-
|`CLAUDE_MEM_CLOUD_SYNC_URL`|`https://cmem.ai/api/pro/sync`| Sync endpoint base URL. Only change this if you're pointing at a non-production server. |
78
-
|`CLAUDE_MEM_CLOUD_SYNC_DEVICE_ID`|`''`| Stable identity of this machine in the cloud. Resolved at first start — adopted from the legacy client's state file if present, otherwise a fresh UUID — then persisted back here. Don't edit it: the server keys rows on it, and a changed id forks every cloud row into a duplicate. |
79
-
|`CLAUDE_MEM_CLOUD_SYNC_DEVICE_NAME`| machine hostname | Human-readable label for this device in the cmem.ai Devices panel. |
122
+
|`CLAUDE_MEM_CLOUD_SYNC_USER_ID`|`''`| Your cmem.ai user id (from **cmem.ai → Connect**). All your devices share it — it names your hub log. |
123
+
|`CLAUDE_MEM_CLOUD_SYNC_HUB_URL`|`''`| Sync hub base URL. **Empty = sync off.**|
|`CLAUDE_MEM_CLOUD_SYNC_DEVICE_ID`|`''`| Stable identity of this machine. Resolved at first start — adopted from the legacy client's state file if present, otherwise a fresh UUID — then persisted back here. Don't edit it: every row is attributed to its origin device, and a changed id forks every previously synced row into a duplicate. |
126
+
|`CLAUDE_MEM_CLOUD_SYNC_DEVICE_NAME`| machine hostname | Human-readable label for this device. |
127
+
|`CLAUDE_MEM_CLOUD_SYNC_URL`| (legacy) | The pre-hub per-kind endpoint base. Unused since the hub cutover; kept only so existing settings files round-trip. |
0 commit comments