CRDT Primitives
Muse ships six lattice-based convergent data types in
muse/core/crdts/. Each satisfies the three lattice laws and
integrates with CRDTPlugin to give any domain automatic,
provably-correct merge semantics.
The three lattice laws
A CRDT is a data structure paired with a merge (join) operation that satisfies three algebraic properties. When these hold, any set of replicas that exchange states in any order will eventually reach the same value — convergence without coordination.
All six primitives in muse/core/crdts/ are proven to
satisfy these laws. Their merge() methods are the join
operation. State grows monotonically — you can add information but never
remove it from a CRDT state directly (deletion is encoded as a tombstone
or carried in an add-wins set).
muse/core/crdts/ and are imported
from muse.core.crdts. They are pure Python with no external
dependencies. Each exports a State TypedDict and a
merge(a, b) function.
Six primitives
| Type | Module | Use case |
|---|---|---|
| VectorClock | vector_clock.py | Causal ordering; happens-before detection across agents |
| LWWRegister | lww_register.py | Last-writer-wins scalar; safe for isolated, rarely-contested fields |
| ORSet | or_set.py | Add-wins set; tracks tags, labels, memberships |
| RGA | rga.py | Replicated Growable Array; ordered sequences with position stability |
| AWMap | aw_map.py | Add-wins map; nested key-value state with per-key CRDT values |
| GCounter | gcounter.py | Grow-only counter; download counts, event tallies, play counts |
VectorClock
A map from node ID to logical timestamp. Tracks causal order across distributed agents without a central clock. The merge is component-wise max — two independent clocks converge to the least upper bound of both.
State: dict[str, int] — node_id → logical timestamp
Used in: Harmony audit log; agent coordination DAG; commit causality in multi-agent branches.
LWWRegister
Last-writer-wins register. Stores a value paired with a timestamp; merge keeps the value with the highest timestamp. Simple but lossy — the losing write is discarded. Safe for rarely-contested fields where the latest value is always correct (e.g. a display name, a status flag).
State: {"value": Any, "timestamp": float}
Used in: MIDI domain — tempo, time signature, key signature (single authoritative value per song position).
ORSet
Observed-Remove Set. Each add operation tags the element with a unique token. Removal records the token, not just the element. When a remove and an add are concurrent, add wins — the element stays if any un-removed add token exists. This is the correct semantic for tags, labels, and membership where concurrent additions should be preserved.
State: {"elements": {val: {token, ...}}, "tombstones": {token, ...}}
Used in: Proposal labels, issue labels, repo topics. Concurrent tag additions from multiple agents all survive.
RGA
Replicated Growable Array. An ordered sequence CRDT where each element
carries a unique identifier (site + sequence number). Insertions are
positioned relative to a predecessor element ID — positions remain stable
as other elements are inserted concurrently. Deletions leave tombstones;
value() filters them out for the live sequence.
State: list of RGANode(id, value, tombstone)
Used in: Ordered MIDI note sequences; comment threads where insertion order must be preserved across concurrent replies.
AWMap
Add-Wins Map. A map where each value is itself a CRDT. Concurrent writes to the same key are merged using the value CRDT's own merge function. Concurrent removal and update of the same key resolves as add-wins — the key survives. The map grows monotonically; keys can only be removed with an explicit tombstone.
State: {"entries": {key: CRDTState}, "tombstones": {key}}
Used in: Address-keyed domain state (e.g., per-note MIDI attributes where the address is pitch + onset; per-symbol code metadata where the address is the symbol path).
GCounter
Grow-only counter. Each node increments its own slot; the global value is the sum of all slots. Merge is component-wise max. Cannot decrement — if you need a PN-counter (positive/negative), compose two GCounters.
State: dict[str, int] — node_id → local count
Used in: Mist download counts, repo star counts, play counts — any metric that only grows and must be consistent across distributed replicas.
CRDTPlugin — integrating with your domain
If your domain's merge logic can be expressed entirely as CRDT state
transitions, implement CRDTPlugin instead of (or alongside)
MuseDomainPlugin. The engine calls to_crdt_state()
before merge and from_crdt_state() after — your domain object
never needs to know about the CRDT internals.
class CRDTPlugin(MuseDomainPlugin): def crdt_schema(self) -> list[CRDTDimensionSpec]: """Declare the CRDT type used for each dimension.""" def join(self, a: CRDTSnapshotManifest, b: CRDTSnapshotManifest) -> CRDTSnapshotManifest: """Lattice join — commutative, associative, idempotent.""" def to_crdt_state(self, snapshot: StateSnapshot) -> CRDTSnapshotManifest: """Lift a plain snapshot into CRDT state representation.""" def from_crdt_state(self, crdt: CRDTSnapshotManifest) -> StateSnapshot: """Materialise a CRDT state back to a plain snapshot."""
The CRDT plugin path bypasses Harmony's three-tier conflict resolution —
convergence is mathematical, not heuristic. Use it when your domain's
conflict semantics are provably expressible as a lattice join. When in
doubt, start with MuseDomainPlugin and add CRDTPlugin
later.
When not to use CRDTs
| Scenario | Use instead |
|---|---|
| Strong consistency required | Harmony policy with merge_mode: three-way and human escalation |
| Complex semantic invariants (e.g. "no two notes at the same pitch and onset") | MuseDomainPlugin.merge() with explicit invariant checks post-merge |
| Deletion must win over concurrent addition | Custom tombstone logic in MuseDomainPlugin |
| History must be auditable and reversible | TypedDelta op log via AddressedMergePlugin — ops are preserving, not lossy |
| State is too large for in-memory CRDT (millions of elements) | Sharded domain with per-shard CRDTs + coordination-layer fan-out |