# CRDT Collaboration Architecture in Macro: Loro and Cloudflare Durable Objects

> Explore Macro's CRDT collaboration architecture using Loro and Cloudflare Durable Objects for strong consistency and automatic conflict resolution in real-time editing.

- Repository: [Macro/macro](https://github.com/macro-inc/macro)
- Tags: architecture
- Published: 2026-08-17

---

**Macro implements real-time collaborative editing by hosting a single Loro CRDT document inside a Cloudflare Durable Object per document, ensuring strong consistency through serialized WebSocket message processing while using Loro's built-in vector clocks for automatic conflict resolution.**

The macro-inc/macro repository powers a high-performance collaborative editor that combines Loro's conflict-free replicated data types (CRDTs) with Cloudflare's edge infrastructure. This CRDT collaboration architecture routes all editing sessions for a given document through a single Durable Object, guaranteeing that concurrent operations merge deterministically while maintaining low-latency synchronization across global regions.

## Four-Layer Architecture

Macro's collaboration stack operates through four tightly coupled layers:

- **Document Core**: Holds the authoritative `LoroDoc` and awareness data within the `DocumentSyncSession` Durable Object defined in [`services/sync-service/src/durable_object.rs`](https://github.com/macro-inc/macro/blob/main/services/sync-service/src/durable_object.rs)
- **Sync Engine**: Serialized WebSocket message handling and CRDT operations managed by `DocumentState` in [`services/sync-service/src/state.rs`](https://github.com/macro-inc/macro/blob/main/services/sync-service/src/state.rs)
- **Persistence**: Shallow snapshots to Durable KV and full snapshots to S3-compatible storage via [`services/sync-service/src/storage/mod.rs`](https://github.com/macro-inc/macro/blob/main/services/sync-service/src/storage/mod.rs) and [`services/sync-service/src/storage/backends/durable_kv.rs`](https://github.com/macro-inc/macro/blob/main/services/sync-service/src/storage/backends/durable_kv.rs)
- **Cloudflare Edge**: HTTP/WebSocket entry points, periodic autosave alarms, and cross-region coordination implemented in the Durable Object trait implementation

## Per-Document Durable Objects and Strong Consistency

When a client connects to `/document/{document_id}/connect`, Cloudflare instantiates or reuses a `DocumentSyncSession` Durable Object. This guarantees that all traffic for a specific document ID routes to the same edge instance, creating a single-writer consistency model for the underlying CRDT.

The Durable Object stores one `LoroDoc` instance in memory along with an `EphemeralStore` for awareness data:

```rust
#[durable_object]
pub struct DocumentSyncSession {
    awareness: EphemeralStore,
    // ...
}

```

Because Cloudflare serializes all inbound WebSocket messages through the `websocket_message` method before they touch the CRDT, the system avoids race conditions while maintaining the performance benefits of edge computing.

## Loro Integration and CRDT State Management

The core CRDT document lives inside `DocumentState` in [`services/sync-service/src/state.rs`](https://github.com/macro-inc/macro/blob/main/services/sync-service/src/state.rs). The system initializes each document with `LoroDoc::new()` and maintains this instance for the Durable Object's entire lifetime.

```rust
pub struct DocumentState {
    pub loro_doc: LoroDoc,
}

```

Operations arrive as binary Loro updates from clients. The Durable Object deserializes these via `websocket::deserialize_message` and applies them using `apply_update()`. Loro automatically merges concurrent edits using its internal **version vectors** and **oplog** frontiers, ensuring that divergent client states converge deterministically without server-side locking.

## Real-Time Synchronization Protocol

The WebSocket message handler in [`services/sync-service/src/durable_object.rs`](https://github.com/macro-inc/macro/blob/main/services/sync-service/src/durable_object.rs) processes all document mutations. When a binary update arrives, the system applies it to the shared `LoroDoc` and broadcasts the delta to connected peers:

```rust
async fn websocket_message(&self, ws: WebSocket, msg: WebSocketIncomingMessage) -> Result<()> {
    let binary = match msg {
        WebSocketIncomingMessage::Binary(b) => b,
        _ => return Ok(()),
    };

    let update = websocket::deserialize_message(&binary)?;
    self.document_state().await?.loro_doc.apply_update(update)?;
    websocket::broadcast_update(&ws, &self.state.get_websockets(), update, self.msg_buffer.clone())?;
    bump_alarm(&self.state).await?;
    Ok(())
}

```

This serialized processing path ensures that even with thousands of concurrent connections, the CRDT state transitions remain atomic and ordered.

## Awareness and Presence Handling

Macro uses Loro's `EphemeralStore` to manage transient user state such as cursor positions and text selections. Stored in the `awareness` field of `DocumentSyncSession`, this data expires automatically (configured to 5000ms) and broadcasts updates when peers join or disconnect.

When a client disconnects, the Durable Object removes the peer from the store and propagates the change:

```rust
self.awareness.delete(&peer_id.to_string());
let update = self.awareness.encode(&peer_id.to_string());
websocket::broadcast_awareness(&ws, …);

```

Unlike document content, awareness data does not persist to storage, ensuring that presence information remains lightweight and ephemeral.

## Persistence and Snapshot Strategy

The architecture implements a tiered persistence strategy to balance durability with performance. The `alarm` method runs periodically or upon client disconnect to evaluate `state.should_save()`:

- **Shallow snapshots**: State vectors cached for search indexing and sent to the Document Search Service (DSS)
- **Full snapshots**: Binary exports stored in Durable KV and S3-compatible backends via `store_snapshot()`

```rust
async fn alarm(&self) -> Result<Response> {
    let state = self.document_state().await?;
    if state.should_save() {
        let sess = self.session_storage().await?;
        sess.store_snapshot(&state).await?;
        sess.clear_applied_ops().await?;
        
        self.state.wait_until(async move {
            if let Ok(snapshot) = state.export_shallow_snapshot() {
                report_new_doc_state(&doc_id, &snapshot, &env).await;
            }
        });
    }
    if !self.state.get_websockets().is_empty() {
        bump_alarm(&self.state).await?;
    }
    Response::ok("ok")
}

```

This fire-and-forget pattern for side effects ensures the main request path remains low-latency while background tasks handle durability.

## Versioning and Historical State

Clients can request document snapshots at specific versions using `VersionIndicator` parameters. The Durable Object constructs an `ExportMode::StateOnly` using the requested `VersionVector`, allowing Loro to reconstruct historical states deterministically:

```rust
let frontiers = body.and_then(|b| {
    b.version_id.map(|vid| {
        let id = loro::ID::new(vid.peer.parse::<u64>().unwrap(), vid.counter);
        ExportMode::StateOnly(Some(Cow::Owned(loro::Frontiers::ID(id))))
    })
});

let snapshot = self.document_state().await?
    .export_snapshot(frontiers)
    .context("Couldn't export snapshot")?;

```

This capability enables features like version history and time-travel debugging while maintaining the cryptographic consistency guarantees of the underlying CRDT.

## Summary

Macro's CRDT collaboration architecture delivers real-time synchronization through these key mechanisms:

- **Single Durable Object per document** guarantees strong consistency and serialized message processing at the edge
- **Loro CRDT** provides deterministic conflict resolution through version vectors and oplog frontiers without requiring operational transformation locks
- **EphemeralStore** handles transient awareness data separately from document persistence
- **Tiered persistence** combines Durable KV for rapid recovery with S3-compatible storage for long-term durability
- **Alarm-based autosave** optimizes storage writes while ensuring data safety during connection drops

## Frequently Asked Questions

### How does Macro handle conflicting edits from simultaneous users?

Macro relies on Loro's CRDT properties rather than operational transformation. When concurrent updates arrive through the Durable Object's serialized WebSocket handler, Loro applies them using vector clock comparison and oplog merging. The `LoroDoc` in [`services/sync-service/src/state.rs`](https://github.com/macro-inc/macro/blob/main/services/sync-service/src/state.rs) automatically reconciles conflicts so that all clients eventually converge to the same state regardless of network latency or message ordering.

### What happens when the last user disconnects from a document?

The `DocumentSyncSession` Durable Object detects empty WebSocket collections during the `alarm` tick. If `state.should_save()` returns true, the system persists a full snapshot via `store_snapshot()`, clears the applied operations list with `clear_applied_ops()`, and reports the final state to downstream services. The Durable Object then hibernates until the next connection, with state reconstructed from the persisted snapshot in [`services/sync-service/src/storage/backends/durable_kv.rs`](https://github.com/macro-inc/macro/blob/main/services/sync-service/src/storage/backends/durable_kv.rs).

### Why does Macro use Cloudflare Durable Objects instead of traditional servers?

Cloudflare Durable Objects provide co-location of compute and state at the network edge, ensuring that all clients editing the same document connect to the same physical instance. This eliminates the need for distributed locking or consensus protocols between regions. As implemented in [`services/sync-service/src/durable_object.rs`](https://github.com/macro-inc/macro/blob/main/services/sync-service/src/durable_object.rs), the single-instance model guarantees that the `LoroDoc` remains authoritative while maintaining sub-100ms latency for global users through Cloudflare's edge network.

### How are document versions and history maintained?

The system stores periodic snapshots and maintains the full oplog within the Loro document structure. When exporting, the `export_snapshot` method accepts an `ExportMode::StateOnly` parameter constructed from a `VersionVector`, allowing precise reconstruction of any historical state. The [`crates/documents/src/domain/ports/markdown.rs`](https://github.com/macro-inc/macro/blob/main/crates/documents/src/domain/ports/markdown.rs) integration shows how initial snapshots are generated from Markdown, while subsequent edits append to the versioned history stored in the Durable Object's memory and periodically flushed to the snapshot storage backend.