Skip to content

Sync Algorithm

Engram’s sync model is version-vector–based, with explicit conflict detection and a 3-way merge fallback when human resolution is needed.

Every note has a version vector — a {device_id → counter} map tracking the last edit each device made. When a device pushes an edit, it tags the push with the parent vector (the vector it forked from) and the new vector (the parent plus an increment for the current device).

When the backend receives a push:

  1. Parent matches current? → write succeeds, vector becomes new current. Other connected devices receive the change via WebSocket fan-out.
  2. Parent is an ancestor of current? → there’s been another edit; this push is a conflict. Backend rejects with 409 and the client opens the conflict UI.
  3. Vectors are concurrent? → also a conflict. Same flow.

This is fundamentally version vectors — a well-studied approach used in CouchDB, Riak, and others.

CRDTs (Yjs/Automerge) are tempting for collaborative text. Engram chose against them for two reasons:

  • Markdown semantics aren’t text semantics. A CRDT op on raw markdown can break wikilink integrity, frontmatter syntax, code fences. Validating CRDT output post-merge is awkward.
  • The use case is async. Engram is “different devices, sometimes offline, mostly one user” — not “two people editing live.” Version vectors + 3-way merge fits that shape more cleanly.

The trade-off: when there IS a conflict, you decide rather than the algorithm. Engram’s job is to detect reliably and hand you the choice, not to merge silently in ways you might not want.

Each user has a Phoenix Channel subscription. When one device pushes a successful edit:

  1. Backend updates Postgres
  2. Enqueues the embed/index job
  3. Broadcasts the change over the user’s Channel
  4. Every other connected device for that user receives the update immediately

Embedding is asynchronous (Oban); search results catch up within seconds.

When a device goes offline:

  • Local edits queue in the plugin
  • Pulls pause
  • On reconnect: plugin replays the queue in order
  • Any item that lands as a conflict (409) moves to the conflicts queue for resolution

This means you can edit confidently while offline. The cost is that catching back up may surface conflicts you’d otherwise never have hit.

For conflicting markdown notes, Engram has the base (common ancestor), the local version, and the remote version. The merge UI shows all three. You can:

  • Pick one side
  • Hand-edit a synthesis
  • Save the remote as a sibling note (“keep both”)

For non-markdown files (canvas, attachments), only the side-pick and “keep both” options exist — there’s no meaningful 3-way text merge.

See Obsidian → Conflicts for the end-user view.