Replication
Replication keeps your app's shared state — counters, lists, maps, documents — in sync across devices, even when people edit at the same time or spend time offline. kuilt-crdt gives you data structures that merge those edits automatically, so no change is quietly lost.
Think of each update as a quilt patch: patches can arrive in different orders and still sew into the same final pattern.
The technical name for these structures is CRDTs (Conflict-free Replicated Data Types): any two peers that have seen the same set of updates will always hold the same value, regardless of arrival order.
Use replication when you want convergence without a central coordinator. If your feature needs strict, globally ordered decisions instead (like turn order in a game), use Consensus for that part.
kuilt-crdt provides fourteen types, grouped by what they model.
kuilt-crdt depends on nothing else in kuilt — not even kuilt-core. Add it to any project on its own and replicate over whatever transport you already have. Live replication over a Seam (via Quilter) is opt-in, not required.
Pick by what you're building
What you're building | Type |
|---|---|
Event tally, vote count, metric |
|
Like/dislike, upvote/downvote |
|
Collaborative tag cloud, ever-growing log |
|
Collaborative labels where items can be archived |
|
Collaborative labels where items can be re-added |
|
Shared status field (last writer wins) |
|
Shared status field (surface conflicts) |
|
Online presence / roster (key → last-write value) |
|
Presence / roster (key → nested CRDT value) |
|
Seat or inventory reservation (can't oversell) |
|
Collaborative text / ordered list |
|
JSON document sync |
|
Ephemeral presence (cursors, typing indicators) |
|
Causal stability / building your own CRDT |
|
Structure at a glance
Group | Types | Convergence property |
|---|---|---|
Counters |
| Per-replica monotone internals; deterministic integer result after merge |
Sets |
| Set union / observe-remove semantics |
Registers |
| Last-write-wins or multi-value concurrent conflict |
Maps |
| Key-level LWW or ORSet-keyed map |
Sequences |
| Ordered list with stable unique ids |
Composite |
| Recursive JSON document — ORMap objects, RGA arrays, MVRegister leaves |
Ephemeral |
| Per-replica presence slot, clock-ordered, with caller-driven TTL eviction |
Causal primitives |
| Causal-context-based remove/add reasoning |
Using the types without kuilt
These types are plain serializable value objects, and kuilt-crdt declares no dependency on kuilt-core or any other kuilt module — you can add it to a project entirely on its own. You do not need a Seam, a Loom, or any other kuilt module to use them. Apply updates by calling .piece() directly; serialize with kotlinx.serialization; ship the bytes over any transport you already have.
Quilter automates this over a kuilt Seam — but it is optional. If you already have a messaging layer, wire the types to it yourself.
Live replication
Quilter<S> runs over a Seam and keeps one replica live: it ships deltas to all peers as you apply updates, and merges inbound deltas as they arrive. state is a StateFlow<S> — always the current converged value.
See Quilter for usage and the MuxSeam multiplexing pattern that lets multiple replicators share one transport.
Serialization
Every CRDT type is @Serializable. Wire transport (CBOR by default, via Quilter) and JSON round-trips both work. Each type's serializer is accessible via T.serializer() or T.serializer(elementSerializer).
How it works (CRDT basics)
The Quilted interface
Every CRDT implements Quilted<S>:
piece is the join in the join-semilattice. Calling it with the same argument twice produces the same result as calling it once (idempotent). Order doesn't matter (commutative). Multiple calls can be grouped in any order (associative). These three laws guarantee convergence.
Delta state. Instead of shipping the entire current state on every update, CRDTs here emit a delta — a minimal patch that represents only what changed. Merging a delta into the current state advances it the same way merging the full state would. Quilter exploits this to send small delta messages over the wire and ship the full state only to late joiners.
The Patch wrapper
Mutations return a delta (a value of the same CRDT type) rather than directly mutating state. Apply it with piece: