A profile is config, a role is engine work — the north star, and why it is no longer a board entry #279
Replies: 3 comments
Correction — two places in the record say #246 is on the
|
The standing obligation this thread said had no wake condition now has one — and the ruling underneath it is the release orderTwo board facts since my comment of 2026-08-02, read from the issues rather than from this thread. 1. The outcome table's #246 row is void twice over. It reads "#246 — So the loud-failure fix is still unbuilt, still specced, and now sits one window later than this thread's table says — behind the chairs rather than beside the polish. 2. The one live item in this thread has a window for the first time. It has stood since 2026-07-27 as:
and my last comment defended keeping this thread open on the grounds that "it fires as a side effect of work nobody has scheduled, which is exactly why it could never be an acceptance criterion." Somebody has now scheduled the work. Two windows do it, in the order this thread's own ruling asks for:
That is danmt's instruction as a release order: "even if it's a mess early on. As we add one or two more we'll probably find a way to generalize them better." Add roles by hand at What triage is not doing. Not minting the record-keeping obligation as an issue: it is still not executable by anyone who claims it, and #333's window is not open. An issue enters a release by decision, never by triage's default. What changes is that this thread now names when it is read — Outcome unchanged: accept, already banked — and the remainder is a record with a wake conditionThe boundary shipped and is cited; the buildable half is #246, closed and carried to #333; the north star and its ruling are this thread. Nothing here is a work order and nothing here is lost. Nothing is waiting on this thread. Re-derived at this write with Triage, 2026-08-03. |
The two-window ordering this thread's ruling underwrites is now partly under a live ruling ask — and this thread is not evidence for either side of itFirst comment here since 2026-08-03 1. The boundary, re-verified where it actually livesThe one artifact this thread has ever shipped is still on #246 is unchanged: closed 2. The ordering is under an ask now — and the distinction mattersMy last comment set out the two-window ordering this thread's ruling asks for: add roles by hand at #163 ( Since This thread is not evidence either way on that ask, and it should not be cited as if it were. #291 is the instance model — one droid per role, one room per droid, an identity that survives the CPU swap. It is not the role-plugin generalisation this thread's ruling defers. What danmt's instruction governs is #246 and #333's "a new chair is configuration, not surgery" half: that is the thing that must wait for two or three real roles, and it is unmoved by any answer to #338's question. Recorded here because the two live one row apart on the same epic and the words "generalise" and "roles" appear in both. 3. The live item, and the censusThe one item still standing — "the next hand-built role leaves behind a written account of what it cost" — is unchanged, still not executable by anyone who claims it, still deliberately unminted, and still fires at #163's window-open when the three chairs are hired by hand. The census in my last comment is void. It read "over all 22 open Outcome unchanged: accept, already banked — and the remainder is a record with a wake conditionThe boundary shipped and is cited as settled precedent; the buildable half is #246, closed and carried to #333; the north star and its ruling are this thread. Nothing here is a work order, nothing here is lost, and there is no question in it for @danmt — which is why this thread carries no return date and needs none. Triage, 2026-08-07. |
Uh oh!
There was an error while loading. Please reload this page.
Why this is a thread and not an issue
This is the sixth issue of one class, and the other five were moved on 2026-08-01: #232 (ex-#119), #233 (ex-#120), #234 (ex-#126), #236 (ex-#128), #231 (ex-#109). Each was a design record wearing a work order's labels. #71 was not moved with them for a defensible reason — it still held buildable work at the time. That reason expired the same day.
1. The buildable half left, and the split did not finish the thought
On 2026-08-01 triage split the one executable item out as #246 — refuse a role profile with no duty module — correctly, and for the right reason: "held work that is independent of the hold is the hold's bug, not the work's." In the same pass it ticked the second item, the
shared/README.mdboundary paragraph, against6ab50e7, which had shipped three hours and twenty-one minutes after the issue was filed and gone unticked for five days.That pass then wrote: "This issue stays
blocked, and the label is now true of what remains." The first half of that sentence was never examined — it asked whether the label was true and not whether the issue was one. With both "Now" items gone, what remains is a north star, a ruling other issues cite, and one criterion. Triage's own doctrine names the move: "Mint an issue to 'discuss' something — that is a discussion" (TRIAGE.md).2. The one remaining criterion cannot be met by anyone who claims it
An issue is a work order a builder must be able to execute without asking anyone anything. This one cannot be executed at all: it is a record-keeping obligation that fires as a side effect of unrelated future work nobody has scheduled. A builder who claimed #71 could do nothing but wait, and the acceptance criteria are also the reviewer's review spec — there is nothing here to review against.
3. The
blockedlabel was false in the taxonomy's own terms, and permanently soLABELS.md defines
blockedas "waiting on another issue or PR (Blocked by #Nin the body names it)." #71 waits on neither. Its declaration says so out loud — "Blocked by the absence of two more roles, not by an issue" — and the issue-flow sweep has been correct about it since2026-07-28T15:57:01Z, where it left ablocked-unparseableflag that no edit to that issue could ever clear, because there is no#Nto name. Read from #71's label events rather than its prose:blockedwas applied by triage at15:56:41Z, the flag landed twenty seconds later, and it stood for five days as the board's only unresolvable queue conflict.That is not the sweep failing. It is the sweep refusing to guess intent, exactly as designed, about a state that had no honest form among
ready/claimed/blocked/post-merge. This board wrote the lesson itself, on #116: "the queue label is a dispatch signal, not a status annotation. A prose caution under areadylabel is not a caution, it is a trap." A north star underblockedis the same trap with no fuse at all — the condition never arrives.What survives, because it is the reason this is a thread and not a
wontfixThe boundary, which is a decision and not a question:
It shipped into
shared/README.mdand it is cited as settled precedent elsewhere on the board — #144 invokes it as "#71's ruling applies: generalize only after two more instances," and #162 leans on it to place #246. Those citations survive this move: a closed issue and this thread are both permanent, and the record below is unedited.danmt's instruction that a mess is acceptable early, and the reason it is right: the shape of a plugin surface guessed from one example is the shape of that example. Generalise from two or three real roles, not before.
The standing obligation, restated as what it actually is — a note for whoever builds the next role, not a ticket: record what had to change beyond the conf and the module (dispatch order, the shared attention wake, box sizing, the panel/bench, whether the role claims issues and therefore needs
protocols/claim-race.md), and what could not be expressed without touching the engine. It belongs to that work when it exists, and this thread is where it lands until then.The outcome: accept, already banked — and the remainder is a thread
Everything acceptance produced from #71 has been minted or shipped:
blockedon #162 (0.1.2), spec decided, on that gate's checklist6ab50e7, in0.1.0Nothing on the board is blocked by this. No open issue declares
Blocked by #71— checked across all 42 open issues, not inferred from the cross-reference list. Every reference to it is related to, split out of, or #71's ruling applies, and none of them changes.Nothing is waiting on this thread, and there is no question in it for @danmt. It is a record, open for the next person who wants to invent a role.
The record, as filed on #71 — verbatim
A role looks like config and is not
shared/conf/roles/builder.confis four session timeouts and three box-sizing values.reviewer.confandtriage.confare the same shape. Nothing in them describes what the role does.Behaviour is compiled into the engine, dispatched by name:
with the work itself in
shared/lib/duty-{triage,review,builder,hygiene,attention}.shand the prompts inshared/prompts/.The failure mode is silence. Drop a
designer.confintoshared/conf/roles/and everything appears to work:validate_profilesaccepts the name (cli/crew:914-917),install.shaccepts it (shared/install.sh:168-170),crew newbuilds a correctly-sized box,crew hirearms cron. The box then ticks forever and does nothing, because no duty module dispatches ondesigner. No error at any step.That is the part worth fixing first, and it is small.
Where the compose analogy breaks
Compose lets you define services because you supply the runtime — an image. crew's roles are behaviours inside the engine, so "define your own role" today means editing the tool: a
duty-<role>.sh, a dispatch branch induty.sh, prompts, a role conf, and a release to get it onto the boxes. Precisely the tree #73 exists to stop operators from editing.So the honest statement of today's boundary is: a new profile is config; a new role is engine work. Different sizing, different timeouts, a beefier builder — config, works now. New behaviour — a crew change and a release.
What to do now, and what to defer
Now — make the silent case loud. A role with no duty module should be refused at
validate_profiles, or at minimum warned about at hire, naming what is missing. One conditional, no design commitment, and it converts the worst failure in this area into a message. → Split out as #246 on 2026-08-01, held on #162 (0.1.2). Re-verified againstmainat229e0a5and still true: both host gates test only that the conf file exists. It is not blocked by anything this issue waits on, and it was the only thing here that ever was buildable.Now — write the boundary down. One paragraph in
shared/README.mddistinguishing profile from role, so the expectation is set before the co-founder hits it. → Shipped.6ab50e7("test: pin init and doctrine boundaries") added exactly that paragraph toshared/README.md— "Agent profiles extend configuration; adding a role changes the engine's duty lifecycle" — three hours and twenty-one minutes after this issue was filed, as part of the #73/#74/#76 pass this issue's Dependencies section already calls shipped. The layout table two seats down labels the two files "agent profile — the runtime" and "role profile — the work".Next one or two roles — add them by hand, deliberately, and keep notes. danmt's explicit instruction is that a mess is acceptable early. The generalisation should be derived from two or three real roles rather than designed before any exist; the shape of a plugin surface guessed from one example is the shape of that example.
Worth recording as each is added: what had to change beyond the conf and the module (dispatch order, the shared attention wake, box sizing, the panel/bench, whether the role claims issues and therefore needs
protocols/claim-race.md), and what could not be expressed without touching the engine.Later, and only then — the plugin surface. The likely shape, on current evidence: a duty-module directory the engine discovers and dispatches over, plus an operator prompt-override dir, both resolved from the config dir #74 introduced. Deliberately not specced here.
The coupling that will surface with the first custom role
shared/prompts/*name heavy-duty's doctrine by filename — "act per TRIAGE.md", "REVIEWER.md governs the role", "per BUILDER.md" — and those files live inheavy-duty/ceremony. A custom role has no doctrine file at all, and a second fleet's roles may have different ones. #76 carried the general fix — ruled and shipped: doctrine paths resolve fromdoctrine.confin the operator config dir, with heavy-duty's filenames as the shipped default. #76 names this issue as the coupling an invented role hits first, and that is still where it bites: a custom role's prompt has a resolvable path and nothing to point it at.Tasks
Refuse or loudly warn on a role profile with no duty module; name what is missing.— moved to shared/lib/roles.sh — a role with a profile and no duty module is refused, not hired into a box that ticks forever #246, 2026-08-01. Not done here and not dropped: it is one issue of its own, on the0.1.1gate, with the spec decided.shared/README.md. — shipped in6ab50e7, 2026-07-27T22:34Z. Ticked 2026-08-01.Acceptance criteria
A role name with no duty module cannot reach a running box silently.— shared/lib/roles.sh — a role with a profile and no duty module is refused, not hired into a box that ticks forever #246's, verbatim, 2026-08-01.shared/README.mdstates that a profile is config and a role is engine work. — met by6ab50e7, 2026-07-27.Dependencies
Blocked by the absence of two more roles, not by an issue — the sweep cannot resolve this and will say so; triage flips it by hand. This issue's own framing is that generalizing after ONE role encodes a guess: the shape becomes knowable once a second and third role exist, and not before. It is a north star recorded to stop "add a role" being answered with improvisation, not a ticket to build a plugin system from.
Related to #73 and its children #74 (the config dir a plugin surface would eventually resolve from) and #76 (the doctrine-path fix a custom role needs). Never blocked by them, and now moot as a dependency: all three merged and shipped in
0.1.0. The loud-failure fix and the documentation were independent of everything above — which is why neither is here any more: the documentation shipped with that same pass, and the fix is #246.Triage, 2026-08-01 — bookkeeping. This body carried an unresolved
#Aplaceholder in four places, from the sherpa session that filed it before those issues had numbers.#Ais resolved above to the issue it actually meant in each place — #73 the anchor, #74 the config dir, #76 the doctrine-path ruling — and their shipped state is written in rather than left implied. No spec, task, criterion or dependency changed: this issue is stillblockedon the absence of two more roles, which is unchanged.Triage, 2026-08-01 — the split, and a tick that was five days late.
This issue has been
blockedon "the absence of two more roles" — a condition no issue closes and nothing on the board moves — while holding two items its own text marks "Now", and states are "independent of everything above" . One of them had already shipped and nobody ticked it; the other is still broken and was unpickable because it lived here.So: the buildable half is #246, on the
0.1.1gate, recorded there with its reason. The shipped half is ticked above against the commit that shipped it —6ab50e7, three hours after this issue was filed, in the very pass this issue's Dependencies section already described as merged. The bookkeeping pass earlier today resolved four#Aplaceholders in this body and said "no spec, task, criterion or dependency changed" — true of what it touched, and it read the Dependencies section without reading the checklist four lines up.This issue stays
blocked, and the label is now true of what remains. Acceptance criterion 3 — "the next hand-built role leaves behind a written account of what it cost" — cannot be met until somebody builds a role, so no builder could close this issue no matter how much of it was ready. That was the trap: a north star and a one-conditional fix in one work order, where the north star's horizon governs both.All reactions