pi-subagents 0.46.0 → 0.47.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/agents/reviewer.md +3 -4
  3. package/docs/agents.md +1 -1
  4. package/docs/configuration.md +14 -6
  5. package/docs/extension-api.md +1 -1
  6. package/docs/missions.md +5 -3
  7. package/docs/models.md +3 -1
  8. package/docs/observability.md +3 -3
  9. package/docs/tool-reference.md +2 -2
  10. package/docs/watchdog.md +1 -1
  11. package/package.json +1 -1
  12. package/skills/pi-subagents/references/execution-controls.md +4 -4
  13. package/skills/pi-subagents/references/management-authoring-rpc.md +1 -1
  14. package/src/extension/config.ts +13 -0
  15. package/src/extension/fanout-child.ts +5 -4
  16. package/src/extension/index.ts +39 -14
  17. package/src/extension/rpc.ts +7 -6
  18. package/src/extension/schemas.ts +27 -7
  19. package/src/extension/tool-description.ts +26 -8
  20. package/src/inspectors/herdr/project-panes.ts +2 -1
  21. package/src/intercom/native-supervisor-channel.ts +102 -4
  22. package/src/missions/store.ts +2 -1
  23. package/src/missions/workflow-state.ts +19 -13
  24. package/src/runs/background/active-run-index.ts +42 -0
  25. package/src/runs/background/async-execution.ts +10 -5
  26. package/src/runs/background/async-job-tracker.ts +293 -133
  27. package/src/runs/background/async-resume.ts +19 -3
  28. package/src/runs/background/async-status.ts +33 -5
  29. package/src/runs/background/control-channel.ts +91 -17
  30. package/src/runs/background/result-watcher.ts +102 -7
  31. package/src/runs/background/run-status.ts +18 -0
  32. package/src/runs/background/scheduled-runs.ts +25 -1
  33. package/src/runs/background/stale-run-reconciler.ts +12 -24
  34. package/src/runs/background/subagent-runner.ts +68 -10
  35. package/src/runs/foreground/async-dismiss-action.ts +85 -0
  36. package/src/runs/foreground/async-steering-action.ts +6 -7
  37. package/src/runs/foreground/chain-execution.ts +3 -0
  38. package/src/runs/foreground/execution.ts +3 -0
  39. package/src/runs/foreground/subagent-executor.ts +129 -20
  40. package/src/runs/foreground/workflow-foreground-steering.ts +187 -0
  41. package/src/runs/shared/dynamic-fanout.ts +1 -1
  42. package/src/runs/shared/model-fallback.ts +8 -4
  43. package/src/runs/shared/model-scope.ts +12 -2
  44. package/src/runs/shared/parallel-utils.ts +1 -0
  45. package/src/runs/shared/subagent-prompt-runtime.ts +24 -5
  46. package/src/runs/shared/worktree.ts +3 -2
  47. package/src/shared/artifacts.ts +15 -15
  48. package/src/shared/display-text.ts +100 -0
  49. package/src/shared/file-coalescer.ts +9 -0
  50. package/src/shared/formatters.ts +4 -6
  51. package/src/shared/settings.ts +15 -2
  52. package/src/shared/types.ts +16 -2
  53. package/src/shared/utils.ts +43 -33
  54. package/src/slash/slash-commands.ts +3 -1
  55. package/src/tui/fleet-status.ts +14 -10
  56. package/src/tui/render.ts +30 -26
  57. package/src/watchdog/change-signature.ts +4 -3
  58. package/src/workflows/scripted-workflow.ts +4 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.47.1] - 2026-08-12
6
+
7
+ ### Fixed
8
+ - Honor configured artifact cleanup retention days and let `0` disable artifact cleanup. Thanks to @elecnix for #1012.
9
+ - Add a display-only dismiss action for reload-recovered running workflows without claiming or attempting to stop their work (#1010).
10
+ - Stop the bundled reviewer from inheriting chain-only plan/progress reads in ad-hoc review runs. Thanks to @Ostii for #1000.
11
+ - Remove mutation-capable tools from the bundled reviewer so read-only review lanes have a hard launch-time tool boundary (#1007).
12
+ - Show the requested child agent in workflow started trace entries. Thanks to @albertgwo for #1001.
13
+
14
+ ## [0.47.0] - 2026-08-11
15
+
16
+ ### Changed
17
+ - Avoid fully parsing stale cross-session result files during watcher recovery and reduce healthy watcher safety scans.
18
+ - Index active async runs so status restoration no longer scans all historical run directories.
19
+ - Refresh active async job state from filesystem events while reserving polling for slow liveness repair.
20
+ - Add optional strict model-scope enforcement that rejects inherited and fallback models outside the configured allowlist. Thanks to @antonioc-cl for #995.
21
+ - Trim legacy chain-control schema fields and guidance by default, saving 1,319 `o200k_base` tokens from the serialized default tool schema plus description versus `legacyChainControls: true`. Thanks to @tajquitgenius for #977.
22
+ - Move project-scoped pi-subagents storage from `.pi-subagents/` to `.pi/subagents/` for cleaner project roots. Thanks to @yceachan for #971.
23
+ - Clarify the accepted mission launch object contract for tool callers.
24
+ - Reduce repeated async status parsing, workflow trace projection, and constrained widget rendering work.
25
+ - Coalesce rapid running-status writes while keeping terminal and attention status changes durable immediately.
26
+ - Keep parsed async statuses cached beyond 50 runs while preserving per-read freshness checks. Thanks to @bcanvural for #982.
27
+ - Prefer native control inbox watchers over per-process 250 ms polling, with polling retained as a fallback.
28
+
29
+ ### Fixed
30
+ - Omit missing configured read files from child task instructions.
31
+ - Stabilize steering recovery budget coverage around the confirmed paused handoff.
32
+ - Keep timeout-sensitive async acceptance verification coverage off Windows CI where signal delivery is intermittent.
33
+ - Retry transient Windows mission state lock creation failures and stabilize no-session steering recovery coverage.
34
+ - Keep async widget running glyphs moving while children are quiet but active. Thanks to @bcanvural for #983.
35
+ - Let active-session workflows live-steer their workflow-owned foreground children by child or unique workflow identity. Thanks to @youlikemodernart for #988.
36
+ - Accept persisted async recovery descriptors that include internal turn-budget state fields (#985).
37
+ - Add a 30-minute default wall-clock timeout to async children while keeping async composite parents unbounded by default. Thanks to @forrestbthomas for #978 and #979.
38
+ - Keep tool argument previews on one physical terminal line so live widget updates do not leave stacked terminal frames. Thanks to @xz-dev for #972.
39
+ - Recover durable async completions when a healthy native result watcher misses a filesystem event. Thanks to @xz-dev for #973.
40
+
5
41
  ## [0.46.0] - 2026-08-11
6
42
 
7
43
  ### Added
@@ -12,6 +48,7 @@
12
48
  - Persist workflow child attempts, status heartbeats, session paths, and artifacts in their enclosing mission, and add explicit mission decision resolution.
13
49
 
14
50
  ### Fixed
51
+ - Suspend subagent status widgets during automatic compaction to prevent duplicate terminal frames.
15
52
  - Make live foreground workflow children visible as workflow-owned Fleet rows, route Herdr inspection to their workflow parent, and report their active and needs-attention state to Herdr. Thanks to @lukechen526 for #965.
16
53
  - Wait for retained-child resumes inside `workflowScript` to finish and return completed output before the script continues (#961).
17
54
  - Keep fork-context workflow children inside their managed worktree by aligning the persisted child session cwd before launch. Thanks to @flopsi for #953.
@@ -1,12 +1,11 @@
1
1
  ---
2
2
  name: reviewer
3
3
  description: Versatile review specialist for code diffs, plans, proposed solutions, codebase health, and PR/issue validation
4
- tools: read, grep, find, ls, bash, edit, write, intercom
4
+ tools: read, grep, find, ls, intercom
5
5
  thinking: high
6
6
  systemPromptMode: replace
7
7
  inheritProjectContext: true
8
8
  inheritSkills: false
9
- defaultReads: plan.md, progress.md
10
9
  ---
11
10
 
12
11
  You are a disciplined review subagent. Your job is to inspect, evaluate, and report findings with evidence. You do not guess; you verify from the code, tests, docs, or requirements.
@@ -51,9 +50,9 @@ Review a PR or issue by understanding the context, then verifying:
51
50
  - Tests and docs are updated as needed.
52
51
 
53
52
  ## Working rules
54
- - Read the plan, progress, and relevant files first when available.
53
+ - Read the relevant files first. Read plan and progress when the task supplies them.
55
54
  - Repo-local `progress.md` files are allowed scratch/memory files. Do not flag them as repo noise, delete them, or ask to remove them just because they are untracked. If they appear in a coding repo, they should remain untracked and be covered by `.gitignore`.
56
- - Use `bash` only for read-only inspection (e.g., `git diff`, `git log`, `git show`, test runs).
55
+ - Do not use shell commands or write files. Report any test or Git command that a supervisor must run.
57
56
  - Do not invent issues. Only report problems you can justify from evidence.
58
57
  - Prefer small corrective edits over broad rewrites.
59
58
  - If everything looks good, say so plainly.
package/docs/agents.md CHANGED
@@ -228,7 +228,7 @@ How it works:
228
228
 
229
229
  - `refine` collects bounded evidence from that agent's recent runs in the project (statuses, errors, review findings, residual risks, output tails), then launches a fresh read-only proposal child to draft small guidance edits from that evidence.
230
230
  - Proposed guidance is validated before it is written. Edits that try to override safety, policy, tool, output, acceptance, developer, or system instructions are rejected, as are edits that target all agents or base agent files.
231
- - The accepted overlay is stored at `.pi-subagents/refinements/<agent>.md` with revision metadata and snapshots. Each `refine` or `refine.rollback` adds a snapshot, and `refine.rollback` restores the previous revision.
231
+ - The accepted overlay is stored at `.pi/subagents/refinements/<agent>.md` with revision metadata and snapshots. Each `refine` or `refine.rollback` adds a snapshot, and `refine.rollback` restores the previous revision.
232
232
  - At launch, the current overlay is injected into that agent's child system prompt as a `<pi-subagents-refinement>` block scoped to this project. The base agent definition is never modified.
233
233
 
234
234
  `refine.show` prints the current overlay and revision history. Delete the overlay file to remove the refinement entirely.
@@ -28,6 +28,14 @@ Controls the parent-facing `subagent` tool description registered at startup. `f
28
28
 
29
29
  `custom` reads `subagent-tool-description.md` from the project config directory, then from `~/.pi/agent/subagent-tool-description.md`. Missing, empty, unreadable, or oversized custom files fall back to the full description. Custom templates may use `{{fullDescription}}`, `{{compactDescription}}`, `{{safetyGuidance}}`, `{{agentDir}}`, and `{{projectConfigDir}}`; the safety guidance is always present so custom prose cannot remove the runtime guardrails. Restart Pi after changing the mode or custom file.
30
30
 
31
+ ## `legacyChainControls`
32
+
33
+ ```json
34
+ { "legacyChainControls": true }
35
+ ```
36
+
37
+ Defaults to `false`. The default registered model-facing tool schema and description omit the legacy `append-step` `step` schema and legacy checkpoint controls. This does not change runtime support for existing durable legacy chains. Set this to `true` before directly managing a legacy chain with `append-step`, `approve-checkpoint`, or `reject-checkpoint`.
38
+
31
39
  ## `inlineToolDisplay`
32
40
 
33
41
  ```json
@@ -133,7 +141,7 @@ A user may explicitly call `subagent({ action: "grant-spawn-budget", additional:
133
141
  { "scheduledRuns": { "enabled": false, "maxPending": 20 } }
134
142
  ```
135
143
 
136
- Durable schedules are enabled by default and stored per project under `.pi-subagents/schedules/<id>/`. See [missions.md](missions.md#schedules) for usage.
144
+ Durable schedules are enabled by default and stored per project under `.pi/subagents/schedules/<id>/`. See [missions.md](missions.md#schedules) for usage.
137
145
 
138
146
  Set `storeRoot` to keep durable schedules outside project repositories. It must be an absolute path or a `~/` path, which expands from the user home directory. Each project is stored under a hash of its resolved working directory, so projects do not share schedules.
139
147
 
@@ -141,7 +149,7 @@ Set `storeRoot` to keep durable schedules outside project repositories. It must
141
149
  { "scheduledRuns": { "storeRoot": "~/.local/share/pi-subagents/schedules" } }
142
150
  ```
143
151
 
144
- When `storeRoot` is omitted, schedules remain at `<cwd>/.pi-subagents/schedules`.
152
+ When `storeRoot` is omitted, schedules remain at `<cwd>/.pi/subagents/schedules`.
145
153
 
146
154
  ## `parallel`
147
155
 
@@ -245,7 +253,7 @@ stdin is a JSON object with `repoRoot`, `worktreePath`, `agentCwd`, `branch`, `i
245
253
  {
246
254
  "missions": {
247
255
  "enabled": true,
248
- "directory": ".pi-subagents/missions",
256
+ "directory": ".pi/subagents/missions",
249
257
  "globalIndex": true,
250
258
  "retainTerminal": 200
251
259
  }
@@ -283,15 +291,15 @@ Each fixed action resolves to `"auto"`, `"confirm"`, or `"forbid"`. This is inte
283
291
 
284
292
  Controls where subagent artifact files (inputs, outputs, transcripts, metadata) are stored:
285
293
 
286
- - `"project"` (default): writes to `<cwd>/.pi-subagents/artifacts/`.
294
+ - `"project"` (default): writes to `<cwd>/.pi/subagents/artifacts/`.
287
295
  - `"session"`: stores artifacts under pi's session directory (`~/.pi/agent/sessions/<session>/subagent-artifacts/`), keeping the working directory clean.
288
296
  - `"temp"`: uses the OS temp directory.
289
297
 
290
- This preference also controls the default chain scratch directory. `"project"` uses `<cwd>/.pi-subagents/chain-runs/`, while `"session"` and `"temp"` use the user-scoped temp chain directory.
298
+ This preference also controls the default chain scratch directory. `"project"` uses `<cwd>/.pi/subagents/chain-runs/`, while `"session"` and `"temp"` use the user-scoped temp chain directory.
291
299
 
292
300
  The `"session"` option uses the same directory that `cleanupAllArtifactDirs` already scans for age-based cleanup, so artifacts are still cleaned up automatically. Temporary chain directories are cleaned up separately after 24 hours.
293
301
 
294
- When a project-scoped launch runs from an npm package directory, pi-subagents warns if package settings can include `.pi-subagents/` in the published package. Add `.pi-subagents/` to `.npmignore` (or `.gitignore` when no `.npmignore` exists), use a `files` allowlist that does not include `.pi-subagents/`, or select `"session"` or `"temp"`.
302
+ When a project-scoped launch runs from an npm package directory, pi-subagents warns if package settings can include `.pi/subagents/` in the published package. Add `.pi/subagents/` to `.npmignore` (or `.gitignore` when no `.npmignore` exists), use a `files` allowlist that does not include `.pi/subagents/`, or select `"session"` or `"temp"`.
295
303
 
296
304
  ## `completionBatch`
297
305
 
@@ -268,7 +268,7 @@ subagent({ action: "project.status", cwd: "/path/to/repo" })
268
268
  subagent({ action: "project.close", cwd: "/path/to/repo" })
269
269
  ```
270
270
 
271
- A project pane runs its own Pi session in the target directory, so subagents launched from that pane use that project's config, agents, skills, files, git state, and missions. The parent session keeps coordination authority; existing headless runs are not moved into the pane. Pane bindings live under `<projectRoot>/.pi-subagents/project-panes/herdr.json` and are only a local pointer to the Herdr pane.
271
+ A project pane runs its own Pi session in the target directory, so subagents launched from that pane use that project's config, agents, skills, files, git state, and missions. The parent session keeps coordination authority; existing headless runs are not moved into the pane. Pane bindings live under `<projectRoot>/.pi/subagents/project-panes/herdr.json` and are only a local pointer to the Herdr pane.
272
272
 
273
273
  Other Pi extensions should use the versioned public TypeScript surface instead of invoking the model-facing tool or importing inspector internals:
274
274
 
package/docs/missions.md CHANGED
@@ -11,7 +11,7 @@ Missions are durable wrappers around runs. The noun map:
11
11
  - **Run** — one actual subagent execution.
12
12
  - **Receipt** — proof or a link for an external outcome, such as a PR, CI check, deployment, or release.
13
13
 
14
- Ordinary workflow launches create one enclosing mission by default, with detailed JSON records under `<cwd>/.pi-subagents/missions/` linking objectives, run ids, lifecycle status, decisions, artifact paths, and delivery receipts. Workflow children do not create separate missions. Each workflow child attempt is stored in the enclosing mission with its stable workflow key, run id when known, agent, task metadata, timestamps, session and artifact paths, and latest status heartbeat.
14
+ Ordinary workflow launches create one enclosing mission by default, with detailed JSON records under `<cwd>/.pi/subagents/missions/` linking objectives, run ids, lifecycle status, decisions, artifact paths, and delivery receipts. Workflow children do not create separate missions. Each workflow child attempt is stored in the enclosing mission with its stable workflow key, run id when known, agent, task metadata, timestamps, session and artifact paths, and latest status heartbeat.
15
15
 
16
16
  Behavior:
17
17
 
@@ -19,7 +19,9 @@ Behavior:
19
19
  - Human receipts end with `Mission: <id> (<status>)`, while JSON/structured output text stays unchanged and `details.missionId` is authoritative.
20
20
  - Pass `mission: false` for an intentionally ephemeral workflow. It creates no mission for the workflow or its children and has no `state` global.
21
21
  - Set `missions.enabled: false` to disable automatic mission creation; explicit mission fields and actions still work.
22
- - A workflow with a mission can use `await state.get(key)` and `await state.set(key, value)` for durable JSON state. Missing keys return `undefined`. Keys use the same format as `runs.run` keys. Each set takes the state-file lock, reads the latest file, merges the key, and atomically writes `<cwd>/.pi-subagents/missions/<mission-id>/state.json`. The complete file cannot exceed 256 KiB. Each workflow caches the file on its first `get`. A `mission:false` workflow has no `state` global.
22
+ - A workflow with a mission can use `await state.get(key)` and `await state.set(key, value)` for durable JSON state. Missing keys return `undefined`. Keys use the same format as `runs.run` keys. Each set takes the state-file lock, reads the latest file, merges the key, and atomically writes `<cwd>/.pi/subagents/missions/<mission-id>/state.json`. The complete file cannot exceed 256 KiB. Each workflow caches the file on its first `get`. A `mission:false` workflow has no `state` global.
23
+
24
+ An explicit `mission` object must have exactly one non-empty `title` or `summary`. `objective` and `labels` are optional. When supplied, `goal` must be `true` and requires `budget: { tokens: <positive integer> }`.
23
25
 
24
26
  ```ts
25
27
  const created = subagent({
@@ -81,7 +83,7 @@ Mission storage configuration (`missions.directory`, `retainTerminal`, `globalIn
81
83
 
82
84
  ## Schedules
83
85
 
84
- Durable schedules are enabled by default and stored per project under `.pi-subagents/schedules/<id>/`.
86
+ Durable schedules are enabled by default and stored per project under `.pi/subagents/schedules/<id>/`.
85
87
 
86
88
  Create a one-shot schedule:
87
89
 
package/docs/models.md CHANGED
@@ -150,6 +150,7 @@ To keep subagents inside a budget or compliance profile, enforce a model scope.
150
150
  "subagents": {
151
151
  "modelScope": {
152
152
  "enforce": true,
153
+ "strict": true,
153
154
  "allow": ["anthropic/*", "openai/gpt-5-*"]
154
155
  }
155
156
  }
@@ -158,7 +159,8 @@ To keep subagents inside a budget or compliance profile, enforce a model scope.
158
159
 
159
160
  - `allow` is a list of glob patterns matched against the resolved `provider/id` (only `*` is special, case-insensitive). A resolved model that matches none of the patterns is rejected.
160
161
  - Models you pass explicitly — the tool-call `model`, `--model`, or a clarify pick — error and abort the run.
161
- - Models that come from agent frontmatter, `subagents.defaultModel`, or the inherited parent session model only warn, so existing configurations keep working while you tighten the scope.
162
+ - By default, models from agent frontmatter, `subagents.defaultModel`, the inherited parent session model, or fallback chains only warn and remain available, so existing configurations keep working while you tighten the scope.
163
+ - Set `strict: true` with `enforce: true` to reject every resolved out-of-scope model. This includes inherited models and fallback candidates. An invalid fallback fails the run instead of being removed from the candidate chain.
162
164
  - `enforce: true` requires a non-empty `allow` list; otherwise the config is rejected at load time.
163
165
 
164
166
  ## Profiles and provider model catalogs
@@ -126,7 +126,7 @@ Foreground and async runners share bounded child-protocol handling:
126
126
 
127
127
  ## Chain and debug artifacts
128
128
 
129
- Each chain run creates a scratch directory under its resolved chain root. With the default `artifactDir: "project"`, that root is `<cwd>/.pi-subagents/chain-runs/`. With `artifactDir: "session"` or `"temp"`, it is user-scoped temp storage:
129
+ Each chain run creates a scratch directory under its resolved chain root. With the default `artifactDir: "project"`, that root is `<cwd>/.pi/subagents/chain-runs/`. With `artifactDir: "session"` or `"temp"`, it is user-scoped temp storage:
130
130
 
131
131
  ```text
132
132
  <tmpdir>/pi-subagents-<scope>/chain-runs/{runId}/
@@ -134,7 +134,7 @@ Each chain run creates a scratch directory under its resolved chain root. With t
134
134
 
135
135
  A run directory may contain files such as `context.md`, `plan.md`, `progress.md`, and `parallel-{stepIndex}/.../output.md`. User-scoped temp chain directories older than 24 hours are cleaned up on extension startup; project-local and explicit persistent roots are not age-scanned.
136
136
 
137
- Debug artifacts live under `{sessionDir}/subagent-artifacts/`, `.pi-subagents/artifacts/` for project-scoped runs, or a user-scoped temp artifact directory. Single-run relative `output` files are saved under `{artifactsDir}/outputs/{runId}/` unless `singleRunOutputBaseDir` is configured. Per task you may see:
137
+ Debug artifacts live under `{sessionDir}/subagent-artifacts/`, `.pi/subagents/artifacts/` for project-scoped runs, or a user-scoped temp artifact directory. Single-run relative `output` files are saved under `{artifactsDir}/outputs/{runId}/` unless `singleRunOutputBaseDir` is configured. Per task you may see:
138
138
 
139
139
  - `{runId}_{agent}_input.md`
140
140
  - `{runId}_{agent}_output.md`
@@ -143,7 +143,7 @@ Debug artifacts live under `{sessionDir}/subagent-artifacts/`, `.pi-subagents/ar
143
143
 
144
144
  Metadata records timing, usage, exit code, final model, attempted models, fallback attempt outcomes, and the resolved acceptance ledger with its parsed child report.
145
145
 
146
- For npm package projects, project-scoped artifacts need a `.npmignore` rule (or `.gitignore` when no `.npmignore` exists) or a `files` allowlist that does not include `.pi-subagents/`. pi-subagents warns at launch when these package settings can include the artifacts. Use `artifactDir: "session"` or `"temp"` to keep them outside the package worktree.
146
+ For npm package projects, project-scoped artifacts need a `.npmignore` rule (or `.gitignore` when no `.npmignore` exists) or a `files` allowlist that does not include `.pi/subagents/`. pi-subagents warns at launch when these package settings can include the artifacts. Use `artifactDir: "session"` or `"temp"` to keep them outside the package worktree.
147
147
 
148
148
  ## Sessions
149
149
 
@@ -35,7 +35,7 @@ Parameters and actions for the `subagent` tool. These are what the LLM passes wh
35
35
  | `config` | object/string | - | Agent or existing durable chain config for management create/update. |
36
36
  | `context` | `fresh \| fork` | per-agent default or `fresh` | Explicit `fresh` or `fork` overrides every workflow child. When omitted, each child agent uses its own `defaultContext`; `fork` creates real branched sessions from the parent leaf. Packaged `worker`, `oracle`, and `advisor` default to `fork`. |
37
37
  | `missionId` | string | - | Attach a workflow to an existing project mission instead of creating its default enclosing mission. |
38
- | `mission` | object/false | auto-create | Override the default enclosing mission with `{ title, objective?, goal?, budget?, labels? }`; `goal: true` requires `budget.tokens` and enables continuation notices. Pass `false` for an intentionally ephemeral workflow with no mission for it or its children and no `state` global. Explicit mission persistence failures are strict. |
38
+ | `mission` | object/false | auto-create | Override the default enclosing mission with `{ title \| summary, objective?, goal?, budget?, labels? }`. Set exactly one non-empty `title` or `summary`; `objective` and `labels` are optional. `goal` may only be `true`, requires `budget.tokens`, and enables continuation notices. Pass `false` for an intentionally ephemeral workflow with no mission for it or its children and no `state` global. Explicit mission persistence failures are strict. |
39
39
  | `handoffPath` | string | - | Aggregate handoff manifest required by `action: "worktree.discard"`. |
40
40
  | `focus` | boolean | true | Focus the newly split pane for `action: "inspector.open"` or `action: "project.open"`; not a standalone action. |
41
41
  | `view` | `fleet \| transcript` | - | Optional `status` view for the active fleet surface or transcript tail inspection. |
@@ -246,7 +246,7 @@ The persisted `steering` ledger retains 20 requests and replaces the old `steerC
246
246
 
247
247
  ### append-step
248
248
 
249
- `append-step` accepts exactly one `step` object for an existing durable chain for a top-level async chain whose status is still `running`. The step is persisted in the run directory and becomes eligible only after the chain's already-queued steps finish. Completed, failed, rejected, paused, foreground, single, and non-chain runs reject appends.
249
+ `append-step` requires `legacyChainControls: true`. The default registered model-facing schema omits this legacy control surface. When enabled, it accepts exactly one `step` object for an existing durable chain for a top-level async chain whose status is still `running`. The step is persisted in the run directory and becomes eligible only after the chain's already-queued steps finish. Completed, failed, rejected, paused, foreground, single, and non-chain runs reject appends.
250
250
 
251
251
  ## Acceptance gates
252
252
 
package/docs/watchdog.md CHANGED
@@ -11,7 +11,7 @@ It reviews repo edits, not ordinary conversation:
11
11
  - It runs at the safe `agent_end` boundary, only when the current agent or child writer changed the final repo state since the start of that turn.
12
12
  - Multiple edits in one turn are coalesced into one review of the final changed state.
13
13
  - Unchanged/reverted diffs are skipped.
14
- - Generated `.pi-subagents/` or `tmp/` artifacts do not trigger review.
14
+ - Generated `.pi/subagents/` or `tmp/` artifacts do not trigger review.
15
15
  - In orchestrated runs, each writing child can review its own edited worktree, and the parent can still review the aggregate repo diff after child changes are applied.
16
16
 
17
17
  ## Choosing a model
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-subagents",
3
- "version": "0.46.0",
3
+ "version": "0.47.1",
4
4
  "description": "Pi extension for single-agent delegation and scripted multi-agent workflows",
5
5
  "author": "Nico Bailon",
6
6
  "license": "MIT",
@@ -158,7 +158,7 @@ A cooperating terminal runtime can register read-only external records through `
158
158
 
159
159
  ### Scheduled subagent runs
160
160
 
161
- Schedules are durable project records under `.pi-subagents/schedules/`. They are enabled by default; set `{ "scheduledRuns": { "enabled": false } }` in `~/.pi/agent/extensions/subagent/config.json` to disable them. Only schedule explicit work the user asked for.
161
+ Schedules are durable project records under `.pi/subagents/schedules/`. They are enabled by default; set `{ "scheduledRuns": { "enabled": false } }` in `~/.pi/agent/extensions/subagent/config.json` to disable them. Only schedule explicit work the user asked for.
162
162
 
163
163
  ```typescript
164
164
  // One-shot reviewer
@@ -235,7 +235,7 @@ The subagent watchdog is an **opt-in** adversarial change reviewer. It is not th
235
235
 
236
236
  When enabled, it reviews actual repo edits at safe `agent_end` boundaries only if
237
237
  the final worktree state changed during that turn. Unchanged or reverted diffs and
238
- generated `.pi-subagents/` / temp artifacts do not trigger review. Writing children
238
+ generated `.pi/subagents/` / temp artifacts do not trigger review. Writing children
239
239
  can review their own worktree; the parent can still review the aggregate diff after
240
240
  child changes land. Enabled watchdogs also run changed-file TypeScript/JavaScript
241
241
  LSP diagnostics before the model pass when `typescript-language-server` is available.
@@ -298,7 +298,7 @@ Routing rule:
298
298
  - Several projects with independent work: one async `workflowScript` whose child keys include repo slugs and whose child calls set explicit `cwd`; keep publication and merge decisions serial per repo.
299
299
  - Different project, substantial or long-running work: open a project-owned Herdr pane rooted there when a separate visible project session is useful, then give that project Pi session a narrow mission/result contract. Do not model it as ordinary child nesting, and do not expect existing headless runs to move into the pane.
300
300
 
301
- Project panes run a separate Pi session from the target directory. Subagents launched inside that pane use that project's config, agents, skills, files, git state, and mission records. The pane binding lives under `<projectRoot>/.pi-subagents/project-panes/herdr.json`. For ordinary headless delegation to another repo, prefer explicit `cwd` first; reserve project panes for visible or persistent project ownership.
301
+ Project panes run a separate Pi session from the target directory. Subagents launched inside that pane use that project's config, agents, skills, files, git state, and mission records. The pane binding lives under `<projectRoot>/.pi/subagents/project-panes/herdr.json`. For ordinary headless delegation to another repo, prefer explicit `cwd` first; reserve project panes for visible or persistent project ownership.
302
302
 
303
303
  ```typescript
304
304
  subagent({ action: "mission.create", mission: { title: "Ship auth refresh", objective: "Implement and validate refresh handling" } })
@@ -340,7 +340,7 @@ single-writer pattern instead.
340
340
 
341
341
  Git worktrees start from tracked files, so ignored or untracked build state
342
342
  such as `node_modules` may be absent. The clean-check ignores pi-subagents'
343
- own `.pi-subagents/` runtime state, including default mission records, but still
343
+ own `.pi/subagents/` runtime state, including default mission records, but still
344
344
  rejects ordinary source/config changes. `pi-subagents` attempts to symlink the
345
345
  root checkout's `node_modules` into each managed worktree when it exists, but
346
346
  agents should still treat dependency setup as an explicit bootstrap step before
@@ -28,7 +28,7 @@ subagent({ action: "refine.show", agent: "reviewer" })
28
28
  subagent({ action: "refine.rollback", agent: "reviewer" })
29
29
  ```
30
30
 
31
- `refine` builds a bounded project-local guidance overlay for one agent from recent run evidence, using a fresh read-only proposal child; validated guidance is stored under `.pi-subagents/refinements/<agent>.md` with revision snapshots and is injected into that agent's child system prompt for this project. `refine.show` prints the current overlay and history; `refine.rollback` restores the previous revision. Guidance that tries to override safety, policy, tool, output, acceptance, developer, or system instructions is rejected. `/subagents-refine <agent>` is the slash equivalent.
31
+ `refine` builds a bounded project-local guidance overlay for one agent from recent run evidence, using a fresh read-only proposal child; validated guidance is stored under `.pi/subagents/refinements/<agent>.md` with revision snapshots and is injected into that agent's child system prompt for this project. `refine.show` prints the current overlay and history; `refine.rollback` restores the previous revision. Guidance that tries to override safety, policy, tool, output, acceptance, developer, or system instructions is rejected. `/subagents-refine <agent>` is the slash equivalent.
32
32
 
33
33
  ### Create an agent
34
34
 
@@ -37,15 +37,28 @@ function validateFleetKeybindingsConfig(value: unknown): void {
37
37
  }
38
38
  }
39
39
 
40
+ function validateArtifactConfig(value: unknown): void {
41
+ if (value === undefined) return;
42
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("config.artifactConfig must be a JSON object");
43
+ const cleanupDays = (value as Record<string, unknown>).cleanupDays;
44
+ if (cleanupDays !== undefined && (typeof cleanupDays !== "number" || !Number.isInteger(cleanupDays) || cleanupDays < 0)) {
45
+ throw new Error("config.artifactConfig.cleanupDays must be a non-negative integer");
46
+ }
47
+ }
48
+
40
49
  function validateConfig(config: Record<string, unknown>): void {
41
50
  if (config.artifactDir !== undefined && !ARTIFACT_DIR_PREFERENCES.has(config.artifactDir as ArtifactDirPreference)) {
42
51
  throw new Error(`config.artifactDir must be "project", "session", or "temp"`);
43
52
  }
53
+ if (config.legacyChainControls !== undefined && typeof config.legacyChainControls !== "boolean") {
54
+ throw new Error("config.legacyChainControls must be a boolean");
55
+ }
44
56
  validateMissionStoreConfig(config.missions);
45
57
  validateAuthorityPolicy(config.authorityPolicy);
46
58
  validatePermissionConfig(config.permissions);
47
59
  validateScheduledRunsConfig(config.scheduledRuns);
48
60
  validateFleetKeybindingsConfig(config.fleetKeybindings);
61
+ validateArtifactConfig(config.artifactConfig);
49
62
  }
50
63
 
51
64
  export function getConfigPath(): string {
@@ -10,7 +10,7 @@ import { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from "../runs/shared/pi
10
10
  import { readNestedControlRequests, resolveNestedRouteFromEnv, type NestedRoute, writeNestedControlResult } from "../runs/shared/nested-events.ts";
11
11
  import { deliverSubagentIntercomMessageEvent } from "../intercom/result-intercom.ts";
12
12
  import { resolveSubagentIntercomTarget } from "../intercom/intercom-bridge.ts";
13
- import { SubagentParams } from "./schemas.ts";
13
+ import { createSubagentParamsSchema } from "./schemas.ts";
14
14
  import { loadConfig, resolveAsyncByDefault } from "./config.ts";
15
15
  import { type Details, type SubagentState } from "../shared/types.ts";
16
16
 
@@ -171,15 +171,16 @@ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI):
171
171
  allowMutatingManagementActions: false,
172
172
  });
173
173
 
174
- const tool: ToolDefinition<typeof SubagentParams, Details> = {
174
+ const params = createSubagentParamsSchema(config);
175
+ const tool: ToolDefinition<typeof params, Details> = {
175
176
  name: "subagent",
176
177
  label: "Subagent",
177
178
  description: [
178
179
  "Delegate to subagents from child-safe fanout mode.",
179
- "Allowed management/control actions: list, get, status, interrupt, resume, steer, append-step, doctor.",
180
+ `Allowed management/control actions: list, get, status, interrupt, resume, steer${config.legacyChainControls === true ? ", append-step" : ""}, doctor.`,
180
181
  "Mutating management actions (create, update, delete, eject, disable, enable, reset, grant-spawn-budget) are blocked in this mode.",
181
182
  ].join("\n"),
182
- parameters: SubagentParams,
183
+ parameters: params,
183
184
  execute(id, params, signal, onUpdate, ctx) {
184
185
  return executor.executePublic(id, params as SubagentParamsLike, signal ?? new AbortController().signal, onUpdate, ctx);
185
186
  },
@@ -27,7 +27,7 @@ import { cleanupOldChainDirs } from "../shared/settings.ts";
27
27
  import { clearLegacyResultAnimationTimer, renderSubagentResult, renderSubagentSummary } from "../tui/render.ts";
28
28
  import { openSubagentFleet } from "../tui/fleet.ts";
29
29
  import { SubagentFleetStatus, resolveFleetViewPlacement } from "../tui/fleet-status.ts";
30
- import { SubagentParams } from "./schemas.ts";
30
+ import { createSubagentParamsSchema } from "./schemas.ts";
31
31
  import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
32
32
  import { createAsyncJobTracker } from "../runs/background/async-job-tracker.ts";
33
33
  import { createResultWatcher } from "../runs/background/result-watcher.ts";
@@ -366,7 +366,8 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
366
366
  const asyncWidgetEnabled = config.asyncWidget !== false;
367
367
  const summaryInlineToolDisplay = config.inlineToolDisplay === "summary";
368
368
  const tempArtifactsDir = getArtifactsDir(null);
369
- cleanupAllArtifactDirs(DEFAULT_ARTIFACT_CONFIG.cleanupDays);
369
+ const artifactCleanupDays = config.artifactConfig?.cleanupDays ?? DEFAULT_ARTIFACT_CONFIG.cleanupDays;
370
+ cleanupAllArtifactDirs(artifactCleanupDays);
370
371
 
371
372
  const state: SubagentState = {
372
373
  baseCwd: "",
@@ -392,6 +393,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
392
393
  lastUiContext: null,
393
394
  poller: null,
394
395
  completionSeen: new Map(),
396
+ widgetsSuspended: false,
395
397
  watcher: null,
396
398
  watcherRestartTimer: null,
397
399
  resultFileCoalescer: {
@@ -429,6 +431,9 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
429
431
  },
430
432
  resolveCapabilityCeiling: (sessionId) => resolveCurrentSubagentCapabilityCeiling(sessionId),
431
433
  });
434
+ const { ensurePoller, refreshWidget, handleStarted, handleComplete, resetJobs, restoreActiveJobs, dispose: disposeAsyncJobTracker } = createAsyncJobTracker(pi, state, DIRS.async, {
435
+ widgetEnabled: asyncWidgetEnabled,
436
+ });
432
437
  const { startResultWatcher, primeExistingResults, stopResultWatcher } = createResultWatcher(
433
438
  pi,
434
439
  state,
@@ -437,6 +442,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
437
442
  {
438
443
  notifier: completionNotifier,
439
444
  observeCompletion: (result) => scheduledRunManager.handleAsyncCompletion(result),
445
+ observedCompletionRunIds: () => scheduledRunManager.observedCompletionRunIds(),
440
446
  deliverIntercomResults: config.intercomBridge?.resultDelivery === true,
441
447
  },
442
448
  );
@@ -450,16 +456,10 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
450
456
  supervisorChannel.dispose();
451
457
  waitSubscriptionManager.dispose();
452
458
  fleetStatus?.dispose();
453
- if (state.poller) {
454
- clearInterval(state.poller);
455
- state.poller = null;
456
- }
459
+ disposeAsyncJobTracker();
457
460
  };
458
461
  globalStore[runtimeCleanupStoreKey] = runtimeCleanup;
459
462
 
460
- const { ensurePoller, refreshWidget, handleStarted, handleComplete, resetJobs, restoreActiveJobs } = createAsyncJobTracker(pi, state, DIRS.async, {
461
- widgetEnabled: asyncWidgetEnabled,
462
- });
463
463
  const executor = createSubagentExecutor({
464
464
  pi,
465
465
  state,
@@ -566,11 +566,12 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
566
566
  });
567
567
 
568
568
 
569
- const tool: ToolDefinition<typeof SubagentParams, Details> = {
569
+ const parameters = createSubagentParamsSchema(config);
570
+ const tool: ToolDefinition<typeof parameters, Details> = {
570
571
  name: "subagent",
571
572
  label: "Subagent",
572
573
  description: buildSubagentToolDescription(config),
573
- parameters: SubagentParams,
574
+ parameters,
574
575
 
575
576
  execute(id, params, signal, onUpdate, ctx) {
576
577
  return executeSubagentCollapsed(id, params as SubagentParamsLike, signal ?? new AbortController().signal, onUpdate, ctx);
@@ -706,14 +707,29 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
706
707
  try {
707
708
  const sessionFile = ctx.sessionManager.getSessionFile();
708
709
  if (sessionFile) {
709
- cleanupOldArtifacts(getArtifactsDir(sessionFile), DEFAULT_ARTIFACT_CONFIG.cleanupDays);
710
+ cleanupOldArtifacts(getArtifactsDir(sessionFile), artifactCleanupDays);
710
711
  }
711
712
  } catch {
712
713
  // Cleanup failures should not block session lifecycle events.
713
714
  }
714
715
  };
715
716
 
717
+ const suspendWidgetsForCompaction = () => {
718
+ if (state.widgetsSuspended) return;
719
+ state.widgetsSuspended = true;
720
+ if (state.lastUiContext?.hasUI) state.lastUiContext.ui.setWidget(WIDGET_KEY, undefined);
721
+ fleetStatus?.refresh();
722
+ };
723
+ const resumeWidgetsAfterCompaction = () => {
724
+ if (!state.widgetsSuspended) return;
725
+ state.widgetsSuspended = false;
726
+ const ctx = state.lastUiContext;
727
+ if (ctx?.hasUI) refreshWidget(ctx);
728
+ fleetStatus?.refresh();
729
+ };
730
+
716
731
  const resetSessionState = (ctx: ExtensionContext, recovering: boolean) => {
732
+ state.widgetsSuspended = false;
717
733
  state.baseCwd = ctx.cwd;
718
734
  goalTurnId = 0;
719
735
  state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
@@ -752,9 +768,18 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
752
768
  };
753
769
 
754
770
  pi.on("agent_start", () => {
771
+ resumeWidgetsAfterCompaction();
755
772
  herdrStatusBridge.agentStarted();
756
773
  });
757
774
 
775
+ pi.on("agent_settled", () => {
776
+ resumeWidgetsAfterCompaction();
777
+ });
778
+
779
+ pi.on("session_before_compact", (event) => {
780
+ if (event.reason !== "manual") suspendWidgetsForCompaction();
781
+ });
782
+
758
783
  pi.on("session_compact", () => {
759
784
  const hasActiveAsyncWork = [...state.asyncJobs.values()].some((job) => job.status === "queued" || job.status === "running");
760
785
  if (!hasActiveAsyncWork || state.lastUiContext?.hasUI !== true) return;
@@ -780,6 +805,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
780
805
  });
781
806
 
782
807
  pi.on("session_shutdown", async () => {
808
+ state.widgetsSuspended = false;
783
809
  stopResultWatcher();
784
810
  state.currentSessionId = null;
785
811
  state.parentSessionFile = null;
@@ -796,8 +822,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
796
822
  delete globalStore[eventUnsubscribeStoreKey];
797
823
  }
798
824
  scheduledRunManager.stop();
799
- if (state.poller) clearInterval(state.poller);
800
- state.poller = null;
825
+ disposeAsyncJobTracker();
801
826
  for (const timer of state.cleanupTimers.values()) {
802
827
  clearTimeout(timer);
803
828
  }
@@ -16,6 +16,7 @@ import {
16
16
  SUBAGENT_PROCESS_TERMINAL_EVENT,
17
17
  SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
18
18
  } from "../shared/types.ts";
19
+ import { sanitizeDisplayText, truncateDisplayText } from "../shared/display-text.ts";
19
20
  import { readStatus } from "../shared/utils.ts";
20
21
  import { SubagentParams } from "./schemas.ts";
21
22
  import { formatWorkflowJsonPreview } from "../workflows/scripted-workflow.ts";
@@ -101,12 +102,8 @@ const MAX_METADATA_LENGTH = 128;
101
102
 
102
103
  function displayText(value: unknown, maxLength: number): string | undefined {
103
104
  if (typeof value !== "string") return undefined;
104
- // Strip complete CSI/OSC/DCS/APC/PM strings and C1 controls before collapsing
105
- // whitespace; never leave CSI parameters behind after removing ESC.
106
- const normalized = value.slice(0, 4_096)
107
- .replace(/\x1b\[[0-?]*[ -/]*[@-~]|\x9b[0-?]*[ -/]*[@-~]|\x1b][\s\S]*?(?:\x07|\x1b\\)|\x1b[PX^_][\s\S]*?\x1b\\|[\u0000-\u001f\u007f-\u009f]/g, " ")
108
- .replace(/\s+/g, " ").trim();
109
- return normalized ? normalized.slice(0, maxLength) : undefined;
105
+ const normalized = sanitizeDisplayText(value.slice(0, 4_096));
106
+ return normalized ? truncateDisplayText(normalized, maxLength) : undefined;
110
107
  }
111
108
 
112
109
  function publicTokens(value: unknown): { input: number; output: number; total: number } {
@@ -493,6 +490,10 @@ function stopAsyncRun(
493
490
  throw new SubagentRpcError("not_found", `Async run '${initialRunId}' was not found in the active session.`);
494
491
  }
495
492
 
493
+ if (initialStatus.mode === "workflow" && initialStatus.state === "running") {
494
+ throw new SubagentRpcError("invalid_state", `Workflow ${initialRunId} is not controlled by this extension runtime; reload recovery cannot stop it safely.`);
495
+ }
496
+
496
497
  let status;
497
498
  try {
498
499
  status = reconcileAsyncRun(location.asyncDir, { resultsDir, kill: options.kill, now: options.now }).status;