Collaboration & Sync

Peer synchronization, CRDT reconciliation, multi-repo federation, and distributed team workflows.

Overview

Trellis is built for distributed teams from the ground up. The sync layer implements a have→want→ops→ack protocol with support for both linear (sequential) and CRDT (concurrent) branch modes, over HTTP or WebSocket transports.

Sync CLI

trellis sync status                       # Local sync state
trellis sync reconcile --remote <path>    # Reconcile with another local repo

How Sync Works

Every sync session follows a four-step handshake:

Engine A sends 'have' { heads: { main: 'h42' }, opCount: 42 }
  → Engine B compares to its own heads
  → B sends 'want' { afterHash: 'h38' }
  → A sends 'ops' [ op39, op40, op41, op42 ]
  → B integrates new ops
  → B sends 'ack' { integrated: ['h39', …] }

In linear mode, B simply filters out duplicates and appends. In CRDT mode, B runs the full reconciler to interleave ops by timestamp and detect conflicts.

CRDT Reconciler

The standalone reconciler works without a network connection — useful for merging local branches or resolving import conflicts:

import { reconcile } from "trellis/sync";

const result = reconcile(localOps, remoteOps);
result.merged; // Op[] — interleaved by timestamp
result.forkPoint; // string — last common op hash
result.conflicts; // string[] — files with divergent changes

SyncEngine

The full SyncEngine wires a transport to your op store and handles the protocol automatically:

import { MemoryTransport, SyncEngine } from "trellis/sync";

const transport = new MemoryTransport("peer-a", "Alice");
const engine = new SyncEngine({
  localPeerId: "peer-a",
  transport,
  getLocalOps: () => ops,
  onOpsReceived: (newOps) => {
    /* integrate */
  },
  branchPolicy: { linear: false }, // CRDT mode
});

await engine.pushTo("peer-b");
await engine.pullFrom("peer-b");
engine.reconcileWith(remoteOps);

Transport Options

HTTP Transport

For async sync over a REST endpoint:

import { HttpSyncTransport } from "trellis/sync";

const transport = new HttpSyncTransport("peer-a");
transport.addPeer("peer-b", "http://192.168.1.10:4200");

WebSocket Transport

For real-time collaborative sessions:

import { WebSocketSyncTransport } from "trellis/sync";

const wsTransport = new WebSocketSyncTransport("peer-a");
await wsTransport.connect("peer-b", "ws://192.168.1.10:4201");

Memory Transport

For testing or in-process peer scenarios:

import { MemoryTransport } from "trellis/sync";

const transport = new MemoryTransport("peer-a", "Alice");

Multi-Repo Federation

Link separate repositories so knowledge and references can cross project boundaries:

import { formatCrossRepoRef, MultiRepoManager } from "trellis/sync";

const repoManager = new MultiRepoManager(kernel);

// Link a remote repository under an alias
await repoManager.linkRepo("backend", "/path/to/backend-api", "Backend service");

// Create a cross-repository relationship
await repoManager.addCrossRepoLink("proj:frontend", "dependsOn", "backend", "lib:api-client");
// Creates: proj:frontend --dependsOn--> @backend:lib:api-client

// Discover what references an entity in a linked repo
const refs = repoManager.findReferencesTo("backend", "lib:api-client");

Cross-repo references use a @alias:entity-id format. The formatCrossRepoRef helper constructs these:

const ref = formatCrossRepoRef("backend", "lib:api-client");
// → '@backend:lib:api-client'

Branch Policies

Branch policies govern how ops are integrated when syncing. Set them per-branch in .trellis/config.json:

{
  "branchPolicies": {
    "main": { "linear": true, "requireSigning": true },
    "feature/*": { "linear": false }
  }
}
PolicyDescription
linear: trueOps must arrive in order; duplicates are dropped
linear: falseCRDT mode — concurrent ops interleaved by timestamp
requireSigningOps without a valid Ed25519 signature are rejected