pi-session-mcp 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/.pi-session-mcp-cli/config.js +47 -7
  2. package/.pi-session-mcp-cli/doctor.js +68 -2
  3. package/.pi-session-mcp-cli/execution-profile.js +1 -0
  4. package/.pi-session-mcp-cli/extension-spec.js +7 -2
  5. package/.pi-session-mcp-cli/setup-result.js +8 -0
  6. package/.pi-session-mcp-cli/skill-catalog.js +35 -0
  7. package/.pi-session-mcp-cli/skill-customize.js +183 -0
  8. package/.pi-session-mcp-cli/skill-library.js +120 -0
  9. package/CHANGELOG.md +110 -0
  10. package/README.md +15 -3
  11. package/dist/capabilities.d.ts +1 -0
  12. package/dist/capabilities.js +2 -1
  13. package/dist/client-adapters/mcp-verifier.js +6 -2
  14. package/dist/config.d.ts +4 -0
  15. package/dist/config.js +47 -7
  16. package/dist/doctor.d.ts +4 -1
  17. package/dist/doctor.js +68 -2
  18. package/dist/execution-profile.d.ts +4 -0
  19. package/dist/execution-profile.js +1 -0
  20. package/dist/extension-spec.js +7 -2
  21. package/dist/pi-adapter.d.ts +1 -1
  22. package/dist/pi-adapter.js +1 -0
  23. package/dist/pi-skills.d.ts +17 -0
  24. package/dist/pi-skills.js +64 -0
  25. package/dist/sdk-pi-adapter.d.ts +7 -3
  26. package/dist/sdk-pi-adapter.js +57 -11
  27. package/dist/server.js +1 -0
  28. package/dist/session-tools.d.ts +1 -1
  29. package/dist/session-tools.js +20 -4
  30. package/dist/setup-cli.d.ts +2 -0
  31. package/dist/setup-cli.js +16 -0
  32. package/dist/setup-main.d.ts +1 -1
  33. package/dist/setup-main.js +18 -3
  34. package/dist/setup-ownership.d.ts +6 -0
  35. package/dist/setup-ownership.js +29 -5
  36. package/dist/setup-skills.d.ts +37 -0
  37. package/dist/setup-skills.js +353 -0
  38. package/dist/setup.d.ts +7 -0
  39. package/dist/setup.js +49 -6
  40. package/dist/skill-catalog.d.ts +26 -0
  41. package/dist/skill-catalog.js +35 -0
  42. package/dist/skill-customize.d.ts +23 -0
  43. package/dist/skill-customize.js +183 -0
  44. package/dist/skill-library.d.ts +61 -0
  45. package/dist/skill-library.js +120 -0
  46. package/docs/acceptance.md +372 -3
  47. package/docs/adr/0006-pi-extension-allowlist.md +5 -2
  48. package/docs/adr/0007-profile-skill-selection.md +217 -0
  49. package/docs/architecture.md +9 -5
  50. package/docs/client-setup-design.md +133 -1
  51. package/docs/client-setup.md +74 -3
  52. package/docs/doctor-design.md +9 -1
  53. package/docs/installation.md +129 -9
  54. package/docs/pi-extensions-design.md +41 -12
  55. package/docs/releasing.md +15 -6
  56. package/docs/session-mcp.md +7 -2
  57. package/docs/skills.md +487 -0
  58. package/docs/threat-model.md +21 -4
  59. package/docs/tool-contracts.md +9 -5
  60. package/package.json +3 -1
  61. package/pi-session-mcp.example.json +9 -3
  62. package/skills/pi-session-implementation/SKILL.md +42 -0
  63. package/skills/pi-session-orchestration/SKILL.md +93 -0
  64. package/skills/pi-session-review/SKILL.md +58 -0
  65. package/docs/superpowers/plans/2026-10-10-macos-setup.md +0 -1725
  66. package/docs/superpowers/plans/2026-10-11-pi-extensions.md +0 -2450
@@ -97,7 +97,12 @@ env -i HOME="$DISPOSABLE_HOME" PATH="$PATH" PI_SESSION_MCP_CONFIG="$ACCEPTANCE_C
97
97
  ### Pass criteria
98
98
 
99
99
  - All nine operations report the expected overall status, finding and exit
100
- status.
100
+ status. From the change that tracks #46 on, every operation also reports the
101
+ controller skill finding (`skill_absent` twice, `skill_installed`,
102
+ `skill_already_equivalent`, `skill_verified`, `skill_removed`,
103
+ `skill_installed`, `skill_verified`, `skill_removed`); the skill file exists
104
+ after steps 3 and 7 and is gone after steps 6 and 9; a foreign skill placed
105
+ in the same skill directory stays unchanged throughout.
101
106
  - Steps 1 and 2 leave the client state unchanged.
102
107
  - After step 9 the `pi-session-mcp` registration is absent, the sentinel is still
103
108
  present, and the ownership record set is empty. The ownership directory has
@@ -124,6 +129,55 @@ that checkout with Doctor reporting the release version, and confirms that the
124
129
  manifests and built runtime files are byte-identical before and after the
125
130
  client cycles.
126
131
 
132
+ ### Tooling
133
+
134
+ `scripts/setup-acceptance.mjs` runs the isolation, the sequence and the pass
135
+ criteria above with the real clients. It uses disposable homes below its work
136
+ directory (default `~/.cache/pi-session-mcp-acceptance`, never below `/tmp`,
137
+ `/var/tmp`, `/dev/shm` or `$TMPDIR`), removes them afterwards (also after Ctrl-C
138
+ or SIGTERM, with exit `130` or `143`) and writes a sanitized JSON result without
139
+ paths or environment values. It exits with `0` when every check passed, `1` when
140
+ a check failed and `2` on a usage, environment or harness error.
141
+
142
+ `--setup` is split on whitespace, so paths in it must not contain spaces. Its
143
+ first token must be an absolute path to an existing file or a command found on
144
+ `PATH`; otherwise the script exits with `2`.
145
+
146
+ The script only reads the real `~/.codex/config.toml` and `~/.claude.json` to
147
+ prove that the MCP registrations in them did not change. A file that does not
148
+ exist counts as the constant digest `absent`; one that exists but cannot be read
149
+ fails the check. A real client session that changes its own MCP registrations
150
+ during the run makes the digest check fail (a false fail), so close or idle real
151
+ clients first.
152
+
153
+ Working tree, after `npm run build` and `npm run build:cli`:
154
+
155
+ ```bash
156
+ node scripts/setup-acceptance.mjs \
157
+ --setup "node $PWD/dist/setup-main.js" --build-dir "$PWD" \
158
+ --label working-tree --out level2-result.json
159
+ ```
160
+
161
+ Published package: install it into a temporary prefix outside `/tmp`, then pass
162
+ the installed executable and package directory.
163
+
164
+ ```bash
165
+ npm install -g --prefix "$PREFIX" pi-session-mcp@<version>
166
+ node scripts/setup-acceptance.mjs \
167
+ --setup "$PREFIX/bin/pi-session-mcp-setup" \
168
+ --build-dir "$PREFIX/lib/node_modules/pi-session-mcp" \
169
+ --label npm-<version> --out level2-result.json
170
+ ```
171
+
172
+ Tag clone: build a fresh clone of the tag (`npm ci`, `npm run build`,
173
+ `npm run build:cli`) and use it like the working tree, with `--setup` and
174
+ `--build-dir` pointing at the clone.
175
+
176
+ `--clients codex` or `--clients claude-code` limits a run to one client, for
177
+ example on a macOS machine without Codex. The script does not cover the macOS
178
+ lock-contention and interruption steps, Level 1, or the tag identity checks
179
+ (tag object, peeled commit, `git describe`). Run those by hand.
180
+
127
181
  ### macOS
128
182
 
129
183
  macOS uses the same isolation, sequence and pass criteria, with the additions
@@ -154,7 +208,7 @@ CI-tested, and it has not passed Level 2 for Codex.
154
208
  - **Interruption.** Start `--apply` for one target and press Ctrl-C while it
155
209
  runs.
156
210
  - The run exits with `130` and reports `operation_interrupted`.
157
- - Afterwards `ps -A -o pid=,command=` lists no `claude mcp` or `codex mcp`
211
+ - Afterwards `ps -ww -A -o pid=,command=` lists no `claude mcp` or `codex mcp`
158
212
  command and no `setup-main.js` or `setup-command-guardian.js` process of
159
213
  the build under test.
160
214
  - A following `--apply` reports one of three outcomes, depending on where
@@ -166,7 +220,8 @@ CI-tested, and it has not passed Level 2 for Codex.
166
220
  - A following `--remove` reports `ok (removed)`.
167
221
  - If the operation completes before the interrupt arrives, record that and
168
222
  repeat. An interrupt that never lands is not a pass.
169
- - **Processes.** Check that no process remains with `ps` instead of `/proc`.
223
+ - **Processes.** Check that no process remains with `ps -ww` instead of `/proc`;
224
+ without `-ww`, `ps` truncates long commands when it has no terminal.
170
225
 
171
226
  The evidence record names the build (commit or tag), the environment above,
172
227
  every command with its exit status, and the sanitized JSON results. It
@@ -273,6 +328,99 @@ Manual, on a machine with Pi, `pi-cursor-sdk@0.5.3` and a stored Cursor key. Run
273
328
 
274
329
  Record date, source commit, versions and outcome under Status. No secret, path or prompt content goes into the record.
275
330
 
331
+ ## Multi-session skill acceptance
332
+
333
+ A targeted live check for profile skill selection ([skills](skills.md)) and for
334
+ several Pi sessions that one real controller drives at the same time. It is not
335
+ the ten-task Level 3 cycle and does not replace it.
336
+
337
+ The maintainer script `scripts/multi-session-acceptance.mjs` prepares the run,
338
+ starts the controller and checks the outcome. The controller itself calls the
339
+ Pi Session MCP tools; the script never does.
340
+
341
+ ### Preparation
342
+
343
+ - Candidate: a separate detached checkout of the commit under test, built with
344
+ `npm ci`, `npm run build` and `npm run build:cli`. `prepare` records the
345
+ commit, whether the checkout is clean, the SHA-256 of `dist/main.js` and,
346
+ from the harness of `cc810c9` on, a SHA-256 over every built module in
347
+ `dist/`; `run` refuses a changed build.
348
+ - Configuration: `prepare --base-config <operator config> --from-profile
349
+ <alias>` copies only provider, model and thinking level of one working profile
350
+ without extensions or MCP servers into a temporary configuration with two
351
+ profiles: `accept-read` (default, `read-only`, skill `review`) and
352
+ `accept-code` (`coding`, skills `implementation` and `handshake`). From the
353
+ harness of `3f98104` on, all three are `builtin:` references: the two worker
354
+ skills ship with the candidate, and the `handshake` skill sits in the
355
+ override directory `skills/` next to the run configuration, so the run also
356
+ exercises library resolution. The `handshake` skill is generated per run and
357
+ holds a random token that no controller prompt contains.
358
+ - Fixtures: four small synthetic git repositories per client (`slug`, `stats`,
359
+ `wait`, `range`), each a separate workspace alias (`cc-*` for Claude Code,
360
+ `cx-*` for Codex). Both clients use the same configuration and build.
361
+ - Controller skill: from the harness of `3f98104` on, `prepare` installs the
362
+ Claude Code controller skill the way an operator does, with the candidate's
363
+ `pi-session-mcp-setup --apply --target claude-code:project:pi-session-mcp` in
364
+ the controller's directory. The setup step runs with a disposable `HOME`, so
365
+ setup state and client files of that step never touch the real home; it must
366
+ report `applied` and `skill_installed` and leave a regular copy identical to
367
+ the shipped skill. Codex installs skills only in the user's home, which the
368
+ run must not change, so the Codex controller gets one run-scoped symbolic link
369
+ in `.agents/skills` of its directory. Earlier runs linked both. No global
370
+ skill directory changes.
371
+ - Clients: Claude Code runs as `claude -p` with `--mcp-config`,
372
+ `--strict-mcp-config`, `--setting-sources project,local`,
373
+ `--permission-mode dontAsk` and an `--allowedTools` list of the eight tools
374
+ plus read-only file tools, `sleep`, `node --test` and `git -C`. Codex runs as
375
+ `codex exec --ignore-user-config --sandbox read-only` with the server
376
+ defined through `-c mcp_servers.pi-candidate.*` and the three per-tool
377
+ approvals from [client setup](client-setup.md#run-codex-without-prompts).
378
+ Neither run changes a persistent registration, `HOME` or `CODEX_HOME`, and
379
+ both use the existing authentication.
380
+
381
+ ### Scenarios
382
+
383
+ Each client runs two invocations, one per scenario:
384
+
385
+ | Scenario | What the controller does | What is checked |
386
+ | --- | --- | --- |
387
+ | `parallel` | Capabilities first; two sessions of `accept-code` on `slug` and `stats`; both first turns started before waiting; one follow-up in the `slug` session that adds a test and completes the handshake from the worker's skills; own verification; close both | Candidate version and configuration fingerprint seen through capabilities; `skills` of `accept-code`; first turns overlap by the server's `startedAt` and `completedAt`; follow-up completed; tests pass and only allowed files changed; handshake file holds the token although no prompt contained it |
388
+ | `abort` | Two sessions on `wait` (a bounded `sleep 120` command) and `range` (a bounded `sleep 45`, then the change); abort `wait` about 15 seconds after both turns started, while `range` runs; verify and close both | Every abort result is `ok` (an `abort_timeout` stays on record even after a retry); the first successful abort comes before `pi_turn_get` shows the `wait` turn `aborted`, and both before its session is closed; its abort time lies inside the running interval of `range`; `range` completed and verified; at the end of the run no late file exists and no worker `sleep` remains |
389
+
390
+ ### Independent checks
391
+
392
+ - The script watches `/proc` during the run and records every process of the
393
+ controller tree, every server of this run (entry point and configuration
394
+ path) and every process whose working directory lies inside the run, by PID
395
+ and start time. After the controller exits and the server's 15-second
396
+ shutdown bound has passed, none of them may remain.
397
+ - From the controller transcript it extracts only tool names, aliases, states,
398
+ timestamps and counts. It scans every Pi Session MCP result, with
399
+ `assistantText` removed, for local paths, the token and credential markers,
400
+ and counts mentions in `assistantText` separately.
401
+ - Fixture oracles run on the workspace after the controller exits: `node
402
+ --test` and `git status` per fixture. Every changed file must have been last
403
+ written during a worker turn of its task's session, and `HANDSHAKE.txt`
404
+ during the follow-up turn, by the server's turn timestamps. This catches a
405
+ write outside the worker turns, not one during them.
406
+ - Controller writes are limited by the client restrictions: the Codex
407
+ controller runs in a read-only sandbox, and the Claude Code controller has no
408
+ write or edit tool, although its allowed `git -C` could still change a
409
+ fixture.
410
+ - The handshake token must appear neither in a worker prompt nor anywhere in
411
+ the controller transcript, and no worker prompt may name a private path or a
412
+ skill file. The controller's own reads and commands must not name the run's
413
+ private directory or its files; this is a marker check, not a sandbox.
414
+ - The harness records its own SHA-256 at `prepare` and refuses to run or
415
+ re-evaluate a run with another version.
416
+ - `evidence` writes one sanitized JSON record and refuses to write a local path
417
+ or the token. Transcripts and the configuration stay in the run's private
418
+ directory and are never published.
419
+
420
+ A controller that reports success does not count; only the checks above do.
421
+ If a client or its authentication is not available, record that client's part
422
+ as blocked.
423
+
276
424
  ## Evidence rules
277
425
 
278
426
  - Record only sanitized evidence: statuses, booleans, counts, durations,
@@ -485,3 +633,224 @@ Claude Code `2.1.296`, on 2026-10-11:
485
633
  The Pi extension live check above ran on `eeaa597`, before the final fixes to
486
634
  extension tool names and the `read-only` contract test. It has not been
487
635
  repeated on the release candidate.
636
+
637
+ After its release, `v0.7.0` passed Level 1 and Level 2 on a fresh clone of its
638
+ tag, and Level 2 as published npm package, on 2026-10-11, on Linux with Node.js
639
+ `26.11.1`, Codex CLI `0.162.1` and Claude Code `2.1.296`:
640
+ - Tag clone: `git describe --tags --exact-match` reported `v0.7.0`. The tag
641
+ object is `a5516ce6e4e159ef1e20ee38aba06672379e9cb7` and the peeled commit
642
+ `6783631d2ae1813707cf798360a0a0f4c58ca66d`.
643
+ - Level 1 on the tag clone:
644
+ - every command exited with `0`;
645
+ - the test run reported 37 files, 660 passed and 5 skipped;
646
+ - `npm audit` and `npm audit --omit=dev` reported 0 vulnerabilities;
647
+ - Doctor reported `packageVersion` `0.7.0` and no finding;
648
+ - `dist/main.js` has the SHA-256
649
+ `47875d38886528ace6b242df2c6ad79d624c51e330e63c2f401576eadc449ed7`.
650
+ - Level 2 passed 96 of 96 checks on the tag clone, and 96 of 96 with the npm
651
+ package installed into a temporary prefix through
652
+ `pi-session-mcp-setup`. In both runs the real client registrations were
653
+ unchanged, no process remained, and the built files were byte-identical
654
+ before and after the client cycles.
655
+ - These runs, and the release candidate's runs above, used the harness that
656
+ #40 later checked in as `scripts/setup-acceptance.mjs`.
657
+ - The checked-in script, after its review fixes, also passed 96 of 96 on the
658
+ tag clone.
659
+ - The npm package has the shasum `c8d73311dbde1bdebd78e58b1f01c7cf3bebf374`.
660
+ The tarball downloaded from the registry had the same SHA-1 and SHA-512
661
+ integrity.
662
+ - Installed package:
663
+ - `pi-session-mcp-doctor` reported a healthy package `0.7.0` when run from an
664
+ unrelated working directory;
665
+ - with a configuration that pins `pi-cursor-sdk@0.5.3` on three profiles,
666
+ `setup --apply` and `--verify` for `claude-code:user:pi-session-mcp`
667
+ reported `applied` and `mcp_verified`.
668
+
669
+ On 2026-10-11 the installed `v0.7.0` package also passed the Pi extension live
670
+ check on Linux:
671
+ - Method: the same scripted JSON-RPC client as above drove the installed
672
+ server. It used the operator's configuration with three `pi-cursor-sdk@0.5.3`
673
+ profiles: one `read-only` and one `coding` profile with
674
+ `claude-opus-5-5@1m` and `claude-opus-5-5@300k` at thinking level `high`, and
675
+ one `coding` profile with `composer-2.5` at thinking level `off`.
676
+ - Each of the three profiles started, completed a prompt with the expected
677
+ one-word answer, and closed. One `coding` and one `read-only` Cursor session started in parallel
678
+ and both completed.
679
+ - Profiles without extensions on three other providers (xAI, Mistral and
680
+ DeepSeek) each completed a prompt.
681
+ - After close no descendant process of the server remained. No stdout line
682
+ failed to parse as an MCP frame; stderr carried four
683
+ `foreign_stdout_suppressed` reports for 12 suppressed writes and no other
684
+ event.
685
+ - The two OpenAI profiles ended `failed` with `turn_failed`. A direct Pi CLI
686
+ call outside the server showed that the account had reached its usage limit,
687
+ so they are not evidence for or against the release.
688
+
689
+ This check does not cover macOS, an MCP client in the loop, or a session that
690
+ uses Cursor's own tools.
691
+
692
+ Before its release, `v0.7.1` passed Level 1 and the working-tree Level 2 on its
693
+ release candidate `82dff20` on Linux, with Node.js `26.11.1`, Codex CLI
694
+ `0.162.1` and Claude Code `2.1.296`, on 2026-10-11:
695
+ - Level 1: every command exited with `0`.
696
+ - The test run reported 38 files, 684 passed and 5 skipped.
697
+ - `npm audit` and `npm audit --omit=dev` reported 0 vulnerabilities.
698
+ - Doctor reported `packageVersion` `0.7.1` and no finding.
699
+ - Level 2 ran through `scripts/setup-acceptance.mjs` on a fresh clone of the
700
+ candidate.
701
+ - All nine steps passed on all four targets, 96 of 96 checks.
702
+ - The real client registrations were unchanged, and no process remained.
703
+ - The built files were byte-identical before and after the client cycles.
704
+ - `dist/main.js` is byte-identical to that of `v0.7.0`. The changed modules are
705
+ the extension tool-name rule, the pin format and the prompt source.
706
+
707
+ The branch build of `8d5b069`, whose sources match this candidate, passed the
708
+ Pi extension live check with the operator's configuration on 2026-10-11:
709
+ - Three `pi-cursor-sdk@0.5.3` profiles and one xAI profile each completed a
710
+ prompt.
711
+ - Two Cursor sessions completed in parallel.
712
+ - No process remained, and no foreign line reached the MCP stdout.
713
+
714
+ Profile skill selection (#46) passed a multi-session skill acceptance with Claude
715
+ Code and Codex on 2026-10-12, on Linux (`x64`) with Node.js `26.11.1`, Claude
716
+ Code `2.1.296`, Codex CLI `0.162.1` and Pi SDK `1.1.0`:
717
+ - Candidate: detached checkouts of `bcef6ba` (runs 1 to 3), `a086339` (run 4)
718
+ and `b6cf624` (run 5), each clean and built with `npm ci`, `npm run build`
719
+ and `npm run build:cli`. `dist/main.js` has the same SHA-256 in all five
720
+ runs, `47875d38886528ace6b242df2c6ad79d624c51e330e63c2f401576eadc449ed7`:
721
+ the commits after `bcef6ba` change only the orchestration skill text, the
722
+ acceptance harness and documentation.
723
+ - Level 1 on `bcef6ba`, from the command output of the maintainer's gate run:
724
+ every command exited with `0`. The test run reported
725
+ 40 files, 761 passed and 5 skipped; `npm audit` reported 0 vulnerabilities;
726
+ `npm pack --dry-run` listed the three skills and no harness. The test suite
727
+ passed again on `a086339` with the same counts.
728
+ - Configuration: the same bytes in every run, fingerprint
729
+ `sha256:b754f0f5e4702d81d99183817abb379eeed2fb5733f93373852eb827ec16d687`,
730
+ with the profiles `accept-read` and `accept-code` derived from an OpenAI
731
+ `gpt-6-luna` profile with thinking level `low`, and the fixture aliases
732
+ `cc-slug`, `cc-stats`, `cc-wait`, `cc-range`, `cx-slug`, `cx-stats`, `cx-wait`
733
+ and `cx-range`. Offline Doctor reported no finding for it.
734
+ - Every invocation confirmed the candidate's version and this fingerprint
735
+ through `pi_capabilities_get` as its first call, and saw the skills
736
+ `implementation` and `handshake` on `accept-code`. Exactly one server of the
737
+ run was observed per invocation, and no process of the controller tree, the
738
+ server or the fixtures remained afterwards. No Pi Session MCP result
739
+ contained a local path, the token or a credential marker, and no
740
+ `assistantText` mentioned one.
741
+
742
+ Run 5, on `b6cf624` with the harness of that commit (SHA-256
743
+ `ca851130bbade9f0b2700a43059b7ac3ce349dad40ad2b647133f950a408c719`), passed
744
+ every check of all four invocations:
745
+
746
+ | Client | Scenario | Result |
747
+ | --- | --- | --- |
748
+ | Claude Code | `parallel` | First turns overlapped by 25.1 s; follow-up in the same session completed; both fixtures pass their tests with only the allowed files changed, each last written during a worker turn; `HANDSHAKE.txt` holds the token and was written during the follow-up turn, although no worker prompt contained the token and the token never appeared in the controller transcript; no controller action named the private run directory; both sessions closed |
749
+ | Claude Code | `abort` | First turns overlapped by 23.7 s; one abort, result `ok`; `aborted` seen through `pi_turn_get` before close; the abort fell inside the running interval of the neighbor, which then completed and passes its tests; the worker's `sleep 120` was observed running, and at the end of the run it was gone and had written no file; both sessions closed |
750
+ | Codex | `parallel` | As for Claude Code, with an overlap of 24.8 s |
751
+ | Codex | `abort` | As for Claude Code, with an overlap of 22.4 s |
752
+
753
+ The controllers made 1 to 6 `pi_turn_get` calls per session and waited between
754
+ polls with `sleep`. Each invocation took 93 to 156 seconds. The run's
755
+ transcripts were also checked afterwards against the criteria the harness
756
+ gained after run 5: in both `abort` invocations the first successful abort came
757
+ before the first `aborted` observation, and no worker prompt named a private
758
+ path or a skill file. Run 5 used the harness of `b6cf624`; the head's harness
759
+ adds these two checks and the run-time checksum check, and changes nothing
760
+ else.
761
+
762
+ Earlier attempts on the same build, kept on record:
763
+ - Run 1 (Claude Code only, with the harness of `bcef6ba`, which did not record
764
+ its checksum yet): `parallel` passed
765
+ every check. In `abort` the abort itself passed (result `ok`, `aborted` seen
766
+ before close, the worker's `sleep` ended, no late file), but the neighbor
767
+ turn had completed about three seconds before the abort, so the run shows no
768
+ neighbor that keeps running. The neighbor task now starts with a bounded
769
+ `sleep 45` and the abort comes after about 15 seconds.
770
+ - Run 2: three invocations passed every check. In Codex `parallel` every
771
+ functional check passed, including the handshake written during the
772
+ follow-up turn with a token that no prompt contained, but the controller
773
+ then read the run's private handshake skill to confirm the token. The
774
+ controller prompt now confines reads and commands to the fixtures.
775
+ - Run 3: three invocations passed every check. In Codex `abort` the abort, the
776
+ running neighbor and cleanup passed, but the controller told both workers to
777
+ "follow configured handshake instructions" because capabilities listed a
778
+ `handshake` skill, so both fixtures got an extra `HANDSHAKE.txt`. The
779
+ orchestration skill now says that a listed skill is not a task.
780
+ - Run 4 (`a086339`): three invocations passed every check. The Codex
781
+ `parallel` controller read the fixtures-only rule as excluding its own skill
782
+ directory, worked from `./fixtures`, and stopped after
783
+ `pi_capabilities_get` without starting a session. The rule now allows the
784
+ controller's own skill directory.
785
+
786
+ No run showed a Pi Session MCP defect. The deviations were controller
787
+ behavior and harness wording.
788
+
789
+ This is a targeted check of skill selection and of two concurrent sessions
790
+ per client. It is not the ten-task Level 3 cycle. It covers neither the
791
+ extension profile path live (the automated SDK contract tests cover it), nor
792
+ `read-only` workers, nor more than two concurrent sessions, nor macOS.
793
+
794
+ After the owner asked on 2026-10-12 that skills install without manual linking
795
+ and can be adjusted and extended, the same change gained the skill library,
796
+ `builtin:` references, `--customize-skill` and the controller-skill
797
+ installation through `setup`. Its final candidate `0ebbaa0` passed on
798
+ 2026-10-12, on Linux (`x64`) with Node.js `26.11.1`, Claude Code `2.1.296`,
799
+ Codex CLI `0.162.1` and Pi SDK `1.1.0`:
800
+ - Level 1 on a clean detached checkout: every command exited with `0`. The
801
+ test run reported 43 files, 884 passed and 5 skipped; `npm audit` reported 0
802
+ vulnerabilities; `npm pack --dry-run` listed 125 files with the three skills
803
+ and no acceptance harness. `git diff --check` from `0cb3c60` reported nothing.
804
+ - Level 2 through `scripts/setup-acceptance.mjs` with the setup of that
805
+ checkout: 160 of 160 checks passed. All nine steps passed on all four
806
+ targets, including the controller skill finding of every step, the skill file
807
+ after steps 3 and 7 and its absence after steps 6 and 9, and a foreign skill
808
+ that stayed unchanged. The real client registrations were unchanged, no
809
+ process remained, no report leaked a path, and the build was unchanged.
810
+ Earlier candidates `24ab13d` and `37034dd` of the same change had passed
811
+ Level 2 with 160 of 160 as well, before the review fixes that followed them.
812
+ - Multi-session skill acceptance, run 7, with the harness of that checkout
813
+ (SHA-256 `42d46f86ae617c229568437e6d96c93f965c27778c3e2fde51e1c449ef35fb12`)
814
+ and the dist tree SHA-256
815
+ `957dfac67bf0c3c179bff6584b3bda49bedad2fe1642790537f6790b31f5ef37`:
816
+ - `prepare` installed the Claude Code controller skill with the candidate's
817
+ `setup --apply` for the controller's project scope: `applied` and
818
+ `skill_installed`, a regular copy identical to the shipped skill.
819
+ - The configuration (fingerprint
820
+ `sha256:b8a341c9ff48ecd7c02f8959b8e4e696f305431945b659ea35d2d98485e948c3`)
821
+ referenced all three skills with `builtin:`: the two shipped worker skills
822
+ and the `handshake` skill from the override directory next to it.
823
+ - All four invocations passed every check: Claude Code `parallel` (first
824
+ turns overlapped by 21.5 s) and `abort` (22.1 s), Codex `parallel` (22.9 s)
825
+ and `abort` (18.9 s). In both `parallel` invocations the worker wrote the
826
+ handshake token, which existed only in the override-directory skill,
827
+ during the follow-up turn. In both `abort` invocations the one abort
828
+ returned `ok`, came before the `aborted` observation and fell inside the
829
+ running neighbor, which completed and passed its tests. No process
830
+ remained, and no result leaked a path, the token or a credential marker.
831
+ - Run 6 on `cc810c9`, before the last review fixes, had passed every check of
832
+ all four invocations with the same configuration fingerprint and the
833
+ setup-installed Claude Code skill.
834
+
835
+ These runs do not cover macOS, Codex skill discovery from the user's home
836
+ (setup installs it there, and Level 2 checks the file, but no live Codex
837
+ controller read it from there), the extension profile path live, or more than
838
+ two concurrent sessions.
839
+
840
+ Before its release, `v0.8.0` passed Level 1 and the working-tree Level 2 on
841
+ its release candidate `6d197a0` on Linux (`x64`), with Node.js `26.11.1`, Codex
842
+ CLI `0.162.1` and Claude Code `2.1.296`, on 2026-10-12:
843
+ - Level 1: every command exited with `0`.
844
+ - The test run reported 43 files, 884 passed and 5 skipped.
845
+ - `npm audit` and `npm audit --omit=dev` reported 0 vulnerabilities.
846
+ - `npm pack --dry-run` listed 125 files, including the three skills.
847
+ - Doctor reported `packageVersion` `0.8.0`. With the example
848
+ configuration its only finding was the missing placeholder workspace.
849
+ - Level 2 ran through `scripts/setup-acceptance.mjs` on the candidate's
850
+ working tree: 160 of 160 checks passed. All nine steps passed on all four
851
+ targets, including the controller skill finding of every step. The real
852
+ client registrations were unchanged, no process remained, and the built
853
+ files were unchanged.
854
+ - Apart from the version, the candidate's sources equal the merge of #48,
855
+ whose final candidate `0ebbaa0` passed the multi-session skill acceptance
856
+ recorded above.
@@ -3,6 +3,9 @@
3
3
  **Status:** Accepted (2026-10-11). Issue #34. Design:
4
4
  [Pi extensions design](../pi-extensions-design.md).
5
5
 
6
+ Amended by [ADR 0007](0007-profile-skill-selection.md) for skills that extension
7
+ packages ship.
8
+
6
9
  ## Context
7
10
 
8
11
  Embedded sessions loaded no Pi extensions. Providers that exist only as Pi
@@ -40,8 +43,8 @@ model runtime per session:
40
43
 
41
44
  Profiles without extensions keep the previous path. Client prompts are plain
42
45
  text for every profile: they never dispatch extension commands, skills or
43
- templates. An always-on stdout guard gives the MCP transport the only real
44
- stdout writer.
46
+ templates, and extension input hooks see them with `source: "rpc"`. An
47
+ always-on stdout guard gives the MCP transport the only real stdout writer.
45
48
 
46
49
  ## Consequences
47
50
 
@@ -0,0 +1,217 @@
1
+ # ADR 0007: Execution profiles select Pi skills from an operator catalog
2
+
3
+ **Status:** Accepted (2026-10-12). Issue #46. Guide: [Pi skills in execution
4
+ profiles](../skills.md).
5
+
6
+ ## Context
7
+
8
+ Embedded sessions are meant to run without skills. A controlling agent that
9
+ delegates work to Pi workers needs a way to give a worker role-specific
10
+ instructions, such as an implementation or review procedure, under the same
11
+ rule as everything else: the operator decides, the MCP caller picks aliases.
12
+
13
+ Verified behavior of Pi SDK 1.1.0:
14
+
15
+ - `noSkills` does not stop skills that an allowlisted extension package
16
+ ships. Those skills reached the session although `noSkills` was set. The
17
+ same held for prompt templates and themes that packages ship. ADR 0006 did
18
+ not intend this and did not decide it.
19
+ - `loadSkills` removes duplicate skill names before the SDK's `skillsOverride`
20
+ runs. An override alone therefore cannot report a collision between two
21
+ selected files, so the server has to check names itself.
22
+ - Resources that extension hooks add later (`resources_discover`) pass through
23
+ the same override.
24
+
25
+ ADR 0003 chose maintenance without a new feature phase and reserves public
26
+ contract changes for a minor release with their own decision. ADR 0006 is the
27
+ precedent for an owner-approved scope expansion. This ADR is such a decision
28
+ for issue #46. It does not rewrite ADR 0006; that ADR records only a forward
29
+ link.
30
+
31
+ On 2026-10-12 the owner rejected installing the shipped skills by manual
32
+ symlinking and asked for skills that can be installed at install time and
33
+ adjusted and extended one by one. The decisions under "Skill library,
34
+ installation and customization" below record that, in the same change.
35
+
36
+ ## Decision
37
+
38
+ The operator may register skills in a catalog and select them per execution
39
+ profile.
40
+
41
+ - **Catalog.** An optional top-level `skillCatalog` maps a skill alias
42
+ (`^[a-z][a-z0-9_-]{0,63}$`, at most 64 entries) to the path of exactly one
43
+ `SKILL.md` (at most 4096 characters). Directories, globs, URLs,
44
+ scheme-prefixed sources, `~` and backslashes are invalid. The one accepted
45
+ scheme is `builtin:<name>` (see below). Relative paths resolve against the
46
+ configuration file's directory. A missing catalog is empty. A skill folder
47
+ needs no `package.json` and no extension.
48
+ - **Selection.** An optional `executionProfiles.<alias>.skills` lists catalog
49
+ aliases (at most 16, no duplicates). Missing or empty means no skills. There
50
+ is no inheritance and no default skill. An unknown alias, a duplicate alias,
51
+ or two selected aliases that resolve to the same path is
52
+ `invalid_execution_profile_config`. All other profile fields are unchanged
53
+ and unknown fields are still rejected.
54
+ - **MCP surface.** Callers still choose only workspace and profile aliases.
55
+ `pi_session_start` and `pi_session_prompt` get no skill input. No tool is
56
+ added.
57
+ - **Loading.** At session start the server loads exactly the selected files
58
+ through the SDK's public `loadSkills`, with no default directories, and
59
+ validates them completely. `skillsOverride` returns exactly that set, also
60
+ after later resource additions by extension hooks.
61
+ - **Failure.** A missing, unreadable or oversized (over 256 KiB) file, an
62
+ invalid skill (SDK diagnostics such as a missing description or invalid
63
+ name), two selected files with the same Pi skill name, two aliases on the
64
+ same canonical file (for example through a symlink), or
65
+ `disable-model-invocation: true` fail the start with the new error
66
+ `skill_load_failed` and the fixed message `Configured Pi skill could not be
67
+ loaded`. The message never contains a path, a skill name or SDK text. The
68
+ same Pi skill name in different sessions is fine.
69
+ - **Explicit-only skills are incompatible in v1.** MCP prompts never run
70
+ `/skill:` commands, and Pi hides such skills from the model. The server does
71
+ not rewrite the flag.
72
+ - **Names.** The catalog alias may differ from the skill's declared `name`.
73
+ Nothing is renamed.
74
+ - **Capabilities.** Every execution profile in `pi_capabilities_get` gains
75
+ `skills: string[]`, the selected catalog aliases in configured order, `[]`
76
+ when none. No paths, file names, contents or SDK diagnostics. The setup
77
+ verifier accepts the field and still rejects unexpected fields.
78
+ - **Doctor.** Offline, it never parses or executes a skill. New check IDs after
79
+ the extension checks, with the subject `<profile>:<skill-alias>`:
80
+ `skill_missing`, `skill_unreadable` (not a regular file, over 256 KiB, or not
81
+ readable) and `skill_duplicate`. Frontmatter, name rules, collisions and
82
+ explicit-only flags are checked only at session start. A further warning
83
+ check, `skill_override_outdated`, is described below.
84
+ - **Diagnostics.** Stderr gets the new stage `skill_load` for a selected file
85
+ that fails validation, with an optional `skill` field holding the catalog
86
+ alias. A catalog that differs from the selection after construction reports
87
+ the existing stages `postcheck`, `extension_load` or `extension_bind`. No
88
+ diagnostic carries a path.
89
+ - **Snapshot semantics.** Profile and skill selection are fixed per session.
90
+ The content is not snapshotted: the SDK keeps file paths and later reads may
91
+ see changed files. Operators manage skill files and companion files, do not
92
+ change them during active sessions, and close affected sessions before an
93
+ update. The configuration fingerprint covers configuration bytes, not skill
94
+ contents. There is no hot reload, watcher, copy store for sessions or
95
+ versioning.
96
+ - **Shipped skills.** The package ships `pi-session-orchestration`,
97
+ `pi-session-implementation` and `pi-session-review` under `skills/`, one
98
+ canonical copy each. They are ordinary skill files. The two worker skills are
99
+ registered through the catalog like any operator skill, by `builtin:`. The
100
+ orchestration skill is for the controlling client agent and is not a Pi
101
+ worker skill.
102
+
103
+ ## Skill library, installation and customization
104
+
105
+ Owner decision of 2026-10-12, same issue. The first design of this change had
106
+ the operator register shipped skills by absolute path and link the
107
+ orchestration skill into the clients by hand; the owner rejected that. Three
108
+ decisions were taken:
109
+ installation through `pi-session-mcp-setup --apply`, an override directory for
110
+ customization, and `builtin:` references in the Pi skill catalog.
111
+
112
+ - **Skill library.** Shipped skills live in the package's `skills/` directory.
113
+ The operator's override directory is `skills/` next to the configuration file
114
+ (for the default configuration `~/.config/pi-session-mcp/skills/<name>/SKILL.md`).
115
+ An override with the same name replaces the shipped skill; a new name adds a
116
+ skill. Names follow the Agent Skills rule: lowercase letters and digits in
117
+ hyphen-separated groups, 1 to 64 characters. Nothing else is searched.
118
+ - **`builtin:<name>`.** A `skillCatalog` value resolves to the override if
119
+ `<override dir>/<name>/SKILL.md` exists, else to the shipped skill. A missing
120
+ skill keeps the shipped location, so Doctor reports `skill_missing` and the
121
+ session start fails with `skill_load_failed`. Resolution happens when the
122
+ server loads the configuration: adding or removing an override needs a
123
+ restart, like any configuration change. Edits to an existing skill file reach
124
+ new sessions under the snapshot semantics above. The example configuration
125
+ uses `builtin:pi-session-implementation` and `builtin:pi-session-review`, so a
126
+ copied example needs no path edits.
127
+ - **Customization.** `pi-session-mcp-setup --customize-skill <name>` (a
128
+ dry-run) and `--apply --customize-skill <name>` copy a shipped skill into the
129
+ override directory as a starting point and record the shipped fingerprint in
130
+ `<override dir>/.pi-session-mcp-bases.json`. An existing override is never
131
+ overwritten. When the shipped skill changed after the copy, or no base is
132
+ recorded, the command reports `skill_override_outdated`; `--apply` then moves
133
+ only the recorded base (`skill_base_updated`) and never touches the
134
+ operator's files, which is how an operator acknowledges a reviewed change.
135
+ The finding target is `{ client: "skills", scope: "override", alias: <name> }`.
136
+ It works without `--target`, requires `PI_SESSION_MCP_CONFIG`, and follows
137
+ the platform rule of setup. A completely new skill needs no command: create
138
+ `<override dir>/<new-name>/SKILL.md` and reference it as `builtin:<new-name>`.
139
+ - **Installation of the controller skill.** `setup --apply` installs
140
+ `pi-session-orchestration` for each target, after the registration, as a real
141
+ copy of the effective skill (override, else shipped). Codex `codex:user` goes
142
+ to `~/.agents/skills/`, Claude Code `user` and `local` share
143
+ `~/.claude/skills/`, and Claude Code `project` goes to
144
+ `<project>/.claude/skills/`, the directory setup runs in. Ownership records
145
+ hold only a content fingerprint and, while an update is pending, the
146
+ fingerprint it replaces; a project record is bound to an opaque project
147
+ identity, never a path. Setup never replaces or deletes a modified installed
148
+ copy, hidden operator entries inside it, a foreign skill of the same name or a
149
+ linked or non-directory entry; it reports `skill_divergent`, which does not
150
+ block the registration. A link to exactly the effective skill is the
151
+ operator's own equivalent setup (`skill_linked`) and is never owned. In the home directory the client's skills
152
+ directory may be the operator's own link and is followed. Inside a project,
153
+ which may be untrusted, any link below the project root makes the slot
154
+ divergent, so setup never writes outside the project. Setup installs only
155
+ `pi-session-orchestration` into clients; the worker skills are for Pi
156
+ profiles through `builtin:`, and controller skills of the operator's own
157
+ belong in the clients' native skill directories. The operator customizes the
158
+ controller skill through the override and runs `--apply` again, and never
159
+ edits the installed copy.
160
+ - **Doctor.** The warning check `skill_override_outdated` has the subject
161
+ `skill:<name>` when a shipped skill's current fingerprint differs from the
162
+ recorded base, with a remediation that names the customize command. A
163
+ corrupt bases file reports the subject `skill:bases`. Doctor stays offline
164
+ and executes nothing.
165
+ - **Fallback.** Where setup does not run, the operator copies the controller
166
+ skill directory into the client's documented skill directory. There is no
167
+ symlink instruction.
168
+
169
+ ## Compatibility change
170
+
171
+ Previously, with Pi SDK 1.1.0, an allowlisted extension package's bundled
172
+ skills, prompt templates and themes reached the session although `noSkills`
173
+ was set. Now an extension allowlist alone makes none of its skills available.
174
+ A skill is available only through the catalog plus the profile's explicit
175
+ selection.
176
+
177
+ Migration: add a catalog entry with the absolute path to the package's
178
+ `SKILL.md` inside Pi's install (normally under
179
+ `<Pi agent dir>/npm/node_modules/<package>/…`; `~` is not expanded) and select
180
+ it in the profile. Prompt templates and themes that packages ship are now also
181
+ closed through the SDK's public overrides. They were already never expanded
182
+ for MCP prompts. This does not mean every previous profile behaves unchanged:
183
+ a profile that used a package's skills needs the migration above.
184
+
185
+ ## Consequences
186
+
187
+ - Skills are operator-managed prompt content. In `coding` profiles a skill's
188
+ instructions are executed with the profile's tools. A skill grants no rights.
189
+ - Skill selection limits the regular skill catalog only. An allowlisted
190
+ extension stays trusted executable code and can still read files or inject
191
+ prompt text. This is no sandbox.
192
+ - Three states stay distinct: configured (capabilities list the alias; intent
193
+ only), available in the SDK catalog (validated at session start and listed
194
+ to the model in its system prompt), and actually read by the model (the model
195
+ decides; not guaranteed). Task success is separate again.
196
+ - Skill files can change under a running session. Operators carry that
197
+ discipline; the server does not enforce it.
198
+ - Setup now writes into client skill directories, in the home directory and in
199
+ a project. It copies only from the package or the operator's override
200
+ directory, never follows a link inside a project, and never replaces what it
201
+ did not install or what the operator modified. The override directory and its
202
+ bases file are operator-trusted configuration, not a workspace. Installed
203
+ skill content becomes instructions for the controlling agent.
204
+ - The server needs a name check of its own for selected files, because
205
+ `loadSkills` dedupes by name before `skillsOverride`.
206
+ - Sessions of profiles with extensions each have their own model runtime
207
+ (ADR 0006). Skill loading does not change that.
208
+
209
+ ## Versioning
210
+
211
+ ADR 0003 requires a minor release for a public contract change. The new
212
+ `skills` capability field and the new `skill_load_failed` error code are such a
213
+ change, and so are the new setup findings, the `--customize-skill` option and
214
+ the controller skill that `setup --apply` installs, so the next release after
215
+ 0.7.1 is 0.8.0. The change is recorded under
216
+ `## Unreleased` in the changelog. Implementing it does not itself release or
217
+ bump the version.