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.
- package/CHANGELOG.md +37 -0
- package/agents/reviewer.md +3 -4
- package/docs/agents.md +1 -1
- package/docs/configuration.md +14 -6
- package/docs/extension-api.md +1 -1
- package/docs/missions.md +5 -3
- package/docs/models.md +3 -1
- package/docs/observability.md +3 -3
- package/docs/tool-reference.md +2 -2
- package/docs/watchdog.md +1 -1
- package/package.json +1 -1
- package/skills/pi-subagents/references/execution-controls.md +4 -4
- package/skills/pi-subagents/references/management-authoring-rpc.md +1 -1
- package/src/extension/config.ts +13 -0
- package/src/extension/fanout-child.ts +5 -4
- package/src/extension/index.ts +39 -14
- package/src/extension/rpc.ts +7 -6
- package/src/extension/schemas.ts +27 -7
- package/src/extension/tool-description.ts +26 -8
- package/src/inspectors/herdr/project-panes.ts +2 -1
- package/src/intercom/native-supervisor-channel.ts +102 -4
- package/src/missions/store.ts +2 -1
- package/src/missions/workflow-state.ts +19 -13
- package/src/runs/background/active-run-index.ts +42 -0
- package/src/runs/background/async-execution.ts +10 -5
- package/src/runs/background/async-job-tracker.ts +293 -133
- package/src/runs/background/async-resume.ts +19 -3
- package/src/runs/background/async-status.ts +33 -5
- package/src/runs/background/control-channel.ts +91 -17
- package/src/runs/background/result-watcher.ts +102 -7
- package/src/runs/background/run-status.ts +18 -0
- package/src/runs/background/scheduled-runs.ts +25 -1
- package/src/runs/background/stale-run-reconciler.ts +12 -24
- package/src/runs/background/subagent-runner.ts +68 -10
- package/src/runs/foreground/async-dismiss-action.ts +85 -0
- package/src/runs/foreground/async-steering-action.ts +6 -7
- package/src/runs/foreground/chain-execution.ts +3 -0
- package/src/runs/foreground/execution.ts +3 -0
- package/src/runs/foreground/subagent-executor.ts +129 -20
- package/src/runs/foreground/workflow-foreground-steering.ts +187 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/model-fallback.ts +8 -4
- package/src/runs/shared/model-scope.ts +12 -2
- package/src/runs/shared/parallel-utils.ts +1 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +24 -5
- package/src/runs/shared/worktree.ts +3 -2
- package/src/shared/artifacts.ts +15 -15
- package/src/shared/display-text.ts +100 -0
- package/src/shared/file-coalescer.ts +9 -0
- package/src/shared/formatters.ts +4 -6
- package/src/shared/settings.ts +15 -2
- package/src/shared/types.ts +16 -2
- package/src/shared/utils.ts +43 -33
- package/src/slash/slash-commands.ts +3 -1
- package/src/tui/fleet-status.ts +14 -10
- package/src/tui/render.ts +30 -26
- package/src/watchdog/change-signature.ts +4 -3
- 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.
|
package/agents/reviewer.md
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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.
|
package/docs/configuration.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
package/docs/extension-api.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
package/docs/observability.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
package/docs/tool-reference.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
package/src/extension/config.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
},
|
package/src/extension/index.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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),
|
|
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
|
-
|
|
800
|
-
state.poller = null;
|
|
825
|
+
disposeAsyncJobTracker();
|
|
801
826
|
for (const timer of state.cleanupTimers.values()) {
|
|
802
827
|
clearTimeout(timer);
|
|
803
828
|
}
|
package/src/extension/rpc.ts
CHANGED
|
@@ -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
|
-
|
|
105
|
-
|
|
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;
|