Local-first and CRDT sync
Solomon works fully offline on plain markdown files. Sync and collaboration are opt-in, per vault — nothing about opening a vault uploads anything anywhere. This page explains what "local-first" actually guarantees, how sync works once you turn it on, and the one folder that never syncs no matter what.
Local-only is the default
A freshly opened vault has no sync identity at all. That state lives (or
rather, doesn't) in a small file at .solomon/vault.json inside the vault
folder: its absence means "local only, never sync." There is no
half-connected state — a vault is either fully local or it has explicitly
been given a sync identity.
Turning on sync
With the vault open and signed in (through WorkOS AuthKit in the web app),
enabling sync writes .solomon/vault.json with a freshly minted vault_id
and your owner_user_id:
{ "vault_id": "<ulid>", "owner_user_id": "<user id>" }
Because that identity file travels inside the vault folder, you can rename
or move a synced vault on disk without losing its server-side identity.
Disabling sync moves the file aside to .solomon/vault.json.disabled — a
soft-delete sentinel — so re-enabling later recovers the same vault_id
instead of stranding server-side state under a new one. Your local files
stay authoritative the whole time; disabling sync never costs you data.
What actually syncs: CRDT rooms, one per note
Once sync is on, each note replicates as its own CRDT document — a
conflict-free replicated data type, the same class of data structure Google
Docs–style collaborative editors use. A note's sync room is keyed
vault:<vault_id>:doc:<note_id>. Two devices (or two people) editing the
same note converge on the same content automatically — there's no "resolve
conflict" dialog, because CRDT merges are mathematically guaranteed to
converge regardless of the order edits arrive in.
This is also why the editor always works on raw markdown underneath its live-preview rendering: the CRDT layer merges the actual text, so the buffer it operates on has to be the real file content, not some transformed representation.
Sign-in, without a server holding your identity
Sync requires an account, but native clients (desktop, iOS) don't hold a browser session cookie the way a web app does. Instead:
- You sign in through WorkOS AuthKit in a web view.
- Solomon's web app mints its own short-lived (1-hour) RS256 session JWT, signed with Solomon's key pair, and hands it to the native client.
- The client stores that token and sends it as a bearer token on every sync request.
- The sync server verifies the token against Solomon's published JWKS document — it never needs to share a secret with the sync server to do this, which is exactly what makes self-hosting a sync server possible.
The _private/ guarantee
One rule is worth memorizing, because it's enforced at the lowest level of the sync engine, not as a UI convention:
Any note under
_private/is local-only and never syncs — full stop.
A path that equals _private or starts with _private/ never enters a
sync room, so it can never reach the server or a collaborator, even in a
fully-synced vault. The same boundary extends to agents: the local MCP
server refuses to read or write anything under _private/, any
dot-directory (.solomon/, .obsidian/), or Widgets/. Use _private/
for drafts, secrets, or anything you want to keep strictly on your own
machine, invisible to both the network and to agent tooling.
Summary
| Concept | Where it lives |
|---|---|
Vault = folder of .md files |
your filesystem |
| Sync identity (opt-in) | .solomon/vault.json |
| Disabled-sync sentinel | .solomon/vault.json.disabled |
| Sync room key | vault:<vault_id>:doc:<note_id> |
| Never syncs, ever | anything under _private/ |
Related
- [[Your first note and vault]] — why the live-preview editor always keeps raw markdown as its buffer.
- [[Agents and skills]] — the same
_private/boundary MCP tools enforce. - [[Publish a folder or vault]] — publishing requires cloud sync to be on for the vault.