pi-pstack-adapter 0.9.0 → 0.11.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.
@@ -4,7 +4,7 @@ The compatibility registry in `src/registry.ts` assigns one support tier to ever
4
4
  certified pstack revision. Generation reads it, the wrapper frontmatter publishes it, and
5
5
  `/pistack-status` reports it.
6
6
 
7
- Certified revision: pstack `0.15.36` at `c742dd61706fbacc1b95f1a20b7b988cf096ce6c` in
7
+ Certified revision: pstack `0.15.42` at `bf0c7cd80abdbf7fe47b634978a7d1b4071f71a7` in
8
8
  `https://github.com/cursor/plugins`.
9
9
 
10
10
  ## Platforms
@@ -21,6 +21,22 @@ executables in the shell the skills run in, with Pi's `shellCommandPrefix` appli
21
21
  that matters most, because Git Bash adds `/usr/bin` and `/mingw64/bin` to the `PATH` Pi
22
22
  started with. A shell that is missing, inactive, or cannot run a command blocks every workflow.
23
23
 
24
+ pstack `0.15.42` was certified on Linux by the unattended release check. No skill or agent was
25
+ added or removed. Upstream changed its default models to Claude Opus 5.5, Claude Fable 5.2, and
26
+ Gemini 4 Carbon, made Fable its own family, and has arena pick its judge from another vendor,
27
+ where Fable and the other Claude models are one vendor. `how` now spawns one read-only subagent,
28
+ and `why` spawns none: it answers inline from commits and PR discussions, so it loses its
29
+ `delegation` gate and keeps `git` and `gh`. The `how explorer`, `how explainer`,
30
+ `why investigators`, and `why synthesizer` role lines are gone, and the host mapping no longer
31
+ names them. The host mapping gained the new default slugs, Fable and Gemini in the family note,
32
+ and a row saying that poteto-mode's `# defaults: 2` check does not apply to the adapter
33
+ configuration. The statechart scripts changed how they draw edges into nested states and how
34
+ they report a SANY parse failure; they need no new executable, and the `/bin/sh` launcher that
35
+ crashes on native Windows did not change. No tier changed. The release check runs no skill end
36
+ to end, because that would execute new upstream content unattended, so no `0.15.42` skill has
37
+ run in Pi, and `0.15.42` has not been checked on native Windows. The evidence below is from
38
+ `0.15.36` and earlier.
39
+
24
40
  pstack `0.15.36` was certified on Linux. No skill or agent was added or removed. The skills
25
41
  changed only in text, and the Orchestrate playbook's `orch` scripts were deleted; no adapter
26
42
  gate named them. `0.15.36` has not been checked on native Windows. The statechart scripts did
@@ -47,8 +63,8 @@ small Git repository holding a token-bucket rate limiter with two marked bugs.
47
63
  `recall` rises to `dependency-gated`, because the transcript was its only Cursor-specific
48
64
  step. In a fresh session it read the two earlier chats itself, so its fan-out did not run. An
49
65
  earlier fresh run looked in the default session folder, which the sandbox had moved, so the
50
- host mapping now says to find the folder from `$PI_SESSION_FILE` first. `reflect` copied the upstream skill
51
- to a Pi skill location before editing it, but its create-skill route did not run, and
66
+ host mapping now says to find the folder from `$PI_SESSION_FILE` first. `reflect` copied the
67
+ upstream skill to a Pi skill location before editing it, but its create-skill route did not run, and
52
68
  `automate-me` found nothing to draft, so both stay `experimental`.
53
69
  - `poteto-mode`'s Orchestrate playbook ran a two-unit program in a linked worktree. Each unit had
54
70
  an owner on its own branch and worktree, and a verifier from another model family checked the
@@ -223,10 +239,10 @@ Earlier Windows evidence, gathered at pstack `0.15.26` with the same toolchain:
223
239
  - `create-formal-verification-skill` completed in the isolated install without `subagent` or
224
240
  `ask_user`. It generated `.pi/skills/verify-inventory-reservations/SKILL.md` and a dependency-free
225
241
  Node 22 explicit-state verifier that imports the fixture's actual reserve/release functions.
226
- It checked 49 reachable states and 490 transitions for stock 0–3 and five request IDs, caught
242
+ It checked 49 reachable states and 490 transitions for stock from 0 to 3 and five request IDs, caught
227
243
  six mutant copies, then caught a removed retry guard in the actual product file. Restoring
228
- that file restored the passing result and its original hash. This supports `adapted`: the
229
- generated skill path is mapped to Pi. It does not certify TLA+, proof assistants, installation
244
+ that file restored the passing result and its original hash. This supports `adapted`, because
245
+ the host mapping maps the generated skill path to Pi. It does not certify TLA+, proof assistants, installation
230
246
  of other verifiers, unbounded inputs, or real database/async concurrency.
231
247
  - The revised `ketchup` ran read-only in a separate fixture. It listed both supplied product
232
248
  choices (CSV versus JSON and seven versus thirty days of history), drew a Mermaid sequence
@@ -237,8 +253,9 @@ Earlier Windows evidence, gathered at pstack `0.15.26` with the same toolchain:
237
253
  `ketchup`, but still names `uptime` as missing for `benchmark-checklist`. Delegation and
238
254
  structured questions remain unavailable in the isolated install; no delegating tier changed.
239
255
  - A cache inside a Git worktree must own its worktree, Git directory, and common directory before
240
- bootstrap or reuse can change Git state. The Windows run exposed ancestor discovery from an
241
- empty nested cache; this is fixed. Regressions also cover incomplete metadata, a foreign Git
256
+ bootstrap or reuse can change Git state. The Windows run found that Git, started in an
257
+ empty nested cache, discovered the enclosing repository. That is fixed. Regression tests also
258
+ cover incomplete metadata, a foreign Git
242
259
  file, foreign worktree/common directory settings, and short paths, while checking that the
243
260
  enclosing repository's HEAD, config, index, and tracked bytes stay unchanged.
244
261
 
@@ -287,13 +304,13 @@ A session that replaces the `bash` tool with `powershell` cannot run the skills.
287
304
 
288
305
  ## Changing a tier
289
306
 
290
- A tier is a claim about what was verified, so raising one needs evidence, not optimism.
307
+ A tier is a claim about what a run verified, so raising one needs evidence from a run.
291
308
 
292
309
  1. Run the workflow end to end in Pi against the certified revision.
293
310
  2. Record what the workflow produced, not that it started. A `dependency-gated` skill rises to
294
311
  `adapted` only when its delegation or question steps completed against a real provider.
295
312
  3. An `experimental` skill rises only when every Cursor-specific step has a Pi equivalent that
296
- was exercised, not merely described in the host mapping.
313
+ a run exercised, not one the host mapping only describes.
297
314
  4. An `unsupported` skill rises only when the missing host feature exists in Pi.
298
315
  5. Add or update a test that fails if the claim regresses.
299
316
 
@@ -332,7 +349,7 @@ Lowering a tier needs one reproduction of the failure and a note saying what bro
332
349
  | `principle-test-behavior-not-implementation` | `/skill:pistack-principle-test-behavior-not-implementation` | - | - | - |
333
350
  | `principle-type-system-discipline` | `/skill:pistack-principle-type-system-discipline` | - | - | - |
334
351
  | `setup-pstack` | `/skill:pistack-setup` | - | - | The upstream body only writes a Cursor .mdc rule, so the adapter replaces it with a Pi setup workflow. |
335
- | `sharpen` | `/skill:pistack-sharpen` | - | - | Verified end to end in Pi on Linux at pstack 0.15.36: it handed back four short sections and a rewritten prompt for a vague gateway task, left the decisions that were the user's open, and did not start the task. Since pstack 0.15.29 it hands back a rewritten prompt and does not do the task. Its body is prompt-only, with no tool, delegation, or executable, and it runs only when you type it. |
352
+ | `sharpen` | `/skill:pistack-sharpen` | - | - | Verified end to end in Pi on Linux at pstack 0.15.36: it handed back four short sections and a rewritten prompt for a vague gateway task, left the decisions that were the user's open, and did not start the task. Since pstack 0.15.29 it hands back a rewritten prompt and does not do the task. Its body is prompt-only, with no tool, delegation, or executable, and it runs only when you type it. Since pstack 0.15.42 the rewritten prompt asks the agent to prototype any choice an experiment can settle and leaves only product or preference choices to the user. That wording has not been rerun in Pi. |
336
353
  | `tdd` | `/skill:pistack-tdd` | - | - | - |
337
354
  | `technical-writing` | `/skill:pistack-technical-writing` | - | - | - |
338
355
  | `typescript-best-practices` | `/skill:pistack-typescript-best-practices` | - | - | - |
@@ -342,7 +359,7 @@ Lowering a tier needs one reproduction of the failure and a note saying what bro
342
359
 
343
360
  | upstream skill | Pi skill | capabilities | executables | why |
344
361
  | --- | --- | --- | --- | --- |
345
- | `arena` | `/skill:pistack-arena` | `delegation` | `git` | The whole skill is spawning N candidate subagents plus a judge. Since pstack 0.15.36 the judge must come from a model family other than the parent's. The adapter maps the cross-judge pool to the `arena-cross-judge-pool` panel role, which uses `judgment`, then `general` while unset, so when the parent knows its model, the judge runs only when one of its entries is on another family. The status report and `check` warn when that pool is on one model. Otherwise arena skips the judge, picks the base alone, and says so. When the parent does not know its model, arena tries the pool in order anyway and says the family check did not run. Verified end to end in Pi on Linux at pstack 0.15.36 with pi-subagents: two candidates on the `judgment` and `general` roles, then a judge from the family other than the parent's, which scored them. The parent picked a base, took the fixes for two bugs from the other candidate, fixed a third that both shared, and finished with passing tests. It left the candidate worktrees for comparison. |
362
+ | `arena` | `/skill:pistack-arena` | `delegation` | `git` | The whole skill is spawning N candidate subagents plus a judge. Since pstack 0.15.36 the judge must come from a model family other than the parent's, and since pstack 0.15.42 from another vendor, where Fable and the other Claude models are one vendor. Its default runners and pool are now one Fable, one Opus, and one Gemini model. The adapter maps the cross-judge pool to the `arena-cross-judge-pool` panel role, which uses `judgment`, then `general` while unset, so when the parent knows its model, the judge comes from the first pool entry from another vendor. When no entry qualifies, arena tries `judgment`, then `general`, and only when neither is from another vendor does it skip the judge, pick the base alone, and say so. The status report and `check` warn when that pool is on one model. When the parent does not know its model, arena tries the pool in order anyway and says the vendor check did not run. Verified end to end in Pi on Linux at pstack 0.15.36 with pi-subagents: two candidates on the `judgment` and `general` roles, then a judge from the family other than the parent's, which scored them. The parent picked a base, took the fixes for two bugs from the other candidate, fixed a third that both shared, and finished with passing tests. It left the candidate worktrees for comparison. The pstack 0.15.42 vendor rule has not run in Pi. |
346
363
  | `create-formal-verification-skill` | `/skill:pistack-create-formal-verification-skill` | - | - | Manual invocation only; no delegation. It installs or runs the verification tool it chooses and temporarily breaks product code to prove the check. Generated skills belong under `.pi/skills/` in Pi. Verified on a Windows Node finite-state fixture; other toolchains need their own prerequisites. Since pstack 0.15.32, a generated TLA+ check tells the next agent to read its results with statechart, which has its own prerequisites. |
347
364
  | `create-verification-skill` | `/skill:pistack-create-verification-skill` | - | - | Only the generated skill's output path needs remapping to Pi's skill directory. |
348
365
  | `interrogate` | `/skill:pistack-interrogate` | `delegation` | `git` | Multi-model adversarial review is entirely one subagent per configured model. Since pstack 0.15.36 it picks adversaries the way arena picks runners, from the `interrogate reviewers` line, and gives each one its own worktree with the scope's uncommitted changes. Each adversary must prove a finding with a run, and the skill keeps only findings whose command it reruns and sees fail. It reports findings and applies no fixes, but adversaries write and run repro commands, and the skill reruns them, all with your permissions. A worktree is not a sandbox, so read each repro before it reruns, especially on code you do not trust. The rubric, the lead's judgment, and the code-quality lens are gone. Verified end to end in Pi on Linux at pstack 0.15.36 with pi-subagents: two adversaries on the `judgment` and `general` roles, from two model families, each in its own worktree, proved the same two real bugs, and the skill reran both repros before it reported them. It left the adversary worktrees in the temporary folder and named the commands that remove them. |
@@ -358,13 +375,13 @@ Lowering a tier needs one reproduction of the failure and a note saying what bro
358
375
  | `blast-radius` | `/skill:pistack-blast-radius` | - | `git`, `gh` | Needs git and gh to read the diff, commits, and PR before any analysis. |
359
376
  | `correct` | `/skill:pistack-correct` | - | `git` | Mines commit history, then commits fixes and edits lint, CI, and agent instruction files. Run it on a branch you will review. |
360
377
  | `figure-it-out` | `/skill:pistack-figure-it-out` | `delegation` | - | Phase B fan-out and the architect/arena routing require parallel subagents. |
361
- | `how` | `/skill:pistack-how` | `delegation` | - | Every path spawns explorer or explainer subagents; nothing runs inline. |
378
+ | `how` | `/skill:pistack-how` | `delegation` | - | Since pstack 0.15.42 it spawns one read-only subagent that explores and explains in one pass, and nothing runs inline. That path has not run in Pi. |
362
379
  | `maintain-formal-verification-skill` | `/skill:pistack-maintain-formal-verification-skill` | - | `git` | Verified end to end on a Windows Node finite-state fixture at pstack 0.15.28. Manual invocation only; no delegation. It diffs the covered code with git from the verify skill's sync commit, edits only the verification and the verify skill, and temporarily breaks product code to prove an updated check can still fail. In Pi, look for the verify skill under `.pi/skills/`; the verification tool needs its own prerequisites. |
363
380
  | `maintain-verification-skill` | `/skill:pistack-maintain-verification-skill` | `delegation` | - | Source wave launches one read-only subagent per feature file. |
364
381
  | `no-comments` | `/skill:pistack-no-comments` | `delegation` | `git` | Step 1 must spawn the comment-sicko agent; the skill only triages its report. |
365
- | `recall` | `/skill:pistack-recall` | `delegation` | `git`, `gh` | Its core corpus is Cursor's on-disk transcript JSONL. The host mapping points it at this workspace's Pi session files instead. Verified end to end in Pi on Linux at pstack 0.15.36: in a fresh session it found the two earlier chats in the folder that holds `$PI_SESSION_FILE`, read their user messages, checked their claims against git and the code, and rebuilt the threads, problems, and next move. With two chats to read, it read them itself, so it did not fan out to subagents, and that step has not run in Pi. A run before the host mapping said to start from `$PI_SESSION_FILE` looked in the default folder instead, which a custom `PI_CODING_AGENT_DIR` had moved, and found nothing. |
366
- | `teach` | `/skill:pistack-teach` | `delegation` | - | It runs the how and why skills, both of which need subagents. |
367
- | `why` | `/skill:pistack-why` | `delegation` | `git`, `gh` | Default posture spawns one investigator subagent per evidence category plus a synthesizer. |
382
+ | `recall` | `/skill:pistack-recall` | `delegation` | `git`, `gh` | Its core corpus is Cursor's on-disk transcript JSONL. The host mapping points it at this workspace's Pi session files instead. Verified end to end in Pi on Linux at pstack 0.15.36: in a fresh session it found the two earlier chats in the folder that holds `$PI_SESSION_FILE`, read their user messages, checked their claims against git and the code, and rebuilt the threads, problems, and next move. With two chats to read, it read them itself, so it did not fan out to subagents, and that step has not run in Pi. A run before the host mapping said to start from `$PI_SESSION_FILE` looked in the default folder instead, which a custom `PI_CODING_AGENT_DIR` had moved, and found nothing. Since pstack 0.15.42 its shared-record sweep spawns one investigator per source itself instead of reusing why's investigators, and that step has not run in Pi. |
383
+ | `teach` | `/skill:pistack-teach` | `delegation` | - | It runs the how skill, which needs a subagent, and the why skill, which since pstack 0.15.42 runs inline and needs git and gh. Check why's prerequisites too. |
384
+ | `why` | `/skill:pistack-why` | - | `git`, `gh` | Since pstack 0.15.42 it spawns no subagents. It answers inline from the commits and PR discussions behind the target, and from other sources when that trail is thin, so it needs git, and gh for the PR discussions. The inline path has not run in Pi. |
368
385
 
369
386
  ### experimental (7)
370
387
 
@@ -373,9 +390,9 @@ Lowering a tier needs one reproduction of the failure and a note saying what bro
373
390
  | `automate-me` | `/skill:pistack-automate-me` | `delegation`, `structured-question` | `git`, `gh` | Its mining step reads this workspace's transcripts. The host mapping points it at this workspace's Pi session files, which use a different format. Verified end to end in Pi on Linux at pstack 0.15.36: it read only this workspace's sessions, told the parent chat from its subagents' files, found no stated preference there, and changed nothing. The built-in create-skill skill it drafts with has no Pi equivalent, and that step has not run in Pi. |
374
391
  | `ketchup-video` | `/skill:pistack-ketchup-video` | - | - | New in pstack 0.15.32. One end-to-end Pi run on native Windows drew its slides and rendered an mp4 with `ffmpeg` alone, before pstack 0.15.36 added subtitles. Verified end to end in Pi on Linux at pstack 0.15.36: it drew five slides as SVG, rendered a 35-second 1280x720 mp4 with `ffmpeg`, with each subtitle burned into a strip below its slide, and checked frames from the file. Manual invocation only; no delegation or structured question. It follows ketchup, read-only, but shows what happened as a short slide video with no audio, rendered outside the repository, with the waiting calls as text under it. Since pstack 0.15.36 it burns a one-line subtitle into each slide's frames, in a strip below the slide, and measures each slide's hold by matching the rendered frames to the slide. Its body names no renderer, so it needs whatever video tool the session has, such as `ffmpeg`, and Pi has no image generation. Its reply embedded the video as an HTML `video` tag with the file path, and whether a Pi client plays it inline is unverified. When the render fails, it says so in one line and gives only the calls. |
375
392
  | `poteto-help` | `/skill:pistack-poteto-help` | - | - | Its answers describe Cursor setup, the slash menu, and Custom Modes. A request for work runs poteto-mode, which is experimental. It asks a multiple-choice question only when the need is unclear. Its README and guide links resolve under the status report's plugin directory. |
376
- | `poteto-mode` | `/skill:pistack-poteto-mode` | `delegation`, `structured-question` | `git`, `gh` | Sticky-mode hosting plus references to Cursor built-in and plugin skills only partially map to Pi. Since pstack 0.15.36 the Orchestrate playbook calls no scripts, because the `orch` helpers are gone, and watches PRs through event subscriptions only when the harness has them. Pi has none. Its Project tracker and Project store, and forking a worker with its built state, have no Pi equivalent; the host mapping says what to do instead. Verified end to end in Pi on Linux at pstack 0.15.36 with pi-subagents and a question tool: Orchestrate ran a two-unit program in a linked worktree, with an owner per unit on its own branch and worktree, a verifier from another model family at the owner's head, and local merges. It kept the tracker and store in a folder it added to the exclude file Git resolved for the worktree, and handed state to new agents instead of messaging old ones. Its other playbooks and sticky mode were not rerun. |
393
+ | `poteto-mode` | `/skill:pistack-poteto-mode` | `delegation`, `structured-question` | `git`, `gh` | Sticky-mode hosting plus references to Cursor built-in and plugin skills only partially map to Pi. Since pstack 0.15.36 the Orchestrate playbook calls no scripts, because the `orch` helpers are gone, and watches PRs through event subscriptions only when the harness has them. Pi standalone has none; a T3 Code host can provide them when it exposes `mcp__t3-code__watch_pull_request`. This is a host capability, not a guarantee of either delegation backend. Its Project tracker and Project store, and forking a worker with its built state, have no Pi equivalent; the host mapping says what to do instead. Verified end to end in Pi on Linux at pstack 0.15.36 with pi-subagents and a question tool: Orchestrate ran a two-unit program in a linked worktree, with an owner per unit on its own branch and worktree, a verifier from another model family at the owner's head, and local merges. It kept the tracker and store in a folder it added to the exclude file Git resolved for the worktree, and handed state to new agents instead of messaging old ones. Its other playbooks and sticky mode were not rerun. Since pstack 0.15.42 it tells the user to rerun setup when the model rule has no `# defaults: 2` line. The host mapping says that check does not apply to the adapter configuration. |
377
394
  | `reflect` | `/skill:pistack-reflect` | `delegation` | - | Its reviewers read the active transcript, and the host mapping points them at the current Pi session file. Verified end to end in Pi on Linux at pstack 0.15.36 with pi-subagents: three reviewers on two model families and a synthesizer read the session file, and the run found a regression the earlier review missed. When told to apply two edits, it copied the upstream skill from the plugin directory to a Pi skill location under a new name and edited only the copy. Its create-skill routing for a new skill is Cursor-specific and has not run in Pi. It never opens a PR or pushes. |
378
- | `statechart` | `/skill:pistack-statechart` | - | `node`, `sh`, `dirname`, `sed` | Lowered from dependency-gated at pstack 0.15.34, because its new scripts crash on native Windows, a certified platform. There, the launcher starts `/bin/sh` by that path to stop TLC's process group, Node cannot start it even when `sh` is on PATH, and with no error handler the run exits 1 with `spawn /bin/sh ENOENT`. It writes no output and leaves an empty hidden staging folder beside the output folder and an empty `statechart-` folder in the temporary folder, so delete those by hand. `/pistack-check` still clears it there, because the gates are found. Use it from Linux, macOS, or WSL. Verified end to end on a Linux checkout fixture at pstack 0.15.34, and again at pstack 0.15.36, where it found the uncapped refill in a token bucket as the shortest path to a broken rule. The Windows end-to-end evidence from pstack 0.15.32 and 0.15.30 describes the scripts it replaced. Manual invocation only; no delegation or structured question. `scripts/statechart` is a POSIX `sh` launcher for TypeScript scripts, and it runs `dirname` and `sed` on every start. It runs the scripts under Node 22.18 to 22.x or Node 23.6 or later, then Bun 1.1.5 or later, then Deno 2.0 or later, and exits with code 127 when none is found. The prerequisite check gates on `node` but cannot check its version. The launcher does: with only an older Node on PATH, it names the versions it needs and exits 127. The command checks a TLA+ spec with TLC and writes `report.md` and TLC output to a new or empty `--out` folder, `SPEC-chart` beside the spec by default, and refuses a folder that is not empty. It also writes `chart.mmd`, except when TLC finds more than 500 states and no rule breaks. It stages its work in a hidden folder beside the output folder and in the system temporary folder, and deletes both. TLC runs from `TLA2TOOLS`, the path to a downloaded `tla2tools.jar`, with the binary in `JAVA` or `java` from PATH. Without `TLA2TOOLS`, it uses TLC 1.7.4 and Java temurin-25 from `mise`, and installs whichever is missing with `mise`, which needs the network. `JAVA` then changes only the binary that runs TLC, and does not skip the `mise` lookup or install of Java. Either `mise` or `TLA2TOOLS` with Java works, so neither is gated, and the script names what is missing when neither is set up. It imports the `--view` module as JavaScript with full process permissions, so review an existing view before you run it. Since pstack 0.15.34 the scripts render no images and do not run `npm`. For an image, the body has the agent install `pele@1.9.0` with `npm` once, outside the repository, which needs `npm` and the network, and render SVG with it. The body does not ask for `--ignore-scripts`, so pass it, and rendering still imports Pele with full process permissions. Run it in a scratch folder outside the repository. Git for Windows bundles `sh`, `dirname`, and `sed`, but not `node`, `mise`, or Java. |
395
+ | `statechart` | `/skill:pistack-statechart` | - | `node`, `sh`, `dirname`, `sed` | Lowered from dependency-gated at pstack 0.15.34, because its new scripts crash on native Windows, a certified platform. There, the launcher starts `/bin/sh` by that path to stop TLC's process group, Node cannot start it even when `sh` is on PATH, and with no error handler the run exits 1 with `spawn /bin/sh ENOENT`. It writes no output and leaves an empty hidden staging folder beside the output folder and an empty `statechart-` folder in the temporary folder, so delete those by hand. `/pistack-check` still clears it there, because the gates are found. Use it from Linux, macOS, or WSL. Verified end to end on a Linux checkout fixture at pstack 0.15.34, and again at pstack 0.15.36, where it found the uncapped refill in a token bucket as the shortest path to a broken rule. The Windows end-to-end evidence from pstack 0.15.32 and 0.15.30 describes the scripts it replaced. Manual invocation only; no delegation or structured question. `scripts/statechart` is a POSIX `sh` launcher for TypeScript scripts, and it runs `dirname` and `sed` on every start. It runs the scripts under Node 22.18 to 22.x or Node 23.6 or later, then Bun 1.1.5 or later, then Deno 2.0 or later, and exits with code 127 when none is found. The prerequisite check gates on `node` but cannot check its version. The launcher does: with only an older Node on PATH, it names the versions it needs and exits 127. The command checks a TLA+ spec with TLC and writes `report.md` and TLC output to a new or empty `--out` folder, `SPEC-chart` beside the spec by default, and refuses a folder that is not empty. It also writes `chart.mmd`, except when TLC finds more than 500 states and no rule breaks. It stages its work in a hidden folder beside the output folder and in the system temporary folder, and deletes both. TLC runs from `TLA2TOOLS`, the path to a downloaded `tla2tools.jar`, with the binary in `JAVA` or `java` from PATH. Without `TLA2TOOLS`, it uses TLC 1.7.4 and Java temurin-25 from `mise`, and installs whichever is missing with `mise`, which needs the network. `JAVA` then changes only the binary that runs TLC, and does not skip the `mise` lookup or install of Java. Either `mise` or `TLA2TOOLS` with Java works, so neither is gated, and the script names what is missing when neither is set up. It imports the `--view` module as JavaScript with full process permissions, so review an existing view before you run it. Since pstack 0.15.34 the scripts render no images and do not run `npm`. For an image, the body has the agent install `pele@1.9.0` with `npm` once, outside the repository, which needs `npm` and the network, and render SVG with it. The body does not ask for `--ignore-scripts`, so pass it, and rendering still imports Pele with full process permissions. Run it in a scratch folder outside the repository. Git for Windows bundles `sh`, `dirname`, and `sed`, but not `node`, `mise`, or Java. At pstack 0.15.42 the Mermaid backend can draw the edges into a nested state as one edge into its parent plus start edges inside it, and a SANY parse failure reruns SANY to print its errors. Those script changes add no executable, leave the `/bin/sh` launcher unchanged, and have not run in Pi. |
379
396
  | `swarm` | `/skill:pistack-swarm` | `delegation` | - | Cloud background workers and cloud_base_branch have no Pi mapping; only local fan-out approximates it. |
380
397
 
381
398
  ### unsupported (1)
@@ -395,8 +412,8 @@ T3 Code's `delegate_task` has no named agents. Generation writes each agent's pr
395
412
  `adapter/agents/<pi-name>.md`, with skill paths relative to that file. The parent begins the
396
413
  child's task by telling it to read that file as its system prompt.
397
414
 
398
- Both agents also register through the `pi-subagents:runtime-agent-register:v1` contract. Without an owner for
399
- that event, the status report says so rather than dropping them silently.
415
+ Both agents also register through the `pi-subagents:runtime-agent-register:v1` contract. When no
416
+ extension handles that event, the status report says the agents did not register.
400
417
 
401
418
  ## Delegation providers
402
419
 
@@ -418,6 +435,13 @@ marker text is reported as a warning, with its bytes unchanged. An agent whose P
418
435
  skill name rules is skipped with an error, before its name reaches a file name or any generated
419
436
  text.
420
437
 
438
+ The generated instructions tell the model to name a role, not a model, by passing
439
+ `pstack:<role>` as the delegation's model. Before either tool runs, the adapter rewrites that
440
+ reference to the role's model and thinking, taking the first model of a pool that the model
441
+ filter allows, and then applies the filter as for any other call. Unit and integration tests drive
442
+ this through the real `tool_call` handler. No workflow has yet run end to end with role
443
+ references, so no tier claim rests on them.
444
+
421
445
  Unit tests cover detection, activation order, and the generated instructions for both providers.
422
446
  No tier changed when T3 support arrived, because no delegating workflow has yet run end to end
423
447
  through `delegate_task`. That run is the evidence the tier rules above require.
@@ -427,11 +451,10 @@ through `delegate_task`. That run is the evidence the tier rules above require.
427
451
  `setup-pstack` writes a Cursor `.mdc` rule, so the adapter substitutes its own body and
428
452
  publishes it as `/skill:pistack-setup`. Upstream's optional closing offers to run
429
453
  `create-verification-skill` and `create-formal-verification-skill` are left out of the Pi body;
430
- both skills stay available by name. The upstream skill stays represented, and the
454
+ both skills stay available by name. The registry still lists the upstream skill, and the
431
455
  wrapper frontmatter records `pistack-upstream-skill: setup-pstack`.
432
456
 
433
457
  ## Unknown skills
434
458
 
435
459
  A skill present upstream but absent from the registry is generated as `experimental` and reported
436
- in diagnostics. A skill in the registry but absent upstream is reported as removed. Neither case
437
- is silent.
460
+ in diagnostics. A skill in the registry but absent upstream is reported as removed.
package/index.ts CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  sessionFilterFromEntries,
19
19
  } from "./src/filter.ts";
20
20
  import { gateDelegation } from "./src/gate.ts";
21
+ import { applyRewrite, routeDelegation } from "./src/route.ts";
21
22
  import { checkPrerequisites, detectCapabilities, type ToolDescriptor } from "./src/capabilities.ts";
22
23
  import {
23
24
  type AdapterConfig,
@@ -149,24 +150,37 @@ export default function (pi: ExtensionAPI) {
149
150
  sessionFilter = sessionFilterFromEntries(ctx.sessionManager.getBranch() as never[]);
150
151
  });
151
152
 
152
- // The model filter applies to every delegation in this session, pstack or not, because a
153
- // delegation tool call names no pstack role. A blocked call fails; nothing retries it.
153
+ // A `pstack:<role>` model is rewritten to the model the adapter picks for that role. The model
154
+ // filter then applies to every delegation in this session, pstack or not, because a call that
155
+ // names a concrete model names no role. A blocked call fails; nothing retries it.
154
156
  pi.on("tool_call", async (event, ctx) => {
155
157
  if (event.toolName !== T3_DELEGATE_TOOL && event.toolName !== SUBAGENT_TOOL) return;
156
- if (sessionFilter === undefined && configError !== undefined) {
157
- return {
158
- block: true,
159
- reason: `Blocked by the pstack adapter: its config could not be read, so it cannot tell whether a saved model filter applies. ${configError} Fix the config and reload, or ask the user whether to set an explicit session filter with \`set_filter\`.`,
160
- };
161
- }
162
- const decision = gateDelegation(event.toolName, event.input, {
158
+ const unreadable = configBlock();
159
+ if (unreadable) return unreadable;
160
+ const context = {
163
161
  filter: filterOf(activeFilter(sessionFilter, savedFilter)),
164
162
  parentModel: parentModelOf(ctx),
165
- resolveModel: (model) => resolveRegistryModel(ctx, model),
166
- });
163
+ resolveModel: (model: string) => resolveRegistryModel(ctx, model),
164
+ };
165
+ const route = await routeDelegation(event.toolName, event.input, { ...context, config: state?.config, parentThinking: ctx.thinkingLevel });
166
+ if (route.kind === "block") return { block: true, reason: route.reason };
167
+ if (route.kind === "rewrite") applyRewrite(event.input as Record<string, unknown>, route.input);
168
+ // Read the config state, filter, and parent model again: a pick that waits on a service could span
169
+ // a reload, a `set_filter`, or a model change.
170
+ const unreadableNow = configBlock();
171
+ if (unreadableNow) return unreadableNow;
172
+ const decision = gateDelegation(event.toolName, event.input, { ...context, filter: filterOf(activeFilter(sessionFilter, savedFilter)), parentModel: parentModelOf(ctx) });
167
173
  if (decision.kind === "block") return { block: true, reason: decision.reason };
168
174
  });
169
175
 
176
+ function configBlock(): { block: true; reason: string } | undefined {
177
+ if (sessionFilter !== undefined || configError === undefined) return undefined;
178
+ return {
179
+ block: true,
180
+ reason: `Blocked by the pstack adapter: its config could not be read, so it cannot tell whether a saved model filter applies. ${configError} Fix the config and reload, or ask the user whether to set an explicit session filter with \`set_filter\`.`,
181
+ };
182
+ }
183
+
170
184
  pi.on("resources_discover", async () => {
171
185
  if (!state) return {};
172
186
  return { skillPaths: [state.generation.skillsDir] };
@@ -210,7 +224,7 @@ export default function (pi: ExtensionAPI) {
210
224
  model: Type.Optional(
211
225
  Type.Union([Type.String(), Type.Array(Type.String())], {
212
226
  description:
213
- "`provider/model` id, or `inherit` to clear the role. A list of ids gives an ordinary role a pool: each delegation picks one. For a panel role such as `arena-runners`, a string of entries separated by commas, one seat each.",
227
+ "`provider/model` id, or `inherit` to clear the role. A list of ids gives an ordinary role a pool, ranked in list order: a `pstack:<role>` delegation runs on the first one the model filter allows. For a panel role such as `arena-runners`, a string of entries separated by commas, one seat each.",
214
228
  }),
215
229
  ),
216
230
  thinking: Type.Optional(
@@ -467,7 +481,7 @@ export default function (pi: ExtensionAPI) {
467
481
  const listed = (typeof plan.value === "string" ? [plan.value] : plan.value).map((entry) => `\`${entry}\``).join(", ");
468
482
  return typeof plan.value === "string"
469
483
  ? `Role \`${role}\` now uses ${listed}. A pool of one is written as a plain model. Written to ${path}.${note}`
470
- : `Role \`${role}\` now has a pool of ${plan.value.length} models: ${listed}. Each delegation picks one model the filter allows. Written to ${path}.${note}`;
484
+ : `Role \`${role}\` now has a pool of ${plan.value.length} models: ${listed}. A \`pstack:${role}\` delegation runs on the first one the model filter allows. Written to ${path}.${note}`;
471
485
  }
472
486
 
473
487
  function setFilter(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-pstack-adapter",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Run Cursor's official pstack plugin inside Pi from an unmodified upstream checkout",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/certified.ts CHANGED
@@ -5,9 +5,9 @@
5
5
  export const CERTIFIED = {
6
6
  repo: "https://github.com/cursor/plugins",
7
7
  ref: "main",
8
- commit: "c742dd61706fbacc1b95f1a20b7b988cf096ce6c",
8
+ commit: "bf0c7cd80abdbf7fe47b634978a7d1b4071f71a7",
9
9
  pluginPath: "pstack",
10
- pstackVersion: "0.15.36",
10
+ pstackVersion: "0.15.42",
11
11
  skillCount: 58,
12
12
  agentCount: 2,
13
13
  } as const;
package/src/config.ts CHANGED
@@ -10,8 +10,9 @@ import type { DelegationProvider, SourceSelection, Trust } from "./types.ts";
10
10
  import { type ModelFilter, parseModelFilter } from "./filter.ts";
11
11
 
12
12
  /**
13
- * A role's model. A list is a pool of alternatives for an ordinary role: each delegation picks one
14
- * eligible entry. A panel role keeps its space-separated string, one seat per entry.
13
+ * A role's model. A list is a ranked pool of alternatives for an ordinary role: each delegation runs
14
+ * on the first eligible entry unless it overrides the ranking. A panel role keeps its
15
+ * space-separated string, one seat per entry.
15
16
  */
16
17
  export type ModelValue = string | readonly string[];
17
18
 
package/src/gate.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { splitModelSpec } from "./config.ts";
2
2
  import { SUBAGENT_TOOL, T3_DELEGATE_TOOL } from "./delegation.ts";
3
+ import { safeEcho } from "./echo.ts";
3
4
  import { type ModelFilter, describeFilter, isRestricting, modelAllowed } from "./filter.ts";
4
5
 
5
6
  export type GateDecision = { readonly kind: "allow" } | { readonly kind: "block"; readonly reason: string };
@@ -50,7 +51,7 @@ function checkModel(requested: unknown, where: string, context: GateContext & {
50
51
  }
51
52
  const resolved = context.resolveModel(splitModelSpec(requested.trim()).model);
52
53
  if (resolved === undefined) {
53
- return block(`${where} asks for \`${requested.trim()}\`, which this Pi installation does not know, so the filter cannot allow it.`, context.filter);
54
+ return block(`${where} asks for ${safeEcho(requested.trim())}, which this Pi installation does not know, so the filter cannot allow it.`, context.filter);
54
55
  }
55
56
  if (!modelAllowed(context.filter, resolved)) return block(`${where} asks for \`${resolved}\`, which the filter excludes.`, context.filter);
56
57
  return ALLOW;
@@ -83,7 +84,7 @@ function gateT3(args: Record<string, unknown>, context: GateContext): GateDecisi
83
84
  }
84
85
  const provider = [providerInstanceId, driverKind].find((value) => value !== undefined && value !== "pi");
85
86
  if (provider !== undefined) {
86
- return block(`\`delegate_task\` targets T3 provider \`${String(provider)}\`, whose models are not Pi registry ids, so the filter cannot check them. Use \`providerInstanceId: "pi"\`.`, filter);
87
+ return block(`\`delegate_task\` targets T3 provider ${safeEcho(provider)}, whose models are not Pi registry ids, so the filter cannot check them. Use \`providerInstanceId: "pi"\`.`, filter);
87
88
  }
88
89
  if (model === undefined) return checkParent("`delegate_task`", scoped);
89
90
  return checkModel(model, "`delegate_task`", scoped);
package/src/generate.ts CHANGED
@@ -464,31 +464,52 @@ ${agentSystemPrompt(notes, upstreamPrompt)}
464
464
  * strongest-judgment delegate's helpers would all run at its level. The rule travels in every brief.
465
465
  */
466
466
  const NESTED_DELEGATION_RULE = `Nested delegation: every agent that delegates, a child included, gives each new child an
467
- explicit role, using \`general\` when no other role fits, and never lets it inherit the delegating
468
- agent's model or thinking. The one exception is a panel seat from an \`inherit\` entry in the
469
- \`panel roles\` table, which runs on the delegating agent's model and thinking because the user
470
- configured it so. Put this line in each brief, verbatim, just before any read-only or
471
- no-commit rule: "If you start agents, give each the model and thinking of its own pstack role from the
472
- \`pstack_adapter\` status report, using \`general\` when no role fits. Never let one inherit your
473
- model or thinking, except a panel seat from an \`inherit\` entry in that report's \`panel roles\` table, and never raise a role above its configured level."`;
474
-
475
- const MODEL_POLICY_SECTION = `Model pools and the model filter work the same on both backends.
476
-
477
- - A role whose \`model roles\` row reads "pool, pick one of" lists alternatives. For each
478
- delegation, pick ONE model from it by fit, and pass that model and its thinking by the backend
479
- rules below. A pool is not a retry list and not a panel: start one child per task, and when a
480
- delegation fails, report the failure instead of trying another model from the pool.
467
+ explicit role as \`pstack:<role>\`, using \`pstack:general\` when no other role fits, and
468
+ never lets it inherit the delegating agent's model or thinking. The one exception is a panel seat
469
+ from an \`inherit\` entry in the \`panel roles\` table, which runs on the delegating agent's
470
+ model and thinking because the user configured it so. Put this line in each brief, verbatim,
471
+ just before any read-only or no-commit rule: "If you start agents, pass each one's model as \`pstack:<role>\` for its own pstack role, using \`pstack:general\` when no role fits, so the pstack adapter gives it the model and thinking that the \`pstack_adapter\` status report shows for that role. Never let one inherit your model or thinking, except a panel seat from an \`inherit\` entry in that report's \`panel roles\` table, and never raise a role above its configured level."`;
472
+
473
+ const MODEL_POLICY_SECTION = `Model roles, pools, and the model filter work the same on both backends.
474
+
475
+ - Name a role, not a model. For every pstack delegation, pass \`pstack:<role>\` as the model, such
476
+ as \`pstack:feature\`, using \`pstack:general\` when no other role fits. Before the call runs, the
477
+ adapter replaces it with the role's model and thinking, or, when the role inherits, with no
478
+ model, so the child inherits the parent model and thinking. The backend sections below say where the
479
+ reference goes.
480
+ - A role whose \`model roles\` row reads "pool, ranked" lists alternatives in rank order. The
481
+ adapter picks the first one the model filter allows, with that entry's thinking. To override the
482
+ ranking for one task, pass \`pstack:<role>@<provider/model>\` with another allowed model from the
483
+ role's pool, and say in your reply which model you picked and why. The adapter blocks an
484
+ override that is not an allowed entry of the pool.
485
+ A pool is not a retry list and not a panel: start one child per task, and when a delegation
486
+ fails, report the failure instead of trying another model from the pool.
487
+ - To lower a role's thinking, end the reference with \`:<level>\`, as in \`pstack:feature:low\` or
488
+ \`pstack:feature@<provider/model>:low\`. A level above the role's configured one runs at the
489
+ configured one, and a role whose thinking reads "backend default" keeps the backend's default.
490
+ - The adapter blocks a reference to an unknown role, to a panel role, or to a blocked role, and
491
+ the block reason says what to do next. Report a blocked call instead of retrying it.
492
+ - A concrete \`provider/model\` still runs, checked only by the model filter. Use it for a
493
+ configured panel seat, which names its own model, and not for a pstack role.
481
494
  - A role whose row reads "BLOCKED" has no model the model filter allows. Do not delegate its work,
482
495
  and do not move the work to \`general\` or to your own model. Tell the user which role is blocked
483
496
  and why.
484
497
  - A panel seat that reads "BLOCKED" does not run. Run the other seats, and say in your reply which
485
- seat did not run and why. A seat that reads "one of" runs once, on one model you pick from it.
498
+ seat did not run and why. The seats of an unset panel are roles: delegate them as
499
+ \`pstack:judgment\` and \`pstack:general\`, so a seat that reads "ranked" runs once on the first
500
+ allowed model unless you override the ranking as for a pool. A configured seat names its model
501
+ and thinking, and the adapter does not rewrite a concrete model, so pass both yourself: on T3,
502
+ \`target: { providerInstanceId: "pi", model: "<provider/model>", options: { thinking: "<level>" } }\`;
503
+ on pi-subagents, \`model: "<provider/model>:<level>"\`. For a seat at "backend default", pass
504
+ \`options: {}\` on T3, which clears the inherited thinking, or no suffix on pi-subagents. An
505
+ \`inherit\` seat omits \`target\` on T3 and passes your own model on pi-subagents.
486
506
  - When the \`model filter\` row of the status report names a filter, the adapter blocks any
487
507
  \`delegate_task\` or \`subagent\` call in this session whose model the filter excludes, or whose
488
508
  model it cannot tell. That covers a call with no model while your own model is excluded, a model
489
509
  Pi does not know, a T3 provider other than \`pi\`, a \`subagent\` child without \`model\`, a
490
510
  \`subagent\` workflow script, and a \`subagent\` action that starts, restarts, steers, or schedules
491
- a child or configures a watchdog. A T3 target with a named environment is blocked too.
511
+ a child or configures a watchdog. A T3 target with a named environment is blocked too. The
512
+ adapter replaces a \`pstack:<role>\` reference first, and checks the model it picked.
492
513
  Report a blocked call instead of retrying it. Keep the active filter unchanged unless the user
493
514
  requests a change. Wait for \`set_filter\` to finish before issuing delegation calls.
494
515
  - Children do not inherit a session filter. Put the brief line that the \`check\` report gives into
@@ -520,20 +541,19 @@ function t3Section(namespace: string): string {
520
541
  \`task\` with "Read <path> and follow it as your system prompt.", where <path> is the absolute
521
542
  path of \`agents/${namespace}-poteto-agent.md\` beside this file. The \`agent prompts\` row of
522
543
  the status report gives that directory.
523
- - Model: a role configured as \`provider/model\` becomes
524
- \`target: { providerInstanceId: "pi", model: "provider/model" }\`. Read the thinking column of
525
- the \`model roles\` table in the \`pstack_adapter\` \`status\` report. When it names a level, add it to
526
- \`target.options\` under the option id that \`${T3_CAPABILITIES_TOOL}\` lists for that model's
527
- thinking setting. For Pi models the id is \`thinking\`, as in \`options: { thinking: "<level>" }\`.
528
- A role whose thinking reads "backend default" omits \`options\`, so the backend applies its own
529
- default. An unconfigured role uses the \`general\` role. Only when \`general\` is unconfigured
530
- too does the child omit \`target\` and inherit the parent model and thinking.
544
+ - Model: pass \`target: { providerInstanceId: "pi", model: "pstack:<role>" }\`. The adapter
545
+ writes the picked model into \`target.model\` and its thinking into \`target.options.thinking\`.
546
+ When the role's thinking reads "backend default", it removes that option and sends
547
+ \`options\` empty, which clears the parent's settings.
548
+ An unconfigured role uses the \`general\` role. Only when \`general\` is unconfigured too does
549
+ the adapter remove the model and its thinking, so the child inherits the parent model and
550
+ thinking. A role
551
+ reference always runs on the \`pi\` provider, so the adapter blocks one with another
552
+ \`providerInstanceId\` or \`driverKind\`.
531
553
  - ${NESTED_DELEGATION_RULE}
532
- - If the model does not list the level, use the highest level it lists that is below the requested
533
- one, and tell the user. If it lists none below the requested level, or lists no thinking option,
534
- omit \`options\` and tell the user. If the level is \`off\` and the model does not list \`off\`, omit
535
- \`options\` and tell the user, because the closest listed level would turn thinking on.
536
- For a different model family, call \`${T3_CAPABILITIES_TOOL}\` and use a provider and model it lists.
554
+ - If T3 rejects the thinking level for the picked model, lower it with a \`:<level>\` suffix to a
555
+ level that \`${T3_CAPABILITIES_TOOL}\` lists for that model, and tell the user. Do not switch to
556
+ another model.
537
557
  - Background: \`mode: "async"\`. A finished child wakes this thread, so end the turn instead of
538
558
  polling. Call \`${T3_STATUS_TOOL}\` only when you need a result mid-turn.
539
559
  - Call \`${T3_CAPABILITIES_TOOL}\` or \`${T3_STATUS_TOOL}\` only when the \`check\` report allows it.
@@ -556,12 +576,12 @@ function subagentSection(namespace: string): string {
556
576
 
557
577
  - Agent names are namespaced, for example \`${namespace}-poteto-agent\`. Call
558
578
  \`${SUBAGENT_TOOL}({ action: "list" })\` to see them.
559
- - Model: pass a role's \`provider/model\` as \`model\`. When the thinking column of the \`model roles\`
560
- table in the \`pstack_adapter\` \`status\` report names a level for the role, append it as
561
- \`provider/model:<level>\`. A role whose thinking reads "backend default" gets no suffix, so
562
- pi-subagents applies its own default. An unconfigured role uses the \`general\` role, and omits
563
- \`model\` only when \`general\` is unconfigured too. Under a model filter, always pass \`model\`,
564
- using your own model for a role that inherits.
579
+ - Model: pass \`model: "pstack:<role>"\` for each child, including each entry of \`tasks\`, each
580
+ \`chain\` step, and each \`parallel\` entry of a step. The adapter replaces it with
581
+ \`provider/model:<level>\`, or \`provider/model\` when the role's thinking reads "backend
582
+ default", so pi-subagents applies its own default.
583
+ An unconfigured role uses the \`general\` role. When \`general\` is unconfigured too, the adapter
584
+ passes your own model and thinking, or omits \`model\` when Pi does not report the model.
565
585
  - ${NESTED_DELEGATION_RULE}
566
586
  - Background: \`async: true\`.`;
567
587
  }
@@ -602,15 +622,16 @@ write no \`Skipped in Pi\` line for it.
602
622
  | \`Grep\`, \`Glob\` | \`rg\` through \`bash\`, or Pi's \`grep\` and \`find\` tools when the session has them. Without \`rg\`, use \`grep -rn\` and \`find\`. |
603
623
  | \`AskUserQuestion\`, \`AskQuestion\` | the \`ask_user\` tool. |
604
624
  | \`TodoWrite\`, \`TaskCreate\`, \`TaskUpdate\`, Open a todolist | no Pi equivalent. Keep an uncommitted \`todo.md\` checklist instead. |
605
- | \`claude-opus-5-5-xhigh\`, \`grok-4.7-xhigh-fast\`, \`gpt-*\`, other Cursor model slugs | the role models configured by \`/skill:${namespace}-setup\`. Call the \`pstack_adapter\` tool with \`action: "status"\` to read the current role table. An effort word in a slug, such as \`-xhigh\` or \`-max\`, is not a model id. The role table's thinking column carries that level, and \`-fast\` has no equivalent. An unconfigured role uses the \`general\` role. When \`general\` is unconfigured too, omit \`target\` or \`model\` so the child inherits the parent model and thinking. |
606
- | "pick each subagent's reasoning level and fast or standard speed", a role's starting level from the \`/setup-pstack\` budget | the thinking column of the \`model roles\` table is a role's ceiling. When it names a level, you may lower it along \`max\`, \`xhigh\`, \`high\`, \`medium\`, \`low\`, \`minimal\`, \`off\` as the body asks, then pass it by the backend rules in [Delegation](#delegation). Never raise a role above its configured level, even where the body says to raise it for the hardest work. Send that work to \`strongest-judgment\` at its own level instead. A role whose thinking reads "backend default" keeps that setting, because it has no level to move. An unconfigured role takes the \`general\` role's level as its ceiling, and keeps the parent's setting only when \`general\` is unconfigured too. Fast or standard speed has no Pi equivalent, so leave it out. If the backend rejects the moved level, use the role's configured level and say so. |
607
- | a role line valued \`auto\` or \`inherit-parent\` | an unconfigured role. Use the \`general\` role, or omit \`target\` or \`model\` only when \`general\` is unconfigured too. |
625
+ | \`claude-opus-5-5-xhigh\`, \`claude-fable-5-2-max\`, \`gemini-4-carbon-high\`, \`grok-*\`, \`gpt-*\`, other Cursor model slugs | the role models configured by \`/skill:${namespace}-setup\`. Pass the role as the model, \`pstack:<role>\`, as [Delegation](#delegation) says, and the adapter picks the role's model and thinking. Call the \`pstack_adapter\` tool with \`action: "status"\` to read the current role table. An effort word in a slug, such as \`-xhigh\` or \`-max\`, is not a model id. The role table's thinking column carries that level, and \`-fast\` has no equivalent. An unconfigured role uses the \`general\` role. When \`general\` is unconfigured too, the adapter lets the child inherit the parent model and thinking. |
626
+ | "pick each subagent's reasoning level and fast or standard speed", a role's starting level from the \`/setup-pstack\` budget | the thinking column of the \`model roles\` table is a role's ceiling. When it names a level, you may lower it along \`max\`, \`xhigh\`, \`high\`, \`medium\`, \`low\`, \`minimal\`, \`off\` as the body asks, by ending the role reference with \`:<level>\`, as in \`pstack:feature:low\`. Never raise a role above its configured level, even where the body says to raise it for the hardest work. Send that work to \`strongest-judgment\` at its own level instead. A role whose thinking reads "backend default" keeps that setting, because it has no level to move. An unconfigured role takes the \`general\` role's level as its ceiling, and keeps the parent's setting only when \`general\` is unconfigured too. Fast or standard speed has no Pi equivalent, so leave it out. If the backend rejects the moved level, pass \`pstack:<role>\` with no suffix to use the role's configured level, and say so. |
627
+ | a role line valued \`auto\` or \`inherit-parent\` | an unconfigured role. Pass \`pstack:<role>\` as for any role. The adapter uses the \`general\` role, and lets the child inherit the parent model only when \`general\` is unconfigured too. |
608
628
  | \`~/.cursor/rules/pstack-models.mdc\` | adapter configuration written by \`/skill:${namespace}-setup\`. |
629
+ | the rule's \`# defaults: 2\` line, a rule without it, telling the user to rerun \`/setup-pstack\` | does not apply. The adapter configuration has no \`# defaults\` line, and an unset role uses the \`general\` role rather than an upstream default slug, so no older setup left stale defaults in it. Do not tell the user to rerun setup for this. |
609
630
  | other \`.cursor/rules/*.mdc\` files, an always-applied rule | Pi reads no rule files. It loads \`AGENTS.md\` or \`CLAUDE.md\` from \`~/.pi/agent/\`, the working directory, and its parent directories into every session, so durable instructions go there. Model roles are the exception, as the row above says. |
610
631
  | role lines \`feature, refactoring\`, \`bug-fix\`, \`perf-issue\`, \`hillclimb\`, \`judgment and prose\`, \`hardest tasks\` | adapter roles \`feature\` or \`refactoring\`, \`bug-fix\`, \`perf-issue\`, \`hillclimb\`, \`judgment\`, and \`strongest-judgment\`. |
611
- | role lines \`how explainer\`, \`why synthesizer\`, \`reflect judgment, divergent, synthesizer\` | adapter role \`judgment\`. |
612
- | role lines \`how explorer\`, \`why investigators\`, \`reflect tooling\`, \`swarm workers\`, and verification | adapter role \`general\`. |
613
- | panel role lines \`arena runners\`, \`arena cross-judge pool\`, \`architect runners\`, \`interrogate reviewers\` | adapter panel roles \`arena-runners\`, \`arena-cross-judge-pool\`, \`architect-runners\`, and \`interrogate-reviewers\`. The \`panel roles\` table of the \`pstack_adapter\` status report lists each panel's seats in order, one per entry, and a seat's thinking is its ceiling, passed by the backend rules in [Delegation](#delegation). A \`model of the agent that starts the panel\` seat comes from an \`inherit\` entry: start it with no \`target\` or \`model\`, so it runs on your own model and thinking even when \`general\` is configured. The \`auto\` and \`inherit-parent\` row does not apply to it. An unset panel uses \`judgment\`, then \`general\`, and those are also the defaults where the body names its own default list or default pool. Families go by the model id after the provider prefix, such as \`claude-*\`, \`gpt-*\`, \`grok-*\`, or \`gemini-*\`, not by the provider, because one provider can serve several families. |
632
+ | role line \`reflect judgment, divergent, synthesizer\` | adapter role \`judgment\`. |
633
+ | role lines \`reflect tooling\`, \`swarm workers\`, and verification | adapter role \`general\`. |
634
+ | panel role lines \`arena runners\`, \`arena cross-judge pool\`, \`architect runners\`, \`interrogate reviewers\` | adapter panel roles \`arena-runners\`, \`arena-cross-judge-pool\`, \`architect-runners\`, and \`interrogate-reviewers\`. The \`panel roles\` table of the \`pstack_adapter\` status report lists each panel's seats in order, one per entry, and a seat's thinking is its ceiling, passed by the backend rules in [Delegation](#delegation). A \`model of the agent that starts the panel\` seat comes from an \`inherit\` entry: on T3, start it with no \`target\`; on pi-subagents, pass your own model as \`model\`, as [Delegation](#delegation) says. It then runs on your own model even when \`general\` is configured. The \`auto\` and \`inherit-parent\` row does not apply to it. An unset panel uses \`judgment\`, then \`general\`, and those are also the defaults where the body names its own default list or default pool. Families and vendors go by the model id after the provider prefix, such as \`claude-fable-*\`, \`claude-*\`, \`gemini-*\`, \`gpt-*\`, or \`grok-*\`, not by the provider, because one provider can serve several families. Apply the body's own family and vendor rules to those ids. |
614
635
  | a panel whose seats all run on one model, or a claim that a model or model family is unavailable | A panel the status report warns about runs on one model because of the adapter configuration. Say that in your reply, name the panel, and name \`/skill:${namespace}-setup\` as the fix. Do not attribute it to which models exist. A \`model of the agent that starts the panel\` seat is your own model, so a panel whose other seats are all on your model is one-model too. Never claim a model or family is unavailable. A model listing is not proof of absence: \`${T3_CAPABILITIES_TOOL}\` filters by \`search\` and omits models beyond its limit, and the \`pstack_adapter\` tool's \`action: "models"\` shows only the session's scoped models when the user scoped some. Say which models the configuration uses instead. |
615
636
  | a delegate no role line names, such as an ad-hoc helper | adapter role \`general\`. |
616
637
  | a Custom Mode, "Use as Mode", or Option+Enter / Alt+Enter on \`/poteto-mode\` | \`/${namespace}-mode on\`, which keeps Poteto Mode active for the Pi session until \`/${namespace}-mode off\`. |
@@ -626,11 +647,11 @@ write no \`Skipped in Pi\` line for it.
626
647
  | \`environment: "cloud"\`, \`cloud_base_branch\` | not available. Run the fan-out locally instead. |
627
648
  | the agent's store (path in the system prompt), the Project store, the Project tracker (\`notes.md\`) | no Pi equivalent; Pi's system prompt names no store. Keep those files in a folder Git ignores, such as \`.work/<project>/\` after adding \`.work/\` to the exclude file that \`git rev-parse --git-path info/exclude\` prints (in a linked worktree \`.git\` is a file, so never open \`.git/info/exclude\` directly), or outside the repository. Give its absolute path to each child that reads or writes them. |
628
649
  | Cursor \`/loop\`, \`/loop <interval>\` | not available in Pi, which has no timer that wakes an agent. When \`/loop\` drives an autonomous run to a predicate, keep working in this session: re-check the predicate after each iteration, and stop only when it passes or at a real dead end. When it arms a timed tick, such as \`/loop 1h\` for an audit, **skip** arming it, say that no tick is armed, and give the user the tick prompt to run again. Do not start a sleep or polling loop in its place. |
629
- | PR and CI event subscriptions | not available in Pi. Check the PR's live state with \`gh pr view\` and \`gh pr checks\` when you report on it, and do not start a polling loop in their place. |
650
+ | PR and CI event subscriptions | Pi standalone has no PR or CI event subscriptions. If the host exposes \`mcp__t3-code__watch_pull_request\`, use it according to its tool instructions: end the turn after subscribing and wait for host events instead of polling. Do not assume every host or delegation backend provides it. Without that tool, check the PR's live state with \`gh pr view\` and \`gh pr checks\` when you report on it, and do not start a polling loop in their place. A wake is not a merge decision; check readiness yourself and respect the user's merge instructions. |
630
651
  | forking a worker to reuse its built state, such as its checkout, uncommitted changes, or a running process | no Pi equivalent; no delegation backend copies a checkout or a running process into a new child. Wait until the earlier worker has finished or stopped, so one writer at a time uses that worktree. Then start a new child, as the \`resume\` row says, and give it the earlier worker's brief and result, the absolute path of the worktree, and every fact it needs. If the earlier worker left a process running, tell the child to reuse it when it can reach it, or to stop it before starting another, so two copies never compete for one port or file. Stop only a process confirmed to have been started by the earlier worker for this task. Never stop an unidentified process that holds the port or file; when ownership is unclear, the child reports it to the parent instead. |
631
652
  | Bugbot | not available in Pi. Bugbot comments on a PR are ordinary review threads, so triage them with the forge command the step gives, such as \`gh\`. When the PR has no Bugbot check or comment, **skip** a step that starts, waits for, or configures Bugbot. |
632
653
  | Cursor cloud agents, a cloud-agent URL | not available. Work that would go to a cloud agent runs locally, as the \`environment: "cloud"\` row says. **Skip** a step that needs the cloud agent itself, such as its status in the Cursor dashboard or its own machine. |
633
- | a built-in PR tool | Pi has none. Use the forge command the step gives, such as \`gh pr create\`. This is not a skip, so write no skip line. |
654
+ | a built-in PR tool | Pi standalone has none. A host may expose PR tools; use them only when available and follow their instructions. Otherwise use the forge command the step gives, such as \`gh pr create\`. This is not a skip, so write no skip line. |
634
655
 
635
656
  A body instruction that conflicts with Pi policy or with the user's explicit instruction loses.
636
657
 
package/src/pools.ts CHANGED
@@ -33,8 +33,8 @@ export function parentProblem(context: ResolutionContext): string | undefined {
33
33
  }
34
34
 
35
35
  export function resolveRole(config: AdapterConfig, role: string, context: ResolutionContext): RoleResolution {
36
- const own = config.models[role];
37
- const from = own !== undefined ? role : config.models[GENERAL_ROLE] !== undefined ? GENERAL_ROLE : undefined;
36
+ const own = Object.hasOwn(config.models, role) ? config.models[role] : undefined;
37
+ const from = own !== undefined ? role : Object.hasOwn(config.models, GENERAL_ROLE) ? GENERAL_ROLE : undefined;
38
38
  if (from === undefined) {
39
39
  const problem = parentProblem(context);
40
40
  return problem ? { kind: "blocked", from: role, reason: `it inherits, and ${problem}`, choices: [] } : { kind: "inherit", from: role };