14 KiB
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.
--restrictedstays 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 <dataRoot>/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.mjsand their testsroles/coder.json,roles/cto.json,roles/pm.json,roles/reviewer.json(theskillskey only)packages/harness/src/bundle.mjs,packages/harness/src/runner.mjs,packages/harness/tests/,packages/harness/README.mdpackages/cli/src/launcher.mjsand its tests (the refusal only)adapters/pi/adapter.sh,adapters/claude/adapter.sh,adapters/README.mdagents/sage/work/queue-<row>/
What ships
-
Role files take an optional
skillslist: 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
<dataRoot>/skills-enabled/<name>/SKILL.md. A name that isn't enabled refuses the launch withskill-not-enabled, naming the skill andscripts/skill.sh activate <name>. -
The bundle copies each resolved skill directory into the bundle, so a deactivate during the session doesn't change it. The manifest's
skillslists each name, its bundle path and the sha256 of itsSKILL.md. -
Pi gets the bundle copies through
MOSAIC_SKILLS, which the adapter already turns into--skillflags. 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--restrictedkept, and record the choice and its evidence in the packet and inadapters/README.md. -
First skill lists.
ms-unslopis on every list because Jason requires it on all output.ms-toolsis 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-skillsand 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 everyscripts/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.mjsand their tests,packages/business/README.mdpackages/harness/src/bundle.mjs,runner.mjs, tests, READMEpackages/seat/src/session.mjsand its tests (thefamilymatch goes)packages/cli/src/launcher.mjsand its testsadapters/pi/adapter.sh,adapters/claude/adapter.sh,adapters/README.md- test business fixtures that set
harness,modelorthinking agents/sage/work/queue-<row>/
What ships
-
A top-level
routingtable in the business file, keyed by role instance name (the keys ofroles). Each entry hasharness(piorclaude-code),model,family(a key ofLAUNCH_CEILING),thinking(the existing six levels), andfallback: null, or an object with the same four keys. Unknown keys refuse. Alaunch.instancesorlaunch.byname with no entry refuses the business file at load. -
One source. The agent-layer
harness,modelandthinkingvars are removed, and session resolution no longer readsexecution.adapterorexecution.model. Those system keys stay forscripts/run-task.shworkers, which this row doesn't touch. A business file that still sets the removed vars refuses with a message pointing atrouting. -
Resolution. A launch for an instance with no
routingentry refuses withno-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 (primaryorfallback) and why. -
The launch cap counts sessions by the
familyof the entry each was launched under.family()insession.mjsgoes.launch.maxkeeps its meaning. -
thinkingreaches 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 andadapters/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-flashwould need aglmfamily 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
opuslaunched under asonnetentry 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 everyscripts/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 andlists.jsonchange only if a test needs it)packages/harness/src/bundle.mjs,runner.mjs, tests, READMEadapters/README.mdagents/sage/work/queue-<row>/
What ships
- Pi. The runner adds
tools/unslop-hook/extension.tstoMOSAIC_EXTENSIONSand pointsMOSAIC_UNSLOP_LOGat<run>/session/unslop.jsonl. - Claude Code. The bundle's
settings.jsongains a hook on reply end that checks the last assistant text withunslop-check.jsand 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 talkalready 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.jsondrives both. The manifest records the extension, the hook script andlists.jsonwith digests. - Fail closed, as the Pi extension does: a broken
lists.jsonstops the checks, logsbroken_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.jsonlin the run directory records per-turncheckedandflaggedevents 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.jsonfails 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 everyscripts/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.