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 }
}
}
| Policy | Description |
|---|---|
linear: true | Ops must arrive in order; duplicates are dropped |
linear: false | CRDT mode — concurrent ops interleaved by timestamp |
requireSigning | Ops without a valid Ed25519 signature are rejected |