On this page
Campaigns the why that rides along๏
๐ต Expert ยท Lesson 15
CAMPAIGNS - the WHY, carried without anyone remembering to carry it. Expert 14 built a subsystem and left one argument dangling: research_group_history(group_id, campaign=...). This is what goes in that slot, and why the slot is worth having.
A campaign is a stamp that crosses lanes. Lanes are WHERE work lives; a campaign is WHICH EFFORT it belonged to, and one effort routinely touches several lanes, several subsystems, and several agents. Nothing else in the record can express that, because everything else is organized by place.
THE SURFACE, AND IT SPLITS THE SAME WAY
SET (codegen rooms only - these WRITE) research_set_campaign(campaign) research_clear_campaign()
READ (capability rooms get this too) research_campaign_view(campaign)
THE ONE THING TO ACTUALLY LEARN HERE
campaign=None DOES NOT MEAN "no campaign". IT MEANS INHERIT.
Pass nothing and the record takes the AMBIENT stamp. To record something with no campaign at all you must CLEAR the ambient one first. That reads like a trap until you see what it buys: campaign membership never depends on an agent remembering to pass an argument on every call, across a session that might be thousands of them. Attribution defaults to true rather than to blank.
An EXPLICIT campaign still wins over the ambient one, so the default is a default and not a cage.
AND None MEANS THE OPPOSITE THING ON THE READ SIDE register_group(campaign=None) -> INHERIT the ambient stamp group_history_view(campaign=None) -> UNFILTERED, every effort Same word, same type, same argument name, opposite meaning - and melder's own docstring calls the difference out rather than leaving you to find it. It is right both times: a write that names no effort belongs to the one in progress, while a read that names no effort wants everything. Attribution defaults to SPECIFIC; queries default to BROAD. Guessing one rule for both is how you end up reading a filtered history and thinking it was the whole story.
AND IT RIDES EXACTLY FOUR SEAMS, NOT EVERYTHING The ambient stamp is applied by the ROOT FACADE: record_world_entry, record_promotion, register_group, recompose_group - every dynamic bind, staged bind, notch, and composition write. The room's ORGANIZATIONAL verbs (research_create_lane, research_attach, research_archive) delegate straight to the research set and are UNSTAMPED. Moving furniture is not part of an effort's story unless you say so.
WRITE/READ AGREEMENT, AND IT IS A LAW (BUG-047) An empty campaign is refused on the WRITE side for one stated reason: campaign_view refuses it on the READ side. A public write may never create a record the public query API cannot reach. That is the never-substitute law wearing a different hat - melder would rather refuse your write than let you produce a fact nobody can ever ask about.
WHAT THE VIEW ANSWERS {campaign, nodes, transitions, lane_names} TRANSITIONS AND NODES ARE NOT THE SAME SET, deliberately. Every stamped journal entry is a transition; only four acts contribute a NODE (registered, staged, group_registered, group_recomposed). A campaign of pure organizational moves yields transitions and an empty node list - that is correct, not a gap.
And the sequence is the JOURNAL's order, not a lane walk. Lane iteration would tie-break same-millisecond ULIDs on their random component, so the story would reorder itself between runs. A history that is not reproducible is not a history.
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/15_campaigns_the_why_that_rides_along.py
py -3.14t UX_and_AIX_experiences/04_expert/15_campaigns_the_why_that_rides_along.py
Public surface๏
research_set_campaign / research_clear_campaign / research_campaign_view, ambient inheritance through research_group_register and research_group_recompose, and research_group_history(campaign=...)
Code๏
1"""
2TIER: expert (15)
3GOAL: CAMPAIGNS - the WHY, carried without anyone remembering to carry
4 it. Expert 14 built a subsystem and left one argument dangling:
5 `research_group_history(group_id, campaign=...)`. This is what goes
6 in that slot, and why the slot is worth having.
7
8 A campaign is a stamp that crosses lanes. Lanes are WHERE work
9 lives; a campaign is WHICH EFFORT it belonged to, and one effort
10 routinely touches several lanes, several subsystems, and several
11 agents. Nothing else in the record can express that, because
12 everything else is organized by place.
13
14 THE SURFACE, AND IT SPLITS THE SAME WAY
15
16 SET (codegen rooms only - these WRITE)
17 research_set_campaign(campaign)
18 research_clear_campaign()
19
20 READ (capability rooms get this too)
21 research_campaign_view(campaign)
22
23 THE ONE THING TO ACTUALLY LEARN HERE
24
25 campaign=None DOES NOT MEAN "no campaign". IT MEANS INHERIT.
26
27 Pass nothing and the record takes the AMBIENT stamp. To record
28 something with no campaign at all you must CLEAR the ambient one
29 first. That reads like a trap until you see what it buys: campaign
30 membership never depends on an agent remembering to pass an
31 argument on every call, across a session that might be thousands of
32 them. Attribution defaults to true rather than to blank.
33
34 An EXPLICIT campaign still wins over the ambient one, so the
35 default is a default and not a cage.
36
37 AND `None` MEANS THE OPPOSITE THING ON THE READ SIDE
38 register_group(campaign=None) -> INHERIT the ambient stamp
39 group_history_view(campaign=None) -> UNFILTERED, every effort
40 Same word, same type, same argument name, opposite meaning - and
41 melder's own docstring calls the difference out rather than leaving
42 you to find it. It is right both times: a write that names no
43 effort belongs to the one in progress, while a read that names no
44 effort wants everything. Attribution defaults to SPECIFIC; queries
45 default to BROAD. Guessing one rule for both is how you end up
46 reading a filtered history and thinking it was the whole story.
47
48 AND IT RIDES EXACTLY FOUR SEAMS, NOT EVERYTHING
49 The ambient stamp is applied by the ROOT FACADE:
50 `record_world_entry`, `record_promotion`, `register_group`,
51 `recompose_group` - every dynamic bind, staged bind, notch, and
52 composition write. The room's ORGANIZATIONAL verbs
53 (`research_create_lane`, `research_attach`, `research_archive`)
54 delegate straight to the research set and are UNSTAMPED. Moving
55 furniture is not part of an effort's story unless you say so.
56
57 WRITE/READ AGREEMENT, AND IT IS A LAW (BUG-047)
58 An empty campaign is refused on the WRITE side for one stated
59 reason: `campaign_view` refuses it on the READ side. A public write
60 may never create a record the public query API cannot reach. That
61 is the never-substitute law wearing a different hat - melder would
62 rather refuse your write than let you produce a fact nobody can
63 ever ask about.
64
65 WHAT THE VIEW ANSWERS
66 {campaign, nodes, transitions, lane_names}
67 TRANSITIONS AND NODES ARE NOT THE SAME SET, deliberately. Every
68 stamped journal entry is a transition; only four acts contribute a
69 NODE (registered, staged, group_registered, group_recomposed). A
70 campaign of pure organizational moves yields transitions and an
71 empty node list - that is correct, not a gap.
72
73 And the sequence is the JOURNAL's order, not a lane walk. Lane
74 iteration would tie-break same-millisecond ULIDs on their random
75 component, so the story would reorder itself between runs. A
76 history that is not reproducible is not a history.
77SURFACE EXERCISED: research_set_campaign / research_clear_campaign /
78 research_campaign_view, ambient inheritance through
79 research_group_register and research_group_recompose,
80 and research_group_history(campaign=...)
81VERIFY: RUN GREEN 2026-08-03 on the owner's 3.14t harness.
82"""
83import melder as md
84
85
86def _room(nexus, kind, name):
87 """Open one rift of the given kind and hand back its command surface."""
88 configuration = nexus.create_rift_configuration()
89 configuration.with_space_type(kind)
90 rift = nexus.create_rift(configuration=configuration, rift_name=name)
91 rift.mark_active()
92 return rift.space.command_system
93
94
95def main() -> None:
96 nexus = md.Nexus()
97 system_configuration = nexus.create_configuration()
98 system_configuration.with_rift_creation_enabled(True)
99 nexus.activate(system_configuration)
100
101 research = md.MutationResearch()
102 research_configuration = research.create_configuration()
103 research_configuration.with_defaults().activate()
104 research.activate(research_configuration)
105
106 codegen = _room(nexus, "codegen", "worker")
107 capability = _room(nexus, "capability", "auditor")
108
109 # THE SPLIT AGAIN. Setting the WHY is a write; reading it is not.
110 assert hasattr(codegen, "research_set_campaign")
111 assert hasattr(codegen, "research_clear_campaign")
112 assert not hasattr(capability, "research_set_campaign")
113 assert not hasattr(capability, "research_clear_campaign")
114 assert hasattr(codegen, "research_campaign_view")
115 assert hasattr(capability, "research_campaign_view")
116 print("set/clear: codegen only view: both rooms")
117
118 # Some declared versions to compose over.
119 research_set = research.research_set()
120 alpha, beta, gamma = "a" * 64, "b" * 64, "c" * 64
121 for spell_id in (alpha, beta, gamma):
122 research_set.register_spell(spell_id)
123 research_set.create_lane("payments", lane_type="production")
124
125 # 1. NAME THE EFFORT ONCE.
126 codegen.research_set_campaign("q3-payments-split")
127 assert research.active_campaign == "q3-payments-split"
128 print()
129 print("ambient campaign set ->", research.active_campaign)
130
131 # 2. WRITE WITHOUT MENTIONING IT. This is the whole point: no
132 # `campaign=` argument anywhere, and the record is attributed.
133 first = codegen.research_group_register(
134 [alpha, beta], lane="payments", reason="the payments core",
135 )
136 assert first["campaign"] == "q3-payments-split", (
137 "campaign=None means INHERIT, not none"
138 )
139 print("registered a composition with NO campaign argument ->",
140 first["campaign"])
141
142 second = codegen.research_group_recompose(
143 first["group_id"], add=[gamma], reason="settlement joined",
144 )
145 assert second["campaign"] == "q3-payments-split"
146 print("recompose inherited the same stamp - so the whole arc is one")
147 print("effort, without one call naming it")
148
149 # 3. ORGANIZATIONAL MOVES DO NOT RIDE ALONG. The room's lane verbs go
150 # straight to the research set, not through the stamping facade.
151 before = len(codegen.research_campaign_view("q3-payments-split")
152 ["transitions"])
153 codegen.research_create_lane("scratch", lane_type="experiment")
154 after = len(codegen.research_campaign_view("q3-payments-split")
155 ["transitions"])
156 assert after == before, (
157 "an organizational move must not be attributed to an effort "
158 "nobody said it belonged to"
159 )
160 print()
161 print("created a lane under a live campaign; transitions unchanged:",
162 before, "->", after)
163 print(" the stamp rides declarations, not furniture moves")
164
165 # 4. TO RECORD WITH NO CAMPAIGN, CLEAR THE AMBIENT ONE. There is no
166 # "campaign=None means none" escape hatch, because None is taken.
167 codegen.research_clear_campaign()
168 assert research.active_campaign is None
169 delta = "d" * 64
170 research_set.register_spell(delta)
171 unattributed = codegen.research_group_register(
172 [alpha, delta], lane="payments", reason="side quest",
173 )
174 assert unattributed["campaign"] is None
175 print()
176 print("cleared the ambient stamp -> the next write is unattributed")
177 print(" clearing is the ONLY way to opt out, which is why the")
178 print(" default direction is 'attributed'")
179
180 # 5. AN EXPLICIT CAMPAIGN BEATS THE AMBIENT ONE. A default, not a cage.
181 codegen.research_set_campaign("q3-payments-split")
182 epsilon = "e" * 64
183 research_set.register_spell(epsilon)
184 override = codegen.research_group_register(
185 [beta, epsilon],
186 lane="payments",
187 reason="borrowed for a different effort",
188 )
189 # The room verb takes no campaign argument, so the override is made at
190 # the root facade - the same seam that applies the ambient default.
191 explicit = research.register_group(
192 [gamma, epsilon],
193 lane="payments",
194 campaign="hotfix-2026-08",
195 reason="explicitly someone else's effort",
196 )
197 assert override["campaign"] == "q3-payments-split"
198 assert explicit.campaign == "hotfix-2026-08"
199 print()
200 print("ambient ->", override["campaign"])
201 print("explicit ->", explicit.campaign, " (wins)")
202
203 # 6. THE WRITE/READ AGREEMENT. An empty stamp is refused on BOTH sides
204 # for one reason: the read side cannot address it.
205 for attempt in (
206 lambda: codegen.research_set_campaign(""),
207 lambda: codegen.research_campaign_view(""),
208 ):
209 try:
210 attempt()
211 raise AssertionError("expected ValueError on an empty campaign")
212 except ValueError:
213 pass
214 print()
215 print("empty campaign refused by the WRITE and by the READ -")
216 print(" a public write may never create a record the public query")
217 print(" API cannot reach")
218
219 # 7. THE VIEW. Transitions are every stamped event; nodes are only the
220 # four acts that declare something.
221 view = codegen.research_campaign_view("q3-payments-split")
222 for key in ("campaign", "nodes", "transitions", "lane_names"):
223 assert key in view, key
224 assert len(view["nodes"]) <= len(view["transitions"]), (
225 "only registered/staged/group_registered/group_recomposed "
226 "contribute a node - transitions is the wider set"
227 )
228 print()
229 print("campaign view:", len(view["nodes"]), "nodes /",
230 len(view["transitions"]), "transitions across lanes",
231 sorted(view["lane_names"]))
232 print(" sequenced by JOURNAL order, so two runs tell the same story")
233
234 # 8. AND THE JOIN EXPERT 14 LEFT OPEN: one subsystem, one effort.
235 story = capability.research_group_history(
236 second["group_id"], campaign="q3-payments-split",
237 )
238 print()
239 print("group_history(subsystem, campaign) ->", type(story).__name__)
240 print(" WHERE by WHEN - 'how did this subsystem change during that")
241 print(" effort' is a question you can only ask if both were named")
242
243 # ...and here `campaign=None` means UNFILTERED, not "inherit the
244 # ambient one" - the exact inverse of what it meant on every write
245 # above. The ambient stamp is still set right now, so if this read
246 # inherited it the two answers could not differ in principle.
247 unfiltered = capability.research_group_history(second["group_id"])
248 assert research.active_campaign is not None
249 print("group_history(subsystem) -> unfiltered, not inherited")
250 print(" same argument, same type, opposite default - writes narrow")
251 print(" to the effort in progress, reads widen to all of them")
252 assert isinstance(unfiltered, type(story))
253
254 print()
255 print("a lane says where work lives; a campaign says what it was for")
256 print("None means INHERIT, so attribution defaults to true")
257 print("and nothing is written that nobody could later ask about")
258
259
260if __name__ == "__main__":
261 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.