458 lines
29 KiB
Markdown
458 lines
29 KiB
Markdown
# Row 155 (#1649): the human and the chief of staff configure the assistant
|
|
|
|
Builder: Darkwing. Brief: `docs/plans/2026-10-16_q169-assistant-config.md`, section "The human
|
|
and the chief of staff configure the assistant", blob b2aeba0364dd (rev 1717). Jason's ruling Q169.
|
|
Base: refactor 7ce156259, origin/refactor after row 125 landed (rebased 2026-10-18; built on
|
|
9098c33f7, then 99d2b49c9). Worktree: `~/darkwing-scratch/r155/wt-125`; `wt` (9098c33f7) and
|
|
`wt2` (99d2b49c9) are kept unchanged. Reviewers: Dewey (security) and Filbert.
|
|
|
|
Status: code, tests and docs written, 30 modified files plus `packages/webui/tests/assistant.test.mjs`.
|
|
Rebased twice: onto 99d2b49c9 (rows 149, 124, 129 and 164; see "Rebase onto 99d2b49c9"), then
|
|
onto row 125's landing (see "Rebase onto row 125" and "Row 125 landed"). The run on 99d2b49c9
|
|
had 27 of 28 files green; the red one, webui `shell.test.mjs`, was my miss from the first round
|
|
and is fixed. The run of all 39 files on 7ce156259 is green (see "Row 125 landed"). The first
|
|
round's runs on 9098c33f7 (sage-0720) are under "Single-file runs". The full gate has not run;
|
|
it is Sage's. Candidate: `r3/build.patch` and `r3/candidate-manifest.sha256` on 0473574b0
|
|
(see "Candidate round 3"); round 2 was `r2/` on 449cadd1f, round 1 `r1/` on 7ce156259.
|
|
|
|
## Rebase onto 99d2b49c9
|
|
|
|
Three commits since 9098c33f7 touch this row's files: 70504d23a (row 124, token usage),
|
|
03bbd2c98 (row 149, roles and harness) and 850484a7d (row 129, Console chat). I saved the old
|
|
worktree's diff against 9098c33f7 (`~/darkwing-scratch/r155/out/pre-rebase-9098c33f7.patch`),
|
|
added a new worktree at 99d2b49c9 and applied the diff with `git apply --3way`. 26 files
|
|
applied cleanly. Three webui files conflicted, and each resolved as a union of both sides:
|
|
|
|
- `src/cli.mjs`: row 129's `chat = createChat({ call })` sits beside this row's `nameWrite`
|
|
and `assistantStop`, and `startServer` gets all three.
|
|
- `src/serve.mjs`: `BUS_VERBS` ends `'messages', 'usage', 'names'`, and `startServer` takes
|
|
`chat`, `nameWrite` and `assistantStop`.
|
|
- `src/public/bus.js`: row 124's `tokenUsage` stays above this row's assistant block.
|
|
`settingsView` reads `usage` first, keeping row 124's rethrow on 403 or signed-out, then
|
|
`names`, and renders the launch form, the Assistant section, then Token usage.
|
|
|
|
`webui/tests/assistant.test.mjs` is untracked in the old worktree, so the diff-based patch
|
|
did not carry it. I copied it over unchanged (sha256 4e921ce653cc…) and marked it
|
|
intent-to-add. Every changed source passes `node --check`, `public/bus.js` parses, and
|
|
`git diff --check` is clean. The rebased diff is `out/rebased-99d2b49c9.patch` in my scratch:
|
|
30 files, +800 -104.
|
|
|
|
The rebased run (`~/darkwing-scratch/r155/run-rebased.sh`, each file singly) covers the
|
|
15 files of the first round plus the tests rows 124, 129 and 149 added or changed beside the
|
|
merged sources: webui `usage`, `usage-browser`, `chat`, `chat-browser`, `browser`, `shell`
|
|
and `views`; bus `usage`, `usage-broker`, `usage-runtime` and `workflow-roles`; business
|
|
`workflow-roles`; harness `raise-domain`. 28 files.
|
|
|
|
Rebased run (sage-0876), 2026-10-18T19:58:43Z to 19:59:52Z, behind `busy.sh`, TMPDIR in my
|
|
scratch: 27 of 28 files green, 236 pass and 1 fail summed over the logs. Per-file totals are in
|
|
`~/darkwing-scratch/r155/out/20261018T195844Z/summary.txt`. The sweep found 0 leftovers.
|
|
|
|
The failure is webui `shell.test.mjs`, the write-boundary test. It lists every POST, PUT, PATCH
|
|
and DELETE `fetch(...)` in `public/bus.js` and counts `method:`. This row's `assistantPost`
|
|
added `fetch(url, {method:'POST', ...})`, so the list had a fourth entry and the count was 4,
|
|
not 3. The assertion is the same at 9098c33f7, and row 129 did not change it. The first round
|
|
ran only the files I touched, so it never ran `shell.test.mjs`. This was my miss, not a rebase
|
|
effect.
|
|
|
|
The fix is in `shell.test.mjs` only:
|
|
|
|
- The expected list gets the `fetch(url, ...)` entry second, in source order.
|
|
- The `method:` count goes from 3 to 4.
|
|
- A new assertion pins every `assistantPost(` first argument to `url` (the definition),
|
|
`'/api/settings'`, `'/api/name'` and `'/api/assistant-stop'`. A fourth caller, or a computed
|
|
route, fails it.
|
|
|
|
`public/bus.js` is unchanged by the fix; its diff against 99d2b49c9 still has sha256
|
|
e37389fd…, the one going to Mos. The rerun of `shell.test.mjs`, with `settings-form` and
|
|
`assistant` as controls, waits for a slot. The rebased diff with the fix is
|
|
`out/rebased-99d2b49c9-r2.patch`: 31 files, +804 -106.
|
|
|
|
## Rebase onto row 125 (sage-0879)
|
|
|
|
Row 125 lands before this row and touches 11 of its files, so the run on 99d2b49c9 is not the
|
|
candidate. I dry-ran the rebase onto row 125's candidate 5289ca1d2 in
|
|
`~/darkwing-scratch/r155/wt-dry`. `git apply --3way` left 12 conflict blocks in 8 files.
|
|
`~/darkwing-scratch/r155/rebase/resolve125.py` resolves each block by index and refuses on a
|
|
count mismatch. Every resolution is a union:
|
|
|
|
- `docs/TOOLS.md`: row 125's token-limit paragraph, then this row's Assistant paragraph.
|
|
- Organization keys. There is one list, row 125's, with `assistants` appended:
|
|
`revision, limits, models, instances, reset, assistants`. It is the same in `SETTINGS_KEYS`
|
|
(`launch-settings.mjs`), both `ORGANIZATION_KEYS` (`broker.mjs`, `harness/src/tools.mjs`;
|
|
row 125's `tools.test.mjs` holds the three equal) and the Console's `writable` (`serve.mjs`).
|
|
This row's `UPDATE_KEYS` is gone; `set` refuses a patch with no key other than `revision`
|
|
by reading `SETTINGS_KEYS`.
|
|
- `cli/src/limits.mjs`: row 125's `reset PATH...` verb and `formatTokens`, and this row's
|
|
`PATCH keys:` list, now with `reset`. `cli/tests/limits.test.mjs` pins the new list.
|
|
- `broker.mjs` `#claimEnd`: row 125's signature with `by = s.run ?? s.human` and both comments.
|
|
This row's callers pass five arguments, so their `by` is unchanged.
|
|
- `harness/src/tools.mjs`: `change_organization` describes both the assistant fields and the
|
|
token-limit reset, and its schema has both `reset` and `assistants`.
|
|
- `public/bus.js` `settingsView`: reads usage, names and token limits, and renders the launch
|
|
form, Assistant, Token limits, then Token usage.
|
|
- `serve.mjs`: `BUS_VERBS` ends `'usage', 'limits', 'names'`. The refusal text names "a reset
|
|
or assistant settings".
|
|
- `shell.test.mjs`: five writes in source order (settings, row 125's reset post, `fetch(url,
|
|
...)`, project file, bus message), `method:` counted 5, and this row's call-site pin.
|
|
|
|
`node --check` passes on every changed source and `git diff --check` is clean. A static
|
|
evaluation of the shell test's regexes over the merged `bus.js` matches the expected list.
|
|
`assistant.test.mjs` came through with sha256 4e921ce653cc…. The dry diff is
|
|
`out/dry-5289ca1d2.patch` (31 files, +801 -104).
|
|
|
|
When row 125 lands, I redo the rebase on the landed commit, compare it with the dry run, and
|
|
run `~/darkwing-scratch/r155/run-125.sh` on Sage's slot. That is the 28 files above plus 11 row
|
|
125 test files on the merged sources: bus `limit-checks`, `limit-stop`, `limits-runtime` and
|
|
`process`; business `token-limit-settings`; cli `launch-limits`, `launch-limit-checks`,
|
|
`limits-tokens` and `thread-launch`; webui `limits-route` and `limits-browser`.
|
|
|
|
Sage's ruling sage-0713: no schema step (no 3i; row 124 keeps 3g to 3h). Row 134 stays its own
|
|
row, waits for row 155 to be done (rev 1722) and rebases on this landing. Until row 134, the
|
|
revision the chief of staff sends comes from the human; the bus, business, harness and cli
|
|
READMEs, `docs/TOOLS.md`, the tool description and `roles/chief-of-staff.md` say so. Row 156
|
|
also touches `broker.mjs` and the role md files; whichever lands second rebases.
|
|
|
|
## Row 125 landed (sage-0882)
|
|
|
|
Row 125 landed as aa625565f, and origin/refactor is now 7ce156259. The two commits after
|
|
row 125's packet (df4d5702f) touch only `docs/SESSIONS.md` and
|
|
`docs/plans/2026-10-14_prd-1.2-build.md`, neither in this row, so I based on 7ce156259 rather
|
|
than df4d5702f. aa625565f has the same tree as the dry run's base 5289ca1d2 (`git diff`
|
|
empty).
|
|
|
|
In `~/darkwing-scratch/r155/wt-125`, `git apply --3way` of `out/rebased-99d2b49c9-r2.patch`
|
|
left the same 8 files in conflict, and `resolve125.py` resolved the same 12 blocks with no
|
|
count mismatch. `node --check` passes on every changed source and `git diff --check` is clean.
|
|
`out/rebased-7ce156259.patch` is byte-identical to `out/dry-5289ca1d2.patch` (sha256
|
|
4dda2fc2…, 31 files, +801 -104), index lines included. `assistant.test.mjs` came through with
|
|
sha256 4e921ce653cc…, staged this time, not intent-to-add. The shell test's own regex over the
|
|
merged `bus.js` gives 5 writes equal to its expected list, 5 `method:` and the four
|
|
`assistantPost(` arguments. The `bus.js` diff for Mos is `out/busjs-7ce156259.diff`, sha256
|
|
9f9ce31b…, +53 -2.
|
|
|
|
Run (sage-0890), 2026-10-18T20:30:29Z to 20:31:58Z, `~/darkwing-scratch/r155/run-125.sh` with
|
|
`WT=~/darkwing-scratch/r155/wt-125`, behind `busy.sh`, TMPDIR in my scratch, each file singly:
|
|
39 of 39 files green. The awk sum over the 39 logs is 300 pass and 0 fail, with 0 skipped and 0
|
|
todo. `shell.test.mjs` passed 5 of 5. Per-file totals are in
|
|
`~/darkwing-scratch/r155/out/20261018T203030Z/`. The sweep found 0 leftovers.
|
|
|
|
## Candidate
|
|
|
|
`r1/build.patch` is the staged diff of `wt-125` against 7ce156259, byte-identical to
|
|
`out/rebased-7ce156259.patch`: sha256 4dda2fc2b7b2971b6daacaab57a027938bd074463ec25bfeb6314095d8503b52,
|
|
31 files, +801 -104. `r1/candidate-manifest.sha256` lists the sha256 of each of the 31 files
|
|
in `wt-125`; its own sha256 is 949873cfda99a2e3d418efee0066fa1438e1c12f69b9d2dfa9413ee089a79183.
|
|
The worktree had no unstaged change when both were written.
|
|
|
|
`busjs-7ce156259.diff` in this packet is the `public/bus.js` diff against 7ce156259 for Mos's
|
|
decision 123 go: sha256 9f9ce31bc14e6c0ed6d1d6c9f6cc282992d0631192be6416e0163866264029cd,
|
|
+53 -2. It is a copy of `out/busjs-7ce156259.diff` in my scratch.
|
|
|
|
## Candidate round 2 (sage-0900)
|
|
|
|
Row 162 landed as 7e7f8e208 and changed two of this row's 31 paths, `docs/TOOLS.md` and
|
|
`packages/cli/README.md`, so the r1 manifest no longer described the tree this row would land
|
|
on. Round 2 is confirm-only and makes no source change.
|
|
|
|
I applied `r1/build.patch` unchanged with `git apply --index` to a fresh worktree
|
|
(`~/darkwing-scratch/r155/wt-r2`) at 449cadd1f, refactor HEAD. It applied without conflict,
|
|
`git diff --cached --check` was clean, and the staged names are the same 31 files (+801 -104)
|
|
with no unstaged change. No run on this base: Sage's landing gate covers it, and
|
|
`host.test.mjs` reads the cli README.
|
|
|
|
`r2/build.patch` against BASE `449cadd1f` (sha256
|
|
`2a5a470d4c6fb53ca9c6ee8ffe4f5d8cb4c49b36b95f3f5ea1bc523224d94170`).
|
|
`r2/candidate-manifest.sha256` (sha256
|
|
`b1989f39012df6cbdd0e676cf19f75ccf88fcbf9d1c0a502eb71ab4247efad25`).
|
|
|
|
What moved from r1:
|
|
|
|
- In the manifest, only `docs/TOOLS.md` (now `e8fa08bf…`) and `packages/cli/README.md` (now
|
|
`7943058d…`). The other 29 sha256s are r1's.
|
|
- In `r2/build.patch`, only lines for those two files: their `index` lines and three hunk
|
|
headers (TOOLS.md `@@ -610` is now `@@ -636`, `@@ -627,6 +630` is now `@@ -653,6 +656`; the
|
|
cli README `@@ -789` is now `@@ -795`). Row 162 added text above these hunks, so they start
|
|
lower. With `index` and `@@` lines removed, r1 and r2 compare byte-identical.
|
|
- `public/bus.js`: its diff on 449cadd1f has the same sha256 as `busjs-7ce156259.diff`
|
|
(`9f9ce31b…`, +53 -2), index line included, since bus.js is unchanged between the two bases.
|
|
Mos's decision 123 go (mos-0816) was on those lines.
|
|
|
|
Round 1 verdicts on manifest 949873cf: Filbert approved (comment 29010, rev 1849), Dewey
|
|
approved (comment 29012, rev 1852). Each gave non-blocking follow-ups (see
|
|
`followup-brief.md`, which carries Mos's bus.js notes and Dewey's F1 on `serve.mjs` null
|
|
bodies; `body()` already refuses those, see its item 3).
|
|
|
|
## Candidate round 3 (sage-0906)
|
|
|
|
Sage's landing gate on r2 (candidate ba32dcad2 on 0473574b0, every suite run through, 20:53:10Z
|
|
to 20:59:57Z) had one failure out of 2370: `packages/bus/tests/channel-proof.test.mjs:18`,
|
|
"channel conversion preserves the four proof/transport byte baselines". Row 164 (008746b6e)
|
|
pins the sha256 of `packages/webui/src/bus.mjs` at :14, and this row's eight STATUS entries in
|
|
that file change it to `d1f3a274…e241`. My single-file runs missed it: the test reads
|
|
`bus.mjs` by path, not by import, and it isn't beside any file this row touches. That was my
|
|
miss. Two other tests read a manifest path literally (`stack-release`, `stack-copy` and webui
|
|
`service` read `packages/webui/src/cli.mjs`); they passed on Sage's gate.
|
|
|
|
Sage ruled to bump the pin: the `bus.mjs` diff is STATUS table entries only, and the freeze
|
|
guards the channel conversion, not later status codes. Round 3 changes only
|
|
`channel-proof.test.mjs:14`, the `bus.mjs` baseline, from `dbad731c…1f24` to
|
|
`d1f3a274f7aa15eb7ecc2267c65bf4fe7e45c898f77c34235b4e9c29cd1be241`, with a trailing comment
|
|
naming row 155. The other three baselines (`human.mjs`, `human-cli.mjs`, `transport.mjs`) and
|
|
the import check are unchanged, and none of those three files is in this row's manifest.
|
|
|
|
I applied `r2/build.patch` unchanged with `git apply --index` to a fresh worktree
|
|
(`~/darkwing-scratch/r155/wt-r3`) at 0473574b0, then made the one edit. Run on Sage's slot
|
|
(sage-0906), output in `~/darkwing-scratch/r155/out/r3-20261018T210058Z/`, 21:00:58Z to
|
|
21:01:13Z:
|
|
|
|
- `channel-proof.test.mjs` singly: 2 pass, 0 fail.
|
|
- Mutant, the `'name-taken': 409, …` STATUS line removed from `bus.mjs`: 1 pass, 1 fail, and
|
|
the failure names `packages/webui/src/bus.mjs`. Restored after, sha256 checked against
|
|
`d1f3a274…`.
|
|
- The whole `packages/bus` suite once (`node --test tests/*.test.mjs`): 399 pass, 0 fail, 0
|
|
skipped, 0 todo.
|
|
|
|
Totals are an awk sum over the logs. The sweep found 0.
|
|
|
|
`r3/build.patch` against BASE `0473574b0` (sha256
|
|
`9bee0e1b6546e70f06c1781beadc67082aa14990228f25571cdaf72e85f8c37e`), 32 files.
|
|
`r3/candidate-manifest.sha256` (sha256
|
|
`96cae28e095eff1632b7edcba195e1793bcabc3fb5aa93ea86d485c35e1f9016`), 32 paths.
|
|
|
|
What moved from r2: one new manifest line, `packages/bus/tests/channel-proof.test.mjs`
|
|
(`3e6d794b…`), and its one-line hunk in the patch. The other 31 manifest lines and every r2
|
|
patch line, index and `@@` lines included, are unchanged. The `public/bus.js` diff still has
|
|
sha256 `9f9ce31b…`, so Mos's go (mos-0816) applies.
|
|
|
|
## No schema step
|
|
|
|
The row needs no migration, so there is no 3i.
|
|
|
|
- `thread_claims` already has `op` (claim, release, revoke), `holder_run`, `by`, `reason` and
|
|
`decision` (`packages/bus/schema.sql`). A thread revoke is one more row there.
|
|
- `role.claim` on the thread path already refuses `run-revoked` for a run with a revoke row, so a
|
|
revoked assistant run can't claim its thread again.
|
|
- Names use row 141's `instance_names`. The new config key lives in the organization file, which
|
|
isn't the bus store.
|
|
|
|
The comment at `broker.mjs:2215` says a thread claim is never revoked and `thread_claims` has no
|
|
reason. Both stop being true here. `#claimEnd` now writes `reason` for a thread claim too, and the
|
|
comment says so.
|
|
|
|
## 1. What "configure" changes
|
|
|
|
A new organization key, `assistants`:
|
|
|
|
```json
|
|
{ "revision": "<64 hex>", "assistants": { "<instance>": { "vars": {}, "tracker": {}, "definition": "assistant" } } }
|
|
```
|
|
|
|
- Each field given replaces that field of the entry. `tracker: null` removes the tracker. A field
|
|
left out stays as it is. An empty entry is refused. An instance the file doesn't have is
|
|
`invalid-request`.
|
|
- Today `tracker: null` changes nothing in practice: the assistant definition uses no vikunja
|
|
credential, so `validateBusinessDocument` refuses any tracker on it and none exists to remove.
|
|
The field is there for a future assistant definition that has one.
|
|
- The target instance's current definition has to be the thread definition, or the write refuses
|
|
`organization-not-assistant`. A given `definition` has to be one the broker treats as the
|
|
assistant, or it refuses `organization-definition-refused`. Today that list is one name,
|
|
`assistant`, so a definition change is accepted only as a no-op. It's there so the field exists
|
|
when a second assistant definition does.
|
|
- `holder` and `credentials` aren't accepted (strict keys, `invalid-request`).
|
|
- The whole next document still goes through `validateBusinessDocument` and the unknown-model check.
|
|
A tracker on a definition with no vikunja credential is refused there, as it is for a hand edit.
|
|
|
|
No writer this row adds can give an instance a definition in `END_PROTECTED`. `assistants` only
|
|
targets instances that are already the assistant, and `instances` still sets only `vars.model`. On
|
|
top of that, `set()` now compares every instance's definition before and after and refuses
|
|
`organization-definition-refused` if any other one changed. That closes the dewey-0546 case
|
|
(an unprotected instance becoming the assistant after an arbiter resolved a revoke of it) for every
|
|
key the writer takes, not only the new one. It's defense in depth: no key today can change a
|
|
definition except `assistants`, which only accepts the same one.
|
|
|
|
*For Dewey.* `vars` replaces the whole object, including the `limits.tools`, `limits.network` and
|
|
`limits.authority` vars. Those only narrow: `resolve.mjs:64-68` filters the role file's tools and
|
|
authority by them and takes the narrower network, so a chief that writes wider ones gets nothing
|
|
the role file doesn't grant. A chief can remove a narrowing the human set, back up to the role
|
|
file. `checkVars` still checks every var.
|
|
|
|
One source for the definitions. `THREAD_DEFINITION` and `END_PROTECTED` move from `broker.mjs` into
|
|
`packages/business/src/vocabulary.mjs`, which the broker already imports for `ID_PATTERN`. The
|
|
broker and `launch-settings.mjs` read the same constants, so no equality test is needed. A test
|
|
holds `END_PROTECTED` including `THREAD_DEFINITION`, so the assistant can't lose its protection by
|
|
an edit to one list.
|
|
|
|
Files: `vocabulary.mjs`, `launch-settings.mjs` (`set` and `read`), `broker.mjs`
|
|
(`ORGANIZATION_KEYS`, the two constants), `packages/harness/src/tools.mjs` (its
|
|
`ORGANIZATION_KEYS` copy, which a test holds equal).
|
|
|
|
`read` adds `assistants`: each assistant instance's `definition`, `vars` and `tracker` as the file
|
|
has them, so the human and the Console see what they're replacing.
|
|
|
|
## 2. Who configures
|
|
|
|
- **Human, CLI.** `mosaic limits set --file PATCH` already sends the whole patch to `limits.set`,
|
|
so a patch with `assistants` works through it. `mosaic limits` prints the revision. The usage
|
|
line, the cli README and `docs/TOOLS.md` name the new key. I'm not adding a second command for
|
|
the same write.
|
|
- **Human, Console.** The Settings view gets an Assistant section per assistant instance: the
|
|
definition, the vars and the tracker as JSON fields, with the same Save, revision and receipt
|
|
handling as the limits form. `settingsSend`'s writable list adds `assistants`.
|
|
- **Chief of staff.** `change_organization` takes `assistants` as well (schema and description).
|
|
It runs `organization.set`, which checks `organization.change` before the writer and consumes it
|
|
after. Every other role is refused there, as today.
|
|
- **Revision.** Both carry the file's revision and a stale one refuses `revision-conflict` in
|
|
`organizationWriter.write`.
|
|
- **Log.** `organization.mjs` already writes the actor, `before`, `after` and `changes` to
|
|
`.history/<id>/changes.jsonl`. The actor is the human id, or `chief-of-staff.<instance>`.
|
|
|
|
**Gap: the chief can't read the revision.** No verb gives an agent the organization revision. Row
|
|
134 ("Organization revision read for a role holding organization.change", queued) owns that read.
|
|
Row 155 doesn't build it. Until row 134 lands, the chief's `change_organization` call works only
|
|
with a revision it was given (by the human, in a message), and the tests pass the revision in
|
|
directly. I've told Sage.
|
|
|
|
## 3. The seat name
|
|
|
|
- **Chief of staff.** A new typed tool, `set_name` (verb `name.set`, action `instance.name`), with
|
|
`instance` and `name`. Row 141 added the grant and the broker check; this adds the tool. With it,
|
|
`untypedActions(chief)` and the bundle's untyped list become `[]`, so `tools.test.mjs:43` and
|
|
`bundle.test.mjs:94` are re-pinned.
|
|
- **Human, Console.** A POST route, `/api/name`, with `{instance, name}` and the settings boundary
|
|
(signed-in header user, exact Origin, same-origin Fetch Metadata, JSON, strict keys, 4 KiB). It
|
|
calls `name.set` through `human-cli`, so the broker's `#human` check applies: a Console started
|
|
inside an agent run gets `human-required`. The Assistant section shows the current name, read
|
|
through a new `names` bus read. `names` is in `BUS_VERBS`, and the webui `cli.mjs` adds it beside
|
|
the shared views (`bus = { ...views({ call }), names: () => call('names') }`); `bus/src/views.mjs`
|
|
is unchanged, because the CLI doesn't need it. The route is `/api/name`; a second one,
|
|
`/api/assistant-stop`, is in section 4.
|
|
- **Human, CLI.** `mosaic name` already exists (row 141).
|
|
- Every other role is refused by `name.set`'s within-role check.
|
|
|
|
## 4. Removing the assistant: ending its runs
|
|
|
|
**role.revoke and role.end of a threaded instance.** Today the end path reads `role_claims` through
|
|
`#holder`, and the assistant claims in `thread_claims`, so the answer is `not-held`. For a threaded
|
|
target the end path now collects every thread of that instance whose latest claim is `claim`, and
|
|
writes a revoke for each. None held is still `not-held`. A role target keeps its one-claim path. The
|
|
result adds `threads: N` for a threaded target.
|
|
|
|
- A chief `role.end` with a reason passes `#protectedEnd` (row 139) and ends them all.
|
|
- A `role.revoke` with a decision the human resolved through the CLI passes `#protectedEnd` and ends
|
|
them all. An arbiter can't resolve that decision at all: `#hardWait` makes a revoke of an
|
|
`END_PROTECTED` target a hard wait, which goes to the human whoever raised it.
|
|
- The chief's own run's end stays refused (`a.role === s.role`).
|
|
|
|
**The null and undefined holder run.** The raise records `holderRun` as
|
|
`#holder(...)?.holder_run ?? null`, which is null for a threaded target. The check compared it with
|
|
`#holder(...)?.holder_run`, undefined, and answered `decision-mismatch`. Both sides now use `?? null`.
|
|
|
|
*For Dewey.* For a role target the decision binds to the holder incarnation at the raise. For a
|
|
threaded target there's no single incarnation, so it binds to nothing beyond the target, the
|
|
action, the raiser and the class. It's still single-use (`#spent`) and still human-resolved through
|
|
the CLI. If you want it bound tighter, the raise could record the set of held thread runs and the
|
|
check could require the same set. I think that refuses a legitimate revoke whenever a thread idles
|
|
out between raise and use, which is the common case.
|
|
|
|
**What the run does.** The runner polls every 2 s. Its next bus call gets `not-holder`, which is in
|
|
`SESSION_GONE`, and it exits 22. A turn already running finishes first, the same as `role.end` of a
|
|
role today. The launcher starts a new run only for a thread with pending messages or an open, so the
|
|
assistant comes back only when the human writes to it again. Removing the instance from the
|
|
organization file is out of scope.
|
|
|
|
**The human, Console.** A new human-only verb, `assistant.stop {instance}`.
|
|
|
|
- It checks `#human(s)` and that the instance is threaded.
|
|
- It revokes the current claim of every thread of that instance the human owns (REQ-USER-2: a user
|
|
reaches only their own threads), with `by` set to the human and reason `stopped by the human`.
|
|
None held is `not-held`. A known instance that isn't threaded is a new code, `not-assistant`
|
|
(webui status 400); an unknown one is `unknown-role`.
|
|
- It returns the stopped runs.
|
|
|
|
The Console route is POST `/api/assistant-stop` with `{instance}` and the same boundary as `/api/name`.
|
|
The button sits in the Assistant section. I chose per-instance over per-run because the Console
|
|
lists no threads or thread runs (`agents` reads `role_claims` only), and stopping the assistant is
|
|
what Q169's "remove" means. `mosaic stop <run>` still stops one run from the CLI.
|
|
|
|
The human can't use `role.revoke` itself: `#consumeAuthority` starts with `#agent`, which refuses a
|
|
human.
|
|
|
|
## 5. Text
|
|
|
|
- `packages/harness/src/bundle.mjs:112` and the `end_run` description (`tools.mjs:166`) say the
|
|
chief can't end the assistant's run. That's false after this row. Row 149's brief leaves the
|
|
`end_run` text in `bundle.mjs` to row 155.
|
|
- `roles/chief-of-staff.md:30-35`: the chief ends the assistant with `end_run`, configures it with
|
|
`change_organization` (`assistants`, with a revision from the human until row 134), and names
|
|
instances with `set_name`. Raising `policy.change` for those goes.
|
|
|
|
## What else changed
|
|
|
|
- `packages/cli/src/limits.mjs`: the usage line names `assistants`; `limits.test.mjs` pins it.
|
|
- `packages/webui/src/bus.mjs`: statuses for the codes the new routes and `assistants` can return
|
|
(`organization-not-assistant`, `organization-definition-refused`, `unknown-model`,
|
|
`name-is-instance`, `name-is-role` and `not-assistant` 400; `name-taken` and `not-held` 409).
|
|
- `roles/chief-of-staff.md`: the contract text names no person, because
|
|
`business/tests/chief-of-staff.test.mjs` refuses `Jason` in it.
|
|
- Re-pinned tests: `chief-of-staff.test.mjs:174`, `threads.test.mjs:396`, the bus and business
|
|
`hard-waits.test.mjs`, `tools.test.mjs`, `bundle.test.mjs`, `runner.test.mjs` (its fixture's
|
|
chief gets `instance.name`), `project-runtime.test.mjs` (the broker's import line) and webui
|
|
`shell.test.mjs` (the write-boundary list, after the rebase; see "Rebase onto 99d2b49c9").
|
|
|
|
## Tests (brief item 5)
|
|
|
|
| Brief item | Where |
|
|
|---|---|
|
|
| Human CLI and Console changes of vars, tracker and definition succeed and log the human | business `launch-settings.test.mjs`, cli `limits.test.mjs`, webui `settings-write.test.mjs` |
|
|
| Chief's change succeeds and logs the chief | bus `chief-of-staff.test.mjs` |
|
|
| Another role's change is refused | bus `chief-of-staff.test.mjs` |
|
|
| Definition change without protection refused; another instance given an END_PROTECTED definition refused | business `launch-settings.test.mjs` |
|
|
| Chief-raised revoke of the assistant is a hard wait to the human, no arbiter resolves it | bus `hard-waits.test.mjs` |
|
|
| Stale revision refused | business and bus |
|
|
| Human's and chief's name changes succeed, another role's refused | bus `chief-of-staff.test.mjs` and row 141's `names.test.mjs`, harness `tools.test.mjs` (`set_name`), webui `assistant.test.mjs` |
|
|
| Chief's end stops every assistant thread run; its own end stays refused | bus `threads.test.mjs` |
|
|
| Human-resolved revoke stops them; arbiter-resolved stays refused | bus `threads.test.mjs` |
|
|
| Console stop route ends an assistant run | bus `threads.test.mjs` (verb), webui `assistant.test.mjs` (route) |
|
|
|
|
Plus the re-pins above, a `set_name` tool test, and a browser test for the Assistant section in
|
|
`settings-form.test.mjs`.
|
|
|
|
### Single-file runs (sage-0720)
|
|
|
|
`~/darkwing-scratch/r155/run-touched.sh`: one `node --test` per file from its package directory,
|
|
behind `busy.sh`, TMPDIR `~/darkwing-scratch/r155/tmp`, logs teed. 15 files: the 13 this row
|
|
touches (12 modified, `assistant.test.mjs` new) plus business `chief-of-staff.test.mjs` and bus
|
|
`names.test.mjs`. No Docker, no full suites, no mutants.
|
|
|
|
- Run 1, 2026-10-18T15:34:50Z to 15:35:18Z: 13 green, 2 red.
|
|
- bus `threads.test.mjs`, 14 of 15. My test bound an inside-agent human session to show it can't
|
|
stop runs. `bindHuman` refuses that at the bind with human-required (pinned since
|
|
`broker.test.mjs:132`), so the session never reaches the verb. The test now asserts the bind
|
|
refusal and checks the verb with pm, chief and a. No broker change.
|
|
- webui `settings-form.test.mjs`, the Q169 test. `render` always reads `/api/settings` again,
|
|
so after a save the page took its revision from the fixture server, which only ever returned
|
|
revision a. The next post carried a where the test expected b. The page is right: a real server
|
|
returns the saved revision. The test's fetch mock now serves the saved state on reads after a
|
|
save. No `bus.js` change.
|
|
- Run 2, 15:38:33Z to 15:39:01Z: all 15 green, 167 pass, 0 fail. Logs:
|
|
`~/darkwing-scratch/r155/out/20261018T153833Z/`. Sweep found 0 leftovers.
|
|
|
|
That run covers the fixtures' `project.create` and `thread.create` calls, the chief's
|
|
`decision.raise` of a revoke with no grant, the decision-consumed check order and the browser
|
|
test's timing, which were unproven before it.
|
|
|
|
## Overlaps
|
|
|
|
- Row 149 (Dewey, in review) changes `roles/chief-of-staff.md` Authority text and the `raise_decision`
|
|
tool in `tools.mjs`. I'll rebase onto its landing before the in-review move and run every touched
|
|
test file singly.
|
|
- Row 156 (Sage, queued after 149) touches `broker.mjs` and the role md files. Whichever lands second
|
|
rebases.
|
|
- Row 124 (Rocko) does the Settings readout and schema 3h. The Settings view is shared; I'll check
|
|
its diff at rebase.
|
|
|
|
## Gate
|
|
|
|
Dewey and Filbert approve on #1649 naming the candidate manifest. Mos's decision 123 go on the
|
|
`public/` diff. Bus, business, harness, cli and webui suites and every `scripts/test-*.sh` green.
|