pi-session-mcp 0.11.9 → 0.12.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 (73) hide show
  1. package/.pi-session-mcp-cli/doctor.js +89 -4
  2. package/.pi-session-mcp-cli/execution-modes.js +16 -4
  3. package/.pi-session-mcp-cli/override-lock.js +4 -4
  4. package/.pi-session-mcp-cli/setup-process.js +5 -4
  5. package/.pi-session-mcp-cli/writer-boundary.js +10 -4
  6. package/.pi-session-mcp-cli/writer-fence.js +50 -0
  7. package/.pi-session-mcp-cli/writer-lease.js +140 -0
  8. package/.pi-session-mcp-cli/writer-lifecycle.js +55 -23
  9. package/.pi-session-mcp-cli/writer-state.js +4 -2
  10. package/CHANGELOG.md +69 -0
  11. package/README.md +3 -2
  12. package/dist/capabilities.d.ts +2 -0
  13. package/dist/capabilities.js +4 -1
  14. package/dist/child-stdio-transport.d.ts +21 -0
  15. package/dist/child-stdio-transport.js +88 -0
  16. package/dist/client-adapters/types.d.ts +1 -0
  17. package/dist/doctor.d.ts +3 -1
  18. package/dist/doctor.js +89 -4
  19. package/dist/execution-modes.d.ts +12 -2
  20. package/dist/execution-modes.js +16 -4
  21. package/dist/main.js +9 -2
  22. package/dist/override-lock.d.ts +2 -0
  23. package/dist/override-lock.js +4 -4
  24. package/dist/pi-adapter.d.ts +30 -0
  25. package/dist/pi-adapter.js +11 -0
  26. package/dist/refusal-server.d.ts +1 -1
  27. package/dist/refusal-server.js +24 -2
  28. package/dist/server.d.ts +2 -2
  29. package/dist/server.js +18 -53
  30. package/dist/session-registry.d.ts +9 -2
  31. package/dist/session-registry.js +17 -12
  32. package/dist/setup-cli.js +7 -2
  33. package/dist/setup-hooks.js +0 -2
  34. package/dist/setup-modes.js +7 -1
  35. package/dist/setup-process.d.ts +3 -1
  36. package/dist/setup-process.js +5 -4
  37. package/dist/setup-writers.d.ts +5 -0
  38. package/dist/setup-writers.js +200 -92
  39. package/dist/tool-schemas.d.ts +339 -0
  40. package/dist/tool-schemas.js +49 -0
  41. package/dist/wake-hook.d.ts +4 -5
  42. package/dist/wake-hook.js +3 -6
  43. package/dist/writer-acceptance.js +10 -1
  44. package/dist/writer-boundary.d.ts +6 -4
  45. package/dist/writer-boundary.js +10 -4
  46. package/dist/writer-fence.d.ts +12 -0
  47. package/dist/writer-fence.js +50 -0
  48. package/dist/writer-launch.d.ts +9 -1
  49. package/dist/writer-launch.js +127 -30
  50. package/dist/writer-lease.d.ts +28 -0
  51. package/dist/writer-lease.js +140 -0
  52. package/dist/writer-lifecycle.d.ts +7 -1
  53. package/dist/writer-lifecycle.js +55 -23
  54. package/dist/writer-relay.d.ts +33 -0
  55. package/dist/writer-relay.js +359 -0
  56. package/dist/writer-state.d.ts +13 -1
  57. package/dist/writer-state.js +4 -2
  58. package/docs/acceptance.md +47 -0
  59. package/docs/adr/0012-leased-writer-pool.md +357 -0
  60. package/docs/architecture.md +61 -13
  61. package/docs/client-setup.md +11 -15
  62. package/docs/doctor-design.md +10 -4
  63. package/docs/execution-modes.md +87 -44
  64. package/docs/installation.md +34 -22
  65. package/docs/isolation-setup.md +88 -102
  66. package/docs/roadmap.md +5 -0
  67. package/docs/threat-model.md +7 -4
  68. package/docs/tool-contracts.md +29 -12
  69. package/package.json +1 -1
  70. package/scripts/isolation-acceptance.mjs +7 -1
  71. package/skills/pi-session-implementation/SKILL.md +1 -0
  72. package/skills/pi-session-orchestration/SKILL.md +6 -5
  73. package/skills/pi-session-orchestration/references/start-errors.md +5 -1
@@ -11,9 +11,11 @@ does not itself prove a live rollout or acceptance on your machine.
11
11
  - **`host`:** coding writes in the operator's checkout; requires stored
12
12
  `acceptHostWrites: true`. Tools, providers and trusted extensions run with
13
13
  the host process's rights, not inside an OS boundary.
14
- - **`container`:** the host refuses coding. Setup provisions separate full clones
15
- and wrapped stdio registrations on Linux with usable rootless Podman. A mode
16
- entry alone is not containment: each writer needs successful acceptance.
14
+ - **`container`:** coding goes through the one host `pi-session-mcp`
15
+ registration, which leases a separate full-clone writer per session on Linux
16
+ with usable rootless Podman ([ADR 0012](adr/0012-leased-writer-pool.md)).
17
+ There are no per-writer registrations or tool sets. A mode entry alone is
18
+ not containment: each writer needs successful acceptance.
17
19
  - **`read-only`:** the host refuses coding; read-only profiles remain available.
18
20
  This is tool policy, not an OS sandbox for provider runtimes or child processes.
19
21
  - **`unset`:** a missing entry retains host coding for backward compatibility.
@@ -47,7 +49,7 @@ dependencies, not a development checkout. Never pass a workspace path.
47
49
  pi-session-mcp-setup --repo my-project --mode container --profiles code
48
50
  # Explicit host control: creates real canary files below $HOME and removes them.
49
51
  pi-session-mcp-setup --isolation-control --repo my-project --apply
50
- # Provisions, accepts and registers the writers.
52
+ # Provisions and accepts the writer pool; no writer registrations.
51
53
  pi-session-mcp-setup --repo my-project --mode container --profiles code --apply
52
54
  ```
53
55
 
@@ -70,7 +72,8 @@ pi-session-mcp-setup --repo my-project --mode container --profiles code,code-loc
70
72
  Repeat the reviewed request with `--apply` to mutate. Mode requests cannot be
71
73
  combined with `--target` registration or `--customize-skill`.
72
74
  A `--repo` dry run for a container alias inspects existing writers using
73
- read-only throwaway containers: no network access and nothing written.
75
+ read-only throwaway containers: no network access and nothing written to the
76
+ clone.
74
77
 
75
78
  | Flag | Accepted values and defaults |
76
79
  | --- | --- |
@@ -82,9 +85,10 @@ read-only throwaway containers: no network access and nothing written.
82
85
  | `--profiles a,b` | Required non-empty coding-profile aliases; no Cursor provider |
83
86
  | `--image <value>` | `node` (default, built-in digest pin) or an existing local 64-character lowercase hex image ID; no arbitrary tags |
84
87
  | `--network <value>` | `pasta` (default, not limited) or `none` |
85
- | `--cache <value>` | `none` (default) or `go` for the writer's Go module/build cache |
88
+ | `--tags <value>` | `none` (default), or `reachable` for the tags reachable from the selected base commit |
89
+ | `--cache <value>` | `none` (default), `go` for the writer's Go module/build cache, or `uv` for its private uv and npm cache |
86
90
  | `--base <ref>` | Named local or remote branch for provisioning; also supported by writer reset; not HEAD or a raw commit hash |
87
- | `--client codex` | Also register writers with Codex (user scope); Claude Code local registration is the default |
91
+ | `--client codex` | No writer effect; coding uses the host registration for either client |
88
92
  | `--discard` | Explicitly allow loss of uncollected surplus writers on mode/count changes |
89
93
  | `--dry-run` / `--apply` | Dry-run is the default; apply mutates |
90
94
  | `--json` | Sanitized JSON projection of the same report |
@@ -106,16 +110,25 @@ other workspace entries. The main configuration's `workspaces` stays an
106
110
  alias-to-path map. Older servers ignore modes; upgrade all relevant registrations.
107
111
  Invalid modes fail startup rather than silently falling back.
108
112
 
109
- Writers are `<alias>-w1` through `<alias>-w16`, with registrations
110
- `pi-coding-<alias>-w<n>`. Their private state lives under
113
+ Writers are `<alias>-w1` through `<alias>-w16`, leased by the host server,
114
+ not selected by tool prefix. Their private state lives under
111
115
  `$HOME/.local/state/pi-session-mcp/writers/<name>/`: `clone`, `agent`, `config`,
112
116
  `bundles`, optional `go`, `wrapper.sh` and `state.json`. This lifecycle metadata
113
117
  is distinct from in-memory MCP sessions; it does not implement session resume.
114
118
  Each clone is made via a bundle, with no hardlinks to the operator's repository.
115
- The base defaults to origin's symbolic HEAD, falling back to local main.
116
-
117
- The generated wrapper launches one disposable `podman run -i --rm` per client
118
- connection: keep-id user namespace, read-only root, dropped capabilities,
119
+ The workspace records a short default `base` in its container entry, selected
120
+ by `--base` or origin's symbolic HEAD, falling back to local main. Repeating a
121
+ mode request without `--base` preserves that recorded value. Migration infers
122
+ a shared writer base or fails with `workspace_base_ambiguous` unless given one.
123
+ Capabilities report configured `writers` and `defaultBase`, not free capacity.
124
+
125
+ Start with an explicit coding profile on `pi-session-mcp`. Optional `base` is
126
+ only for container coding and selects a branch, not a writer; elsewhere it
127
+ fails with `base_not_applicable`. Start and get report `writer` and
128
+ `base: { ref, commit }`. Omit `base` for ordinary default-pool work.
129
+
130
+ The generated wrapper launches one disposable `podman run -i --rm` per coding
131
+ session: keep-id user namespace, read-only root, dropped capabilities,
119
132
  no-new-privileges, tmpfs home and /tmp. The clone and isolated agent copy are
120
133
  writable; clone hooks/config, boundary config and release installation are
121
134
  read-only mounts. Host Git never runs in the clone. Throwaway lifecycle
@@ -125,7 +138,7 @@ Each contained config has only its workspace and selected coding profiles,
125
138
  plus the synthetic default **`boundary-read-only`**. Coding sessions must name
126
139
  their coding profile explicitly. The clone's internal mode is acknowledged host
127
140
  because writes there are intended *inside* the boundary; the operator's host
128
- alias stays container and never dispatches coding automatically.
141
+ alias stays container and routes coding through the relay, never host tools.
129
142
 
130
143
  State phases are provisioning, not_ready and ready. Acceptance binds wrapper
131
144
  bytes, package version, image ID, Podman version, network and cache settings.
@@ -137,9 +150,9 @@ silently replaced. Review
137
150
  and collect work before removing/reprovisioning them. Restart/reconnect affected
138
151
  controllers after successful changes; setup never restarts them.
139
152
 
140
- **Inspection has side effects:** `claude mcp get` health-checks the registration,
141
- briefly starting its container. Setup's registration inspect and verify do this
142
- too. Do not mistake inspection for a purely offline read. Doctor alone is offline:
153
+ **Inspection has side effects:** `claude mcp get` health-checks the host
154
+ registration, starting the host server but no writer container. Writer verify
155
+ explicitly spawns the fenced relay launch path. Doctor alone is offline:
143
156
  it starts no writer, session or provider request and reads no credentials.
144
157
 
145
158
  The launcher copies only selected providers from the host Pi auth store at launch
@@ -172,7 +185,7 @@ new work invalidates that authorization. Stashes are not exported as branches:
172
185
  resolve any needed stash inside the boundary before collection.
173
186
 
174
187
  `--finish` collects, then resets a persistent writer or recreates an ephemeral
175
- writer. A collect refusing any branch name is incomplete (`writer_collect_incomplete`,
188
+ writer, on the workspace default base. A collect refusing any branch name is incomplete (`writer_collect_incomplete`,
176
189
  `ref_name_refused`), even if other refs were imported. Finish stops without reset
177
190
  or recreation. Resolve by renaming inside the writer and collecting again, or
178
191
  by an operator's explicit `--reset --discard --apply` after deciding to lose the
@@ -182,28 +195,33 @@ unexported work. Do not blindly retry or bypass a refusal by changing profiles.
182
195
  | --- | --- |
183
196
  | `--collect --apply` | Bundle export; retain clone |
184
197
  | `--reset --apply [--base <ref>]` | Reset to current source base branch, delete extra branches/stash and clean untracked files; falls back to recreation if post-reset verification fails |
185
- | `--recreate --apply` | Rebuild clone and agent copy from the source base, retaining image/settings |
186
- | `--finish --apply` | Collect, then reset persistent / recreate ephemeral |
198
+ | `--recreate --apply` | Rebuild clone and agent copy from the workspace default base, retaining image/settings |
199
+ | `--finish --apply` | Collect, then reset persistent / recreate ephemeral on the workspace default base |
187
200
  | `--remove` | Explicit mutation: unregister owned registrations before removing writer state |
188
- | `--verify` | Doctor, registration equality and bounded direct MCP handshake/tools/list/capabilities; no session or provider request |
201
+ | `--verify` | Doctor and bounded relay-path MCP handshake/tools/list/capabilities; verify the host registration with client-target verify too; no session or provider request |
189
202
 
190
203
  Other than explicit `--remove` and `--verify`, omitting apply only plans.
191
204
  Reset/recreate/remove refuse changed uncollected work unless given `--discard`.
192
- `--reset`, `--recreate` or `--remove` with `--discard` cannot see open sessions
193
- in the writer; close those sessions first.
194
- Only reset accepts `--base`; finish does not accept discard.
195
-
196
- **Verify and registration health checks:** setup's registration inspect in apply,
197
- verify and remove uses `claude mcp get`, which health-checks a registered server
198
- through its normal writer launcher. That start may re-copy the boundary
199
- credentials. Only if a selected OAuth token expires within **10 minutes** (or is
200
- already expired; invalid/missing expiry is treated as needing refresh) may it
201
- refresh the host credential through **Pi's own locked store**. The client CLI cannot
202
- be given a different environment for that health check. Verify's own direct MCP
203
- handshake uses `PI_WRITER_NO_CREDENTIAL_REFRESH=1`, touching no credential in
204
- that launch and making no session or provider request. **Setup itself never
205
- writes the host credential file**; host login updates, when needed, are performed
206
- by Pi through its locked store.
205
+ Every mutating writer operation holds the host-only lease and container fence,
206
+ including collect, acceptance and reconciliation. A held session (even idle)
207
+ or launcher cleanup makes setup fail with `writer_busy` after a 10-second wait,
208
+ also with `--discard`; close sessions first. Only reset accepts `--base`;
209
+ finish does not accept discard. No reconnect is needed after reset, finish or
210
+ recreate.
211
+
212
+ For a fix round, create a local branch at the collected ref in the operator's
213
+ checkout, run `--writer <name> --reset --base <branch> --apply`, then call
214
+ `pi_session_start` with the coding profile and `base: "<branch>"`. Read `writer`
215
+ from the result for collection; default starts cannot take a writer on that
216
+ other branch. `writer_unavailable` is a capacity/state signal, not a reason to
217
+ switch profiles; see [Tool contracts](tool-contracts.md).
218
+
219
+ **Verify:** the direct relay-path MCP handshake uses
220
+ `PI_WRITER_NO_CREDENTIAL_REFRESH=1`, touching no credential in that launch and
221
+ making no session or provider request. Host registration equality is checked
222
+ by client-target verify; no writer registration is required. **Setup itself
223
+ never writes the host credential file**; host login updates, when needed, are
224
+ performed by Pi through its locked store.
207
225
 
208
226
  The server guard refuses coding when the clone differs from its recorded baseline
209
227
  (extra branches, stash, uncommitted/untracked changes, or different HEAD), and
@@ -245,7 +263,7 @@ listed below; dynamic planned and MCP verification outcomes are listed explicitl
245
263
  | `writer_provisioned` | Clone/config/wrapper prepared, not yet accepted |
246
264
  | `writer_unchanged` | Provisioned settings already match |
247
265
  | `writer_settings_changed` | Existing writer settings or its rendered configuration differ from the current ones; no silent replacement, no acceptance and no registration change ([Isolation setup](isolation-setup.md)) |
248
- | `writer_registered` | Registration added or already equivalent |
266
+ | `registration_divergent` | Writer registration is foreign or differs from its ownership record; it is retained and the writer stays in legacy launch mode |
249
267
  | `writer_reconcile_failed` | Unexpected reconciliation failure |
250
268
  | `writer_operation_failed` | Unclassified lifecycle/orchestrator failure |
251
269
  | `hook_install_failed` | The wake hook step of a writer failed unexpectedly; other `hook_*` and `settings_*` codes are in [Client setup](client-setup.md#wake-hook) |
@@ -269,10 +287,12 @@ listed below; dynamic planned and MCP verification outcomes are listed explicitl
269
287
  | `writer_reset` | Persistent clone reset and verified |
270
288
  | `writer_recreate_failed` | Recreated clone is not at baseline |
271
289
  | `writer_recreated` | Clone/agent recreated and verified |
272
- | `writer_reconnect_required` | Writer credentials were cleared; reconnect the writer's MCP server before the next coding start |
290
+ | `writer_busy` | Another process holds the writer's lease (a relayed session, a launcher still cleaning up, or another setup run) for longer than the setup wait; retry after it ends |
291
+ | `writer_fenced` | The writer's container fence could not confirm that no container of this writer exists; nothing was changed |
292
+ | `writer_lease_unavailable` | The writer's lease file or directory could not be opened safely (see Doctor `writer_lease_directory`) |
273
293
  | `writer_removed` | Writer state deleted after registration cleanup |
274
294
  | `ownership_unavailable` | Existing registration lacks ownership proof; removal refused |
275
- | `registration_not_equivalent` | Verify found absent/divergent/unsupported required registration |
295
+ | `workspace_base_ambiguous` | Existing writers have different base branches; supply an explicit base before migration can change the workspace modes |
276
296
  | `isolation_control_planned` | Control planned only; no canaries written |
277
297
  | `isolation_control_required` | Passing host control record required before writer acceptance |
278
298
  | `isolation_control_passed` | Uncontained control detected expected escapes and cleaned up |
@@ -312,22 +332,23 @@ The writer baseline guard is the deliberate re-read exception, not general reloa
312
332
 
313
333
  ## Migration from manual writers
314
334
 
315
- For **`psm-alpha`** and **`go-app-alpha`**, collect/review all work first through
335
+ For hand-made writers, collect/review all work first through
316
336
  the old boundary's bundle route. Automated setup does not adopt their manual
317
337
  state, wrappers or names.
318
338
 
319
339
  1. In each writer's original project remove its manual local registration:
320
- `claude mcp remove pi-coding-<name> -s local` (substitute `psm-alpha` or
321
- `go-app-alpha`). Remove any separately configured Codex registration too.
340
+ `claude mcp remove pi-coding-<name> -s local`. Remove any separately
341
+ configured Codex registration too.
322
342
  2. After preserving work, remove that manual wrapper and state directory.
323
343
  Do not delete the operator's source checkout or another writer's files.
324
344
  3. Configure the desired coding profiles, pass the explicit host isolation
325
345
  control above, then run
326
346
  `pi-session-mcp-setup --repo <alias> --mode container --profiles <coding-aliases> --apply`.
327
347
  New names derive from the configured repository alias, not the old manual name.
328
- 4. Restart/reconnect controllers and verify the new writer registrations before use.
348
+ 4. Restart/reconnect the host `pi-session-mcp` server, verify its client targets
349
+ and run `--writer <name> --verify` before use; no writer registrations are added.
329
350
 
330
- For the Go-toolchain writer **`go-app-alpha`**, supply an **operator-supplied
351
+ For a Go-toolchain writer, supply an **operator-supplied
331
352
  local image ID** containing Node, Git and the Go toolchain, plus `--cache go`:
332
353
 
333
354
  ```sh
@@ -341,5 +362,27 @@ Container options are rebuilt from supplied values and defaults each time:
341
362
  repeat the image ID and `--cache go` on later mode requests, or cache defaults
342
363
  to `none`. Setup does not build or install the Go toolchain for you.
343
364
 
365
+ A Python writer using uv and npm uses the same pattern with `--cache uv`:
366
+
367
+ ```sh
368
+ pi-session-mcp-setup --repo <alias> --mode container --profiles <coding-aliases> \
369
+ --image <image-id> --cache uv --apply
370
+ ```
371
+
372
+ This records `cache: "uv"` and mounts a private per-writer directory at
373
+ `/pi/uv`, with `UV_CACHE_DIR=/pi/uv/cache` and `npm_config_cache=/pi/uv/npm`.
374
+ No other uv or npm variables are set, so download and network policy stay the
375
+ image's. The directory is per writer: it is never shared between writers and
376
+ never a host cache mount. Setup does not install uv or npm for you.
377
+
378
+ ### Base tags (opt-in)
379
+
380
+ Container writers default to `tags: "none"`. Pass `--tags reachable` to bundle
381
+ only operator-checkout tags merged into the selected base commit. Repeat this
382
+ flag on later mode requests to retain it. Reset deletes every clone tag before
383
+ importing the selected base tags; tags created by workers are never collected
384
+ and do not change the collected-work digest. Changing the tag setting requires
385
+ reprovisioning for settings drift, but does not change boundary acceptance.
386
+
344
387
  See [Isolation setup](isolation-setup.md) for boundary prerequisites and what
345
388
  setup automates, and the historical manual acceptance evidence linked there.
@@ -65,8 +65,9 @@ the same arguments.
65
65
  [client setup reference](client-setup.md), works independently of `setup`.
66
66
  - For real prompts: local Pi authentication and a provider/model pair that Pi
67
67
  knows. Doctor is offline. Container apply may pull the built-in pinned image
68
- and runs the acceptance network probes; writer registration health checks can
69
- start its launcher and copy/refresh selected credentials. Direct writer verify
68
+ and runs the acceptance network probes; coding session launchers can
69
+ copy/refresh selected credentials. Host registration health checks do not
70
+ start writer containers. Direct writer verify
70
71
  disables credential refresh and uses no session or provider request.
71
72
 
72
73
  Pi Session MCP embeds the Pi SDK version pinned in `package.json`. It reuses the
@@ -207,12 +208,12 @@ coding-profile aliases (`--profiles a,b`); without it, setup reports
207
208
  [Execution modes](execution-modes.md#choose-at-installation) for explicit
208
209
  host-write acknowledgment and all options. With usable rootless Podman, setup
209
210
  proposes container; otherwise it requires an explicit host or read-only choice.
210
- On Linux, container apply provisions and accepts separate full-clone writers,
211
- then registers their wrappers. First explicitly authorize the host isolation
211
+ On Linux, container apply provisions and accepts separate full-clone writers
212
+ for the host registration's leased pool; it adds no writer registrations. First explicitly authorize the host isolation
212
213
  control (`--isolation-control --repo <alias> --apply`): it writes real canaries
213
214
  below $HOME and removes them. Without a passing control writers remain not ready.
214
215
  See [Isolation setup](isolation-setup.md) for prerequisites, residual credential
215
- and network risks, and migration of `psm-alpha` and `go-app-alpha`.
216
+ and network risks, and migration of hand-made writers.
216
217
  Missing entries retain host behavior as `unset` for compatibility and warn in
217
218
  Doctor when a coding profile exists.
218
219
 
@@ -395,8 +396,8 @@ not a token or cost saving, and none is measured on short tasks. Design:
395
396
  `pi_capabilities_get` and stops polling only when it is `spool`.
396
397
 
397
398
  **What setup writes.** One entry, with `matcher` for
398
- `pi_session_prompt` and `pi_turn_get` of `pi-session-mcp` and of the
399
- `pi-coding-*` writer registrations, and one command hook:
399
+ `pi_session_prompt` and `pi_turn_get` of `pi-session-mcp` only
400
+ (`mcp__pi-session-mcp__*`), and one command hook:
400
401
  `<node> <package>/dist/wake-main.js --budget-ms 1800000`, `asyncRewake: true`
401
402
  and `timeout` 1860 seconds (the 30-minute wait budget plus 60 seconds, so the
402
403
  client never ends the hook before its own budget). The entry is compared by
@@ -405,17 +406,16 @@ and keeps its permissions; every other setting stays as it was. Verify checks
405
406
  the file and that the program exists; it does not test that Claude Code honors
406
407
  the hook.
407
408
 
408
- The hook reads only the spool of the server that armed it: the host's
409
- `$XDG_RUNTIME_DIR/pi-session-mcp/events/inst-*` for `pi-session-mcp`, and a
410
- writer's own `~/.local/state/pi-session-mcp/writers/<name>-wN/events/inst-*` for
411
- `pi-coding-<name>-wN`. Its claims stay in `$XDG_RUNTIME_DIR/pi-session-mcp/claims`.
409
+ The hook reads only the host spool
410
+ `$XDG_RUNTIME_DIR/pi-session-mcp/events/inst-*`, including relayed container
411
+ turns. Its claims stay in `$XDG_RUNTIME_DIR/pi-session-mcp/claims`. There are no
412
+ writer hook entries or per-session writer spool mounts.
412
413
 
413
414
  **Change and remove.** With delivery switched off, `--apply` removes an entry
414
415
  that setup owns (`hook_removed`); `--remove` and `--rollback` remove it with the
415
416
  registration. Setup never replaces or deletes an entry it did not write or that
416
- you edited (`hook_divergent`). The local-scope entry of a project is shared by
417
- the host registration and the writer registrations there and stays
418
- (`hook_kept_shared`) until the last of them that needs it is removed. With
417
+ you edited (`hook_divergent`). During migration an owned shared legacy entry may stay
418
+ (`hook_kept_shared`) while another owned registration still needs it. With
419
419
  delivery off and no entry of its own, setup does not read the settings file. The upgrade procedure below removes the entry with the
420
420
  old registration and adds it again with the new one, because it names the
421
421
  package path. The settings-file codes are in the
@@ -446,8 +446,8 @@ package path. The settings-file codes are in the
446
446
  Whether a running extension or TUI session does the same is not checked, so
447
447
  step 3 above stays the rule
448
448
  ([Sprint 7 evidence](evaluation/s7-live-acceptance-2026-10-24/README.md)).
449
- - Container writers need their own spool mount; see
450
- [Isolation setup](isolation-setup.md#event-spool-mount).
449
+ - Container turns are relayed into the host spool, with no writer spool mount;
450
+ see [Isolation setup](isolation-setup.md#event-spool-mount).
451
451
 
452
452
  ### Register without setup
453
453
 
@@ -728,13 +728,24 @@ Writer acceptance binds the package version, so every container writer is
728
728
  targets until they are accepted again. With container writers, run the
729
729
  repository step between the install and the client targets:
730
730
 
731
- 1. Install the new package (`npm install -g` or the release checkout above).
732
- 2. For each repository in mode `container`, repeat its provisioning command
731
+ 1. Close all coding sessions and controllers first. This quiescence barrier
732
+ ensures no old-release launcher is still in flight. Install the new package
733
+ (`npm install -g` or the release checkout above).
734
+ 2. For each repository in mode `container`, repeat its existing provisioning command
733
735
  with `--apply`, for example
734
736
  `pi-session-mcp-setup --repo <alias> --mode container ... --apply`. It
735
- accepts every writer again whose acceptance is not current and keeps its
736
- clone and the work in it. Close the coding sessions on those writers
737
- first: acceptance briefly places canaries in the clone. Use the same
737
+ migrates writers under the lease and container fence: removes owned writer
738
+ registrations and hooks, rewrites stable wrappers for the new release,
739
+ marks writers `launch: "relay"` and re-accepts them provider-free before
740
+ admitting them to the pool. It keeps the clone and work in it; acceptance
741
+ briefly places canaries in the clone. Migration records the shared default
742
+ base; differing writer bases fail with `workspace_base_ambiguous` unless
743
+ you supply an explicit `--base <branch>`. A divergent registration is never
744
+ deleted or replaced: it reports `registration_divergent` and keeps that
745
+ writer legacy, outside the pool. Resolve divergence before use; do not
746
+ launch through a retained legacy registration. `--client codex` has no writer effect any
747
+ more; both clients code through the one host `pi-session-mcp` registration.
748
+ Use the same
738
749
  settings as before: a writer whose settings or coding profiles changed is
739
750
  reported as `writer_settings_changed` and left as it is, and one whose
740
751
  coding profile selects an overridden or unshipped `builtin:` skill as
@@ -743,7 +754,8 @@ repository step between the install and the client targets:
743
754
  `--writer <name> --remove` it and provision it again
744
755
  ([Isolation setup](isolation-setup.md)).
745
756
  3. `--apply` and `--verify` the client targets, then `--writer <name> --verify`
746
- for each writer.
757
+ for each writer. Reconnect the host `pi-session-mcp` server after migration
758
+ and client setup. Later reset, finish and recreate need no reconnect.
747
759
 
748
760
  The repository step does not count another repository's writers that are not
749
761
  ready yet, so the repositories can be accepted one after the other. Releases