On this page

Branching the record and joining back๏ƒ

๐Ÿ”ต Expert ยท Lesson 23

BRANCHING, AND WHY A JOIN CAN REFUSE. Research lanes are melder's branches: parallel lines of versions over one world, anchored to each other, finished by joining. It looks like git until three places where it deliberately is not.

A BRANCH STARTS EMPTY research_create_lane("try-a", attach_to="default", attach_at_spell_id=v1) Anchoring records ANCESTRY ONLY. No node is copied, no node is moved, and the anchor lane is untouched - the new lane begins with zero versions and a memory of where it came from. That is the opposite of a git branch, which starts AT a commit and carries the whole history with it.

ANCHORING IS ALL OR NOTHING attach_to and attach_at_spell_id must be supplied together; exactly one of them raises before the lock is even taken. There is no "anchor to whatever the tip is" shorthand, because the anchor is the one fact the join later reasons about and a shorthand would let you record a different anchor than you meant.

THE DEFAULT LANE TYPE IS experiment, NOT development Omit lane_type and you get experiment - deliberately NOT the default lane's development. A freshly cut lane is an experiment until someone says otherwise, so the safe classification is the one you get by not thinking about it.

AND A JOIN IS DIVERGENCE-AWARE, WHICH IS THE WHOLE LESSON research_join("try-a", into="default") The CLEAN path requires two things at once: the source is anchored onto the receiver, AND the receiver's tip is STILL AT that anchor. Anything else - the receiver moved on, a foreign anchor, no anchor at all - is a DIVERGENT join and refuses unless you pass force=True. The refusal NAMES BOTH TIPS, so you are told exactly what drifted apart rather than being handed a conflict to hunt.

force=True IS NOT A MERGE. It is an EXPLICIT SUPERSEDE, and melder is blunt about the difference: "Reconciliation-by-content is not a join concern: compose in the codegen workshop, register the multi-parent result, then join." Merging content is work you do with code, in a room, and the record only ever books the outcome. A version-control system that merges FOR you is guessing, and this one refuses to guess about your source.

COLLAPSE CHOOSES HOW MUCH LINE COMES WITH IT collapse=False (default) fold the source's FULL line in collapse=True move only the TIP, leaving the rest readable in the joined source lane Residence transfers with whatever moves, and the source is marked joined - it accepts no further work afterwards. A NEW VERSION IS BOUND THE WAY THE FIRST ONE WAS. The receiver is moved by staging RuleV2 on RuleV1's index, so both are bound under one string category and binding name, ('rule', 'branch'): an index keeps its first version's binding, and bind_inactive refuses a version bound any other way (2026-10-25).

Before you run๏ƒ

Use the Expert guide for prerequisite concepts. Run from a checkout with Melder installed and Python 3.14 free-threading selected. The collection download includes the level's local helper modules.

Run the saved script๏ƒ

python UX_and_AIX_experiences/04_expert/23_branching_the_record_and_joining_back.py
py -3.14t UX_and_AIX_experiences/04_expert/23_branching_the_record_and_joining_back.py

Download this collection ยท Source on GitHub

Public surface๏ƒ

research_create_lane (anchored, half-anchored and refused), research_heads, research_join with and without force, Conduit.bind_inactive to move the receiver, and the lane payload create_lane returns (a lane describe() snapshot: lane_type, anchor, nodes)

Code๏ƒ

  1"""
  2TIER: expert (23)
  3GOAL: BRANCHING, AND WHY A JOIN CAN REFUSE. Research lanes are melder's
  4      branches: parallel lines of versions over one world, anchored to
  5      each other, finished by joining. It looks like git until three
  6      places where it deliberately is not.
  7
  8      A BRANCH STARTS EMPTY
  9        research_create_lane("try-a", attach_to="default",
 10                             attach_at_spell_id=v1)
 11      Anchoring records ANCESTRY ONLY. No node is copied, no node is
 12      moved, and the anchor lane is untouched - the new lane begins with
 13      zero versions and a memory of where it came from. That is the
 14      opposite of a git branch, which starts AT a commit and carries the
 15      whole history with it.
 16
 17      ANCHORING IS ALL OR NOTHING
 18      `attach_to` and `attach_at_spell_id` must be supplied together;
 19      exactly one of them raises before the lock is even taken. There is
 20      no "anchor to whatever the tip is" shorthand, because the anchor is
 21      the one fact the join later reasons about and a shorthand would let
 22      you record a different anchor than you meant.
 23
 24      THE DEFAULT LANE TYPE IS `experiment`, NOT `development`
 25      Omit `lane_type` and you get `experiment` - deliberately NOT the
 26      default lane's `development`. A freshly cut lane is an experiment
 27      until someone says otherwise, so the safe classification is the one
 28      you get by not thinking about it.
 29
 30      AND A JOIN IS DIVERGENCE-AWARE, WHICH IS THE WHOLE LESSON
 31        research_join("try-a", into="default")
 32      The CLEAN path requires two things at once: the source is anchored
 33      onto the receiver, AND the receiver's tip is STILL AT that anchor.
 34      Anything else - the receiver moved on, a foreign anchor, no anchor
 35      at all - is a DIVERGENT join and refuses unless you pass
 36      `force=True`. The refusal NAMES BOTH TIPS, so you are told exactly
 37      what drifted apart rather than being handed a conflict to hunt.
 38
 39      `force=True` IS NOT A MERGE. It is an EXPLICIT SUPERSEDE, and
 40      melder is blunt about the difference: "Reconciliation-by-content is
 41      not a join concern: compose in the codegen workshop, register the
 42      multi-parent result, then join." Merging content is work you do
 43      with code, in a room, and the record only ever books the outcome.
 44      A version-control system that merges FOR you is guessing, and this
 45      one refuses to guess about your source.
 46
 47      COLLAPSE CHOOSES HOW MUCH LINE COMES WITH IT
 48        collapse=False (default)  fold the source's FULL line in
 49        collapse=True             move only the TIP, leaving the rest
 50                                  readable in the joined source lane
 51      Residence transfers with whatever moves, and the source is marked
 52      joined - it accepts no further work afterwards.
 53      A NEW VERSION IS BOUND THE WAY THE FIRST ONE WAS. The receiver is
 54      moved by staging RuleV2 on RuleV1's index, so both are bound
 55      under one string category and binding name, ('rule', 'branch'):
 56      an index keeps its first version's binding, and bind_inactive
 57      refuses a version bound any other way (2026-10-25).
 58SURFACE EXERCISED: research_create_lane (anchored, half-anchored and
 59                   refused), research_heads, research_join with and
 60                   without force, Conduit.bind_inactive to move the
 61                   receiver, and the lane payload create_lane returns
 62                   (a lane describe() snapshot: lane_type, anchor, nodes)
 63VERIFY: RUN GREEN 2026-10-25 on 3.14t, GIL off and on; asserts are the
 64        contract. The SURFACE line was corrected 2026-08-05, and both
 65        versions moved under one binding 2026-10-25.
 66"""
 67import melder as md
 68
 69
 70FRAME = "branch-world"
 71
 72
 73class RuleV1:
 74    def __init__(self) -> None:
 75        self.tag = "v1"
 76
 77
 78class RuleV2:
 79    def __init__(self) -> None:
 80        self.tag = "v2"
 81
 82
 83def main() -> None:
 84    # Record and custody live BEFORE the world, so binds auto-record.
 85    crystallizer = md.Crystallizer()
 86    crystallizer.activate(
 87        md.CrystallizerConfigurationBuilder().with_defaults().activate(),
 88    )
 89    research = md.MutationResearch()
 90    configuration = research.create_configuration()
 91    configuration.with_defaults().activate()
 92    research.activate(configuration)
 93
 94    # A RECORDED WORLD MUST BE BORN CONFIGURED. With custody active, a
 95    # dynamic conjure REFUSES if any bind ran before the configuration
 96    # was finalized - the profile record and default bootstrap would
 97    # otherwise durably persist binds made against unsettled config.
 98    spellbook_configuration = (
 99        md.SpellbookConfiguration(FRAME).with_defaults().finalize()
100    )
101    book = md.Spellbook(aetheric_frame=FRAME,
102                        configuration=spellbook_configuration)
103    # THE FRAME POSTURE GOES BEFORE THE BIND. A plain bind auto-declares
104    # into research only `if self._is_dynamic_posture()` - a frame-level
105    # question answerable before conjure. Bind into a not-yet-dynamic
106    # frame and nothing is declared, silently, because research
107    # bookkeeping never gates a bind. There would then be no node in the
108    # default lane to anchor a branch at.
109    book.configure_aether_frame(
110        system_state="dynamic",
111        disposal=None,
112        disposal_method_names=None,
113        rift_enabled=True,
114        ai_native=True,
115    )
116    v1 = book.bind(spell=RuleV1, spellframe="rule", existence="unique",
117                   permissions="create", binding_name="branch")
118    conduit = book.conjure(name="branch-root")
119    print("v1 recorded on the default lane:", v1[:12], "...")
120
121    nexus = md.Nexus()
122    system_configuration = nexus.create_configuration()
123    system_configuration.with_rift_creation_enabled(True)
124    system_configuration.with_allowed_target_frame_names([FRAME])
125    nexus.activate(system_configuration)
126    rift_configuration = nexus.create_rift_configuration()
127    rift_configuration.with_space_type("codegen")
128    rift = nexus.create_rift(configuration=rift_configuration,
129                             rift_name="brancher")
130    rift.mark_active()
131    rift.create_frame_link(FRAME)
132    commands = rift.space.command_system
133
134    # ANCHORING IS ALL OR NOTHING - and it refuses before doing anything.
135    try:
136        commands.research_create_lane("half-anchored", attach_to="default")
137        raise AssertionError("expected a refusal: anchor is all-or-nothing")
138    except ValueError as error:
139        print()
140        print("attach_to without attach_at_spell_id refused -")
141        print("  ", error)
142
143    # CUT A BRANCH, ANCHORED. It starts EMPTY.
144    clean = commands.research_create_lane(
145        "clean-branch",
146        attach_to="default",
147        attach_at_spell_id=v1,
148        reason="a branch that will join cleanly",
149    )
150    print()
151    print("created 'clean-branch':")
152    print("   lane_type   :", clean["lane_type"], " (default is experiment)")
153    print("   anchor_spell:", str(clean["anchor_spell_id"])[:12], "...")
154    print("   nodes       :", len(clean["nodes"]),
155          " <- EMPTY: ancestry only, nothing copied")
156    assert clean["lane_type"] == "experiment"
157    assert clean["anchor_spell_id"] == v1
158    assert len(clean["nodes"]) == 0
159
160    # A SECOND BRANCH FROM THE SAME POINT. This one will go stale.
161    commands.research_create_lane(
162        "stale-branch",
163        attach_to="default",
164        attach_at_spell_id=v1,
165        reason="a branch the receiver will move away from",
166    )
167    heads = commands.research_heads()
168    print()
169    print("research_heads() ->", len(heads), "open lanes")
170
171    # THE CLEAN JOIN. The receiver's tip is still exactly at the anchor.
172    commands.research_join("clean-branch", into="default",
173                           reason="nothing diverged")
174    print()
175    print("join('clean-branch' -> 'default') accepted")
176    print("  source anchored onto the receiver AND the receiver still at")
177    print("  the anchor: the only arrangement that is not a supersede")
178
179    # NOW MOVE THE RECEIVER. A new version lands on the default lane, so
180    # 'stale-branch' is anchored at a point the receiver has left. It is
181    # bound the way v1's index was - ('rule', 'branch') - or it is refused.
182    v2 = conduit.bind_inactive(
183        spell=RuleV2,
184        spell_index=conduit.get_spell_by_spell_id(v1).spell_index,
185        spellframe="rule",
186        binding_name="branch",
187        existence="unique",
188        permissions="create",
189    )
190    print()
191    print("v2 recorded; the default lane moved on:", v2[:12], "...")
192
193    # THE DIVERGENT JOIN REFUSES, AND THE REFUSAL NAMES THE TIPS.
194    try:
195        commands.research_join("stale-branch", into="default")
196        raise AssertionError("expected a refusal: divergent join")
197    except RuntimeError as error:
198        print()
199        print("join('stale-branch' -> 'default') refused -")
200        print("  ", str(error)[:150])
201        print("  it names BOTH tips, so you are told what drifted rather")
202        print("  than handed a conflict to go and find")
203
204    # force=True IS A SUPERSEDE, NOT A MERGE.
205    commands.research_join("stale-branch", into="default", force=True,
206                           reason="explicit supersede, content already "
207                                  "reconciled elsewhere")
208    print()
209    print("join(..., force=True) accepted - and note what it is NOT:")
210    print("  it did not merge any SOURCE. Reconciliation by content is")
211    print("  work you do in the codegen workshop; you register the")
212    print("  multi-parent result and THEN join. The record books an")
213    print("  outcome and never guesses at your code")
214
215    # THE JOINED LANES ARE CLOSED. A join is terminal for the source.
216    final_heads = commands.research_heads()
217    print()
218    print("open lanes now:", len(final_heads), " (joined sources closed)")
219    assert len(final_heads) <= len(heads)
220
221    print()
222    print("a branch starts EMPTY and remembers where it came from")
223    print("a clean join is anchored AND un-drifted; anything else is a")
224    print("supersede you have to ask for by name")
225
226
227if __name__ == "__main__":
228    main()

Check the outcome๏ƒ

The script contains its own assertions or demonstrated refusal paths. Run it to evaluate those checks against your installed version. The code above is taken directly from the saved file; no run output is invented here.

More expert examples ยท Level guide

API contracts๏ƒ