From 26cc4e196072ae6e1e5107ab41e28d0b73c1d2c7 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 11 Oct 2026 14:52:45 -0500 Subject: [PATCH] brief: managed session config rows, skills (Q82), model routing (Q83), unslop (Q84) (sage) Co-Authored-By: Claude Opus 5.5 --- .../2026-10-11_managed-session-config.md | 293 ++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 docs/plans/2026-10-11_managed-session-config.md diff --git a/docs/plans/2026-10-11_managed-session-config.md b/docs/plans/2026-10-11_managed-session-config.md new file mode 100644 index 000000000..e933f4c20 --- /dev/null +++ b/docs/plans/2026-10-11_managed-session-config.md @@ -0,0 +1,293 @@ +# Managed session config: skills, model routing, unslop (2026-10-11) + +Status: written by Sage, lead. It follows `docs/plans/BRIEF-TEMPLATE.md`. +Jason ruled Q82, Q83 and Q84 option A, relayed by Mos. All three rows +change what a managed session (the row 41 bundle, what `mosaic talk` +starts) is given at launch, and all three touch +`packages/harness/src/bundle.mjs` and the two adapters. They run one at a +time in the order below, each after the one before it is done, so no two +candidates edit the same files at once. Sage owns all three. The order +is after rows 55, 42 and 49. + +Two decisions shape the set. Q84 is its own row, not part of the skills +row: the ms-unslop skill is guidance the model reads, and the hook is a +check that runs on its output. They differ in files, failure mode and +tests. Row 29 (console model switching) stays parked. It is a WebUI +feature. When Jason reopens it, it reads the routing table this set adds. + +Constraints that hold for all three rows: + +- Fail closed. A launch that can't give a session what its role + declares refuses, with a refusal code and a message naming the missing + piece. Nothing is silently dropped or defaulted. +- `--restricted` stays on the Claude Code adapter, and no harness reads + a default config location (`~/.pi`, `~/.claude`) for skills, settings, + extensions or hooks. Every file a session loads is in its bundle or + named in its manifest with a digest. +- Run records stay write-once. Anything a session writes for later + reading goes under its run directory. + +## Managed sessions get their role's skills on both harnesses + +### Problem + +ROADMAP M17 says role contracts declare skill names and the runner passes +them explicitly. Only the older path does it: `scripts/agent.sh:173-184` +reads a seat's `skills`, checks each is in `/skills-enabled` +and refuses if not. Managed sessions get none. +`packages/harness/src/bundle.mjs:147` writes `skills: []`, +`packages/harness/src/runner.mjs` sets no `MOSAIC_SKILLS`, so +`adapters/pi/adapter.sh:52` runs Pi with `--no-skills`, and +`adapters/claude/adapter.sh` passes `--disable-slash-commands`. Role +files have no place for a skill list (`packages/business/src/role.mjs:19`). +Today `skills-enabled` holds only `ms-communications`; `ms-tools` is +installed but not enabled. + +### Owner and reviewer + +- Owner: Sage. +- Reviewers: Darkwing and Filbert. + +### Files owned + +- `packages/business/src/role.mjs`, `packages/business/src/resolve.mjs` + and their tests +- `roles/coder.json`, `roles/cto.json`, `roles/pm.json`, + `roles/reviewer.json` (the `skills` key only) +- `packages/harness/src/bundle.mjs`, `packages/harness/src/runner.mjs`, + `packages/harness/tests/`, `packages/harness/README.md` +- `packages/cli/src/launcher.mjs` and its tests (the refusal only) +- `adapters/pi/adapter.sh`, `adapters/claude/adapter.sh`, + `adapters/README.md` +- `agents/sage/work/queue-/` + +### What ships + +- Role files take an optional `skills` list: distinct names matching + `[A-Za-z0-9._-]+`. A role without the key has no skills. The role + version stays 2; the key is optional. +- Resolution carries the role's skill list. The bundle resolves each name + against `/skills-enabled//SKILL.md`. A name that isn't + enabled refuses the launch with `skill-not-enabled`, naming the skill + and `scripts/skill.sh activate `. +- The bundle copies each resolved skill directory into the bundle, so a + deactivate during the session doesn't change it. The manifest's + `skills` lists each name, its bundle path and the sha256 of its + `SKILL.md`. +- Pi gets the bundle copies through `MOSAIC_SKILLS`, which the adapter + already turns into `--skill` flags. Claude Code gets the same skills + and no others. Probe the way in first (a bundle-local plugin + directory, or the skill text in the generated system prompt are the + two candidates) with `--restricted` kept, and record the choice and + its evidence in the packet and in `adapters/README.md`. +- First skill lists. `ms-unslop` is on every list because Jason requires + it on all output. `ms-tools` is on none: it documents the conductor's + repository scripts, which a managed session can't run. + + | Role | Skills | + |---|---| + | coder | ms-unslop, ms-sdlc, ms-file-read, ms-file-write | + | reviewer | ms-unslop, ms-sdlc, ms-file-read | + | cto | ms-unslop, ms-sdlc, ms-archify, ms-grill-me | + | pm | ms-unslop, ms-goal, ms-communications | + +- Tests, each on both harnesses with the existing fake binaries: + a session gets exactly its contracted skills (the argv or prompt the + fake records lists those names and no others); a contracted skill that + isn't enabled refuses the launch before any spawn; an enabled skill + that isn't on the role's list is absent; a role with no list runs Pi + with `--no-skills` and gives Claude Code none. +- Gate suites: `node --test 'packages/business/tests/*.test.mjs'`, + `node --test 'packages/harness/tests/*.test.mjs'`, + `node --test 'packages/cli/tests/*.test.mjs'`, and every + `scripts/test-*.sh`. + +### Out of scope + +Per-instance or per-business skill lists (role files only for now). The +`mosaic skill` CLI verbs (`scripts/skill.sh` stays the lifecycle tool). +Enabling skills in the live data root: that is a landing step, below. + +### Gate + +Darkwing and Filbert approve on the issue, naming the candidate manifest. +The suites above pass on a gate rerun in a clean worktree. At landing, +Sage installs and activates the skills the four lists name, then starts +one managed coder session on each harness and attaches the manifest +showing the four skills and their digests. + +## Model routing table: sessions resolve harness, model and thinking from one table + +### Problem + +A session's harness, model and thinking come from free-string agent-layer +vars (`packages/business/src/vars.mjs:106-108`) with the system's +`execution.adapter` and `execution.model` as fallback +(`packages/harness/src/bundle.mjs:51-55`). The launch cap guesses a +model's family by substring (`packages/seat/src/session.mjs:51`, used at +`packages/cli/src/launcher.mjs:148`), so a model name that contains +neither `opus` nor `sonnet`, or both, refuses for the wrong reason, and a +renamed model can move between families unnoticed. `thinking` is +recorded in the manifest and applied to neither harness +(`packages/harness/README.md:188-190`). The live business file +`mosaic-stack.json` sets no harness, model or thinking for any role, and +the system config has no `execution.adapter`, so a managed launch there +refuses today with `unsupported-harness`. The row 41 recorded run used a +test business with `claude-code` and `claude-sonnet-5-5`. + +### Owner and reviewer + +- Owner: Sage. +- Reviewers: Darkwing and Dewey. + +### Files owned + +- `packages/business/src/business.mjs`, `vars.mjs`, `vocabulary.mjs`, + `index.mjs` and their tests, `packages/business/README.md` +- `packages/harness/src/bundle.mjs`, `runner.mjs`, tests, README +- `packages/seat/src/session.mjs` and its tests (the `family` match + goes) +- `packages/cli/src/launcher.mjs` and its tests +- `adapters/pi/adapter.sh`, `adapters/claude/adapter.sh`, + `adapters/README.md` +- test business fixtures that set `harness`, `model` or `thinking` +- `agents/sage/work/queue-/` + +### What ships + +- A top-level `routing` table in the business file, keyed by role + instance name (the keys of `roles`). Each entry has `harness` + (`pi` or `claude-code`), `model`, `family` (a key of + `LAUNCH_CEILING`), `thinking` (the existing six levels), and + `fallback`: null, or an object with the same four keys. Unknown keys + refuse. A `launch.instances` or `launch.by` name with no entry refuses + the business file at load. +- One source. The agent-layer `harness`, `model` and `thinking` vars are + removed, and session resolution no longer reads `execution.adapter` + or `execution.model`. Those system keys stay for `scripts/run-task.sh` + workers, which this row doesn't touch. A business file that still sets + the removed vars refuses with a message pointing at `routing`. +- Resolution. A launch for an instance with no `routing` entry refuses + with `no-route`. The fallback is used only when the entry states one, + and only at launch, when the primary harness fails its preflight + (binary missing, or credential status not ok). Never mid-session. The + manifest records which entry was used (`primary` or `fallback`) and + why. +- The launch cap counts sessions by the `family` of the entry each was + launched under. `family()` in `session.mjs` goes. `launch.max` keeps + its meaning. +- `thinking` reaches both harnesses. Pi gets its thinking flag. Claude + Code gets its effort or thinking setting. Probe the exact flag or + setting on the pinned versions first and record it in the packet and + `adapters/README.md`. The README line saying thinking isn't applied + goes. +- First table. The live business file sets nothing today, so this table + starts from the row 41 recorded run and the R26 caps. Every fallback + is null: a pi fallback on `zai/glm-5.3-flash` would need a `glm` + family ceiling, which is a separate ruling. + + | Role | Harness | Model | Family | Thinking | Fallback | + |---|---|---|---|---|---| + | coder | claude-code | claude-sonnet-5-5 | sonnet | medium | none | + | reviewer | claude-code | claude-opus-5-5 | opus | high | none | + | cto | claude-code | claude-opus-5-5 | opus | high | none | + | pm | claude-code | claude-opus-5-5 | opus | high | none | + +- Tests: a session resolves harness, model and thinking from its table + entry; an unknown role or a missing entry refuses the launch; a stated + fallback is used when the primary's preflight fails and an unstated + one never is; the cap counts by entry family, so a model name holding + `opus` launched under a `sonnet` entry counts as sonnet; thinking + reaches the Pi and Claude Code fake adapters as the recorded flag. +- Gate suites: `node --test 'packages/business/tests/*.test.mjs'`, + `node --test 'packages/harness/tests/*.test.mjs'`, + `node --test 'packages/seat/tests/*.test.mjs'`, + `node --test 'packages/cli/tests/*.test.mjs'`, and every + `scripts/test-*.sh`. + +### Out of scope + +The provider and profile registry in `packages/mosaic` stays parked; it +comes with CLIProxyAPI. Model switching in a running session and the +console UI for it (row 29, parked). `scripts/run-task.sh` worker model +selection. + +### Gate + +Darkwing and Dewey approve on the issue, naming the candidate manifest. +The suites above pass on a gate rerun in a clean worktree. At landing, +Sage writes the first table into `mosaic-stack.json` and starts one +managed coder session, attaching the manifest that shows the entry used, +the family counted and the thinking flag passed. + +## Managed sessions run the unslop check on both harnesses + +### Problem + +`tools/unslop-hook` checks assistant replies against `lists.json`, but +only as a Pi extension loaded by hand (`tools/unslop-hook/README.md`). +Managed sessions don't load it: the runner passes Pi only the bundle's +own extension (`packages/harness/src/runner.mjs:223`), and the Claude +Code bundle has one hook, the PreToolUse gate +(`packages/harness/src/bundle.mjs:125-127`). A managed session can +drift into the tells the ms-unslop skill bans and nothing notices. + +### Owner and reviewer + +- Owner: Sage. +- Reviewers: Dewey and Filbert. + +### Files owned + +- `tools/unslop-hook/` (a Claude Code hook script beside the extension; + the checker and `lists.json` change only if a test needs it) +- `packages/harness/src/bundle.mjs`, `runner.mjs`, tests, README +- `adapters/README.md` +- `agents/sage/work/queue-/` + +### What ships + +- Pi. The runner adds `tools/unslop-hook/extension.ts` to + `MOSAIC_EXTENSIONS` and points `MOSAIC_UNSLOP_LOG` at + `/session/unslop.jsonl`. +- Claude Code. The bundle's `settings.json` gains a hook on reply end + that checks the last assistant text with `unslop-check.js` and logs + the result, and a hook on the next prompt that adds the correction + notice when the last reply was flagged. Same notify-then-correct + behaviour as the Pi extension. Neither hook blocks a reply or a turn. +- The operator is told about a flagged reply on the channel `mosaic talk` + already shows session events on. Find that channel and name it in the + packet; a notice only in a log file doesn't meet this. +- One `lists.json` drives both. The manifest records the extension, the + hook script and `lists.json` with digests. +- Fail closed, as the Pi extension does: a broken `lists.json` stops the + checks, logs `broken_lists`, tells the operator once, and the session + keeps running. That includes the headless case the README lists as + a limit (print mode notifies nobody); a managed session must reach + the operator. +- Counts for a later blocking decision (Q84 option B). `unslop.jsonl` in + the run directory records per-turn `checked` and `flagged` events with + the pattern ids hit. A test reads the session's checked and flagged + counts from it after the session has ended. +- Tests, on both harnesses: a reply with a known tell produces a + correction notice on the next turn; a clean reply produces none; a + broken `lists.json` fails closed (no clean verdict, operator told + once, session alive); the counts are readable after the session ends. +- Gate suites: `node tools/unslop-hook/test-unslop-check.js`, + `node --test 'packages/harness/tests/*.test.mjs'`, + `node --test 'packages/cli/tests/*.test.mjs'`, and every + `scripts/test-*.sh`. + +### Out of scope + +Blocking a reply (Q84 option B), which waits on the measured +false-positive rate. Changes to the word lists. The ms-unslop skill +itself, which the skills row delivers. + +### Gate + +Dewey and Filbert approve on the issue, naming the candidate manifest. +The suites above pass on a gate rerun in a clean worktree. At landing, +Sage runs one managed session on each harness with a prompt that draws a +known tell, and attaches the operator notice, the next-turn correction +and the `unslop.jsonl` counts.