kuilt Help

MVRegister

Holds one value, but surfaces conflicts instead of silently picking a winner. When two devices write at the same time, both values are kept. The next write — one that has seen both — resolves them back to one.

Converges to: the set of values written concurrently at the causal frontier — a single value when writes are sequential, multiple values when they are truly concurrent.

Merge rule

MVRegister uses a DotFun (a map from causal dots to values). Each set(replica, value) mints a new dot, superseding all dots the replica has already seen. On merge, a dot that was witnessed-and-superseded by one side is removed; a dot unknown to the other side is kept. The result is the set of values at the causal frontier.

Code examples

Set and read:

@Test fun setThenRead() { assertEquals(setOf("x"), MVRegister.empty<String>().set(a, "x").values) }

Concurrent writes keep both values:

@Test fun concurrentWritesKeepBothValues() { val base = MVRegister.empty<String>() val x = base.set(a, "x") val y = base.set(b, "y") assertEquals(setOf("x", "y"), x.piece(y).values) }

A later write resolves the conflict:

@Test fun aLaterWriteResolvesTheConflict() { val base = MVRegister.empty<String>() val conflicted = base.set(a, "x").piece(base.set(b, "y")) // {x, y} val resolved = conflicted.set(a, "z") // observes both, supersedes assertEquals(setOf("z"), resolved.values) }

When to use

MVRegister is appropriate when concurrent writes are possible and you want to surface the conflict explicitly rather than silently dropping a value. The application can inspect values.size > 1 to detect and present a conflict. For silent last-write-wins, use LWWRegister.

Last modified: 22 June 2026