280 lines
23 KiB
Markdown
280 lines
23 KiB
Markdown
# Row 65: harness, model and thinking from the instance vars, a fallback route, and the harness plug-in kind
|
|
|
|
Sage, 2026-10-19. Brief: `docs/plans/2026-10-11_managed-session-config.md`, section "Model routing
|
|
table", blob 0cdff8739051 (0235fa9fd, queue rev 1950; round 1 was blob f3b06a165586). Issue #1558.
|
|
Reviewers: Darkwing and Dewey. Built in the detached worktree `/mnt/storage/scratch/sage-r65`.
|
|
|
|
Round 2 candidate **8d7c9e2f1** (8d7c9e2f142822046f20eb1e5acad760a674fab8) on base 89ddb0225: 64 files, 2292 insertions, 336 deletions. `candidate.diff` is
|
|
`git diff 89ddb0225 8d7c9e2f1`; `candidate-manifest.sha256` has the sha256 of each changed file
|
|
at the candidate. Round 1 was ccdbc2232 on d8336daef; d8336daef..89ddb0225 touches no manifest
|
|
path (it adds this packet, the brief re-pin, SESSIONS and queue revs 1945-1950).
|
|
|
|
`public.diff` in this directory is the `packages/webui/src/public/` diff for Mos (decision 123).
|
|
|
|
## What changed
|
|
|
|
**Harness plug-in kind.** `kinds.mjs` gains `harness` (interface 1, switch list). A module
|
|
exports `adapter`, `levels`, `preflight`, `version`, `countsAccount`, `mounts`, `builtinWeb`,
|
|
`builtins`, `typedName`, `reliesOn`, `gateTimeout`, `bundle`, `env`, `turnEnd` and `transcript`.
|
|
`packages/plugins/harness/pi/` and `claude-code/` hold what core used to branch on: the adapter
|
|
path, the thinking levels, the binary and credential preflight, the version probe, the sandbox
|
|
mounts, the builtins prompt line, the typed tool names, the gate timeout, the bundle files (the
|
|
pi extension or the Claude settings and MCP files), the adapter environment, how a turn ends and
|
|
where the session files live. Core reaches a module only through the loader.
|
|
|
|
**Route.** `sessionModel(resolved, models, which)` (bundle.mjs) reads `harness`, `model` and
|
|
`thinking`, or the three `fallback.` vars, from the instance. A missing one throws `no-route`
|
|
naming the var. The system file's `execution.adapter` and `execution.model` are never read for a
|
|
session; they stay for `run-task.sh`.
|
|
|
|
**Launcher check order** (launcher.mjs). Primary route, then the fallback route when
|
|
`fallback.harness` is set; `no-role-contract`; `harness-off` for either route not in
|
|
`plugins.harness`, so an off primary never falls back; the primary module through a fresh
|
|
loader (`harness-refused` names the module and the loader's code); its levels
|
|
(`thinking-unsupported`); its preflight. A failed preflight with no fallback refuses with the
|
|
preflight's code. With a fallback, the fallback module goes through the same steps, and a second
|
|
failure names both reasons. The account checks (only when the route module's `countsAccount()`
|
|
is true), the sandbox check, the capacity limits and `mayLaunch` all run on the route taken.
|
|
Any refusal of the fallback (preflight, `harness-refused`, `harness-failed`,
|
|
`thinking-unsupported`) ends with `(primary <harness>: <reason>)`, so the reason it was tried
|
|
stays in the launch log.
|
|
The manifest records `route` (`primary` or `fallback`), `routeReason` (the primary's preflight
|
|
code, or null) and `harnessVersion`.
|
|
|
|
**Thinking.** pi's adapter passes `--thinking <level>`; Claude Code's passes `--effort <level>`.
|
|
Both read `MOSAIC_THINKING`. The levels each module applies:
|
|
|
|
| Harness | Binary the session runs | Flag | Levels |
|
|
|---|---|---|---|
|
|
| pi | 0.85.1, the repository's `node_modules/.bin/pi` (the sandbox mounts the repo's node_modules on the session's PATH; `pi` on the host PATH is 1.2.0 and isn't used) | `--thinking <level>` | off, minimal, low, medium, high, xhigh, max |
|
|
| claude-code | 2.1.302 (`MOSAIC_CLAUDE_BIN` or PATH `claude`) | `--effort <level>` | low, medium, high, xhigh, max |
|
|
|
|
The `thinking` var takes off, minimal, low, medium, high, xhigh and max. Nothing is remapped: a
|
|
level the route's harness doesn't apply refuses `thinking-unsupported`, at a write and at launch.
|
|
The live file uses high on five instances and medium on the coder, all claude-code, so nothing
|
|
live changes. `adapters/README.md` has the `MOSAIC_THINKING` row.
|
|
|
|
**Business vars and writes.** `harness` and `fallback.harness` take a module name;
|
|
`fallback.model` is a string; `thinking` and `fallback.thinking` take a level. The file layer
|
|
refuses a fallback with one or two of its three vars. `checkRoutes` (launch-settings.mjs) is shared
|
|
by `routes.set`, `instances.set` and `limits.set`: `unknown-model` for a fallback model outside
|
|
`launch.models`, then, only for a route whose harness or thinking changed, `harness-off`,
|
|
`harness-refused` (the module didn't load, so its levels are unknown) and `thinking-unsupported`.
|
|
A route the write doesn't touch isn't re-checked, so an earlier unswitched harness doesn't block
|
|
an unrelated save. `setRoutes` checks the primary and fallback models against `launch.models`
|
|
before the file's own validation, which would refuse an unknown primary model with no code, so
|
|
`routes.set` answers `unknown-model` for either (round 2, Dewey N1).
|
|
|
|
**Routes verbs** (mos-0840). `routes` reads `{revision, harnesses, levels, models, instances}`;
|
|
`routes.set` takes exactly `{revision, routes}`, each route `{harness, model, thinking,
|
|
fallback}` with `fallback` null or all three. Both are human-only broker actions outside
|
|
`ORGANIZATION_KEYS`, so `organization.set` and the chief of staff's `change_organization` can't
|
|
send a `routes` key. The writer logs the human as actor. The bus passes `harness-off`,
|
|
`harness-refused` and `thinking-unsupported` through as their own codes, and `unknown-model`
|
|
for `routes.set` only (the older verbs keep `invalid-request`).
|
|
|
|
The chief of staff still reaches one route. Under Q169 (Jason: "The COS and the user should have
|
|
the ability to (re)configure the assistant role and seat name"), `organization.set` takes
|
|
`assistants.<instance>.vars`, which replaces the assistant's whole vars, harness, thinking and
|
|
`fallback.*` included. Round 2 keeps that and names it in the brief (ruling (b), sage-1006,
|
|
blob 0cdff8739051): the write goes through `limits.set`, runs the same `checkRoutes`, and logs
|
|
`chief-of-staff.<instance>` as actor. Every other instance's route is human-only.
|
|
|
|
**Bus binds.** A new run binds only on a harness module name (`HARNESS_NAME`, the loader's
|
|
pattern) that `plugins.harness` switches on now (`harnessOn`, read from the file at bind time);
|
|
otherwise `invalid-harness`. A run the store already holds binds again unchanged, so a switched
|
|
off harness doesn't strand a running session. The bind record takes `transcript`, the module's
|
|
session-files path, which the usage reader now uses instead of a harness-name branch.
|
|
|
|
**Console** (mos-0840). Settings gets a "Routing" section and loses the "Role models" fieldset
|
|
from Agent limits. One row per instance, six selects: harness, model, thinking, and the
|
|
fallback's three. Each select offers the switched-on harnesses, the model table's keys or the
|
|
levels the row's harness applies. A value the file holds that isn't offered stays selected with
|
|
its reason in the label ("not switched on", "not in the model table", "not applied by
|
|
claude-code"), so a save sends it and the bus names the refusal. A fallback with all three set to
|
|
none is cleared; a partial one is sent as it is and refused. Save posts only the changed rows to
|
|
`POST /api/routes` with the read's revision. Every refusal keeps the draft; nothing retries.
|
|
`GET /api/bus/routes` reads the table. `packages/webui/src/bus.mjs` is unchanged, so the
|
|
channel-proof sha256 pin holds.
|
|
|
|
## Decisions taken in the build
|
|
|
|
- Write-time checks run on changed routes only; the model check runs on every model set. A
|
|
missing route var is a launch refusal (`no-route`), not a write refusal, so the file can hold
|
|
an instance that isn't launchable yet.
|
|
- The bus runtime gains `harnessLevels()` (each module in the tree, loaded once at start, on or
|
|
off, so a write can check a level before the harness is switched on) and `harnessOn` for binds.
|
|
- `RUNNER_FILES` drops `bundle.mjs` (the runner no longer imports it) and adds `kinds.mjs`.
|
|
- The runner's turn check is by `turnEnd()`: pi's "marker" needs the marker file, and the message
|
|
reads "(no turn marker)". Claude Code's is "exit".
|
|
- A usage end after a begin with no transcript reads `baseline-unknown`.
|
|
- `bus start --pm` refuses `harness-off` while `plugins.harness` is unset. At the re-pin the bus
|
|
starts without `--pm`, Jason runs the plugins patch, then the PM launches.
|
|
|
|
## Changes outside the owned list
|
|
|
|
- `claudeVersion` moved out of the launcher into the claude-code module's `version()`, with
|
|
row 68's precedence (`MOSAIC_CLAUDE_BIN`, else PATH `claude`) and its tests, now in
|
|
`harness.test.mjs`. The manifest's `piVersion` and `claudeVersion` fields are replaced by one
|
|
`harnessVersion` from the module the route took. Nothing in the tree reads either old field
|
|
(grep over `packages`, `scripts` and the READMEs); old run manifests keep theirs.
|
|
- Not changed, follow-up: `packages/cli/systemd/mosaic-bus.service.in:16-17` still says the
|
|
pinned binary serves "the manifest's claudeVersion". It's a comment, but row 166 changes this
|
|
unit and Mos rechecks its quoting before that lands, so I left the file alone. One-word fix
|
|
after row 166 lands.
|
|
- `packages/cli/tests/launch-limit-checks.test.mjs` (row 125's file), the REQ-LIMIT-4 test.
|
|
It asserted the launcher never imports the plug-in loader. The launcher now loads its harness
|
|
through the loader, so the assertion is narrowed: the launcher's only `.load(` call is
|
|
`{ harness: [m.harness] }`, and no `modules(` call names a kind other than harness. A limit
|
|
check still can't load in the launcher. This is the one guarantee in another row's test that
|
|
got weaker, and it's the reviewers' call whether the narrowed form holds.
|
|
- `packages/bus/tests/project-runtime.test.mjs`, the broker import-closure test: the broker gains
|
|
two imports, `isAbsolute` from `node:path` (the `transcript` check) and `NAME` from
|
|
`packages/plugins/src/kinds.mjs` (the harness name pattern). kinds.mjs imports nothing and is
|
|
in `RUNNER_FILES`, so the sandboxed runner can load the broker.
|
|
- `packages/plugins/tests/loader.test.mjs`: each `loaded` map gains `harness: []`.
|
|
- `packages/webui/tests/settings-form.test.mjs`: the Agent limits form has no selects now, and
|
|
the save no longer sets an instance model. `shell.test.mjs`: the write list gains the
|
|
`/api/routes` POST, and the `method:` count goes from 6 to 7.
|
|
- Bus test fixtures (`chief-of-staff`, `credential-status`, `daily-quota-runtime`, `end-launch`,
|
|
`limits-runtime`, `process`, `tasks`, `tester`, `usage-runtime`): each business fixture sets
|
|
`plugins.harness` so its runtime launch binds. `usage` and `usage-runtime` bind a
|
|
`transcript`, and `createUsage` no longer takes `dataRoot`.
|
|
- READMEs beyond the brief's list: bus, cli, seat and webui, for the same changes.
|
|
|
|
## Round 2
|
|
|
|
Round 1 drew changes from both reviewers: Dewey, comment 29136 (rev 1947), and Darkwing,
|
|
comment 29137 (rev 1948). Each finding and what round 2 does about it:
|
|
|
|
| Finding | Round 2 |
|
|
|---|---|
|
|
| Dewey B1, Darkwing F1: the chief can change the assistant's harness, thinking and fallback through `assistants.<i>.vars` | Ruling (b), sage-1006: Q169 stands and the brief names the assistant as the one exception (0235fa9fd). The write already runs `checkRoutes`; the new chief-of-staff case proves it through the real bus, refusals and a passing save. Comments corrected in broker.mjs, launch-settings.mjs, routes-verb.test.mjs, the bus and business READMEs and this file. serve.mjs's comment is about the `routes.set` verb, which the chief's tool still can't reach, so it stays. |
|
|
| Dewey B2, Darkwing F3: no `thinking-unsupported` or dropped-fallback-columns mutant | m8a, m8b and m9 below. |
|
|
| Dewey B3, Darkwing F2: no test reads a thinking flag from a run | harness.test runs both real adapters against recording stand-in binaries (Tests table). |
|
|
| Dewey N1: `unknown-model` can't reach the Console | The broker passes `unknown-model` through for `routes.set`, and `setRoutes` checks the primary model before the file's uncoded refusal. routes-verb asserts both. The webui `ROUTE_REASONS` entry is now reachable. |
|
|
| Dewey N2: a fallback refused at `harnessOf` drops the primary's reason | launcher.mjs appends `(primary <harness>: <reason>)` to that refusal; launch-routes asserts it for `thinking-unsupported` and `harness-refused`. |
|
|
|
|
REQ-LIMIT-4: both reviewers accept the narrowed launch-limit-checks assertion. Darkwing's
|
|
optional tightening, pinning the single `createLoader` import, isn't in this round.
|
|
|
|
Darkwing's round 1 runs on ccdbc2232, by path only: `~/darkwing-scratch/r65/singles/` (33
|
|
changed test files singly, 311 pass, 0 fail), `mut/` (their m8-m11, all killed; m11 only
|
|
because the table never renders) and `probe/` (the F1 business-layer probe).
|
|
|
|
Follow-ups, no change in this row (Darkwing's notes):
|
|
|
|
1. `runner.mjs:276` spreads `session.harnessEnv` last, so a module's env can shadow a core
|
|
variable. The brief puts env in the module.
|
|
2. `launcher.mjs` wraps `levels()` in `call()` but not the `.includes` and `.join` after it, so
|
|
a module returning a non-array throws a raw TypeError; a preflight result with no `code`
|
|
gives `Refusal(undefined)`.
|
|
3. `adapter()` and `mounts()` run after the workspace and session dirs exist; a throw there
|
|
leaves them behind.
|
|
4. `mosaic-bus.service.in:16-17` still names `claudeVersion`. Already on the row 166 list above.
|
|
5. Darkwing and Dewey, round 2: when the chief of staff sends a partial fallback through
|
|
`assistants`, the write is refused, but with `invalid-request` instead of
|
|
`organization-partial-fallback`. The refusal holds; only the code is less exact.
|
|
|
|
## Tests
|
|
|
|
| Test file | Covers |
|
|
|---|---|
|
|
| `packages/cli/tests/launch-routes.test.mjs` (new, 9) | primary taken when it passes; fallback on a failed preflight with limits on the fallback; no fallback refuses with the preflight code; both failing names both; account on a counting fallback; `no-route` per var with execution keys never used; `harness-off` primary or fallback; `harness-refused` and `harness-failed`; `thinking-unsupported` on primary and on a fallback taken; a fallback refused at `thinking-unsupported` or `harness-refused` names the primary's failure (round 2) |
|
|
| `packages/cli/tests/launcher.test.mjs` (row 65 case) | a real sandboxed launch on a fixture harness module no core file names, switched on in a test organization file: the primary fails preflight, the fallback runs, the manifest records route and reason |
|
|
| `packages/plugins/tests/harness.test.mjs` (new) | both modules load with every export; per-module answers; claude-code preflight codes; version probes; env and bundle files; the grep test, with the brief's exceptions listed and each still naming a harness; round 2: the real `adapters/pi/adapter.sh` and `adapters/claude/adapter.sh` run against stand-in `pi` and `claude` binaries that record their argv, with each module's `env()` for thinking `xhigh` and `low`, and the recorded argv holds `--thinking <level>` or `--effort <level>` exactly once |
|
|
| `packages/bus/tests/routes-verb.test.mjs` (new, 4) | human-only for every non-human cap including the chief; `routes` not in `ORGANIZATION_KEYS`; `harnessOn` binds; `harnessLevels`; the runtime write path and each refusal leaving the bytes and the log unchanged; round 2: `unknown-model` for a primary or fallback model the table lacks |
|
|
| `packages/bus/tests/chief-of-staff.test.mjs` (round 2 case) | the chief's assistant write through the real bus: `harness-off` for an off harness, primary or full fallback; `thinking-unsupported` for claude-code at off; a partial fallback refused (`invalid-request`, the file layer's uncoded refusal); each with the bytes and the log unchanged. A valid route with a whole fallback saves, the vars equal the route, and the log's actor is `chief-of-staff.chief` (Dewey's suggested passing case) |
|
|
| `packages/business/tests/launch-settings.test.mjs`, `instance-settings.test.mjs`, `vars.test.mjs` | `checkRoutes` codes through each writer; partial fallback at the file layer |
|
|
| `packages/webui/tests/routes.test.mjs` (new, 5) | GET and POST routes, status codes, receipts, the identity log line |
|
|
| `packages/webui/tests/routes-browser.test.mjs` (new, 1) | the table lists every instance with six values; read-only without send; a full save read back from the file; partial fallback, thinking-unsupported, revision-conflict and harness-off each refused with the draft kept; "Nothing changed." posts nothing |
|
|
|
|
The chief's tool refusing a `routes` key is covered at the bus (`organization.set` with the
|
|
chief's cap); there's no separate chief-tool test.
|
|
|
|
## Mutants
|
|
|
|
`/mnt/storage/scratch/sage-r65-out/mutants.py`, run in a second worktree
|
|
(`/mnt/storage/scratch/sage-r65m`, checked out at 8d7c9e2f1 and the same files as the gated
|
|
tree by `diff -rq`, before and after the run), so the gated tree was never edited. Each mutant
|
|
replaces one anchor that appears exactly once, runs the tests named for it, and restores the
|
|
file from a checked backup. Round 2 ran after the gate, in the slot: the control run of the
|
|
union of the tests passed 87/87 first (round 1: 71/71, 8 mutants).
|
|
|
|
| Mutant | Edit | Killed by |
|
|
|---|---|---|
|
|
| m1-no-route | `sessionModel` returns the system file's `execution.adapter` instead of throwing `no-route` | launch-routes `no-route` test; bundle.test `sessionModel` |
|
|
| m2-harness-off-launch | launcher skips the `harness-off` check | launch-routes `harness-off` primary or fallback |
|
|
| m3-harness-off-write | `checkRoutes` skips `harness-off` | 5 tests: launch-settings (3, Q169 assistants among them), routes-verb runtime, webui routes |
|
|
| m4a-partial-fallback-file | vars.mjs skips the partial-fallback check | vars.test `types` (the partial cases at lines 73-74) |
|
|
| m4b-partial-fallback-routes | `setRoutes` skips the partial-fallback check | launch-settings `setRoutes`; routes-verb runtime |
|
|
| m5-fallback-on-pass | a passing primary preflight is treated as failed when a fallback is set | launch-routes "a passing primary is the route" |
|
|
| m6-harness-name-in-core | `sandbox.mjs` gains `DEFAULT_HARNESS = "pi"` | harness.test grep test |
|
|
| m7-routes-in-org-keys | `routes` added to `ORGANIZATION_KEYS` | routes-verb human-only test |
|
|
| m8a-thinking-write | `checkRoutes` skips `thinking-unsupported` | launch-settings `setRoutes`; routes-verb runtime; the chief-of-staff Q169 route case |
|
|
| m8b-thinking-launch | launcher skips `thinking-unsupported` | launch-routes thinking test |
|
|
| m9-fallback-cells | the routing table drops its three fallback cells (`public/bus.js`, mutant tree only) | routes-browser, on the cells' deep-equal |
|
|
| m10a-pi-thinking-flag | pi's adapter never sets `--thinking` | harness.test adapter argv test |
|
|
| m10b-claude-effort | Claude Code's adapter drops `--effort` | harness.test adapter argv test |
|
|
| m11a-chief-skips-checks | `limits.set` skips `checkRoutes` when the actor is a role (`<definition>.<instance>`) | the chief-of-staff Q169 route case, on a missing refusal |
|
|
| m11b-chief-refused-route | the chief's assistant write is refused whenever it sets `harness` | the chief-of-staff Q169 route case, on the passing save |
|
|
| m12-unknown-model-masked | the broker drops the `routes.set` `unknown-model` pass-through | routes-verb runtime |
|
|
| m13-unknown-primary-uncoded | `setRoutes` checks only the fallback model | launch-settings `setRoutes`; routes-verb runtime |
|
|
| m14-fallback-refusal-unnamed | a fallback refused at `harnessOf` no longer names the primary's failure | launch-routes thinking test |
|
|
|
|
18 of 18 killed, each by an assertion failure in the named test, not a load error. Round 2
|
|
logs are in `/mnt/storage/scratch/sage-r65-out/mutants/` (console `mutants-r2.console.txt`),
|
|
round 1's in `mutants-r1/`. Darkwing's m11 shape (dropping the fallback `ROUTE_CELLS`, killed
|
|
only because the table never renders) is left out on purpose; m9 is the column check.
|
|
|
|
## Gate
|
|
|
|
Round 2: `/mnt/storage/scratch/sage-r65-out/gate2.sh` (gate.sh with the output dir `g2/`) on the
|
|
committed candidate 8d7c9e2f1, `dirty 0`. GATE_PASSED, 25 steps, each rc=0. 2514 node tests
|
|
(2358 package, 156 in test-queue) and 422 script selftests, 0 failed, summed with awk over
|
|
`g2.console.txt`. The same one webui skip. pkg-bus is 420, one more than round 1 (the new
|
|
chief-of-staff case); the other brief suites match round 1: pkg-plugins 111, pkg-business 148,
|
|
pkg-harness 123, pkg-seat 151, pkg-cli 344, pkg-webui 237, and every test-*.sh. Compose project
|
|
`sage-r65`; its network was removed on exit and no container was left.
|
|
|
|
Round 1: `/mnt/storage/scratch/sage-r65-out/gate.sh`, from the 2026-10-14 template, on d8336daef plus
|
|
the 64 changed files, the tree committed as ccdbc2232 with nothing else changed (`git diff HEAD
|
|
--quiet` after the commit): every package suite except queue, then every `scripts/test-*.sh`.
|
|
GATE_PASSED. 25 steps, each rc=0. 2513 node tests (2357 package, 156 in test-queue) and 422
|
|
script selftests, 0 failed, summed with awk over `g1.console.txt`. One skip, the existing webui
|
|
esbuild rebuild test (pinned esbuild not installed), which this row doesn't touch. The brief's
|
|
suites: pkg-plugins 111, pkg-bus 419, pkg-business 148, pkg-harness 123, pkg-seat 151, pkg-cli
|
|
344, pkg-webui 237, and every test-*.sh. Compose project `sage-r65`; its network was removed on
|
|
exit and no container was left.
|
|
|
|
## Landing
|
|
|
|
At landing Sage sends Jason, through Mos, the patch for the live business file:
|
|
`mosaic plugins set` adding `"harness": ["claude-code"]`. Nothing goes live before a re-pin on
|
|
Mos's go. Until that patch is in, a new launch refuses `harness-off`.
|
|
|
|
Landed 2026-10-19 as c216f47db, 8d7c9e2f1 cherry-picked onto 866f3c5a8 with the round 2 message.
|
|
Approvals: Darkwing 29144 (rev 1953) and Dewey 29145 (rev 1954), both on manifest 8df15e39.
|
|
Between 89ddb0225 and 866f3c5a8 only this packet, the queue files and SESSIONS changed, none of
|
|
them a candidate path. The landed commit's path list has the same sha256 as the candidate's, all
|
|
64 manifest hashes check against the landed tree, and the public/ +/- digest is a91cf866, the one
|
|
Mos cleared (mos-0841 condition 3).
|
|
|
|
Gate g3 (`/mnt/storage/scratch/sage-r65-out/gate3.sh`) on c216f47db, `dirty 0`: GATE_PASSED, 25
|
|
steps, each rc=0. 2514 node tests (2358 package, 156 in test-queue) and 422 script selftests, 0
|
|
failed, summed with awk over `g3.console.txt`, the same counts as g2. The same one webui skip.
|
|
Network `sage-r65_default` removed, no container left.
|
|
|
|
The mos-0840 view is the Routing section of the Console's Settings page (`<h2
|
|
id="routes-title">Routing</h2>` in `packages/webui/src/public/bus.js`), which replaces the Role
|
|
models fieldset.
|
|
|
|
The patch for Jason, sent through Mos: read the revision with `mosaic plugins --business
|
|
mosaic-stack --json`, then run `mosaic plugins set --business mosaic-stack --file PATCH` with
|
|
`{"revision": "<that revision>", "plugins": {"harness": ["claude-code"]}}`. A kind the patch
|
|
doesn't name keeps its setting, so this adds harness to whatever `plugins` holds. It works only
|
|
on a stack pinned past c216f47db: an older setter refuses the harness kind as unknown. A new
|
|
bind reads `plugins.harness` from the file as it stands, so no bus restart is needed for it.
|
|
After that Sage runs one managed coder session and attaches the manifest (module, route,
|
|
counted model, thinking).
|