pi-subagents 0.46.0 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/docs/agents.md +1 -1
  3. package/docs/configuration.md +14 -6
  4. package/docs/extension-api.md +1 -1
  5. package/docs/missions.md +5 -3
  6. package/docs/models.md +3 -1
  7. package/docs/observability.md +3 -3
  8. package/docs/tool-reference.md +2 -2
  9. package/docs/watchdog.md +1 -1
  10. package/package.json +1 -1
  11. package/skills/pi-subagents/references/execution-controls.md +4 -4
  12. package/skills/pi-subagents/references/management-authoring-rpc.md +1 -1
  13. package/src/extension/config.ts +3 -0
  14. package/src/extension/fanout-child.ts +5 -4
  15. package/src/extension/index.ts +30 -3
  16. package/src/extension/rpc.ts +3 -6
  17. package/src/extension/schemas.ts +25 -5
  18. package/src/extension/tool-description.ts +26 -8
  19. package/src/inspectors/herdr/project-panes.ts +2 -1
  20. package/src/missions/store.ts +2 -1
  21. package/src/missions/workflow-state.ts +19 -13
  22. package/src/runs/background/async-execution.ts +10 -5
  23. package/src/runs/background/async-job-tracker.ts +15 -0
  24. package/src/runs/background/async-resume.ts +19 -3
  25. package/src/runs/background/async-status.ts +6 -1
  26. package/src/runs/background/control-channel.ts +36 -0
  27. package/src/runs/background/result-watcher.ts +16 -2
  28. package/src/runs/background/scheduled-runs.ts +2 -1
  29. package/src/runs/background/stale-run-reconciler.ts +2 -21
  30. package/src/runs/background/subagent-runner.ts +47 -6
  31. package/src/runs/foreground/async-steering-action.ts +1 -1
  32. package/src/runs/foreground/chain-execution.ts +3 -0
  33. package/src/runs/foreground/execution.ts +3 -0
  34. package/src/runs/foreground/subagent-executor.ts +95 -12
  35. package/src/runs/foreground/workflow-foreground-steering.ts +187 -0
  36. package/src/runs/shared/dynamic-fanout.ts +1 -1
  37. package/src/runs/shared/model-fallback.ts +8 -4
  38. package/src/runs/shared/model-scope.ts +12 -2
  39. package/src/runs/shared/parallel-utils.ts +1 -0
  40. package/src/runs/shared/worktree.ts +3 -2
  41. package/src/shared/artifacts.ts +14 -14
  42. package/src/shared/display-text.ts +100 -0
  43. package/src/shared/formatters.ts +4 -6
  44. package/src/shared/settings.ts +15 -2
  45. package/src/shared/types.ts +11 -1
  46. package/src/shared/utils.ts +43 -33
  47. package/src/slash/slash-commands.ts +3 -1
  48. package/src/tui/fleet-status.ts +14 -10
  49. package/src/tui/render.ts +30 -26
  50. package/src/watchdog/change-signature.ts +4 -3
package/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.47.0] - 2026-08-11
6
+
7
+ ### Changed
8
+ - Add optional strict model-scope enforcement that rejects inherited and fallback models outside the configured allowlist. Thanks to @antonioc-cl for #995.
9
+ - 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.
10
+ - Move project-scoped pi-subagents storage from `.pi-subagents/` to `.pi/subagents/` for cleaner project roots. Thanks to @yceachan for #971.
11
+ - Clarify the accepted mission launch object contract for tool callers.
12
+ - Reduce repeated async status parsing, workflow trace projection, and constrained widget rendering work.
13
+ - Keep parsed async statuses cached beyond 50 runs while preserving per-read freshness checks. Thanks to @bcanvural for #982.
14
+
15
+ ### Fixed
16
+ - Omit missing configured read files from child task instructions.
17
+ - Stabilize steering recovery budget coverage around the confirmed paused handoff.
18
+ - Keep timeout-sensitive async acceptance verification coverage off Windows CI where signal delivery is intermittent.
19
+ - Retry transient Windows mission state lock creation failures and stabilize no-session steering recovery coverage.
20
+ - Keep async widget running glyphs moving while children are quiet but active. Thanks to @bcanvural for #983.
21
+ - Let active-session workflows live-steer their workflow-owned foreground children by child or unique workflow identity. Thanks to @youlikemodernart for #988.
22
+ - Accept persisted async recovery descriptors that include internal turn-budget state fields (#985).
23
+ - 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.
24
+ - 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.
25
+ - Recover durable async completions when a healthy native result watcher misses a filesystem event. Thanks to @xz-dev for #973.
26
+
5
27
  ## [0.46.0] - 2026-08-11
6
28
 
7
29
  ### Added
@@ -12,6 +34,7 @@
12
34
  - Persist workflow child attempts, status heartbeats, session paths, and artifacts in their enclosing mission, and add explicit mission decision resolution.
13
35
 
14
36
  ### Fixed
37
+ - Suspend subagent status widgets during automatic compaction to prevent duplicate terminal frames.
15
38
  - 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
39
  - Wait for retained-child resumes inside `workflowScript` to finish and return completed output before the script continues (#961).
17
40
  - Keep fork-context workflow children inside their managed worktree by aligning the persisted child session cwd before launch. Thanks to @flopsi for #953.
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.0",
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
 
@@ -41,6 +41,9 @@ function validateConfig(config: Record<string, unknown>): void {
41
41
  if (config.artifactDir !== undefined && !ARTIFACT_DIR_PREFERENCES.has(config.artifactDir as ArtifactDirPreference)) {
42
42
  throw new Error(`config.artifactDir must be "project", "session", or "temp"`);
43
43
  }
44
+ if (config.legacyChainControls !== undefined && typeof config.legacyChainControls !== "boolean") {
45
+ throw new Error("config.legacyChainControls must be a boolean");
46
+ }
44
47
  validateMissionStoreConfig(config.missions);
45
48
  validateAuthorityPolicy(config.authorityPolicy);
46
49
  validatePermissionConfig(config.permissions);
@@ -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";
@@ -392,6 +392,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
392
392
  lastUiContext: null,
393
393
  poller: null,
394
394
  completionSeen: new Map(),
395
+ widgetsSuspended: false,
395
396
  watcher: null,
396
397
  watcherRestartTimer: null,
397
398
  resultFileCoalescer: {
@@ -566,11 +567,12 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
566
567
  });
567
568
 
568
569
 
569
- const tool: ToolDefinition<typeof SubagentParams, Details> = {
570
+ const parameters = createSubagentParamsSchema(config);
571
+ const tool: ToolDefinition<typeof parameters, Details> = {
570
572
  name: "subagent",
571
573
  label: "Subagent",
572
574
  description: buildSubagentToolDescription(config),
573
- parameters: SubagentParams,
575
+ parameters,
574
576
 
575
577
  execute(id, params, signal, onUpdate, ctx) {
576
578
  return executeSubagentCollapsed(id, params as SubagentParamsLike, signal ?? new AbortController().signal, onUpdate, ctx);
@@ -713,7 +715,22 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
713
715
  }
714
716
  };
715
717
 
718
+ const suspendWidgetsForCompaction = () => {
719
+ if (state.widgetsSuspended) return;
720
+ state.widgetsSuspended = true;
721
+ if (state.lastUiContext?.hasUI) state.lastUiContext.ui.setWidget(WIDGET_KEY, undefined);
722
+ fleetStatus?.refresh();
723
+ };
724
+ const resumeWidgetsAfterCompaction = () => {
725
+ if (!state.widgetsSuspended) return;
726
+ state.widgetsSuspended = false;
727
+ const ctx = state.lastUiContext;
728
+ if (ctx?.hasUI) refreshWidget(ctx);
729
+ fleetStatus?.refresh();
730
+ };
731
+
716
732
  const resetSessionState = (ctx: ExtensionContext, recovering: boolean) => {
733
+ state.widgetsSuspended = false;
717
734
  state.baseCwd = ctx.cwd;
718
735
  goalTurnId = 0;
719
736
  state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
@@ -752,9 +769,18 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
752
769
  };
753
770
 
754
771
  pi.on("agent_start", () => {
772
+ resumeWidgetsAfterCompaction();
755
773
  herdrStatusBridge.agentStarted();
756
774
  });
757
775
 
776
+ pi.on("agent_settled", () => {
777
+ resumeWidgetsAfterCompaction();
778
+ });
779
+
780
+ pi.on("session_before_compact", (event) => {
781
+ if (event.reason !== "manual") suspendWidgetsForCompaction();
782
+ });
783
+
758
784
  pi.on("session_compact", () => {
759
785
  const hasActiveAsyncWork = [...state.asyncJobs.values()].some((job) => job.status === "queued" || job.status === "running");
760
786
  if (!hasActiveAsyncWork || state.lastUiContext?.hasUI !== true) return;
@@ -780,6 +806,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
780
806
  });
781
807
 
782
808
  pi.on("session_shutdown", async () => {
809
+ state.widgetsSuspended = false;
783
810
  stopResultWatcher();
784
811
  state.currentSessionId = null;
785
812
  state.parentSessionFile = null;
@@ -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 } {
@@ -254,7 +254,7 @@ const ControlOverrides = Type.Object({
254
254
  })),
255
255
  });
256
256
 
257
- const SubagentParamsSchema = Type.Object({
257
+ const SubagentParamProperties = {
258
258
  agent: Type.Optional(Type.String({ description: "Agent target for management actions such as get, update, delete, and models." })),
259
259
  resume: Type.Optional(Type.String({ description: "Retained child run id for a workflowScript runs.run/runs.all item. Mutually exclusive with agent; task supplies the follow-up." })),
260
260
  // Management action (when present, tool operates in management mode)
@@ -296,7 +296,7 @@ const SubagentParamsSchema = Type.Object({
296
296
  overlap: Type.Optional(Type.String({ enum: ["skip"], description: "Overlap policy. This slice supports skip only." })),
297
297
  catchUp: Type.Optional(Type.String({ enum: ["none", "latest"], description: "Missed occurrence policy for recurring schedules. Defaults to latest." })),
298
298
  missionId: Type.Optional(Type.String({ description: "Mission id." })),
299
- mission: Type.Optional(Type.Unsafe({ ...MissionLaunchOverride, description: "Mission object, or false for no mission. Use objective for intent; goal:true with budget.tokens enables turn-end continuation notices." })),
299
+ mission: Type.Optional(Type.Unsafe({ ...MissionLaunchOverride, description: "Mission object, or false for no mission. Set exactly one non-empty title or summary; objective and labels are optional. goal may only be true and then requires budget.tokens." })),
300
300
  missionUpdate: Type.Optional(Type.Unsafe({ ...MissionUpdateOverride, description: "Mission update: objective, goal false or {paused:boolean}, budget, summary, labels, decisions, artifacts, or delivery receipts." })),
301
301
  missionStatus: Type.Optional(Type.String({ description: "Mission status." })),
302
302
  missionScope: Type.Optional(Type.String({ description: "Mission list scope: project (default) or global pointer index." })),
@@ -324,8 +324,8 @@ const SubagentParamsSchema = Type.Object({
324
324
  description: "'fresh' or 'fork' to branch from parent session. Explicit context overrides every child in the invocation. If omitted, each requested agent uses its own defaultContext; agents without defaultContext: 'fork' run fresh.",
325
325
  })),
326
326
  async: Type.Optional(Type.Boolean({ description: "Run in background (default: false, or per config)" })),
327
- timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Optional timeout for foreground and async/background runs. Foreground workflows default to 30m; async workflows have no default timeout. Alias maxRuntimeMs." })),
328
- maxRuntimeMs: Type.Optional(Type.Integer({ minimum: 1, description: "Alias timeoutMs for foreground and async/background runs. Foreground workflows default to 30m; async workflows have no default timeout." })),
327
+ timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Run timeout. Foreground runs and async children default to 30m; async composites have no default parent deadline. Alias maxRuntimeMs." })),
328
+ maxRuntimeMs: Type.Optional(Type.Integer({ minimum: 1, description: "Alias timeoutMs. Foreground runs and async children default to 30m; async composites have no default parent deadline." })),
329
329
  turnBudget: Type.Optional(TurnBudgetOverride),
330
330
  toolBudget: Type.Optional(ToolBudgetOverride),
331
331
  usageBudget: Type.Optional(UsageBudgetOverride),
@@ -353,9 +353,29 @@ const SubagentParamsSchema = Type.Object({
353
353
  agentContract: Type.Optional(AgentContractOverride),
354
354
  acceptance: Type.Optional(AcceptanceOverride),
355
355
  gate: Type.Optional(Type.String({ minLength: 1, description: "Host gate command. Cannot be combined with acceptance." })),
356
- });
356
+ };
357
+
358
+ const { step: _legacyChainStep, ...subagentParamPropertiesWithoutStep } = SubagentParamProperties;
359
+ const trimmedSubagentParamProperties = {
360
+ ...subagentParamPropertiesWithoutStep,
361
+ id: Type.Optional(Type.String({
362
+ description: "Run id or prefix for status, interrupt, stop, resume, steer, mission.attach-run, or the decision id for mission.resolve-decision."
363
+ })),
364
+ runId: Type.Optional(Type.String({
365
+ description: "Target run ID for interrupt, stop, resume, steer, or mission.attach-run. Prefer id for new calls."
366
+ })),
367
+ };
368
+ const SubagentParamsSchema = Type.Object(SubagentParamProperties);
369
+ const TrimmedSubagentParamsSchema = Type.Object(trimmedSubagentParamProperties);
357
370
 
358
371
  export const SubagentParams = keepTopLevelParameterDescriptions(SubagentParamsSchema);
372
+ export const SubagentParamsWithoutLegacyChainControls = keepTopLevelParameterDescriptions(TrimmedSubagentParamsSchema);
373
+
374
+ export function createSubagentParamsSchema(options: { legacyChainControls?: boolean } = {}): typeof SubagentParams | typeof SubagentParamsWithoutLegacyChainControls {
375
+ return options.legacyChainControls === true
376
+ ? SubagentParams
377
+ : SubagentParamsWithoutLegacyChainControls;
378
+ }
359
379
 
360
380
  const SubagentWaitParamsSchema = Type.Object({
361
381
  id: Type.Optional(Type.String({
@@ -22,7 +22,7 @@ EXECUTION:
22
22
  • Sequential example: { workflowScript: "const a = await runs.run('analyze', {agent:'agent-a', task:'Analyze the request'}); return (await runs.run('plan', {agent:'agent-b', task:'Plan from: '+a.output})).output" }
23
23
  • Parallel example: { workflowScript: "const [a,b] = await runs.all([{key:'correctness',agent:'agent-a',task:'Review correctness'},{key:'tests',agent:'agent-b',task:'Review tests'}]); return {correctness:a.output,tests:b.output}" }
24
24
  • Optional context is "fresh" or "fork". timeoutMs/maxRuntimeMs apply to foreground and async workflows; foreground workflows default to 30 minutes and async workflows have no default timeout. Omit acceptance for reviewer/read-only calls; evidence levels end at verified, and acceptance.review.required requests independent writer review.
25
- • Durable mission attachment is automatic by default. Use missionId to attach an existing mission, mission:{...} to override auto-create, or mission:false for ephemeral work.
25
+ • Durable mission attachment is automatic by default. Use missionId to attach an existing mission, mission:{...} to override auto-create, or mission:false for ephemeral work. A mission object needs exactly one non-empty title or summary; objective and labels are optional. goal may only be true and requires budget:{tokens}.
26
26
 
27
27
  MANAGEMENT / CONTROL (use action; omit execution fields):
28
28
  • list, get, models, guide, children.list, create, update, delete, eject, disable, enable, reset, doctor, grant-spawn-budget, worktree.discard, refine/refine.show/refine.rollback, mission.create/list/show/update/resolve-decision/attach-run/close, inspector.open/status/close, project.open/status/close, and watchdog actions remain available. Use {action:"guide", topic:"overview"} for packaged current-version help; topics are overview, workflows, agents, missions, observability, tool-reference, configuration, models, watchdog, and extension-api.
@@ -44,6 +44,7 @@ EXECUTE:
44
44
  MANAGE / CONTROL:
45
45
  • Use action without execution fields for list/get/models/guide/authoring, refine/refine.show/refine.rollback, mission, watchdog, status, interrupt, stop, resume, steer, script-only scheduling, diagnostics, and other management actions. guide reads shipped current-version docs by topic.
46
46
  • append-step uses step:{...} only for an already-running durable legacy chain; step is not an execution mode.
47
+ • A mission object needs exactly one non-empty title or summary; objective and labels are optional. goal may only be true and requires budget:{tokens}.
47
48
 
48
49
  ASYNC / SAFETY:
49
50
  • Omitted async detaches background work. Do not sleep or poll merely to wait; use subagent_wait only when this turn must receive results.
@@ -152,13 +153,30 @@ function withMandatorySafetyGuidance(description: string): string {
152
153
  : SUBAGENT_SAFETY_GUIDANCE;
153
154
  }
154
155
 
155
- export function buildSubagentToolDescription(config: Pick<ExtensionConfig, "toolDescriptionMode"> = {}, options?: ToolDescriptionOptions): string {
156
+ const LEGACY_CHAIN_CONTROL_GUIDANCE_LINES = new Set([
157
+ '• { action: "append-step", id: "...", step: {agent:"agent-c", task:"Use {previous}"} } appends one step to an already-running durable legacy chain. step is control-only, not an execution mode.',
158
+ "• approve-checkpoint and reject-checkpoint decide a paused durable legacy chain checkpoint.",
159
+ "• append-step uses step:{...} only for an already-running durable legacy chain; step is not an execution mode.",
160
+ ]);
161
+
162
+ function withoutLegacyChainControlGuidance(description: string): string {
163
+ return description
164
+ .split("\n")
165
+ .filter((line) => !LEGACY_CHAIN_CONTROL_GUIDANCE_LINES.has(line.trim()))
166
+ .join("\n");
167
+ }
168
+
169
+ export function buildSubagentToolDescription(config: Pick<ExtensionConfig, "toolDescriptionMode" | "legacyChainControls"> = {}, options?: ToolDescriptionOptions): string {
156
170
  const mode = resolveToolDescriptionMode(config, options);
157
- if (mode === "compact") return COMPACT_SUBAGENT_TOOL_DESCRIPTION;
158
- if (mode === "custom") {
171
+ let description: string;
172
+ if (mode === "compact") description = COMPACT_SUBAGENT_TOOL_DESCRIPTION;
173
+ else if (mode === "custom") {
159
174
  const custom = loadCustomToolDescription(options);
160
- if (custom) return withMandatorySafetyGuidance(custom);
161
- warn(options, `${CUSTOM_TOOL_DESCRIPTION_FILE} was not found or valid for toolDescriptionMode "custom"; using full description.`);
162
- }
163
- return FULL_SUBAGENT_TOOL_DESCRIPTION;
175
+ if (custom) description = withMandatorySafetyGuidance(custom);
176
+ else {
177
+ warn(options, `${CUSTOM_TOOL_DESCRIPTION_FILE} was not found or valid for toolDescriptionMode "custom"; using full description.`);
178
+ description = FULL_SUBAGENT_TOOL_DESCRIPTION;
179
+ }
180
+ } else description = FULL_SUBAGENT_TOOL_DESCRIPTION;
181
+ return config.legacyChainControls === true ? description : withoutLegacyChainControlGuidance(description);
164
182
  }
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
4
4
  import { getPiSpawnCommand } from "../../runs/shared/pi-spawn.ts";
5
+ import { getProjectSubagentsDir } from "../../shared/artifacts.ts";
5
6
  import { writeAtomicJson } from "../../shared/atomic-json.ts";
6
7
  import type { Details } from "../../shared/types.ts";
7
8
  import { createHerdrClient, detectHerdr, type HerdrClient, type HerdrErrorCode } from "./client.ts";
@@ -153,7 +154,7 @@ function formatProjectPaneError(input: { code: ProjectPaneErrorCode; message: st
153
154
  }
154
155
 
155
156
  function projectPaneDir(projectRoot: string): string {
156
- return path.join(projectRoot, ".pi-subagents", "project-panes");
157
+ return path.join(getProjectSubagentsDir(projectRoot), "project-panes");
157
158
  }
158
159
 
159
160
  export function projectPaneBindingPath(projectRoot: string): string {
@@ -2,6 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import * as fs from "node:fs";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
+ import { getProjectSubagentsDir } from "../shared/artifacts.ts";
5
6
  import { writePrivateAtomicJson } from "../shared/atomic-json.ts";
6
7
  import { getAgentDir } from "../shared/utils.ts";
7
8
  import {
@@ -290,7 +291,7 @@ export function resolveMissionStoreLocation(input: {
290
291
  const projectRoot = path.resolve(input.projectRoot);
291
292
  const missionDir = input.config?.directory
292
293
  ? expandConfiguredPath(input.config.directory, projectRoot)
293
- : path.join(projectRoot, ".pi-subagents", "missions");
294
+ : path.join(getProjectSubagentsDir(projectRoot), "missions");
294
295
  const globalIndexDir = input.config?.globalIndexDir
295
296
  ? expandConfiguredPath(input.config.globalIndexDir, projectRoot)
296
297
  : path.join(input.agentDir ?? getAgentDir(), "missions", "index");