On this page
The workstation canvas๏
๐ Advanced ยท Lesson 12
THE WORKSTATION - the room's binding canvas, and the last fixture in arc B. Every RiftSpace owns one, whatever its kind.
WHAT IT IS: a room-local scratchpad that holds things across steps. WHAT IT IS NOT, and this is the contract that matters:
"Stores only room-local bindings; it does not discover or resolve new targets from Melder/Nexus."
The workstation is NOT a resolver, NOT a registry, and NOT a second spellbook. It holds what you hand it. If you want something out of melder you get it through the command system (lesson 11) and then park it here. Keeping those two jobs apart is why the canvas can be wiped without touching the world.
THREE LOGICAL STORES, kept separate on purpose: objects bind_object(name, value) attributes bind_attribute(name, value) methods bind_method(name, value) Same name can live in two stores without collision, because get and release take a store= selector.
ONE ACTIVE TARGET AT A TIME set_target(name, store=...) select a saved binding as THE target get_target() read it back call_target(*args) invoke it (optionally re-binding the result straight back onto the canvas) clear_target() deselect without deleting the binding "At most one active target" is a deliberate ceiling. A canvas with many simultaneous targets is a canvas where "the target" stops meaning anything.
WEAK BY REQUEST, AND NEVER BY ACCIDENT weak_ref=True force weak storage weak_ref=False force strong storage weak_ref=None use the room-local default captured at creation And the rule worth carrying to the rest of the library:
"Explicit weak binding RAISES when the supplied value cannot be weak-referenced; IT NEVER SILENTLY DEGRADES TO STRONG STORAGE."
That is the same honesty you met at validate() in lesson 06, which raises instead of returning False. Melder would rather fail loudly than quietly give you something adjacent to what you asked for. A silent degrade here would mean an object you believed was collectable is pinned for the life of the room - a leak that looks like correct code.
Before you run๏
Use the Advanced 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/03_advanced/12_the_workstation_canvas.py
py -3.14t UX_and_AIX_experiences/03_advanced/12_the_workstation_canvas.py
Public surface๏
md.Workstation via room.workstation - bind_object, bind_method, get, release, describe_bindings, set_target/get_target/clear_target, weak_ref semantics
Code๏
1"""
2TIER: advanced (12)
3GOAL: THE WORKSTATION - the room's binding canvas, and the last fixture
4 in arc B. Every RiftSpace owns one, whatever its kind.
5
6 WHAT IT IS: a room-local scratchpad that holds things across steps.
7 WHAT IT IS NOT, and this is the contract that matters:
8
9 "Stores only room-local bindings; it does not discover or resolve
10 new targets from Melder/Nexus."
11
12 The workstation is NOT a resolver, NOT a registry, and NOT a second
13 spellbook. It holds what you hand it. If you want something out of
14 melder you get it through the command system (lesson 11) and then
15 park it here. Keeping those two jobs apart is why the canvas can be
16 wiped without touching the world.
17
18 THREE LOGICAL STORES, kept separate on purpose:
19 objects bind_object(name, value)
20 attributes bind_attribute(name, value)
21 methods bind_method(name, value)
22 Same name can live in two stores without collision, because `get`
23 and `release` take a `store=` selector.
24
25 ONE ACTIVE TARGET AT A TIME
26 set_target(name, store=...) select a saved binding as THE target
27 get_target() read it back
28 call_target(*args) invoke it (optionally re-binding the
29 result straight back onto the canvas)
30 clear_target() deselect without deleting the binding
31 "At most one active target" is a deliberate ceiling. A canvas with
32 many simultaneous targets is a canvas where "the target" stops
33 meaning anything.
34
35 WEAK BY REQUEST, AND NEVER BY ACCIDENT
36 weak_ref=True force weak storage
37 weak_ref=False force strong storage
38 weak_ref=None use the room-local default captured at creation
39 And the rule worth carrying to the rest of the library:
40
41 "Explicit weak binding RAISES when the supplied value cannot be
42 weak-referenced; IT NEVER SILENTLY DEGRADES TO STRONG STORAGE."
43
44 That is the same honesty you met at validate() in lesson 06, which
45 raises instead of returning False. Melder would rather fail loudly
46 than quietly give you something adjacent to what you asked for. A
47 silent degrade here would mean an object you believed was
48 collectable is pinned for the life of the room - a leak that looks
49 like correct code.
50SURFACE EXERCISED: md.Workstation via room.workstation - bind_object,
51 bind_method, get, release, describe_bindings,
52 set_target/get_target/clear_target, weak_ref semantics
53VERIFY: rides the owner's 3.14t run; asserts are the contract.
54
55DOC DRIFT FOUND AND FIXED (2026-08-02): describe_bindings() used to
56document "a FOUR-KEY summary - `objects`, `attributes`, `methods` and
57`target_name` - always with all four keys present, so callers can index
58them" while RETURNING FIVE; the implementation also emits `target_store`.
59That one had teeth, because the docstring explicitly invited callers to
60rely on the count. Now documented as five, with `target_store` explained:
61it names WHICH store the active target came from, so a caller can
62round-trip it back through get(name, store=...).
63"""
64import melder as md
65
66
67class Greeter:
68 """A weak-referenceable object - unlike an int."""
69
70 def greet(self) -> str:
71 return "hello from the canvas"
72
73
74def main() -> None:
75 nexus = md.Nexus()
76 system_config = nexus.create_configuration()
77 system_config.with_rift_creation_enabled(True)
78 nexus.activate(system_config)
79
80 rift_config = nexus.create_rift_configuration()
81 rift_config.with_space_type("capability")
82 rift = nexus.create_rift(configuration=rift_config, rift_name="bench")
83 rift.mark_active()
84
85 workstation = rift.space.workstation
86 assert isinstance(workstation, md.Workstation)
87 assert workstation.owner_space_id == rift.space.space_id
88 print("workstation:", workstation.workstation_id)
89 print("owned by room:", workstation.owner_space_id == rift.space.space_id)
90
91 # THREE STORES. The same name in two stores is not a collision.
92 greeter = Greeter()
93 workstation.bind_object("subject", greeter)
94 workstation.bind_method("subject", greeter.greet)
95 print("bound 'subject' into two different stores")
96
97 from_objects = workstation.get("subject", store="objects")
98 from_methods = workstation.get("subject", store="methods")
99 assert from_objects is greeter
100 assert from_methods() == "hello from the canvas"
101 print("objects['subject'] is the instance; methods['subject'] is callable")
102
103 # THE READ DOOR. Ask the canvas what is on it.
104 summary = workstation.describe_bindings()
105 print("describe_bindings keys:", sorted(summary))
106 for store in ("objects", "attributes", "methods", "target_name"):
107 assert store in summary, store
108 assert "subject" in summary["objects"]
109 assert "subject" in summary["methods"]
110
111 # Five keys, and the docstring now says five. `target_store` names the
112 # store the active target came from - enough to round-trip it back
113 # through get(name, store=...).
114 assert set(summary) == {"objects", "attributes", "methods",
115 "target_name", "target_store"}
116 print("keys:", len(summary), "- documented and returned agree")
117
118 # ONE TARGET AT A TIME.
119 workstation.set_target("subject", store="methods")
120 assert workstation.get_target() is not None
121 print("target set from the methods store")
122
123 result = workstation.call_target()
124 assert result == "hello from the canvas"
125 print("call_target ->", result)
126
127 workstation.clear_target()
128 # Clearing deselects; it does not delete the binding underneath.
129 assert workstation.get("subject", store="methods") is not None
130 print("target cleared; the binding it pointed at is still there")
131
132 # WEAK BY REQUEST. A class instance can be weak-referenced.
133 workstation.bind_object("weak_subject", Greeter(), weak_ref=True)
134 print("weak binding accepted for a weak-referenceable object")
135
136 # ...and an int cannot. This RAISES rather than quietly storing it
137 # strongly, which is the whole point.
138 try:
139 workstation.bind_object("weak_number", 42, weak_ref=True)
140 raise AssertionError(
141 "expected a refusal - int cannot be weak-referenced"
142 )
143 except (TypeError, ValueError, RuntimeError) as error:
144 print("explicit weak binding refused:", type(error).__name__)
145
146 # No silent degrade means: it is not on the canvas at all.
147 after = workstation.describe_bindings()
148 assert "weak_number" not in after["objects"]
149 print("refused binding was NOT stored strongly as a fallback")
150
151 # release() takes it back off the canvas and hands it to you.
152 released = workstation.release("subject", store="objects")
153 assert released is greeter
154 assert "subject" not in workstation.describe_bindings()["objects"]
155 print("released 'subject' from objects; methods copy untouched:",
156 "subject" in workstation.describe_bindings()["methods"])
157
158 print()
159 print("the canvas holds; the command system resolves. separate jobs.")
160 print("weak when asked, never by accident - refuse instead of degrade")
161
162
163if __name__ == "__main__":
164 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.