pi-subagents 0.77.0 → 0.79.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.
- package/CHANGELOG.md +93 -0
- package/agents/evidence-auditor.md +1 -0
- package/agents/oracle.md +1 -0
- package/agents/reviewer.md +1 -0
- package/docs/agents.md +4 -2
- package/docs/configuration.md +22 -6
- package/docs/extension-api.md +9 -1
- package/docs/observability.md +11 -3
- package/docs/tool-reference.md +14 -3
- package/docs/workflows.md +6 -4
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +2 -1
- package/skills/pi-subagents/references/execution-controls.md +2 -0
- package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
- package/src/api/external-runs.d.ts.map +1 -1
- package/src/api/external-runs.js +5 -4
- package/src/api/external-runs.js.map +1 -1
- package/src/api/preflight.js +1 -1
- package/src/api/preflight.js.map +1 -1
- package/src/extension/active-runs-prompt.d.ts +8 -0
- package/src/extension/active-runs-prompt.d.ts.map +1 -0
- package/src/extension/active-runs-prompt.js +30 -0
- package/src/extension/active-runs-prompt.js.map +1 -0
- package/src/extension/config.d.ts.map +1 -1
- package/src/extension/config.js +6 -0
- package/src/extension/config.js.map +1 -1
- package/src/extension/declaration-pinning.d.ts +5 -0
- package/src/extension/declaration-pinning.d.ts.map +1 -1
- package/src/extension/declaration-pinning.js +24 -3
- package/src/extension/declaration-pinning.js.map +1 -1
- package/src/extension/index.d.ts +0 -1
- package/src/extension/index.d.ts.map +1 -1
- package/src/extension/index.js +73 -18
- package/src/extension/index.js.map +1 -1
- package/src/extension/public-execution.d.ts +1 -0
- package/src/extension/public-execution.d.ts.map +1 -1
- package/src/extension/public-execution.js +3 -0
- package/src/extension/public-execution.js.map +1 -1
- package/src/extension/rpc.d.ts.map +1 -1
- package/src/extension/rpc.js +5 -3
- package/src/extension/rpc.js.map +1 -1
- package/src/extension/schemas.d.ts +2 -0
- package/src/extension/schemas.d.ts.map +1 -1
- package/src/extension/schemas.js +1 -0
- package/src/extension/schemas.js.map +1 -1
- package/src/extension/tool-activation.d.ts.map +1 -1
- package/src/extension/tool-activation.js +25 -6
- package/src/extension/tool-activation.js.map +1 -1
- package/src/intercom/native-supervisor-channel.d.ts +18 -2
- package/src/intercom/native-supervisor-channel.d.ts.map +1 -1
- package/src/intercom/native-supervisor-channel.js +130 -15
- package/src/intercom/native-supervisor-channel.js.map +1 -1
- package/src/runs/background/active-async-capacity.d.ts +2 -1
- package/src/runs/background/active-async-capacity.d.ts.map +1 -1
- package/src/runs/background/active-async-capacity.js +10 -6
- package/src/runs/background/active-async-capacity.js.map +1 -1
- package/src/runs/background/async-execution.js +3 -3
- package/src/runs/background/async-execution.js.map +1 -1
- package/src/runs/background/async-resume.d.ts.map +1 -1
- package/src/runs/background/async-resume.js +5 -2
- package/src/runs/background/async-resume.js.map +1 -1
- package/src/runs/background/async-status-snapshot.d.ts.map +1 -1
- package/src/runs/background/async-status-snapshot.js +4 -2
- package/src/runs/background/async-status-snapshot.js.map +1 -1
- package/src/runs/background/async-status.d.ts.map +1 -1
- package/src/runs/background/async-status.js +10 -6
- package/src/runs/background/async-status.js.map +1 -1
- package/src/runs/background/chain-root-attachment.js +4 -4
- package/src/runs/background/chain-root-attachment.js.map +1 -1
- package/src/runs/background/control-channel.d.ts +1 -0
- package/src/runs/background/control-channel.d.ts.map +1 -1
- package/src/runs/background/control-channel.js +27 -3
- package/src/runs/background/control-channel.js.map +1 -1
- package/src/runs/background/inspect-rpc.d.ts +5 -0
- package/src/runs/background/inspect-rpc.d.ts.map +1 -1
- package/src/runs/background/inspect-rpc.js +20 -3
- package/src/runs/background/inspect-rpc.js.map +1 -1
- package/src/runs/background/notify.d.ts +1 -1
- package/src/runs/background/notify.d.ts.map +1 -1
- package/src/runs/background/notify.js +8 -4
- package/src/runs/background/notify.js.map +1 -1
- package/src/runs/background/result-delivery-ownership.d.ts +0 -2
- package/src/runs/background/result-delivery-ownership.d.ts.map +1 -1
- package/src/runs/background/result-delivery-ownership.js +5 -21
- package/src/runs/background/result-delivery-ownership.js.map +1 -1
- package/src/runs/background/result-files.d.ts +5 -3
- package/src/runs/background/result-files.d.ts.map +1 -1
- package/src/runs/background/result-files.js +10 -6
- package/src/runs/background/result-files.js.map +1 -1
- package/src/runs/background/result-watcher.d.ts.map +1 -1
- package/src/runs/background/result-watcher.js +15 -8
- package/src/runs/background/result-watcher.js.map +1 -1
- package/src/runs/background/resume-guidance.d.ts.map +1 -1
- package/src/runs/background/resume-guidance.js +3 -2
- package/src/runs/background/resume-guidance.js.map +1 -1
- package/src/runs/background/run-child-session.d.ts.map +1 -1
- package/src/runs/background/run-child-session.js +13 -17
- package/src/runs/background/run-child-session.js.map +1 -1
- package/src/runs/background/run-status.d.ts.map +1 -1
- package/src/runs/background/run-status.js +11 -4
- package/src/runs/background/run-status.js.map +1 -1
- package/src/runs/background/runner-aliases.d.ts.map +1 -1
- package/src/runs/background/runner-aliases.js +11 -8
- package/src/runs/background/runner-aliases.js.map +1 -1
- package/src/runs/background/subagent-runner.d.ts.map +1 -1
- package/src/runs/background/subagent-runner.js +212 -283
- package/src/runs/background/subagent-runner.js.map +1 -1
- package/src/runs/background/workflow-terminal-proof.d.ts +1 -1
- package/src/runs/background/workflow-terminal-proof.d.ts.map +1 -1
- package/src/runs/background/workflow-terminal-proof.js +12 -3
- package/src/runs/background/workflow-terminal-proof.js.map +1 -1
- package/src/runs/foreground/async-dismiss-action.d.ts.map +1 -1
- package/src/runs/foreground/async-dismiss-action.js +2 -1
- package/src/runs/foreground/async-dismiss-action.js.map +1 -1
- package/src/runs/foreground/async-steering-action.d.ts.map +1 -1
- package/src/runs/foreground/async-steering-action.js +3 -2
- package/src/runs/foreground/async-steering-action.js.map +1 -1
- package/src/runs/foreground/async-stop-action.d.ts +1 -0
- package/src/runs/foreground/async-stop-action.d.ts.map +1 -1
- package/src/runs/foreground/async-stop-action.js +9 -8
- package/src/runs/foreground/async-stop-action.js.map +1 -1
- package/src/runs/foreground/command-action.d.ts.map +1 -1
- package/src/runs/foreground/command-action.js +2 -1
- package/src/runs/foreground/command-action.js.map +1 -1
- package/src/runs/foreground/execution.d.ts.map +1 -1
- package/src/runs/foreground/execution.js +67 -60
- package/src/runs/foreground/execution.js.map +1 -1
- package/src/runs/foreground/subagent-executor.d.ts +2 -0
- package/src/runs/foreground/subagent-executor.d.ts.map +1 -1
- package/src/runs/foreground/subagent-executor.js +124 -67
- package/src/runs/foreground/subagent-executor.js.map +1 -1
- package/src/runs/shared/async-status-projection.d.ts +4 -0
- package/src/runs/shared/async-status-projection.d.ts.map +1 -1
- package/src/runs/shared/async-status-projection.js +23 -1
- package/src/runs/shared/async-status-projection.js.map +1 -1
- package/src/runs/shared/child-commands.d.ts +2 -0
- package/src/runs/shared/child-commands.d.ts.map +1 -1
- package/src/runs/shared/child-commands.js +4 -0
- package/src/runs/shared/child-commands.js.map +1 -1
- package/src/runs/shared/child-runtime-config.d.ts +6 -0
- package/src/runs/shared/child-runtime-config.d.ts.map +1 -1
- package/src/runs/shared/child-runtime-config.js +6 -0
- package/src/runs/shared/child-runtime-config.js.map +1 -1
- package/src/runs/shared/child-session.d.ts +2 -2
- package/src/runs/shared/child-session.d.ts.map +1 -1
- package/src/runs/shared/child-session.js +104 -42
- package/src/runs/shared/child-session.js.map +1 -1
- package/src/runs/shared/effective-system-prompt.d.ts +4 -4
- package/src/runs/shared/effective-system-prompt.d.ts.map +1 -1
- package/src/runs/shared/effective-system-prompt.js +5 -4
- package/src/runs/shared/effective-system-prompt.js.map +1 -1
- package/src/runs/shared/external-cli-runner.d.ts.map +1 -1
- package/src/runs/shared/external-cli-runner.js +3 -2
- package/src/runs/shared/external-cli-runner.js.map +1 -1
- package/src/runs/shared/long-running-guard.d.ts +3 -1
- package/src/runs/shared/long-running-guard.d.ts.map +1 -1
- package/src/runs/shared/long-running-guard.js +4 -14
- package/src/runs/shared/long-running-guard.js.map +1 -1
- package/src/runs/shared/model-resolution-diagnostic.d.ts +7 -5
- package/src/runs/shared/model-resolution-diagnostic.d.ts.map +1 -1
- package/src/runs/shared/model-resolution-diagnostic.js +13 -10
- package/src/runs/shared/model-resolution-diagnostic.js.map +1 -1
- package/src/runs/shared/model-resolution.d.ts +5 -1
- package/src/runs/shared/model-resolution.d.ts.map +1 -1
- package/src/runs/shared/model-resolution.js +8 -1
- package/src/runs/shared/model-resolution.js.map +1 -1
- package/src/runs/shared/nested-events.d.ts.map +1 -1
- package/src/runs/shared/nested-events.js +2 -0
- package/src/runs/shared/nested-events.js.map +1 -1
- package/src/runs/shared/orca-progress-tabs.js +1 -1
- package/src/runs/shared/orca-progress-tabs.js.map +1 -1
- package/src/runs/shared/partial-output.d.ts +7 -0
- package/src/runs/shared/partial-output.d.ts.map +1 -1
- package/src/runs/shared/partial-output.js +7 -0
- package/src/runs/shared/partial-output.js.map +1 -1
- package/src/runs/shared/run-ownership.d.ts +4 -0
- package/src/runs/shared/run-ownership.d.ts.map +1 -0
- package/src/runs/shared/run-ownership.js +9 -0
- package/src/runs/shared/run-ownership.js.map +1 -0
- package/src/runs/shared/single-output.d.ts +11 -1
- package/src/runs/shared/single-output.d.ts.map +1 -1
- package/src/runs/shared/single-output.js +48 -26
- package/src/runs/shared/single-output.js.map +1 -1
- package/src/runs/shared/structured-output.d.ts +2 -0
- package/src/runs/shared/structured-output.d.ts.map +1 -1
- package/src/runs/shared/structured-output.js +9 -0
- package/src/runs/shared/structured-output.js.map +1 -1
- package/src/runs/shared/subagent-control.d.ts +1 -0
- package/src/runs/shared/subagent-control.d.ts.map +1 -1
- package/src/runs/shared/subagent-control.js +2 -1
- package/src/runs/shared/subagent-control.js.map +1 -1
- package/src/runs/shared/subagent-prompt-runtime.d.ts +56 -0
- package/src/runs/shared/subagent-prompt-runtime.d.ts.map +1 -1
- package/src/runs/shared/subagent-prompt-runtime.js +192 -6
- package/src/runs/shared/subagent-prompt-runtime.js.map +1 -1
- package/src/runs/shared/tool-budget.js +1 -1
- package/src/runs/shared/tool-budget.js.map +1 -1
- package/src/shared/child-transcript.js +1 -1
- package/src/shared/child-transcript.js.map +1 -1
- package/src/shared/extension-context.d.ts +5 -0
- package/src/shared/extension-context.d.ts.map +1 -1
- package/src/shared/extension-context.js +7 -0
- package/src/shared/extension-context.js.map +1 -1
- package/src/shared/file-lease.d.ts +7 -1
- package/src/shared/file-lease.d.ts.map +1 -1
- package/src/shared/file-lease.js +8 -2
- package/src/shared/file-lease.js.map +1 -1
- package/src/shared/fork-context.d.ts +3 -1
- package/src/shared/fork-context.d.ts.map +1 -1
- package/src/shared/fork-context.js +23 -13
- package/src/shared/fork-context.js.map +1 -1
- package/src/shared/parent-wake.d.ts +6 -0
- package/src/shared/parent-wake.d.ts.map +1 -1
- package/src/shared/parent-wake.js +11 -0
- package/src/shared/parent-wake.js.map +1 -1
- package/src/shared/session-succession.d.ts +5 -0
- package/src/shared/session-succession.d.ts.map +1 -0
- package/src/shared/session-succession.js +25 -0
- package/src/shared/session-succession.js.map +1 -0
- package/src/shared/types.d.ts +22 -9
- package/src/shared/types.d.ts.map +1 -1
- package/src/shared/types.js +7 -4
- package/src/shared/types.js.map +1 -1
- package/src/shared/utils.d.ts +13 -0
- package/src/shared/utils.d.ts.map +1 -1
- package/src/shared/utils.js +19 -0
- package/src/shared/utils.js.map +1 -1
- package/src/slash/slash-commands.d.ts.map +1 -1
- package/src/slash/slash-commands.js +22 -11
- package/src/slash/slash-commands.js.map +1 -1
- package/src/slash/subagents-admin.d.ts.map +1 -1
- package/src/slash/subagents-admin.js +3 -2
- package/src/slash/subagents-admin.js.map +1 -1
- package/src/tui/fleet-status.d.ts +5 -0
- package/src/tui/fleet-status.d.ts.map +1 -1
- package/src/tui/fleet-status.js +24 -3
- package/src/tui/fleet-status.js.map +1 -1
- package/src/tui/fleet.js +2 -2
- package/src/tui/fleet.js.map +1 -1
- package/src/watchdog/change-signature.d.ts.map +1 -1
- package/src/watchdog/change-signature.js +1 -3
- package/src/watchdog/change-signature.js.map +1 -1
- package/src/watchdog/register-main.js +2 -2
- package/src/watchdog/register-main.js.map +1 -1
- package/src/watchdog/runtime.d.ts.map +1 -1
- package/src/watchdog/runtime.js +3 -5
- package/src/watchdog/runtime.js.map +1 -1
- package/src/workflows/scripted-workflow.d.ts +1 -1
- package/src/workflows/scripted-workflow.d.ts.map +1 -1
- package/src/workflows/scripted-workflow.js +13 -4
- package/src/workflows/scripted-workflow.js.map +1 -1
- package/src/workflows/workflow-child-summary.d.ts +5 -0
- package/src/workflows/workflow-child-summary.d.ts.map +1 -1
- package/src/workflows/workflow-child-summary.js +10 -0
- package/src/workflows/workflow-child-summary.js.map +1 -1
- package/src/workflows/workflow-reuse.d.ts +6 -1
- package/src/workflows/workflow-reuse.d.ts.map +1 -1
- package/src/workflows/workflow-reuse.js +22 -1
- package/src/workflows/workflow-reuse.js.map +1 -1
- package/src/runs/shared/run-history.d.ts +0 -67
- package/src/runs/shared/run-history.d.ts.map +0 -1
- package/src/runs/shared/run-history.js +0 -270
- package/src/runs/shared/run-history.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,98 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.79.0] - 2026-10-17
|
|
6
|
+
|
|
7
|
+
### Highlights
|
|
8
|
+
|
|
9
|
+
- Handoffs keep your background work. A session that takes over from another one in the same Pi process can see, wait on, steer, stop and resume the old session's background runs, and a stopped workflow can be picked up with `reuseFrom` without running its finished children again.
|
|
10
|
+
- Coordinators can give a finished worker another round. Resuming a finished background child, a forked child, or a child that was already resumed once now works instead of failing with "session file is not under that nested run's session directory".
|
|
11
|
+
- Fewer wasted tokens. Forked subagents reuse the parent's prompt cache, `/subagent-cost` and other view-only reports no longer ride along in every request, and codemode no longer breaks the cache on every resume.
|
|
12
|
+
- `subagent` survives updates. Updating or reloading pi-subagents mid-session no longer leaves `subagent` or `bg_wait` turned off.
|
|
13
|
+
- RPC hosts see more: each background child's agent, run id, requested model, and the model that actually answered.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Inspect replies (`/subagents-inspect-rpc`) now include `resolvedModel: { provider, model }`, the provider and model of the inspected child's latest reply. Before, a host could only see the requested model. The field is missing until the child replies, for a whole workflow or multi-step run, and for runs from before this release. A forked child's inherited history never counts. Thanks to [@koenvg](https://github.com/koenvg) for reporting [#2922](https://github.com/nicobailon/pi-subagents/issues/2922).
|
|
18
|
+
- The RPC `subagent-async` status snapshot now includes each background step's `model`, `thinking` level and `agent`. A workflow lane's label names the lane, not the agent, so hosts could not name a running child until it finished. Thanks to [@agustinsacco](https://github.com/agustinsacco) for [#2893](https://github.com/nicobailon/pi-subagents/pull/2893) ([#2892](https://github.com/nicobailon/pi-subagents/issues/2892)) and [@kevinsleegerslameco](https://github.com/kevinsleegerslameco) for [#2912](https://github.com/nicobailon/pi-subagents/pull/2912) ([#2911](https://github.com/nicobailon/pi-subagents/issues/2911)).
|
|
19
|
+
- In a workflow that waits for its result, a background child's `workflowChildren` row now carries its `agent` and `runId` from launch, and that `runId` matches the child's run in the `subagent-async` snapshot. Before, the row had only `{ childId, state }` until the child finished. Thanks to [@kevinsleegerslameco](https://github.com/kevinsleegerslameco) for reporting [#2911](https://github.com/nicobailon/pi-subagents/issues/2911).
|
|
20
|
+
- After a handoff, a relaunched workflow can reuse the work of a run that was stopped because the old session's runtime was replaced: pass `options: { reuseFrom: "<run id>" }`. Finished children are reused and running ones are awaited, even from another session, as long as the run ran the same script in the same cwd. Before, relaunching ran every child again, so two writers could share one output. The stop notice names the option and the run id. Thanks to [@wireknight](https://github.com/wireknight) for reporting [#2909](https://github.com/nicobailon/pi-subagents/issues/2909).
|
|
21
|
+
- A session that takes over from another one in the same Pi process, for example after a handoff to a new session file, now owns the old session's background runs. They show in its run list, fleet view and RPC snapshot. It can wait on, steer, stop, resume and inspect them, and it gets their notices and answers their supervisor questions. `status` shows `Owner: inherited from session '…' (replaced in this process).` for these runs. A fresh start, `pi --continue`, or another Pi process inherits nothing. Thanks to [@wireknight](https://github.com/wireknight) for reporting [#2909](https://github.com/nicobailon/pi-subagents/issues/2909).
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- The pi-subagents skill now says that `subagent`, `subagents_enable`, `subagent_supervisor`, `contact_supervisor` and `structured_output` can't be called from codemode scripts. With codemode on, models used to try `tools.subagent_supervisor(...)` and get a "does not exist" error. Thanks to [@CovetingEpiphany2152](https://github.com/CovetingEpiphany2152) for the report. ([#2887](https://github.com/nicobailon/pi-subagents/issues/2887))
|
|
26
|
+
- Replying to a supervisor question that was already answered, expired or withdrawn now lists the questions still pending, so the parent can answer a newer one without another `pending` call. Thanks to [@agustinsacco](https://github.com/agustinsacco) for [#2895](https://github.com/nicobailon/pi-subagents/pull/2895). ([#2894](https://github.com/nicobailon/pi-subagents/issues/2894))
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- A coordinator could not resume its own finished background child: `resume` failed with "session file is not under that nested run's session directory", so it had to start a new child and send the brief again. The same happened for forked children (`context: "fork"`) and for a child that had already been resumed once. All three now resume, with the same ownership checks as before. Background and forked children now keep their session in a folder named by their run id. Children launched before this update still can't be resumed. Thanks to [@mannainsect](https://github.com/mannainsect) for reporting [#2924](https://github.com/nicobailon/pi-subagents/issues/2924) and [@GeorgeLautenschlager](https://github.com/GeorgeLautenschlager) for showing it outside workflows. ([#2932](https://github.com/nicobailon/pi-subagents/issues/2932))
|
|
31
|
+
- A coordinator child (one that delegates its own work) stayed stuck in `bg_wait` when one of its children asked it a question with `contact_supervisor`, so the questions expired unanswered. Its `bg_wait` now returns as soon as a question is waiting, as the parent's already does. Thanks to [@mannainsect](https://github.com/mannainsect) for reporting [#2923](https://github.com/nicobailon/pi-subagents/issues/2923).
|
|
32
|
+
- A child's `contact_supervisor` question could expire before the parent saw it when the parent was busy in a long tool call, so the child gave up and asked again and the parent's late reply failed. The question's deadline now moves forward until the parent's model sees it. Thanks to [@agustinsacco](https://github.com/agustinsacco) for the report. ([#2894](https://github.com/nicobailon/pi-subagents/issues/2894))
|
|
33
|
+
- When a handoff or reload stopped an async workflow, its still-running children were listed as `stopped`, its notice didn't say that reusing finished children works only from the same session, and `status` offered a `Revive:` command to sessions that can't use it. The children now show as `running`, the notice explains how the same session and another session can reuse them, and `status` offers resume only to the session that launched the run. Thanks to [@wireknight](https://github.com/wireknight) for reporting [#2909](https://github.com/nicobailon/pi-subagents/issues/2909).
|
|
34
|
+
- After pi-subagents was updated or reloaded mid-session, `subagent` stayed off once the session started again (`/reload` or `pi --continue`), and `subagents_enable` wasn't offered either, so nothing could turn it back on. Pi records the unload as a removal of the extension's tools, and that was replayed as if you had turned `subagent` off. An unload is now recognized and the tools come back as you had them. Thanks to [@bkdinoop](https://github.com/bkdinoop) for reporting [#2910](https://github.com/nicobailon/pi-subagents/issues/2910).
|
|
35
|
+
- After such an update, a session continued in a new process (`pi --continue`) also kept `bg_wait` off, and a second update in that session lost `subagent` again. The tools the update removed now come back when the session starts, unless they are excluded for this process. ([#2916](https://github.com/nicobailon/pi-subagents/issues/2916))
|
|
36
|
+
- Forked subagents now reuse the parent's prompt cache. A fork used to replace the parent's system prompt and tools, so its first request re-read the whole conversation. It now sends the parent's system prompt, tools and messages unchanged, and gets its own instructions in a `<subagent_instructions>` block at the start of its task. A fork also sees the parent's project context and skills even when its agent turns them off. On Claude with a 60k-token parent, a fork's first request went from 9.6k cached and 52k written tokens to 60.8k cached and 1.7k written. See [tool-reference.md](docs/tool-reference.md#fork-context-details) for the forks that still miss the cache. Fresh children are unchanged. Thanks to [@gabsn](https://github.com/gabsn) for [#2879](https://github.com/nicobailon/pi-subagents/pull/2879).
|
|
37
|
+
- `/subagent-cost` and other view-only slash reports no longer go into every later model request. They are still saved, shown in the TUI and sent to RPC callers. `/run` results stay in context because the model needs them. Reports saved before this update stay until compaction, so updating doesn't break a session's prompt cache. Thanks to [@isjiajia01](https://github.com/isjiajia01) for [#2927](https://github.com/nicobailon/pi-subagents/pull/2927) and [@summer-tt](https://github.com/summer-tt) for the report. ([#2925](https://github.com/nicobailon/pi-subagents/issues/2925))
|
|
38
|
+
- With Pi's codemode on, every start or resume of a session added another copy of codemode's note to the `bg_wait` description, so the provider re-read the conversation without its prompt cache. The note now appears once and a resumed session keeps its tool declaration. Thanks to [@SamuelBadr](https://github.com/SamuelBadr) for the report. ([#2889](https://github.com/nicobailon/pi-subagents/issues/2889))
|
|
39
|
+
- When a provider stopped a child with an error, such as a content-filter refusal, the child reported only the stop code. Its error now includes the provider's reply text as `Reply text: …` (up to 500 characters), so a refusal can be told apart from an outage. In a failed workflow, each failed child's line also starts with its own error instead of the `Run fan-out: N/64 used` line. Thanks to [@stanleytelehealth](https://github.com/stanleytelehealth) for [#2913](https://github.com/nicobailon/pi-subagents/pull/2913).
|
|
40
|
+
- A read-only child running `grep … 2>/dev/null` could be flagged `needs_attention`: the redirect counted as a file change, and output containing words like "error" counted as a failure. Redirects to `/dev/null` no longer count as changes, and a tool call counts as failed only when its result says `isError: true`. Calls blocked by a hard `toolBudget` don't count either. Thanks to [@Liaokc](https://github.com/Liaokc) for the fix and [@builtroller](https://github.com/builtroller) for the report. ([#2878](https://github.com/nicobailon/pi-subagents/issues/2878), [#2884](https://github.com/nicobailon/pi-subagents/issues/2884))
|
|
41
|
+
- A notice about a long-running `bash` call in a child suggested `command.status`, `command.yield` and `command.cancel` even when the child had no command controller, so following it failed with "No command controller is available for this child." The notice now suggests them only when they work. Thanks to [@G0-0000](https://github.com/G0-0000) for [#2882](https://github.com/nicobailon/pi-subagents/pull/2882). ([#2880](https://github.com/nicobailon/pi-subagents/issues/2880))
|
|
42
|
+
- On Pi 1.2 and later, the watchdog took the system prompt of a run started by an extension message (for example a wake sent with `triggerTurn: true`) as your prompt and showed it to its reviewer model as a real requirement. These runs no longer add to the watchdog's scope, and their edits are still reviewed against your earlier prompts. Thanks to [@isjiajia01](https://github.com/isjiajia01) for [#2928](https://github.com/nicobailon/pi-subagents/pull/2928) and [@sternnick](https://github.com/sternnick) for reporting [#2926](https://github.com/nicobailon/pi-subagents/issues/2926).
|
|
43
|
+
- A child using built-in MCP tools failed after 10 seconds even when its server was still connecting normally. It now waits for the connection, fails promptly when a connection fails, and stops waiting when the child is stopped or times out. This needs Pi 0.99.2 or later. Thanks to [@qsgy-edge](https://github.com/qsgy-edge) for [#2904](https://github.com/nicobailon/pi-subagents/pull/2904).
|
|
44
|
+
- An extension that force-exits a headless run on quit could kill the background runner when one of its children ended, so the run failed with "Async runner process N exited with code 0 before writing a result". The runner now sets `PI_SUBAGENT_RUNNER_PID` to its own pid, so an extension can compare it with `process.pid` and leave the runner alone. Processes spawned from a child inherit the value but have their own pid. See [extension-api.md](docs/extension-api.md#background-work-provider-api). Thanks to [@hughy](https://github.com/hughy) for [#2885](https://github.com/nicobailon/pi-subagents/pull/2885). ([#2883](https://github.com/nicobailon/pi-subagents/issues/2883))
|
|
45
|
+
- After Pi auto-compacted in the middle of a turn, the async and Fleet status widgets stayed hidden until the whole run ended. They now come back as soon as the compaction finishes. Thanks to [@agustinsacco](https://github.com/agustinsacco) for [#2891](https://github.com/nicobailon/pi-subagents/pull/2891). ([#2890](https://github.com/nicobailon/pi-subagents/issues/2890))
|
|
46
|
+
- When a background subagent finished, the parent often logged `Failed to acknowledge the mission observer ... Timed out waiting for another process to finish updating` with a stack trace and stalled for 200 ms. It now retries quietly a moment later. Thanks to [@RainbowXie](https://github.com/RainbowXie) for the report. ([#2896](https://github.com/nicobailon/pi-subagents/issues/2896))
|
|
47
|
+
- A finished mission run from an earlier Pi process in the same session was synced to its mission again every 60 seconds, rewriting the mission record each time. It is now synced once. Thanks to [@BelovedYaoo](https://github.com/BelovedYaoo) for reporting [#2905](https://github.com/nicobailon/pi-subagents/issues/2905).
|
|
48
|
+
|
|
49
|
+
## [0.78.0] - 2026-10-12
|
|
50
|
+
|
|
51
|
+
### Highlights
|
|
52
|
+
|
|
53
|
+
- Background subagents work on Pi 1.2.0 again. With 0.77.0 they failed to start on that Pi version.
|
|
54
|
+
- Pressing `Esc` now keeps the parent agent stopped: a background subagent that finishes a moment later no longer starts it up again. `/subagents-stop` can also stop every running background subagent at once.
|
|
55
|
+
- Fewer tokens spent re-reading the conversation: forked subagents, staged resumes, and the resume after `/compact` now reuse the provider's prompt cache.
|
|
56
|
+
- Reports are no longer lost. Resuming a run saves a new report next to the old one instead of overwriting it, and a timed-out child's partial output now reaches the parent.
|
|
57
|
+
- Sessions that run workflows no longer run out of background run slots after a child is stopped or fails to launch.
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
|
|
61
|
+
- New `pinToolDeclarations` config key. A long-running session keeps the `subagent` tool definition it started with, so an edited `subagent-tool-description.md`, a `toolDescriptionMode` change, or a package upgrade did not reach it until you started a new session. Set `pinToolDeclarations: false` (the default is `true`) and a resumed session picks up the current definitions on its next restart, at the cost of one prompt-cache miss. Thanks to [@advaitpaliwal](https://github.com/advaitpaliwal) for [#2853](https://github.com/nicobailon/pi-subagents/pull/2853). ([#2852](https://github.com/nicobailon/pi-subagents/issues/2852))
|
|
62
|
+
- New `activeRunsPrompt` config key, off by default. A parent agent heard about its background subagents only from completion notices or a `status` call, so a few turns later it could forget one was running, launch the same work again, or say nothing was running. With this on, the parent's system prompt lists each queued or running background run (id, agents, mode, and chain step) until the last one finishes. Thanks to [@advaitpaliwal](https://github.com/advaitpaliwal) for the request. ([#2831](https://github.com/nicobailon/pi-subagents/issues/2831))
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
|
|
66
|
+
- After you press `Esc` to stop the parent, a background subagent that finishes or asks for input no longer starts a new parent turn, so the agent you just stopped stays stopped. Its result or request is added to the session, and the parent sees it with your next prompt. A completion notice added this way tells the parent not to resume the stopped task unless you ask. Background runs keep running, and the TUI shows how many are still active and points to `/subagents-stop`.
|
|
67
|
+
- `/subagents-stop` now starts with a "stop all" row when two or more background runs from this session are active, so you can stop them all after one confirmation instead of one at a time. Scheduled runs are not paused.
|
|
68
|
+
- A forked subagent's first request re-sent the parent's whole conversation without the prompt cache, because the child's tools and history differed from the parent's. A forked child now declares the same tools as its parent (a tool it may not use fails with "not found" if called) and inherits the history exactly as the parent sent it. A session with the `subagent` tool also declares `contact_supervisor`, which returns an error if the parent calls it. A fork launched through `subagent` can now reuse the parent's prompt cache; one launched by a slash command before `subagent` is enabled still starts uncached. A session that already has `subagent` when you update may miss the prompt cache once, on its next request.
|
|
69
|
+
- The built-in `reviewer`, `oracle`, and `evidence-auditor` agents are no longer asked to end with an acceptance report when the call has no `acceptance`, since that report is meant for agents that edit files. Pass `acceptance` to still get one. Without it these runs have no child report, so `/subagents-refine` no longer sees their review findings. Thanks to [@8FAX](https://github.com/8FAX) for finding it. ([#2865](https://github.com/nicobailon/pi-subagents/issues/2865))
|
|
70
|
+
- pi-subagents no longer writes `run-history.jsonl`. The file got one line per run, grew without limit, and nothing read it. You can delete an existing `run-history.jsonl` from your Pi agent directory (`~/.pi/agent/` or `PI_CODING_AGENT_DIR`). Thanks to [@nycruslan](https://github.com/nycruslan) for the report. ([#2864](https://github.com/nicobailon/pi-subagents/issues/2864))
|
|
71
|
+
- The pi-subagents skill now says that `worktree: true` refuses to launch from a checkout with uncommitted or untracked changes, and what to do in a shared or dirty checkout. Agents used to learn this only from a failed launch. Thanks to [@jmclaughlin724](https://github.com/jmclaughlin724) for the report. ([#2851](https://github.com/nicobailon/pi-subagents/issues/2851))
|
|
72
|
+
|
|
73
|
+
### Fixed
|
|
74
|
+
|
|
75
|
+
- Background subagents failed to start on Pi 1.2.0 with "does not provide @earendil-works/chord, @earendil-works/chord/context". Pi 1.2.0 no longer ships that package, but pi-subagents required it on every Pi from 0.85 on. It is now required only when the installed Pi declares it. Thanks to [@tlitovsk](https://github.com/tlitovsk) and [@amchen2310](https://github.com/amchen2310) for the reports, and to [@ArdalanM](https://github.com/ArdalanM) for the fix. ([#2854](https://github.com/nicobailon/pi-subagents/issues/2854), [#2855](https://github.com/nicobailon/pi-subagents/pull/2855))
|
|
76
|
+
- A finished workflow kept its `maxActiveAsyncRunsPerSession` slot for the rest of the session, so later launches were rejected, when one of its background children had been stopped or had finished after the Pi process that launched it exited. Such a workflow is now released after `capacity.abandonedSlotReleaseAfterMs`, the same as one whose child failed. Top-level runs still release this way only when they failed. Thanks to [@nycruslan](https://github.com/nycruslan) and [@builtroller](https://github.com/builtroller) for the reports. ([#2863](https://github.com/nicobailon/pi-subagents/issues/2863))
|
|
77
|
+
- An async workflow kept its `maxActiveAsyncRunsPerSession` slot for as long as its session lived when one of its children failed to launch, for example because of an unknown model. The slot is now released once the workflow ends. Thanks to [@nycruslan](https://github.com/nycruslan) for the report and the fix. ([#2856](https://github.com/nicobailon/pi-subagents/issues/2856))
|
|
78
|
+
- A child stopped by a hard `toolBudget` block was not reported as blocked. A delegated foreground run said `completed` instead of `tool_budget_exhausted`, a workflow child got no `budget_exhausted` outcome, a background run's live status never showed the block, and the notice about a child repeatedly failing to change files never appeared. Tool results are now read the way Pi reports them. Thanks to [@adameq](https://github.com/adameq) for the report and the diagnosis. ([#2846](https://github.com/nicobailon/pi-subagents/issues/2846))
|
|
79
|
+
- A `toolBudget` state reported `outcome: "hard-blocked"` as soon as the tool count passed `hard`, even when `block` listed only some tools and none had been blocked. It now reports `hard-reached` until a call is actually blocked, then `hard-blocked` with `blockedTool`. Thanks to [@adameq](https://github.com/adameq) for spotting it. ([#2866](https://github.com/nicobailon/pi-subagents/issues/2866))
|
|
80
|
+
- A child resumed for a later stage with a new output path re-sent its whole conversation without the prompt cache, because the output path was part of its system prompt. Through `pi-claude-bridge`, one staged run re-cached about 1.65M tokens this way. The output path now goes only in the task message, so the system prompt stays the same across stages.
|
|
81
|
+
- After a manual `/compact` with background subagents still running, the parent's next turn went out without the prompt sections that extensions add at the start of a turn, so the provider re-read the whole conversation without its prompt cache. Pi reports the compaction before it is ready for a new turn ([earendil-works/pi#5581](https://github.com/earendil-works/pi/issues/5581)). The resume now waits until the parent is idle, and it is dropped if another turn starts first, the session ends, or the parent is still busy after 10 seconds. Thanks to [@rnavarro](https://github.com/rnavarro) for [#2812](https://github.com/nicobailon/pi-subagents/pull/2812).
|
|
82
|
+
- `subagent({ action: "resume", id, message })` without `output` reused the original run's output path, so the revived run overwrote the earlier report. A resume without its own `output`, including a `steer` that revives a finished child, now saves to a new file next to the original, named with the new run id (for example `report.1a2b3c4d.md`), and its completion notice names that file. An explicit `output` still wins, and resumes inside a workflow script still use the workflow's own output paths. ([#2837](https://github.com/nicobailon/pi-subagents/issues/2837))
|
|
83
|
+
- A workflow stage that resumed a run started outside the workflow, without its own `output`, wrote its report over that run's saved report. It now writes to its own default output path, so the earlier report is kept. ([#2839](https://github.com/nicobailon/pi-subagents/issues/2839))
|
|
84
|
+
- A background run with a required `file-only` output and valid structured output failed with "Required file-only output was not produced" when its final message held only an acceptance report, and the JSON was never written. It is now written. If a required `file-only` output cannot be written, for example because of a permission error, the run now fails instead of finishing with the report inline. Thanks to [@8FAX](https://github.com/8FAX) for the fix. ([#2850](https://github.com/nicobailon/pi-subagents/pull/2850))
|
|
85
|
+
- When a background child timed out mid-reply, the completion notice and `status` showed only `Subagent timed out after …ms.`, so the parent never saw the text the child had already written. The notice now shows the first 2,000 characters of that partial output and the path of the file with the full text, followed by the child's recovery summary. Thanks to [@advaitpaliwal](https://github.com/advaitpaliwal) for [#2830](https://github.com/nicobailon/pi-subagents/pull/2830). ([#2829](https://github.com/nicobailon/pi-subagents/issues/2829))
|
|
86
|
+
- Since 0.77.0, apps that run Pi in RPC mode and can draw custom screens, such as pi-web, got the plain-text version of `/subagents-fleet`, `/subagents-stop`, and the `/subagents` model and thinking pickers. Such an app can now set `PI_SUBAGENTS_COMPONENT_UI=1` to get the full screens; plain RPC keeps the text version. Thanks to [@kushaldotdev](https://github.com/kushaldotdev) for the report and the fix. ([#2848](https://github.com/nicobailon/pi-subagents/issues/2848))
|
|
87
|
+
- Every workflow script that launched children in a loop or from its `args` printed a `workflowScript contains dynamic child launches` warning across Pi's input area on each run, although that is the normal way to write a fan-out. Launches no longer print it; `subagent({ action: "validate" })` still lists it under `warnings`. Thanks to [@builtroller](https://github.com/builtroller) for the report and the first fix, and to [@VincentHanxiaoDu](https://github.com/VincentHanxiaoDu) for removing it from launches. ([#2835](https://github.com/nicobailon/pi-subagents/issues/2835), [#2847](https://github.com/nicobailon/pi-subagents/pull/2847))
|
|
88
|
+
- FleetView printed external-run problems, such as `Failed to inspect external jobs: …`, straight over Pi's screen and repeated them on every refresh. In an interactive session each distinct message is now shown once as a TUI warning, and headless runs print it once. A malformed record is now named by its run id. ([#2844](https://github.com/nicobailon/pi-subagents/issues/2844))
|
|
89
|
+
- Opening Pi in a directory whose path contains a non-breaking space logged `Failed to inspect external jobs: External-run snapshot sessionId must contain only display-safe text.` on every refresh. Run and session ids may now contain any text except terminal control, bidirectional, and line-break characters. Thanks to [@louisje](https://github.com/louisje) for the report and the diagnosis. ([#2841](https://github.com/nicobailon/pi-subagents/issues/2841))
|
|
90
|
+
- Workflow scripts that called `Promise.all`, `Promise.allSettled`, `Promise.race`, `Promise.any`, or `Promise.resolve` failed with a "Proxy handler's 'get' result …" error on Pi builds that freeze `Promise`, such as pi-bolt. They now work there. Thanks to [@albertgwo](https://github.com/albertgwo) for [#2828](https://github.com/nicobailon/pi-subagents/pull/2828).
|
|
91
|
+
- Stopping a background subagent with `subagent({ action: "stop" })` produced a notice saying `Subagent stopped by user.` even when the parent agent stopped it, so it read as if you had stepped in. Stop notices for background runs, external CLI runs, and workflow children now say `Subagent stopped.`, `Placed external run stopped.`, or `Workflow child '<key>' stopped.` ([#2826](https://github.com/nicobailon/pi-subagents/issues/2826))
|
|
92
|
+
- When an extension in a background child switched its model, for example a model router acting on the first message, status and FleetView kept naming the launch model. In one session, 14 of 15 children ran on a different model than status showed. Status now names the model the child's replies came from. Reviving the child still starts it on the launch model. Thanks to [@dodo-robot](https://github.com/dodo-robot) for the report and the fix. ([#2824](https://github.com/nicobailon/pi-subagents/issues/2824))
|
|
93
|
+
- A child launched on a virtual model registered with `pi.registerVirtualModel()` failed with "model not found" when the extension that registers it was not loaded in the child, and the explanation suggested the model should have resolved. The explanation now says foreground children inherit providers but not virtual models, and names the fix, `registerRequiredChildExtensions`, next to the agent frontmatter options. Thanks to [@toomanyopenfiles](https://github.com/toomanyopenfiles) for the report. ([#2820](https://github.com/nicobailon/pi-subagents/issues/2820))
|
|
94
|
+
- When a background run kept failing to read its control inbox, such as the `EPERM` errors seen on Windows, it wrote the same stack trace to `runner.stderr.log` on every retry and could fill gigabytes in minutes. A repeating error is now logged once, then once a minute with a repeat count. Retries continue, so queued requests still arrive once the inbox can be read. Thanks to [@infectiousstupidity](https://github.com/infectiousstupidity) for the report. ([#2811](https://github.com/nicobailon/pi-subagents/issues/2811))
|
|
95
|
+
- A child whose structured output used a value outside a schema's `enum` or `const` got only "must be equal to one of the allowed values" or "must be equal to constant". The error now names the allowed values, up to 20 for a long `enum`. Thanks to [@rtbe](https://github.com/rtbe) for the report. ([#2813](https://github.com/nicobailon/pi-subagents/issues/2813))
|
|
96
|
+
|
|
5
97
|
## [0.77.0] - 2026-10-09
|
|
6
98
|
|
|
7
99
|
### Highlights
|
|
@@ -358,6 +450,7 @@
|
|
|
358
450
|
- Logs from running and failed external CLI agents can be viewed in Fleet, tool status, and the TUI. Thanks to [@Shujakuinkuraudo](https://github.com/Shujakuinkuraudo) for [#2375](https://github.com/nicobailon/pi-subagents/issues/2375).
|
|
359
451
|
- Structured delegation updates report cumulative usage and no longer count missing provider cache numbers as zero. Thanks to [@bioShaun](https://github.com/bioShaun) for [#2374](https://github.com/nicobailon/pi-subagents/pull/2374).
|
|
360
452
|
- Forked sessions keep Pi 0.87 context edits, including replaced content and signed Anthropic thinking blocks.
|
|
453
|
+
|
|
361
454
|
## [0.70.1] - 2026-09-20
|
|
362
455
|
|
|
363
456
|
### Highlights
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
name: evidence-auditor
|
|
3
3
|
description: Independent evidence reviewer for checking whether important research claims are supported by their sources
|
|
4
4
|
tools: read, web_search, fetch_content, get_search_content, source_check
|
|
5
|
+
acceptanceRole: read-only
|
|
5
6
|
thinking: high
|
|
6
7
|
systemPromptMode: replace
|
|
7
8
|
inheritProjectContext: true
|
package/agents/oracle.md
CHANGED
|
@@ -3,6 +3,7 @@ name: oracle
|
|
|
3
3
|
aliases: advisor
|
|
4
4
|
description: High-context decision-consistency oracle that protects inherited state and prevents drift
|
|
5
5
|
tools: read, grep, find, ls, bash
|
|
6
|
+
acceptanceRole: read-only
|
|
6
7
|
thinking: high
|
|
7
8
|
systemPromptMode: replace
|
|
8
9
|
inheritProjectContext: true
|
package/agents/reviewer.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
name: reviewer
|
|
3
3
|
description: Versatile review specialist for code diffs, plans, proposed solutions, codebase health, and PR/issue validation
|
|
4
4
|
tools: read, grep, find, ls, watchdog_diff, contact_supervisor
|
|
5
|
+
acceptanceRole: read-only
|
|
5
6
|
thinking: high
|
|
6
7
|
systemPromptMode: replace
|
|
7
8
|
inheritProjectContext: true
|
package/docs/agents.md
CHANGED
|
@@ -359,6 +359,8 @@ Use these fields when an agent should see more:
|
|
|
359
359
|
|
|
360
360
|
Builtin agents opt into repository instruction inheritance by default so they follow repo-specific rules out of the box, but global context remains excluded unless `inheritGlobalContext: true` is set. This changes the behavior of existing agents that previously received global context as part of `inheritProjectContext: true`. `delegate` also uses append mode because its job is orchestration inside the parent workflow.
|
|
361
361
|
|
|
362
|
+
These fields and `systemPromptMode` shape a fresh child's system prompt. A forked child (`context: "fork"`) sends its parent's whole system prompt instead, project context, global context, and skills included, and gets its agent prompt in its task message (see [fork context details](tool-reference.md#fork-context-details)).
|
|
363
|
+
|
|
362
364
|
## Frontmatter reference
|
|
363
365
|
|
|
364
366
|
A full example:
|
|
@@ -537,7 +539,7 @@ Examples:
|
|
|
537
539
|
|
|
538
540
|
### MCP tools
|
|
539
541
|
|
|
540
|
-
When [pi-mcp-adapter](https://github.com/nicobailon/pi-mcp-adapter) is not installed and Pi has built-in MCP (Pi 0.99 or later), `mcp:server` and `mcp:server/tool` entries resolve against Pi's built-in MCP. Name the server and tool as the server reports them: `mcp:my-docs/get-item` selects the tool Pi registers as `mcp__my_docs__get_item`. The server part may also use the `-`→`_` form of its name that Pi's MCP namespace uses, such as `mcp:my_docs`. The child connects the servers in Pi's `mcp.json` (the project's `.pi/mcp.json` only when the parent trusts the project) and gets only the selected tools, as direct tools, whatever exposure the server is configured with; tools configured as `hidden` cannot be selected. Servers that an extension adds with `pi.registerMcpServer()` work in background children, because the registering extension loads there too. They do not work in foreground children, which load no ambient extensions, so a foreground launch that selects one fails and says to use `async: true`. The launch
|
|
542
|
+
When [pi-mcp-adapter](https://github.com/nicobailon/pi-mcp-adapter) is not installed and Pi has built-in MCP (Pi 0.99.2 or later), `mcp:server` and `mcp:server/tool` entries resolve against Pi's built-in MCP. Name the server and tool as the server reports them: `mcp:my-docs/get-item` selects the tool Pi registers as `mcp__my_docs__get_item`. The server part may also use the `-`→`_` form of its name that Pi's MCP namespace uses, such as `mcp:my_docs`. The child connects the servers in Pi's `mcp.json` (the project's `.pi/mcp.json` only when the parent trusts the project) and gets only the selected tools, as direct tools, whatever exposure the server is configured with; tools configured as `hidden` cannot be selected. Servers that an extension adds with `pi.registerMcpServer()` work in background children, because the registering extension loads there too. They do not work in foreground children, which load no ambient extensions, so a foreground launch that selects one fails and says to use `async: true`. The child waits for the selected servers' current MCP connection attempts before its first prompt, then rejects the launch if any selected tool is missing or not available as a direct tool. Pi's MCP request timeout still bounds each connection; stopping, interrupting, timing out, or shutting down the child cancels the startup wait. An unrelated server does not delay this check.
|
|
541
543
|
|
|
542
544
|
When pi-mcp-adapter is installed, it keeps priority and provides the direct MCP tools. Subagents only receive direct MCP tools when `mcp:` entries are listed in their frontmatter; global `directTools: true` in the adapter's MCP config is not enough by itself. The generic `mcp` proxy tool can still be used for discovery when available. The adapter caches tool metadata at startup, so after connecting a new MCP server for the first time, restart Pi before relying on direct tools. Server `includeTools` and `excludeTools` policies are enforced while resolving cached metadata for children: both accept exact names and `*`/`?` glob patterns against raw, generated-resource, and server/short/mcp/none-prefixed names, with `excludeTools` taking precedence. `mcp:` entries must name servers from the adapter's configuration files. A server that exists only in the adapter's runtime snapshot (registered at runtime, not persisted) cannot be provided to a child: children are pi sessions inside the parent or the runner process, not `pi` processes that could receive an MCP config argument, so such a launch fails with an error saying that MCP tools must come from an ambient adapter extension in a background child. An `mcp:` entry named `subagent` does not authorize nested fanout; declare the builtin `subagent` tool or set `allowNestedSubagents: true`. If a resolved direct MCP name is missing from the child registry, pi-subagents keeps the launch failed under the strict allowlist and reports the registration mismatch; check the resolved names against what the host or pi-mcp-adapter actually registers before child startup.
|
|
543
545
|
|
|
@@ -613,7 +615,7 @@ Agent-local `skillPath` candidates never enter Pi's parent/global skills catalog
|
|
|
613
615
|
|
|
614
616
|
## The bundled pi-subagents skill
|
|
615
617
|
|
|
616
|
-
The package bundles a `pi-subagents` skill that is automatically available to the parent agent when the extension is installed. Availability is not automatic routing or permission to delegate: the parent works directly unless the operator requests delegation in the current request or through applicable user/project instructions. Once authorized, use the smallest bounded child or workflow whose evidence, independent review, specialization, parallelism, or isolation benefit earns its overhead. It is for the orchestrating parent only: child subagents never receive it, and
|
|
618
|
+
The package bundles a `pi-subagents` skill that is automatically available to the parent agent when the extension is installed. Availability is not automatic routing or permission to delegate: the parent works directly unless the operator requests delegation in the current request or through applicable user/project instructions. Once authorized, use the smallest bounded child or workflow whose evidence, independent review, specialization, parallelism, or isolation benefit earns its overhead. It is for the orchestrating parent only: fresh child subagents never receive it, and a child's own context is filtered to strip parent-only orchestration instructions. A forked child is the exception: it keeps the history, system prompt, and tool declarations it inherited as the parent sent them, so it sees this skill in the parent's skills list and the parent's subagent catalog. It receives its own instructions, including its subagent boundary, in its task message, and its first request reuses the parent's prompt cache (see [fork context details](tool-reference.md#fork-context-details)).
|
|
617
619
|
|
|
618
620
|
What it covers:
|
|
619
621
|
|
package/docs/configuration.md
CHANGED
|
@@ -80,7 +80,7 @@ A local Pi child whose selected model is a virtual model (`pi.registerVirtualMod
|
|
|
80
80
|
|
|
81
81
|
## Tool activation lifecycle
|
|
82
82
|
|
|
83
|
-
On Pi 0.86.1 or newer, when the model can take a new tool mid-conversation (see [`toolActivation`](#toolactivation)), a fresh unrestricted parent starts with `subagents_enable`, `bg_wait`, and `subagent_supervisor` active while `subagent` stays registered but inactive. Calling `subagents_enable({})` preserves unrelated active tools and exposes `subagent` on the next model request. It does not launch a child or infer authority from prompt keywords. With other models, a fresh parent starts with `subagent`, `bg_wait`, and `subagent_supervisor` active and no `subagents_enable`.
|
|
83
|
+
On Pi 0.86.1 or newer, when the model can take a new tool mid-conversation (see [`toolActivation`](#toolactivation)), a fresh unrestricted parent starts with `subagents_enable`, `bg_wait`, and `subagent_supervisor` active while `subagent` stays registered but inactive. Calling `subagents_enable({})` preserves unrelated active tools and exposes `subagent` on the next model request. It does not launch a child or infer authority from prompt keywords. With other models, a fresh parent starts with `subagent`, `bg_wait`, and `subagent_supervisor` active and no `subagents_enable`. `contact_supervisor` is active whenever `subagent` is, with the definition children use, so a forked child declares the same tools as its parent and can reuse its prompt cache; called in a session that is not a subagent, it returns an error.
|
|
84
84
|
|
|
85
85
|
The recorded native `subagent` selection is restored on resume, reload, and tree navigation, so an activated session stays activated and a cold session stays cold. With the default `toolActivation`, the recorded `subagents_enable` selection is restored too, so a session that started without the loader never gets it later. Older history without tool-selection records keeps eager `subagent` availability. If Pi's allowlist or exclusions remove the loader, the extension does not hide `subagent`; if they remove `subagent`, the loader reports it unavailable. Hosts whose extension API lacks `getAllTools`, `getActiveTools`, or `setActiveTools` keep eager behavior and log one compatibility warning. The host version is not read from disk, so in-process hosts such as pi-web activate the same way as the Pi CLI.
|
|
86
86
|
|
|
@@ -108,7 +108,15 @@ The choice is made at session start or tree navigation, and only for a session w
|
|
|
108
108
|
|
|
109
109
|
Controls the parent-facing `subagent` tool description registered at startup. The default registers the short execution/safety description, which states the delegation authorization rule, plus a one-line `promptSnippet`. Explicit `"compact"` uses the same description without the snippet; `"full"` adds workflow and management detail, also without the snippet. All modes retain the same flat parameter schema. Extended examples and recipes are available on demand through `action:"guide"` and the bundled pi-subagents skill; full mode is not an exhaustive manual. Count the default snippet as well as the tool definition when comparing prompt footprints.
|
|
110
110
|
|
|
111
|
-
`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. The change applies to new sessions; a resumed session keeps the tool definition and prompt text it already declared, so its prompt cache stays valid.
|
|
111
|
+
`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. The change applies to new sessions; a resumed session keeps the tool definition and prompt text it already declared, so its prompt cache stays valid. To apply it to resumed sessions too, set [`pinToolDeclarations`](#pintooldeclarations) to `false`.
|
|
112
|
+
|
|
113
|
+
## `pinToolDeclarations`
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{ "pinToolDeclarations": false }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Pi records each tool definition and prompt section a session declares, and declares any difference again before the next request; a changed definition makes the provider read the whole conversation again without its prompt cache. So by default (`true`) a resumed or reloaded session keeps the `subagent`, `bg_wait`, and `subagents_enable` definitions and the pi-subagents prompt text it already declared, and only a session that never declared a tool gets its current definition. With `false`, every parent session gets the current definitions (subagent children stay pinned): a long-lived session picks up an upgrade, a changed `toolDescriptionMode`, `disabledFeatures`, or an edited custom description on the next restart, and pays one cache miss on its next request. Restart Pi after changing it.
|
|
112
120
|
|
|
113
121
|
## `disabledFeatures`
|
|
114
122
|
|
|
@@ -270,7 +278,7 @@ Controls how resolved fork launches prepare the inherited session. The default `
|
|
|
270
278
|
|
|
271
279
|
Child-visible spilled items contain only the model summary and a stable `{ batchId, itemId }` recovery ref. Raw bodies and their digests, source entry ids, labels, sizes, and tool metadata go to a private `0600` sidecar next to the child session. This release does not add a recovery command or expose that payload to the child model.
|
|
272
280
|
|
|
273
|
-
Pruned forks keep the normal `parentSession` link, child cwd alignment, and fork thinking-block sanitization (signed Anthropic thinking blocks are stripped; the child keeps its requested thinking level). Missing model or auth, invalid or incomplete summary JSON, budget overflow, recovery validation failure, and raw overflow leakage all stop the launch before child spawn. The extension never falls back to a full fork or refs-only context after a prune failure.
|
|
281
|
+
Pruned forks keep the normal `parentSession` link, child cwd alignment, and fork thinking-block sanitization (a pruned fork rewrites inherited content, so its signed and redacted Anthropic thinking blocks are stripped; the child keeps its requested thinking level). Missing model or auth, invalid or incomplete summary JSON, budget overflow, recovery validation failure, and raw overflow leakage all stop the launch before child spawn. The extension never falls back to a full fork or refs-only context after a prune failure.
|
|
274
282
|
|
|
275
283
|
## `fleetView`
|
|
276
284
|
|
|
@@ -353,6 +361,14 @@ Reports are sent only from an interactive TUI session whose standard output is a
|
|
|
353
361
|
|
|
354
362
|
Each record's `title` is the agent names or workflow key, and its `msg` is the current tool while working, or the outcome. Neither includes the task text. Finished runs keep their `done`, `error`, or `idle` record after they leave the async widget, for as long as they are in Fleet's recent history (the last 20 finished runs). Records of runs this session no longer tracks, for example after switching sessions, are cleared. At most 64 records are shown: running and waiting runs first, then finished ones, most recently updated first; older finished records are cleared to make room. When the extension is reloaded or the session is replaced, it clears every record it sent and the new session reports the runs it still tracks. Pi 1.1 and later reports its own state on the root record and clears every record, these included, when it quits, suspends with Ctrl+Z, or opens an external editor. After a suspend, this extension sends its records again; after an external editor, each record comes back the next time it changes. Foreground runs are not reported, and tmux drops these sequences unless you configure passthrough.
|
|
355
363
|
|
|
364
|
+
## `activeRunsPrompt`
|
|
365
|
+
|
|
366
|
+
```json
|
|
367
|
+
{ "activeRunsPrompt": true }
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Adds a short `active_subagents` section to the parent's system prompt while this session has queued or running background runs, so the model remembers work it launched earlier instead of starting it again. Off by default. Each run gets one line, for example `- <run id> researcher (chain, step 2/3)`, built from in-memory run state with no file reads; at most 10 runs are listed, then a `+N more` line. The line holds only the run id, agents, mode, and chain step (task text is redacted at launch), so the section changes only when a run starts, finishes, or moves to another chain step. Pi sends such a change as a mid-conversation system update where the provider supports it; on other providers it rebuilds the system prompt, so the prompt cache starts over on those turns. Nothing is added when no run is active.
|
|
371
|
+
|
|
356
372
|
## `waitTool`
|
|
357
373
|
|
|
358
374
|
```json
|
|
@@ -459,7 +475,7 @@ The budget counts single launches, expanded `tasks`/`count`, static chain steps
|
|
|
459
475
|
|
|
460
476
|
Optionally caps concurrently active top-level async runs owned by one parent session. Unset or `0` keeps the existing unlimited behavior. A positive integer reserves one slot before an async single, parallel, chain, or workflow creates run artifacts or starts children. Foreground runs and nested/workflow children do not reserve another slot.
|
|
461
477
|
|
|
462
|
-
Queued, running, paused, and needs-attention runs retain capacity. Runner-backed slots release only after terminal logical state and matching observed process-terminal proof from #1030. Missing, malformed, or unknown cleanup proof retains the slot. A terminal async workflow releases after its controller is gone and every launched child is accounted for: awaited foreground children are covered by workflow settlement, while actual background children still require observed process-terminal proof.
|
|
478
|
+
Queued, running, paused, and needs-attention runs retain capacity. Runner-backed slots release only after terminal logical state and matching observed process-terminal proof from #1030. Missing, malformed, or unknown cleanup proof retains the slot. A terminal async workflow releases after its controller is gone and every launched child is accounted for: awaited foreground children are covered by workflow settlement, while actual background children still require observed process-terminal proof. Three cases release without that proof: a run whose runner failed before child startup, recorded as `not-started` with an error; an async workflow child whose launch failed before any runner existed, recorded as a failed roster step with an error and no status or runner files in its run directory; and the abandoned-slot policy below. Resume transfers the source slot without a second charge. Dismissal and history cleanup do not release capacity.
|
|
463
479
|
|
|
464
480
|
When the runner is gone but process cleanup proof remains unknown, configure a bounded policy reclaim under `capacity.abandonedSlotReleaseAfterMs`:
|
|
465
481
|
|
|
@@ -467,9 +483,9 @@ When the runner is gone but process cleanup proof remains unknown, configure a b
|
|
|
467
483
|
{ "maxActiveAsyncRunsPerSession": 4, "capacity": { "abandonedSlotReleaseAfterMs": 1200000 } }
|
|
468
484
|
```
|
|
469
485
|
|
|
470
|
-
The default is `1200000` milliseconds (20 minutes).
|
|
486
|
+
The default is `1200000` milliseconds (20 minutes). For a top-level run, the policy releases only a failed terminal run whose runner PID is dead and whose last activity is older than the threshold (workflow children follow the rule below). A live or unknown PID, a top-level run in a non-failed terminal state, a recent run, or missing activity timestamp retains the slot. Set the value to `false` to keep strict retention. Valid configured durations range from 5 minutes through 24 hours. Policy release is reported as `abandoned-timeout` with `processProof: unknown`; it is not observed process-terminal proof and may reclaim capacity while an orphan child still exists.
|
|
471
487
|
|
|
472
|
-
A terminal workflow whose controller is gone uses the same policy when async children lack proof: the workflow must have ended longer ago than the threshold, and every async child without proof must
|
|
488
|
+
A terminal workflow whose controller is gone uses the same policy when async children lack proof: the workflow must have ended longer ago than the threshold, and every async child without proof must be a terminal run (failed, stopped, complete, partial or rejected, but not paused) with a dead runner PID in this PID namespace and old activity. A stopped or completed child counts too, because a child whose launching Pi process is gone never gets observed proof however it ended. Any other child without proof keeps the slot. The release event lists those children and their states under `abandonedChildren`.
|
|
473
489
|
|
|
474
490
|
This limit bounds current top-level async load. It is separate from cumulative `maxSubagentSpawnsPerSession`, `maxSubagentSpawnsPerRun`, and `globalConcurrencyLimit`.
|
|
475
491
|
|
package/docs/extension-api.md
CHANGED
|
@@ -432,6 +432,10 @@ Children do not gain provider tools or extensions automatically. Add `bg_wait` t
|
|
|
432
432
|
|
|
433
433
|
Local foreground children never load the parent's ambient extensions: they share the parent's process, and loading them would start a second copy of every ambient extension, including this one, inside it. They do inherit the providers the parent's extensions registered, so a provider extension's models resolve in a local foreground child. Pane-native remote foreground children instead use the remote machine's provider discovery and configuration. Agents that need MCP tools (`mcpDirectTools`, or MCP tools from an ambient adapter such as pi-mcp-adapter) must run as background children (`async: true`), which load the ambient extensions inside the detached runner process unless the agent sets `extensions` or the capability ceiling denies extensions.
|
|
434
434
|
|
|
435
|
+
The runner hosts many child sessions in one process and keeps working after each one ends: a background child's extensions see `session_shutdown` with reason `"quit"` in print mode, but the process does not exit with it. An extension that acts on process exit at that point (for example by force-exiting a headless run) should first check `process.env.PI_SUBAGENT_RUNNER_PID === String(process.pid)`, which is true only inside the runner. `PI_SUBAGENT_CHILD=1` cannot make that distinction: processes spawned from a child, such as a headless `pi` started by its bash tool, inherit both variables but have their own pid.
|
|
436
|
+
|
|
437
|
+
A local foreground child ends the same way, but it runs inside the parent Pi process, which is not a runner and sets no marker. An extension configured into a foreground child (through the agent's `extensions` or `subagentOnlyExtensions`) must not act on process exit at `session_shutdown`.
|
|
438
|
+
|
|
435
439
|
## External job provider bridge
|
|
436
440
|
|
|
437
441
|
Extensions that own long-running advisor jobs can register a process-local provider for `runner.type: external-job` agents:
|
|
@@ -596,7 +600,7 @@ The API returns discriminated structured results with canonical project root, bi
|
|
|
596
600
|
|
|
597
601
|
A host that embeds this extension owns whether completion wakes can be delivered at all.
|
|
598
602
|
|
|
599
|
-
Ordinary async and foreground completion wakes use `registerSubagentNotify` and `sendCompletion`. They listen for completion events and deliver through `pi.sendMessage(..., { triggerTurn })`. When the parent is idle, every pi-subagents notice that should start a turn (completions, supervisor asks, control and steering notices, wait subscriptions) is instead appended without a turn, followed by one
|
|
603
|
+
Ordinary async and foreground completion wakes use `registerSubagentNotify` and `sendCompletion`. They listen for completion events and deliver through `pi.sendMessage(..., { triggerTurn })`. When the parent is idle, every pi-subagents notice that should start a turn (completions, supervisor asks, control and steering notices, wait subscriptions) is instead appended without a turn, followed by one user message, `PARENT_WAKE_TEXT` from `src/shared/parent-wake.ts`, sent with `pi.sendUserMessage`. Pi runs `before_agent_start` only for prompts, so this keeps extension-set system prompt sections in the woken run. A busy parent still gets the notice as a steering message. After the user aborts a parent run in a host with a UI, these notices are only appended, with no turn and no wake message, until the next parent run starts. Session shutdown stops the result watcher and disposes this completion notifier. `createWaitSubscriptionManager` is separate: it is the explicit non-blocking `bg_wait` subscription path for work without native notification, not the ordinary completion wake path.
|
|
600
604
|
|
|
601
605
|
Each completion notice is a custom message with `customType: "subagent-notify"`. Its `content` is the text the model reads. Pi does not send `details` to the model, so hosts can use it to match a notice to its runs by id without parsing the text. `details.runs` has one entry per finished run, in the order of the notice. A grouped notice has several entries. Each entry has `agent` and `status` (`completed`, `failed`, `paused` or `stopped`). When known, an entry also has these fields:
|
|
602
606
|
|
|
@@ -624,6 +628,10 @@ If your host reclaims idle sessions, keep a session alive while it still has liv
|
|
|
624
628
|
|
|
625
629
|
The symptom when this is missed is quiet and easy to misattribute: subagents appear never to report back, which looks like a fault in this extension rather than in the host that disposed the listener.
|
|
626
630
|
|
|
631
|
+
## Component screens over RPC
|
|
632
|
+
|
|
633
|
+
`/subagents-fleet`, `/subagents-stop` and the `/subagents` model and thinking pickers draw a `ctx.ui.custom()` component in the TUI. In every other mode they fall back to text or `ctx.ui.select`, because stock Pi RPC defines `custom()` as a no-op. A host that runs Pi in RPC mode and renders `custom()` components itself, such as pi-web, can set `PI_SUBAGENTS_COMPONENT_UI=1` in Pi's environment to get the component screens.
|
|
634
|
+
|
|
627
635
|
## Runtime files
|
|
628
636
|
|
|
629
637
|
The main runtime files in this repository:
|
package/docs/observability.md
CHANGED
|
@@ -10,6 +10,8 @@ A foreground child is a pi session created inside the parent Pi process, not a s
|
|
|
10
10
|
|
|
11
11
|
A background child is a pi session created inside the detached runner process. The runner mirrors session events into `events.jsonl`, `output-<index>.log`, and the transcript. Interrupt and stop abort the child session; steer requests are delivered with the session's `steer` or `followUp`.
|
|
12
12
|
|
|
13
|
+
Pressing `Esc` in the parent stops the parent turn and its foreground children. Background children keep running. Until the next parent turn starts, usually your next prompt, their results and requests are added to the session but do not start a new parent turn, and the TUI shows how many background runs are still active. Use `/subagents-stop` to stop them.
|
|
14
|
+
|
|
13
15
|
Long-open `bash` attention notices identify the exact tool call and include status and child-specific transcript commands. Each distinct long-open call notifies once. Inspect the running command and recent output before intervening: development servers and watch commands may intentionally never return, while builds and tests may legitimately take time. A queued steer does not cancel the shell command. For local native Pi children explicitly granted `subagent_command`, `command.yield` returns control while the selected command remains managed; `command.cancel` stops that command without interrupting the child. Query `command.status` to confirm its terminal state. Unsupported children require the existing run-scoped interrupt/resume path, which may affect siblings. See [command controls](tool-reference.md#command-controls).
|
|
14
16
|
|
|
15
17
|
Live progress shows compact detail for single, chain, and parallel modes: a bounded one-line task, current tool, recent output, token counts, aggregate cost, duration, activity freshness, current-tool duration, and chain graph metadata when available. Workflow `label` metadata wins over raw task text in compact multi-child cards.
|
|
@@ -153,9 +155,11 @@ If something feels misconfigured, run `/subagents-doctor` or ask: "Check whether
|
|
|
153
155
|
## Host inspection protocol (RPC)
|
|
154
156
|
|
|
155
157
|
RPC hosts receive live async status through the bounded `subagent-async` widget
|
|
156
|
-
(`PI_SUBAGENT_ASYNC_JSON:` payload).
|
|
157
|
-
|
|
158
|
-
|
|
158
|
+
(`PI_SUBAGENT_ASYNC_JSON:` payload). Step nodes report the `agent` the child
|
|
159
|
+
runs as, and the `model` and `thinking` level it runs on once its status reports
|
|
160
|
+
them. For on-demand
|
|
161
|
+
detail — a child's delegated task, transcript window, or final output — hosts
|
|
162
|
+
can invoke the extension command:
|
|
159
163
|
|
|
160
164
|
```text
|
|
161
165
|
/subagents-inspect-rpc <requestId> <asyncId> [childId] [--lines N]
|
|
@@ -181,6 +185,10 @@ Inspection properties:
|
|
|
181
185
|
- `task` is the child session's first user message and is only populated when
|
|
182
186
|
it is genuinely attributable (fresh-context child whose session file fits the
|
|
183
187
|
read window); forked children omit it.
|
|
188
|
+
- `resolvedModel` (`{ provider, model }`) is the pair Pi recorded on the
|
|
189
|
+
inspected child's latest own assistant reply, never the requested model. It is
|
|
190
|
+
absent before the first reply, for run-level inspection of a multi-step or
|
|
191
|
+
workflow run, and for runs recorded before pi-subagents reported it.
|
|
184
192
|
- `childId` is exactly the node id the host received in the status snapshot
|
|
185
193
|
(step `workflowKey`/`runId`/`step:<index>`, or a nested run id).
|
|
186
194
|
|
package/docs/tool-reference.md
CHANGED
|
@@ -82,6 +82,7 @@ The `subagent` tool schema lists `agent`, `task`, `workflow`, `args`, `async`, `
|
|
|
82
82
|
| `options.context` | `fresh \| fork \| profile` | global or per-agent default, else `fresh` | Explicit `fresh` or `fork` overrides every workflow child. `profile` requires the selected agent's declared `defaultContext` and ignores config `defaultSubagentContext`; missing agent defaults fail. When omitted, [`defaultSubagentContext`](configuration.md#defaultsubagentcontext) wins over each agent's `defaultContext`; implicit fork falls back to fresh without a persisted parent session and leaf. Explicit fork is strict. Packaged `worker` defaults to `fresh`; packaged `oracle` and `advisor` default to `fork`. |
|
|
83
83
|
| `model` | string | agent default | An exact `provider/id`; bare ids resolve only if unique, and agent names are not model ids. An unknown or ambiguous model fails with up to 5 closest registry ids to copy; `{action:"models"}` lists them all. A suffix such as `provider/id:high` (`off/minimal/low/medium/high/xhigh/max`) overrides agent thinking. The `thinking` field is only for `watchdog.configure`, ignored on dispatch. On the `claude-code` and `claude-code-writer` adapters the model is Claude Code's own alias or id, and the same suffix becomes its `--effort`, bounded by `subagents.maxThinking` and by an enforced `subagents.modelScope`. |
|
|
84
84
|
| `options.missionId` | string | - | Attach a workflow, including each fire of `schedule.create`, to an existing project mission. Scheduled attachment uses ordinary lifecycle and retention rules. |
|
|
85
|
+
| `options.reuseFrom` | string | - | Exact id of an async workflow run stopped by a runtime replacement (`workflow.stopCause: "runtime-replaced"`). An async relaunch of the same script in the same cwd, from any session, reuses that run's finished children and waits for its running ones; see [Workflows](workflows.md). |
|
|
85
86
|
| `options.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. |
|
|
86
87
|
| `options.handoffPath` | string | - | Aggregate handoff manifest for `action: "worktree.discard"` or lane evidence actions, or optional explicit metadata for `action: "worktree.cleanup"`. |
|
|
87
88
|
| `options.repo` | string | runtime cwd | Repository path for `action: "worktree.cleanup"`; required explicitly for apply. The configured worktree base filters candidates by their per-project folder under it but never discovers them. |
|
|
@@ -131,7 +132,17 @@ Bound writer work with a narrow task and an outer `timeoutMs` or `maxRuntimeMs`
|
|
|
131
132
|
|
|
132
133
|
Explicit `context: "fork"` fails fast when the parent session is not persisted, the current leaf is missing, or the branched child session cannot be created. By contrast, global `defaultSubagentContext: "fork"` and agent-level `defaultContext: fork` are preferences: when the parent has no persisted session file or current leaf yet, the launch uses `fresh` immediately instead of failing and requiring a retry. Global `defaultSubagentContext: "fresh"` starts fresh. Explicit `context: "fresh"` always wins over both preferences.
|
|
133
134
|
|
|
134
|
-
|
|
135
|
+
A forked child keeps the inherited transcript as the parent sent it, signed Anthropic `thinking` blocks included: a signature covers its block's content, so a verbatim copy stays valid, and Pi replays a signed block only to the model that signed it. Raw `redacted_thinking` blocks, signed thinking in a `context_edit` replacement, and every signed or redacted Anthropic thinking block of a [pruned fork](configuration.md#forkcontext) are still removed. The child keeps its requested thinking level either way. Explicit `context: "fork"` never silently downgrades to `fresh`.
|
|
136
|
+
|
|
137
|
+
A forked child's requests start exactly as its parent's last request did, so the provider reads the whole inherited conversation from the parent's prompt cache:
|
|
138
|
+
|
|
139
|
+
- The child sends its parent's system prompt and tool declarations unchanged. Its own instructions (its agent prompt, the subagent boundary, structured-output rules, and the list of tools it can call) go in a `<subagent_instructions>` block at the start of its task message. Sections that describe a loadout (tools, rules, docs, skills, advertised subagents, MCP servers, project context) are not repeated there.
|
|
140
|
+
- A fork's prompt therefore includes the parent's base prompt, project and global context, skills (the bundled `pi-subagents` skill included), the parent's subagent catalog, and its list of running runs when that is shown. This holds even when the fork's agent sets `inheritProjectContext`, `inheritGlobalContext`, or `inheritSkills` to `false`, and `systemPromptMode: replace` does not remove the parent's base prompt: the agent prompt goes in the instructions block in both modes.
|
|
141
|
+
- A parent tool the child does not have stays declared, and calling it returns Pi's "not found" error without running anything. A tool only the child has is declared at the fork point. Models whose Pi `compat` sets `supportsMidConvoSystemMessages` and `supportsMidConvoToolChanges` take it there and keep the cache; others get it appended to the tool list, which changes the very start of the request and loses the cache. Give a fork agent no tool the parent lacks to keep its cache on those models.
|
|
142
|
+
- A tool the child defines differently under a parent tool's name, such as a `structured_output` with its own schema, is declared again at the fork point with the child's definition, so the model sees the schema the child's tool checks. Pi cannot redefine a tool in place, so that request sends the child's tool list and loses the cache.
|
|
143
|
+
- On Anthropic-style endpoints where Pi sends a session-affinity header, a child that runs on the model that wrote its inherited history sends its parent's session id, so a router with several upstreams sends it to the one that holds the parent's cache. A child on another model sends its own session id.
|
|
144
|
+
- A child on a different model than its parent gets each inherited thinking block as plain text, so the parent's reasoning is sent to that model's provider. Pi-shaped redacted thinking (`redacted: true` on a `thinking` block) is kept in a verbatim fork and replayed only to the model that wrote it.
|
|
145
|
+
- Another extension that returns a replacement `systemPrompt` from `before_agent_start` in the child replaces the parent's prompt and loses the cache. After a compaction in the child, no inherited message remains, so the child sends its own prompt with the boundary as a prompt section.
|
|
135
146
|
|
|
136
147
|
In workflow runs that omit `context`, each `runs.run` child follows the global `defaultSubagentContext` when set, then its own `defaultContext`. Without the global setting, a fresh-default worker can run fresh beside a fork-default oracle. If the parent session file or current leaf is not available yet, implicit fork-default children run fresh. Pass explicit `context: "fork"` or `context: "fresh"` when you intentionally want one context for every child.
|
|
137
148
|
|
|
@@ -181,7 +192,7 @@ return writer;
|
|
|
181
192
|
|
|
182
193
|
Each workflow key identifies one result lane. Use a new stable workflow key for every distinct retained resume pass; same-key calls are reused only when launch parameters are identical, and incompatible parameters are rejected.
|
|
183
194
|
|
|
184
|
-
Inside a workflow script, `await runs.run(key, { resume, task })` waits for the revived child to finish and returns its completed output and new `runId`. Each resume can return a new retained run id, so loops must continue from the latest returned `runId`. Top-level `{ action: "resume" }` remains detached and returns a background-run receipt. When it revives a failed async workflow child (or a failed revival of one), the revived run becomes that key's latest run: workflow `status` adds `Revived → <run>: <state>` under the key, and keyed `resume: { workflowRunId, key, latest: true }` continues from it with lineage `[original, revived, ...]`. If the workflow was still running, the receipt it writes when it ends adds the revived runs to the key's lineage, and its completion notice keeps the child's failed result with the same `Revived →` line. The workflow's return value and state are not changed, notices already delivered are not rewritten, and a key accepts at most 16 chained revivals. Reviving a completed, paused, or stopped child does not change its key.
|
|
195
|
+
Inside a workflow script, `await runs.run(key, { resume, task })` waits for the revived child to finish and returns its completed output and new `runId`. Each resume can return a new retained run id, so loops must continue from the latest returned `runId`. Without its own `output`, a workflow resume of a run that saved a report writes to `<key>.md` in the workflow's output directory (or `<name>.<key><ext>` next to the workflow's `output`), never over the resumed run's report, whether that run belongs to this workflow or not. If that name is the resumed run's own report, as when rerunning the same workflow and key, the first 8 characters of the workflow run id are added before the extension, so read the returned `savedOutputPath` instead of building the name. Top-level `{ action: "resume" }` remains detached and returns a background-run receipt. Without its own `output`, it saves the revived report next to the original run's saved output as `<name>.<first 8 characters of the revived run id><ext>`, so the earlier report is kept; pass `output` to choose the path. When it revives a failed async workflow child (or a failed revival of one), the revived run becomes that key's latest run: workflow `status` adds `Revived → <run>: <state>` under the key, and keyed `resume: { workflowRunId, key, latest: true }` continues from it with lineage `[original, revived, ...]`. If the workflow was still running, the receipt it writes when it ends adds the revived runs to the key's lineage, and its completion notice keeps the child's failed result with the same `Revived →` line. The workflow's return value and state are not changed, notices already delivered are not rewritten, and a key accepts at most 16 chained revivals. Reviving a completed, paused, or stopped child does not change its key.
|
|
185
196
|
|
|
186
197
|
For a simple implementation challenge outside a workflow script, send the challenge through `subagent({ action: "resume", id: "<retained-writer-run>", message: "Reconsider the implementation and make any better current-scope change." })` when `children.list` reports that retained writer as `resumable`, or attempt it after exact-id status inspection identifies a known direct-child candidate. Status is advisory; resume authoritatively checks eligibility and may reject missing recovery requirements. If there is no known candidate or resume rejects eligibility, start a same-role fallback challenge and record why it is a fallback. Use workflow `runs.run({ resume })` only when the script must await the revived writer output before the next step. Do not use `steer` as the sole challenge action for a completed retained child; `steer` with `mode: "follow_up"` only queues text for the next `resume`.
|
|
187
198
|
|
|
@@ -376,7 +387,7 @@ Commands remain owned by the child session. Normal completion with unfinished co
|
|
|
376
387
|
- It is not a resumable pause; stopped runs should be restarted as new runs.
|
|
377
388
|
- Foreground and nested targets are rejected.
|
|
378
389
|
- Direct id calls execute immediately.
|
|
379
|
-
- `/subagents-stop` without an id opens a selector with confirmation when a TUI is available. Use `↑`/`↓` or `j`/`k` to move through the selector.
|
|
390
|
+
- `/subagents-stop` without an id opens a selector with confirmation when a TUI is available. Use `↑`/`↓` or `j`/`k` to move through the selector. When two or more current-session async runs are active, the first row is "stop all", which stops each of them after one confirmation and leaves scheduled runs alone.
|
|
380
391
|
- In non-TUI contexts the slash command prints exact `subagent({ action: "stop", id })` and `/subagents-stop <id>` commands.
|
|
381
392
|
- Pass a child id to stop one child of a multi-child async run or workflow while the rest continue: `/subagents-stop <run-id> <child-id>` (equivalent to `subagent({ action: "stop", id, childId })`). Child ids come from status output, the async status snapshot, or `/subagents-inspect-rpc` replies. Only pending or running children are stoppable; the request is rejected for anything else instead of widening to a run-level stop.
|
|
382
393
|
- Inactive schedules can appear in the selector, but they are labeled as schedules and route through `schedule.pause`, not `stop`.
|
package/docs/workflows.md
CHANGED
|
@@ -17,7 +17,7 @@ Launch children in the background (`async` follows `asyncByDefault`, normally tr
|
|
|
17
17
|
Child-safety boundaries are enforced at runtime:
|
|
18
18
|
|
|
19
19
|
- Child sessions do not receive the bundled `pi-subagents` skill.
|
|
20
|
-
-
|
|
20
|
+
- Child context filtering removes parent-only subagent artifacts (hidden orchestration-instruction messages, slash/status/control messages, and `subagent` tool-call/tool-result history) while preserving ordinary prose and unrelated tool calls/results. A forked child sends the history it inherited exactly as the parent sent it, so its first request can reuse the parent's prompt cache; it still cannot run `subagent`.
|
|
21
21
|
- By default, children do not register the `subagent` tool and receive boundary instructions that they are not the parent orchestrator and must not propose or run subagents.
|
|
22
22
|
- The explicit exception is an agent whose resolved builtin `tools` includes `subagent`; that child gets a child-safe `subagent` tool for the fanout work the parent assigned, still bounded by `maxSubagentDepth`.
|
|
23
23
|
|
|
@@ -29,7 +29,9 @@ Stop and report the exact failure, run/status, and repository/cwd/worktree/branc
|
|
|
29
29
|
|
|
30
30
|
Failed workflow details and async `status.json` include `workflow.failureKind` as `validation`, `script`, `child`, `return-serialization`, `timeout`, `detached-child`, or `runtime`. `validation` means the host rejected the script before it ran (syntax or portability) or at completion (unawaited calls); errors thrown into a running script, including rejected `runs.run` parameters and runtime `SyntaxError`s, are `script` because the script could catch them, and failed children are `child`. `runtime` covers host setup and infrastructure failures, such as an unavailable cwd or a crashed worker. A workflow that is stopped or reloaded is not a failure and has no `failureKind`.
|
|
31
31
|
|
|
32
|
-
When `/reload`, a session resume, or a pi-web project switch replaces the extension runtime, a running async workflow stops with `workflow.stopCause: "runtime-replaced"` in `status.json`, and its awaited async children keep running. Its in-process foreground children are stopped, including ones that detached to ask their supervisor. When the same workflow script is launched again with the same `args` in that session, a `runs.run` with the same key and params returns a child that already finished successfully without launching it, and waits for a child that is still running instead of starting another. Those results carry `reused: true`, their step and trace entry are marked `reused`, and the new run records `workflow.reusedFrom`. Failed and stopped children, and children that ran in-process, launch again. Reuse applies only when the newest run of that script and args in the session was stopped this way; a user stop or a completed run ends it.
|
|
32
|
+
When `/reload`, a session resume, or a pi-web project switch replaces the extension runtime, a running async workflow stops with `workflow.stopCause: "runtime-replaced"` in `status.json`, and its awaited async children keep running. Its in-process foreground children are stopped, including ones that detached to ask their supervisor. When the same workflow script is launched again with the same `args` in that session, a `runs.run` with the same key and params returns a child that already finished successfully without launching it, and waits for a child that is still running instead of starting another. Those results carry `reused: true`, their step and trace entry are marked `reused`, and the new run records `workflow.reusedFrom`. Failed and stopped children, and children that ran in-process, launch again. Reuse applies only when the newest run of that script and args in the session was stopped this way; a user stop or a completed run ends it. From another session, such as one that took over after a handoff, relaunch the same script as an async workflow with `options: { reuseFrom: "<stopped run id>" }`; the run must have stopped this way in the same cwd, and `args` may differ, because each child is still reused only when its key and params match.
|
|
33
|
+
|
|
34
|
+
After `/new`, `/resume`, or a fork in the same Pi process, the new session owns the async runs of the session it replaced, and of the sessions that one replaced. It can list, `bg_wait` on, steer, stop, and resume them, receives their completion notices, and answers their children's pending supervisor requests. Over RPC, a host can steer, interrupt, stop, resume, and inspect them, and sees them in the fleet and `subagent-async` snapshots. Startup and other Pi processes inherit nothing; `/reload` adds nothing new but keeps what the session already inherited. To relaunch a stopped workflow instead of controlling its children one by one, use `options.reuseFrom`.
|
|
33
35
|
|
|
34
36
|
Pi core may print a generic `pi -ne` extension-load hint; that out-of-repo hint is not protocol-approved fallback. A verified compaction abort may continue the retained child once on its already resolved model; it does not authorize an execution-mode or model switch.
|
|
35
37
|
|
|
@@ -429,7 +431,7 @@ known, or for explicit emergency hotfix lanes.
|
|
|
429
431
|
|
|
430
432
|
For watched same-repo workflows, pass `async:false` only when the parent must block until completion. That blocking mode also shows the live in-chat workflow card. `chatProgress` can force `off` or `live-card` when the automatic policy is not what you want. Blocking workflows default to a 30-minute timeout; async workflows have no default timeout. See the [tool reference](tool-reference.md) for the full parameter list.
|
|
431
433
|
|
|
432
|
-
Synchronous workflows publish trace and `emit(...)` updates through the tool update callback regardless of `chatProgress`, including RPC/headless and cross-repository runs. These updates include `details.workflow` and `details.workflowChildren`; `chatProgress: "off"` disables the live card, not transport progress. Running foreground child rows additionally expose bounded `activity` (current tool, timing, and counters), plus resolved model/thinking when available, keyed by `childId`. Activity-only updates coalesce over 100 ms; lifecycle updates remain immediate. Activity clears when children settle, and is not persisted for async workflows. Tool names are limited to 256 UTF-8 bytes and each activity object is below 2 KiB (including JSON escaping); arguments and transcripts are not forwarded.
|
|
434
|
+
Synchronous workflows publish trace and `emit(...)` updates through the tool update callback regardless of `chatProgress`, including RPC/headless and cross-repository runs. These updates include `details.workflow` and `details.workflowChildren`; `chatProgress: "off"` disables the live card, not transport progress. Once a child launches, its running row carries its `agent` and `runId`; for a child running in the background, that `runId` is the id of its run node in the `subagent-async` snapshot. Running foreground child rows additionally expose bounded `activity` (current tool, timing, and counters), plus resolved model/thinking when available, keyed by `childId`. Activity-only updates coalesce over 100 ms; lifecycle updates remain immediate. Activity clears when children settle, and is not persisted for async workflows. Tool names are limited to 256 UTF-8 bytes and each activity object is below 2 KiB (including JSON escaping); arguments and transcripts are not forwarded.
|
|
433
435
|
|
|
434
436
|
The workflow result text keeps the Return, Emitted, and Console sections, and a failed workflow's error, under 200 KB and 5000 lines, or under `maxOutput` when set. Each call-trace error is cut to 500 characters. A cut adds a `[TRUNCATED: ... - full output at <path>]` line. The full text goes to the workflow `output` file when one is set, otherwise to `<run>_workflow-result.md` in the run's artifacts directory, outside the `outputs/` tree where children save their reports. That file has the same retention as the other run artifacts: age-based cleanup removes it from temp and session artifact directories, and `artifactDir: "project"` files are kept. The status line, the rest of each trace line, warnings, and output-path mappings are never cut. Async completion notices and `action: "status"` show short previews; a cut preview or async error ends in `…` and names the run's `status.json`, which holds the full `workflow.value`, `workflow.emits`, and `error`.
|
|
435
437
|
|
|
@@ -494,7 +496,7 @@ Inspect the handoff before reconciliation; cleanup still requires fresh checks.
|
|
|
494
496
|
|
|
495
497
|
## Supervisor coordination (child asks parent)
|
|
496
498
|
|
|
497
|
-
Child agents can talk back to the parent Pi session without installing `pi-intercom`. `pi-subagents` provides the child-facing `contact_supervisor` tool and the parent-facing `subagent_supervisor({ action: "reply" })` path natively. Generic `intercom` remains available only when an explicitly loaded external provider supplies it.
|
|
499
|
+
Child agents can talk back to the parent Pi session without installing `pi-intercom`. `pi-subagents` provides the child-facing `contact_supervisor` tool and the parent-facing `subagent_supervisor({ action: "reply" })` path natively. A session that has `subagent` also declares `contact_supervisor`, so a forked child's tools match its parent's; there it only returns an error saying the session has no supervisor. Generic `intercom` remains available only when an explicitly loaded external provider supplies it.
|
|
498
500
|
|
|
499
501
|
Use it for work where the child might need a decision instead of guessing:
|
|
500
502
|
|
package/package.json
CHANGED
|
@@ -105,11 +105,12 @@ For an authorized complex delegated workflow, read `prompting-and-roles.md` and
|
|
|
105
105
|
## Operating rules
|
|
106
106
|
|
|
107
107
|
- Avoid duplicate scouts, overlapping writers, and vague prompts without a concrete deliverable.
|
|
108
|
+
- Call `subagent`, `subagents_enable`, `subagent_supervisor`, `contact_supervisor`, and `structured_output` as ordinary model tool calls. They are model-only, so codemode scripts cannot reach them through `tools.*`, `ALL_TOOLS`, or `searchTools`.
|
|
108
109
|
- Keep the parent on the ordinary strong default model. Route workers/scouts to a fast capable tier, serious reviews to a strong tier, and top reasoning to bounded read-only critique.
|
|
109
110
|
- Exact model names are deployment policy. Put them in user/project settings or profiles, not package guidance.
|
|
110
111
|
- Give every child a compact meta-prompt checklist: objective; repo/cwd/ref; authority/edit boundary; relevant files/contracts and constraints; success/acceptance criteria; validation; expected output/report; and stop/ask conditions. See `references/prompting-and-roles.md`.
|
|
111
112
|
- Before launching a writer for substantial mutation work, classify it as single-seam or multi-seam and partition multi-seam work across exclusive component owners, gates, and durable handoffs before an integration-only owner. See `references/multi-lane-orchestration.md`.
|
|
112
|
-
- For mutation work, use an isolated lane/worktree when isolation, overlap, or concurrent juggling matters; keep one writer per cwd/worktree. See `references/multi-lane-orchestration.md` for lane mechanics.
|
|
113
|
+
- For mutation work, use an isolated lane/worktree when isolation, overlap, or concurrent juggling matters; keep one writer per cwd/worktree. `worktree: true` refuses to launch unless the source checkout is clean (untracked files count; `.pi/subagents/` does not), so commit your own changes first or plan the lane without managed isolation. See `references/multi-lane-orchestration.md` for lane mechanics.
|
|
113
114
|
- Keep long/high-output validation out of chat: prefer `interactive_shell` dispatch/background monitors, bounded logs, or subagent-owned reports; return a concise summary plus report path unless same-turn output is required. Do not use `interactive_shell` as an implicit fallback for a failed `subagent` lane; see `references/execution-controls.md`.
|
|
114
115
|
- Treat subagent workflow, child launch, prompt runtime, extension load, and child tooling setup failures as lane infrastructure blockers. Stop, report the exact failure and run/worktree state, verify a clean worktree or capture a partial diff, and use only a clear same-protocol retry or an owner-approved execution-mode fallback.
|
|
115
116
|
- For cross-codebase work, record the repo, explicit `cwd`, authority boundary, and expected output before launch.
|
|
@@ -558,4 +558,6 @@ subagent_supervisor({ action: "pending" })
|
|
|
558
558
|
|
|
559
559
|
Native supervisor coordination does not expose generic `intercom` as a fallback. Use `subagent_supervisor` for parent replies.
|
|
560
560
|
|
|
561
|
+
`subagent`, `subagents_enable`, `subagent_supervisor`, `contact_supervisor`, and `structured_output` are model-only: call them as ordinary model tool calls. Codemode scripts cannot reach them through `tools.*`, `ALL_TOOLS`, or `searchTools`.
|
|
562
|
+
|
|
561
563
|
If intercom messages do not show up, run `subagent({ action: "doctor" })` or `/subagents-doctor`.
|
|
@@ -24,7 +24,7 @@ For every lane, record the delivery target, allowed actions, required validation
|
|
|
24
24
|
|
|
25
25
|
## Partitioned runs
|
|
26
26
|
|
|
27
|
-
Use one writer per repo/cwd or worktree. Mutation lanes need distinct isolation paths and explicit `cwd` values. Set `worktree: true` when a run needs managed worktree isolation within one repository. Read-only runs can share a checkout only when they cannot change state or create generated files.
|
|
27
|
+
Use one writer per repo/cwd or worktree. Mutation lanes need distinct isolation paths and explicit `cwd` values. Set `worktree: true` when a run needs managed worktree isolation within one repository. Each managed worktree branches from `HEAD` unless `baseRef` names another ref, and launch fails with "worktree isolation requires a clean git working tree" while the source checkout has modified, staged, or untracked files outside `.pi/subagents/`. In a shared or dirty checkout, commit your own changes before launching, give each lane an explicit `cwd` in a worktree you created, or run without isolation. Read-only runs can share a checkout only when they cannot change state or create generated files.
|
|
28
28
|
|
|
29
29
|
For Pi extension repositories, keep lane worktrees outside auto-discovered extension directories such as `~/.pi/agent/extensions`. A stale extension worktree there can auto-load duplicate tools and shortcuts. Remove or move it only after its handoff is durable, the worktree is clean, and no run owns it.
|
|
30
30
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"external-runs.d.ts","sourceRoot":"","sources":["../../../src/api/external-runs.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAC/C,eAAO,MAAM,yBAAyB,kCAAkC,CAAC;AAEzE,eAAO,MAAM,mBAAmB;;;;IAI/B;;;OAGG;;;;;;CAMM,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEzF,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,gBAAgB,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,WAAW,GAAG,QAAQ,CAAC,CAAC,CAAC;AAE1F,MAAM,WAAW,0BAA0B;IAC1C,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,eAAe,CAAC,EAAE,OAAO,CAAC;CAC1B;AAkKD,sFAAsF;AACtF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,WAAW,GAAG,WAAW,CASnE;AAED,kGAAkG;AAClG,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,WAAW,CAYvG;AAED,kGAAkG;AAClG,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAM5E;AAUD,8FAA8F;AAC9F,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,0BAA+B,GAAG,SAAS,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"external-runs.d.ts","sourceRoot":"","sources":["../../../src/api/external-runs.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAC/C,eAAO,MAAM,yBAAyB,kCAAkC,CAAC;AAEzE,eAAO,MAAM,mBAAmB;;;;IAI/B;;;OAGG;;;;;;CAMM,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEzF,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,gBAAgB,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,WAAW,GAAG,QAAQ,CAAC,CAAC,CAAC;AAE1F,MAAM,WAAW,0BAA0B;IAC1C,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,eAAe,CAAC,EAAE,OAAO,CAAC;CAC1B;AAkKD,sFAAsF;AACtF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,WAAW,GAAG,WAAW,CASnE;AAED,kGAAkG;AAClG,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,WAAW,CAYvG;AAED,kGAAkG;AAClG,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAM5E;AAUD,8FAA8F;AAC9F,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,0BAA+B,GAAG,SAAS,WAAW,EAAE,CA6BxH;AAED,sDAAsD;AACtD,eAAO,MAAM,gBAAgB,6BAAuB,CAAC"}
|
package/src/api/external-runs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sanitizeDisplayText, truncateDisplayText } from "../shared/display-text.js";
|
|
1
|
+
import { previewDisplayText, safeTerminalText, sanitizeDisplayText, truncateDisplayText } from "../shared/display-text.js";
|
|
2
2
|
export const EXTERNAL_RUN_REGISTRY_VERSION = 2;
|
|
3
3
|
export const EXTERNAL_RUN_REGISTRY_KEY = "pi-subagents.external-runs.v2";
|
|
4
4
|
export const EXTERNAL_RUN_LIMITS = {
|
|
@@ -70,8 +70,8 @@ function identity(value, field, maxLength = EXTERNAL_RUN_LIMITS.maxIdentityLengt
|
|
|
70
70
|
throw new Error(`${field} must be a non-empty trimmed string without NUL characters.`);
|
|
71
71
|
if (value.length > maxLength)
|
|
72
72
|
throw new Error(`${field} must be at most ${maxLength} characters.`);
|
|
73
|
-
|
|
74
|
-
if (
|
|
73
|
+
// Identities are often session-file paths, so Unicode spaces such as U+00A0 are valid; only terminal controls and line breaks are not.
|
|
74
|
+
if (/[\t\n\p{Zl}\p{Zp}]/u.test(value) || safeTerminalText(value) !== value)
|
|
75
75
|
throw new Error(`${field} must contain only display-safe text.`);
|
|
76
76
|
return value;
|
|
77
77
|
}
|
|
@@ -220,7 +220,8 @@ export function snapshotExternalRuns(sessionId, options = {}) {
|
|
|
220
220
|
runs.push(run);
|
|
221
221
|
}
|
|
222
222
|
catch (error) {
|
|
223
|
-
|
|
223
|
+
// The cache key joins session and run id with NUL, and a malformed record's id is unvalidated.
|
|
224
|
+
const message = `Malformed cached external run '${previewDisplayText(cacheKey.slice(sessionPrefix.length), EXTERNAL_RUN_LIMITS.maxIdentityLength)}': ${getErrorMessage(error)}`;
|
|
224
225
|
if (!options.ignoreMalformed)
|
|
225
226
|
throw new Error(message, { cause: error instanceof Error ? error : undefined });
|
|
226
227
|
current.runs.delete(cacheKey);
|