@justjoshn/pi-pstack 0.15.36-pi.1 → 0.15.36-pi.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/README.md +26 -21
  3. package/agents/ci-watcher.md +28 -0
  4. package/agents/thermo-nuclear-code-quality-review.md +23 -0
  5. package/assets/logo.png +0 -0
  6. package/automations/benny/FOR_AGENTS.md +2 -1
  7. package/automations/benny/README.md +2 -2
  8. package/automations/benny/skills/setup-benny/SKILL.md +10 -9
  9. package/automations/benny/templates/reproduce-automation-prompt.md +1 -1
  10. package/automations/benny/templates/triage-automation-prompt.md +1 -1
  11. package/bin/pstack-routines-supervisor +9 -0
  12. package/docs/guide/01-setup.md +2 -2
  13. package/docs/guide/07-overnight.md +1 -1
  14. package/docs/guide/09-make-it-yours.md +1 -1
  15. package/docs/guide/10-recipes-and-pitfalls.md +1 -1
  16. package/docs/native-gaps.md +14 -8
  17. package/docs/parity-reverse-engineering.md +94 -0
  18. package/docs/parity-status.md +113 -0
  19. package/package.json +41 -6
  20. package/packaging/install-supervisor-unit.sh +164 -0
  21. package/packaging/launchd/com.pstack.routines-supervisor.plist.example +24 -0
  22. package/packaging/systemd/pstack-routines-supervisor.service.example +15 -0
  23. package/skills/architect/SKILL.md +1 -1
  24. package/skills/architect/references/runner-prompt.md +1 -1
  25. package/skills/arena/SKILL.md +4 -4
  26. package/skills/check-compiler-errors/SKILL.md +23 -0
  27. package/skills/create-skill/SKILL.md +515 -29
  28. package/skills/fix-ci/SKILL.md +29 -0
  29. package/skills/fix-merge-conflicts/SKILL.md +32 -0
  30. package/skills/get-pr-comments/SKILL.md +23 -0
  31. package/skills/how/SKILL.md +1 -1
  32. package/skills/ketchup/SKILL.md +2 -2
  33. package/skills/ketchup-video/SKILL.md +2 -2
  34. package/skills/loop-on-ci/SKILL.md +50 -0
  35. package/skills/make-bot-ui/SKILL.md +2 -2
  36. package/skills/make-pr-easy-to-review/SKILL.md +59 -0
  37. package/skills/new-branch-and-pr/SKILL.md +29 -0
  38. package/skills/no-inline-imports/SKILL.md +12 -0
  39. package/skills/poteto-help/SKILL.md +4 -4
  40. package/skills/poteto-mode/SKILL.md +2 -2
  41. package/skills/poteto-mode/playbooks/bug-fix.md +1 -1
  42. package/skills/poteto-mode/playbooks/feature.md +1 -1
  43. package/skills/poteto-mode/playbooks/hillclimb.md +1 -1
  44. package/skills/poteto-mode/playbooks/multi-phase-plan.md +2 -2
  45. package/skills/poteto-mode/playbooks/orchestrate.md +2 -2
  46. package/skills/poteto-mode/playbooks/perf-issue.md +1 -1
  47. package/skills/poteto-mode/playbooks/refactoring.md +1 -1
  48. package/skills/pr-review-canvas/SKILL.md +176 -0
  49. package/skills/pr-review-canvas/renderer.js +170 -0
  50. package/skills/pr-review-canvas/styles.css +96 -0
  51. package/skills/pr-review-canvas/template.html +18 -0
  52. package/skills/reflect/SKILL.md +2 -2
  53. package/skills/review-and-ship/SKILL.md +41 -0
  54. package/skills/run-smoke-tests/SKILL.md +42 -0
  55. package/skills/setup-pstack/SKILL.md +13 -13
  56. package/skills/statechart/scripts/test/cli.test.ts +1 -1
  57. package/skills/swarm/SKILL.md +2 -2
  58. package/skills/thermo-nuclear-code-quality-review/SKILL.md +192 -0
  59. package/skills/typescript-exhaustive-switch/SKILL.md +10 -0
  60. package/skills/verify-this/SKILL.md +74 -0
  61. package/skills/weekly-review/SKILL.md +32 -0
  62. package/skills/what-did-i-get-done/SKILL.md +31 -0
  63. package/skills/why/SKILL.md +2 -2
  64. package/skills/workflow-from-chats/SKILL.md +50 -0
  65. package/src/child-env.ts +15 -2
  66. package/src/index.ts +18 -3
  67. package/src/loop.ts +63 -14
  68. package/src/mode.ts +31 -7
  69. package/src/path-skills.ts +104 -0
  70. package/src/paths.ts +18 -0
  71. package/src/pi-command.ts +12 -0
  72. package/src/plan.ts +109 -24
  73. package/src/rule.ts +4 -4
  74. package/src/sections.ts +21 -2
  75. package/src/subscriptions/durable-store.ts +531 -0
  76. package/src/subscriptions/durable-types.ts +69 -0
  77. package/src/subscriptions/ensure-supervisor.ts +67 -0
  78. package/src/subscriptions/index.ts +310 -282
  79. package/src/subscriptions/supervisor-main.ts +134 -0
  80. package/src/subscriptions/supervisor.ts +325 -0
  81. package/src/subscriptions/webhook.ts +82 -21
  82. package/src/task/card.ts +29 -0
  83. package/src/task/models.ts +7 -0
  84. package/src/task/progress.ts +112 -0
  85. package/src/task/records.ts +7 -9
  86. package/src/task/registry.ts +7 -3
  87. package/src/task/runner.ts +112 -23
  88. package/src/task/tool.ts +112 -43
  89. package/src/task/worktree.ts +5 -3
  90. package/src/todo.ts +8 -3
  91. package/scripts/live-e2e.mjs +0 -257
  92. package/test/aliases.test.ts +0 -57
  93. package/test/ask.test.ts +0 -85
  94. package/test/autocomplete.test.ts +0 -39
  95. package/test/e2e/aliases.e2e.test.ts +0 -81
  96. package/test/e2e/ask.e2e.test.ts +0 -135
  97. package/test/e2e/harness.ts +0 -179
  98. package/test/e2e/host.e2e.test.ts +0 -193
  99. package/test/e2e/lifecycle.e2e.test.ts +0 -99
  100. package/test/e2e/mode.e2e.test.ts +0 -160
  101. package/test/e2e/package.e2e.test.ts +0 -67
  102. package/test/e2e/task.e2e.test.ts +0 -734
  103. package/test/e2e/todo.e2e.test.ts +0 -59
  104. package/test/fixtures/cursor-askquestion-calls.json +0 -806
  105. package/test/fixtures/fake-pi.mjs +0 -96
  106. package/test/fixtures/image-model.ts +0 -16
  107. package/test/fixtures/list-models.ts +0 -15
  108. package/test/fixtures/readonly-shell-exit.ts +0 -17
  109. package/test/fixtures/scripted-model.ts +0 -113
  110. package/test/loop.test.ts +0 -209
  111. package/test/mode.test.ts +0 -143
  112. package/test/plan.test.ts +0 -126
  113. package/test/readonly-shell-security.test.ts +0 -84
  114. package/test/readonly-shell.test.ts +0 -44
  115. package/test/records.test.ts +0 -143
  116. package/test/registry.test.ts +0 -18
  117. package/test/resources.test.ts +0 -162
  118. package/test/rule.test.ts +0 -92
  119. package/test/scripts.test.ts +0 -182
  120. package/test/sections.test.ts +0 -56
  121. package/test/subscriptions-cron.test.ts +0 -60
  122. package/test/subscriptions.test.ts +0 -598
  123. package/test/task-agents.test.ts +0 -49
  124. package/test/task-card.test.ts +0 -34
  125. package/test/task-fork.test.ts +0 -233
  126. package/test/task-format.test.ts +0 -65
  127. package/test/task-model.test.ts +0 -141
  128. package/test/task-runner.test.ts +0 -259
  129. package/test/task-schema.test.ts +0 -50
  130. package/test/task-status.test.ts +0 -155
  131. package/test/task-worktree.test.ts +0 -106
  132. package/test/todo.test.ts +0 -74
  133. package/test/visualize.test.ts +0 -152
  134. package/tsconfig.json +0 -5
  135. package/vitest.config.ts +0 -16
package/CHANGELOG.md ADDED
@@ -0,0 +1,79 @@
1
+ # Changelog
2
+
3
+ ## [0.15.36-pi.11] - 2026-11-04
4
+
5
+ - Title a background completion notice with the Task `description`, as Cursor names a subagent, instead of a slice of its prompt.
6
+ - Stream a foreground Task's last tool steps and latest line into its card while the child works, the way Cursor shows conversation steps.
7
+ - `/loop` accepts the interval first or last, in unit words (`10 minutes`, `every 10 minutes`), prints usage for a bare command, and keeps wall-clock cadence so a long run does not delay the next tick.
8
+ - A bare `/plan` enters plan mode as Cursor's plan command does, and plan mode keeps `stop_agent` so a launched child can be stopped.
9
+ - Attach a skill whose frontmatter declares `paths`/`globs` (including the shipped `typescript-best-practices`) when the session reads or edits a matching file, once per session.
10
+ - Inject the shipped always-applied engineering rules as their full text instead of a paraphrase.
11
+ - Put the current todo list in every request's system prompt so compaction and branch switches do not drop it.
12
+ - Start `cloud_base_branch` from `origin/<branch>` when that ref exists, falling back to a local ref.
13
+ - Say that a resume ignores a supplied `subagent_type` instead of dropping it silently.
14
+ - Record a detached `new_session` fire's own final message as its run output in `list_subscriptions`.
15
+ - Restore the upstream Benny commit-before-enable gate and sweep from the last successful sweep so downtime does not lose reports.
16
+ - Restore the architect runner prompt's independence sentence, describe `visualize` as a bar or line chart, and deliver screenshots and video the way Pi can.
17
+ - A webhook routine accepts `sessionStrategy: "new_session"`, so an accepted POST runs detached with no chat open, as a Cursor Automation does, and records its outcome. Older stored routines keep `wake_self`.
18
+ - A timer or webhook routine no longer expires after seven days. Cursor defines no automation expiry, so `expiresInHours` is the only limit; `0` means none, and a GitHub watch keeps its 7-day default.
19
+ - Each workspace binds its own routines port: a busy default falls back to an ephemeral port the workspace reuses and reports, while an explicit `PSTACK_ROUTINES_PORT` still reports a collision.
20
+ - A mode attaches its skill body on the request that enters it and sends a path-naming reminder on later requests, as Cursor's Custom Mode does, instead of re-sending the whole body every turn.
21
+ - Plan mode saves a `.plan.md` artifact through `create_plan`, and `/plan build` starts the work from the saved file.
22
+
23
+ ## [0.15.36-pi.10] - 2026-11-03
24
+
25
+ - Resume and fork a descendant that a worktree worker started inside its worktree, from the root chat, while keeping unrelated-directory records refused.
26
+ - Recover a steering message stranded at settlement with its recorded images and its own message boundary instead of joining text and dropping attachments.
27
+ - Refuse a new subscription event with HTTP 503 and `Retry-After` when 20 events are undelivered, instead of evicting an event the sender was told was accepted.
28
+ - Keep accepted `new_session` work eligible after the subscription expires; expiry only stops new occurrences.
29
+ - Run a `new_session` timer on the model, thinking level, and project-trust decision it was created with, live or detached.
30
+ - Append Task settle-time notices to entries another extension proposed at the same boundary.
31
+ - Signal a pre-epoch supervisor lock only when its command line identifies this supervisor, so a reused pid cannot target an unrelated process.
32
+ - Report durable subscriptions and detached supervision in the host section instead of saying they live only in the current Pi process.
33
+
34
+ ## [0.15.36-pi.9] - 2026-11-03
35
+
36
+ - Follow the repository Vitest rules in the test suite. No runtime change.
37
+ - Correct stale documentation against the current code, including versions, counts, paths and host requirements.
38
+
39
+ ## [0.15.36-pi.8] - 2026-11-02
40
+
41
+ - Declare `cost.totalFromProvider` on the exported `Usage` type so the package typechecks again, completing the provider-reported cost provenance aggregation shipped in 0.15.36-pi.7.
42
+
43
+ ## [0.15.36-pi.7] - 2026-11-02
44
+
45
+ - Update the host compatibility documentation for Pi 1.5.0. Retain the four native gaps for RPC process control, skill-block formatting, named-ID fork truncation, and workspace folder naming.
46
+ - Keep earlier verification records unchanged. This documentation audit does not claim new runtime-test results or complete upstream parity.
47
+ - Preserve string-enum argument shapes and optional literal types with TypeBox schemas under Pi 1.5.0 validation.
48
+ - Preserve provider-reported cost provenance when aggregating subagent usage.
49
+ - Ship a versioned package-root changelog entry for Pi 1.5.0's extension update notices.
50
+
51
+ ## [0.15.36-pi.6]
52
+
53
+ - Implement version-2 durable subscription registry with transactional file locking, native owner continuation delivery via `pi.sendMessage`, bounded exponential retry on detached failures, per-subscription queue caps, non-blocking GitHub arming, legacy supervisor termination, and in-process fallback isolation.
54
+ - Decode child RPC stdout streams using `StringDecoder` to preserve split multibyte UTF-8 code points across buffer boundaries.
55
+ - Publish upstream pstack git commit provenance in `pstackSource` and exclude nested script dependencies from npm publication archives via `skills/.npmignore`.
56
+ - Canonicalize statechart symlink test fixture paths on macOS and add checksum-verified TLC statechart verification to CI.
57
+
58
+ ## [0.15.36-pi.5]
59
+
60
+ - Replace hand-rolled frontmatter, attachment MIME, and session header parsers with Pi 1.4.0 exports (`stripFrontmatter`, `detectSupportedImageMimeTypeFromFile`, and `parseSessionEntries`).
61
+ - Register TUI message renderers for `pstack/task` and `pstack/subscription`, drain parked `runtime/inbox/` wakes on session restore, surface durable delivery state in `list_subscriptions`, and report live supervisor status in `/routines`.
62
+ - Declare published `files` in `package.json`, record the REA architecture reconstruction report in `docs/parity-reverse-engineering.md`, and enforce zero dependencies on an external `pstack` checkout.
63
+
64
+ ## [0.15.36-pi.4]
65
+
66
+ - Add a durable routines supervisor (`pstack-routines-supervisor`) that dual-writes subscription desired state and auto-starts on `subscribe_*` so timers and webhook routines keep firing after the interactive chat exits. Optional launchd/systemd examples live under `packaging/`.
67
+ - Add `packaging/install-supervisor-unit.sh` for opt-in reboot survival (idempotent launchd or systemd user unit). Benny setup and the overnight guide point at it.
68
+ - Teach `/ketchup-video` and the host media section to open the rendered mp4 in the system player, read one frame per slide, and name the path (Pi chat does not inline-play video).
69
+ - Expand the vendored `create-skill` soft-dep to full interview and structure depth with Pi paths.
70
+
71
+ ## [0.15.36-pi.3]
72
+
73
+ - Vendor the full soft-dependency team-kit skill and agent set into the package (CI, PR, review, verification, weekly summary, and the two engineering rules as skills), so pi-pstack no longer needs a separate team-kit install for parity.
74
+ - Inject the two former always-apply engineering rules into the host system prompt, and teach the thermo-nuclear agent to gather diffs with Pi tools instead of inventing `shell` / `explore` Task types.
75
+ - Keep Claude Code harness notes out of the package (Pi users get `docs/native-gaps.md` instead).
76
+
77
+ ## [0.15.36-pi.2]
78
+
79
+ - Add repository, homepage, and bugs metadata and a changelog to the npm package.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # pstack for Pi (pi-pstack)
2
2
 
3
- pi-pstack is pstack 0.15.36 for Pi 1.4.0: rigorous agent workflows (`/poteto-mode` and its playbooks), the principle skills, and the review, research, and verification skills, plus `deslop`, `control-cli`, and `control-ui`. A small extension provides the host features the skills call by name: `/name` skill commands, `/mode`, `/plan`, the `Task` subagent tool with isolated workers, forks, and `agent_status`, `TodoWrite`, `AskQuestion`, the model rule, `/loop`, subscriptions and webhook routines, and `visualize`.
3
+ pi-pstack is pstack 0.15.36 for Pi 1.5.0: rigorous agent workflows (`/poteto-mode` and its playbooks), the principle skills, and the review, research, and verification skills, plus the full soft-dependency team-kit set (`deslop`, `control-cli`, `control-ui`, CI/PR helpers, thermo-nuclear review, and the rest) vendored in so you do not install a separate team-kit package. A small extension provides the host features the skills call by name: `/name` skill commands, `/mode`, `/plan`, the `Task` subagent tool with local worktree workers, forks, and `agent_status`, `TodoWrite`, `AskQuestion`, the model rule, `/loop`, subscriptions and webhook routines, and `visualize`.
4
4
 
5
5
  ## Get set up
6
6
 
@@ -13,8 +13,8 @@ pi-pstack is pstack 0.15.36 for Pi 1.4.0: rigorous agent workflows (`/poteto-mod
13
13
  pi install ./extensions/pi-pstack
14
14
  ```
15
15
 
16
- Pi does not install dependencies for a local package, so `bun install` (or `npm install` in `extensions/pi-pstack`) fetches the one runtime dependency, `@anthropic-ai/sandbox-runtime`, which the read-only shell loads (readonly subagents and `/plan`). Without it everything else works, and a read-only shell command fails instead of running unsandboxed. To try it for one session without installing, run `pi -e ./extensions/pi-pstack`. To install the published package instead, run `pi install npm:@justjoshn/pi-pstack`.
17
- Installing loads pi-pstack's tools and system-prompt sections at once, but no skill runs until you invoke it. 57 pstack skills stay out of the model's automatic skill list; only `/setup-pstack`, `create-skill`, `deslop`, `control-cli`, and `control-ui` can load from your words alone.
16
+ Pi does not install dependencies for a local package, so `bun install` (or `npm install` in `extensions/pi-pstack`) fetches the one runtime dependency, `@anthropic-ai/sandbox-runtime`, which the read-only shell loads (readonly subagents and `/plan`). Without it everything else works, and a read-only shell command fails instead of running unsandboxed. On Linux the sandbox also needs `bubblewrap`, `socat`, and `ripgrep` (`sudo apt install bubblewrap socat ripgrep`), and Ubuntu 24.04 and later need the user-namespace change under Limits. macOS uses the built-in `sandbox-exec`. pi-pstack needs Node 22.19 or newer. To try it for one session without installing, run `pi -e ./extensions/pi-pstack`. To install the published package instead, run `pi install npm:@justjoshn/pi-pstack`.
17
+ Installing loads pi-pstack's tools and system-prompt sections at once, but no skill runs until you invoke it. 61 workflow skills stay out of the model's automatic skill list. Setup, create-skill, and the team-kit shipping and review skills (`deslop`, `control-cli`, `control-ui`, CI and PR helpers, and the rest of that set) can load from your words alone.
18
18
  2. Run `/setup-pstack`. It lists the models the `Task` tool can use, asks for a reasoning budget, maps a model to each role, and writes the model rule. A new rule applies from the next request.
19
19
  3. Start real work with `/poteto-mode`, a goal, and a check that can pass or fail.
20
20
 
@@ -27,9 +27,9 @@ Remove the package with `pi remove ./extensions/pi-pstack`. Its state lives in `
27
27
  | You type | What happens |
28
28
  |---|---|
29
29
  | `/poteto-mode <task>` | The skill attaches to that one message. Every skill Pi loaded works this way: pstack's (`/how`, `/why`, `/arena`, `/interrogate`, `/principle-prove-it-works`, `/deslop`) and your own, such as a `/<handle>-mode` skill from `/automate-me`. `/skill:<name>` works too. |
30
- | `/mode poteto-mode [task]` | Keeps the skill in context on every turn. The footer shows a badge with the skill's `icon` and `color` metadata, such as a yellow `♛ mode: poteto-mode` (other skills get `⚡`). `/mode off` exits and `/mode` reports. Any loaded skill can back a mode. |
31
- | `/plan [task]` | Plan mode: the agent gets read-only tools (`read`, `grep`, `find`, `ls`, the sandboxed `bash_readonly`, `AskQuestion`, `TodoWrite`, `agent_status`, and `Task`, whose launches run readonly: a fresh one runs here with no worktree, and a resume or fork stays where its agent ran), investigates, and writes a plan with goal, verified steps, and risks. Writes are blocked. `/plan off` restores your tools so the agent can execute; `/plan` reports. The footer shows `plan`. |
32
- | `/loop 30m <prompt>` | Runs the prompt now and again 30 minutes after each run settles. Without an interval the agent picks each wake with the `Loop` tool, and the loop ends when it picks none and no background subagent is still running. `/loop stop` or Escape during a run ends it. The prompt can be a skill command, such as `/loop 1h /poteto-mode run the audit tick`. When a playbook says to arm `/loop`, the agent starts it with the `Loop` tool. Intervals go up to 24 days. |
30
+ | `/mode poteto-mode [task]` | Keeps the skill in context on every turn. The skill body rides the first request after entry, then each later request gets a reminder that names the skill file, so a long session does not re-send 21 KB every turn. The footer shows a badge with the skill's `icon` and `color` metadata, such as a yellow `♛ mode: poteto-mode` (other skills get `⚡`). `/mode off` exits and `/mode` reports. Any loaded skill can back a mode. |
31
+ | `/plan [task]` | Plan mode: the agent gets read-only tools (`read`, `grep`, `find`, `ls`, the sandboxed `bash_readonly`, `create_plan`, `AskQuestion`, `TodoWrite`, `agent_status`, `Task`, whose launches run readonly: a fresh one runs here with no worktree, and a resume or fork stays where its agent ran, and `stop_agent`), investigates, and writes a plan with goal, verified steps, and risks. `create_plan` saves it under the agent dir's `plans/` as a `.plan.md` file, so the user can read it. Writes are blocked. A bare `/plan` enters plan mode. `/plan build` leaves plan mode and starts the work from the saved file, as the plan menu does; `/plan off` restores your tools. The footer shows `plan`. |
32
+ | `/loop 30m <prompt>` | Runs the prompt now, then again every 30 minutes from each fire, so a long run does not push the next tick out. A tick that lands while a run is still going is skipped rather than queued, so one long run cannot leave a backlog of stale ticks; the next tick after it settles runs. The interval can come first or last, written as `30m`, `10 minutes`, or `every 10 minutes`. Without an interval the agent picks each wake with the `Loop` tool, and the loop ends when it picks none and no background subagent is still running. `/loop stop` or Escape during a run ends it. The prompt can be a skill command, such as `/loop 1h /poteto-mode run the audit tick`. When a playbook says to arm `/loop`, the agent starts it with the `Loop` tool. Intervals go up to 24 days. |
33
33
  | `/routines` | Lists webhook routines with their URL, credential file, and status. |
34
34
  | `/setup-pstack` | Configures the per-role models. |
35
35
  | `/poteto-help <question>` | Answers a question about pstack and hands you a prompt to send. |
@@ -40,34 +40,38 @@ Remove the package with `pi remove ./extensions/pi-pstack`. Its state lives in `
40
40
 
41
41
  | Feature | Behavior |
42
42
  |---|---|
43
- | `Task` (`description`, `prompt`, `subagent_type`, `model`, `run_in_background`, `resume`, `fork`, `fork_turns`, `readonly`, `file_attachments`, `environment`, `cloud_base_branch`) | Starts a separate `pi --mode rpc` process per subagent, so this Pi process can steer and abort it. Foreground calls return the response and an agent ID. Background calls return at once, and a `<task>` notification arrives after your turn ends; print and JSON runs wait for pending notifications before exiting, unless aborted. Several calls in one message run in parallel. A subagent's own subagents cannot spawn more. A run lock per agent ID keeps out a second writer, even from another Pi process (Limits notes the stale-lock race). Children inherit the parent's project-trust decision. Foreground usage counts toward the session totals. The Task card leads with the call's `description`, then the agent type and the model that ran. |
44
- | `resume` | Sends a follow-up to an idle agent with its context, model, type, and directory. On an agent running in this Pi process, it delivers the prompt as a steer and returns at once. A readonly or plan-mode call cannot steer a writable agent. An agent running in another Pi process is refused. This is how a playbook "messages" a worker. Pi's own `/resume` command reopens a chat session instead. |
43
+ | `Task` (`description`, `prompt`, `subagent_type`, `model`, `run_in_background`, `resume`, `fork`, `fork_turns`, `readonly`, `file_attachments`, `environment`, `cloud_base_branch`) | Starts a separate `pi --mode rpc` process per subagent, so this Pi process can steer and abort it. Foreground calls return the response and an agent ID. Background calls return at once, and a `<task>` notification arrives after your turn ends, titled by the call's `description`; print and JSON runs wait for pending notifications before exiting, unless aborted. Several calls in one message run in parallel. A subagent's own subagents cannot spawn more. A run lock per agent ID keeps out a second writer, even from another Pi process (Limits notes the stale-lock race). Children inherit the parent's project-trust decision. Foreground usage counts toward the session totals. The Task card leads with the call's `description`, then the agent type and the model that ran, and while a foreground child works it shows its last few tool steps and latest line instead of a blank wait. |
44
+ | `resume` | Sends a follow-up to an idle agent with its context, model, type, and directory. The root chat can resume or fork a descendant that a worktree worker started inside that worktree, and the descendant still runs where it first ran. On an agent running in this Pi process, it delivers the prompt as a steer and returns at once. A readonly or plan-mode call cannot steer a writable agent. An agent running in another Pi process is refused. This is how a playbook "messages" a worker. Pi's own `/resume` command reopens a chat session instead. |
45
45
  | `fork` | Starts a new agent whose transcript begins as a copy of an idle agent's transcript, in that agent's directory (its worktree for an isolated worker), so it sees the built checkout and uncommitted changes. Type, model, and readonly default from the source; readonly can be added, never lifted. `fork_turns: N` (only with `fork`) copies just the first N user turns. `fork` excludes `resume`, `environment`, and `cloud_base_branch`. Pi's own `/fork` command branches the chat session instead. |
46
46
  | `stop_agent` | `agent_id`. Stops a running subagent. One this Pi process owns gets an RPC abort, then a kill after a grace period. One another of your Pi processes owns gets a signal to the live pid in its run lock. An idle or finished agent is left alone, and the result says so. |
47
47
  | `agent_status` | Reads a subagent's state (`running` while its run lock names a live pid, `idle`, or `missing`), type, model, directory, branch, times, and last reply, without waking it. It also shows whether the agent is readonly, the agent it was forked from, its pid while running, and its transcript. Without `agent_id` it lists the workspace's 20 newest agents. In the root chat that includes every descendant, isolated workers too. |
48
- | Subagent types | `generalPurpose` (default), `poteto-agent` (a background delegate that reads poteto-mode first), and `comment-sicko`, read from `agents/*.md`. Unknown types fail with the list. |
49
- | Models | A Pi model spec such as `claude-opus-5-5:xhigh` or `xai/grok-4.7:high`, model parameters such as `claude-opus-5-5[effort=high]` or `grok-4.7[fast=true]` (picks a listed `-fast` id of that model), or `inherit`, `inherit-parent`, `auto`, or nothing for the parent model. A model Pi does not list, or a thinking level the model does not support, fails with the allowed list. A bare model id that several providers offer resolves to the parent chat's provider when it offers that id, and otherwise fails with the choices. Older rules that wrote `claude-opus-5-5-xhigh` still resolve. The system prompt's `pstack_subagents` section lists the types, the parent model, and the models with their levels. |
50
- | Isolated workers (`environment: "cloud"`) | The subagent runs in a new git worktree at `<pstack dir>/workspaces/<workspace folder>/worktrees/<agent id>` on branch `pstack/<agent id>`, from `cloud_base_branch` (a local ref, else `origin/<branch>`) or your HEAD commit, so uncommitted changes do not reach it. The result and the card name the branch to merge, and `resume` and `fork` run in the same worktree. It fails clearly outside a git repository, in a repository with no commits, or when the base does not resolve. The worker shares the root chat's workspace state, so it sees the same projects, stores, and subagent transcripts. `swarm` and `orchestrate` spawn isolated workers by default. |
48
+ | Subagent types | `generalPurpose` (default), `poteto-agent` (a background delegate that reads poteto-mode first), `ci-watcher` (a background PR check watcher), `comment-sicko`, and `thermo-nuclear-code-quality-review`, the last four read from `agents/*.md`. Unknown types fail with the list. |
49
+ | Models | A Pi model spec such as `claude-opus-5-5:xhigh` or `xai/grok-4.7:high`, model parameters such as `claude-opus-5-5[effort=high]` or `grok-4.7[fast=true]` (picks a listed `-fast` id of that model), `fast` for the parent model's listed `-fast` variant when it has one, else the parent model, or `inherit`, `inherit-parent`, `auto`, or nothing for the parent model. A model Pi does not list, or a thinking level the model does not support, fails with the allowed list. A bare model id that several providers offer resolves to the parent chat's provider when it offers that id, and otherwise fails with the choices. Older rules that wrote `claude-opus-5-5-xhigh` still resolve. The system prompt's `pstack_subagents` section lists the types, the parent model, and the models with their levels. |
50
+ | Isolated workers (`environment: "cloud"`) | The subagent runs in a new git worktree at `<pstack dir>/workspaces/<workspace folder>/worktrees/<agent id>` on branch `pstack/<agent id>`, from `cloud_base_branch` (`origin/<branch>` when that remote-tracking ref exists, else a local branch or other ref) or your HEAD commit, so uncommitted changes do not reach it. The result and the card name the branch to merge, and `resume` and `fork` run in the same worktree. It fails clearly outside a git repository, in a repository with no commits, or when the base does not resolve. The worker shares the root chat's workspace state, so it sees the same projects, stores, and subagent transcripts. It also shares this machine's ports, processes, writable caches, simulators, and credentials. Assign separate ports, output directories, and writable caches to concurrent runtime lanes. `swarm` and `orchestrate` spawn worktree workers by default. |
51
51
  | `readonly: true` | The child gets `read`, `grep`, `find`, `ls`, `TodoWrite`, and a read-only `bash`, and no MCP. That shell runs each command in an OS sandbox ([`@anthropic-ai/sandbox-runtime`](https://github.com/anthropics/sandbox-runtime): `sandbox-exec` on macOS, bubblewrap on Linux) that blocks every file write and all network access, so `git log` works and `touch` or `curl` fail. Where no sandbox can start, the command fails rather than running unsandboxed. |
52
52
  | `TodoWrite` | `todos` with `id`, `content`, `status`, and `merge`. The list follows the session branch and shows above the editor. |
53
53
  | `AskQuestion` | `title`, and per question `id`, `prompt`, `options` (`id`, `label`, optional `description`), and `allow_multiple`, through Pi's select dialog. Every question ends with `Other: (type to answer)`; a typed answer comes back as `other` with its text. In print and JSON runs, including subagents, each question comes back skipped. |
54
+ | Path-scoped skills | A skill whose frontmatter declares `paths` or `globs` (also under `metadata`) attaches once per session when the agent reads or edits a matching file, the way a path-scoped rule attaches. The shipped `typescript-best-practices` skill attaches on `.ts` and `.tsx` files this way. |
55
+ | Always-applied rules | The two team-kit rules are in `pstack_host` as their shipped text on every request, alongside the same rules as hidden skills. |
54
56
  | The model rule | `<pstack dir>/pstack-models.md`, plain Markdown with one line per role, added to every request's system prompt. A `pstack-models.mdc` from an earlier version is moved there once, without its frontmatter, and kept as `pstack-models.mdc.migrated`. |
55
- | Subscriptions | `subscribe_github_pr` (`scope` `pr` with `prUrl`, `repo` with `repo`, or `author` with `author` and `repo`) polls with `gh` every 60 seconds and wakes the chat when a PR's state, head, mergeability, review decision, reviews, or comments change, or when a check concludes or its conclusion changes; the first poll is a baseline. `subscribe_timer` takes a `name`, a `prompt`, and a 5-field `cron` or `delaySeconds`, either with `once`; `sessionStrategy` `wake_self` wakes this chat, and `new_session` runs each fire in a fresh background subagent. Re-subscribing an existing timer name returns `created: false`. `list_subscriptions` and `unsubscribe` manage them. Subscriptions persist in the session, restore on reload and resume, expire after 7 days unless `expiresInHours` says otherwise (at most 30 days), and stop with the Pi process. |
56
- | Webhook routines | `subscribe_webhook` (`name`, `prompt`) serves `http://127.0.0.1:<port>/routines/<slug>` (port `PSTACK_ROUTINES_PORT`, default 47913, where `0` picks a free port) and keeps a random sender key in `<pstack dir>/routines/<slug>/credentials.json` (mode 0600). The result names the URL and the file, never the key. A JSON POST with `X-Automation-Key` or `Authorization: Bearer` returns 200 and wakes the chat with a `[routine]` turn holding the prompt and a `<webhook_event>` block (`headers`, `body_digest`, `body`, `timestamp_ms`). A wrong key gets 401, an unknown routine 404, a method other than POST 405, a body over 64 KiB 413, and a body that is not a JSON object 400. `make-bot-ui` builds a page on top of this. |
57
+ | Subscriptions | `subscribe_github_pr` (`scope` `pr` with `prUrl`, `repo` with `repo`, or `author` with `author` and `repo`) polls with `gh` every 60 seconds and wakes the chat when a PR's state, head, mergeability, review decision, reviews, or comments change, or when a check concludes or its conclusion changes; the first poll is a baseline. `subscribe_timer` takes a `name`, a `prompt`, and a 5-field `cron` or `delaySeconds`, either with `once`; `sessionStrategy` `wake_self` queues a wake for this owning session. With `new_session`, a live owner starts a background Task and receives its notification. An offline owner permits a detached run with a recorded outcome. Both reuse the model, thinking level, and project-trust decision the timer was created with. Re-subscribing an existing timer name returns `created: false`. `list_subscriptions` and `unsubscribe` manage them. The authoritative registry is `<pstack dir>/durable/<workspace>/subscriptions.json`. Any session in that workspace can list or cancel subscriptions. Session history does not recreate cancelled or completed subscriptions. Owner-directed events wait for the creating session rather than waking another workspace session. A detached run records its own final message, shown by `list_subscriptions`. A timer does not expire unless `expiresInHours` says so (at most 30 days); `0` means no expiry. A GitHub watch keeps a 7-day default because it is this port's own surface. |
58
+ | Webhook routines | `subscribe_webhook` (`name`, `prompt`, optional `sessionStrategy`) serves `http://127.0.0.1:<port>/routines/<slug>` (port `PSTACK_ROUTINES_PORT`, else the port this workspace last bound, else 47913, where `0` picks a free port) and keeps a random sender key in `<pstack dir>/routines/<slug>/credentials.json` (mode 0600). The result names the URL and the file, never the key. A JSON POST with `X-Automation-Key` or `Authorization: Bearer` returns 200 after durable acceptance, and 503 with `Retry-After` while that routine already holds 20 undelivered events. With the default `wake_self`, the owner receives a `[routine]` turn holding the prompt and a `<webhook_event>` block (`headers`, `body_digest`, `body`, `timestamp_ms`). With `new_session`, each accepted POST starts a fresh detached run, the way a hosted automation runs with no chat open, and records its outcome in `list_subscriptions`. HTTP 200 does not mean the run finished. Each authenticated POST is a separate event, including identical bodies. Storage failure returns 503 rather than acknowledging a lost event, and a full delivery queue refuses the new event instead of dropping an earlier accepted one. A wrong key gets 401, an unknown routine 404, a method other than POST 405, a body over 64 KiB 413, and a body that is not a JSON object 400. A routine does not expire unless `expiresInHours` says so. `make-bot-ui` builds a page on top of this. |
59
+ | Durable supervisor | Creating a subscription auto-starts `pstack-routines-supervisor --workspace <cwd>` when it is not already running. Creation waits for that subscription to arm, and webhook creation returns the bound listener's URL. The supervisor reads the durable registry, arms timers and GitHub polls, and owns the routines port. An online owner receives native Pi continuation messages. Owner-directed events accepted while the owner is offline wait for that same session to resume. A timer or webhook routine explicitly configured with `sessionStrategy: "new_session"` starts detached work while its owner is offline. You can still start the bin yourself. For reboot survival, run `packaging/install-supervisor-unit.sh --workspace <cwd>` (idempotent; installs the launchd or systemd unit from the examples under `packaging/`). Print and JSON runs still cannot *create* subscriptions. |
57
60
  | Stores and projects | The system prompt names "the current agent's store" (per session), "the root agent's store" (the main chat's, named to every subagent however deep), and the workspace's `projects/` folder, where a program's Project is `<slug>/notes.md` (its tracker) and `<slug>/store/`. |
58
61
  | `visualize` | `title`, `kind` (`bar` or `line`), `labels`, `series` (`name`, `values`), and `unit` render a Unicode chart in the result and write a standalone SVG to the session's artifacts folder. |
59
62
  | Transcripts | The system prompt names this workspace's Pi session directory, the current session file, and this workspace's subagent transcripts. |
60
63
  | Image generation | Pi's codemode `models.generateImages()`, when codemode is on and an image model is configured; otherwise skills fall back to a Mermaid diagram. |
61
64
 
62
- Subagent transcripts are Pi sessions in `<pstack dir>/workspaces/<workspace folder>/subagents/`, with the agent ID in each file name, next to a small record that `resume`, `fork`, and `agent_status` read. Agent IDs belong to the workspace that started them. Children inherit your installed extensions and MCP servers, and local ones run in your working directory. Give each local subagent that writes its own git worktree, or launch it with `environment: "cloud"`, because a child process does not isolate the filesystem. A child exits on its own if its parent Pi process dies.
65
+ Subagent transcripts are Pi sessions in `<pstack dir>/workspaces/<workspace folder>/subagents/`, with the agent ID in each file name, next to a small record that `resume`, `fork`, and `agent_status` read. Records belong to the main chat's shared workspace state, so the root chat can act on a descendant that ran in a worktree. Children inherit your installed extensions and MCP servers, and local ones run in your working directory. Give each local subagent that writes its own git worktree, or launch it with `environment: "cloud"`, because a child process does not isolate the filesystem. A child exits on its own if its parent Pi process dies.
63
66
 
64
67
  ## Limits
65
68
 
66
- - Isolated workers run on this machine. They start from committed code only and share your installed extensions, MCP servers, and network, except that a readonly worker gets no MCP servers and no network. pi-pstack never pushes their branches, though a worker can push its own. Worktrees and branches stay until you or the worktree-cleanup playbook remove them.
67
- - Subscriptions and routines live in the Pi process that created them. Keep that session running (for example in tmux) while they matter. A GitHub subscription restored after a restart takes a new baseline, so changes made while Pi was down do not wake it. Only one Pi process can hold the routine port; set `PSTACK_ROUTINES_PORT` for a second one. The routine listener binds `127.0.0.1`, so a page served to other machines forwards through a local server, as `make-bot-ui` describes.
68
- - The Benny automation pack under `automations/benny/` runs on pi-pstack subscriptions in a long-lived Pi session and needs a Slack MCP server for its Slack steps.
69
+ - Worktree workers run on this machine, not remote VMs. They start from committed code only and share your installed extensions, MCP servers, and network, except that a readonly worker gets no MCP servers and no network. A worktree is not a credential boundary. Concurrent runtime lanes need separate ports, output directories, and writable caches. pi-pstack never pushes their branches, though a worker can push its own. Worktrees and branches stay until you or the worktree-cleanup playbook remove them.
70
+ - The supervisor can accept events after chat exit, but owner-directed events wait until the owning session resumes. Explicit `new_session` timers and webhook routines can spend tokens with no human watching. Detached runs do not supply the live owner's Task notification flow. For reboot survival, run `packaging/install-supervisor-unit.sh --workspace <cwd>`. If the supervisor cannot start, an explicitly reported in-process fallback stops when that Pi process exits. A GitHub subscription restarted after downtime takes a new baseline, so changes made while nothing was polling do not wake it. Each workspace binds its own routines port. With no explicit port, a busy default falls back to an ephemeral port that the workspace then reuses and reports, so two workspaces serve routines at once. An explicit `PSTACK_ROUTINES_PORT` is a configuration: if it is taken, the routine reports that error instead of moving. The listener binds `127.0.0.1`, so a page served to other machines forwards through a local server, as `make-bot-ui` describes.
71
+ - Event delivery is at-least-once. A process crash after Pi queues a message but before its durable acknowledgement can repeat a wake or a fresh run. Automation prompts must make external actions idempotent. Up to 20 undelivered events per subscription stay queued until delivery or cancellation, and a new event beyond that is refused rather than replacing an older accepted one. Expiry stops new occurrences rather than discarding accepted work. Failed detached executions retain their event for retry and expose the error in `list_subscriptions`. Cancelling a subscription drops pending undelivered events, but cannot undo work already handed to Pi.
72
+ - The Benny automation pack under `automations/benny/` runs on pi-pstack subscriptions. Keep its owning Pi session running for immediate handling, or resume that session to handle parked events. It needs a Slack MCP server for its Slack steps.
69
73
  - `/plan` uses `bash_readonly` for its shell rather than replacing `bash`, so your shell settings stay as they are outside plan mode.
70
- - Background subagent token usage is recorded in the child's transcript and in the `usage` field of the notification's details, but does not enter the parent session's totals: Pi 1.4.0 gives extensions no way to add usage outside a tool result.
74
+ - Background subagent token usage is recorded in the child's transcript and in the `usage` field of the notification's details, but does not enter the parent session's totals: Pi 1.5.0 has no public API for adding a background completion's usage to the parent totals. Native tool-result usage covers synchronous nested work, not later notifications.
71
75
  - `/reload`, `/new`, and Pi's `/resume` and `/fork` commands stop running background subagents, with a warning that names how many. Continue one later with `Task` `resume` and its agent ID.
72
76
  - A print or JSON run waits for its background subagents before it exits, with no timeout of its own, unless it is aborted.
73
77
  - Subagents start the same Pi script that runs the parent (`process.argv[1]`), or `pi` from `PATH`. Embedding the package through the SDK in another host is not supported.
@@ -76,21 +80,22 @@ Subagent transcripts are Pi sessions in `<pstack dir>/workspaces/<workspace fold
76
80
  - Subagents run headless in RPC mode, so an extension that asks for permission through a dialog cannot ask there.
77
81
  - The read-only shell sandboxes shell subprocesses only, and is verified on macOS and on the Ubuntu CI runner. On Linux it needs `bubblewrap`, `socat`, and `ripgrep`. Ubuntu 24.04 and later restrict unprivileged user namespaces: either add an AppArmor profile that grants `userns` to `bwrap`, or run `sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0`, which lifts the restriction for every program on the machine. The policy sets `allowAllUnixSockets: true` so Linux skips `apply-seccomp`, whose nested userns fails on GitHub-hosted runners; filesystem write denial and network denial still come from bubblewrap.
78
82
  - Whether a model follows a playbook is the model's call.
79
- - Workspace folders follow Pi's session-folder naming, so two paths that differ only by `/` versus `-` share a folder; resume and fork refuse an agent whose record names the other directory.
83
+ - Workspace folders follow Pi's session-folder naming, so two paths that differ only by `/` versus `-` share a folder. Resume and fork refuse an agent whose record names the other directory, except that a record created inside this workspace's own worktrees belongs to this workspace. Subscription reads and mutations also reject a registry owned by the other workspace.
80
84
  - The run lock that keeps one writer per agent ID names the process writing the transcript. A lock file without a readable pid counts as held; the error names the file to delete. `agent_status` shows such an agent as `idle`. A lock whose pid is dead is taken over, and two processes that take over the same stale lock at the same moment can both succeed.
81
85
  - `skills/poteto-mode/scripts/watch-pr` needs Bun and installs its one dependency on first run. `check-plan.mjs` needs only Node. `worktree-audit.sh` needs `rg` and `jq`, and works with BSD (macOS) or GNU `stat` and `date`.
82
86
  - `statechart` runs `skills/statechart/scripts/statechart` on Node 22.18 to 22.x, Node 23.6 or later, Bun 1.1.5 or later, or Deno 2.0 or later. The first run installs TLC and Java through [mise](https://mise.jdx.dev/) and needs the network. Without a SPEC it prints `Give one SPEC.tla.` and its usage, then exits 2.
83
87
 
84
88
  ## Maintain it
85
89
 
86
- The skills, agents, guide, and automation pack are this package's own files; edit them in place. They track pstack 0.15.36 in behavior. The record of the move to Pi-native text and the host features added for it is at the repository root, in `parity/pi-pstack-0.15.36/` (design and decisions); earlier records are in the root's `parity/pi-pstack-*`. Four mechanisms Pi 1.4.0 does not export keep a hand-written implementation here: [`docs/native-gaps.md`](docs/native-gaps.md) names each one, the evidence, and the public API that would replace it.
90
+ The skills, agents, guide, and automation pack are this package's own files; edit them in place. They track pstack 0.15.36 in behavior. [`docs/parity-status.md`](docs/parity-status.md) records what was reconstructed, what each host translation uses, and which differences remain host-bound. The record of the move to Pi-native text and the host features added for it is at the repository root, in `parity/pi-pstack-0.15.36/` (design and decisions); earlier records are in the root's `parity/pi-pstack-*`. Four mechanisms Pi 1.5.0 does not export keep a hand-written implementation here: [`docs/native-gaps.md`](docs/native-gaps.md) names each one, the evidence, and the public API that would replace it.
87
91
 
88
92
  ```sh
89
93
  bun run typecheck
90
94
  bun run test
95
+ bun run test:statechart # full compiler suite; set TLA2TOOLS and JAVA for an existing verified toolchain
91
96
  bun run live:e2e -- --model deepseek/deepseek-flash # real-model scenarios; needs credentials and gives the model under test your real auth.json (see the script header)
92
97
  ```
93
98
 
94
99
  ## Licenses
95
100
 
96
- The port's own code is MIT (`LICENSE`). pstack's files keep their MIT notice in `licenses/pstack.LICENSE` (© 2026 Lauren Tan), and the `deslop`, `control-cli`, and `control-ui` skills keep theirs in `licenses/cursor-team-kit.LICENSE`.
101
+ The port's own code is MIT (`LICENSE`). pstack's files keep their MIT notice in `licenses/pstack.LICENSE` (© 2026 Lauren Tan), and the vendored team-kit skills and agents keep theirs in `licenses/cursor-team-kit.LICENSE`.
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: ci-watcher
3
+ description: Watch PR CI for the current branch and report pass/fail with relevant failure links. Use when waiting for CI results or CI has failed. Use proactively to monitor branch CI.
4
+ model: fast
5
+ is_background: true
6
+ ---
7
+
8
+ # CI watcher
9
+
10
+ CI monitoring specialist for PR-attached checks.
11
+
12
+ ## Trigger
13
+
14
+ Use when waiting for CI results, CI has failed, or when proactively monitoring branch CI.
15
+
16
+ ## Workflow
17
+
18
+ 1. Determine current branch: `git branch --show-current`
19
+ 2. Resolve the PR: `gh pr view --json number,url,headRefName`
20
+ 3. Inspect attached checks: `gh pr checks --json name,bucket,state,workflow,link`
21
+ 4. If checks are pending, watch: `gh pr checks --watch --fail-fast`
22
+ 5. If a GitHub Actions check failed, fetch logs with `gh run view <run-id> --log-failed`; otherwise, return the check link and concise next step.
23
+
24
+ ## Output
25
+
26
+ - CI status (passed/failed)
27
+ - PR and check metadata
28
+ - If failed: concise failure excerpt or external check link and likely next step
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: thermo-nuclear-code-quality-review
3
+ description: Thermo-nuclear code quality audit (maintainability, structure, 1k-line rule, spaghetti, code-judo). Invoked via Task after a parent gathers diff and file contents. Loads the rubric from the `thermo-nuclear-code-quality-review` skill shipped with pi-pstack.
4
+ ---
5
+
6
+ # Thermo-Nuclear Code Quality Review
7
+
8
+ You are a **Task subagent**. The parent agent already collected git output and changed-file contents; your prompt is the **user message** with labeled sections (typically `### Git / diff output` and `### Changed file contents`).
9
+
10
+ ## Rubric
11
+
12
+ 1. Load the `thermo-nuclear-code-quality-review` skill (shipped with pi-pstack) and treat its `SKILL.md` as the **complete** rubric — tone, approval bar, output ordering, code-judo / 1k-line / spaghetti rules.
13
+ 2. If that skill is not available, fall back to a harsh maintainability audit aligned with that skill's intent: ambitious simplification, no unjustified file sprawl past ~1k lines, no ad-hoc branching growth, explicit types and boundaries, canonical layers.
14
+
15
+ ## Work
16
+
17
+ - Apply the rubric **only** to what the diff and contents show. Trace cross-file impact when the change touches module boundaries.
18
+ - Output in the **priority order** the rubric specifies. Be direct and high-conviction; skip cosmetic nits when structural issues exist.
19
+ - Do **not** spawn nested subagents unless the user or parent explicitly asks.
20
+
21
+ ## Parent orchestration
22
+
23
+ Typical flow: collect `git diff <base>...HEAD` (default base `main`) and the full contents of changed files with this chat's own tools (`bash`, `read`, `grep`), or with one `Task` `generalPurpose` / `poteto-agent` helper when the tree is large. Then invoke this agent with `subagent_type: "thermo-nuclear-code-quality-review"` and a user prompt containing `### Git / diff output` and `### Changed file contents`. Do not invent Task types such as `shell` or `explore`; only the types in `<pstack_subagents>` exist.
Binary file
@@ -31,6 +31,7 @@ i want two pi automations that work together in one slack issue channel. each on
31
31
  - i want this entire pack committed at `.pi/automations/benny/` in the target repository. its `SKILL.md` files are direct automation instructions, not skills pi discovers.
32
32
  - i want pi-pstack installed in pi for shared dependencies such as `how`, `why`, `tdd`, `unslop`, and the required principle skills.
33
33
  - i want each subscription prompt to read its operational file directly from the target repository. i do not want pi-pstack package paths, copied excerpts, or slash-skill discovery.
34
+ - i want the pack and every referenced secret-free configuration file committed on the branch the owning session has checked out before either subscription is enabled, so no subscription runs instructions i have not reviewed.
34
35
  - i keep user-owned configuration, feature maps, routing maps, memory, and secrets outside `.pi/automations/benny/` so pack refreshes cannot overwrite them.
35
36
  - i want durable memory as files in one directory setup names, such as `~/.pi/agent/pstack/automations/benny/memories/`: a claim ledger so no report runs twice, and short operating notes.
36
37
  - i want both automations to fail closed when channel coordinates, tracker access, the control adapter, or the feature map are missing or uncertain.
@@ -73,7 +74,7 @@ i want this setup session to run in the target repository, so a fresh subagent i
73
74
 
74
75
  if pi-pstack or any shared dependency does not resolve, stop and explain what failed. do not move `.pi/automations/benny/skills/` into `.pi/skills/` or expect its files to appear in the slash-skill list.
75
76
 
76
- tell me that subscriptions live in the pi process: the session that creates them must keep running, for example in tmux, and they expire after seven days unless renewed. do not create or update a subscription until i explicitly ask.
77
+ tell me that creating a subscription auto-starts `pstack-routines-supervisor`, that Benny's owner-session wakes park while this chat is offline until the same session is restored, and that a timer or webhook routine runs with no chat open only when it is created with `sessionStrategy: "new_session"`, in which case the run must do the work itself instead of handing it to a background `Task`. routines do not expire unless `expiresInHours` names a limit. for reboot survival, ask whether to run `packaging/install-supervisor-unit.sh --workspace <target-repository>` and run it when i say yes. do not create or update a subscription until i explicitly ask.
77
78
 
78
79
  for first-time creation, create the triage subscription first, check it with `list_subscriptions`, and confirm one harmless fire before creating the repro subscription.
79
80
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  benny gives you two pi automations for slack issue reports. one triages each report. the other reproduces confirmed bugs and may prepare a small draft fix.
4
4
 
5
- a pi automation is a pi-pstack subscription in a long-running pi session rooted in the target repository. a scheduled sweep is `subscribe_timer` with a cron schedule. each fire wakes that session, which hands one report to a background subagent on the automation's model. a push trigger is `subscribe_webhook`: a slack app or relay you run posts each new report to the local routine url. slack reads and writes go through a slack mcp server configured in pi (`pi mcp list`). durable memory is plain files in a directory setup names, such as `~/.pi/agent/pstack/automations/benny/memories/`.
5
+ a pi automation is a pi-pstack subscription rooted in the target repository. creating one auto-starts `pstack-routines-supervisor`. owner-session wakes received while the setup chat is offline park until that same session is restored. a scheduled sweep is `subscribe_timer` with a cron schedule. each fire hands one report to a background subagent on the automation's model. a push trigger is `subscribe_webhook`: a slack app or relay you run posts each new report to the local routine url. slack reads and writes go through a slack mcp server configured in pi (`pi mcp list`). durable memory is plain files in a directory setup names, such as `~/.pi/agent/pstack/automations/benny/memories/`.
6
6
 
7
7
  the files in this directory are dormant setup and automation sources. they do not appear as slash skills.
8
8
 
@@ -17,4 +17,4 @@ the files in this directory are dormant setup and automation sources. they do no
17
17
 
18
18
  ## what differs from hosted automations
19
19
 
20
- subscriptions live in the pi process. they run only while the session that created them runs, so keep it open, for example in tmux, and keep the machine awake. they come back when you resume that session after a restart. they expire after seven days by default: `list_subscriptions` shows `expiresAt`, and renewing one means `unsubscribe`, then subscribing again with the same name. a webhook routine listens on `127.0.0.1` only, so slack cannot reach it directly. a push trigger needs a relay you run.
20
+ creating a subscription auto-starts `pstack-routines-supervisor`. keep the owning pi session running for immediate handling, or restore it later to drain parked wakes. a timer or webhook routine explicitly configured with `sessionStrategy: "new_session"` starts detached work while its owner is offline, the way a hosted automation runs with no chat open; a detached run is one-shot, so it does the work itself instead of handing it to a background `Task`. for reboot survival, run `packaging/install-supervisor-unit.sh --workspace <target-repository>` (idempotent launchd or systemd user unit). if the supervisor cannot start, subscriptions still arm in the creating pi process and stop when that process exits, so tmux still works as a fallback. routines do not expire unless `expiresInHours` names a limit, which `list_subscriptions` reports; changing one means `unsubscribe`, then subscribing again with the same name. each workspace gets its own listener port, so two repositories can both serve routines. a webhook routine listens on `127.0.0.1` only, so slack cannot reach it directly. a push trigger needs a relay you run.
@@ -15,7 +15,7 @@ Benny needs external configuration and two live automations. Each automation is
15
15
  - Timer mode (default): `subscribe_timer` with a 5-field `cron`. Each fire wakes the owning session, which sweeps the source channel, claims one report, and hands it to one background `Task` on the automation's configured model. The run's result arrives as a Task notification.
16
16
  - Webhook mode: `subscribe_webhook`. Each POST to the local routine URL wakes the owning session with a `[routine]` turn, and the prompt hands the event to one background `Task` on the automation's configured model.
17
17
 
18
- Subscriptions live in the Pi process. They stop when that session exits, return when it is resumed, and expire (default seven days). Tell the user this before creating anything.
18
+ Creating a subscription auto-starts `pstack-routines-supervisor`. Benny's `wake_self` timers and webhook routines target the owning session. While it is offline, wakes park durably until that session is restored. A timer or webhook routine created with `sessionStrategy: "new_session"` instead starts a fresh detached run per fire, with no chat open, and records each outcome in `list_subscriptions`. Routines do not expire unless `expiresInHours` names a limit. Tell the user this before creating anything. For fires that survive a reboot, ask whether to run `packaging/install-supervisor-unit.sh --workspace <target-repository>` (resolve the script from the pi-pstack package root) and run it when they say yes.
19
19
 
20
20
  Do not create or update a subscription until the user explicitly asks. Never put a secret value in pack files, prompts, memory, or committed configuration.
21
21
 
@@ -38,7 +38,7 @@ If this file is already being read from the target destination, treat the copy a
38
38
 
39
39
  Confirm that pi-pstack is installed in Pi. A personal install (`pi install <path-to-pi-pstack>`) serves every repository on this machine. A project install (`pi install -l <path-to-pi-pstack>`) writes the target repository's `.pi/settings.json`; preserve every unrelated setting and package entry when that file exists, and validate it after editing.
40
40
 
41
- This setup session must run in the target repository, because a `Task` subagent starts in its parent's working directory. If it does not, stop and tell the user to start Pi in the target repository and point it at `FOR_AGENTS.md` again. Start a fresh `Task` subagent with `readonly: true`. Ask it which of these skills its system prompt lists as installed:
41
+ This setup session must run in the target repository, because a `Task` subagent starts in its parent's working directory. If it does not, stop and tell the user to start Pi in the target repository and point it at `FOR_AGENTS.md` again. Start a fresh `Task` subagent with `readonly: true`. Ask it to resolve `<SKILLS>` from its own system prompt and read each required `<SKILLS>/<name>/SKILL.md` file below. It must report each absolute path and confirm the file's frontmatter names the required skill. Hidden skills need not appear in Pi's automatic skill catalog.
42
42
 
43
43
  - `how`
44
44
  - `why`
@@ -51,7 +51,7 @@ This setup session must run in the target repository, because a `Task` subagent
51
51
  - `principle-fix-root-causes`
52
52
  - `principle-prove-it-works`
53
53
 
54
- Do not count a skill this session read by hand. The check must show that a fresh subagent in the target repository receives pi-pstack.
54
+ Do not count a skill this session read by hand or a catalog listing alone. The check must show that a fresh subagent in the target repository receives pi-pstack's host instructions and can read every required installed file.
55
55
 
56
56
  If pi-pstack or any shared dependency does not resolve, stop and explain the failure.
57
57
 
@@ -59,7 +59,7 @@ Confirm that `pi mcp list` shows the Slack MCP server the user names, and that i
59
59
 
60
60
  The Benny files are read directly from `<target-repository>/.pi/automations/benny/`. Do not move that directory into `.pi/skills/` or expect its `SKILL.md` files to appear in the slash-skill list.
61
61
 
62
- Tell the user to commit `.pi/automations/benny/` and any referenced secret-free configuration, so pack refreshes stay reviewable. Do not commit them unless the user asks.
62
+ Tell the user that `.pi/automations/benny/`, any referenced secret-free configuration, and, for a project install, the target repository's `.pi/settings.json` must be committed on the branch the owning session has checked out before either subscription is enabled. A subscription reads the working tree, so an uncommitted instruction would run unreviewed. Do not commit them unless the user asks.
63
63
 
64
64
  Once this check passes, subscription prompts read the operational files by their absolute paths in the target checkout. They must not embed a pi-pstack package path or copy the file contents.
65
65
 
@@ -169,7 +169,7 @@ Ask whether this is first-time creation or configuration of existing subscriptio
169
169
 
170
170
  Read `../../FOR_AGENTS.md` from the copied pack as the primary user-intent source for either path. Use it to understand the two triggers, tools, instructions, outcomes, and shared rules.
171
171
 
172
- The owning session must be interactive or RPC, rooted in the target repository, and expected to keep running. A print or JSON run cannot own a subscription. Suggest a dedicated tmux window, such as `tmux new -s benny 'cd <target-repository> && pi'`. In both trigger modes, each fire hands its report to one background `Task` with the automation's configured `model`, so one owning session serves both automations, whatever model that session runs on.
172
+ The owning session must be interactive or RPC and rooted in the target repository. A print or JSON run cannot create a subscription. Creating the subscription auto-starts `pstack-routines-supervisor`. Keep the owning session available for prompt handling, or restore it later to drain parked wakes. In both trigger modes, each delivered fire hands its report to one background `Task` with the automation's configured `model`. After both subscriptions exist, ask whether to install the reboot unit with `packaging/install-supervisor-unit.sh --workspace <target-repository>`, and run it only when the user says yes.
173
173
 
174
174
  ### First-time creation
175
175
 
@@ -183,7 +183,8 @@ For each automation:
183
183
  4. Show the user the subscription name, mode, schedule, and full prompt, and get approval.
184
184
  5. Create the subscription:
185
185
  - Timer mode: `subscribe_timer` with `name`, `prompt`, and `cron`. Keep the default `sessionStrategy`, `wake_self`, so each fire reaches the owning session, which starts the `Task` on the configured model.
186
- - Webhook mode: `subscribe_webhook` with `name` and `prompt`. It returns the local URL (`http://127.0.0.1:<port>/routines/<slug>`, where the slug is the kebab-case form of the name) and the credential file path. The key stays in that file. Never print it, paste it into chat, or copy it into the configuration.
186
+ - Webhook mode: `subscribe_webhook` with `name` and `prompt`. Keep the default `sessionStrategy`, `wake_self`, for the same handoff. It returns the local URL (`http://127.0.0.1:<port>/routines/<slug>`, where the slug is the kebab-case form of the name) and the credential file path. The key stays in that file. Never print it, paste it into chat, or copy it into the configuration.
187
+ - Only when the user asks for the automations to run with no chat open, pass `sessionStrategy: "new_session"` and replace the background `Task` handoff in the prompt with the same work performed in that run, because a detached run ends when it settles and takes any background child with it. State that tradeoff before switching.
187
188
  6. Run `list_subscriptions`. Confirm the subscription appears with the expected name, schedule or URL, and `expiresAt`. A result of `created: false` means a subscription with that name already exists and kept its old prompt; follow the existing-subscription path instead.
188
189
  7. Finish the thread-safety checks that apply to this automation before creating the next one.
189
190
 
@@ -225,11 +226,11 @@ Finish configuration, routing, control-adapter, and feature-map validation. Then
225
226
  3. Create it again with the same name and the new prompt. A webhook routine keeps its slug, so check whether its URL or credential file changed and tell the user to update the relay.
226
227
  4. Run `list_subscriptions` and confirm exactly one subscription per name.
227
228
 
228
- To renew an expiring subscription, do the same with the unchanged prompt. Do not create replacements under new names.
229
+ To renew a subscription created with `expiresInHours`, do the same with the unchanged prompt. Do not create replacements under new names.
229
230
 
230
231
  ### Creation boundary
231
232
 
232
- Create Benny's automations only with pi-pstack's `subscribe_timer` and `subscribe_webhook` tools in the owning session. Never start a scheduler outside Pi, such as `cron` or `launchd`, and never expose the routine listener beyond `127.0.0.1` from this setup.
233
+ Create Benny's automations only with pi-pstack's `subscribe_timer` and `subscribe_webhook` tools in the owning session. Do not invent a separate `cron` or `launchd` schedule for Benny. The opt-in `packaging/install-supervisor-unit.sh` install is the reboot path for the durable supervisor, not a Benny schedule. Never expose the routine listener beyond `127.0.0.1` from this setup.
233
234
 
234
235
  Do not let either automation handle normal traffic until the thread-safety test passes.
235
236
 
@@ -237,7 +238,7 @@ Do not let either automation handle normal traffic until the thread-safety test
237
238
 
238
239
  Create both subscriptions with a test channel as the source channel, or test with a harmless report.
239
240
 
240
- Before testing, confirm that both subscription prompts point at their exact operational files in the target checkout, that the memory directory exists, and that `list_subscriptions` shows both subscriptions. If any check fails, stop. Tell the user that the automation cannot handle traffic yet.
241
+ Before testing, confirm that the target repository's `.pi/automations/benny/`, every referenced secret-free configuration file, and, for a project install, its `.pi/settings.json` are committed on the branch the owning session has checked out. Confirm that both subscription prompts point at their exact committed operational files in the target checkout, that the memory directory exists, and that `list_subscriptions` shows both subscriptions. If any check fails, stop. Tell the user that the automation cannot handle traffic yet.
241
242
 
242
243
  Verify:
243
244
 
@@ -17,7 +17,7 @@ Memory: `{{BENNY_MEMORY_DIR}}`. Read `MEMORIES.md` first. Record claims only in
17
17
  Trigger, timer mode (this session woke from a `subscribe_timer` fire):
18
18
 
19
19
  1. If `ledger.tsv` has a `benny-reproduce` claim with no `done` line from the last `{{REPRO_PLUS_FIX_MINUTES}}` minutes, another repro run owns the app. End the turn and post nothing.
20
- 2. Read top-level messages in the configured source Slack channel from the last `{{SWEEP_LOOKBACK_MINUTES}}` minutes. Skip every message `ledger.tsv` lists as `done` for `benny-reproduce`. A `claimed` line older than `{{REPRO_PLUS_FIX_MINUTES}}` minutes with no `done` line belongs to a run that died, so retry that message.
20
+ 2. Read top-level messages in the configured source Slack channel, starting `{{SWEEP_LOOKBACK_MINUTES}}` minutes ago or from the last successful sweep time recorded in `MEMORIES.md` when that is earlier, so reports posted while the session was offline are still seen. Skip every message `ledger.tsv` lists as `done` for `benny-reproduce`. A `claimed` line older than `{{REPRO_PLUS_FIX_MINUTES}}` minutes with no `done` line belongs to a run that died, so retry that message. Record this sweep's start time in `MEMORIES.md` when the sweep ends.
21
21
  3. Take the oldest remaining message whose thread holds a triage marker from the configured triage identity, or whose age exceeds the verdict wait. If none remain, end the turn with `no report ready` and post nothing. A newer message without a marker waits for a later fire.
22
22
  4. Append a `claimed` line for it to `ledger.tsv` before any other work. Handle only this one report.
23
23
  5. Start one background `Task` with `model: "{{REPRODUCE_MODEL}}"` whose prompt is this prompt with this trigger block replaced by this trigger JSON, and do no other work in this session:
@@ -16,7 +16,7 @@ Memory: `{{BENNY_MEMORY_DIR}}`. Read `MEMORIES.md` first. Record claims only in
16
16
 
17
17
  Trigger, timer mode (this session woke from a `subscribe_timer` fire):
18
18
 
19
- 1. Read top-level messages in the configured source Slack channel from the last `{{SWEEP_LOOKBACK_MINUTES}}` minutes with the configured Slack read tool.
19
+ 1. Read top-level messages in the configured source Slack channel with the configured Slack read tool, starting `{{SWEEP_LOOKBACK_MINUTES}}` minutes ago or from the last successful sweep time recorded in `MEMORIES.md` when that is earlier, so reports posted while the session was offline are still seen. Record this sweep's start time in `MEMORIES.md` when the sweep ends.
20
20
  2. Skip every message that `ledger.tsv` lists as `done` for `benny-triage`, and every message with a `claimed` line from the last `{{TRIAGE_TOTAL_MINUTES}}` minutes. A `claimed` line older than that with no `done` line belongs to a run that died, so retry that message.
21
21
  3. Take the oldest remaining message. If none remain, end the turn with `no new report` and post nothing.
22
22
  4. Append a `claimed` line for it to `ledger.tsv` before any other work. Handle only this one report. The next fire takes the next one.
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ # Starts the durable routines supervisor for one workspace.
3
+ set -euo pipefail
4
+ here="$(cd "$(dirname "$0")" && pwd)"
5
+ entry="$here/../src/subscriptions/supervisor-main.ts"
6
+ if command -v bun >/dev/null 2>&1; then
7
+ exec bun "$entry" "$@"
8
+ fi
9
+ exec node --experimental-strip-types "$entry" "$@"
@@ -13,7 +13,7 @@ bun install
13
13
  pi install ./extensions/pi-pstack
14
14
  ```
15
15
 
16
- `pi install <path-to-pi-pstack>` loads the package in place, so keep the checkout where it is. To install the published package instead, run `pi install npm:@justjoshn/pi-pstack`. Start Pi, and the pstack commands appear in the `/` menu.
16
+ `pi install <path-to-pi-pstack>` loads the package in place, so keep the checkout where it is. To install the published package instead, run `pi install npm:@justjoshn/pi-pstack`. On Linux, the read-only shell also needs `bubblewrap`, `socat`, and `ripgrep`, as the package README explains. Start Pi, and the pstack commands appear in the `/` menu.
17
17
 
18
18
  ## Pick your models
19
19
 
@@ -23,7 +23,7 @@ Run:
23
23
  /setup-pstack
24
24
  ```
25
25
 
26
- [`/setup-pstack`](../../skills/setup-pstack/SKILL.md) detects the models you have access to, asks for a reasoning budget, shows you each role (code delegates, judgment, the review panels), and asks what you want. Answer the questions. It writes `~/.pi/agent/pstack/pstack-models.md`, the model rule. pi-pstack adds it to every request, so every pstack skill reads it.
26
+ [`/setup-pstack`](../../skills/setup-pstack/SKILL.md) detects the models you have access to, asks for a reasoning budget, shows you each role (code delegates, judgment, the review panels), and asks what you want. Answer the questions. It writes `~/.pi/agent/pstack/pstack-models.md` (`$PI_CODING_AGENT_DIR/pstack/pstack-models.md` when that variable is set), the model rule. pi-pstack adds it to every request, so every pstack skill reads it.
27
27
 
28
28
  The defaults run at `xhigh` reasoning, the same as the `large` budget. `unlimited` lifts each model to its highest tier, up to `max`. Opus goes to `max`. Grok tops out at `xhigh`, so it stays there. `medium` and `small` lower the reasoning and spend fewer tokens.
29
29
 
@@ -115,7 +115,7 @@ Every loop above still waits for you to start it. A scheduled or event-driven au
115
115
  - Every stage can stop the line. Triage can decide the report is expected behavior, repro can fail to reproduce it, and the fixer can judge the change too risky. Each of those outcomes is useful, because it keeps bad work from reaching the next stage, where it costs more to undo.
116
116
  - Every stage hands over evidence. Repro attaches screenshots and video of the broken state, and the fix attaches before-and-after proof. A human can then check that the agent fixed the right thing before reading a line of code.
117
117
 
118
- pstack ships this as a dormant [automation pack](../../automations/benny/README.md) for Slack issue reports. It runs as subscriptions in a Pi session you keep running. One automation triages each report. The other reproduces confirmed bugs and may prepare a small draft fix. Point an agent at its [`FOR_AGENTS.md`](../../automations/benny/FOR_AGENTS.md) and name the target repository to set it up.
118
+ pstack ships this as a dormant [automation pack](../../automations/benny/README.md) for Slack issue reports. It runs as pi-pstack subscriptions. Creating a subscription auto-starts `pstack-routines-supervisor` for the workspace. Benny's timers and webhook routines target their owning Pi session. Keep it running for immediate handling, or resume it later to drain parked events. Only timers explicitly configured with `sessionStrategy: "new_session"` start detached work while their owner is offline. For reboot survival, run [`packaging/install-supervisor-unit.sh`](../../packaging/install-supervisor-unit.sh) `--workspace <repo>` (it writes the launchd or systemd unit from the examples under `packaging/`). One automation triages each report. The other reproduces confirmed bugs and may prepare a small draft fix. Point an agent at its [`FOR_AGENTS.md`](../../automations/benny/FOR_AGENTS.md) and name the target repository to set it up.
119
119
 
120
120
  **Pitfall:** a duration is not a finish condition. "work on this for 4 hours" gives the agent nothing to check, and you'll wake up to four hours of motion instead of a result. Give `/loop` a predicate that can pass or fail.
121
121
 
@@ -93,7 +93,7 @@ Read every output yourself before accepting the verdict. If you disagree with th
93
93
 
94
94
  ## Build a bot UI with `/make-bot-ui`
95
95
 
96
- One situational skill is for driving a Pi session from a page. [`/make-bot-ui`](../../skills/make-bot-ui/SKILL.md) builds a small page whose buttons wake this Pi session through a pi-pstack webhook routine (`subscribe_webhook`). For example, you could swipe through a review queue and have each swipe ask the session to act on that item. The session that owns the routine is the bot, so keep it running. A server on your machine holds the webhook's sender key, so the key never reaches the browser or the chat. The skill also covers exposing the page on Tailscale.
96
+ One situational skill is for driving agent work from a page. [`/make-bot-ui`](../../skills/make-bot-ui/SKILL.md) builds a small page whose buttons POST to a pi-pstack webhook routine (`subscribe_webhook`). For example, you could swipe through a review queue and have each swipe ask an agent to act on that item. Creating the subscription auto-starts `pstack-routines-supervisor`. Keep the owning Pi session running for immediate handling. Events accepted while it is offline wait until that same session resumes. For reboot survival, run `packaging/install-supervisor-unit.sh --workspace <repo>`. A server on your machine holds the webhook's sender key, so the key never reaches the browser or the chat. The skill also covers exposing the page on Tailscale.
97
97
 
98
98
  **Pitfall:** don't edit a skill mid-task because it's misbehaving. Fix it in its own PR and keep the task moving. A skill edit that ships tangled into feature work is invisible to review and impossible to evaluate.
99
99
 
@@ -158,7 +158,7 @@ That's the whole prompt. [`/bro`](../../skills/bro/SKILL.md) restates the last m
158
158
 
159
159
  [`/ketchup`](../../skills/ketchup/SKILL.md) tells you in plain words what changed since your last message, then lists each call waiting on you with its pick, evidence, and risk. It changes nothing while it reports.
160
160
 
161
- To watch the catch-up instead, run [`/ketchup-video`](../../skills/ketchup-video/SKILL.md). It shows what happened in a short subtitled video of slides, and lists the calls waiting on you as text under it.
161
+ To watch the catch-up instead, run [`/ketchup-video`](../../skills/ketchup-video/SKILL.md). It opens a short subtitled video of slides in the system player, shows one frame per slide in chat, and lists the calls waiting on you as text under it.
162
162
 
163
163
  ## The pitfalls
164
164
 
@@ -1,8 +1,13 @@
1
1
  # Pi native gaps
2
2
 
3
- pi-pstack uses Pi's public extension API. Four mechanisms have no public equivalent in Pi 1.4.0, so the package
4
- implements them itself against documented lower-level surfaces. Each entry names what Pi lacks, what the package does
5
- instead, and what would let the copy go. Pi line numbers refer to `@earendil-works/pi-coding-agent@1.4.0`.
3
+ pi-pstack uses Pi's public extension API. Four mechanisms have no public equivalent in Pi 1.5.0, so the package
4
+ implements them itself against documented lower-level APIs. Each entry names what Pi lacks, what the package does
5
+ instead, and what would let the copy go.
6
+
7
+ The Pi 1.5.0 audit checked `docs/extensions.md`, `docs/rpc.md`, `docs/rpc-extension-ui.md`,
8
+ `dist/index.d.ts`, `dist/modes/rpc/rpc-client.d.ts`, and the corresponding installed implementations.
9
+ Pi paths below refer to `@earendil-works/pi-coding-agent@1.5.0`. Legacy line coordinates are retained
10
+ as search hints from the 1.4.0 audit, not as 1.5.0 line references. This is a contract audit, not a new runtime-test receipt.
6
11
 
7
12
  ## 1. The subagent process
8
13
 
@@ -12,7 +17,7 @@ instead, and what would let the copy go. Pi line numbers refer to `@earendil-wor
12
17
 
13
18
  - **Spawn shape.** `start()` runs `spawn("node", [cliPath, ...args], { cwd, env, stdio })` (`:42`) with no spawn
14
19
  options and a single script path. pi-pstack starts `[process.execPath, script]`, `[process.execPath]`, or `pi` from
15
- `PATH` (`src/task/tool.ts:105-109`), and `RpcClientOptions` accepts none of those.
20
+ `PATH` (`src/pi-command.ts:9-11`), and `RpcClientOptions` accepts none of those.
16
21
  - **Process-group kill.** The runner spawns with `detached: true` (`src/task/runner.ts:253`) and signals `-pid`
17
22
  (`signalGroup`, `:202-210`), so abort and session shutdown stop the child and everything it started. `stop()` signals
18
23
  the direct child pid only, SIGTERM then SIGKILL after 1 s (`rpc-client.js:94`), and there is no `detached` option.
@@ -24,7 +29,7 @@ instead, and what would let the copy go. Pi line numbers refer to `@earendil-wor
24
29
  listeners and offers no way to send the response; its `send` is private.
25
30
  - **stderr and exit.** The runner keeps the last 8 KiB of stderr and reports the child's exit code in the outcome
26
31
  (`outcomeOf`, `:226-234`). `RpcClient` appends stderr without a bound and mirrors it into the parent's stderr
27
- (`rpc-client.js:49-52`), and exposes neither an exit promise nor a code.
32
+ (`rpc-client.js:49-52`), and exposes neither an exit promise nor a code. Pi 1.5.0 does reject pending requests on process exit and startup errors, but that does not expose a successful exit outcome.
28
33
 
29
34
  `RpcClient` does cover the rest of what the runner sends: `prompt(message, images, "steer")`, `abort`, `clearQueue`,
30
35
  `followUp`, and the event stream. The runner speaks the JSONL protocol from `docs/rpc.md` directly and keeps its own
@@ -36,14 +41,15 @@ thin adapter over `RpcClient`.
36
41
 
37
42
  ## 2. The skill block `/mode` sends
38
43
 
39
- `modeSection` (`src/mode.ts:52-58`) builds the block Pi's own `/skill:name` expansion builds
44
+ `modeSection` (`src/mode.ts:52-58`) strips frontmatter with Pi's exported `stripFrontmatter` (`dist/index.d.ts:50`) and
45
+ wraps the body in the `<skill name="..." location="...">` block Pi's own `/skill:name` expansion builds
40
46
  (`AgentSession._expandSkillCommand`, `dist/core/agent-session.js:1739`). Pi exports `parseSkillBlock` and
41
47
  `formatSkillsForPrompt` (`dist/index.d.ts:3,23`), which parse a block and format the skills catalog; neither formats
42
48
  one skill block. **When this goes away.** Pi exports the block formatter and `mode.ts` calls it.
43
49
 
44
50
  ## 3. The `fork_turns` truncation
45
51
 
46
- `keepFirstTurns` (`src/task/records.ts:110-121`) rewrites the forked transcript file by hand.
52
+ `keepFirstTurns` (`src/task/records.ts:110-116`) rewrites the forked transcript file by hand.
47
53
  `SessionManager.createBranchedSession(leafId)` (`dist/core/session-manager.d.ts:365`) always mints a new session ID and
48
54
  file name (`dist/core/session-manager.js:1205-1264`), so it cannot produce the `<timestamp>_<agentId>.jsonl` that
49
55
  `SessionManager.forkFrom(..., { id: agentId })` just created. No export truncates an existing session file at an entry.
@@ -51,7 +57,7 @@ file name (`dist/core/session-manager.js:1205-1264`), so it cannot produce the `
51
57
 
52
58
  ## 4. The workspace folder name
53
59
 
54
- `workspaceSlug` (`src/paths.ts:26-35`) repeats Pi's session-folder rule (`getDefaultSessionDirPath`,
60
+ `workspaceSlug` (`src/paths.ts:44-48`) repeats Pi's session-folder rule (`getDefaultSessionDirPath`,
55
61
  `dist/core/session-manager.js:290-295`). The only public accessor is `ctx.sessionManager.getSessionDir()`
56
62
  (`ReadonlySessionManager`, `dist/core/session-manager.d.ts:178`), and it is not a substitute:
57
63