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.
The shape
Section titled “The shape”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:
- Parent matches current? → write succeeds, vector becomes new current. Other connected devices receive the change via WebSocket fan-out.
- 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.
- Vectors are concurrent? → also a conflict. Same flow.
This is fundamentally version vectors — a well-studied approach used in CouchDB, Riak, and others.
Why not CRDTs
Section titled “Why not CRDTs”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.
Real-time fan-out
Section titled “Real-time fan-out”Each user has a Phoenix Channel subscription. When one device pushes a successful edit:
- Backend updates Postgres
- Enqueues the embed/index job
- Broadcasts the change over the user’s Channel
- Every other connected device for that user receives the update immediately
Embedding is asynchronous (Oban); search results catch up within seconds.
Offline behavior
Section titled “Offline behavior”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.
The 3-way merge
Section titled “The 3-way merge”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.