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 { SyncEngine, MemoryTransport } 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 { MultiRepoManager, formatCrossRepoRef } 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