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
@@ -47,13 +47,13 @@ reconciled only when the registration still matches that binding. A changed
47
47
  release fails closed and cannot silently inherit or replace another release's
48
48
  registration.
49
49
 
50
- `pi_capabilities_get` projects the validated workspace and execution-profile maps plus the immutable configuration metadata captured while loading the file. It returns aliases plus the configured permission/provider/model/thinking intent, sorted by alias, and `configuration: { fingerprint, reloadPolicy: "restart-required" }`. The fingerprint is SHA-256 over the exact loaded file bytes, including formatting; it is not a current-file freshness check. The projection makes no provider, authentication, filesystem, network, server-reachability, or catalog-state probe, so it cannot assert availability.
50
+ `pi_capabilities_get` projects the validated workspace and execution-profile maps plus the immutable configuration metadata captured while loading the file. It returns aliases plus the configured permission/provider/model/thinking intent, the configured extension specs and the selected skill aliases, sorted by alias, and `configuration: { fingerprint, reloadPolicy: "restart-required" }`. The fingerprint is SHA-256 over the exact loaded file bytes, including formatting; it is not a current-file freshness check. The projection makes no provider, authentication, filesystem, network, server-reachability, or catalog-state probe, so it cannot assert availability.
51
51
 
52
52
  ## Deterministic Pi selection
53
53
 
54
- The SDK adapter owns one durable `ModelRuntime`. For the configured profile, it uses that same runtime to call `getProvider(providerId)`, `getModel(providerId, modelId)`, `checkAuth()`, `getAuth(model)`, and `getAvailable(providerId)`. It rejects an absent provider or model, missing or unavailable local authentication, and a model not present in the local available-model set before constructing a session. Authentication uses only Pi's existing local mechanism; auth objects are presence-checked and never retained, serialized, or logged.
54
+ The SDK adapter owns one durable `ModelRuntime` for profiles without extensions. (A profile with `extensions` creates a fresh runtime for each session; see [Pi extensions design](pi-extensions-design.md).) For the configured profile, it uses that runtime to call `getProvider(providerId)`, `getModel(providerId, modelId)`, `checkAuth()`, `getAuth(model)`, and `getAvailable(providerId)`. It rejects an absent provider or model, missing or unavailable local authentication, and a model not present in the local available-model set before constructing a session. Authentication uses only Pi's existing local mechanism; auth objects are presence-checked and never retained, serialized, or logged.
55
55
 
56
- Only then does the adapter call `createAgentSession` with the same runtime and explicit `model`, `thinkingLevel`, and permission-derived `tools`. It never calls `resolveCliModel` or a fuzzy resolver, and does not depend on SDK settings or first-available fallback behavior. After construction it verifies `session.model.provider`, `session.model.id`, and effective `session.thinkingLevel`. A clamped thinking level or any selection mismatch fails closed. Any post-creation failure disposes the SDK session before a handle can enter the registry.
56
+ Only then does the adapter call `createAgentSession` with that runtime and explicit `model`, `thinkingLevel`, and permission-derived `tools`. It never calls `resolveCliModel` or a fuzzy resolver, and does not depend on SDK settings or first-available fallback behavior. After construction it verifies `session.model.provider`, `session.model.id`, and effective `session.thinkingLevel`. A clamped thinking level or any selection mismatch fails closed. Any post-creation failure disposes the SDK session before a handle can enter the registry.
57
57
 
58
58
  These postchecks establish the selected provider, model, and effective Pi session
59
59
  thinking level. They do not inspect provider-side reasoning or enforce a
@@ -65,11 +65,15 @@ provider/engine behavior rather than inferred from the effective level.
65
65
 
66
66
  The registry owns public UUIDs, lifecycle state, the turn read model, background turn promises, abort, and disposal. A per-session operation queue serializes prompt admission and close; concurrent abort callers share one bounded abort attempt for the active turn. It registers a session only after adapter creation and postconditions complete. Once shutdown begins, starts fail with `server_stopping`; a handle created by an already-running start is disposed instead of admitted. The SDK session ID is never exposed.
67
67
 
68
+ ## Skills
69
+
70
+ `skillCatalog` and each profile's `skills` are parsed by `loadConfig`. A path value resolves against the configuration file's directory, like a workspace; a `builtin:<name>` value resolves through the skill library to the operator's override in `skills/<name>` next to the configuration file, else to the skill shipped in the package. Setup, not the server, installs the controller skill into the clients' skill directories. A profile keeps its selected skills as a frozen list of `{alias, path}` pairs; the path never leaves the server. At session start the adapter loads exactly the selected files through the Pi SDK's public `loadSkills` with no default directories and validates them completely (SDK diagnostics, duplicate Pi skill names, aliases that share one canonical file, `disable-model-invocation: true`, files over 256 KiB). The SDK's skill override returns exactly that set, also after extension hooks add resources, so a package's bundled skills, prompt templates and themes no longer reach a session. Any failure is `skill_load_failed` with a fixed message. The model sees the loaded skills in its system prompt and decides itself whether to read one; the server neither guarantees that nor snapshots skill contents. Capability discovery projects only the selected aliases. See [Pi skills](skills.md) and [ADR 0007](adr/0007-profile-skill-selection.md).
71
+
68
72
  ## Lifecycle and public data
69
73
 
70
74
  `pi_session_prompt` creates a provisional turn identity, invokes Pi preflight, and only after accepted preflight publishes `{session, turn}`; rejected preflight leaves no orphan turn. Completion returns to `idle`; failure is generic. Each active session retains at most 20 turns. A turn is `running`, `completed`, `failed`, or `aborted`; its public view contains IDs, timestamps, and terminal `completedAt`. Completed text is normalized and capped at 64 KiB UTF-8 with Unicode-safe truncation. It can contain model-visible workspace data and remains potentially sensitive.
71
75
 
72
- Session metadata contains only whitelisted values: public session ID, workspace alias, execution-profile alias, derived permission `profile`, post-verified provider/model/effective thinking level, lifecycle state, and timestamps. Capability metadata uses a separate whitelist: server name/version, loaded configuration fingerprint/reload policy, workspace aliases, and configured profile fields. Neither view contains workspace paths, full model objects, SDK session IDs, auth sources, credential paths, headers, base URLs, prompts, transcripts, or raw provider errors.
76
+ Session metadata contains only whitelisted values: public session ID, workspace alias, execution-profile alias, derived permission `profile`, post-verified provider/model/effective thinking level, lifecycle state, and timestamps. Capability metadata uses a separate whitelist: server name/version, loaded configuration fingerprint/reload policy, workspace aliases, and configured profile fields, including the selected skill aliases. Neither view contains workspace paths, full model objects, SDK session IDs, auth sources, credential paths, headers, base URLs, prompts, transcripts, or raw provider errors.
73
77
 
74
78
  Profile-specific authentication/catalog operations and session creation have fixed server-side deadlines of 5,000 ms and 10,000 ms. Prompt preflight, abort, close cleanup, registry shutdown, and process shutdown have fixed server-side deadlines of 5,000 ms, 5,000 ms, 5,000 ms, 12,000 ms, and 15,000 ms respectively. A preflight timeout returns `prompt_timeout` without publishing a turn. The unpublished operation remains quarantined until its prompt and abort cannot race with later work; late settlement can release that quarantine but never publish the turn. An abort timeout returns `abort_timeout` without falsely marking the turn aborted; a later normal outcome or successful SDK abort may still terminalize it exactly once. Close marks the session terminal, removes its public registry entry deterministically, and performs best-effort abort/dispose. Repeated close calls return a cached closed view while its tombstone is retained. Each SDK handle is disposed at most once. A late promise cannot revive a removed session or mutate a removed turn.
75
79
 
@@ -79,4 +83,4 @@ Public errors are stable and sanitized. They omit caller values, complete prompt
79
83
 
80
84
  ## Scope
81
85
 
82
- The product boundary is local stdio and in-memory-only operation for one trusted local MCP peer. Allowlisted Pi extensions may persist state of their own outside this boundary; Pi Session MCP itself keeps no state beyond process memory. A controller keeps one immutable configuration/profile snapshot for its lifetime; file edits require a genuinely new controller process and do not hot-reload, mark stale, or mutate running sessions. Each new session performs external catalog discovery within the loaded grants, so catalog changes do not require a controller restart, but a running session's active tools remain fixed. Restarting the controller loses all sessions; reconnecting a client reloads configuration only when it actually starts a new controller process. Capability discovery reports captured metadata only and does not prove current-file freshness, server reachability, provider availability, or catalog state. It excludes HTTP, a web UI, persistence/resume, remote access, multi-user behavior, transcript export, streaming, live provider/model discovery, availability probes during capability discovery, and provider credential management. A coding execution profile is an application-level tool selection, not an OS sandbox.
86
+ The product boundary is local stdio and in-memory-only operation for one trusted local MCP peer. Allowlisted Pi extensions may persist state of their own outside this boundary, and their input hooks see every client prompt as `rpc` input, never `interactive`; Pi Session MCP itself keeps no state beyond process memory. A controller keeps one immutable configuration/profile snapshot for its lifetime; file edits require a genuinely new controller process and do not hot-reload, mark stale, or mutate running sessions. Each new session performs external catalog discovery within the loaded grants, so catalog changes do not require a controller restart, but a running session's active tools remain fixed. Skill selection is part of that snapshot, but skill file contents are not: the SDK keeps file paths, so operators must not change a skill file while sessions that use it are active (see [Pi skills](skills.md)). Restarting the controller loses all sessions; reconnecting a client reloads configuration only when it actually starts a new controller process. Capability discovery reports captured metadata only and does not prove current-file freshness, server reachability, provider availability, or catalog state. It excludes HTTP, a web UI, persistence/resume, remote access, multi-user behavior, transcript export, streaming, live provider/model discovery, availability probes during capability discovery, and provider credential management. A coding execution profile is an application-level tool selection, not an OS sandbox.
@@ -10,6 +10,7 @@ The repository-local command shape is:
10
10
 
11
11
  ```text
12
12
  npm run --silent setup -- [--dry-run|--apply|--verify|--rollback|--remove] [--json] --target CLIENT:SCOPE:pi-session-mcp
13
+ npm run --silent setup -- [--apply] [--json] --customize-skill NAME
13
14
  ```
14
15
 
15
16
  The script executes the already-built production entry point. Building is a
@@ -61,6 +62,124 @@ of descendant termination must assert zombie-or-absence without catching their
61
62
  own failed assertion; fence retention is checked separately with a live member
62
63
  and an indeterminate scan.
63
64
 
65
+ ## Skill installation
66
+
67
+ Besides the registration, setup installs the controller skill
68
+ `pi-session-orchestration` (`src/setup-skills.ts`). The skill step runs inside
69
+ the same ownership transaction as the registration, per target and after it.
70
+ `--dry-run` and `--verify` run it for every target. `--apply` runs it only for a
71
+ registration finding of `ok` or `unchanged`, and `--rollback` and `--remove`
72
+ only after the registration finding is `ok`. A skill finding therefore never
73
+ blocks a registration, but `divergent`, `failed` and `unsupported` skill
74
+ findings make the overall result `failed`, like any other finding.
75
+
76
+ **Source.** The effective skill is the operator's override if
77
+ `<override dir>/pi-session-orchestration/SKILL.md` exists, else the shipped skill
78
+ (see [Pi skills](skills.md#the-skill-library)). Setup reads the tree with a
79
+ bounded reader: regular files only, at most 32 files, 1 MiB and 8 levels, no
80
+ links or special files inside the tree, entries whose name starts with `.`
81
+ skipped. A missing or unusable source is `skill_source_invalid`. The fingerprint
82
+ is a SHA-256 over the sorted relative paths and contents.
83
+
84
+ **Slots.** A slot is the skill directory a client reads:
85
+
86
+ | Target | Slot |
87
+ | --- | --- |
88
+ | `codex:user` | `~/.agents/skills/pi-session-orchestration` |
89
+ | `claude-code:user`, `claude-code:local` | `~/.claude/skills/pi-session-orchestration` (one shared slot) |
90
+ | `claude-code:project` | `<project>/.claude/skills/pi-session-orchestration` (the directory in which setup runs) |
91
+
92
+ **Ownership records.** One record per client slot holds the target client,
93
+ the scope kind (`project` or `user`, so Claude Code `user` and `local` share a
94
+ record), the phase and a content fingerprint, plus `replaces` during an update (see below). A home slot uses the
95
+ alias `skill:pi-session-orchestration`. A project slot uses
96
+ `skill:pi-session-orchestration@<id>`, where `<id>` is the first 16 hex digits
97
+ of a SHA-256 over the project's real path: an opaque identity, not a path, so a
98
+ `--remove` in another project never touches this project's copy. The `pending`
99
+ phase is written before the first filesystem change and promoted to `owned`
100
+ after a post-install inspection. During an update, the pending record also
101
+ keeps the fingerprint of the copy it replaces (`replaces`), so an interrupted
102
+ update leaves a copy that setup still recognizes as its own: the next
103
+ `--apply` finishes the update and `--remove` deletes it. A later run that finds
104
+ a `pending` record whose slot already equals the source promotes it.
105
+
106
+ **Atomic copy.** Setup creates missing parent directories (mode `0755`), stages
107
+ the tree in a temporary directory in the slot's parent (files `0644`, each file
108
+ and directory fsynced), and re-checks that the slot is unchanged
109
+ (fingerprint, device and inode). It then renames the exact old tree to a backup
110
+ name, renames the staged directory into place, inspects the result, and deletes
111
+ the backup only if it is still the exact old tree it moved. An unhealthy
112
+ ownership fence retains staged and backup content for conservative recovery.
113
+ The slot is never edited in place.
114
+
115
+ **Divergence rules.** Setup never replaces or deletes, and reports
116
+ `skill_divergent` for:
117
+
118
+ - an installed copy whose fingerprint differs from setup's own copy (the
119
+ owned fingerprint, or the one a pending update replaces), even if it happens
120
+ to equal the new source;
121
+ - a copy that differs from the source without a record (a foreign skill of the
122
+ same name);
123
+ - any entry whose name starts with `.` inside the installed copy: setup never
124
+ writes such entries, so they are operator content that the fingerprint over
125
+ the visible files would not protect;
126
+ - a link to anything other than the effective skill, a non-directory at the
127
+ skill entry, or a tree that cannot be read.
128
+
129
+ A copy that equals the source stays unowned when no record exists
130
+ (`skill_equivalent` or `skill_already_equivalent`). A link at the skill entry
131
+ whose real path is exactly the effective library skill directory (your
132
+ override, else the shipped skill) is the operator's own equivalent setup:
133
+ `--dry-run` and `--apply` report `skill_linked` (unchanged), `--verify` reports
134
+ `skill_verified`, and removal leaves it alone (`skill_not_owned`). An unmodified owned copy
135
+ that differs from the source is replaced after a package update or a changed
136
+ override (`skill_update_planned`, `skill_updated`).
137
+
138
+ **Link rules.** The skill entry itself is never followed. In the home
139
+ directory, the client's skills directory (`~/.claude/skills`, `~/.agents/skills`)
140
+ may be the operator's own link, for example into a dotfiles checkout, and is
141
+ resolved to its real path. A project may be untrusted: only the project root is
142
+ canonicalized, and a link anywhere below it makes the slot divergent, so setup
143
+ never writes outside the project. If a slot cannot be resolved at all, the
144
+ finding is `skill_source_invalid` for `--dry-run`, `skill_install_failed` for
145
+ `--apply`, `skill_missing` for `--verify` and `skill_divergent` for removal.
146
+
147
+ **Shared slot.** For `--rollback` and `--remove`, the Claude Code `user` and
148
+ `local` registrations share one slot. While the other registration is still
149
+ `owned`, the skill stays and the finding is `skill_kept_shared`. The last
150
+ removal deletes it (`skill_removed`; `skill_absent` if it is already gone;
151
+ `skill_not_owned` if setup never owned it). A modified copy is left in place
152
+ (`skill_divergent`) and its record stays, so a later run can finish after you
153
+ removed the copy.
154
+
155
+ **Remove retry.** Registration removal can finish before skill cleanup, for
156
+ example after an interruption or a `skill_divergent`. When the registration
157
+ record is gone but setup still owns the skill of that target, `--remove` and
158
+ `--rollback` run once more for that target. They first prove that the
159
+ registration is absent and then report the registration as removed and clean up
160
+ the skill. They never run for a target whose skill setup does not own, nor for
161
+ a Claude Code `user` or `local` target while the other one of the two is still
162
+ registered, and an unowned live registration stays untouched
163
+ (`ownership_unavailable`).
164
+
165
+ Skill reports carry only client, scope, the alias `pi-session-orchestration`,
166
+ status and a stable code. No skill content, fingerprint or path leaves setup.
167
+
168
+ ## Skill customization
169
+
170
+ `setup --customize-skill <name>` (and `--apply --customize-skill <name>`,
171
+ `src/skill-customize.ts`) does not touch clients and takes no ownership lock. It
172
+ copies a shipped skill into the override directory `skills/` next to the
173
+ configuration file and records the shipped fingerprint under the skill name in
174
+ `<override dir>/.pi-session-mcp-bases.json` (`{"schemaVersion":1,"bases":{...}}`,
175
+ strictly validated, at most 64 KiB, replaced atomically). The copy is staged in
176
+ a temporary directory and renamed into place; an existing override is never
177
+ overwritten. A changed shipped skill is only reported (`skill_override_outdated`);
178
+ `--apply` then moves the recorded base and nothing else. It cannot be combined
179
+ with `--target`, `--verify`, `--rollback` or `--remove`. The finding target is
180
+ `{ client: "skills", scope: "override", alias: <name> }`; the alias is the
181
+ skill name only if it is valid, else `invalid`.
182
+
64
183
  ## Verification boundary
65
184
 
66
185
  Production verification requires the offline Doctor, exact public client-CLI registration inspection, and a directly launched bounded MCP handshake. The verifier calls `pi_capabilities_get` with `{}` only; session start/prompt/turn and provider/model operations are forbidden. Its child process is terminated and awaited before success is reported.
@@ -84,7 +203,20 @@ Stable finding codes are grouped by responsibility:
84
203
  - verification: `registration_not_equivalent`, `mcp_verified`,
85
204
  `mcp_unsupported`, `mcp_timeout`, `mcp_failed`;
86
205
  - rollback/removal: `rolled_back`, `removed`, `ownership_diverged`,
87
- `cleanup_failed`.
206
+ `cleanup_failed`;
207
+ - controller skill, dry-run: `skill_absent`, `skill_equivalent`, `skill_linked`,
208
+ `skill_update_planned`, `skill_divergent`, `skill_source_invalid`;
209
+ - controller skill, apply: `skill_installed`, `skill_already_equivalent`, `skill_linked`,
210
+ `skill_updated`, `skill_divergent`, `skill_source_invalid`,
211
+ `skill_install_failed`;
212
+ - controller skill, verify: `skill_verified`, `skill_missing`,
213
+ `skill_not_equivalent`;
214
+ - controller skill, rollback/removal: `skill_removed`, `skill_kept_shared`,
215
+ `skill_not_owned`, `skill_absent`, `skill_divergent`;
216
+ - skill customization: `skill_customize_planned`, `skill_customized`,
217
+ `skill_override_current`, `skill_override_outdated`, `skill_base_updated`,
218
+ `skill_not_shipped`, `skill_bases_corrupt`, `skill_override_invalid`,
219
+ `skill_customize_failed`.
88
220
 
89
221
  The public statuses are `planned`, `ok`, `unchanged`, `absent`, `divergent`,
90
222
  `unsupported`, and `failed`. Reports never contain command arguments, command
@@ -90,6 +90,77 @@ bounded direct MCP `initialize`, `tools/list`, and
90
90
  `pi_capabilities_get({})` check. It never starts a session or invokes
91
91
  a provider.
92
92
 
93
+ ### Controller skill
94
+
95
+ Besides the registration, `--apply` installs the controller skill
96
+ `pi-session-orchestration` for each target, as a real copy of the effective
97
+ skill: your override in the skill library if you have one, else the skill
98
+ shipped in the package ([Pi skills](skills.md)). Clients discover it without
99
+ further steps.
100
+
101
+ | Target | Where the skill goes |
102
+ | --- | --- |
103
+ | `codex:user:pi-session-mcp` | `~/.agents/skills/pi-session-orchestration` |
104
+ | `claude-code:user:pi-session-mcp`, `claude-code:local:pi-session-mcp` | `~/.claude/skills/pi-session-orchestration` (one shared directory) |
105
+ | `claude-code:project:pi-session-mcp` | `<project>/.claude/skills/pi-session-orchestration`, where `<project>` is the directory in which setup runs |
106
+
107
+ Every result therefore has a second finding per target, with the same client
108
+ and scope and the alias `pi-session-orchestration`:
109
+
110
+ ```text
111
+ apply: ok
112
+ claude-code/user/pi-session-mcp: ok (applied)
113
+ claude-code/user/pi-session-orchestration: ok (skill_installed)
114
+ ```
115
+
116
+ Skill findings per operation:
117
+
118
+ - `--dry-run`: `skill_absent` (absent), `skill_equivalent` (unchanged),
119
+ `skill_linked` (unchanged; a link to exactly the effective skill),
120
+ `skill_update_planned` (planned), `skill_divergent` (divergent),
121
+ `skill_source_invalid` (failed).
122
+ - `--apply`: `skill_installed` (ok), `skill_already_equivalent` (unchanged),
123
+ `skill_linked` (unchanged),
124
+ `skill_updated` (ok; setup replaced its own unmodified copy after a package
125
+ update or a changed override), `skill_divergent`, `skill_source_invalid`,
126
+ `skill_install_failed` (failed).
127
+ - `--verify`: `skill_verified` (ok), `skill_missing` and
128
+ `skill_not_equivalent` (failed).
129
+ - `--rollback` and `--remove`: `skill_removed` (ok), `skill_kept_shared`
130
+ (unchanged; the other Claude Code `user` or `local` registration still uses
131
+ the shared directory), `skill_not_owned` (unchanged), `skill_absent` (ok) and
132
+ `skill_divergent` (a modified copy stays in place).
133
+
134
+ Setup never replaces or deletes a modified installed copy, a skill of the same
135
+ name that it did not install, or a link or non-directory at the skill entry; it
136
+ reports `skill_divergent`. That does not block the registration, but it makes
137
+ the overall status `failed` (exit 1). Never edit the installed copy. To
138
+ customize the skill, run `--apply --customize-skill pi-session-orchestration`,
139
+ edit the override, and run `--apply` for the target again. Design and trust
140
+ rules: [setup lifecycle design](client-setup-design.md#skill-installation).
141
+
142
+ Setup installs only `pi-session-orchestration` into clients. Worker skills are
143
+ for Pi profiles and are selected through `builtin:` entries in `skillCatalog`.
144
+
145
+ ### Customize a shipped skill
146
+
147
+ ```bash
148
+ npm run --silent setup -- --customize-skill pi-session-review
149
+ npm run --silent setup -- --apply --customize-skill pi-session-review
150
+ ```
151
+
152
+ This works without `--target` and needs `PI_SESSION_MCP_CONFIG`. It copies the
153
+ shipped skill into the `skills/` directory next to the configuration file and
154
+ records the fingerprint of the shipped skill in
155
+ `.pi-session-mcp-bases.json` there. The finding target is
156
+ `skills/override/<name>`; the codes are `skill_customize_planned` (planned),
157
+ `skill_customized` (ok), `skill_override_current` (unchanged),
158
+ `skill_override_outdated` (planned), `skill_base_updated` (ok),
159
+ `skill_not_shipped`, `skill_bases_corrupt`, `skill_override_invalid` and
160
+ `skill_customize_failed` (failed). An existing override is never overwritten.
161
+ After `skill_override_outdated`, `--apply` moves only the recorded base, which
162
+ acknowledges a change you reviewed. Details: [Pi skills](skills.md#customize-and-add-skills).
163
+
93
164
  ## Local configuration
94
165
 
95
166
  Create an unversioned JSON configuration file such as `pi-session-mcp.json`:
@@ -118,7 +189,7 @@ Create an unversioned JSON configuration file such as `pi-session-mcp.json`:
118
189
  }
119
190
  ```
120
191
 
121
- Configuration is strict. A profile has exactly `default`, `permissionProfile`, `provider`, `model`, and `thinkingLevel`, plus the optional `mcpServers` and `extensions` described in [Installation](installation.md); aliases follow the configured alias naming rules. At least one profile and exactly one default are required, and that default must be `read-only`. `thinkingLevel` is one of `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`. Workspace paths resolve relative to the configuration file. Clients can select only configured aliases, never an arbitrary working directory.
192
+ Configuration is strict. A profile has exactly `default`, `permissionProfile`, `provider`, `model`, and `thinkingLevel`, plus the optional `mcpServers`, `extensions` and `skills` described in [Installation](installation.md) and [Pi skills](skills.md); aliases follow the configured alias naming rules. At least one profile and exactly one default are required, and that default must be `read-only`. `thinkingLevel` is one of `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`. Workspace paths resolve relative to the configuration file. Clients can select only configured aliases, never an arbitrary working directory.
122
193
 
123
194
  Use placeholders until a locally available provider/model pair is known. Do not put keys, tokens, credential objects, headers, base URLs, or authentication paths in this file.
124
195
 
@@ -190,7 +261,7 @@ Claude Code enforces each tool's declared JSON Schema (`additionalProperties: fa
190
261
  ## Expected flow and lifecycle deadlines
191
262
 
192
263
  1. Call `pi_capabilities_get` with `{}` and select explicit workspace and read-only execution-profile aliases from the returned config-derived lists.
193
- 2. Call `pi_session_start` with those aliases. The server resolves the configured provider/model through its one `ModelRuntime`, checks existing local Pi authentication and availability, creates with explicit model/thinking/tools, and verifies the effective SDK result.
264
+ 2. Call `pi_session_start` with those aliases. The server resolves the configured provider/model through its shared `ModelRuntime` (a profile with extensions gets a fresh runtime for each session), loads and validates the profile's selected skills, checks existing local Pi authentication and availability, creates with explicit model/thinking/tools, and verifies the effective SDK result.
194
265
  3. Call `pi_session_prompt` with the returned `sessionId` and a read-only prompt. It returns after accepted preflight with a `turnId` in `running` state.
195
266
  4. Poll `pi_turn_get` with that `sessionId` and `turnId` until `completed`, `failed`, or `aborted`, with a deadline no longer than three minutes. There is no `pi_turn_wait` tool.
196
267
  5. Call `pi_session_close` when the session is no longer needed.
@@ -246,7 +317,7 @@ Poll `pi_turn_get` for at most three minutes. If it does not become terminal, re
246
317
 
247
318
  ### A prompt fails immediately after a provider config or credential change
248
319
 
249
- `pi_session_prompt` reads the configured provider/model through one `ModelRuntime` that is created once and cached for the lifetime of the Pi Session MCP server process; the underlying SDK reads `~/.pi/agent/models.json` only at that creation. Editing that file (for example adding or fixing a provider API key) does not take effect in an already-running server process, and a resulting turn typically fails (`turn_failed`) within milliseconds rather than after a real provider round trip. Restart or reconnect the MCP server (which restarts the process) after any provider config or credential change, then retry.
320
+ For profiles without extensions, `pi_session_prompt` reads the configured provider/model through one `ModelRuntime` that is created once and cached for the lifetime of the Pi Session MCP server process (a profile with extensions creates a fresh runtime when each session starts); the underlying SDK reads `~/.pi/agent/models.json` only at that creation. Editing that file (for example adding or fixing a provider API key) does not take effect in an already-running server process, and a resulting turn typically fails (`turn_failed`) within milliseconds rather than after a real provider round trip. Restart or reconnect the MCP server (which restarts the process) after any provider config or credential change, then retry.
250
321
 
251
322
  ### Session cleanup
252
323
 
@@ -10,7 +10,7 @@ directory: an npm executable can run from any directory.
10
10
  sanitized semver `packageVersion` when metadata is available, ordered `checks`,
11
11
  and `exitCode`). Human and JSON output are projections of that same model.
12
12
  Subjects are only fixed component names, validated workspace aliases, or
13
- validated `<profile>:<package>` pairs; paths, configuration values, errors,
13
+ validated `<profile>:<package>`, `<profile>:<skill-alias>` or `skill:<name>` pairs; paths, configuration values, errors,
14
14
  credentials, and provider data are never output.
15
15
 
16
16
  Each check is `{ id, severity, subject, remediation? }`. Severity is `ok`,
@@ -34,9 +34,17 @@ and owners are:
34
34
  | `workspace_not_writable` | A workspace is not writable while a coding profile can target it | validated workspace alias |
35
35
  | `extension_missing` | Pi has no user install of a package that a profile's `extensions` names | `<profile>:<package>` |
36
36
  | `extension_version_mismatch` | The installed manifest's version differs from the pinned version, or the manifest or its `pi.extensions` entries are invalid | `<profile>:<package>` |
37
+ | `skill_missing` | The `SKILL.md` file that a profile's selected skill alias names does not exist; a `builtin:<name>` value that matches no override and no shipped skill resolves to the shipped location and is reported here | `<profile>:<skill-alias>` |
38
+ | `skill_unreadable` | A selected skill path is not a regular file, is larger than 256 KiB, or cannot be read | `<profile>:<skill-alias>` |
39
+ | `skill_duplicate` | Two selected skill aliases of one profile resolve to the same canonical file | `<profile>:<skill-alias>` |
40
+ | `skill_override_outdated` | Warning. The current fingerprint of a shipped skill differs from the base recorded in the skill bases file next to the configuration file, or the bases file is corrupt | `skill:<name>`, or `skill:bases` for a corrupt file |
37
41
 
38
42
  Doctor reads extension manifests from Pi's user install location only; it never installs packages and never imports extension code.
39
43
 
44
+ The skill checks run after the extension checks. They look only at file availability: Doctor never reads a skill's frontmatter, never parses or executes it, and the subject carries the catalog alias, never a path. Frontmatter, skill-name rules, name collisions and explicit-only flags (`disable-model-invocation`) are checked only at session start by the Pi SDK contract, so a clean Doctor run does not promise that a skill loads. Unknown or duplicate aliases and two selected aliases with one path are configuration errors and surface as `config_invalid`. See [Pi skills](skills.md).
45
+
46
+ `skill_override_outdated` concerns the operator's skill overrides, not a profile. It is `ok` for a recorded base that still matches the shipped skill, and a warning (exit status 1, never blocking `setup`) when the shipped skill changed after the operator ran `pi-session-mcp-setup --customize-skill <name>`; its remediation names `pi-session-mcp-setup --apply --customize-skill <name>` to acknowledge a reviewed change. Only skills with a recorded base are compared, and a recorded name that is no longer shipped is skipped. A corrupt bases file yields a warning with the subject `skill:bases`. Doctor reads the bases file and the shipped skill files only; nothing is executed and the subject is a validated skill name, never a path. The check sorts after `skill_duplicate`.
47
+
40
48
  The Doctor reuses `loadConfig`; it does not duplicate alias, profile, thinking
41
49
  level, or read-only-default rules. Workspace values are used only for local
42
50
  filesystem checks and are never copied into the result.
@@ -14,12 +14,14 @@ Pi Session MCP is a local, stdio-only MCP server. An MCP client starts
14
14
  Nothing listens on a network port, no daemon runs between client sessions, and
15
15
  session state exists only in the memory of that child process.
16
16
 
17
- An installation consists of three locations:
17
+ An installation consists of these locations:
18
18
 
19
19
  | What | Location | Notes |
20
20
  | --- | --- | --- |
21
21
  | Package | the npm global prefix, or a dedicated directory per release such as `~/.local/share/pi-session-mcp/releases/<tag>` | The built server, `setup` and Doctor. |
22
22
  | Configuration | a file outside the package, for example `~/.config/pi-session-mcp/pi-session-mcp.json` | Operator-owned, mode `0600`, never committed. |
23
+ | Skill overrides (optional) | the `skills/` directory next to the configuration file, for example `~/.config/pi-session-mcp/skills/` | Your own or customized skills, plus `.pi-session-mcp-bases.json`. Created by you or by `setup --customize-skill`. See [Pi skills](#pi-skills). |
24
+ | Controller skill | `~/.agents/skills/pi-session-orchestration` (Codex), `~/.claude/skills/pi-session-orchestration` or `<project>/.claude/skills/pi-session-orchestration` (Claude Code) | Written by `setup --apply`; see [Register a client](#4-register-a-client). |
23
25
  | Setup ownership state | `~/.local/state/pi-session-mcp/ownership.json` | Written by `setup` only. Path-free and secret-free. |
24
26
 
25
27
  Never register a development checkout. A registered client launches
@@ -155,7 +157,14 @@ Rules:
155
157
  - A profile may load Pi extensions that Pi has installed, through
156
158
  `extensions`: a list of at most 8 entries of the form
157
159
  `npm:<name>@<exact version>`, each package once. Ranges, tags, `git:`
158
- sources and paths are rejected. See [Pi extensions](#pi-extensions).
160
+ sources, paths and build metadata (`+build`) are rejected. See [Pi extensions](#pi-extensions).
161
+ - A profile may select Pi skills through `skills`: a list of at most 16 aliases
162
+ from the top-level `skillCatalog`, which maps an alias to `builtin:<name>`
163
+ (a skill of the package or of your override directory) or to the path of one
164
+ `SKILL.md` file (at most 64 entries). A relative path resolves against the
165
+ configuration file's directory; `~` is not expanded. The example already uses
166
+ `builtin:` for the two worker skills, so you need not change its skill
167
+ entries. See [Pi skills](#pi-skills).
159
168
  - Never put keys, tokens, credential objects, headers, base URLs or
160
169
  authentication paths into this file.
161
170
 
@@ -217,6 +226,10 @@ pi-session-mcp-setup --apply --target claude-code:user:pi-session-mcp
217
226
  pi-session-mcp-setup --verify --target claude-code:user:pi-session-mcp
218
227
  ```
219
228
 
229
+ `--apply` also installs the controller skill `pi-session-orchestration` for the
230
+ target, after the registration, so the client knows how to drive Pi worker
231
+ sessions. See [Controller skill](#controller-skill).
232
+
220
233
  `setup` changes a Codex registration only through `codex mcp add` and
221
234
  `codex mcp remove`, and Codex rewrites `config.toml` on each of them. Codex CLI
222
235
  `0.162.0` groups all `[mcp_servers.*]` tables together and drops a comment
@@ -224,12 +237,14 @@ directly above such a table; settings are preserved. If your `config.toml`
224
237
  contains comments worth keeping, copy it before `--apply`, `--rollback` or
225
238
  `--remove` on `codex:user:pi-session-mcp`.
226
239
 
227
- Each report has one line for the operation with its overall status, and one line
228
- per target with a finding in the form `status (code)`:
240
+ Each report has one line for the operation with its overall status, and per
241
+ target two lines with a finding in the form `status (code)`: the registration
242
+ and the controller skill, whose alias is `pi-session-orchestration`:
229
243
 
230
244
  ```text
231
245
  dry-run: planned
232
246
  claude-code/user/pi-session-mcp: absent (absent)
247
+ claude-code/user/pi-session-orchestration: absent (skill_absent)
233
248
  ```
234
249
 
235
250
  Expected results:
@@ -237,8 +252,11 @@ Expected results:
237
252
  | Operation | Overall | Finding | Meaning |
238
253
  | --- | --- | --- | --- |
239
254
  | `--dry-run` | `planned` | `absent (absent)` | No registration exists. Apply will add one. |
255
+ | `--dry-run` | `planned` | `absent (skill_absent)` | The controller skill is not installed. Apply will install it. |
240
256
  | `--apply` | `ok` | `ok (applied)` | The registration was added and is now owned by `setup`. |
257
+ | `--apply` | `ok` | `ok (skill_installed)` | The controller skill was copied into the client's skill directory and is owned by `setup`. |
241
258
  | `--verify` | `ok` | `ok (mcp_verified)` | Doctor passed, the registration equals the intended one, and a direct MCP handshake listed the eight tools and answered `pi_capabilities_get`. |
259
+ | `--verify` | `ok` | `ok (skill_verified)` | The installed skill equals the effective skill. |
242
260
 
243
261
  Verify never starts a session and never contacts a provider. Add `--json` to
244
262
  any operation for the sanitized JSON report. Exit status `0` is success, `1` an
@@ -255,6 +273,39 @@ If a registration already exists, `setup` never replaces it:
255
273
  Start a new client session after registering. The client launches the server on
256
274
  demand; there is nothing to start by hand.
257
275
 
276
+ ### Controller skill
277
+
278
+ `--apply` copies the effective `pi-session-orchestration` skill (your override
279
+ if you have one, else the shipped skill) as a real copy to the client's skill
280
+ directory, where the client discovers it without further steps:
281
+
282
+ | Target | Skill directory |
283
+ | --- | --- |
284
+ | `codex:user:pi-session-mcp` | `~/.agents/skills/pi-session-orchestration` |
285
+ | `claude-code:user:pi-session-mcp`, `claude-code:local:pi-session-mcp` | `~/.claude/skills/pi-session-orchestration` (one shared directory) |
286
+ | `claude-code:project:pi-session-mcp` | `<project>/.claude/skills/pi-session-orchestration`, where `<project>` is the directory in which you run `setup` |
287
+
288
+ - `setup` never replaces or deletes a modified installed copy, hidden files
289
+ inside it (macOS Finder's `.DS_Store` counts too), a skill of the same name
290
+ that it did not install, or a link or non-directory entry. It reports
291
+ `skill_divergent`; the registration is still made, but the overall status is
292
+ `failed`. A link to exactly the effective skill is accepted as your own
293
+ (`skill_linked`). Inside a project, any link below the project root counts as
294
+ divergent.
295
+ - A package update or a changed override replaces setup's own unmodified copy on
296
+ the next `--apply` (`skill_updated`). `--remove` and `--rollback` delete it
297
+ again (`skill_removed`), except while the other Claude Code registration of
298
+ the shared directory still uses it (`skill_kept_shared`).
299
+ - To change the skill, do not edit the installed copy. Run
300
+ `pi-session-mcp-setup --apply --customize-skill pi-session-orchestration`,
301
+ edit the override it creates, and run `--apply` for the target again.
302
+ - Setup installs only this one skill into clients. Worker skills are for Pi
303
+ profiles; additional controller skills of your own belong in the clients'
304
+ native skill directories.
305
+
306
+ The codes and the rules are in [Pi skills](skills.md#install-the-orchestration-skill-into-codex-and-claude-code)
307
+ and the [setup lifecycle design](client-setup-design.md#skill-installation).
308
+
258
309
  ### Register without setup
259
310
 
260
311
  `setup` runs on Linux and macOS. On every other platform, Windows included, it
@@ -277,7 +328,11 @@ acceptance level covers this path.
277
328
  directory: in an npm installation `echo "$(npm root -g)/pi-session-mcp"`
278
329
  prints that directory, in a release checkout it is the checkout. Write the
279
330
  resulting absolute path into the command; a substitution inside the single
280
- quotes of the Codex example would not be expanded.
331
+ quotes of the Codex example would not be expanded. A manual registration
332
+ installs no skill. To use the controller skill, copy (do not link) the
333
+ directory `skills/pi-session-orchestration` of the package to the client's
334
+ skill directory listed under [Controller skill](#controller-skill), and
335
+ repeat the copy after an upgrade.
281
336
  3. Check the registration. These checks are not a replacement for
282
337
  `setup --verify`: it also performs a bounded direct MCP handshake, and
283
338
  outside `setup` nothing does that yet. The last check below depends on the
@@ -374,13 +429,16 @@ pin the installed version in the profile:
374
429
  and system-prompt changes. In `read-only` profiles the model still gets only
375
430
  `read`, `grep`, `find` and `ls`; in `coding` profiles every extension tool
376
431
  may be active. Extension commands never run from client prompts, but
377
- extension input hooks can still see, rewrite or consume a client prompt.
432
+ extension input hooks can still see, rewrite or consume a client prompt;
433
+ they receive it with `source: "rpc"`, never `interactive`.
378
434
  - **Fail-closed start.** A session start fails with `extension_load_failed` if
379
435
  an extension reports a hook error while binding. It fails the same way if an
380
436
  extension registers an MCP server while loading or starting, because Pi's
381
437
  built-in MCP extension is not loaded. An extension tool whose name collides
382
- with another tool, or whose name is not a plain identifier, fails with
383
- `extension_tool_conflict`.
438
+ with another tool, or whose name is not 1 to 64 letters, digits, `_` or `-`
439
+ (not starting with `-`), or whose name Pi treats as an MCP tool (starting
440
+ with `mcp__`, or `list_mcp_resources`, `list_mcp_resource_templates`,
441
+ `read_mcp_resource`), fails with `extension_tool_conflict`.
384
442
  - **Cursor runtime.** `pi-cursor-sdk` runs Cursor's local agent runtime. That
385
443
  runtime starts its own processes (for example git and the MCP servers
386
444
  configured for Cursor) and may act through Cursor's own tooling, so a
@@ -391,6 +449,57 @@ pin the installed version in the profile:
391
449
  reports a placeholder key. Session start succeeds and the first prompt fails
392
450
  with `turn_failed`. Store the key with Pi first.
393
451
 
452
+ ## Pi skills
453
+
454
+ An execution profile can give its sessions Pi skills. The package ships two
455
+ worker skills, `pi-session-implementation` and `pi-session-review`, and the
456
+ controller skill `pi-session-orchestration`, in its `skills/` directory. The
457
+ catalog maps an alias to a skill; the profile selects aliases. MCP callers never
458
+ choose skills.
459
+
460
+ ```json
461
+ "skillCatalog": {
462
+ "pi-session-review": "builtin:pi-session-review"
463
+ },
464
+ "executionProfiles": {
465
+ "safe-readonly": {
466
+ "default": true,
467
+ "permissionProfile": "read-only",
468
+ "provider": "your-configured-provider",
469
+ "model": "your-configured-model",
470
+ "thinkingLevel": "medium",
471
+ "skills": ["pi-session-review"]
472
+ }
473
+ }
474
+ ```
475
+
476
+ - `builtin:<name>` resolves to your override
477
+ `<directory of the configuration file>/skills/<name>/SKILL.md` if it exists,
478
+ else to the shipped skill. The shipped example configuration already uses
479
+ `builtin:pi-session-implementation` and `builtin:pi-session-review`, so the
480
+ copied example needs no path edits for skills. Adding or removing an override
481
+ needs a server restart, like any configuration change.
482
+ - To adjust a shipped skill, run `pi-session-mcp-setup --customize-skill <name>`
483
+ (a dry-run) and then `pi-session-mcp-setup --apply --customize-skill <name>`
484
+ with `PI_SESSION_MCP_CONFIG` set. It copies the shipped skill into the
485
+ override directory as a starting point and never overwrites an existing
486
+ override. Edit the copy, then restart the server. To add a skill of your own,
487
+ create `<override dir>/<new-name>/SKILL.md` and reference it as
488
+ `builtin:<new-name>`; no command is needed. After a package update, Doctor
489
+ warns with `skill_override_outdated` when a shipped skill changed since you
490
+ copied it.
491
+ - A `builtin:` name with no override and no shipped skill makes Doctor report
492
+ `skill_missing` and the session start fail with `skill_load_failed`.
493
+ - An extension allowlist alone makes none of a package's skills available. If a
494
+ profile relied on them, add a catalog entry with the absolute path of the
495
+ package's `SKILL.md` and select it; see the migration section of [Pi
496
+ skills](skills.md).
497
+ - A skill file is read by the SDK at session start and later by the model.
498
+ Do not change skill files while sessions that use them are active; close
499
+ those sessions first.
500
+
501
+ The complete rules, errors, Doctor checks and limits are in [Pi skills](skills.md).
502
+
394
503
  ## Upgrade
395
504
 
396
505
  `setup` binds each owned registration to the package manifests and to every
@@ -398,6 +507,12 @@ built runtime file, so the old version must remove its registration before the
398
507
  new one applies. Read the [changelog](../CHANGELOG.md) before upgrading; it
399
508
  records changes that affect operators, such as renamed provider keys.
400
509
 
510
+ Removing the registration also removes the controller skill that setup
511
+ installed, unless you modified it, and `--apply` for the new version installs
512
+ the new text. A package update that changes a shipped skill does not touch an
513
+ override of yours; Doctor warns with `skill_override_outdated` so that you can
514
+ review the change.
515
+
401
516
  ### npm installation
402
517
 
403
518
  An npm upgrade replaces the package in place. Close the client sessions that
@@ -462,7 +577,12 @@ npm uninstall -g pi-session-mcp
462
577
  same owned removal and reports `rolled_back`; use it to undo an apply. After
463
578
  every target reports `removed`, uninstall the package or delete the release
464
579
  directory, and delete the configuration file and
465
- `~/.local/state/pi-session-mcp/`.
580
+ `~/.local/state/pi-session-mcp/`. The same call removes the controller skill
581
+ that `setup` installed (`skill_removed`). It keeps a copy that you modified
582
+ (`skill_divergent`) and the shared Claude Code directory while the other
583
+ Claude Code registration is still owned (`skill_kept_shared`). Your override
584
+ directory `skills/` next to the configuration file is yours: delete it with the
585
+ configuration file if you no longer need it.
466
586
 
467
587
  ## When something fails
468
588