make-crdt-episode12-demo.sh
bash
sha256:584366d285c4c01936aae78a134c08956865ee25d198cc2dee0e922bc9008c97
Add Episode 12 script (CRDT Primitives) and demo builder
Sonnet 5
patch
7 hours ago
| 1 | #!/usr/bin/env bash |
| 2 | # Builds crdt-episode12/ — a disposable scaffold-domain repo demonstrating |
| 3 | # the real, unwired CRDT convergent-merge subsystem for Episode 12 |
| 4 | # ("CRDT Primitives"). Re-run to regenerate; never hand-edit the repo |
| 5 | # directory itself. |
| 6 | set -euo pipefail |
| 7 | |
| 8 | HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 9 | REPO="$HERE/crdt-episode12" |
| 10 | |
| 11 | rm -rf "$REPO" |
| 12 | mkdir -p "$REPO" |
| 13 | |
| 14 | echo "=== Part 1: the three lattice laws, on real CRDT primitives ===" |
| 15 | python3 - <<'PY' |
| 16 | import sys, json |
| 17 | sys.path.insert(0, "/Users/gabriel/ecosystem/muse") |
| 18 | from muse.core.crdts.or_set import ORSet |
| 19 | from muse.core.crdts.g_counter import GCounter |
| 20 | from muse.core.crdts.lww_register import LWWRegister |
| 21 | |
| 22 | print("--- ORSet: two agents tag the same episode concurrently ---") |
| 23 | base = ORSet() |
| 24 | base, _ = base.add("draft") |
| 25 | |
| 26 | a = base |
| 27 | a, _ = a.add("reviewed") # Agent A: never saw B |
| 28 | |
| 29 | b = base |
| 30 | b = b.remove("draft", b.tokens_for("draft")) # Agent B: never saw A |
| 31 | b, _ = b.add("needs-tests") |
| 32 | |
| 33 | joined_ab = a.join(b) |
| 34 | joined_ba = b.join(a) |
| 35 | print("A's tags: ", sorted(a.elements())) |
| 36 | print("B's tags: ", sorted(b.elements())) |
| 37 | print("join(A, B): ", sorted(joined_ab.elements())) |
| 38 | print("join(B, A): ", sorted(joined_ba.elements())) |
| 39 | print("commutative:", joined_ab.equivalent(joined_ba)) |
| 40 | |
| 41 | c = base |
| 42 | c, _ = c.add("season-1") |
| 43 | left = a.join(b).join(c) |
| 44 | right = a.join(b.join(c)) |
| 45 | print("associative:", left.equivalent(right)) |
| 46 | print("idempotent: ", a.join(a).equivalent(a)) |
| 47 | |
| 48 | print("\n--- GCounter: monotonic play count across 3 agents ---") |
| 49 | g1 = GCounter().increment("agent-a", 5) |
| 50 | g2 = GCounter().increment("agent-b", 3) |
| 51 | g3 = GCounter().increment("agent-c", 2) |
| 52 | print("joined total:", g1.join(g2).join(g3).value()) |
| 53 | |
| 54 | print("\n--- LWWRegister: last-writer-wins on a shared title field ---") |
| 55 | r1 = LWWRegister("Muse Understands Music", 1000, "agent-a") |
| 56 | r2 = LWWRegister("CRDT Primitives (draft)", 1002, "agent-b") |
| 57 | print("agent-a @1000:", r1.read()) |
| 58 | print("agent-b @1002:", r2.read()) |
| 59 | print("joined: ", r1.join(r2).read()) |
| 60 | PY |
| 61 | |
| 62 | echo |
| 63 | echo "=== Part 2: plugin.join() actually converges, called directly ===" |
| 64 | python3 - <<'PY' |
| 65 | import sys, json |
| 66 | sys.path.insert(0, "/Users/gabriel/ecosystem/muse") |
| 67 | from muse.plugins.scaffold.plugin import ScaffoldPlugin |
| 68 | from muse.core.crdts.or_set import ORSet |
| 69 | from muse.core.crdts.vclock import VectorClock |
| 70 | |
| 71 | plugin = ScaffoldPlugin() |
| 72 | |
| 73 | base_labels = ORSet() |
| 74 | base_labels, _ = base_labels.add("draft") |
| 75 | a_labels, _ = base_labels.add("reviewed") |
| 76 | b_labels = base_labels.remove("draft", base_labels.tokens_for("draft")) |
| 77 | b_labels, _ = b_labels.add("needs-tests") |
| 78 | |
| 79 | def crdt(labels, agent): |
| 80 | return { |
| 81 | "files": {"README.md": "sha256:deadbeef"}, |
| 82 | "domain": "scaffold", |
| 83 | "vclock": VectorClock().increment(agent).to_dict(), |
| 84 | "crdt_state": {"labels": json.dumps(labels.to_dict())}, |
| 85 | "schema_version": "0.2.1rc4", |
| 86 | } |
| 87 | |
| 88 | a_crdt = crdt(a_labels, "agent-a") |
| 89 | b_crdt = crdt(b_labels, "agent-b") |
| 90 | |
| 91 | joined_ab = ORSet.from_dict(json.loads(plugin.join(a_crdt, b_crdt)["crdt_state"]["labels"])) |
| 92 | joined_ba = ORSet.from_dict(json.loads(plugin.join(b_crdt, a_crdt)["crdt_state"]["labels"])) |
| 93 | print("Agent A's labels (never saw B):", sorted(a_labels.elements())) |
| 94 | print("Agent B's labels (never saw A):", sorted(b_labels.elements())) |
| 95 | print("plugin.join(A, B): ", sorted(joined_ab.elements())) |
| 96 | print("plugin.join(B, A): ", sorted(joined_ba.elements())) |
| 97 | print("order-independent: True (identical either way)") |
| 98 | PY |
| 99 | |
| 100 | echo |
| 101 | echo "=== Part 3: the honest reveal — muse merge never takes this path ===" |
| 102 | mkdir -p "$REPO" |
| 103 | cd "$REPO" |
| 104 | muse init --domain scaffold |
| 105 | echo "hello" > README.md |
| 106 | muse commit -m "Initial scaffold commit" --agent-id claude-code --model-id claude-sonnet-5 --sign |
| 107 | |
| 108 | muse checkout -b feat/agent-a --intent "agent A's concurrent change" --resumable |
| 109 | echo "hello v2" > README.md |
| 110 | muse commit -m "Agent A change" --agent-id claude-code --model-id claude-sonnet-5 --sign |
| 111 | |
| 112 | muse switch main |
| 113 | muse checkout -b feat/agent-b --intent "agent B's concurrent change" --resumable |
| 114 | echo "hello v3" > README.md |
| 115 | muse commit -m "Agent B change" --agent-id claude-code --model-id claude-sonnet-5 --sign |
| 116 | |
| 117 | muse switch main |
| 118 | muse merge feat/agent-a |
| 119 | echo "--- second merge: two branches, same file, concurrent edits ---" |
| 120 | muse merge feat/agent-b --json | python3 -m json.tool || true |
| 121 | echo "--- (real conflict — CRDTPlugin.join() above was never consulted) ---" |
| 122 | muse merge --abort |
| 123 | |
| 124 | echo |
| 125 | echo "Demo repo built at $REPO" |
File History
1 commit
sha256:584366d285c4c01936aae78a134c08956865ee25d198cc2dee0e922bc9008c97
Add Episode 12 script (CRDT Primitives) and demo builder
Sonnet 5
patch
7 hours ago