pi-subagents 0.49.0 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/agents/gpt-pro.md +17 -0
  3. package/agents/oracle.md +7 -5
  4. package/agents/researcher.md +1 -1
  5. package/agents/reviewer.md +2 -2
  6. package/agents/scout.md +1 -1
  7. package/agents/worker.md +1 -1
  8. package/async-retention-discovery-worker.mjs +180 -0
  9. package/docs/agents.md +37 -2
  10. package/docs/configuration.md +76 -14
  11. package/docs/extension-api.md +78 -1
  12. package/docs/missions.md +1 -1
  13. package/docs/observability.md +20 -4
  14. package/docs/tool-reference.md +55 -39
  15. package/docs/workflows.md +171 -5
  16. package/package.json +4 -2
  17. package/skills/pi-subagents/SKILL.md +5 -4
  18. package/skills/pi-subagents/references/constraints-and-recipes.md +9 -6
  19. package/skills/pi-subagents/references/execution-controls.md +22 -18
  20. package/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
  21. package/skills/pi-subagents/references/prompting-and-roles.md +33 -17
  22. package/src/agents/agent-management.ts +100 -345
  23. package/src/agents/agent-serializer.ts +2 -0
  24. package/src/agents/agents.ts +135 -25
  25. package/src/api/external-job-provider.ts +185 -0
  26. package/src/api/external-runs.ts +174 -84
  27. package/src/api/preflight.ts +42 -11
  28. package/src/api/shared-types.ts +2 -0
  29. package/src/extension/config.ts +36 -3
  30. package/src/extension/doctor.ts +3 -6
  31. package/src/extension/fanout-child.ts +2 -2
  32. package/src/extension/index.ts +210 -90
  33. package/src/extension/public-execution.ts +31 -2
  34. package/src/extension/rpc.ts +5 -1
  35. package/src/extension/schemas.ts +14 -36
  36. package/src/extension/tool-description.ts +37 -24
  37. package/src/inspectors/herdr/actions.ts +5 -9
  38. package/src/inspectors/herdr/inspector-runner.ts +2 -1
  39. package/src/inspectors/herdr/project-panes.ts +4 -8
  40. package/src/inspectors/herdr/shell-command.ts +16 -0
  41. package/src/intercom/intercom-bridge.ts +2 -3
  42. package/src/intercom/native-supervisor-channel.ts +49 -51
  43. package/src/missions/goal-driver.ts +3 -1
  44. package/src/missions/lifecycle.ts +6 -1
  45. package/src/missions/store.ts +4 -9
  46. package/src/profiles/profiles.ts +3 -1
  47. package/src/runs/background/active-run-index.ts +94 -1
  48. package/src/runs/background/async-execution.ts +98 -38
  49. package/src/runs/background/async-job-tracker.ts +21 -4
  50. package/src/runs/background/async-resume.ts +30 -17
  51. package/src/runs/background/async-retention.ts +888 -0
  52. package/src/runs/background/async-status-snapshot.ts +277 -0
  53. package/src/runs/background/async-status.ts +47 -56
  54. package/src/runs/background/chain-append.ts +3 -33
  55. package/src/runs/background/chain-root-attachment.ts +2 -2
  56. package/src/runs/background/completion-replay.ts +11 -1
  57. package/src/runs/background/control-channel.ts +14 -68
  58. package/src/runs/background/fleet-view.ts +3 -1
  59. package/src/runs/background/index-segment.ts +59 -0
  60. package/src/runs/background/notify.ts +3 -1
  61. package/src/runs/background/result-files.ts +505 -0
  62. package/src/runs/background/result-watcher.ts +250 -51
  63. package/src/runs/background/retained-children.ts +79 -20
  64. package/src/runs/background/run-id-query.ts +7 -0
  65. package/src/runs/background/run-id-resolver.ts +37 -29
  66. package/src/runs/background/run-status.ts +34 -20
  67. package/src/runs/background/scheduled-runs.ts +71 -27
  68. package/src/runs/background/stale-run-reconciler.ts +33 -16
  69. package/src/runs/background/steering.ts +11 -1
  70. package/src/runs/background/subagent-runner.ts +535 -161
  71. package/src/runs/background/subagent-wait.ts +9 -7
  72. package/src/runs/background/terminal-run-index.ts +129 -0
  73. package/src/runs/background/wait-completions.ts +22 -2
  74. package/src/runs/background/wait-subscriptions.ts +80 -1
  75. package/src/runs/foreground/async-dismiss-action.ts +2 -1
  76. package/src/runs/foreground/async-steering-action.ts +21 -14
  77. package/src/runs/foreground/execution.ts +221 -15
  78. package/src/runs/foreground/subagent-executor.ts +529 -1519
  79. package/src/runs/foreground/workflow-foreground-steering.ts +6 -5
  80. package/src/runs/shared/chain-outputs.ts +1 -3
  81. package/src/runs/shared/completion-guard.ts +96 -6
  82. package/src/runs/shared/external-cli-runner.ts +4 -0
  83. package/src/runs/shared/external-job-bridge.ts +450 -0
  84. package/src/runs/shared/external-job-runner.ts +286 -0
  85. package/src/runs/shared/mcp-direct-tool-allowlist.ts +14 -0
  86. package/src/runs/shared/model-fallback.ts +34 -3
  87. package/src/runs/shared/nested-events.ts +66 -62
  88. package/src/runs/shared/orca-progress-tabs.ts +437 -0
  89. package/src/runs/shared/parallel-handoff.ts +46 -4
  90. package/src/runs/shared/parallel-utils.ts +6 -15
  91. package/src/runs/shared/permissions.ts +5 -1
  92. package/src/runs/shared/pi-args.ts +8 -1
  93. package/src/runs/shared/subagent-control.ts +41 -4
  94. package/src/runs/shared/subagent-prompt-runtime.ts +13 -15
  95. package/src/runs/shared/subagent-startup-retry.ts +12 -0
  96. package/src/runs/shared/tool-timeout.ts +93 -0
  97. package/src/runs/shared/workflow-graph.ts +1 -23
  98. package/src/runs/shared/worktree.ts +12 -1
  99. package/src/shared/atomic-json.ts +22 -2
  100. package/src/shared/capacity-resilient-json.ts +102 -0
  101. package/src/shared/completion-owner.ts +14 -0
  102. package/src/shared/file-system-retry.ts +49 -1
  103. package/src/shared/fork-context.ts +42 -0
  104. package/src/shared/prompt-resources.ts +0 -40
  105. package/src/shared/settings.ts +3 -27
  106. package/src/shared/types.ts +88 -27
  107. package/src/shared/utils.ts +8 -0
  108. package/src/shared/watch-strategy.ts +10 -0
  109. package/src/slash/slash-commands.ts +45 -28
  110. package/src/slash/slash-live-state.ts +3 -0
  111. package/src/tui/fleet-status.ts +160 -45
  112. package/src/tui/fleet.ts +186 -28
  113. package/src/tui/render.ts +41 -10
  114. package/src/workflows/chat-progress.ts +7 -5
  115. package/src/workflows/scripted-workflow.ts +424 -125
  116. package/src/runs/foreground/chain-clarify.ts +0 -1354
  117. package/src/runs/foreground/chain-execution.ts +0 -1565
package/CHANGELOG.md CHANGED
@@ -2,6 +2,102 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.51.0] - 2026-08-18
6
+
7
+ ### Added
8
+ - Add stable-key `runs.steer` to `workflowScript`, with routing for foreground and async children, structured receipts, trace entries, and checks for unawaited calls (#1186).
9
+ - Add `runner.type: external-job`, the exported provider bridge, the Surf GPT Pro `gpt-pro` profile, and docs for external advisor data boundaries (#1189).
10
+ - Add `defaultSubagentContext: "fork"` for launches that do not set an explicit context (#1161).
11
+ - Allow `defaultSubagentContext: "fresh"` to override agent fork defaults for launches that do not set an explicit context.
12
+ - Add `PI_SUBAGENT_FS_RETRY_MAX_TOTAL_MS` so hosts can cap filesystem retry waits. Unset by default. Thanks to [@MarcusNeufeldt](https://github.com/MarcusNeufeldt) for #1143.
13
+
14
+ ### Changed
15
+ - Document rolling `workflowScript` fanout with `runs.run`, `Promise.race`, `runs.steer`, and `Promise.all` (#1187).
16
+ - Document scripted chaining as the supported workflow API, with migration examples for removed top-level chain and task inputs.
17
+ - Clarify `workflowScript` fanout guidance: use awaited `runs.all` for ordinary parallel work, and use stored `runs.run` promises only for fully observed advanced rolling fanout (#1229, #1230).
18
+ - Clarify that async workflows do not have inline `live-card` projection (#1229, #1230).
19
+ - Describe `async:false` as a blocking parent wait, not a UI or foreground-only mode.
20
+ - Clarify that subagent reviews and gates should stay async unless the parent must block until completion.
21
+ - Document that a host's session lifetime owns completion wakes, and how to key an idle check on live run state rather than parent activity. Thanks to [@MarcusNeufeldt](https://github.com/MarcusNeufeldt) for #1144.
22
+ - Register the default `subagent` tool prompt as split metadata with a short description, `promptSnippet`, and `promptGuidelines`, while keeping explicit `full`, `compact`, and `custom` description modes.
23
+ - Keep `worktree: true` workflow children on the single-child path while preserving managed patch handoffs.
24
+
25
+ ### Removed
26
+ - Remove unused foreground chain and parallel execution and durable chain management surfaces.
27
+ - Remove legacy subagent tool compatibility fields for append-step control, schedule aliases, async recovery metadata, and string mission goals.
28
+ - Remove chain approval checkpoint steps and the `approve-checkpoint` / `reject-checkpoint` controls.
29
+ - Remove `prompts.render` from `workflowScript`; pass explicit task text to `runs.run` or use `/prompt-workflow` for reusable prompt templates.
30
+
31
+ ### Fixed
32
+ - Avoid Darwin reload hangs by disabling idle native filesystem watchers and using demand-gated delivery for live results, supervisor messages, controls, and steering. Thanks to [@youlikemodernart](https://github.com/youlikemodernart) for #1220.
33
+ - Bound async result session, run, active-run, and result-index path segments so long provider IDs do not break launches or waits with `ENAMETOOLONG`. Thanks to [@hlstwizard](https://github.com/hlstwizard) for #1131 and [@zhouatie](https://github.com/zhouatie) for #1135.
34
+ - Hash result-index session segments that look like Windows paths or file names, keep reading previous URI-encoded keys, and treat `EPERM` and `EACCES` as empty scans. Thanks to [@apoapostolov](https://github.com/apoapostolov) for #1211.
35
+ - Sanitize foreground workflow output path segments derived from provider run IDs, so Windows launches do not fail when tool-call IDs contain path-invalid characters. Thanks to [@maxime-louward-shift](https://github.com/maxime-louward-shift) for #1235.
36
+ - Keep async status and result persistence retrying after temporary `ENOSPC`, quota, or file-descriptor exhaustion errors. Thanks to [@ahmadaccino](https://github.com/ahmadaccino) for #1227.
37
+ - Route async completion notifications and cleanup only to the parent Pi process that launched the run, so concurrent windows sharing one session file cannot consume each other's results. Thanks to [@wangjianming](https://github.com/wangjianming) for #1225.
38
+ - Keep extension reload cleanup scoped to the replaced session runtime, so concurrent Pi sessions in one process do not remove each other's subscriptions or parent-session identity. Thanks to [@ryanbbrown](https://github.com/ryanbbrown) for #1222.
39
+ - Stop failing child runs when an explicit allowlist names `contact_supervisor` without the legacy `intercom` companion. A lone `intercom` entry still requires a real external provider. Thanks to [@MingTeer](https://github.com/MingTeer) for #1207.
40
+ - Add explicit `isolation: "none"` for schema-driven workflows without Git worktree setup, while keeping strict `isolation: "worktree"` behavior. Thanks to [@tlsneo](https://github.com/tlsneo) for #1203.
41
+ - Fail closed when an existing external-job `status.json` is unreadable or malformed, including an invalid `steps` shape.
42
+ - Skip malformed agent definitions during discovery so valid agents still list and launch, while showing configuration errors in management diagnostics (#1200).
43
+ - Resolve `/subagents-generate-profiles` provider probes through the shared Pi executable resolver so configured and Windows-specific Pi commands work. Thanks to [@Wumpf](https://github.com/Wumpf) for #1199.
44
+ - Resolve the workflowScript parser from pi-subagents instead of the caller's working directory, so workflows start in projects that do not install Acorn. Thanks to [@xz-dev](https://github.com/xz-dev) for #1214, following up #1190.
45
+ - Keep workflowScript child-launch tracking working on Bun-built Pi without a hard dependency on V8 promise hooks. Thanks to [@rochecompaan](https://github.com/rochecompaan) for #1158 and [@rholak](https://github.com/rholak) for the version-window diagnosis.
46
+ - Treat provider subscription usage-limit errors as retryable model failures so `fallbackModels` can continue to the next configured model. Thanks to [@dwizzle204](https://github.com/dwizzle204) for #1215.
47
+ - Skip fallback models that are unavailable in the active registry, so shared agent configs still run where their primary model is available. Thanks to [@JPFrancoia](https://github.com/JPFrancoia) for #1147.
48
+ - Preserve workflow async session roots for Herdr inspectors so workflow runs open with the same trusted session-root context as standalone runs. Thanks to [@hank-warren](https://github.com/hank-warren) for #1219.
49
+ - Keep Herdr project and inspector panes in the background by default, and move focus only when callers set `focus: true`. The FleetView inspect key still focuses the pane it opens. Thanks to [@boggylp](https://github.com/boggylp) for #1226.
50
+ - Show FleetView transcript fallbacks for trusted session roots instead of warning about an untrusted session file. Thanks to [@aliceisjustplaying](https://github.com/aliceisjustplaying) for #1154.
51
+ - Route Fleet inspector steering for live in-process workflow children through their foreground routes instead of the detached async queue. Thanks to [@ViktorBarzin](https://github.com/ViktorBarzin) for #1218 and #1216.
52
+ - Serialize same-worktree Orca progress-tab creation so numbered tabs appear left to right in sequence. Thanks to [@hyein-cbio](https://github.com/hyein-cbio) for #1196.
53
+ - Bound repeated async-state queries to active, exact-id, and recent-terminal indexes instead of scanning the full async history (#1162).
54
+ - Move retention directory discovery to a read-only worker so full scans do not block the extension event loop (#1188).
55
+ - Reclaim proven-safe async run and orphan result state after 30 days in bounded, locked cleanup passes with rename-first tombstones (#1163).
56
+ - Sweep expired wait subscriptions armed by another session, so stale records stop accumulating in the subscriptions directory. Thanks to [@MarcusNeufeldt](https://github.com/MarcusNeufeldt) for #1142.
57
+ - Restore and list schedules after their project directory is deleted, and skip orphan schedule directories without letting create reuse stale state. Thanks to [@ELA718](https://github.com/ELA718) for #1171 and [@colinb4987](https://github.com/colinb4987) for #1167.
58
+ - Fall back from an implicit `defaultContext: fork` to `fresh` when the parent session file or current leaf is not available yet. Explicit `context: "fork"` remains fail-fast. Thanks to [@hyein-cbio](https://github.com/hyein-cbio) for #1137.
59
+ - Keep retained workflow children resumable when their managed worktree cwd is preserved in the handoff manifest (#1172).
60
+ - Preserve workflow child task output when neither the workflow nor child configures an output file (#1136).
61
+ - Preserve a child's file-only report when its output path also names the workflow summary output.
62
+ - Keep concurrent async result promotion from deleting a newer payload or another promoter's published result. Thanks to [@albertgwo](https://github.com/albertgwo) for #1130.
63
+ - Keep `mcp:<server>` direct tools available when pi-mcp-adapter cache identity includes a request-header command. Thanks to [@xz-dev](https://github.com/xz-dev) for #1141.
64
+ - Isolate test async state from the user temp root and write each missing-mission sync diagnostic only once (#1164, #1165).
65
+ - Keep structured delegation integration coverage active when the test process inherits a subagent-child environment marker.
66
+
67
+ ## [0.50.0] - 2026-08-15
68
+
69
+ ### Added
70
+ - Add optional Orca progress tabs with bounded, sanitized mirrors for native Pi and external CLI children. Thanks to @hyein-cbio for #1080.
71
+ - Show caller-owned external jobs in FleetView through a bounded push/cache API, without polling or exposing managed controls. Thanks to @ssyram for #1083.
72
+ - Add a bounded current-status snapshot for async runs in RPC surfaces, without replaying terminal history. Thanks to @yanqianglu for #1078.
73
+ - Add an optional `foregroundDetachShortcut` binding and show it in the running single-subagent card, so foreground work can be moved to the background without editing package source. Thanks to @Lewis-E for #1097.
74
+
75
+ ### Changed
76
+ - Clarify retained-child resumability and native supervisor coordination guidance. Thanks to @ELA718 for #1126.
77
+ - Clarify that completed retained writers should use `resume`, while `steer` with `mode: "follow_up"` only queues text for the next revival (#1104).
78
+ - Treat oracle/advisor consultation prompts as supervisor-backed dialogue when material unknowns remain (#1102).
79
+ - Show explicit resumable and not-resumable states, with fallback guidance, in retained child listings (#1101).
80
+ - Reduce reload work for large async histories by indexing the async result inbox by session, observer, and tool-call id instead of scanning every old result file. Stale terminal active markers now age out, and replay cleanup scans run less often.
81
+
82
+ ### Fixed
83
+ - Keep Orca progress tabs from treating write-stream backpressure as mirror truncation.
84
+ - Stop advertising an `output-<index>.log` artifact in run transcripts when that file was never written, so workflow runs no longer point at a path that cannot exist. Thanks to @lbijeau for #1124.
85
+ - Keep FleetView working when a session file path is longer than a short identity, instead of failing external-job inspection on every poll. Thanks to @albertgwo for #1121 and @Don-Yin for #1122.
86
+ - Keep structured single-child runs from overriding output paths in the task, while preserving explicit and agent-configured outputs. Thanks to @pasemes for #1119.
87
+ - Keep no-edit confirmations guarded after later changes retract a prior implementation (#1115).
88
+ - Remove the native generic `intercom` compatibility fallback from supervisor coordination while preserving `contact_supervisor`, `subagent_supervisor`, and external `intercom` providers. Thanks to @jaudiger for #1107.
89
+ - Report an actionable project-settings override when duplicate ambient Pi extensions prevent a child from starting (#1114).
90
+ - Keep the FleetView overlay refreshed while open and count active leaf agents in the compact summary. Thanks to @Don-Yin for #1108.
91
+ - Keep user-requested foreground detaches from showing supervisor-response recovery guidance. Thanks to @Lewis-E for #1109.
92
+ - Reject configured subagent models that are not in the active host model registry before spawning a child, instead of forwarding an invalid `--model` argument to Pi. Thanks to @DresvyanskiyDenis for #1093.
93
+ - Start Herdr inspector and project pane commands with a shell-safe executable token, including paths that need quoting in Nushell. Thanks to @Rival for #1092.
94
+ - Stop `agentContract.version` from using an `enum` on an integer, which Gemini's function-calling schema subset rejects. Integer bounds express the same constraint and are valid everywhere. Thanks to @MarcusNeufeldt for #1095.
95
+ - Show supervisor-detached workflow children as paused and needing attention instead of failed while preserving recovery guidance (#1096).
96
+ - Show workflow-owned foreground children and recursive nested runs as a bounded tree in FleetView. Thanks to @expoli for #1086.
97
+ - Warn once, instead of on every heartbeat, when a long-running workflow child outlives its mission record. Thanks to @albertgwo for #1079.
98
+ - Keep deleted-schedule timers from exiting Pi and re-arm recurring schedules after unexpected timer fire failures. Thanks to @albertgwo for #1084.
99
+ - Count native `await` use of `runs.run`, `runs.all`, and launch-containing Promise combinators as consumed without allowing fire-and-forget launches. Thanks to @kebinzhi for #1082.
100
+
5
101
  ## [0.49.0] - 2026-08-13
6
102
 
7
103
  ### Added
@@ -10,6 +106,7 @@
10
106
  - Inspect async run state with `debug.run`, without exposing prompts, secrets, or transcripts (#1037).
11
107
  - Let builtin role overrides keep Pi's normal tools and extensions with `tools: "inherit"`. Thanks to @estanexanavsem for #1047 and @davidarny for #1049.
12
108
  - Add simple terminal examples for FleetView, the async widget, and inline tool display. Thanks to @czottmann for #1050.
109
+ - Add per-tool-call wedge protection with `toolTimeoutMs` call → agent → config → environment precedence. Known-fast built-in tools get a five-minute default, long-running tools get attention notices without a hard default, matching `toolCallId` timers survive parallel tool completions, and supervisor waits (`contact_supervisor`, `intercom`, `subagent_wait`) remain exempt. Thanks to @forrestbthomas for #1077.
13
110
 
14
111
  ### Changed
15
112
  - Clean up active-run limits and artifact packaging code without changing behavior.
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: gpt-pro
3
+ description: Surf GPT Pro advisory runner through the external-job provider bridge
4
+ runner:
5
+ type: external-job
6
+ provider: surf-oracle
7
+ async: true
8
+ systemPromptMode: replace
9
+ inheritProjectContext: false
10
+ inheritSkills: false
11
+ ---
12
+
13
+ You are a read-only GPT Pro advisor reached through Surf Oracle.
14
+
15
+ Review the supplied task and context.
16
+ Return clear advice, risks, and recommended next steps.
17
+ Do not claim you edited files or ran local tools.
package/agents/oracle.md CHANGED
@@ -2,7 +2,7 @@
2
2
  name: oracle
3
3
  aliases: advisor
4
4
  description: High-context decision-consistency oracle that protects inherited state and prevents drift
5
- tools: read, grep, find, ls, bash, intercom
5
+ tools: read, grep, find, ls, bash
6
6
  thinking: high
7
7
  systemPromptMode: replace
8
8
  inheritProjectContext: true
@@ -16,9 +16,11 @@ Your primary job is to prevent the main agent from making hidden, conflicting, o
16
16
 
17
17
  Before you do anything else, reconstruct the key inherited decisions, constraints, and open questions from the forked conversation, codebase state, and task. Those decisions form your baseline contract. Preserve them unless there is strong evidence they should be overturned.
18
18
 
19
- If you need clarification from the main agent and runtime bridge instructions are present, use `contact_supervisor` with `reason: "need_decision"` and wait for the reply. Use `reason: "progress_update"` only for concise updates when blocked, explicitly asked for progress, or when a recommendation or concern would benefit from immediate discussion. Keep coordination traffic tight and purposeful. Do not narrate your whole review through `contact_supervisor`.
19
+ If the task is framed as asking or consulting the oracle, treat it as a live consultation unless the parent explicitly requests a one-shot report. When runtime bridge instructions provide `contact_supervisor`, ask one focused question or challenge if a material unknown, contradiction, or unapproved decision would make a final recommendation guessy. If no supervisor channel is available, return the best recommendation and name the decision that still needs the main agent.
20
20
 
21
- Do not send routine completion handoffs. If no coordination is needed, return the final oracle recommendation normally. Fall back to generic `intercom` only if `contact_supervisor` is unavailable and the runtime bridge instructions identify a safe target.
21
+ If you need clarification from the main agent and bridge instructions provide `contact_supervisor`, use it with `reason: "need_decision"` and wait for the reply. Use `reason: "progress_update"` only for concise updates when blocked, explicitly asked for progress, or when a recommendation or concern would benefit from immediate discussion. Keep coordination traffic tight and purposeful. Do not narrate your whole review through `contact_supervisor`.
22
+
23
+ Do not send routine completion handoffs. If no coordination is needed, or after needed coordination is answered, return the final oracle recommendation normally. If `contact_supervisor` is unavailable, return the best recommendation and name the decision that still needs the main agent. Use generic `intercom` only when an external intercom provider explicitly supplies that tool and the task identifies a safe target.
22
24
 
23
25
  Core responsibilities:
24
26
  - reconstruct inherited decisions, constraints, and open questions from the context
@@ -39,8 +41,8 @@ What you do not do by default:
39
41
 
40
42
  Working rules:
41
43
  - Use `bash` only for inspection, verification, or read-only analysis.
42
- - If information is missing and it matters, ask the main agent with `contact_supervisor` and `reason: "need_decision"` instead of guessing.
43
- - If the answer depends on a decision the main agent has not made yet, stop and ask with `contact_supervisor` before continuing.
44
+ - If information is missing and it matters, ask the main agent with `contact_supervisor` and `reason: "need_decision"` when bridge instructions provide that tool. If no supervisor channel is available, return the best recommendation and name the unresolved decision instead of guessing.
45
+ - If the answer depends on a decision the main agent has not made yet, stop and ask with `contact_supervisor` when bridge instructions provide that tool. If no supervisor channel is available, mark the decision as still needed in the final recommendation.
44
46
  - When bridge instructions are present, send concise coordination messages only when a recommendation, concern, or question would benefit from immediate discussion instead of waiting silently until the final return.
45
47
  - Prefer narrow, specific corrections to the current path over rewriting the whole plan.
46
48
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: researcher
3
3
  description: Autonomous web researcher — searches, evaluates, and synthesizes a focused research brief
4
- tools: read, write, web_search, fetch_content, get_search_content, intercom
4
+ tools: read, write, web_search, fetch_content, get_search_content
5
5
  thinking: medium
6
6
  systemPromptMode: replace
7
7
  inheritProjectContext: true
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: reviewer
3
3
  description: Versatile review specialist for code diffs, plans, proposed solutions, codebase health, and PR/issue validation
4
- tools: read, grep, find, ls, intercom
4
+ tools: read, grep, find, ls
5
5
  thinking: high
6
6
  systemPromptMode: replace
7
7
  inheritProjectContext: true
@@ -62,7 +62,7 @@ Review a PR or issue by understanding the context, then verifying:
62
62
  ## Supervisor coordination
63
63
  If runtime bridge instructions identify a safe supervisor target and you are blocked or need a decision, use `contact_supervisor` with `reason: "need_decision"` and wait for the reply. Do not ask for clarification when the only conflict is review-only/no-edit versus progress-writing; no-edit wins. Use `reason: "progress_update"` only for meaningful progress or unexpected discoveries that change the review plan. Do not send routine completion handoffs; return the completed review normally.
64
64
 
65
- Fall back to generic `intercom` only if `contact_supervisor` is unavailable and the runtime bridge instructions identify a safe target. If no safe target is discoverable, do not guess.
65
+ If `contact_supervisor` is unavailable, report the blocking decision in your final review. Use generic `intercom` only when an external intercom provider explicitly supplies that tool and the task identifies a safe target.
66
66
 
67
67
  ## Review output format
68
68
  Structure your findings clearly:
package/agents/scout.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: scout
3
3
  description: Fast codebase recon that returns compressed context for handoff
4
- tools: read, grep, find, ls, bash, write, intercom
4
+ tools: read, grep, find, ls, bash, write
5
5
  thinking: low
6
6
  systemPromptMode: replace
7
7
  inheritProjectContext: true
package/agents/worker.md CHANGED
@@ -22,7 +22,7 @@ The builtin worker uses a strict tool allowlist. It does not inherit ambient ext
22
22
 
23
23
  If the task is framed as an approved direction, oracle handoff, or execution plan, treat that direction as the contract. Validate it against the actual code, but do not silently make new product, architecture, or scope decisions.
24
24
 
25
- If the implementation reveals a decision that was not approved and is required to continue safely, pause and escalate through the live coordination channel. If runtime bridge instructions are present, use them as the source of truth for which supervisor session to contact and how to coordinate. Use `contact_supervisor` with `reason: "need_decision"` when a new decision is needed, and stay alive to receive the reply before continuing. Use `reason: "progress_update"` only for concise non-blocking progress updates when that extra coordination is helpful or explicitly requested. Fall back to generic `intercom` only if `contact_supervisor` is unavailable. Do not finish your final response with a question that requires the supervisor to choose before you can continue.
25
+ If the implementation reveals a decision that was not approved and is required to continue safely, pause and escalate through the live coordination channel. If runtime bridge instructions are present, use them as the source of truth for which supervisor session to contact and how to coordinate. Use `contact_supervisor` with `reason: "need_decision"` when a new decision is needed, and stay alive to receive the reply before continuing. Use `reason: "progress_update"` only for concise non-blocking progress updates when that extra coordination is helpful or explicitly requested. If `contact_supervisor` is unavailable, stop and report the required decision in your final response. Do not finish your final response with a question that requires the supervisor to choose before you can continue.
26
26
 
27
27
  Default responsibilities:
28
28
  - validate the task or approved direction against the actual code
@@ -0,0 +1,180 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { parentPort } from "node:worker_threads";
4
+
5
+ const ACTIVE_RUN_INDEX_DIR = ".active-runs";
6
+ const RESULT_TOMBSTONE_PREFIX = ".deleting-result-";
7
+
8
+ function compareRelative(left, right) {
9
+ if (left < right) return -1;
10
+ if (left > right) return 1;
11
+ return 0;
12
+ }
13
+
14
+ function insertSmallest(entries, candidate, limit) {
15
+ if (limit <= 0) return;
16
+ const index = entries.findIndex((entry) => compareRelative(candidate.relative, entry.relative) < 0);
17
+ if (index === -1) entries.push(candidate);
18
+ else entries.splice(index, 0, candidate);
19
+ if (entries.length > limit) entries.pop();
20
+ }
21
+
22
+ function streamDirWindow(dir, limit, after, relativePath, usable) {
23
+ if (limit <= 0) return { entries: [], rawReads: 0, exhausted: true, cursorCleared: false };
24
+ let handle;
25
+ try {
26
+ handle = fs.opendirSync(dir);
27
+ const next = [];
28
+ const wrapped = [];
29
+ let rawReads = 0;
30
+ while (true) {
31
+ const entry = handle.readSync();
32
+ if (!entry) break;
33
+ rawReads += 1;
34
+ const relative = relativePath(entry);
35
+ if (!usable(entry, relative)) continue;
36
+ const candidate = { relative, name: entry.name };
37
+ insertSmallest(wrapped, candidate, limit);
38
+ if (after === undefined || compareRelative(relative, after) > 0) insertSmallest(next, candidate, limit);
39
+ }
40
+ const cursorCleared = after !== undefined && next.length === 0;
41
+ return { entries: cursorCleared ? wrapped : next, rawReads, exhausted: true, cursorCleared };
42
+ } catch (error) {
43
+ if (error?.code === "ENOENT") return { entries: [], rawReads: 0, exhausted: true, cursorCleared: false };
44
+ throw error;
45
+ } finally {
46
+ handle?.closeSync();
47
+ }
48
+ }
49
+
50
+ function discover(request) {
51
+ const startedAt = Date.now();
52
+ const cursor = structuredClone(request.cursor);
53
+ const raw = { reads: 0, exhausted: {} };
54
+ const record = (source, scan) => {
55
+ raw.reads += scan.rawReads;
56
+ raw.exhausted[source] = scan.exhausted;
57
+ };
58
+ const runScan = streamDirWindow(
59
+ request.asyncDirRoot,
60
+ request.runBudget,
61
+ cursor.runAfter,
62
+ (entry) => entry.name,
63
+ (entry) => entry.isDirectory() && entry.name !== ACTIVE_RUN_INDEX_DIR,
64
+ );
65
+ record("runs", runScan);
66
+ if (runScan.cursorCleared) delete cursor.runAfter;
67
+ const runCandidates = runScan.entries.slice(0, request.runBudget);
68
+ for (const candidate of runCandidates) cursor.runAfter = candidate.relative;
69
+
70
+ const resultCandidates = [];
71
+ const sourceLimit = Math.max(1, Math.ceil(request.resultBudget / 4));
72
+ const addFiles = (dir, kind, cursorTarget, source, budget = sourceLimit) => {
73
+ const remaining = Math.min(budget, request.resultBudget - resultCandidates.length);
74
+ if (remaining <= 0) return 0;
75
+ const before = resultCandidates.length;
76
+ const after = cursorTarget.type === "pending"
77
+ ? cursor.resultPendingAfterBySession?.[cursorTarget.session]
78
+ : cursor[cursorTarget.key];
79
+ const scan = streamDirWindow(
80
+ dir,
81
+ remaining,
82
+ after,
83
+ (entry) => path.relative(request.resultsDir, path.join(dir, entry.name)),
84
+ (entry) => entry.isFile() && (entry.name.startsWith(RESULT_TOMBSTONE_PREFIX) || entry.name.endsWith(".json")),
85
+ );
86
+ record(source, scan);
87
+ if (scan.cursorCleared) {
88
+ if (cursorTarget.type === "pending") delete cursor.resultPendingAfterBySession?.[cursorTarget.session];
89
+ else delete cursor[cursorTarget.key];
90
+ }
91
+ for (const candidate of scan.entries.slice(0, remaining)) {
92
+ const tombstone = candidate.name.startsWith(RESULT_TOMBSTONE_PREFIX);
93
+ resultCandidates.push({
94
+ name: candidate.name,
95
+ relative: candidate.relative,
96
+ kind: tombstone ? "tombstone" : kind,
97
+ cursor: cursorTarget,
98
+ });
99
+ if (cursorTarget.type === "pending") {
100
+ cursor.resultPendingAfterBySession ??= {};
101
+ cursor.resultPendingAfterBySession[cursorTarget.session] = candidate.relative;
102
+ } else cursor[cursorTarget.key] = candidate.relative;
103
+ }
104
+ return resultCandidates.length - before;
105
+ };
106
+
107
+ addFiles(request.resultsDir, "public", { type: "result", key: "resultPublicAfter" }, "results.public");
108
+ const pendingRoot = path.join(request.resultsDir, "result-pending");
109
+ const pendingRemaining = Math.min(request.resultBudget, sourceLimit, request.resultBudget - resultCandidates.length);
110
+ const pendingScan = streamDirWindow(
111
+ pendingRoot,
112
+ pendingRemaining,
113
+ cursor.pendingSessionAfter,
114
+ (entry) => entry.name,
115
+ (entry) => entry.isDirectory(),
116
+ );
117
+ record("results.pendingSessions", pendingScan);
118
+ const livePendingSessions = new Set(pendingScan.entries.map((entry) => entry.relative));
119
+ if (cursor.resultPendingAfterBySession) {
120
+ let pruned = 0;
121
+ for (const session of Object.keys(cursor.resultPendingAfterBySession).sort()) {
122
+ if (pruned >= request.resultBudget) break;
123
+ if (!livePendingSessions.has(session) && !fs.existsSync(path.join(pendingRoot, session))) {
124
+ delete cursor.resultPendingAfterBySession[session];
125
+ pruned += 1;
126
+ }
127
+ }
128
+ if (Object.keys(cursor.resultPendingAfterBySession).length === 0) delete cursor.resultPendingAfterBySession;
129
+ }
130
+ if (pendingScan.cursorCleared) delete cursor.pendingSessionAfter;
131
+ const pendingSessions = pendingScan.entries.slice(0, pendingRemaining);
132
+ let pendingFileBudget = Math.min(sourceLimit, request.resultBudget - resultCandidates.length);
133
+ for (const session of pendingSessions) {
134
+ if (pendingFileBudget <= 0) break;
135
+ cursor.pendingSessionAfter = session.relative;
136
+ pendingFileBudget -= addFiles(
137
+ path.join(pendingRoot, session.name),
138
+ "pending",
139
+ { type: "pending", session: session.relative },
140
+ `results.pending.${session.name}`,
141
+ pendingFileBudget,
142
+ );
143
+ }
144
+ addFiles(path.join(request.resultsDir, "completion-replay"), "replay", { type: "result", key: "resultReplayAfter" }, "results.replay");
145
+ addFiles(path.join(request.resultsDir, "output-archives"), "archive", { type: "result", key: "resultArchiveAfter" }, "results.archive");
146
+
147
+ const cursorOps = [];
148
+ for (const key of ["runAfter", "resultPublicAfter", "resultReplayAfter", "resultArchiveAfter", "pendingSessionAfter"]) {
149
+ if (cursor[key] === request.cursor[key]) continue;
150
+ cursorOps.push(cursor[key] === undefined ? { type: "delete", key } : { type: "set", key, value: cursor[key] });
151
+ }
152
+ const oldPending = request.cursor.resultPendingAfterBySession ?? {};
153
+ const nextPending = cursor.resultPendingAfterBySession ?? {};
154
+ for (const session of new Set([...Object.keys(oldPending), ...Object.keys(nextPending)])) {
155
+ if (oldPending[session] === nextPending[session]) continue;
156
+ cursorOps.push(nextPending[session] === undefined
157
+ ? { type: "delete-pending", session }
158
+ : { type: "set-pending", session, value: nextPending[session] });
159
+ }
160
+ return {
161
+ type: "result",
162
+ passId: request.passId,
163
+ discoveryDurationMs: Math.max(0, Date.now() - startedAt),
164
+ rawReads: raw.reads,
165
+ sourceExhausted: raw.exhausted,
166
+ runCandidates,
167
+ resultCandidates,
168
+ cursorOps,
169
+ };
170
+ }
171
+
172
+ if (!parentPort) throw new Error("Async retention discovery requires a worker parent port.");
173
+ parentPort.on("message", (request) => {
174
+ const passId = request?.passId;
175
+ try {
176
+ parentPort.postMessage(discover(request));
177
+ } catch (error) {
178
+ parentPort.postMessage({ type: "error", passId, error: error instanceof Error ? error.message : String(error) });
179
+ }
180
+ });
package/docs/agents.md CHANGED
@@ -41,12 +41,45 @@ Builtins load at the lowest priority, so a user or project agent with the same n
41
41
  | `worker` | Implementation work, including approved oracle handoffs. It edits files, validates, and escalates unapproved decisions instead of guessing. |
42
42
  | `reviewer` | Code review and small fixes. It checks the implementation against the task/plan, tests, edge cases, and simplicity. |
43
43
  | `oracle` | A second opinion before acting. It challenges assumptions, catches drift, and recommends the safest next move without editing. |
44
+ | `gpt-pro` | Read-only Surf GPT Pro advice through the `surf-oracle` external-job provider bridge. |
44
45
  | `delegate` | A lightweight general delegate when you want a child agent that behaves close to the parent session. |
45
46
 
46
47
  Rule of thumb: `scout` before you understand the code, `researcher` before you trust external facts, `worker` to implement, `reviewer` to check, and `oracle` when the decision itself feels risky.
47
48
 
48
49
  `oracle` is an advisory reviewer that critiques direction and proposes an execution prompt without editing files. `advisor` is the same bundled role under the Claude Code-compatible name.
49
50
 
51
+ `gpt-pro` uses `runner.type: external-job` with provider `surf-oracle`. It starts through the same `subagent({ agent: "gpt-pro" })` mental model as any other agent, but the work is owned by Surf through the external-job provider bridge. The Pi async run remains the source of truth for status, artifacts, wake/wait, mission attachment, retention, and diagnostics.
52
+
53
+ Claude Code can be configured as a read-only advisor with `runner.type: external-cli` when the Claude Code CLI is installed and you have verified the flags for your local version. pi-subagents does not ship or enforce Claude Code flags. Use a project or user agent like this only after checking your CLI help:
54
+
55
+ ```yaml
56
+ ---
57
+ name: claude-advisor
58
+ description: Read-only Claude Code advisor through the local CLI
59
+ runner:
60
+ type: external-cli
61
+ command: claude
62
+ args: ["<verified-read-only-flags>"]
63
+ promptDelivery: stdin
64
+ async: true
65
+ ---
66
+
67
+ Review the task and return advice only. Do not edit files.
68
+ ```
69
+
70
+ ### Advisory runner data boundary
71
+
72
+ Native `oracle` runs inside Pi and can use its configured read tools. `claude-advisor` sends the assembled prompt to the configured local external CLI through stdin. `gpt-pro` sends the assembled prompt to the registered Surf provider. Provider options and a prompt digest are persisted in Pi run state. The prompt text is delivered through the local host bridge to the provider and is not stored in the public result payload. Do not place secrets in advisory prompts unless the target provider is approved to receive them.
73
+
74
+ ### External-job state table
75
+
76
+ | Durable file | Owner | States | Release predicate | Rollback predicate | Stale-head behavior | Fail-closed cases |
77
+ |--------------|-------|--------|-------------------|--------------------|---------------------|-------------------|
78
+ | `status.json` step `runner` and `externalJob` | pi-subagents async runner | `queued`, `running`, `completed`, `failed`, `stopped`, `blocked` | Provider `result` returns terminal data and the async result is written | Provider start/status/result/reattach returns an error | If a status file already has a provider job id, recovery calls `reattach` and `result`; it refuses to start a new prompt when the provider or prompt digest differs | Missing provider, capacity conflict, malformed provider response, bridge timeout, prompt digest mismatch |
79
+ | `result.json` or session result payload | pi-subagents async runner | `complete`, `failed`, `stopped` | All steps reach terminal state and result publication succeeds or is recoverably indexed | Result write fails and pending result repair records the terminal state | Stale status can repair from an existing result file | Unindexed sessionless stale failure |
80
+ | `external-job-requests/` and `external-job-responses/` | Host-mediated provider bridge | pending request, terminal response | Host process writes a matching response and removes the request | Bridge timeout or malformed request response | Requests are operation-scoped. Recovery sends `reattach`/`result`, not `start`, when job metadata exists | Provider not registered, host bridge not loaded, malformed request, provider exception |
81
+ | Provider artifact path | External provider | provider-defined terminal artifact | Provider returns `artifactPath`, or Pi writes returned text to `external-job-<index>.result.md` | Provider reports failure or no result | Existing artifact path is retained in `status.json` | Missing artifact with no text output returns a terminal message instead of inventing content |
82
+
50
83
  The `researcher` builtin uses `web_search`, `fetch_content`, and `get_search_content`. Those require [pi-web-access](https://github.com/nicobailon/pi-web-access):
51
84
 
52
85
  ```bash
@@ -102,7 +135,7 @@ Use these fields when an agent should see more:
102
135
  | `systemPromptMode: append` | Append the agent prompt to Pi's normal base prompt. |
103
136
  | `inheritProjectContext: true` | Keep inherited project instructions from files like `AGENTS.md` and `CLAUDE.md`. |
104
137
  | `inheritSkills: true` | Let the child see Pi's discovered skills catalog. |
105
- | `defaultContext: fork` | Use forked session context when a launch omits `context`; explicit `context: "fresh"` still wins. |
138
+ | `defaultContext: fork` | Prefer forked session context when a launch omits `context`; if the parent has no persisted session file or current leaf yet, the implicit default falls back to `fresh` without a failed first attempt. Explicit `context: "fork"` remains strict, and explicit `context: "fresh"` still wins. |
106
139
 
107
140
  Builtin agents opt into project instruction inheritance by default so they follow repo-specific rules out of the box. `delegate` also uses append mode because its job is orchestration inside the parent workflow.
108
141
 
@@ -133,6 +166,7 @@ defaultReads: context.md
133
166
  defaultProgress: true
134
167
  async: true
135
168
  timeoutMs: 900000
169
+ toolTimeoutMs: 600000
136
170
  turnBudget: {"maxTurns":20,"graceTurns":2}
137
171
  acceptance: {"level":"none","reason":"lightweight lookup"}
138
172
  acceptanceRole: read-only
@@ -170,7 +204,7 @@ Field notes:
170
204
  | `systemPromptMode` | `replace` by default; `append` keeps Pi's base prompt. |
171
205
  | `inheritProjectContext` | Keeps or strips inherited project instruction blocks. |
172
206
  | `inheritSkills` | Keeps or strips Pi's discovered skills catalog. |
173
- | `defaultContext` | Optional `fresh` or `fork` launch context default for this agent. |
207
+ | `defaultContext` | Optional `fresh` or `fork` launch-context preference. An implicit `fork` falls back to `fresh` when the parent has no persisted session file or current leaf; an explicit launch `context: "fork"` remains strict. |
174
208
  | `skills` | Selects specific skills for the child, regardless of `inheritSkills`. |
175
209
  | `skillPath` | Invocation-private skill files or discovery directories. Relative paths resolve from the agent definition file. Local matches take precedence, while unresolved or unreadable matches fall back to normal skill discovery. This field discovers candidates only; `skills` still selects what the child receives. |
176
210
  | `output` | Default single-agent output file. |
@@ -178,6 +212,7 @@ Field notes:
178
212
  | `defaultProgress` | Maintain `progress.md`. |
179
213
  | `async` | Default a single-agent launch to background (`true`) or foreground (`false`) when the call omits `async`. Explicit call values and `forceTopLevelAsync` win. |
180
214
  | `timeoutMs` | Positive integer default runtime deadline in milliseconds for single-agent launches. Foreground launches use 30 minutes when neither the call nor agent provides a timeout; explicit `timeoutMs`/`maxRuntimeMs` and agent defaults win. |
215
+ | `toolTimeoutMs` | Optional positive integer hard per-tool-call deadline in milliseconds. An explicit call value wins, then this agent default, global `toolTimeoutMs`, and `PI_SUBAGENT_TOOL_TIMEOUT_MS`. When omitted, known-fast built-in tools get a five-minute default; long-running tools get attention notices but no hard default. It does not extend the run-level deadline; `contact_supervisor`, `intercom`, and `subagent_wait` are exempt. |
181
216
  | `turnBudget` | JSON object default such as `{"maxTurns":20,"graceTurns":2}` for single-agent launches. An explicit call value wins, followed by this agent default, then global `turnBudget` config. |
182
217
  | `acceptance` | Acceptance default for single-agent launches. Use a scalar level such as `checked` or an inline/block YAML map such as `{ level: "none", reason: "lightweight lookup" }`. Explicit call values win; chain and parallel acceptance remains task/step configuration. |
183
218
  | `acceptanceRole` | Optional `read-only` or `writer` role for automatic acceptance inference. Explicit task mutation or no-edit intent wins; otherwise the declared role replaces agent-name guessing. This does not grant or revoke tools. |