Understanding any
Local-first
In any, "offline" is not a degraded mode — it is the normal one. Every read and write is served from the database on your disk. Sync is the background process that carries your changes to other devices and members, and theirs to you.
What "local" means here
The any server process owns an any-store database under the account's data dir (<data-dir>/<accountId>/sdk/). A query is an indexed read against that file. A write validates, appends a change to the object's DAG, applies the CRDT ops to the rows, and returns — all before any network I/O.
# works with the network cable unplugged
curl -X POST http://127.0.0.1:7001/v1/spaces/$SPACE/objects \
-H 'content-type: application/json' \
-d '{"types":["page"],"initialProperties":{"any":{"name":"Offline note"}}}'
The reply is 201 with the new object's id. It will sync later; nothing about the request depends on whether "later" is milliseconds or days away.
What sync is
A change is a signed, encrypted node in a per-object DAG (a git-like history: each change lists the heads it was built on). Sync is the exchange of DAG changes between peers:
- Network sync — the space's responsible any-sync nodes store ciphertext changes and relay them. A device that comes online pulls what it missed and pushes what it wrote.
- Local-network sync — devices of the same account on one LAN discover each other over mDNS and exchange changes directly, including while the nodes are unreachable (config
p2p.enabled, on by default).
Both paths deliver the same changes; a space can be fully synced with networkPeers: 0 when everything converged over the LAN.
Head-sync and convergence
Peers compare heads — the tips of each object's DAG. If two peers have the same heads for every object, they hold the same state. A head-sync round diffs the head sets against the responsible nodes and exchanges the missing changes. It runs on a periodic timer (~30 s) and on every local write; you can force one:
curl -X POST http://127.0.0.1:7001/v1/spaces/$SPACE/sync # → 204 when the round completes
any space sync $SPACE
Convergence follows from the CRDT: once two peers have applied the same set of changes, in any order, they hold identical rows (CRDTs and consistency). There is no reconciliation step, no conflict dialog, no server copy that wins.
Observing sync state
Sync state is a first-class read, cheap enough to poll on a render tick:
curl http://127.0.0.1:7001/v1/spaces/$SPACE/sync-status
{ "spaceId": "bafy…", "state": "syncing",
"synced": 1, "total": 3,
"networkPeers": 1, "localPeers": 0, "p2p": "notconnected",
"lastSyncedAt": "2026-08-24T10:12:00Z" }
state |
Meaning |
|---|---|
synced |
Every object's heads match the responsible peers. |
syncing |
Changes are in flight in one direction or the other. |
offline |
No peer reachable; local reads and writes continue. |
unknown |
Space not loaded yet on this device (or an object id nobody has seen). |
error |
The last round failed; the next timer retries. |
Per-object state lives at …/sync-status/objects/:objectId, and both levels stream transitions over SSE (Sync status).
Boot catch-up
When the server starts, it binds the listener first and runs the space loading + offline catch-up on a background pass. GET /v1/health reports "bootstrapping": true while that pass runs; reads against a space that has not had its turn yet serve the pre-offline state. Per-space convergence is what /sync-status reports, not the health flag.
Why it matters. A hosted backend answers a query with the truth as the server sees it, and returns an error when it cannot. any answers with the truth as this device sees it, always. That is the property that makes an app usable on a plane, a phone in a tunnel, or a laptop whose owner simply never turned sync on — and it is why the API never asks you to handle a "not connected" error on a read or write.
What does not sync
- Local-scope fields — dataset fields declared
local(chat'sunreadflags, for example) are device-only; they never enter the DAG (System fields). - Account-scope settings — per-space
settingssync across the account's own devices through the tech space, never to other members. - The search index — derived state, rebuilt locally from the change feed (Indexing).
- Secrets stored by anyrt — device-local values, never synced (Credentials).
Note. Timestamps such as
modifiedAtare the author's clock. They converge (every peer ends up with the same value), but they are display and sort quality only — never use them as a fence for "has this synced yet". Use/sync-statusfor that.