brief: managed session config rows, skills (Q82), model routing (Q83), unslop (Q84) (sage)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -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 `<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.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-<row>/`
|
||||
|
||||
### 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 `<dataRoot>/skills-enabled/<name>/SKILL.md`. A name that isn't
|
||||
enabled refuses the launch with `skill-not-enabled`, naming the skill
|
||||
and `scripts/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
|
||||
`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-<row>/`
|
||||
|
||||
### 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-<row>/`
|
||||
|
||||
### What ships
|
||||
|
||||
- Pi. The runner adds `tools/unslop-hook/extension.ts` to
|
||||
`MOSAIC_EXTENSIONS` and points `MOSAIC_UNSLOP_LOG` at
|
||||
`<run>/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.
|
||||
Reference in New Issue
Block a user