brief: row 65 keeps the instance vars as its one source, adds fallback vars and the full harness list (decision 151, #1558)

This commit is contained in:
2026-10-19 08:24:25 -05:00
parent efb51169de
commit 8e2cbb5558
2 changed files with 178 additions and 106 deletions
+31
View File
@@ -3401,3 +3401,34 @@ which stay with him. Each item names who decided it and what happened.
check name and tested.
- Limits live in the organization file. Row 125 needs no bus schema
change, so Mos's re-pin plan carries no migration for it.
151. **Row 65 keeps the instance vars as its one source instead of adding
a `routing` table (2026-10-19, Sage, before the row 65 build).** The
brief section from 2026-10-11 asked for a top-level `routing` table
and the removal of the agent `harness`, `model` and `thinking` vars.
Rows 122 and 165 then built that table out of those vars:
`launch.models` maps a key to a provider and model, each instance's
vars select a harness, a key and a thinking level, the CLI and the
Console edit them, the limits count exact provider and model, and the
live file sets all three on every instance. A second table would be a
second source for the same fact and would undo two landed rows.
- The vars stay. Session resolution and the Console views stop
falling back to `execution.adapter` and `execution.model`, and a
missing var refuses with `no-route`.
- The fallback Q83 asked for becomes three agent vars,
`fallback.harness`, `fallback.model` and `fallback.thinking`, all or
none, used only when the primary fails its launch preflight. Every
fallback starts unset.
- The family cap drops out of row 65, since row 122 removed
`family()`. Thinking on both harnesses and the harness plug-in kind
stay as written, and the hard-coded list now covers `runner.mjs`,
`usage.mjs` and the row 126 account branch too.
- `harness-off` refuses launches and writes, not loading the file, so
the Console and the bus keep working while `plugins.harness` is
unset. The only business-file entry Jason gets at landing is
`plugins.harness` with `claude-code`, through `mosaic plugins set`.
- Q83 option A's text isn't in the repository. This keeps what the
brief derived from it (one source, fallback at launch only, thinking
applied) and changes only where the data lives. If Jason's ruling
named a separate table, Mos tells me and the amendment is redone
before the review.
+147 -106
View File
@@ -20,7 +20,8 @@ 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.
feature. When Jason reopens it, it reads the instance vars that row 65
makes the one source (amended 2026-10-19).
Constraints that hold for all three rows:
@@ -125,30 +126,52 @@ showing the four skills and their digests.
## Model routing table: sessions resolve harness, model and thinking from one table
Amended 2026-10-19 by Sage before the build started (lead decision 151).
Rows 122 and 165 landed after this section was written and built most of
the table it asked for. `launch.models` maps a key to a provider and a
model. Each role instance's agent vars name a `harness`, a `model` (a
`launch.models` key) and a `thinking` level. The `mosaic` CLI and the
Console edit them, and the launch limits count exact provider and model,
so `family()` and the family cap are already gone. The live file sets
all three vars on every instance. A second `routing` table would hold
the same data twice, so the vars stay the one source and this row adds
what is still missing: no execution fallback, a fallback route, thinking
applied on both harnesses, and the harness plug-in kind. The first
version of this section is blob 8872802c in git.
### 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`.
At 54786df03:
The harness names are hard-coded too, which PRD 1.3 REQ-PLUG-2 says
this row converts (Q156). The pair `pi` and `claude-code` is a list in
`packages/harness/src/bundle.mjs:20` and a check in
`packages/bus/src/broker.mjs:197`. `packages/seat/src/session.mjs:39`
maps each to its adapter script, `packages/seat/src/sandbox.mjs:104`
adds the Claude Code mounts, and `packages/cli/src/launcher.mjs:171` and
`:209` branch on `claude-code` for the credential and version checks.
- Session resolution falls back to the system's `execution.adapter` and
`execution.model` when an instance sets no `harness` or `model`
(`packages/harness/src/bundle.mjs:68-70`). The Console views do the
same (`packages/business/src/launch-settings.mjs:84-88` and `:162`,
`packages/business/src/instance-settings.mjs:132`). The system config
has no `execution.adapter`, and its `execution.model` is the pi worker
model, so an instance that drops a var refuses with a message about a
system key instead of the var it is missing.
- `thinking` is optional. The manifest records it and neither harness
applies it (`packages/harness/README.md`).
- There is no fallback. A launch whose harness binary or credential is
missing refuses.
- The harness names are hard-coded, which PRD 1.3 REQ-PLUG-2 says this
row converts (Q156):
- `packages/business/src/vars.mjs:128`, where `harness` takes only
`pi` or `claude-code`;
- `packages/harness/src/bundle.mjs:31`, `:37`, `:47-53`, `:104`,
`:179` and `:204-212`: the web-builtin check, the list, the relies-on
lines and gate timeout, the builtins prompt line, the pi extension or Claude settings
files, and the manifest fields;
- `packages/harness/src/runner.mjs:251` and `:275`, the turn-end marker
and the adapter environment;
- `packages/seat/src/session.mjs:42-43`, the adapter scripts;
- `packages/seat/src/sandbox.mjs:105-106`, the mounts;
- `packages/cli/src/launcher.mjs:242`, `:249` and `:316`, the
credential check, the row 126 account and the versions;
- `packages/bus/src/broker.mjs:421`, the `invalid-harness` check;
- `packages/bus/src/usage.mjs:77-78`, where each harness keeps its
session files.
### Owner and reviewer
@@ -157,129 +180,147 @@ adds the Claude Code mounts, and `packages/cli/src/launcher.mjs:171` and
### Files owned
- `packages/business/src/business.mjs`, `vars.mjs`, `vocabulary.mjs`,
`index.mjs` and their tests, `packages/business/README.md`
- `packages/business/src/vars.mjs`, `business.mjs`, `launch-settings.mjs`,
`instance-settings.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/seat/src/session.mjs`, `packages/seat/src/sandbox.mjs` (the
mounts only) and their tests
- `packages/cli/src/launcher.mjs` and its tests
- `packages/bus/src/broker.mjs` (the harness check only),
`packages/bus/src/usage.mjs` (the session directory only) and their
tests
- `adapters/pi/adapter.sh`, `adapters/claude/adapter.sh`,
`adapters/README.md`
- the `harness` entry in `packages/plugins/src/kinds.mjs`,
`packages/plugins/harness/pi/` and `packages/plugins/harness/claude-code/`,
their tests, and the harness section of `packages/plugins/README.md`
- `packages/bus/src/broker.mjs` (the harness check only) and
`packages/seat/src/sandbox.mjs` (the Claude Code mounts only), with
their tests
- test business fixtures that set `harness`, `model` or `thinking`
- `agents/sage/work/queue-<row>/`
- `agents/sage/work/queue-65/`
A change outside this list is named in the packet with its reason.
### What ships
- Harnesses as plug-in modules (PRD 1.3 REQ-PLUG-1, REQ-PLUG-2; Q156).
Row 131's loader and kind registry are in place first.
Row 131's loader and kind registry are in place.
- A `harness` entry in `packages/plugins/src/kinds.mjs` at interface
version 1. The organization file switches on a list,
`plugins.harness`, through the write path from row 122.
`plugins.harness`, through `mosaic plugins set`.
- Two modules, `packages/plugins/harness/pi/` and
`packages/plugins/harness/claude-code/`. Each `index.mjs` exports
the adapter script it runs (a path under `adapters/`, which stay
where they are), a preflight (binary present, version, credential
status), its sandbox mounts, its bundle notes and gate timeout, and
how it passes `thinking`. Rows 64 and 66 add their skills and
`packages/plugins/harness/claude-code/`. Each `index.mjs` exports a
function for every place core branches on a harness today: the
adapter script it runs (a path under `adapters/`, which stay where
they are), a preflight (binary present, version, credential status),
its sandbox mounts, its bundle files, relies-on lines and gate
timeout, the adapter environment, the turn-end check, where its
session files live for the usage reader, whether its launches count
against `launch.account`, whether it serves `harness-builtin` web,
and how it passes `thinking`. The packet and the plug-ins README
list the final export names. Rows 64 and 66 add their skills and
unslop exports to these modules.
- Core reaches a harness only through the loader. The five places
listed under Problem read the module instead, and none of them
names `pi` or `claude-code` after this row. The broker's
`invalid-harness` check accepts a harness that is switched on.
- A routing entry or fallback that names a harness not switched on
refuses the business file at load with `harness-off`. The loader's
own refusals (bad name, missing directory, symlink, load failure,
missing export, other version) refuse the launch and name the
module.
- A top-level `routing` table in the business file, keyed by role
instance name (the keys of `roles`). Each entry has `harness`
(a harness module that is switched on), `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
- Core reaches a harness only through the loader. None of the places
listed under Problem names `pi` or `claude-code` after this row. The
broker's `invalid-harness` check accepts a harness that is switched
on.
- The `harness` var takes a module name (the loader's name pattern),
not a fixed pair. A launch whose harness, primary or fallback, isn't
switched on refuses with `harness-off`. So does a CLI or Console
write that sets one. Loading the business file doesn't refuse on
it, so the Console and the bus keep working while `plugins.harness`
is unset, and only launches refuse. The loader's own refusals (bad
name, missing directory, symlink, load failure, missing export,
other version) refuse the launch and name the module.
- One source. A managed session takes its harness, model and thinking
from its instance's three vars. Session resolution and the Console
views stop reading `execution.adapter` and `execution.model`. Those
system keys stay for `scripts/run-task.sh` workers, which this row
doesn't touch, and for the print-only `migrate-launch.mjs`. A launch
for an instance missing any of the three refuses with `no-route` and
names the missing var.
- Fallback. Three agent vars, `fallback.harness`, `fallback.model` (a
`launch.models` key) and `fallback.thinking`, set all together or not
at all. A partial set refuses the business file at load. The fallback
is used 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.
limit checks and `mayLaunch` run on the route the launch actually
takes. The manifest records `route` as `primary` or `fallback`, with
the preflight reason that chose the fallback.
- `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.
- Harness tests: a route to a harness that isn't switched on refuses
with `harness-off`; a fixture harness module switched on in a test
organization file launches a fake session with no core file changed;
a grep test that no file under `packages/*/src` outside
`packages/plugins` names `pi` or `claude-code` as a harness, with the
exceptions under Out of scope listed in the test. One mutant per
refusal, and one that puts a harness name back in core.
- No new table. The live file already sets the three vars on every
instance, and this row changes none of them. Every fallback starts
unset. The first table in the old version of this section is void.
- Tests: a session resolves harness, model and thinking from its vars;
a missing var refuses with `no-route`, and a session never reads
`execution.adapter` or `execution.model`; a partial fallback refuses
the file at load; a stated fallback is used when the primary's
preflight fails, an unstated one never is, and a primary that passes
never uses it; the manifest records the route and the reason; the
limit check sees the fallback's provider and model; thinking reaches
the Pi and Claude Code fake adapters as the recorded flag.
- Harness tests: a launch on a harness that isn't switched on refuses
with `harness-off`, primary or fallback, and so does a write that
sets one; a business file with harness vars and no `plugins.harness`
still loads and its Console view reads; a fixture harness module
switched on in a test organization file launches a fake session with
no core file changed; a grep test that no file under `packages/*/src`
outside `packages/plugins` names `pi` or `claude-code` as a harness,
with the exceptions under Out of scope listed in the test. One mutant
per refusal (`no-route`, `harness-off` at launch, `harness-off` at a
write, a partial fallback, a fallback taken when the primary passes),
and one that puts a harness name back in core.
- Gate suites: `node --test 'packages/plugins/tests/*.test.mjs'`,
`node --test 'packages/bus/tests/*.test.mjs'`,
`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`.
`node --test 'packages/cli/tests/*.test.mjs'`,
`node --test 'packages/webui/tests/*.test.mjs'` (the Console reads the
launch view), 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.
selection. Changing any instance's harness, model or thinking in the
live file.
The tool-policy gate stays in core: `packages/harness/src/gate.mjs` and
`claude-gate.mjs` enforce the session's tool policy, and a module
mustn't be able to change that boundary (PRD REQ-PLUG-1). Pi started
directly by `packages/conversation` and the Discord CLI converts when a
row changes those engines (Q156 item 2). No model provider kind; row
122 names providers as data, and the kind waits on row 127.
These keep a harness name, and the grep test lists them:
- the tool-policy gate: `packages/harness/src/gate.mjs`,
`claude-gate.mjs` and `pi-extension.mjs` enforce the session's tool
policy, and a module mustn't be able to change that boundary (PRD
REQ-PLUG-1);
- `packages/bus/src/human.mjs:85`, the human-proof check that refuses
any agent harness binary, which is a security boundary;
- `packages/conversation` and `packages/discord`, which start pi
directly and convert when a row changes those engines (Q156 item 2);
- `packages/control-board/src/scan.mjs`, which matches running process
names.
No model provider kind; row 122 names providers as data, and the kind
waits on row 127.
### Gate
Darkwing and Dewey approve on the issue, naming the candidate manifest.
The suites above pass on a gate rerun in a clean worktree. Jason edits
the business file himself (decision 144), so at landing Sage sends him,
through Mos, the exact `plugins.harness` and `routing` entries for
`mosaic-stack.json`. Once he has added them, Sage starts one managed
coder session, attaching the manifest that shows the module loaded, the
entry used, the family counted and the thinking flag passed. The
modules go live only through a re-pin on Mos's go (decision 109).
the business file himself (decision 144). This row needs one entry from
him: at landing Sage sends him, through Mos, the exact
`mosaic plugins set` patch that adds `"harness": ["claude-code"]` to
whatever `plugins` holds then. Only claude-code goes on, because no live
instance uses pi. Until he runs it on a stack pinned past this row,
every managed launch refuses `harness-off`, as chat refuses before
`plugins.channel` (row 164), and the re-pin plan says so. Once he has
run it, Sage starts one managed coder session and attaches the manifest
showing the module loaded, the route used, the provider and model
counted, and the thinking flag passed. The modules go live only through
a re-pin on Mos's go (decision 109).
## Managed sessions run the unslop check on both harnesses