# Macro's CRDT-Based Real-Time Collaboration Architecture: A Deep Dive into Loro-Powered Document Sync

> Explore Macro's Loro-powered CRDT architecture for real-time document sync. Discover how WAL, anti-entropy, and pluggable transports ensure eventual consistency.

- Repository: [Macro/macro](https://github.com/macro-inc/macro)
- Tags: deep-dive
- Published: 2026-08-16

---

**Macro's real-time collaboration engine uses a Loro-based CRDT architecture with a Write-Ahead Log (WAL), anti-entropy convergence, and pluggable transport layers to guarantee eventual consistency across peers.**

Macro's collaborative editing system is built on a sophisticated **CRDT (Conflict-Free Replicated Data Type)** engine powered by the open-source **Loro** library. The `macro-inc/macro` repository implements a loosely-coupled architecture that delivers low-latency, always-online editing with automatic conflict resolution, durability, and live presence awareness. This article examines the core components, data flow, and convergence mechanisms that make this system production-ready.

## Core Architectural Components

The collaboration stack in `packages/collaboration/src/collab/` separates concerns into distinct, testable modules:

### LoroManager: The CRDT Document Wrapper

The **`LoroManager`** (implemented in [[`manager.ts`](https://github.com/macro-inc/macro/blob/main/manager.ts)](https://github.com/macro-inc/macro/blob/main/packages/collaboration/src/collab/manager.ts)) wraps a raw Loro document and exposes a typed API for:

- Importing and exporting binary updates
- Resetting the document from a snapshot
- Converting between Loro's internal state and Macro's application-specific schema

This abstraction allows the rest of the system to remain agnostic of Loro's internal versioning format while still leveraging its CRDT properties.

### SyncEngine: Central Orchestration

The **`SyncEngine`** ([[`engine.ts`](https://github.com/macro-inc/macro/blob/main/engine.ts)](https://github.com/macro-inc/macro/blob/main/packages/collaboration/src/collab/engine.ts)) is the heart of Macro's **CRDT-based real-time collaboration**. It coordinates:

- Local update capture and forwarding
- WAL persistence and remote broadcasting
- Remote update application and anti-entropy
- Periodic snapshot generation
- Awareness state management

The engine is **idempotent**—calling `start()` multiple times has no side effects, and `reset()` reinitializes from a fresh snapshot without losing unsaved local edits.

### Write-Ahead Log (WAL) for Durability

The **WAL** ([[`wal.ts`](https://github.com/macro-inc/macro/blob/main/wal.ts)](https://github.com/macro-inc/macro/blob/main/packages/collaboration/src/collab/wal.ts)) guarantees that local edits survive crashes before server acknowledgment. Key characteristics:

- Appends raw Loro updates in order
- Can be replayed on startup if snapshots are stale
- Automatically pruned after successful snapshot persistence

### Awareness for Presence

**Awareness** ([[`awareness.ts`](https://github.com/macro-inc/macro/blob/main/awareness.ts)](https://github.com/macro-inc/macro/blob/main/packages/collaboration/src/collab/awareness.ts)) handles lightweight, ephemeral data:

- Cursor positions and text selections
- User identity (name, avatar)
- Per-peer state that merges without conflicts

Awareness updates are encoded, broadcast over the same channel as document edits, and merged locally via `awareness.importRemoteAwareness`.

### SyncService: Pluggable Transport

The sync layer provides two concrete sources ([[`source.ts`](https://github.com/macro-inc/macro/blob/main/source.ts)](https://github.com/macro-inc/macro/blob/main/packages/collaboration/src/collab/source.ts)):

- **`LiveSyncSource`**: WebSocket-based transport to the Macro backend
- **`WALSyncer`**: Persistent log replay for offline-first recovery

This abstraction enables alternative transports—Cloudflare Workers, server-side AI editors, or custom backends—without modifying the core engine.

### SnapshotStore for Fast Recovery

The **`SnapshotStore`** ([[`snapshot-store.ts`](https://github.com/macro-inc/macro/blob/main/snapshot-store.ts)](https://github.com/macro-inc/macro/blob/main/packages/collaboration/src/collab/snapshot-store.ts)) persists shallow Loro snapshots every **5 seconds** (`SNAPSHOT_INTERVAL_MS`). Implementations target S3, IndexedDB, or in-memory storage depending on the environment.

### Chatter for Intra-Tab Sync

**Chatter** ([[`chatter.ts`](https://github.com/macro-inc/macro/blob/main/chatter.ts)](https://github.com/macro-inc/macro/blob/main/packages/collaboration/src/collab/chatter.ts)) enables zero-latency sync between browser tabs editing the same document. The default `BroadcastChannelChatter` can be replaced via the `makeChatter` factory for other messaging systems.

## Data Flow Through the System

A single edit traverses the architecture as follows:

1. **Local capture**: `loroManager.doc.subscribeLocalUpdates` detects changes
2. **WAL append**: `syncs.wal.append` durably logs the update
3. **Chatter broadcast**: `chatter.post({type:'update', data})` notifies other tabs immediately
4. **Server sync**: `LiveSyncSource` forwards via WebSocket; server persists to durable storage
5. **Remote application**: `engine.handleRemoteUpdate` imports peer changes into the Loro document
6. **Anti-entropy trigger**: If prerequisite operations are missing, `convergeFromServer` requests catch-up state
7. **Snapshot cycle**: Every 5 seconds, `persistSnapshot` exports `doc.export({mode:'shallow-snapshot'})` and prunes covered WAL entries

## Anti-Entropy and Convergence

CRDTs guarantee that concurrent edits commute, but **anti-entropy** ensures all peers eventually receive the same operations. The convergence mechanism in [[`engine.ts`](https://github.com/macro-inc/macro/blob/main/engine.ts)](https://github.com/macro-inc/macro/blob/main/packages/collaboration/src/collab/engine.ts) uses a generation counter to coordinate requests:

```typescript
private convergeFromServer({ rerunIfInFlight = false } = {}): Promise<void> {
  const generation = this.lifecycleGeneration;
  if (!this.isActiveGeneration(generation)) return Promise.resolve();

  const inFlight = this.convergence;
  if (inFlight?.generation === generation) {
    if (rerunIfInFlight) inFlight.rerunRequested = true;
    return inFlight.promise;
  }

  const since = this.loroManager.doc.version(); // current version vector
  const promise = this.requestAndHandleUpdatesSince(since, 1, generation);
  this.convergence = { generation, promise, rerunRequested: false };
  // ...
}

```

- **`requestUpdatesSince`** fetches all operations after the client's version vector
- **Retries** execute up to `REQUEST_UPDATES_MAX_ATTEMPTS` (3) with exponential backoff
- **Generation tracking** prevents stale updates from interfering after `stop()`/`start()` cycles

## Persistence and Crash Recovery

The dual-layer persistence strategy balances speed and durability:

| Layer | Purpose | Trigger |
|-------|---------|---------|
| WAL | Immediate durability for in-flight edits | Every local update |
| Snapshot | Fast recovery, historical truncation | Every `SNAPSHOT_INTERVAL_MS` (5s) |

On startup, the engine:

1. Loads the latest snapshot via `defaultSnapshotThunk`
2. Replays WAL entries newer than the snapshot
3. Enters normal sync operation

Snapshots use Loro's efficient binary format: `doc.export({mode:'shallow-snapshot', frontiers:doc.oplogFrontiers()})`.

## Practical Implementation

Below is a minimal integration showing how to instantiate Macro's **CRDT-based real-time collaboration** engine:

```typescript
import { createSyncEngine } from '@macro-inc/collaboration/collab/engine';
import { createLoroManager } from '@macro-inc/collaboration/collab/manager';
import { createAwareness } from '@macro-inc/collaboration/collab/awareness';
import { createLiveSyncSource } from '@macro-inc/collaboration/sync-service/source';
import { createWALSyncer } from '@macro-inc/collaboration/collab/wal';
import { createSnapshotStore } from '@macro-inc/collaboration/collab/snapshot-store';

// 1. Initialise low-level components
const loroManager = createLoroManager({ schema: MyDocumentSchema });
const awareness   = createAwareness({ userId: currentUser.id, name: currentUser.name });
const liveSource  = createLiveSyncSource({ documentId: docId, url: WS_URL, token: authToken });
const walSyncer   = createWALSyncer({ storage: indexedDB });
const snapshotStore = createSnapshotStore({ bucket: 'macro-snapshots' });

// 2. Build the reactive engine
const syncEngine = createSyncEngine({
  loroManager,
  awareness,
  syncs: { live: liveSource, wal: walSyncer },
  bindings: {
    onRemoteState: (state) => {
      editor.setContent(state);
    },
  },
  snapshotStore,
});

// 3. Start collaboration
syncEngine.start();

// 4. Push local changes
editor.onChange((newState) => {
  syncEngine.syncStateToLoro(newState);
});

// 5. Clean shutdown
window.addEventListener('beforeunload', () => syncEngine.stop());

```

The returned `syncEngine` exposes reactive state (`isRunning`), lifecycle methods (`start`, `stop`, `reset`), and synchronization hooks (`syncStateToLoro`, `syncAwarenessToLoro`).

## Scaling and Extensibility

Macro's architecture intentionally separates concerns to support diverse deployment scenarios:

- **Transport-agnostic**: Replace `LiveSyncSource` for WebRTC, WebTransport, or serverless edge functions
- **Custom Chatter**: Inject `makeChatter` for Electron IPC, SharedWorker, or Node.js cluster messaging
- **Snapshot backends**: Implement `SnapshotStore<RawUpdate>` for Redis, PostgreSQL, or tiered storage

This modularity enables the same codebase to power browser-based editors, AI-assisted writing agents, and server-side document processors.

## Summary

- **LoroManager** wraps the CRDT document with schema-aware serialization in [[`manager.ts`](https://github.com/macro-inc/macro/blob/main/manager.ts)](https://github.com/macro-inc/macro/blob/main/packages/collaboration/src/collab/manager.ts)
- **SyncEngine** orchestrates the full sync lifecycle with idempotent start/stop semantics in [[`engine.ts`](https://github.com/macro-inc/macro/blob/main/engine.ts)](https://github.com/macro-inc/macro/blob/main/packages/collaboration/src/collab/engine.ts)
- **WAL + SnapshotStore** provide crash recovery and historical truncation via [[`wal.ts`](https://github.com/macro-inc/macro/blob/main/wal.ts)](https://github.com/macro-inc/macro/blob/main/packages/collaboration/src/collab/wal.ts) and [[`snapshot-store.ts`](https://github.com/macro-inc/macro/blob/main/snapshot-store.ts)](https://github.com/macro-inc/macro/blob/main/packages/collaboration/src/collab/snapshot-store.ts)
- **Anti-entropy** with version vectors and retry logic ensures convergence despite network partitions
- **Modular transport layers** allow the same core to run in browsers, workers, and server environments

## Frequently Asked Questions

### How does Macro's CRDT-based real-time collaboration handle offline editing?

The WAL persists every local update to IndexedDB or similar storage immediately. When connectivity returns, the `WALSyncer` replays buffered operations and the anti-entropy loop fetches any missing remote changes. The Loro CRDT guarantees that concurrent offline edits merge correctly without user intervention.

### What happens if two users edit the same text simultaneously?

Loro's **Reg** (register) and **Text** CRDT types automatically resolve conflicts using last-writer-wins or multi-value semantics depending on the schema. The `SyncEngine` applies remote updates via `loroManager.importUpdate`, and the CRDT's commutative properties ensure all peers converge to identical document states regardless of operation ordering.

### Can the sync engine run entirely without a server?

Yes, for single-user or local-network scenarios. The `BroadcastChannelChatter` enables tab-to-tab sync without WebSocket infrastructure. However, server persistence via `LiveSyncSource` is required for cross-device synchronization and durable history. The modular design allows either mode without code changes.

### How often does Macro snapshot document state?

The engine snapshots every **5 seconds** (`SNAPSHOT_INTERVAL_MS`) by default, as defined in [[`engine.ts`](https://github.com/macro-inc/macro/blob/main/engine.ts)](https://github.com/macro-inc/macro/blob/main/packages/collaboration/src/collab/engine.ts). This interval balances recovery speed against storage costs. After a successful snapshot, WAL entries covered by that snapshot are pruned to reclaim space.