Agora
Engines

Engines

The CRDT backend behind a document — what a driver owns, how Yjs, Automerge and the edge engines compare, how a document picks one, and what changes in your code when it does.

An engine is the CRDT backend that owns a document: its merge, its wire protocol, its debounced persistence and its version mechanism. The library wraps each one behind a driver and exposes the same manager API over all of them — getDocumentState, getDocumentText, createVersion, restoreVersion, diffVersions — so the engine is a config decision rather than an architectural one.

Drivers are built lazily, one per engine, on first use. Configuring four engines and only ever opening Yjs documents means only the Yjs driver exists in memory.

At a glance

yjsautomergepartykit / partyserver
Where the socket livesyour Adonis processyour Adonis processCloudflare Durable Objects
Data modelshared types — rich text, maps, arraysa JSON-like documentsame as Yjs, at the edge
Rich-text editorsTiptap, ProseMirror, CodeMirrornoneTiptap, ProseMirror, CodeMirror
Live cursors (awareness)yesnoyes
Historybinary snapshots the driver addsnative change graphbinary snapshots
Client hookuseCollabDocuseAutomergeDocuseCollabDoc
Runs behind >1 instancewith sticky sessionsnoyes, natively
Extra deploymentnonenonea Worker you own

Choosing

Start with yjs. It is the default, it has the editor bindings, it has awareness, and it is what the rest of these docs assume unless a page says otherwise.

Reach for automerge when the document is structured JSON that several clients mutate — a form, a config object, a board of records — and you would rather have a native change graph than an editor binding you were never going to use. It cannot back a rich-text editor.

Reach for the edge engines when latency across continents is the problem you are actually solving, and you are willing to own a Cloudflare Worker and a shared secret to fix it.

A whiteboard is usually still Yjs

The instinct to pick Automerge for a canvas is understandable — a scene is structured JSON — but Excalidraw and tldraw both want awareness for remote cursors, and the shipped editor adapters are built on the Yjs session. Automerge is for data you compute over, not for a surface people point at.

How a document picks its engine

Three ways, in the order the manager resolves them:

config/collaboration.ts
// 1. A sweeping function — wins outright, for every document
engineFor: (docName) => (docName.startsWith('archives/') ? 'automerge' : 'yjs'),

// 2. Per declared document type
documents: {
  'whiteboards/:id': { engine: 'automerge' },
},

// 3. The global default (and the fallback when nothing else matches)
engine: 'yjs',

The client is never told which engine to use — it asks. GET /collaboration/token?doc=… answers with { token, wsUrl, engine }, and the hook picks the transport that matches. Moving a document from the embedded Yjs server to a PartyKit worker is a config change plus a deploy; the editor component does not know it happened.

What stays the same when you switch

Everything except the sync itself:

  • Comments live at manager.comments and never touch a driver, so they follow the document across engines untouched.
  • Storage is one CollaborationStorage implementation shared by every driver — the same tables, the same rows.
  • Authorization is resolved by the manager before any driver is involved.
  • Versions answer the same four calls, with the mechanism differing underneath.

What does change is the client hook — Automerge documents cannot back a Y.Doc, so they get their own hook rather than being forced through a Y.Doc-shaped interface. See Automerge.

On this page