How the Computer Sync Protocol Handles Conflict Resolution with Its Change Coalescing Algorithm

The Computer sync protocol resolves filesystem conflicts by coalescing all mutations per path to the highest revision and applying a last-writer-wins rule when batches arrive at the Durable Object.

The Computer project from Cloudflare implements a distributed filesystem that synchronizes state between Durable Objects (DOs) and container-side FUSE mounts. A critical component of this system is how it handles conflict resolution when multiple containers modify the same path concurrently. This article examines the change coalescing algorithm at the heart of the sync protocol and explains how it deterministically resolves conflicts without merge operations or error states.

What Is Change Coalescing in the Computer Sync Protocol?

At its core, change coalescing reduces multiple filesystem operations on the same path into a single representative entry. Rather than streaming every write, rename, or unlink across the wire, the protocol compresses history into the latest state per path.

This design serves two purposes: it minimizes network payload and simplifies conflict resolution by ensuring only one mutation per path competes at the DO layer.

How coalesceChanges Implements Per-Path Reduction

The algorithm lives in [packages/dofs/src/sync/coalesce.ts](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/sync/coalesce.ts). The coalesceChanges function accepts a cursor { rev, path? } and yields a stream of ChangeEntry objects ordered by (rev, path).

The implementation follows six distinct phases:

  1. Collect live mutations — Scan vfs_nodes for the highest rev per path touched after the cursor
  2. Collect tombstones — Scan vfs_changes where op = 'delete' for deletions at the highest rev per path
  3. Merge and pick highest revision — For each path, keep only the candidate with the greatest rev
  4. Apply ignore list — Drop paths matching ignore patterns (e.g., node_modules) before yielding
  5. Sort deterministically — Order remaining candidates by (rev, path) for safe checkpointing
  6. Materialize entries — Call materialiseChange to read current VFS state and emit ChangeEntry objects

The cursor-based streaming model guarantees that receivers can advance their fetch position after each batch. If a path is rewritten after the scan, its entry is simply omitted now and re-delivered later—preserving eventual convergence.

Conflict Resolution: Last Writer Wins

When two containers edit the same path concurrently, conflict resolution is handled through arrival order at the DO, not through merge logic. As documented in [docs/02_sync_protocol.md](https://github.com/cloudflare/computer/blob/main/docs/02_sync_protocol.md), the protocol applies batches in the order received.

The DO does not detect that the same path changed on both sides. Each container pushes its coalesced batch (one entry per path at the highest local revision). The later batch overwrites the earlier one. No error is produced; no three-way merge occurs. The final state equals the most recent ChangeEntry received.

This last-writer-wins semantics is a deliberate trade-off. It keeps the protocol stateless at the DO layer and avoids the complexity of operational transforms or CRDTs for filesystem data.

Idempotence and Duplicate Suppression

The DO's applyChanges routine implements idempotence through duplicate detection. An alreadyApplied check drops entries that were previously processed, making re-application cheap and safe. This matters for reliability: cursors can retry without corrupting state.

Practical Examples

Concurrent Writes to the Same File

import { Workspace } from "@cloudflare/computer";

const ws = new Workspace(/* workspace config */);

// Container A writes first
await ws.fs.writeFile("/shared/config.json", JSON.stringify({ version: "A" }));
await ws.push(); // pushes rev = 1

// Container B writes concurrently
await ws.fs.writeFile("/shared/config.json", JSON.stringify({ version: "B" }));
await ws.push(); // pushes rev = 2, arrives later at DO

// Final state reflects B's write
await ws.pull();
const config = await ws.fs.readFile("/shared/config.json", "utf8");
console.log(JSON.parse(config).version); // → "B"

Each push internally streams coalesceChanges, yielding one entry per path. The higher-revision entry from Container B overwrites Container A's entry at the DO.

Direct Coalescing API

import { openDatabase } from "@cloudflare/dofs";
import { coalesceChanges } from "@cloudflare/dofs/sync/coalesce";

const db = await openDatabase("workspace.db");

// Simulate rapid successive writes
await db.exec(`INSERT INTO vfs_nodes (inode, rev, path) VALUES (1, 1, '/data.txt')`);
await db.exec(`INSERT INTO vfs_nodes (inode, rev, path) VALUES (1, 2, '/data.txt')`);
await db.exec(`INSERT INTO vfs_nodes (inode, rev, path) VALUES (1, 3, '/data.txt')`);

// Coalesce from the beginning
for await (const entry of coalesceChanges(db, { rev: 0, path: null })) {
  console.log(entry.rev, entry.path); // Only entry: rev = 3, /data.txt
}

Despite three mutations, coalesceChanges yields a single entry at the highest revision, demonstrating the per-path coalescing rule.

Excluding Generated Directories

for await (const entry of coalesceChanges(db, { rev: 0, path: null }, {
  ignore: ["node_modules/**", ".git/**", "*.log"]
})) {
  // Entries matching these patterns never cross the wire
}

The ignore option prevents large generated artifacts from consuming bandwidth and cursor positions.

Key Source Files and Their Roles

File Purpose
[packages/dofs/src/sync/coalesce.ts](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/sync/coalesce.ts) Core coalescing algorithm that compresses mutations per path
[docs/02_sync_protocol.md](https://github.com/cloudflare/computer/blob/main/docs/02_sync_protocol.md) Documents cursor semantics, streaming protocol, and conflict resolution behavior
[packages/rpc/src/sync-driver.ts](https://github.com/cloudflare/computer/blob/main/packages/rpc/src/sync-driver.ts) RPC layer that streams coalesced batches between container and DO
[docs/01_vfs.md](https://github.com/cloudflare/computer/blob/main/docs/01_vfs.md) Describes the SQLite-backed VFS schema powering revision tracking

Summary

  • Change coalescing collapses all mutations per path to the highest revision, minimizing sync payload
  • Conflict resolution follows last-writer-wins: the DO applies batches in arrival order without merge logic
  • Cursor-based streaming enables deterministic checkpointing and safe batch retries
  • Idempotence through duplicate detection ensures reliable re-application of changes
  • Ignore patterns filter noise paths before they reach the wire

The Computer sync protocol trades merge complexity for operational simplicity, achieving eventual consistency through ordered, coalesced, and idempotent change streams.

Frequently Asked Questions

Does the Computer sync protocol detect conflicts between concurrent writers?

No. The protocol does not detect that the same path was modified on multiple containers. Each container pushes its coalesced batch independently. The DO applies entries in arrival order, so the later batch simply overwrites the earlier one. This last-writer-wins semantics is intentional and documented in the protocol specification.

What happens if a file is modified while coalesceChanges is running?

The algorithm reads from current VFS state rather than historical snapshots. If a path is rewritten after the scan phase, its ChangeEntry will be omitted from the current batch. The next sync cursor will pick up the newer revision, ensuring eventual consistency without blocking writers.

How does the ignore pattern option affect conflict resolution?

Ignore patterns filter paths before they enter the coalesced stream. If a path matches an ignore pattern, it is never sent to the DO and therefore never participates in conflict resolution. This is useful for ephemeral directories like node_modules that should remain local to each container.

Can the same ChangeEntry be applied multiple times safely?

Yes. The DO's applyChanges routine checks an alreadyApplied set and silently drops duplicates. This idempotence allows receivers to retry cursor positions without corrupting filesystem state, making the protocol resilient to network interruptions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →