Docs/Concepts/Local-first and CRDT sync

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:

  1. You sign in through WorkOS AuthKit in a web view.
  2. 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.
  3. The client stores that token and sends it as a bearer token on every sync request.
  4. 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.