Developers

Sync protocol

How edits flow between clients and Convex — sequence numbers, commits, the confirmed-plus-pending client model, loading, snapshots and presence.

On this page

Convex is the authority for every design. Clients edit optimistically and converge on the server's order. The rules below are part of the frozen contract and apply to the web, desktop and iOS apps, the AI and the Agent API alike.

  • Authority: Convex. Each design has a monotonically increasing seq. designOps table holds accepted transactions by seq. Materialized state lives in node documents; periodic snapshots (every 200 tx or 5 min of activity) are stored as compressed JSON in S3 (snapshots/<designId>/<seq>.json.zst) and referenced from designs.snapshot.
  • Client commit: designs.commit({ designId, baseSeq, tx }) -> { seq, applied, dropped }. Server applies ops to the current state (not to baseSeq) with last-writer-wins per property; structural conflicts resolved by server order; invalid ops are dropped and reported. Idempotent on tx.id.
  • Client model: confirmed (design at last server seq) + pending (own uncommitted transactions). Render = confirmed + pending. On remote tx: apply to confirmed, then re-apply pending over it (dropping what no longer applies). On own ack: move the tx from pending into confirmed (using the server's applied).
  • Load: designs.load (paginated one-shot queries, nodes in chunks of 500) returns { design, seq }; then subscribe to designOps.since({ designId, seq }) (reactive) for live transactions. If the gap is large the client reloads a snapshot.
  • Ephemeral state (cursors, selections, live drag previews, "AI is editing X" markers) uses presence (Convex table with expiresAt, heartbeat every 5 s, auto-cleanup cron) and never enters history.
  • Dragging/typing never commits per frame. Clients commit one transaction per gesture (pointer-up / debounced text edit 400 ms).
  • AI/workflow transactions use the same commit with source: "ai" | "workflow"; the client groups consecutive transactions with the same actor.runId into one undo step.

Sequence diagram#

A typical edit from one client while another is watching:

Text
Client A                         Convex                          Client B
   │  apply tx locally (pending)    │                                 │
   │── designs.commit(baseSeq, tx) ─▶ apply to current state          │
   │                                 │ seq = seq + 1, log in designOps │
   │◀──── { seq, applied, dropped } ─│                                 │
   │  move tx: pending → confirmed   │── designOps.since(seq) update ─▶│
   │                                 │        apply to confirmed,      │
   │                                 │        re-apply own pending     │

Implementing a client#

  1. Load: call designs.load page by page, assemble the Design, remember seq.
  2. Subscribe to designOps.since({ designId, seq }) and apply each incoming transaction to the confirmed state, then re-apply your pending transactions on top (dropping ops that no longer apply).
  3. Edit: build a transaction per gesture, apply it locally as pending, and call designs.commit({ designId, baseSeq, tx }).
  4. Acknowledge: when the commit returns, replace the pending transaction with the server's applied ops and advance to its seq.
  5. Recover: if the subscription reports a large gap, reload (snapshot plus subsequent transactions).

@desyn/doc-ops provides this state machine — createSyncState, commitLocal, acknowledge, receiveRemote and rebase — so TypeScript clients only wire it to the transport. Over plain HTTP, the Agent API exposes the same commit and since operations.

Presence#

Cursors, selections, live drag previews and "AI is editing" markers go through the presence table: clients heartbeat every 5 seconds, entries expire on their own, and a cron removes stale ones. Presence is never written into the design or its history.

Popular pages

QuickstartGet started
Keyboard shortcutsEditor
The agentAI
Docker Compose quickstartSelf-hosting
Configuration referenceSelf-hosting
Agent API (v1)Developers