148 lines
10 KiB
Markdown
148 lines
10 KiB
Markdown
# Row 64: a managed session gets its role's skills as prompt text on both harnesses
|
|
|
|
Author and gate owner: Sage (mos-0855). Reviewers Darkwing and Filbert. Sage, 2026-10-19.
|
|
Brief `docs/plans/2026-10-11_managed-session-config.md`, anchor "Managed sessions get their role's skills on
|
|
both harnesses", blob 21ae25a3. Issue #1557. Rulings mos-0857 (reviewer checks), mos-0862 (mutant coverage).
|
|
|
|
Base 087d202bc. Candidate c6d7d538f, one commit, built in `/mnt/storage/scratch/sage-r64/wt` and gated in a
|
|
clean detached worktree at `/mnt/storage/scratch/sage-r64/cand`. The commit is in the canonical object store,
|
|
so `git show c6d7d538f` works from the canonical checkout. `candidate.diff` is that output.
|
|
`candidate-manifest.sha256` lists the 28 changed paths.
|
|
|
|
## The choice (`probe.md`)
|
|
|
|
The probe ran Claude Code 2.1.303 and read the pinned pi docs. On Claude Code a `--plugin-dir` skill works only
|
|
with the `Skill` tool on, and with it on, Claude Code's own bundled skills come along under names that belong to
|
|
the Claude Code version. On pi, `--skill` puts a description in the prompt and the model reads SKILL.md with a
|
|
file tool, which the gate refuses because the bundle is outside the workspace.
|
|
|
|
So the skill text goes into `prompt.md` on both harnesses, rendered from the bundle copies. Pi keeps
|
|
`--no-skills`. Claude Code keeps `--disable-slash-commands` and `--restricted`. Neither harness discovers a skill,
|
|
no new tool passes the gate, and the manifest's digests cover what the model reads.
|
|
|
|
## What changed
|
|
|
|
- `roles/*.json`: an optional `skills` list. Names match `/^[A-Za-z0-9._-]+$/`, with "." and ".." refused, no
|
|
repeats, none on version 1 role files (`role.mjs`). `resolve.mjs` carries the list on the record.
|
|
- First lists: 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. The
|
|
chief of staff lists none.
|
|
- `bundle.mjs` reads each skill from `<dataRoot>/skills-enabled/<name>`, copies it to `<bundle>/skills/<name>`
|
|
and renders every file, in sorted path order, between `<skill-file skill="..." path="...">` and
|
|
`</skill-file>` lines under a "## Skills" heading. A role with no list gets no heading, no skills directory and
|
|
an empty manifest list. The manifest records each skill's path and per-file sha256.
|
|
- Refusals: a listed skill that isn't enabled is skill-not-enabled and names `scripts/skill.sh activate`. A
|
|
symlink anywhere in the skill (the skill directory, SKILL.md, a sub-file) or a non-file entry, a file name
|
|
outside SKILL_PATH, non-UTF-8 bytes, or a file holding the end marker is skill-refused. `lstatSync` throughout,
|
|
so a symlink is refused, not followed.
|
|
- SKILL_BYTES is 64 KiB across all of a role's skills together. The claude adapter passes `prompt.md` as one
|
|
`--system-prompt` argument and Linux caps one argument at 128 KiB (MAX_ARG_STRLEN), so the cap leaves room for
|
|
the contract and the rest of the prompt. Bytes per role by `wc -c` over the repo copies: coder 16853,
|
|
reviewer 16252, cto 35888, pm 29080. Each fits (skills.test.mjs, the SKILL_BYTES test).
|
|
- `launcher.mjs:254` checks `readSkills(resolved.skills, skillsDir)` after the route and contract checks and
|
|
before the harness-off and binary checks, so the refusal comes before any run exists and doesn't depend on the
|
|
harness. skill-not-enabled stays skill-not-enabled, anything else becomes skill-refused.
|
|
- `adapters/README.md` and `packages/harness/README.md` describe the section and the flags. The other test
|
|
edits give the fixtures a `skills-enabled` directory.
|
|
|
|
## Tests the brief names, each on both harnesses
|
|
|
|
| Brief test | Where |
|
|
|---|---|
|
|
| exactly the contracted skills | skills.test.mjs:45, four roles x pi and claude-code, prompt vs bundle copy vs source, byte for byte |
|
|
| a contracted skill that isn't enabled refuses before any spawn | skills.test.mjs:109 (build, both harnesses), launcher.test.mjs row 64 pi test and the row 64 Claude Code test (launch, no run made) |
|
|
| an enabled but unlisted skill is absent | skills.test.mjs:92, both harnesses |
|
|
| no list: Pi runs with --no-skills, Claude Code gets none | skills.test.mjs:79 (build, both harnesses) and :170 (chief through each real adapter) |
|
|
|
|
skills.test.mjs:170 runs each real `adapter.sh` against a stand-in CLI that records argv. For the reviewer and
|
|
the chief on each harness it checks:
|
|
|
|
- one `--system-prompt`, equal to `prompt.md` less trailing newlines
|
|
- every marked file equal to its bundle copy
|
|
- no skill path or marker anywhere else in argv
|
|
- `--no-skills` and no `--skill` on pi, `--disable-slash-commands` and `--restricted` on Claude Code
|
|
|
|
The four-way loop and the Claude Code launcher test were added after gate run 1 started, when I read the brief's
|
|
"each on both harnesses" against the tests. I stopped run 1 (bus and business had passed). Run 2 is on the
|
|
amended commit.
|
|
|
|
## Mutants (`mutants.py` and `mutants-run2.txt` here, run 2 on c6d7d538f, logs in `/mnt/storage/scratch/sage-r64/mutants/`)
|
|
|
|
| Mutant | Change | Caught by |
|
|
|---|---|---|
|
|
| M1 | drop the launcher precheck | launcher.test.mjs, both row 64 tests |
|
|
| M2 | map skill-not-enabled to skill-refused | launcher.test.mjs, both row 64 tests |
|
|
| M3 | no SKILL_PATH check | skills.test.mjs |
|
|
| M4 | no SKILL_BYTES check | skills.test.mjs |
|
|
| M5 | no end-marker check | skills.test.mjs |
|
|
| M6 | the walk follows symlinks (statSync) | skills.test.mjs |
|
|
| M7 | the found check follows symlinks | skills.test.mjs |
|
|
| M8 | no newline before the end marker | skills.test.mjs |
|
|
| M9 | role names allow "." and ".." | role.test.mjs |
|
|
| M10 | resolve drops the skills list | skills.test.mjs (resolve.test.mjs passes) |
|
|
| M11 | Claude Code without --disable-slash-commands | skills.test.mjs |
|
|
| M12 | Claude Code without --restricted | skills.test.mjs |
|
|
| M13 | pi without --no-skills | skills.test.mjs |
|
|
|
|
13 of 13 killed. M10 is caught only by skills.test.mjs. Mos ruled that enough (mos-0862): a mutant counts as
|
|
killed if any gate test catches it. M6 and M7 add `statSync` to the import so they fail on behavior, not on a
|
|
ReferenceError. The files were restored byte for byte (`restored True`) and the orphan sweep found nothing.
|
|
|
|
## Base drift
|
|
|
|
`packages/harness/README.md` and `packages/harness/tests/bundle.test.mjs` changed on refactor in row 134
|
|
(3a600cf5b), in different hunks from this row. The rebase onto 087d202bc was clean, and the gate runs on that
|
|
base.
|
|
|
|
## Gate
|
|
|
|
`/mnt/storage/scratch/sage-r64/gate.sh` from the template, on the clean worktree at c6d7d538f, dirty 0, with
|
|
the manifest checked before any suite. Run 2: GATE_PASSED (`gate-run2.txt` here, logs in `/mnt/storage/scratch/sage-r64/out/`). 15 package suites,
|
|
2384 pass and 0 fail by an awk sum over the log (harness 133, cli 349, business 149, plugins 111). All 10
|
|
script suites passed. The compose network was removed, and no container from the gate is left.
|
|
|
|
Run 1, on the earlier commit ece9fb5a3, was stopped by hand after bus and business passed (`gate-run1.txt`,
|
|
`out-stopped-run1/`). It didn't fail. I stopped it to add the both-harness tests above.
|
|
|
|
## For the reviewers (mos-0857)
|
|
|
|
1. The rendered `prompt.md` matches the bundle copies byte for byte: skills.test.mjs:45 for the build and :170
|
|
for what each adapter actually passes.
|
|
2. `--no-skills`, `--disable-slash-commands` and `--restricted` are still on: `adapters/pi/adapter.sh`,
|
|
`adapters/claude/adapter.sh`, and skills.test.mjs:170 with M11-M13.
|
|
3. `candidate-manifest.sha256` against `git show c6d7d538f`.
|
|
4. The 64 KiB cap against the one-argument limit, and that every repo role fits.
|
|
5. That the launcher precheck comes before any run. (Round 1 correction, Darkwing N1: the code also puts it
|
|
before the harness-specific checks, but no test pins that order. The brief asks for "before any spawn",
|
|
which the launcher tests check.)
|
|
|
|
Landing waits for Mos's go (mos-0856, mos-0861 A2): activate the skills in the live data root, then start one
|
|
managed coder session per harness and attach the manifest.
|
|
|
|
## Round 1 follow-ups
|
|
|
|
- F1 (Filbert, #1557 comment 29218, doc-only): `packages/harness/README.md:55-58` says a symlinked skill, a bad
|
|
file name, non-UTF-8 bytes, the end marker and an over-cap list refuse `skill-not-enabled`. The code gives
|
|
them `bundle-refused`, which the launcher maps to `skill-refused`. No test reads that README (grep of
|
|
packages/*/tests and scripts/test-*.sh), so under decision 137 it doesn't block. It gets its own doc commit
|
|
at landing, after the reviewed candidate, so the approved manifest stays as reviewed.
|
|
- Darkwing (#1557 comment 29219) N1: covered by the item 5 correction above. N2: a leading UTF-8 BOM is stripped
|
|
by the decoder, so the copy and digest would be 3 bytes off (`ignoreBOM: true` fixes it). N3: EACCES or
|
|
ENOTDIR inside a skill dir comes out as launch-failed or a raw throw, not skill-refused, though the launch
|
|
still refuses. N2 and N3 are follow-up issue #1672. N4: the cli and webui fixture edits are mechanical.
|
|
N5: past this row, coder, reviewer, cto and pm launches refuse until all eight listed skills are active in
|
|
the data root. Live has only ms-communications. Any re-pin past row 64 activates them first (sage-1064 R4).
|
|
- Landing prep (sage-1065): cherry-pick 9f0039711 of c6d7d538f on 1a01f63ea, range-diff `=`, patch-id
|
|
7e1948e2f2e9 equal. F1 doc commit 6b6567b8b. `landing-manifest.sha256` is the same 28 paths at 6b6567b8b; 27
|
|
match the candidate manifest and the README differs by F1 only. Nothing goes onto refactor before Mos's go.
|
|
|
|
## Landing (mos-0863)
|
|
|
|
Mos gave the go for steps 1, 2, 3 and 5 in mos-0863. Step 4, the live activation, is now a window step
|
|
(D3, `agents/sage/work/window-plan/carry-past-row64.md`). N2 and N3 are #1672, with its own row after 173 (D4).
|
|
D5: the item 5 correction above is the N1 text fix, in this packet commit.
|
|
|
|
The landing gate ran `gate-land.sh` on a clean worktree at 6b6567b8b, with `landing-manifest.sha256` checked
|
|
before any suite (`gate-land-run1.txt` here, logs in `/mnt/storage/scratch/sage-r64/out-land/`). 9f0039711 and
|
|
6b6567b8b go onto refactor by fast-forward from 1a01f63ea, so the landed shas are the gated ones.
|