9.5 KiB
Row 119 (#1613): role files for the chief of staff and the assistant
Researcher, 2026-10-15. Brief: docs/plans/2026-10-14_prd-1.2-build.md, "Role files for the chief of
staff and the assistant", pinned at 240d25518. Requirements: REQ-COS-1, REQ-ASSIST-1, REQ-ROLE-4,
REQ-LAUNCH-1. Decisions 139 (chief holds role.launch), 140 (leftover "main point of interaction"
duties removed), 144 (Jason's org file isn't edited by the stack). Reviewer: Filbert (decision 130,
role files, no sandbox change).
Base: c6570f142 (row119.patch applies cleanly to it by git apply --cached --check; no commit
between c6570f142 and 9e837ce5f touches a candidate path). No broker, thread or org-file verb changes
(rows 120, 121, 124).
What changes
Role files (the candidate):
| File | Change |
|---|---|
roles/assistant.json (new) |
tools read grep find ls; network api-only; withinRole ["message.send"], crossRole []; credentials [] |
roles/assistant.md (new) |
REQ-ASSIST-1: talks with the user, passes work to the chief of staff, directs no agent, creates or assigns no task, launches no session |
roles/chief-of-staff.json |
withinRole ["message.send", "role.launch"] (decision 139); tools, network and credentials unchanged |
roles/chief-of-staff.md |
rewritten to REQ-COS-1; the "main point of interaction" duties are gone (decision 140); never talks to the user |
roles/pm.json |
role.launch removed from withinRole (REQ-LAUNCH-1) |
roles/pm.md |
the launch bullet becomes "Take the work the chief of staff passes you. You don't launch sessions."; "resume or launch work" becomes "resume or start work" |
Docs and example: packages/business/README.md (Chief-of-staff and assistant section, rewritten) and
packages/business/examples/mosaic-stack.example.json (adds chief and assistant, launch.by "chief").
Tests (fixtures follow the roles; the only source files untouched are the packages' src/):
- business:
helpers.mjs(sharedbusinessDocgainschief,launch.by "chief"),business.test.mjs,cli.test.mjs,resolve.test.mjs,role.test.mjs,chief-of-staff.test.mjs, newassistant.test.mjs. - harness:
tools.test.mjs(PM has no launch; new chief and assistant tool lists),bundle.test.mjs(PM bundle has no launch; new chief bundle test),mcp-server.test.mjs,pi-session.test.mjs(the two symlink-write tests and the climbs loop use the PM, because the chief has no write tool). - cli:
launcher.test.mjs(launcher instance renamed pm to chief; the founder-credentials test launches the coder through a bound chief capability),trackers-boot.test.mjs(guards for roles with no tracker or credentials). - webui:
reads.test.mjs(chiefrole.launchwithin, PM gated,b.launch.by "chief"). - Not changed:
packages/webui/tests/views.test.mjs(stub fixture, no launch assertion). Correction to my earlier message to Sage: bus needed no fixture edits. Its suite passes 156/156 untouched because its fixtures are in memory.
Instance names chief and assistant are my fixture and example choices. Jason picks the real ones.
Gate (decision 136, targeted; slot held per sage-0250)
Worktree /mnt/storage/scratch/researcher/q119 at c6570f142 plus row119.patch. Outputs are in suites/.
| Run | Result |
|---|---|
| business | 78 / 78 |
| bus | 156 / 156 |
| harness | 84 / 84 (first pass 81 / 84; the three pi-session symlink tests called write on the chief; fixed as above) |
| cli | 187 / 187 |
| webui | 108 / 108 |
| tasks | 92 / 92 |
| conversation | 182 / 182 |
| seat | 132 / 132 |
scripts/test-task.sh |
98 passed, 0 failed |
Mutants (mutants.sh, mutants-run.txt, mutants/)
Eight controls pass. 25 mutants, each one exact-once edit, each run against the named test file:
| Mutant | Edit | Caught by |
|---|---|---|
| m1a-f | chief drops role.launch |
chief-of-staff, harness tools, harness bundle, cli launcher, webui reads, business |
| m2a, m2b | PM regains role.launch |
chief-of-staff ("the pm does not hold role.launch", "launch.by pm refused"), role ("shipped authority follows the note's table") |
| m2c, m2d, m2e | same edit | survive (harness tools, harness bundle, webui reads); see below |
| m3 | assistant gains role.launch |
assistant |
| m4 | assistant gains write |
assistant |
| m5 | assistant gains a crossRole action | assistant |
| m6 | assistant gains a credential | assistant |
| m7 | chief gains write |
chief-of-staff |
| m8 | chief gains a credential | chief-of-staff |
| m9a-c | validator stops requiring role.launch of the launcher |
chief-of-staff, assistant, business |
| m10 | pm.md launches again |
chief-of-staff (contract pin) |
| m11 | chief-of-staff.md is the main point of interaction again |
chief-of-staff (contract pin) |
| m12 | assistant.md directs agents |
assistant (contract pin) |
| m13a, m13b | assistant.json names a missing contract |
role ("every role file in roles/ loads through the validator"), assistant |
Why m2c, m2d and m2e survive: packages/business/src/resolve.mjs:74 keeps role.launch only for the
instance launch.by names, and lines 76-77 filter it out of every other instance. The PM is never
launch.by in the fixtures, so a PM role file that holds role.launch resolves with it gated, and the
harness tools, harness bundle and webui reads see the same effective result as before the edit. Those
tests pin the resolved behaviour, which stays safe. The role file itself is pinned where it is read,
by m2a and m2b in the business package. The broker has the same gate at packages/bus/src/broker.mjs:305
(role.launch is gated unless business.launch.by === s.role). Sage agreed on 2026-10-15 (sage-0255).
What Jason has to do, and in what order
The stack never writes Jason's org file (decision 144). Row 119 ships the role files; the entry below is
what makes them usable. File: ~/.config/mosaic-dev/businesses/mosaic-stack.json.
Add under roles (names are Jason's choice; vars mirror the PM's, and the assistant's harness and model
are his call):
"chief": {
"definition": "chief-of-staff",
"vars": { "harness": "claude-code", "model": "claude-opus-5-5", "thinking": "high" }
},
"assistant": {
"definition": "assistant",
"holder": "mos",
"vars": { "harness": "claude-code", "model": "claude-opus-5-5", "thinking": "high" }
}
and change launch.by from "pm" to "chief" (instances and max stay as they are:
["coder","reviewer"], {"opus":4,"sonnet":4}). Neither instance takes tracker or credentials; the
validator refuses both.
Landing order: one window, PM idle
- Before the re-pin, Jason can't name a chief instance in
launch.by: the pinned copy'schief-of-staff.jsonlacksrole.launch, so the old copy refuses it. - After the re-pin, his current file (
launch.by: "pm") fails to load, because the newpm.jsonno longer holdsrole.launch. - So his edit and the re-pin happen in one window run by Mos, with the PM run idle. This is the same pattern as decision 145 for row 122. The re-pin is Sage's and lands with rows 104, 106 and 109.
- Until Jason adds the entry, the broker keeps
role.launchgated for any chief-of-staff session (broker.mjs:305:launch.byis stillpm). That is expected and not a defect.
After that window
Only a running chief-of-staff session can launch. Until one runs, coders and reviewers don't start. Mos needs to plan the window with that in mind: start the chief session first, then let it launch the rest. This ships what decision 139 chose; it is not a defect in the row.
Out of scope and follow-ups
- Broker send limits for the assistant: row 120 (REQ-ASSIST-2, #1614), mine next.
- Threads, org-file verbs, anything under
packages/bus/src: rows 121, 124, 128. scripts/test-task.shstill namespm cto coder reviewerin its shipped-roles loop; the node tests (role.test.mjs) load every role file, so I left the script alone.
Files in this packet
row119.patch, candidate-manifest.sha256 (the 22 post-patch candidate files), mutants.sh,
mutants-run.txt, mutants/ (per-mutant output, return code, patch), suites/ (suite output),
packet-manifest.sha256 (digests of the packet files themselves).
Round 2 (Filbert round 1, comment 28522, rev 1233)
F1 (blocking): roles/assistant.md duty 3 told the assistant to relay rulings "to the role it concerns", which
sends to roles other than the chief of staff and contradicts REQ-ASSIST-1 and REQ-ASSIST-2. The line was copied
from chief-of-staff.md. Fixed on the same base (c6570f142): the assistant relays each ruling verbatim to the
chief of staff, naming whose ruling it is and which role it concerns, and doesn't send it to that role itself.
Pin: packages/business/tests/assistant.test.mjs, "the contract passes work to the chief of staff and directs no
one", now asserts the new duty wording, "Don't send it to that role yourself", and doesNotMatch
/to the role it concerns/.
Changed since round 1: roles/assistant.md and packages/business/tests/assistant.test.mjs only. The other 20
candidate files are byte-identical. row119.patch and candidate-manifest.sha256 are rebuilt; the patch applies
to c6570f142 by git apply --cached --check, and no commit since touches a candidate path.
Run: assistant 7/7, role 13/13, chief-of-staff 10/10 (suites/round2-business-files.txt, single files, no slot).
The round 1 suite outputs and mutants-run.txt still describe the other 20 files. I ran no new mutant for the new
pin; the round 1 mutant m12 (assistant contract directs agents) still fails on its own check. A mutant for the new
pin needs the slot and Sage's go.