pi-session-mcp 0.7.1 → 0.8.1

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 +75 -2
  3. package/.pi-session-mcp-cli/execution-profile.js +1 -0
  4. package/.pi-session-mcp-cli/override-lock.js +125 -0
  5. package/.pi-session-mcp-cli/setup-platform.js +195 -0
  6. package/.pi-session-mcp-cli/setup-result.js +8 -0
  7. package/.pi-session-mcp-cli/skill-catalog.js +35 -0
  8. package/.pi-session-mcp-cli/skill-customize.js +268 -0
  9. package/.pi-session-mcp-cli/skill-library.js +188 -0
  10. package/CHANGELOG.md +176 -0
  11. package/README.md +15 -3
  12. package/dist/capabilities.d.ts +1 -0
  13. package/dist/capabilities.js +2 -1
  14. package/dist/client-adapters/mcp-verifier.js +6 -2
  15. package/dist/config.d.ts +4 -0
  16. package/dist/config.js +47 -7
  17. package/dist/doctor.d.ts +7 -1
  18. package/dist/doctor.js +75 -2
  19. package/dist/execution-profile.d.ts +4 -0
  20. package/dist/execution-profile.js +1 -0
  21. package/dist/override-lock.d.ts +34 -0
  22. package/dist/override-lock.js +125 -0
  23. package/dist/pi-adapter.d.ts +7 -5
  24. package/dist/pi-adapter.js +1 -0
  25. package/dist/pi-skills.d.ts +17 -0
  26. package/dist/pi-skills.js +64 -0
  27. package/dist/sdk-pi-adapter.d.ts +7 -3
  28. package/dist/sdk-pi-adapter.js +82 -16
  29. package/dist/server.js +1 -0
  30. package/dist/session-registry.d.ts +8 -6
  31. package/dist/session-registry.js +3 -2
  32. package/dist/setup-cli.d.ts +2 -0
  33. package/dist/setup-cli.js +16 -0
  34. package/dist/setup-main.d.ts +1 -1
  35. package/dist/setup-main.js +18 -3
  36. package/dist/setup-ownership.d.ts +9 -0
  37. package/dist/setup-ownership.js +32 -5
  38. package/dist/setup-skills.d.ts +76 -0
  39. package/dist/setup-skills.js +586 -0
  40. package/dist/setup.d.ts +7 -0
  41. package/dist/setup.js +49 -6
  42. package/dist/skill-catalog.d.ts +26 -0
  43. package/dist/skill-catalog.js +35 -0
  44. package/dist/skill-customize.d.ts +41 -0
  45. package/dist/skill-customize.js +268 -0
  46. package/dist/skill-library.d.ts +106 -0
  47. package/dist/skill-library.js +188 -0
  48. package/docs/acceptance.md +593 -1
  49. package/docs/adr/0006-pi-extension-allowlist.md +3 -0
  50. package/docs/adr/0007-profile-skill-selection.md +228 -0
  51. package/docs/architecture.md +10 -6
  52. package/docs/client-setup-design.md +219 -1
  53. package/docs/client-setup.md +86 -3
  54. package/docs/doctor-design.md +9 -1
  55. package/docs/installation.md +128 -5
  56. package/docs/pi-extensions-design.md +28 -4
  57. package/docs/releasing.md +18 -9
  58. package/docs/session-mcp.md +7 -2
  59. package/docs/skills.md +616 -0
  60. package/docs/threat-model.md +19 -2
  61. package/docs/tool-contracts.md +9 -5
  62. package/package.json +2 -1
  63. package/pi-session-mcp.example.json +9 -3
  64. package/skills/pi-session-implementation/SKILL.md +42 -0
  65. package/skills/pi-session-orchestration/SKILL.md +94 -0
  66. package/skills/pi-session-review/SKILL.md +58 -0
package/docs/skills.md ADDED
@@ -0,0 +1,616 @@
1
+ # Pi skills in execution profiles
2
+
3
+ Status: decided in [ADR 0007](adr/0007-profile-skill-selection.md), issue
4
+ [#46](https://github.com/plagor44-ux/pi-session-mcp/issues/46).
5
+
6
+ A Pi skill is a `SKILL.md` file with instructions that Pi lists to the model in
7
+ its system prompt. The model may read the file when a task matches the skill's
8
+ description. Pi Session MCP lets the operator register skill files in a catalog
9
+ and select them per execution profile. MCP callers never choose skills.
10
+
11
+ The package ships three skills and keeps them in a skill library together with
12
+ the operator's own overrides. `pi-session-mcp-setup --apply` also installs the
13
+ controller skill into Claude Code and Codex. Nothing needs to be linked by hand.
14
+ Sections: [Configuration](#configuration), [The skill library](#the-skill-library),
15
+ [Customize and add skills](#customize-and-add-skills), [Install the
16
+ orchestration skill into Codex and Claude Code](#install-the-orchestration-skill-into-codex-and-claude-code)
17
+ and [Upgrade from 0.8.0](#upgrade-from-080).
18
+
19
+ ## Configuration
20
+
21
+ Two optional parts of `pi-session-mcp.json`:
22
+
23
+ ```json
24
+ {
25
+ "workspaces": {
26
+ "my-project": "/absolute/path/to/my-project"
27
+ },
28
+ "skillCatalog": {
29
+ "pi-session-implementation": "builtin:pi-session-implementation",
30
+ "pi-session-review": "builtin:pi-session-review",
31
+ "release-notes": "skills-local/release-notes/SKILL.md"
32
+ },
33
+ "executionProfiles": {
34
+ "safe-readonly": {
35
+ "default": true,
36
+ "permissionProfile": "read-only",
37
+ "provider": "your-configured-provider",
38
+ "model": "your-configured-model",
39
+ "thinkingLevel": "medium",
40
+ "skills": ["pi-session-review"]
41
+ },
42
+ "coding-worker": {
43
+ "default": false,
44
+ "permissionProfile": "coding",
45
+ "provider": "your-configured-provider",
46
+ "model": "your-configured-model",
47
+ "thinkingLevel": "high",
48
+ "skills": ["pi-session-implementation", "release-notes"]
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ ### Catalog: `skillCatalog`
55
+
56
+ - An optional top-level object that maps a skill alias to the path of a
57
+ `SKILL.md` file. A missing catalog is an empty catalog.
58
+ - At most 64 entries. An alias matches `^[a-z][a-z0-9_-]{0,63}$`, like every
59
+ other alias.
60
+ - Each value is either `builtin:<name>` or names exactly one regular, readable
61
+ file whose last path segment is `SKILL.md`. `builtin:<name>` refers to a
62
+ skill of the [skill library](#the-skill-library); `<name>` must follow the
63
+ skill name rule below, and `builtin:` followed by anything else is rejected.
64
+ Directories, globs, URLs and every other scheme-prefixed source (`npm:`,
65
+ `git+…`, `https:`) are rejected. So are `~` and backslashes. A value has at
66
+ most 4096 characters.
67
+ - A relative path resolves against the directory of the configuration file,
68
+ like a workspace. It never resolves against a workspace or the shell's
69
+ working directory. `~` is not expanded: write an absolute path, or use
70
+ `builtin:<name>` for a library skill.
71
+ - A skill folder needs no `package.json` and no extension. The file can live
72
+ anywhere the server's OS user can read. Keep catalog files outside every
73
+ configured workspace: a `coding` session can change any file in its
74
+ workspace, and a changed skill reaches other sessions too (see
75
+ [snapshot semantics](#snapshot-semantics)).
76
+ - The catalog alias is only the public handle. It may differ from the `name` in
77
+ the skill's own frontmatter. Nothing is renamed.
78
+
79
+ ### Selection: `executionProfiles.<alias>.skills`
80
+
81
+ - An optional list of catalog aliases, at most 16, no duplicates.
82
+ - Missing or empty means no skills for that profile. There is no inheritance
83
+ between profiles and no default skill.
84
+ - All other profile fields and the rule that unknown fields are rejected are
85
+ unchanged.
86
+
87
+ ### Invalid configuration
88
+
89
+ These make the whole configuration invalid. The server does not start, and
90
+ Doctor reports `config_invalid`. Stderr shows the stable startup code
91
+ `invalid_execution_profile_config`:
92
+
93
+ - a selected alias that is not in the catalog;
94
+ - a duplicate alias in one profile's list;
95
+ - two selected aliases of one profile whose paths resolve to the same path,
96
+ for example two aliases for `builtin:pi-session-review`;
97
+ - a catalog value that violates the rules above (including a malformed
98
+ `builtin:` name), or more than 64 entries;
99
+ - more than 16 selected aliases.
100
+
101
+ ## The skill library
102
+
103
+ The skill library has two sources and nothing else:
104
+
105
+ 1. **Shipped skills.** The package's `skills/` directory.
106
+ 2. **Operator overrides.** A `skills/` directory next to the configuration
107
+ file, that is, in the directory of the path in `PI_SESSION_MCP_CONFIG`. For
108
+ the default location `~/.config/pi-session-mcp/pi-session-mcp.json` it is
109
+ `~/.config/pi-session-mcp/skills/<name>/SKILL.md`.
110
+
111
+ An override with the same name replaces the shipped skill. A name that is not
112
+ shipped adds a skill. A name follows the Agent Skills rule: lowercase letters
113
+ and digits in hyphen-separated groups, 1 to 64 characters. No other directory
114
+ is searched: not the current directory, not a workspace and not a client's
115
+ skill directory.
116
+
117
+ ### `builtin:<name>` in the catalog
118
+
119
+ A catalog value `builtin:<name>` resolves to `<override dir>/<name>/SKILL.md`
120
+ if that file exists, else to the shipped skill of that name. If neither
121
+ exists, the value keeps the shipped location. Nothing fails when the
122
+ configuration loads, but Doctor then reports `skill_missing` for the profile
123
+ that selects it, and a session start fails with `skill_load_failed`.
124
+
125
+ Resolution happens when the server loads the configuration. Adding or removing
126
+ an override therefore needs a server restart, like any configuration change.
127
+ Edits to a skill file that already resolves reach new sessions, as described
128
+ under [snapshot semantics](#snapshot-semantics). Deleting a resolved override
129
+ leaves the catalog pointing at a missing file until the restart.
130
+
131
+ The shipped example configuration registers the two worker skills with
132
+ `builtin:`. After you copy the example, its skill entries need no path edits.
133
+
134
+ ### Shipped skills
135
+
136
+ The package ships three skills under `skills/`. Each has one canonical copy and
137
+ frontmatter with only `name` and `description`.
138
+
139
+ | Skill | For | Use |
140
+ | --- | --- | --- |
141
+ | `pi-session-implementation` | A Pi worker | One bounded implementation task and a fixed final report. Register it as `builtin:pi-session-implementation` and select it on coding profiles. |
142
+ | `pi-session-review` | A Pi worker | A review of a fixed target with findings and evidence. Register it as `builtin:pi-session-review` and select it on read-only profiles. |
143
+ | `pi-session-orchestration` | The controlling Claude Code or Codex agent | How to run Pi worker sessions through this server. `setup --apply` installs it into the clients. It is not a Pi worker skill: do not select it in a profile. |
144
+
145
+ A package update can change the shipped skills. Close affected sessions before
146
+ you update, as for any skill change.
147
+
148
+ ## Customize and add skills
149
+
150
+ Do not edit the files inside the installed package: an update replaces them.
151
+ Put your version in the override directory.
152
+
153
+ ### Customize a shipped skill
154
+
155
+ ```bash
156
+ export PI_SESSION_MCP_CONFIG="$HOME/.config/pi-session-mcp/pi-session-mcp.json"
157
+ pi-session-mcp-setup --customize-skill pi-session-review # dry-run
158
+ pi-session-mcp-setup --apply --customize-skill pi-session-review
159
+ ```
160
+
161
+ The dry-run reports what would happen, for example:
162
+
163
+ ```text
164
+ dry-run: planned
165
+ skills/override/pi-session-review: planned (skill_customize_planned)
166
+ ```
167
+
168
+ `--apply` copies the shipped skill directory into the override directory as a
169
+ starting point and records the fingerprint of the shipped skill in
170
+ `<override dir>/.pi-session-mcp-bases.json`. Then edit
171
+ `<override dir>/pi-session-review/SKILL.md`, restart the server so that the new
172
+ override is picked up, and start new sessions.
173
+
174
+ - The command works without `--target`; it touches no client. It cannot be
175
+ combined with `--target`, `--verify`, `--rollback` or `--remove`, which are
176
+ usage errors (exit 64).
177
+ - It requires `PI_SESSION_MCP_CONFIG` (otherwise `config_required`) and the
178
+ same platform as the rest of `setup`: Linux or macOS, otherwise
179
+ `platform_unsupported`. It takes no ownership lock. `--apply` takes the
180
+ [override lock](#concurrent-runs-and-the-override-lock); the dry-run takes no
181
+ lock.
182
+ - It never overwrites an existing override and never edits your files.
183
+ - Only regular files are copied: at most 32 files, 1 MiB and 8 directory
184
+ levels, no links, and entries whose name starts with `.` are skipped.
185
+
186
+ The finding target is `{ client: "skills", scope: "override", alias: <name> }`:
187
+
188
+ | Code | Status | Meaning |
189
+ | --- | --- | --- |
190
+ | `skill_customize_planned` | planned | Dry-run: no override exists, or an existing override equals the shipped skill but has no recorded base; `--apply` would copy the shipped skill if it is absent and record the base. |
191
+ | `skill_customized` | ok | The override was created and its base recorded, or an existing override that equals the shipped skill got its missing base. The second case completes a customization whose bases commit failed. |
192
+ | `skill_override_current` | unchanged | The override exists and its recorded base equals the current shipped skill. Of two concurrent applies for the same skill, the later one reports this. |
193
+ | `skill_override_outdated` | planned | The shipped skill changed since the copy, or no base is recorded for an existing override that differs from the shipped skill, or a 0.8.0 base names a released shipped skill that is not the current one. |
194
+ | `skill_base_migration_planned` | planned | Dry-run: a 0.8.0 base names the current shipped skill; `--apply` would record it as a version 2 base. |
195
+ | `skill_base_migrated` | ok | `--apply` after `skill_base_migration_planned`: the base is recorded as version 2. |
196
+ | `skill_base_unverified` | planned | Dry-run: a 0.8.0 base names no released shipped skill, so it cannot be confirmed. `--apply` records the current shipped skill as the base after you reviewed it. |
197
+ | `skill_base_updated` | ok | `--apply` after `skill_override_outdated` or `skill_base_unverified`: only the recorded base moved. |
198
+ | `skill_not_shipped` | failed | The name is not valid or the package ships no such skill. |
199
+ | `skill_bases_corrupt` | failed | The bases file is not valid. Repair or remove it. |
200
+ | `skill_override_invalid` | failed | The override entry is not a directory, or its tree is unusable (no `SKILL.md`, a link, too large). |
201
+ | `skill_customize_lock_busy` | failed | Another run held the override lock for the whole wait of 30 seconds. Run the command again. |
202
+ | `skill_customize_lock_unavailable` | failed | The lock cannot be taken: the platform, the filesystem or the `flock` binary is not supported. |
203
+ | `skill_customize_failed` | failed | Any other failure while copying or recording. |
204
+
205
+ ### Concurrent runs and the override lock
206
+
207
+ `--apply --customize-skill` is one transaction per override directory. It takes
208
+ an exclusive kernel lock on `<override dir>/.pi-session-mcp-bases.lock` (mode
209
+ `0600`, never deleted), reads and validates the bases file under the lock,
210
+ copies the shipped skill if no override exists, merges the base, and replaces
211
+ the bases file atomically: an fsynced temporary file, a rename and a directory
212
+ fsync. The lock is released on every path. Two runs for different skills
213
+ therefore cannot lose each other's base. If the commit of the bases file
214
+ fails, the previous file stays; a retry then completes the customization
215
+ without overwriting your files (`skill_customized` for an override that equals
216
+ the shipped skill and has no base).
217
+
218
+ - The lock wait is bounded at 30 seconds, then the result is
219
+ `skill_customize_lock_busy`. A lock that cannot be taken at all is
220
+ `skill_customize_lock_unavailable`.
221
+ - On Linux the lock needs the trusted util-linux `flock` at `/usr/bin/flock` or
222
+ `/bin/flock`; on macOS it uses `O_EXLOCK`. Both need a supported local
223
+ filesystem.
224
+ - A holder that is killed releases the lock through the kernel. It may leave a
225
+ stale hidden temporary bases file `.pi-session-mcp-bases.json.<hex>.tmp` or
226
+ hidden temporary copy directory `.<skill>.<hex>.tmp` in the override
227
+ directory. Both are harmless: they are hidden and never resolved as skills.
228
+ Nothing removes them automatically; you may delete them while no
229
+ customization runs.
230
+ - The dry-run takes no lock and writes nothing: no directory, lock file or
231
+ temporary file.
232
+ - Doctor stays offline, read-only and lock-free.
233
+
234
+ The bases file is `{"schemaVersion":2,"bases":{"<name>":"<fingerprint>"}}`. A
235
+ value is a `v2:` fingerprint, or a legacy 64-hex value that could not be
236
+ migrated. A version 1 file (0.8.0) is still read. Every write produces version
237
+ 2 and migrates each legacy value that names a released 0.8.0 shipped skill; any
238
+ other legacy value is kept as it is. See [Upgrade from
239
+ 0.8.0](#upgrade-from-080).
240
+
241
+ ### Follow a changed shipped skill
242
+
243
+ After a package update, `skill_override_outdated` (from the command above, or
244
+ as a [Doctor](#doctor-boundary) warning) means that the shipped skill differs
245
+ from the one your override was copied from. Review the shipped text, merge what
246
+ you want into your override by hand, and then acknowledge it:
247
+
248
+ ```bash
249
+ pi-session-mcp-setup --apply --customize-skill pi-session-review
250
+ ```
251
+
252
+ That moves only the recorded base (`skill_base_updated`). It never touches your
253
+ files. The override keeps winning until you delete it; the base only decides
254
+ whether Doctor warns.
255
+
256
+ ### Add a new skill
257
+
258
+ No command is needed. Create `<override dir>/<new-name>/SKILL.md` (a valid
259
+ Agent Skills file with `name` and `description` frontmatter), reference it as
260
+ `builtin:<new-name>` in `skillCatalog`, select its alias in a profile, and
261
+ restart the server. Companion files belong in the same directory. A name that
262
+ equals a shipped skill replaces that skill instead of adding one.
263
+
264
+ Where `setup` is unsupported, create an override by copying a shipped skill
265
+ directory by hand. Doctor does not track a copy that has no recorded base.
266
+
267
+ ## Install the orchestration skill into Codex and Claude Code
268
+
269
+ The orchestration skill is for the controlling agent, not for a Pi worker.
270
+ `pi-session-mcp-setup --apply --target <target>` installs it after the
271
+ registration, as a real copy of the effective skill: your override if you have
272
+ one, else the shipped skill. Clients discover it without further steps.
273
+
274
+ | Target | Skill directory |
275
+ | --- | --- |
276
+ | `codex:user:pi-session-mcp` | `~/.agents/skills/pi-session-orchestration` |
277
+ | `claude-code:user:pi-session-mcp` and `claude-code:local:pi-session-mcp` | `~/.claude/skills/pi-session-orchestration` (one shared directory) |
278
+ | `claude-code:project:pi-session-mcp` | `<project>/.claude/skills/pi-session-orchestration`, where `<project>` is the directory in which `setup` runs |
279
+
280
+ Claude Code reads personal skills in `~/.claude/skills/<name>/SKILL.md` and
281
+ project skills in `.claude/skills/<name>/SKILL.md`
282
+ ([Claude Code skills](https://code.claude.com/docs/en/skills)). Codex reads user
283
+ skills in `~/.agents/skills`
284
+ ([Codex skills](https://learn.chatgpt.com/docs/build-skills)).
285
+
286
+ Every `setup` result has a second finding per target, with the same client and
287
+ scope and the alias `pi-session-orchestration`. A skill problem never blocks
288
+ the registration, but a `divergent` or `failed` skill finding makes the overall
289
+ status `failed` (exit 1), so it is not overlooked.
290
+
291
+ | Operation | Code (status) |
292
+ | --- | --- |
293
+ | `--dry-run` | `skill_absent` (absent), `skill_equivalent` (unchanged), `skill_linked` (unchanged), `skill_update_planned` (planned), `skill_recovery_planned` (planned), `skill_divergent` (divergent), `skill_legacy_unverified` (divergent), `skill_source_invalid` (failed) |
294
+ | `--apply` | `skill_installed` (ok), `skill_already_equivalent` (unchanged), `skill_recovered` (ok), `skill_linked` (unchanged), `skill_updated` (ok), `skill_divergent` (divergent), `skill_legacy_unverified` (divergent), `skill_recovery_retained` (divergent), `skill_source_invalid` (failed), `skill_install_failed` (failed) |
295
+ | `--verify` | `skill_verified` (ok), `skill_missing` (failed), `skill_not_equivalent` (failed) |
296
+ | `--rollback`, `--remove` | `skill_removed` (ok), `skill_kept_shared` (unchanged), `skill_not_owned` (unchanged), `skill_absent` (ok), `skill_divergent` (divergent), `skill_legacy_unverified` (divergent), `skill_recovery_retained` (divergent) |
297
+
298
+ - `--apply` installs the skill only when the registration finding is `ok` or
299
+ `unchanged`; `--rollback` and `--remove` handle it only after the
300
+ registration was removed.
301
+ - `skill_updated` means that setup replaced its own unmodified copy after a
302
+ package update or a changed override.
303
+ - `skill_recovered` means that `--apply` settled an interrupted update of
304
+ setup's own copy (it renamed an intermediate into place or deleted one, or
305
+ finished the pending ownership record) and needed no further update.
306
+ If the settled copy still needs the new version, `--apply` continues and
307
+ reports `skill_installed` or `skill_updated`.
308
+ - `skill_recovery_planned` means that the ownership record is pending for an
309
+ update that `--apply` would settle. The dry-run does not write or delete
310
+ anything and does not look at the intermediate directories, so `--apply` can
311
+ still find a modified or foreign intermediate, keep it and report
312
+ `skill_recovery_retained`. A divergent or
313
+ foreign copy keeps `skill_divergent`, and a 0.8.0 record keeps its own code.
314
+ - `skill_kept_shared` means that the other Claude Code registration (`user` or
315
+ `local`) still uses the shared directory. The last one removes the skill.
316
+ - A copy that already equals the effective skill but was not created by `setup`
317
+ is reported as equivalent and stays unowned; a later `--remove` reports
318
+ `skill_not_owned`.
319
+ - `--verify` compares the installed copy with the effective skill and does not
320
+ look at ownership.
321
+
322
+ ### What setup never replaces or deletes
323
+
324
+ Setup reports `skill_divergent` and leaves the entry alone when it finds:
325
+
326
+ - an installed copy that was modified after setup wrote it;
327
+ - a skill of the same name that setup did not install and that differs from the
328
+ effective skill;
329
+ - any entry whose name starts with `.` inside an installed copy, such as your
330
+ own notes or a `.DS_Store` that macOS Finder adds: setup never writes such
331
+ entries, so it treats them as yours; delete them to let setup manage the copy
332
+ again;
333
+ - a link at the skill entry that points anywhere but the effective skill, or a
334
+ non-directory there.
335
+
336
+ A link at the skill entry that points exactly at the effective skill (your
337
+ override, else the shipped skill) is your own equivalent setup: setup reports
338
+ `skill_linked` (unchanged), `--verify` reports `skill_verified`, and removal
339
+ leaves the link alone (`skill_not_owned`). If you replace setup's own copy with
340
+ such a link, setup forgets its copy on the next `--apply` or `--remove`. A link
341
+ counts only while the effective skill itself is valid.
342
+
343
+ A project copy belongs to its project: setup records an opaque project
344
+ identity, so a `--remove` in another project never deletes it.
345
+
346
+ Links in the path to the entry are handled by place. In your home directory,
347
+ the client's skill directory may be your own link, for example into a dotfiles
348
+ checkout, and is followed. Inside a project, which may be untrusted, any link
349
+ below the project root makes the entry divergent, so setup never writes outside
350
+ the project.
351
+
352
+ ### Interrupted updates
353
+
354
+ An update first writes a pending ownership record, then works in intermediate
355
+ directories in the parent of the skill directory. They are named after the
356
+ record's transaction id: `.pi-session-mcp-<skill>-<transactionId>.staging`,
357
+ `.backup` and `.trash`. The order is: pending record, staging directory, rename
358
+ of the old copy to the backup, install, verification, rename of the backup to
359
+ the trash, deletion of the trash entry by entry, owned record.
360
+
361
+ If an update is interrupted, for example by a full disk or a signal, setup
362
+ still recognizes the old copy as its own. `--apply`, `--remove` and
363
+ `--rollback` settle a pending transaction first:
364
+
365
+ - `--apply` finishes forward when a complete staging directory is left, or
366
+ moves the verified backup back, and then continues.
367
+ - `--remove` and `--rollback` delete the verified intermediates and then remove
368
+ the copy as usual.
369
+ - A partial trash directory is deleted after a structural recheck (real
370
+ directories and regular files only, no hidden entries, within the tree
371
+ limits).
372
+ - A partially written staging directory is deleted only when every file in it
373
+ is a prefix-consistent part of the current library tree, and that tree is the
374
+ record's target.
375
+ - Anything modified or foreign stays and is reported as
376
+ `skill_recovery_retained` (divergent). Inspect the directory and delete it
377
+ yourself if it is not yours.
378
+
379
+ A failed update leaves its staging directory until the next run settles it.
380
+ The directories of updates by 0.8.0 had random names. Setup never searches for
381
+ or deletes them; you may delete them by hand. `--dry-run` and `--verify` do not
382
+ report leftover intermediates.
383
+
384
+ ### Customize the controller skill
385
+
386
+ Never edit the installed copy: it becomes divergent. Instead:
387
+
388
+ 1. `pi-session-mcp-setup --apply --customize-skill pi-session-orchestration`
389
+ 2. Edit `<override dir>/pi-session-orchestration/SKILL.md`.
390
+ 3. `pi-session-mcp-setup --apply --target <target>` again. The unmodified
391
+ installed copy is replaced (`skill_updated`).
392
+
393
+ To recover after editing the installed copy, move your edits into the override,
394
+ delete the installed copy, and run `--apply` again.
395
+
396
+ ### Scope of what setup installs
397
+
398
+ Setup installs only `pi-session-orchestration` into clients. The worker skills
399
+ are for Pi profiles and are used through `builtin:`. Controller skills of your
400
+ own belong in the clients' native skill directories; setup does not manage
401
+ them.
402
+
403
+ ### Install by hand where setup is unsupported
404
+
405
+ Where `setup` does not run, copy the directory; do not link it. Copy
406
+ `skills/pi-session-orchestration` from the installed package (in an npm
407
+ installation `"$(npm root -g)/pi-session-mcp/skills/pi-session-orchestration"`)
408
+ to `~/.claude/skills/` or `.claude/skills/` of a project for Claude Code, or to
409
+ `~/.agents/skills/` for Codex. Repeat the copy after a package update; nothing
410
+ keeps a manual copy current. Do not replace an existing skill of the same name
411
+ without checking it first.
412
+
413
+ Neither client needs a skill to call the Pi Session MCP tools; the skill only
414
+ describes the working method. Headless `codex exec` runs still need the
415
+ per-tool approvals for `pi_session_prompt`, `pi_session_abort` and
416
+ `pi_session_close` that [client
417
+ setup](client-setup.md#run-codex-without-prompts) describes.
418
+
419
+ ### Pi workers
420
+
421
+ Pi workers never discover skills on their own in Pi Session MCP. Register the
422
+ worker skills with `builtin:` and select them on profiles, as described above.
423
+ Do not select the orchestration skill for a Pi worker: a worker has only the
424
+ tools of its profile and is not a controller of other Pi sessions.
425
+
426
+ ## What happens at session start
427
+
428
+ The server loads exactly the selected files through the Pi SDK's public
429
+ `loadSkills`, without any default directories, and validates them completely.
430
+ The SDK's skill override returns exactly that set, also after an extension adds
431
+ resources later (`resources_discover`). A profile without skills gets no
432
+ skills, whatever is installed on the machine or shipped by an extension.
433
+
434
+ The start fails with `skill_load_failed` (message `Configured Pi skill could
435
+ not be loaded`) if:
436
+
437
+ - a selected file is missing, unreadable, or larger than 256 KiB (also a
438
+ `builtin:` value that resolved to no skill);
439
+ - the SDK reports the skill as invalid, for example a missing description or an
440
+ invalid name;
441
+ - two selected files declare the same Pi skill name;
442
+ - two aliases point at the same canonical file, for example through a symlink;
443
+ - the skill sets `disable-model-invocation: true`.
444
+
445
+ The message is fixed. It never contains a path, a skill name or SDK text.
446
+ Stderr gets a structured diagnostic. A selected file that fails validation
447
+ reports the stage `skill_load` and, when one alias is to blame, that catalog
448
+ alias in a `skill` field. If the session's actual skill catalog differs from
449
+ the selection after construction, the stage is `postcheck`, or
450
+ `extension_load` and `extension_bind` for a profile with extensions. Neither
451
+ carries a path.
452
+
453
+ If the session's prompt-template or theme catalog is not empty after
454
+ construction, which the overrides should prevent, the start fails with
455
+ `pi_session_creation_failed`.
456
+
457
+ Explicit-only skills (`disable-model-invocation: true`) are incompatible in
458
+ this version. Pi hides such skills from the model, and MCP prompts never run
459
+ `/skill:` commands, so the skill could never be used. The server does not
460
+ rewrite the flag. The same Pi skill name in different sessions is fine.
461
+
462
+ ## Doctor boundary
463
+
464
+ Doctor stays offline and never parses or executes a skill. For each profile and
465
+ selected alias it adds three checks after the extension checks, with the
466
+ subject `<profile>:<skill-alias>`. A `builtin:` alias is checked at the
467
+ location it resolved to:
468
+
469
+ | ID | Condition |
470
+ | --- | --- |
471
+ | `skill_missing` | The selected file does not exist. |
472
+ | `skill_unreadable` | The path is not a regular file, is over 256 KiB, or cannot be read. |
473
+ | `skill_duplicate` | Two selected aliases of one profile resolve to the same canonical file. |
474
+
475
+ Frontmatter, name rules, name collisions and the explicit-only flag are checked
476
+ only at session start by the SDK contract. A clean Doctor run therefore does
477
+ not promise that the skill loads.
478
+
479
+ A fourth check concerns overrides, not profiles:
480
+
481
+ | ID | Severity | Subject | Condition |
482
+ | --- | --- | --- | --- |
483
+ | `skill_override_outdated` | warning | `skill:<name>` | The shipped skill's current fingerprint differs from the base recorded in `<override dir>/.pi-session-mcp-bases.json`. A 0.8.0 base counts as the released shipped skill that it names. |
484
+ | `skill_override_outdated` | warning | `skill:<name>` | The recorded base is a 0.8.0 value that names no released shipped skill. The remediation says that the base predates version 2 fingerprints and cannot be confirmed: review the shipped skill, then run `pi-session-mcp-setup --apply --customize-skill <name>`. |
485
+ | `skill_override_outdated` | warning | `skill:bases` | The bases file is corrupt. |
486
+
487
+ It is `ok` for a recorded base that still matches. The remediation for a
488
+ changed skill names the command
489
+ `pi-session-mcp-setup --apply --customize-skill <name>`, after you reviewed the
490
+ change (see [Follow a changed shipped skill](#follow-a-changed-shipped-skill)).
491
+ Only skills with a recorded base are compared. Doctor reads the bases file and
492
+ the shipped files and executes nothing. A warning gives exit status `1`; it does
493
+ not block `setup`.
494
+
495
+ ## Three states, and a fourth thing
496
+
497
+ Keep these apart:
498
+
499
+ 1. **Configured.** `pi_capabilities_get` lists the alias under the profile's
500
+ `skills`. This is configuration intent only. It says nothing about the file.
501
+ 2. **Available in the SDK catalog.** The skill passed validation at session
502
+ start and Pi lists it to the model in the system prompt.
503
+ 3. **Actually read by the model.** The model decides to read the `SKILL.md`
504
+ with its `read` tool. This is not guaranteed.
505
+
506
+ Task success is separate from all three. A skill that the model read can still
507
+ produce a bad result, and a task can succeed without the skill.
508
+
509
+ ## Snapshot semantics
510
+
511
+ The profile and its skill selection are fixed per session. The skill content
512
+ is not snapshotted: the SDK keeps file paths, and the model's later reads may
513
+ see changed files. This also applies to companion files that a skill refers
514
+ to.
515
+
516
+ - Manage skill files and companion files as operator content. Do not change
517
+ them while sessions that use them are active.
518
+ - Close the affected sessions before an update. New sessions then load the new
519
+ content.
520
+ - The configuration fingerprint covers the configuration bytes, not skill
521
+ contents.
522
+ - There is no hot reload, no watcher, no copy store for sessions and no
523
+ versioning. A change
524
+ to `skillCatalog` or `skills` needs a new server process like any other
525
+ configuration change. So does adding or removing an override of a library
526
+ skill, because `builtin:` is resolved when the configuration loads. Editing
527
+ the file of an override that already resolves needs no restart for new
528
+ sessions.
529
+
530
+ ## Trust and limits
531
+
532
+ Skills are operator-managed prompt content. The override directory and its
533
+ bases file are operator-trusted configuration next to the configuration file,
534
+ never a workspace; keep them out of every workspace and writable only by you.
535
+ Setup copies the effective orchestration skill into the clients' skill
536
+ directories, where it becomes instructions for the controlling agent, so review
537
+ an override like code. In a `coding` profile a skill's
538
+ instructions are executed with that profile's tools, so review a skill like
539
+ code. A skill grants no rights: the tools of the permission profile stay the
540
+ limit.
541
+
542
+ Skill selection limits the regular skill catalog only. An allowlisted
543
+ extension remains trusted executable code. It can still read files or inject
544
+ prompt text. This is not a sandbox.
545
+
546
+ No MCP tool and no tool argument takes a skill. `pi_session_start` and
547
+ `pi_session_prompt` are unchanged.
548
+
549
+ Pi lists each available skill to the model with the absolute path of its
550
+ `SKILL.md`, so that the model can read it. The model may repeat that path in
551
+ its answer. Capabilities, errors and diagnostics stay path-free, but completed
552
+ `assistantText` is model output and is not sanitized.
553
+
554
+ ## Upgrade from 0.8.0
555
+
556
+ ### Skill tree fingerprints
557
+
558
+ A fingerprint identifies a skill tree by content. Version 2, stored as
559
+ `v2:<64 hex>`, is a SHA-256 over a domain and version tag, the file count, and
560
+ for each file in path byte order the length-prefixed UTF-8 path and the
561
+ length-prefixed content. The size and file type limits are unchanged. Version 1
562
+ (0.8.0) hashed `path NUL content NUL` for each file. Content may contain NUL
563
+ bytes, so two different trees could share a version 1 value.
564
+
565
+ A version 1 value never proves identity alone. It counts only when a trusted
566
+ reference has that version 1 value and is byte-identical, by its version 2
567
+ value, to the tree in question. The trusted references are the shipped skills of
568
+ the 0.8.0 release (a fixed table in the code) and, for setup's installed copies,
569
+ the current library tree.
570
+
571
+ ### What to do after the upgrade
572
+
573
+ 1. Run `pi-session-mcp-setup` as a dry-run for your targets, then with
574
+ `--apply`. An unchanged copy of the 0.8.0 controller skill is proven against
575
+ the 0.8.0 table, its ownership record is migrated, and the copy is updated to
576
+ the new shipped skill. A copy that cannot be proven stays untouched, and
577
+ dry-run, apply, remove and rollback report `skill_legacy_unverified` for it.
578
+ Inspect the copy. If it is unchanged setup content that you no longer need,
579
+ delete it yourself and run the operation again: an absent slot clears the
580
+ record. Otherwise keep it.
581
+ 2. For customized skills, Doctor resolves 0.8.0 bases against the 0.8.0 table.
582
+ The next `--apply --customize-skill` for any skill rewrites the bases file as
583
+ version 2.
584
+
585
+ Ownership records of skill copies hold `v2:` values in `fingerprint` and
586
+ `replaces`. Registration records are unchanged.
587
+
588
+ ### Downgrade
589
+
590
+ 0.8.0 rejects the new formats, so it never acts on them. It treats a version 2
591
+ bases file as corrupt (`skill_bases_corrupt`, and the Doctor warning with the
592
+ subject `skill:bases`). It validates the ownership file as a whole and treats a
593
+ skill record with a `v2:` value as invalid, so it rejects the entire file as
594
+ corrupt, registration records included. Before you run 0.8.0 again, repair or
595
+ remove those files.
596
+
597
+ ## Migration: profiles that relied on package skills
598
+
599
+ With Pi SDK 1.1.0, an allowlisted extension package's bundled skills reached
600
+ the session although `noSkills` was set. The same held for prompt
601
+ templates and themes that a package shipped. Now an extension allowlist alone
602
+ makes none of its skills available. Prompt templates and themes that packages
603
+ ship are closed as well, through the SDK's public overrides. They were never
604
+ expanded for MCP prompts. Profiles without extensions are not affected.
605
+
606
+ If a profile used a skill that a package shipped:
607
+
608
+ 1. Find the package's `SKILL.md` inside Pi's install, normally
609
+ `<Pi agent dir>/npm/node_modules/<package>/…`.
610
+ 2. Add a catalog entry with the absolute path of that file. `~` is not
611
+ expanded.
612
+ 3. Select the alias in the profile's `skills`.
613
+ 4. Run Doctor and start a session to confirm.
614
+
615
+ Keep in mind that a package update can move or change the file. The catalog
616
+ path then needs a check.