# 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` (shared `businessDoc` gains `chief`, `launch.by "chief"`), `business.test.mjs`, `cli.test.mjs`, `resolve.test.mjs`, `role.test.mjs`, `chief-of-staff.test.mjs`, new `assistant.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` (chief `role.launch` within, 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): ```json "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's `chief-of-staff.json` lacks `role.launch`, so the old copy refuses it. - After the re-pin, his current file (`launch.by: "pm"`) fails to load, because the new `pm.json` no longer holds `role.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.launch` gated for any chief-of-staff session (`broker.mjs:305`: `launch.by` is still `pm`). 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.sh` still names `pm cto coder reviewer` in 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.