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.
- package/.pi-session-mcp-cli/config.js +47 -7
- package/.pi-session-mcp-cli/doctor.js +75 -2
- package/.pi-session-mcp-cli/execution-profile.js +1 -0
- package/.pi-session-mcp-cli/override-lock.js +125 -0
- package/.pi-session-mcp-cli/setup-platform.js +195 -0
- package/.pi-session-mcp-cli/setup-result.js +8 -0
- package/.pi-session-mcp-cli/skill-catalog.js +35 -0
- package/.pi-session-mcp-cli/skill-customize.js +268 -0
- package/.pi-session-mcp-cli/skill-library.js +188 -0
- package/CHANGELOG.md +176 -0
- package/README.md +15 -3
- package/dist/capabilities.d.ts +1 -0
- package/dist/capabilities.js +2 -1
- package/dist/client-adapters/mcp-verifier.js +6 -2
- package/dist/config.d.ts +4 -0
- package/dist/config.js +47 -7
- package/dist/doctor.d.ts +7 -1
- package/dist/doctor.js +75 -2
- package/dist/execution-profile.d.ts +4 -0
- package/dist/execution-profile.js +1 -0
- package/dist/override-lock.d.ts +34 -0
- package/dist/override-lock.js +125 -0
- package/dist/pi-adapter.d.ts +7 -5
- package/dist/pi-adapter.js +1 -0
- package/dist/pi-skills.d.ts +17 -0
- package/dist/pi-skills.js +64 -0
- package/dist/sdk-pi-adapter.d.ts +7 -3
- package/dist/sdk-pi-adapter.js +82 -16
- package/dist/server.js +1 -0
- package/dist/session-registry.d.ts +8 -6
- package/dist/session-registry.js +3 -2
- package/dist/setup-cli.d.ts +2 -0
- package/dist/setup-cli.js +16 -0
- package/dist/setup-main.d.ts +1 -1
- package/dist/setup-main.js +18 -3
- package/dist/setup-ownership.d.ts +9 -0
- package/dist/setup-ownership.js +32 -5
- package/dist/setup-skills.d.ts +76 -0
- package/dist/setup-skills.js +586 -0
- package/dist/setup.d.ts +7 -0
- package/dist/setup.js +49 -6
- package/dist/skill-catalog.d.ts +26 -0
- package/dist/skill-catalog.js +35 -0
- package/dist/skill-customize.d.ts +41 -0
- package/dist/skill-customize.js +268 -0
- package/dist/skill-library.d.ts +106 -0
- package/dist/skill-library.js +188 -0
- package/docs/acceptance.md +593 -1
- package/docs/adr/0006-pi-extension-allowlist.md +3 -0
- package/docs/adr/0007-profile-skill-selection.md +228 -0
- package/docs/architecture.md +10 -6
- package/docs/client-setup-design.md +219 -1
- package/docs/client-setup.md +86 -3
- package/docs/doctor-design.md +9 -1
- package/docs/installation.md +128 -5
- package/docs/pi-extensions-design.md +28 -4
- package/docs/releasing.md +18 -9
- package/docs/session-mcp.md +7 -2
- package/docs/skills.md +616 -0
- package/docs/threat-model.md +19 -2
- package/docs/tool-contracts.md +9 -5
- package/package.json +2 -1
- package/pi-session-mcp.example.json +9 -3
- package/skills/pi-session-implementation/SKILL.md +42 -0
- package/skills/pi-session-orchestration/SKILL.md +94 -0
- package/skills/pi-session-review/SKILL.md +58 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# ADR 0007: Execution profiles select Pi skills from an operator catalog
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted (2026-10-12). Issue #46. Guide: [Pi skills in execution
|
|
4
|
+
profiles](../skills.md).
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Embedded sessions are meant to run without skills. A controlling agent that
|
|
9
|
+
delegates work to Pi workers needs a way to give a worker role-specific
|
|
10
|
+
instructions, such as an implementation or review procedure, under the same
|
|
11
|
+
rule as everything else: the operator decides, the MCP caller picks aliases.
|
|
12
|
+
|
|
13
|
+
Verified behavior of Pi SDK 1.1.0:
|
|
14
|
+
|
|
15
|
+
- `noSkills` does not stop skills that an allowlisted extension package
|
|
16
|
+
ships. Those skills reached the session although `noSkills` was set. The
|
|
17
|
+
same held for prompt templates and themes that packages ship. ADR 0006 did
|
|
18
|
+
not intend this and did not decide it.
|
|
19
|
+
- `loadSkills` removes duplicate skill names before the SDK's `skillsOverride`
|
|
20
|
+
runs. An override alone therefore cannot report a collision between two
|
|
21
|
+
selected files, so the server has to check names itself.
|
|
22
|
+
- Resources that extension hooks add later (`resources_discover`) pass through
|
|
23
|
+
the same override.
|
|
24
|
+
|
|
25
|
+
ADR 0003 chose maintenance without a new feature phase and reserves public
|
|
26
|
+
contract changes for a minor release with their own decision. ADR 0006 is the
|
|
27
|
+
precedent for an owner-approved scope expansion. This ADR is such a decision
|
|
28
|
+
for issue #46. It does not rewrite ADR 0006; that ADR records only a forward
|
|
29
|
+
link.
|
|
30
|
+
|
|
31
|
+
On 2026-10-12 the owner rejected installing the shipped skills by manual
|
|
32
|
+
symlinking and asked for skills that can be installed at install time and
|
|
33
|
+
adjusted and extended one by one. The decisions under "Skill library,
|
|
34
|
+
installation and customization" below record that, in the same change.
|
|
35
|
+
|
|
36
|
+
## Decision
|
|
37
|
+
|
|
38
|
+
The operator may register skills in a catalog and select them per execution
|
|
39
|
+
profile.
|
|
40
|
+
|
|
41
|
+
- **Catalog.** An optional top-level `skillCatalog` maps a skill alias
|
|
42
|
+
(`^[a-z][a-z0-9_-]{0,63}$`, at most 64 entries) to the path of exactly one
|
|
43
|
+
`SKILL.md` (at most 4096 characters). Directories, globs, URLs,
|
|
44
|
+
scheme-prefixed sources, `~` and backslashes are invalid. The one accepted
|
|
45
|
+
scheme is `builtin:<name>` (see below). Relative paths resolve against the
|
|
46
|
+
configuration file's directory. A missing catalog is empty. A skill folder
|
|
47
|
+
needs no `package.json` and no extension.
|
|
48
|
+
- **Selection.** An optional `executionProfiles.<alias>.skills` lists catalog
|
|
49
|
+
aliases (at most 16, no duplicates). Missing or empty means no skills. There
|
|
50
|
+
is no inheritance and no default skill. An unknown alias, a duplicate alias,
|
|
51
|
+
or two selected aliases that resolve to the same path is
|
|
52
|
+
`invalid_execution_profile_config`. All other profile fields are unchanged
|
|
53
|
+
and unknown fields are still rejected.
|
|
54
|
+
- **MCP surface.** Callers still choose only workspace and profile aliases.
|
|
55
|
+
`pi_session_start` and `pi_session_prompt` get no skill input. No tool is
|
|
56
|
+
added.
|
|
57
|
+
- **Loading.** At session start the server loads exactly the selected files
|
|
58
|
+
through the SDK's public `loadSkills`, with no default directories, and
|
|
59
|
+
validates them completely. `skillsOverride` returns exactly that set, also
|
|
60
|
+
after later resource additions by extension hooks.
|
|
61
|
+
- **Failure.** A missing, unreadable or oversized (over 256 KiB) file, an
|
|
62
|
+
invalid skill (SDK diagnostics such as a missing description or invalid
|
|
63
|
+
name), two selected files with the same Pi skill name, two aliases on the
|
|
64
|
+
same canonical file (for example through a symlink), or
|
|
65
|
+
`disable-model-invocation: true` fail the start with the new error
|
|
66
|
+
`skill_load_failed` and the fixed message `Configured Pi skill could not be
|
|
67
|
+
loaded`. The message never contains a path, a skill name or SDK text. The
|
|
68
|
+
same Pi skill name in different sessions is fine.
|
|
69
|
+
- **Explicit-only skills are incompatible in v1.** MCP prompts never run
|
|
70
|
+
`/skill:` commands, and Pi hides such skills from the model. The server does
|
|
71
|
+
not rewrite the flag.
|
|
72
|
+
- **Names.** The catalog alias may differ from the skill's declared `name`.
|
|
73
|
+
Nothing is renamed.
|
|
74
|
+
- **Capabilities.** Every execution profile in `pi_capabilities_get` gains
|
|
75
|
+
`skills: string[]`, the selected catalog aliases in configured order, `[]`
|
|
76
|
+
when none. No paths, file names, contents or SDK diagnostics. The setup
|
|
77
|
+
verifier accepts the field and still rejects unexpected fields.
|
|
78
|
+
- **Doctor.** Offline, it never parses or executes a skill. New check IDs after
|
|
79
|
+
the extension checks, with the subject `<profile>:<skill-alias>`:
|
|
80
|
+
`skill_missing`, `skill_unreadable` (not a regular file, over 256 KiB, or not
|
|
81
|
+
readable) and `skill_duplicate`. Frontmatter, name rules, collisions and
|
|
82
|
+
explicit-only flags are checked only at session start. A further warning
|
|
83
|
+
check, `skill_override_outdated`, is described below.
|
|
84
|
+
- **Diagnostics.** Stderr gets the new stage `skill_load` for a selected file
|
|
85
|
+
that fails validation, with an optional `skill` field holding the catalog
|
|
86
|
+
alias. A catalog that differs from the selection after construction reports
|
|
87
|
+
the existing stages `postcheck`, `extension_load` or `extension_bind`. No
|
|
88
|
+
diagnostic carries a path.
|
|
89
|
+
- **Snapshot semantics.** Profile and skill selection are fixed per session.
|
|
90
|
+
The content is not snapshotted: the SDK keeps file paths and later reads may
|
|
91
|
+
see changed files. Operators manage skill files and companion files, do not
|
|
92
|
+
change them during active sessions, and close affected sessions before an
|
|
93
|
+
update. The configuration fingerprint covers configuration bytes, not skill
|
|
94
|
+
contents. There is no hot reload, watcher, copy store for sessions or
|
|
95
|
+
versioning.
|
|
96
|
+
- **Shipped skills.** The package ships `pi-session-orchestration`,
|
|
97
|
+
`pi-session-implementation` and `pi-session-review` under `skills/`, one
|
|
98
|
+
canonical copy each. They are ordinary skill files. The two worker skills are
|
|
99
|
+
registered through the catalog like any operator skill, by `builtin:`. The
|
|
100
|
+
orchestration skill is for the controlling client agent and is not a Pi
|
|
101
|
+
worker skill.
|
|
102
|
+
|
|
103
|
+
## Skill library, installation and customization
|
|
104
|
+
|
|
105
|
+
Owner decision of 2026-10-12, same issue. The first design of this change had
|
|
106
|
+
the operator register shipped skills by absolute path and link the
|
|
107
|
+
orchestration skill into the clients by hand; the owner rejected that. Three
|
|
108
|
+
decisions were taken:
|
|
109
|
+
installation through `pi-session-mcp-setup --apply`, an override directory for
|
|
110
|
+
customization, and `builtin:` references in the Pi skill catalog.
|
|
111
|
+
|
|
112
|
+
- **Skill library.** Shipped skills live in the package's `skills/` directory.
|
|
113
|
+
The operator's override directory is `skills/` next to the configuration file
|
|
114
|
+
(for the default configuration `~/.config/pi-session-mcp/skills/<name>/SKILL.md`).
|
|
115
|
+
An override with the same name replaces the shipped skill; a new name adds a
|
|
116
|
+
skill. Names follow the Agent Skills rule: lowercase letters and digits in
|
|
117
|
+
hyphen-separated groups, 1 to 64 characters. Nothing else is searched.
|
|
118
|
+
- **`builtin:<name>`.** A `skillCatalog` value resolves to the override if
|
|
119
|
+
`<override dir>/<name>/SKILL.md` exists, else to the shipped skill. A missing
|
|
120
|
+
skill keeps the shipped location, so Doctor reports `skill_missing` and the
|
|
121
|
+
session start fails with `skill_load_failed`. Resolution happens when the
|
|
122
|
+
server loads the configuration: adding or removing an override needs a
|
|
123
|
+
restart, like any configuration change. Edits to an existing skill file reach
|
|
124
|
+
new sessions under the snapshot semantics above. The example configuration
|
|
125
|
+
uses `builtin:pi-session-implementation` and `builtin:pi-session-review`, so a
|
|
126
|
+
copied example needs no path edits.
|
|
127
|
+
- **Customization.** `pi-session-mcp-setup --customize-skill <name>` (a
|
|
128
|
+
dry-run) and `--apply --customize-skill <name>` copy a shipped skill into the
|
|
129
|
+
override directory as a starting point and record the shipped fingerprint in
|
|
130
|
+
`<override dir>/.pi-session-mcp-bases.json`. An existing override is never
|
|
131
|
+
overwritten. When the shipped skill changed after the copy, or no base is
|
|
132
|
+
recorded, the command reports `skill_override_outdated`; `--apply` then moves
|
|
133
|
+
only the recorded base (`skill_base_updated`) and never touches the
|
|
134
|
+
operator's files, which is how an operator acknowledges a reviewed change.
|
|
135
|
+
The finding target is `{ client: "skills", scope: "override", alias: <name> }`.
|
|
136
|
+
It works without `--target`, requires `PI_SESSION_MCP_CONFIG`, and follows
|
|
137
|
+
the platform rule of setup. A completely new skill needs no command: create
|
|
138
|
+
`<override dir>/<new-name>/SKILL.md` and reference it as `builtin:<new-name>`.
|
|
139
|
+
- **Installation of the controller skill.** `setup --apply` installs
|
|
140
|
+
`pi-session-orchestration` for each target, after the registration, as a real
|
|
141
|
+
copy of the effective skill (override, else shipped). Codex `codex:user` goes
|
|
142
|
+
to `~/.agents/skills/`, Claude Code `user` and `local` share
|
|
143
|
+
`~/.claude/skills/`, and Claude Code `project` goes to
|
|
144
|
+
`<project>/.claude/skills/`, the directory setup runs in. Ownership records
|
|
145
|
+
hold only a content fingerprint and, while an update is pending, the
|
|
146
|
+
fingerprint it replaces; a project record is bound to an opaque project
|
|
147
|
+
identity, never a path. Setup never replaces or deletes a modified installed
|
|
148
|
+
copy, hidden operator entries inside it, a foreign skill of the same name or a
|
|
149
|
+
linked or non-directory entry; it reports `skill_divergent`, which does not
|
|
150
|
+
block the registration. A link to exactly the effective skill is the
|
|
151
|
+
operator's own equivalent setup (`skill_linked`) and is never owned. In the home directory the client's skills
|
|
152
|
+
directory may be the operator's own link and is followed. Inside a project,
|
|
153
|
+
which may be untrusted, any link below the project root makes the slot
|
|
154
|
+
divergent, so setup never writes outside the project. Setup installs only
|
|
155
|
+
`pi-session-orchestration` into clients; the worker skills are for Pi
|
|
156
|
+
profiles through `builtin:`, and controller skills of the operator's own
|
|
157
|
+
belong in the clients' native skill directories. The operator customizes the
|
|
158
|
+
controller skill through the override and runs `--apply` again, and never
|
|
159
|
+
edits the installed copy.
|
|
160
|
+
- **Doctor.** The warning check `skill_override_outdated` has the subject
|
|
161
|
+
`skill:<name>` when a shipped skill's current fingerprint differs from the
|
|
162
|
+
recorded base, with a remediation that names the customize command. A
|
|
163
|
+
corrupt bases file reports the subject `skill:bases`. Doctor stays offline
|
|
164
|
+
and executes nothing.
|
|
165
|
+
- **Fallback.** Where setup does not run, the operator copies the controller
|
|
166
|
+
skill directory into the client's documented skill directory. There is no
|
|
167
|
+
symlink instruction.
|
|
168
|
+
|
|
169
|
+
## Compatibility change
|
|
170
|
+
|
|
171
|
+
Previously, with Pi SDK 1.1.0, an allowlisted extension package's bundled
|
|
172
|
+
skills, prompt templates and themes reached the session although `noSkills`
|
|
173
|
+
was set. Now an extension allowlist alone makes none of its skills available.
|
|
174
|
+
A skill is available only through the catalog plus the profile's explicit
|
|
175
|
+
selection.
|
|
176
|
+
|
|
177
|
+
Migration: add a catalog entry with the absolute path to the package's
|
|
178
|
+
`SKILL.md` inside Pi's install (normally under
|
|
179
|
+
`<Pi agent dir>/npm/node_modules/<package>/…`; `~` is not expanded) and select
|
|
180
|
+
it in the profile. Prompt templates and themes that packages ship are now also
|
|
181
|
+
closed through the SDK's public overrides. They were already never expanded
|
|
182
|
+
for MCP prompts. This does not mean every previous profile behaves unchanged:
|
|
183
|
+
a profile that used a package's skills needs the migration above.
|
|
184
|
+
|
|
185
|
+
## Consequences
|
|
186
|
+
|
|
187
|
+
- Skills are operator-managed prompt content. In `coding` profiles a skill's
|
|
188
|
+
instructions are executed with the profile's tools. A skill grants no rights.
|
|
189
|
+
- Skill selection limits the regular skill catalog only. An allowlisted
|
|
190
|
+
extension stays trusted executable code and can still read files or inject
|
|
191
|
+
prompt text. This is no sandbox.
|
|
192
|
+
- Three states stay distinct: configured (capabilities list the alias; intent
|
|
193
|
+
only), available in the SDK catalog (validated at session start and listed
|
|
194
|
+
to the model in its system prompt), and actually read by the model (the model
|
|
195
|
+
decides; not guaranteed). Task success is separate again.
|
|
196
|
+
- Skill files can change under a running session. Operators carry that
|
|
197
|
+
discipline; the server does not enforce it.
|
|
198
|
+
- Setup now writes into client skill directories, in the home directory and in
|
|
199
|
+
a project. It copies only from the package or the operator's override
|
|
200
|
+
directory, never follows a link inside a project, and never replaces what it
|
|
201
|
+
did not install or what the operator modified. The override directory and its
|
|
202
|
+
bases file are operator-trusted configuration, not a workspace. Installed
|
|
203
|
+
skill content becomes instructions for the controlling agent.
|
|
204
|
+
- The server needs a name check of its own for selected files, because
|
|
205
|
+
`loadSkills` dedupes by name before `skillsOverride`.
|
|
206
|
+
- Sessions of profiles with extensions each have their own model runtime
|
|
207
|
+
(ADR 0006). Skill loading does not change that.
|
|
208
|
+
|
|
209
|
+
## Versioning
|
|
210
|
+
|
|
211
|
+
ADR 0003 requires a minor release for a public contract change. The new
|
|
212
|
+
`skills` capability field and the new `skill_load_failed` error code are such a
|
|
213
|
+
change, and so are the new setup findings, the `--customize-skill` option and
|
|
214
|
+
the controller skill that `setup --apply` installs, so the next release after
|
|
215
|
+
0.7.1 is 0.8.0. The change is recorded under
|
|
216
|
+
`## Unreleased` in the changelog. Implementing it does not itself release or
|
|
217
|
+
bump the version.
|
|
218
|
+
|
|
219
|
+
## Note of 2026-10-12: fingerprints, locking and recovery after 0.8.0
|
|
220
|
+
|
|
221
|
+
The decisions above stand. Three details changed after 0.8.0 (issues #50, #51
|
|
222
|
+
and #52): skill tree fingerprints are version 2 (`v2:` values; a 0.8.0 value
|
|
223
|
+
counts only when a trusted reference proves it), `--apply --customize-skill`
|
|
224
|
+
runs under a kernel lock on the override directory and writes the bases file as
|
|
225
|
+
schema version 2, and an interrupted update of an installed copy is settled by
|
|
226
|
+
the next `--apply`, `--remove` or `--rollback`. See [Pi
|
|
227
|
+
skills](../skills.md#upgrade-from-080) and the [setup lifecycle
|
|
228
|
+
design](../client-setup-design.md#skill-installation).
|
package/docs/architecture.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
`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.
|
|
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`. While a turn runs, its `updatedAt` is the time of the last observed public SDK activity (assistant message and tool execution events, content-free) and is a liveness lower bound only; see [Tool contracts](tool-contracts.md). 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, 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. 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,204 @@ 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 version 2 (`src/skill-library.ts`): `v2:` and a SHA-256 over a domain and
|
|
83
|
+
version tag, the file count, and per file in path byte order the length-prefixed
|
|
84
|
+
UTF-8 path and the length-prefixed content. Version 1 (0.8.0) hashed
|
|
85
|
+
`path NUL content NUL` per file and was ambiguous, because content may contain
|
|
86
|
+
NUL bytes. A version 1 value never proves identity alone (see *Legacy records*
|
|
87
|
+
below).
|
|
88
|
+
|
|
89
|
+
**Slots.** A slot is the skill directory a client reads:
|
|
90
|
+
|
|
91
|
+
| Target | Slot |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| `codex:user` | `~/.agents/skills/pi-session-orchestration` |
|
|
94
|
+
| `claude-code:user`, `claude-code:local` | `~/.claude/skills/pi-session-orchestration` (one shared slot) |
|
|
95
|
+
| `claude-code:project` | `<project>/.claude/skills/pi-session-orchestration` (the directory in which setup runs) |
|
|
96
|
+
|
|
97
|
+
**Ownership records.** One record per client slot holds the target client,
|
|
98
|
+
the scope kind (`project` or `user`, so Claude Code `user` and `local` share a
|
|
99
|
+
record), the phase and a content fingerprint, plus `replaces` during an update (see below). A home slot uses the
|
|
100
|
+
alias `skill:pi-session-orchestration`. A project slot uses
|
|
101
|
+
`skill:pi-session-orchestration@<id>`, where `<id>` is the first 16 hex digits
|
|
102
|
+
of a SHA-256 over the project's real path: an opaque identity, not a path, so a
|
|
103
|
+
`--remove` in another project never touches this project's copy. The `pending`
|
|
104
|
+
phase is written before the first filesystem change and promoted to `owned`
|
|
105
|
+
after a post-install inspection. Skill records hold `v2:` values in
|
|
106
|
+
`fingerprint` and `replaces`; registration records keep their 64-hex snapshot
|
|
107
|
+
hash. During an update, the pending record also keeps the fingerprint of the
|
|
108
|
+
copy it replaces (`replaces`), so an interrupted update leaves a copy that
|
|
109
|
+
setup still recognizes as its own: the next `--apply` finishes the update and
|
|
110
|
+
`--remove` deletes it. A later run that finds a `pending` record whose slot
|
|
111
|
+
already equals the source promotes it.
|
|
112
|
+
|
|
113
|
+
**Atomic copy.** Setup creates missing parent directories (mode `0755`), stages
|
|
114
|
+
the tree in a staging directory in the slot's parent (files `0644`, each file
|
|
115
|
+
and directory fsynced), and re-checks that the slot is unchanged
|
|
116
|
+
(fingerprint, device and inode). It then renames the exact old tree to a backup
|
|
117
|
+
name, renames the staged directory into place, inspects the result, verifies
|
|
118
|
+
that the backup is still the exact old tree it moved, renames it to a trash
|
|
119
|
+
name and deletes the trash entry by entry. The slot is never edited in place.
|
|
120
|
+
|
|
121
|
+
**Intermediates and recovery.** The three intermediate directories are named
|
|
122
|
+
after the pending record's transaction id, in the slot's parent:
|
|
123
|
+
`.pi-session-mcp-<skill>-<transactionId>.staging`, `.backup` and `.trash`.
|
|
124
|
+
Recovery derives them from the persisted record and never searches. The order
|
|
125
|
+
is: pending record, staging, backup rename, install, verify backup, rename to
|
|
126
|
+
trash, delete the trash entry by entry, owned record. `--apply`, `--remove` and
|
|
127
|
+
`--rollback` settle a pending transaction first, inside the ownership
|
|
128
|
+
transaction, and only when the slot is empty, a link, or one of setup's two
|
|
129
|
+
copies:
|
|
130
|
+
|
|
131
|
+
- A trash directory is deleted after a structural recheck: a real directory,
|
|
132
|
+
regular files and directories only, no hidden entries, within the tree
|
|
133
|
+
limits. It may be partial, because it was verified before the rename.
|
|
134
|
+
- The backup must equal the replaced copy; the staging directory must equal
|
|
135
|
+
the target or be an interrupted write of it. A partially written staging
|
|
136
|
+
directory counts only when every file is a prefix-consistent part of the
|
|
137
|
+
current library tree and that tree is the record's target.
|
|
138
|
+
- `--apply` with an empty slot finishes forward with a complete staging
|
|
139
|
+
directory, else moves the verified backup back. `--remove` and `--rollback`
|
|
140
|
+
delete the verified intermediates and leave the slot to the removal.
|
|
141
|
+
- Anything modified or foreign stays and is reported as
|
|
142
|
+
`skill_recovery_retained` (divergent). A failed update leaves its staging
|
|
143
|
+
directory until the next run settles it, and an unhealthy ownership fence
|
|
144
|
+
retains intermediates the same way.
|
|
145
|
+
- Random-name directories of 0.8.0 updates are never searched for or deleted;
|
|
146
|
+
an operator may remove them by hand.
|
|
147
|
+
- `--dry-run` and `--verify` do not report leftover intermediates.
|
|
148
|
+
|
|
149
|
+
**Legacy records.** A skill record whose `fingerprint` or `replaces` is a bare
|
|
150
|
+
64-hex value was written by 0.8.0. It names the slot only if a trusted
|
|
151
|
+
reference has that version 1 value and is byte-identical, by its version 2
|
|
152
|
+
fingerprint, to the installed copy. The references are the shipped skills of
|
|
153
|
+
0.8.0 (a fixed table in `src/skill-library.ts`) and the current library tree.
|
|
154
|
+
When proven, `--apply` migrates the record to version 2 and then updates the
|
|
155
|
+
copy as usual; `--remove` removes the proven copy. Otherwise `--dry-run`,
|
|
156
|
+
`--apply`, `--remove` and `--rollback` report `skill_legacy_unverified`
|
|
157
|
+
(divergent) and change neither the copy nor the record. The operator inspects
|
|
158
|
+
the copy and, if it is unchanged setup content that is no longer needed,
|
|
159
|
+
deletes it; an absent slot clears the record on the next operation. The
|
|
160
|
+
intermediates of an interrupted 0.8.0 update cannot be derived and are not
|
|
161
|
+
recovered.
|
|
162
|
+
|
|
163
|
+
**Divergence rules.** Setup never replaces or deletes, and reports
|
|
164
|
+
`skill_divergent` for:
|
|
165
|
+
|
|
166
|
+
- an installed copy whose fingerprint differs from setup's own copy (the
|
|
167
|
+
owned fingerprint, or the one a pending update replaces), even if it happens
|
|
168
|
+
to equal the new source;
|
|
169
|
+
- an installed copy that a 0.8.0 record names but no trusted reference proves
|
|
170
|
+
(`skill_legacy_unverified`);
|
|
171
|
+
- a copy that differs from the source without a record (a foreign skill of the
|
|
172
|
+
same name);
|
|
173
|
+
- any entry whose name starts with `.` inside the installed copy: setup never
|
|
174
|
+
writes such entries, so they are operator content that the fingerprint over
|
|
175
|
+
the visible files would not protect;
|
|
176
|
+
- a link to anything other than the effective skill, a non-directory at the
|
|
177
|
+
skill entry, or a tree that cannot be read.
|
|
178
|
+
|
|
179
|
+
A copy that equals the source stays unowned when no record exists
|
|
180
|
+
(`skill_equivalent` or `skill_already_equivalent`). A link at the skill entry
|
|
181
|
+
whose real path is exactly the effective library skill directory (your
|
|
182
|
+
override, else the shipped skill) is the operator's own equivalent setup:
|
|
183
|
+
`--dry-run` and `--apply` report `skill_linked` (unchanged), `--verify` reports
|
|
184
|
+
`skill_verified`, and removal leaves it alone (`skill_not_owned`). An unmodified owned copy
|
|
185
|
+
that differs from the source is replaced after a package update or a changed
|
|
186
|
+
override (`skill_update_planned`, `skill_updated`).
|
|
187
|
+
|
|
188
|
+
**Link rules.** The skill entry itself is never followed. In the home
|
|
189
|
+
directory, the client's skills directory (`~/.claude/skills`, `~/.agents/skills`)
|
|
190
|
+
may be the operator's own link, for example into a dotfiles checkout, and is
|
|
191
|
+
resolved to its real path. A project may be untrusted: only the project root is
|
|
192
|
+
canonicalized, and a link anywhere below it makes the slot divergent, so setup
|
|
193
|
+
never writes outside the project. If a slot cannot be resolved at all, the
|
|
194
|
+
finding is `skill_source_invalid` for `--dry-run`, `skill_install_failed` for
|
|
195
|
+
`--apply`, `skill_missing` for `--verify` and `skill_divergent` for removal.
|
|
196
|
+
|
|
197
|
+
**Shared slot.** For `--rollback` and `--remove`, the Claude Code `user` and
|
|
198
|
+
`local` registrations share one slot. While the other registration is still
|
|
199
|
+
`owned`, the skill stays and the finding is `skill_kept_shared`. The last
|
|
200
|
+
removal deletes it (`skill_removed`; `skill_absent` if it is already gone;
|
|
201
|
+
`skill_not_owned` if setup never owned it). A modified copy is left in place
|
|
202
|
+
(`skill_divergent`) and its record stays, so a later run can finish after you
|
|
203
|
+
removed the copy.
|
|
204
|
+
|
|
205
|
+
**Remove retry.** Registration removal can finish before skill cleanup, for
|
|
206
|
+
example after an interruption or a `skill_divergent`. When the registration
|
|
207
|
+
record is gone but setup still owns the skill of that target, `--remove` and
|
|
208
|
+
`--rollback` run once more for that target. They first prove that the
|
|
209
|
+
registration is absent and then report the registration as removed and clean up
|
|
210
|
+
the skill. They never run for a target whose skill setup does not own, nor for
|
|
211
|
+
a Claude Code `user` or `local` target while the other one of the two is still
|
|
212
|
+
registered, and an unowned live registration stays untouched
|
|
213
|
+
(`ownership_unavailable`).
|
|
214
|
+
|
|
215
|
+
Skill reports carry only client, scope, the alias `pi-session-orchestration`,
|
|
216
|
+
status and a stable code. No skill content, fingerprint or path leaves setup.
|
|
217
|
+
|
|
218
|
+
## Skill customization
|
|
219
|
+
|
|
220
|
+
`setup --customize-skill <name>` (and `--apply --customize-skill <name>`,
|
|
221
|
+
`src/skill-customize.ts`) does not touch clients and takes no ownership lock. It
|
|
222
|
+
copies a shipped skill into the override directory `skills/` next to the
|
|
223
|
+
configuration file and records the shipped fingerprint under the skill name in
|
|
224
|
+
`<override dir>/.pi-session-mcp-bases.json`
|
|
225
|
+
(`{"schemaVersion":2,"bases":{"<name>":"<fingerprint>"}}`, strictly validated,
|
|
226
|
+
at most 64 KiB). A value is a `v2:` fingerprint or a legacy 64-hex value that
|
|
227
|
+
could not be migrated. A version 1 file is still read. The copy is staged in
|
|
228
|
+
a temporary directory and renamed into place; an existing override is never
|
|
229
|
+
overwritten. A changed shipped skill is only reported (`skill_override_outdated`);
|
|
230
|
+
`--apply` then moves the recorded base and nothing else.
|
|
231
|
+
|
|
232
|
+
`--apply` is a cross-process transaction per override directory
|
|
233
|
+
(`src/override-lock.ts`). It takes an exclusive kernel lock on the stable lock
|
|
234
|
+
file `<override dir>/.pi-session-mcp-bases.lock` (mode `0600`, never deleted;
|
|
235
|
+
on Linux the trusted util-linux `flock` at `/usr/bin/flock` or `/bin/flock`, on
|
|
236
|
+
macOS `O_EXLOCK`; the same supported local filesystems as the ownership
|
|
237
|
+
state). Under the lock it re-reads and validates the bases file, copies the
|
|
238
|
+
skill if no override exists, merges the base and writes the file atomically: an
|
|
239
|
+
fsynced temporary file, a rename and a directory fsync. The lock is released on
|
|
240
|
+
every path. The wait is bounded at 30 seconds (`skill_customize_lock_busy`);
|
|
241
|
+
a lock that cannot be taken is `skill_customize_lock_unavailable`. A killed
|
|
242
|
+
holder releases the lock through the kernel and may leave a stale
|
|
243
|
+
`.pi-session-mcp-bases.json.<hex>.tmp` or a temporary copy directory
|
|
244
|
+
`.<skill>.<hex>.tmp`, which are harmless. The dry-run takes no
|
|
245
|
+
lock and writes nothing: no directory, lock file or temporary file. Every write
|
|
246
|
+
produces version 2 and migrates each legacy value that names a released 0.8.0
|
|
247
|
+
shipped skill; other legacy values are kept as they are.
|
|
248
|
+
|
|
249
|
+
Decisions for an existing legacy base: if it names the current shipped skill,
|
|
250
|
+
the dry-run reports `skill_base_migration_planned` and `--apply` reports
|
|
251
|
+
`skill_base_migrated`; if it names no released shipped skill, the dry-run
|
|
252
|
+
reports `skill_base_unverified` and `--apply` acknowledges it as
|
|
253
|
+
`skill_base_updated`; if it names another released skill, the result is
|
|
254
|
+
`skill_override_outdated`. An existing override that equals the shipped skill
|
|
255
|
+
but has no base completes a customization whose bases commit failed
|
|
256
|
+
(`skill_customize_planned`, `skill_customized`). Of two concurrent applies for
|
|
257
|
+
the same skill, one reports `skill_customized` and the other
|
|
258
|
+
`skill_override_current`. Doctor stays offline, read-only and lock-free. It cannot be combined
|
|
259
|
+
with `--target`, `--verify`, `--rollback` or `--remove`. The finding target is
|
|
260
|
+
`{ client: "skills", scope: "override", alias: <name> }`; the alias is the
|
|
261
|
+
skill name only if it is valid, else `invalid`.
|
|
262
|
+
|
|
64
263
|
## Verification boundary
|
|
65
264
|
|
|
66
265
|
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 +283,26 @@ Stable finding codes are grouped by responsibility:
|
|
|
84
283
|
- verification: `registration_not_equivalent`, `mcp_verified`,
|
|
85
284
|
`mcp_unsupported`, `mcp_timeout`, `mcp_failed`;
|
|
86
285
|
- rollback/removal: `rolled_back`, `removed`, `ownership_diverged`,
|
|
87
|
-
`cleanup_failed
|
|
286
|
+
`cleanup_failed`;
|
|
287
|
+
- controller skill, dry-run: `skill_absent`, `skill_equivalent`, `skill_linked`,
|
|
288
|
+
`skill_update_planned`, `skill_recovery_planned`, `skill_divergent`,
|
|
289
|
+
`skill_legacy_unverified`, `skill_source_invalid`;
|
|
290
|
+
- controller skill, apply: `skill_installed`, `skill_already_equivalent`, `skill_recovered`, `skill_linked`,
|
|
291
|
+
`skill_updated`, `skill_divergent`, `skill_legacy_unverified`,
|
|
292
|
+
`skill_recovery_retained`, `skill_source_invalid`,
|
|
293
|
+
`skill_install_failed`;
|
|
294
|
+
- controller skill, verify: `skill_verified`, `skill_missing`,
|
|
295
|
+
`skill_not_equivalent`;
|
|
296
|
+
- controller skill, rollback/removal: `skill_removed`, `skill_kept_shared`,
|
|
297
|
+
`skill_not_owned`, `skill_absent`, `skill_divergent`,
|
|
298
|
+
`skill_legacy_unverified`, `skill_recovery_retained`;
|
|
299
|
+
- skill customization: `skill_customize_planned`, `skill_customized`,
|
|
300
|
+
`skill_override_current`, `skill_override_outdated`,
|
|
301
|
+
`skill_base_migration_planned`, `skill_base_migrated`,
|
|
302
|
+
`skill_base_unverified`, `skill_base_updated`, `skill_not_shipped`,
|
|
303
|
+
`skill_bases_corrupt`, `skill_override_invalid`,
|
|
304
|
+
`skill_customize_lock_busy`, `skill_customize_lock_unavailable`,
|
|
305
|
+
`skill_customize_failed`.
|
|
88
306
|
|
|
89
307
|
The public statuses are `planned`, `ok`, `unchanged`, `absent`, `divergent`,
|
|
90
308
|
`unsupported`, and `failed`. Reports never contain command arguments, command
|
package/docs/client-setup.md
CHANGED
|
@@ -90,6 +90,89 @@ 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_recovery_planned` (planned; the
|
|
121
|
+
ownership record is pending for an update that `--apply` would settle),
|
|
122
|
+
`skill_divergent` (divergent), `skill_legacy_unverified` (divergent),
|
|
123
|
+
`skill_source_invalid` (failed).
|
|
124
|
+
- `--apply`: `skill_installed` (ok), `skill_already_equivalent` (unchanged),
|
|
125
|
+
`skill_recovered` (ok; `--apply` settled an interrupted update and needed no
|
|
126
|
+
further update), `skill_linked` (unchanged),
|
|
127
|
+
`skill_updated` (ok; setup replaced its own unmodified copy after a package
|
|
128
|
+
update or a changed override), `skill_divergent`, `skill_legacy_unverified`,
|
|
129
|
+
`skill_recovery_retained` (divergent), `skill_source_invalid`,
|
|
130
|
+
`skill_install_failed` (failed).
|
|
131
|
+
- `--verify`: `skill_verified` (ok), `skill_missing` and
|
|
132
|
+
`skill_not_equivalent` (failed).
|
|
133
|
+
- `--rollback` and `--remove`: `skill_removed` (ok), `skill_kept_shared`
|
|
134
|
+
(unchanged; the other Claude Code `user` or `local` registration still uses
|
|
135
|
+
the shared directory), `skill_not_owned` (unchanged), `skill_absent` (ok) and
|
|
136
|
+
`skill_divergent` (a modified copy stays in place), `skill_legacy_unverified`
|
|
137
|
+
(a record of 0.8.0 that no trusted reference proves; copy and record stay) and
|
|
138
|
+
`skill_recovery_retained` (a modified or foreign intermediate directory of an
|
|
139
|
+
interrupted update stays).
|
|
140
|
+
|
|
141
|
+
Setup never replaces or deletes a modified installed copy, a skill of the same
|
|
142
|
+
name that it did not install, or a link or non-directory at the skill entry; it
|
|
143
|
+
reports `skill_divergent`. That does not block the registration, but it makes
|
|
144
|
+
the overall status `failed` (exit 1). Never edit the installed copy. To
|
|
145
|
+
customize the skill, run `--apply --customize-skill pi-session-orchestration`,
|
|
146
|
+
edit the override, and run `--apply` for the target again. Design and trust
|
|
147
|
+
rules: [setup lifecycle design](client-setup-design.md#skill-installation).
|
|
148
|
+
|
|
149
|
+
Setup installs only `pi-session-orchestration` into clients. Worker skills are
|
|
150
|
+
for Pi profiles and are selected through `builtin:` entries in `skillCatalog`.
|
|
151
|
+
|
|
152
|
+
### Customize a shipped skill
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
npm run --silent setup -- --customize-skill pi-session-review
|
|
156
|
+
npm run --silent setup -- --apply --customize-skill pi-session-review
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
This works without `--target` and needs `PI_SESSION_MCP_CONFIG`. It copies the
|
|
160
|
+
shipped skill into the `skills/` directory next to the configuration file and
|
|
161
|
+
records the fingerprint of the shipped skill in
|
|
162
|
+
`.pi-session-mcp-bases.json` there. The finding target is
|
|
163
|
+
`skills/override/<name>`; the codes are `skill_customize_planned` (planned),
|
|
164
|
+
`skill_customized` (ok), `skill_override_current` (unchanged),
|
|
165
|
+
`skill_override_outdated` (planned), `skill_base_migration_planned` (planned),
|
|
166
|
+
`skill_base_unverified` (planned), `skill_base_migrated` (ok),
|
|
167
|
+
`skill_base_updated` (ok), `skill_not_shipped`, `skill_bases_corrupt`,
|
|
168
|
+
`skill_override_invalid`, `skill_customize_lock_busy`,
|
|
169
|
+
`skill_customize_lock_unavailable` and `skill_customize_failed` (failed). An
|
|
170
|
+
existing override is never overwritten. `--apply` runs under an exclusive lock
|
|
171
|
+
on the override directory (`.pi-session-mcp-bases.lock`, 30 s wait); the dry-run
|
|
172
|
+
takes no lock and writes nothing.
|
|
173
|
+
After `skill_override_outdated`, `--apply` moves only the recorded base, which
|
|
174
|
+
acknowledges a change you reviewed. Details: [Pi skills](skills.md#customize-and-add-skills).
|
|
175
|
+
|
|
93
176
|
## Local configuration
|
|
94
177
|
|
|
95
178
|
Create an unversioned JSON configuration file such as `pi-session-mcp.json`:
|
|
@@ -118,7 +201,7 @@ Create an unversioned JSON configuration file such as `pi-session-mcp.json`:
|
|
|
118
201
|
}
|
|
119
202
|
```
|
|
120
203
|
|
|
121
|
-
Configuration is strict. A profile has exactly `default`, `permissionProfile`, `provider`, `model`, and `thinkingLevel`, plus the optional `mcpServers` and `
|
|
204
|
+
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
205
|
|
|
123
206
|
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
207
|
|
|
@@ -190,7 +273,7 @@ Claude Code enforces each tool's declared JSON Schema (`additionalProperties: fa
|
|
|
190
273
|
## Expected flow and lifecycle deadlines
|
|
191
274
|
|
|
192
275
|
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
|
|
276
|
+
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
277
|
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
278
|
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
279
|
5. Call `pi_session_close` when the session is no longer needed.
|
|
@@ -246,7 +329,7 @@ Poll `pi_turn_get` for at most three minutes. If it does not become terminal, re
|
|
|
246
329
|
|
|
247
330
|
### A prompt fails immediately after a provider config or credential change
|
|
248
331
|
|
|
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.
|
|
332
|
+
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
333
|
|
|
251
334
|
### Session cleanup
|
|
252
335
|
|