pi-subagents 0.34.0 → 0.35.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 (98) hide show
  1. package/CHANGELOG.md +78 -9
  2. package/README.md +213 -32
  3. package/index.ts +1 -0
  4. package/install.mjs +1 -1
  5. package/package.json +23 -8
  6. package/prompts/review-loop.md +3 -1
  7. package/skills/pi-subagents/SKILL.md +87 -25
  8. package/src/agents/agent-management.ts +82 -15
  9. package/src/agents/agent-serializer.ts +19 -0
  10. package/src/agents/agents.ts +91 -49
  11. package/src/agents/frontmatter.ts +67 -13
  12. package/src/agents/skills.ts +25 -12
  13. package/src/api/background-work.ts +197 -0
  14. package/src/api/delegation.ts +158 -0
  15. package/src/extension/chain-validation.ts +165 -0
  16. package/src/extension/doctor.ts +15 -0
  17. package/src/extension/fanout-child.ts +3 -1
  18. package/src/extension/index.ts +65 -124
  19. package/src/extension/rpc.ts +10 -2
  20. package/src/extension/schemas.ts +18 -14
  21. package/src/extension/steering-notices.ts +35 -0
  22. package/src/extension/tool-description.ts +20 -9
  23. package/src/intercom/intercom-bridge.ts +3 -2
  24. package/src/intercom/native-supervisor-channel.ts +9 -1
  25. package/src/intercom/result-intercom.ts +4 -0
  26. package/src/runs/background/async-execution.ts +293 -44
  27. package/src/runs/background/async-job-tracker.ts +56 -9
  28. package/src/runs/background/async-resume.ts +159 -52
  29. package/src/runs/background/async-status.ts +25 -18
  30. package/src/runs/background/auto-drain.ts +67 -0
  31. package/src/runs/background/chain-root-attachment.ts +16 -8
  32. package/src/runs/background/control-channel.ts +260 -13
  33. package/src/runs/background/fleet-view.ts +23 -2
  34. package/src/runs/background/notify.ts +79 -10
  35. package/src/runs/background/result-watcher.ts +12 -9
  36. package/src/runs/background/run-id-resolver.ts +14 -2
  37. package/src/runs/background/run-status.ts +23 -15
  38. package/src/runs/background/scheduled-runs.ts +3 -0
  39. package/src/runs/background/stale-run-reconciler.ts +32 -10
  40. package/src/runs/background/steering.ts +237 -0
  41. package/src/runs/background/subagent-runner.ts +898 -236
  42. package/src/runs/background/subagent-wait.ts +484 -0
  43. package/src/runs/background/top-level-async.ts +2 -1
  44. package/src/runs/background/wait-config.ts +36 -0
  45. package/src/runs/background/wait-tool.ts +26 -0
  46. package/src/runs/foreground/async-steering-action.ts +230 -0
  47. package/src/runs/foreground/chain-clarify.ts +22 -6
  48. package/src/runs/foreground/chain-execution.ts +50 -32
  49. package/src/runs/foreground/execution.ts +308 -94
  50. package/src/runs/foreground/subagent-executor.ts +592 -268
  51. package/src/runs/shared/acceptance.ts +355 -97
  52. package/src/runs/shared/child-protocol.ts +121 -0
  53. package/src/runs/shared/completion-guard.ts +8 -127
  54. package/src/runs/shared/dynamic-fanout.ts +6 -4
  55. package/src/runs/shared/model-fallback.ts +36 -0
  56. package/src/runs/shared/nested-events.ts +9 -4
  57. package/src/runs/shared/nested-render.ts +4 -1
  58. package/src/runs/shared/parallel-utils.ts +7 -0
  59. package/src/runs/shared/pi-args.ts +34 -7
  60. package/src/runs/shared/pi-spawn.ts +18 -12
  61. package/src/runs/shared/session-lease.ts +279 -0
  62. package/src/runs/shared/single-output.ts +61 -6
  63. package/src/runs/shared/spawn-budget.ts +128 -0
  64. package/src/runs/shared/subagent-control.ts +10 -6
  65. package/src/runs/shared/subagent-prompt-runtime.ts +127 -26
  66. package/src/runs/shared/task-intent.ts +176 -0
  67. package/src/runs/shared/tool-availability.ts +65 -0
  68. package/src/runs/shared/turn-budget.ts +49 -4
  69. package/src/shared/atomic-json.ts +4 -1
  70. package/src/shared/fork-context.ts +28 -3
  71. package/src/shared/model-info.ts +7 -4
  72. package/src/shared/status-format.ts +7 -1
  73. package/src/shared/types.ts +203 -25
  74. package/src/shared/utils.ts +35 -7
  75. package/src/slash/delegation-adapters.ts +457 -0
  76. package/src/slash/delegation-request.ts +103 -0
  77. package/src/slash/prompt-template-bridge.ts +167 -344
  78. package/src/slash/slash-commands.ts +239 -6
  79. package/src/slash/subagents-admin.ts +428 -0
  80. package/src/slash/subagents-editor.ts +86 -0
  81. package/src/tui/fleet.ts +405 -0
  82. package/src/tui/render.ts +90 -16
  83. package/src/watchdog/change-signature.ts +127 -0
  84. package/src/watchdog/child-status.ts +205 -0
  85. package/src/watchdog/emission-guard.ts +123 -0
  86. package/src/watchdog/lsp-diagnostics.ts +532 -0
  87. package/src/watchdog/model-selection.ts +167 -0
  88. package/src/watchdog/register-child.ts +117 -0
  89. package/src/watchdog/register-main.ts +433 -0
  90. package/src/watchdog/render.ts +54 -0
  91. package/src/watchdog/review.ts +293 -0
  92. package/src/watchdog/runtime.ts +712 -0
  93. package/src/watchdog/settings.ts +528 -0
  94. package/src/watchdog/tool-actions.ts +155 -0
  95. package/src/watchdog/turn-delta.ts +161 -0
  96. package/src/watchdog/types.ts +188 -0
  97. package/src/watchdog/warning-format.ts +73 -0
  98. package/src/runs/background/wait.ts +0 -394
@@ -67,11 +67,11 @@ const JsonSchemaObject = Type.Unsafe({
67
67
 
68
68
  const AcceptanceOverride = Type.Unsafe({
69
69
  anyOf: [
70
- { type: "string", enum: ["auto", "none", "attested", "checked", "verified", "reviewed"] },
70
+ { type: "string", enum: ["auto", "attested", "checked", "verified", "reviewed"] },
71
71
  { type: "boolean", enum: [false] },
72
72
  { type: "object", additionalProperties: true },
73
73
  ],
74
- description: "Optional acceptance policy. Omitted means auto-inferred; verified requires configured runtime commands.",
74
+ description: "Optional acceptance policy. Omitted means auto-inferred; verified requires configured runtime commands. Reviewed is inferred-only because explicit runs cannot supply an independent reviewer result. Bare \"none\" requires { level: \"none\", reason: \"...\" }, while false is deprecated.",
75
75
  });
76
76
 
77
77
  const TurnBudgetOverride = Type.Object({
@@ -108,7 +108,7 @@ const TaskItem = Type.Object({
108
108
  });
109
109
 
110
110
  // Parallel task item (within a parallel step)
111
- const ParallelTaskSchema = Type.Object({
111
+ export const ParallelTaskSchema = Type.Object({
112
112
  agent: Type.String(),
113
113
  task: Type.Optional(Type.String({ description: "Task template with {task}, {previous}, {chain_dir} variables. Defaults to {previous}." })),
114
114
  phase: Type.Optional(Type.String({ description: "Optional phase/group label for status and graph rendering." })),
@@ -127,7 +127,7 @@ const ParallelTaskSchema = Type.Object({
127
127
  acceptance: Type.Optional(AcceptanceOverride),
128
128
  });
129
129
 
130
- const DynamicExpandSchema = Type.Object({
130
+ export const DynamicExpandSchema = Type.Object({
131
131
  from: Type.Object({
132
132
  output: Type.String({ description: "Prior named structured output to expand from." }),
133
133
  path: Type.String({ description: "JSON Pointer into the structured output, e.g. /items." }),
@@ -138,7 +138,7 @@ const DynamicExpandSchema = Type.Object({
138
138
  onEmpty: Type.Optional(Type.String({ enum: ["skip", "fail"], description: "Empty input behavior. Defaults to skip." })),
139
139
  }, { additionalProperties: false });
140
140
 
141
- const DynamicParallelTemplateSchema = Type.Object({
141
+ export const DynamicParallelTemplateSchema = Type.Object({
142
142
  agent: Type.String(),
143
143
  task: Type.Optional(Type.String({ description: "Task template with {item}, {item.path}, {task}, {previous}, {chain_dir}, and {outputs.name} variables." })),
144
144
  phase: Type.Optional(Type.String({ description: "Optional phase/group label for status and graph rendering." })),
@@ -155,13 +155,13 @@ const DynamicParallelTemplateSchema = Type.Object({
155
155
  acceptance: Type.Optional(AcceptanceOverride),
156
156
  }, { additionalProperties: false });
157
157
 
158
- const DynamicCollectSchema = Type.Object({
158
+ export const DynamicCollectSchema = Type.Object({
159
159
  as: Type.String({ description: "Safe output name for the ordered collected result array." }),
160
160
  outputSchema: Type.Optional(JsonSchemaObject),
161
161
  }, { additionalProperties: false });
162
162
 
163
163
  // Flattened so chain steps do not need an object-shape anyOf/oneOf union.
164
- const ChainItem = Type.Object({
164
+ export const ChainItem = Type.Object({
165
165
  agent: Type.Optional(Type.String({ description: "Sequential step agent name" })),
166
166
  task: Type.Optional(Type.String({
167
167
  description: "Task template with variables: {task}=original request, {previous}=prior step's text response, {chain_dir}=shared folder, {outputs.name}=prior named output. Required for first step, defaults to '{previous}' for subsequent steps."
@@ -221,13 +221,13 @@ const SubagentParamsSchema = Type.Object({
221
221
  description: "Management/control action only. Must be omitted for execution mode (single, parallel, or chain)."
222
222
  })),
223
223
  id: Type.Optional(Type.String({
224
- description: "Run id or prefix for action='status', action='interrupt', action='resume', action='steer', or action='append-step'."
224
+ description: "Run id or prefix for action='status', action='interrupt', action='stop', action='resume', action='steer', or action='append-step'."
225
225
  })),
226
226
  runId: Type.Optional(Type.String({
227
- description: "Target run ID for action='interrupt', action='resume', action='steer', or action='append-step'. Defaults to the most recently active controllable run for interrupt. Prefer id for new calls."
227
+ description: "Target run ID for action='interrupt', action='stop', action='resume', action='steer', or action='append-step'. Prefer id for new calls."
228
228
  })),
229
229
  dir: Type.Optional(Type.String({
230
- description: "Async run directory for action='status', action='resume', or action='steer'."
230
+ description: "Async run directory for action='status', action='stop', action='resume', or action='steer'."
231
231
  })),
232
232
  index: Type.Optional(Type.Integer({ minimum: 0, description: "Zero-based child index for actions that target a specific child or transcript." })),
233
233
  view: Type.Optional(Type.String({
@@ -235,7 +235,11 @@ const SubagentParamsSchema = Type.Object({
235
235
  description: "Optional status view. Use view='fleet' for a read-only active foreground/async fleet surface, or view='transcript' with id/dir (and optional index) to tail a run transcript.",
236
236
  })),
237
237
  lines: Type.Optional(Type.Integer({ minimum: 1, maximum: 500, description: "Maximum transcript lines for action='status', view='transcript'. Defaults to 80." })),
238
- message: Type.Optional(Type.String({ description: "Follow-up message for action='resume' or non-terminal guidance for action='steer'. Use index to choose a child from multi-child runs." })),
238
+ message: Type.Optional(Type.String({ description: "Follow-up message for action='resume' (revive paused, completed, or failed children, or reach a routed nested run) or live async guidance for action='steer'. Stopped runs are non-resumable. Use index to choose a child from multi-child runs." })),
239
+ additional: Type.Optional(Type.Integer({ minimum: 1, description: "Positive launches to add with action='grant-spawn-budget'. Root interactive parent with native user confirmation only; total grants cannot exceed the original configured cap." })),
240
+ scope: Type.Optional(Type.String({ enum: ["session", "user", "project"], description: "Scope for action='watchdog.configure'. Defaults to session to avoid persistent settings writes unless user/project is explicit." })),
241
+ target: Type.Optional(Type.String({ enum: ["main", "children", "child"], description: "Target for action='watchdog.configure'. Defaults to main. Use target='child' with agent for a per-agent child watchdog override." })),
242
+ thinking: Type.Optional(Type.Unsafe({ anyOf: [{ type: "string" }, { type: "boolean", enum: [false] }], description: "Thinking level for action='watchdog.configure' (off/minimal/low/medium/high/xhigh/max, inherit, or false for off)." })),
239
243
  schedule: Type.Optional(Type.String({ description: "Explicit one-shot schedule for action='schedule'. Only honored when scheduledRuns.enabled is true. Use '+10m' or a future ISO timestamp with timezone; scheduled runs always launch async with fresh context." })),
240
244
  scheduleName: Type.Optional(Type.String({ description: "Optional display name for action='schedule'." })),
241
245
  // Chain identifier for management (can't reuse 'chain' — that's the execution array)
@@ -293,9 +297,9 @@ const SubagentParamsSchema = Type.Object({
293
297
 
294
298
  export const SubagentParams = keepTopLevelParameterDescriptions(SubagentParamsSchema);
295
299
 
296
- const WaitParamsSchema = Type.Object({
300
+ const SubagentWaitParamsSchema = Type.Object({
297
301
  id: Type.Optional(Type.String({
298
- description: "Run id or prefix to wait for one specific run. Omit to wait across every active async run started in this session.",
302
+ description: "Async run or remembered detached foreground run id/prefix to wait for one specific run. Omit to wait across every active async run started in this session.",
299
303
  })),
300
304
  all: Type.Optional(Type.Boolean({
301
305
  description: "Wait for ALL active runs to finish. Default false: return as soon as the first run finishes, so a fleet manager can spawn a replacement and wait again. Ignored when id targets a single run.",
@@ -306,4 +310,4 @@ const WaitParamsSchema = Type.Object({
306
310
  })),
307
311
  });
308
312
 
309
- export const WaitParams = keepTopLevelParameterDescriptions(WaitParamsSchema);
313
+ export const SubagentWaitParams = keepTopLevelParameterDescriptions(SubagentWaitParamsSchema);
@@ -0,0 +1,35 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import type { SteeringNotice, SubagentState } from "../shared/types.ts";
3
+
4
+ export const SUBAGENT_STEERING_MESSAGE_TYPE = "subagent_steering_notice";
5
+
6
+ export interface SubagentSteeringMessageDetails extends SteeringNotice {
7
+ source?: "async";
8
+ asyncDir?: string;
9
+ noticeText?: string;
10
+ }
11
+
12
+ export function formatSteeringNotice(details: Pick<SubagentSteeringMessageDetails, "runId" | "requestId" | "state" | "message">): string {
13
+ return [
14
+ `Subagent steering ${details.state}: ${details.runId}`,
15
+ `Request: ${details.requestId}`,
16
+ details.message,
17
+ "Inspect the run status before sending another correction.",
18
+ ].join("\n");
19
+ }
20
+
21
+ export function handleSubagentSteeringNotice(input: {
22
+ pi: Pick<ExtensionAPI, "sendMessage">;
23
+ state: SubagentState;
24
+ details: SubagentSteeringMessageDetails;
25
+ }): void {
26
+ if (!input.details || (input.details.state !== "failed" && input.details.state !== "partial" && input.details.state !== "recovered")) return;
27
+ if (!input.state.currentSessionId || input.details.currentSessionId !== input.state.currentSessionId) return;
28
+ const noticeText = input.details.noticeText ?? formatSteeringNotice(input.details);
29
+ input.pi.sendMessage({
30
+ customType: SUBAGENT_STEERING_MESSAGE_TYPE,
31
+ content: noticeText,
32
+ display: true,
33
+ details: { ...input.details, noticeText },
34
+ }, { triggerTurn: true });
35
+ }
@@ -8,8 +8,8 @@ const CUSTOM_TOOL_DESCRIPTION_MAX_BYTES = 50 * 1024;
8
8
 
9
9
  export const SUBAGENT_SAFETY_GUIDANCE = `SAFETY-CRITICAL SUBAGENT GUIDANCE:
10
10
  • Use { action: "list" } before execution and only run executable/non-disabled agents or chains.
11
- • Keep execution and management separate: omit action for SINGLE/PARALLEL/CHAIN execution; use action only for list/get/models/create/update/delete/status/interrupt/resume/append-step/doctor.
12
- • Async/background runs: launch with async:true only when work can proceed independently. Do not sleep or poll status just to wait; if this turn must block, use the wait tool. Otherwise continue useful work or respond and let completion notifications arrive.
11
+ • Keep execution and management separate: omit action for SINGLE/PARALLEL/CHAIN execution; use action only for list/get/models/create/update/delete/status/grant-spawn-budget/interrupt/stop/resume/append-step/doctor.
12
+ • Async/background runs: launch with async:true only when work can proceed independently. Do not sleep or poll status just to wait. In an interactive session, normally return control and let Pi wake you; do not call subagent_wait merely to wait. Override that default and call subagent_wait when the current request is run-to-completion — for example, the user asked you to report results back before continuing or a skill must finish in one turn. Headless sessions auto-drain current-session work at agent_end; use subagent_wait when this turn must receive results before it ends.
13
13
  • Child-safety boundary: ordinary child subagents are not orchestrators and must not run subagents. Only explicitly configured fanout children may use the child-safe subagent tool, still bounded by depth/session limits.
14
14
  • Writing/review safety: keep one writer for the same cwd/worktree. Use fresh-context read-only reviewers/validators for independent review, then have the parent synthesize and apply fixes as the sole writer unless an isolated worktree was intentionally requested.
15
15
  • Artifacts/status essentials: chain outputs live under {chain_dir}; async runs expose asyncId/asyncDir with status.json, events.jsonl, output logs, and status via { action: "status", id }. Include output paths and residual risks when reporting results.`;
@@ -22,6 +22,7 @@ EXECUTION (use exactly ONE mode):
22
22
  • CHAIN: { chain: [{agent:"agent-a"}, {parallel:[{agent:"agent-b",count:3}]}] } - sequential pipeline with optional parallel fan-out
23
23
  • PARALLEL: { tasks: [{agent,task,count?,output?,reads?,progress?}, ...], concurrency?: number, worktree?: true } - concurrent execution (worktree: isolate each task in a git worktree)
24
24
  • Optional context: { context: "fresh" | "fork" } (explicit value overrides every child; when omitted, each requested agent uses its own defaultContext, otherwise "fresh"; inspect agent defaults via { action: "list" })
25
+ • Fork thinking: model strings accept a thinking suffix (provider/model:off|minimal|low|medium|high|xhigh|max). Forking over a parent transcript that carries signed Anthropic thinking blocks forces thinking off only when a child's effective primary or fallback model resolves to the Anthropic provider or anthropic-messages API; unresolved models are treated conservatively. The result notes affected children, including on failures. Use fresh context when an Anthropic child needs thinking.
25
26
  • Optional timeout: { timeoutMs } or { maxRuntimeMs } sets a run-level max runtime for foreground and async/background runs
26
27
  • If { action: "list" } shows proactive skill subagent suggestions, consider a small fresh-context fanout for broad tasks where one of those skills would materially help
27
28
 
@@ -30,19 +31,26 @@ CHAIN TEMPLATE VARIABLES (use in task strings):
30
31
  • {previous} - Text response from the previous step (empty for first step)
31
32
  • {chain_dir} - Shared directory for chain files (e.g., <tmpdir>/pi-subagents-<scope>/chain-runs/abc123/)
32
33
 
33
- Example: { chain: [{agent:"agent-a", task:"Analyze {task}"}, {agent:"agent-b", task:"Plan based on {previous}"}] }
34
+ CHAIN EXAMPLES (quick reference for the nested schema):
35
+ • Sequential: { chain: [{agent:"agent-a", task:"Analyze {task}"}, {agent:"agent-b", task:"Plan based on {previous}"}] }
36
+ • Parallel fan-out: { chain: [{parallel: [{agent:"agent-a", task:"Check part of {task}", count: 3}]}] }
37
+ • Mixed: { chain: [{agent:"agent-a", task:"Research {task}"}, {parallel: [{agent:"agent-b", task:"Review {previous}", count: 2}]}, {agent:"agent-c", task:"Summarize {previous}"}] }
34
38
 
35
39
  MANAGEMENT (use action field, omit agent/task/chain/tasks):
36
40
  • { action: "list" } - discover executable agents/chains
37
41
  • { action: "get", agent: "name" } - full detail; packaged agents use dotted runtime names like "package.agent"
38
42
  • { action: "models", agent?: "name" } - show the runtime-loaded builtin subagent model mapping, optionally filtered to one builtin
39
- • { action: "create", config: { name: "custom-agent", package: "code-analysis", systemPrompt, systemPromptMode, inheritProjectContext, inheritSkills, defaultContext, ... } }
43
+ • { action: "watchdog.status" | "watchdog.check" | "watchdog.recommend-model" } - inspect the opt-in subagent watchdog and its strong complementary model recommendation
44
+ • { action: "watchdog.configure", model: "recommended" | "inherit" | "provider/model[:thinking]", scope?: "session" | "user" | "project", target?: "main" | "children" | "child", agent?: "name", thinking?: "inherit" | "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" } - configure watchdog model selection; default scope is session, use persistent scopes only when the user asks
45
+ • { action: "create", config: { name: "custom-agent", package: "code-analysis", systemPrompt, systemPromptMode, inheritProjectContext, inheritSkills, defaultContext, acceptance, acceptanceRole: "read-only" | "writer", ... } }
46
+ • acceptanceRole affects inferred acceptance only, never tool access. Explicit task mutation/no-edit intent wins; omission preserves name heuristics. Update with false or an empty string to clear it.
40
47
  • { action: "update", agent: "code-analysis.custom-agent", config: { package: "analysis", ... } } - merge
41
48
  • { action: "delete", agent: "code-analysis.custom-agent" }
42
49
  • { action: "eject", agent: "reviewer", agentScope?: "user" | "project" } - copy a bundled/package agent to user/project scope as an editable custom file that shadows the original (default scope: user)
43
50
  • { action: "disable", agent: "reviewer", agentScope?: "user" | "project" } - hide any agent from runtime discovery via a reversible settings override (default scope: user)
44
51
  • { action: "enable", agent: "reviewer", agentScope?: "user" | "project" } - remove a disabled override and restore discovery
45
52
  • { action: "reset", agent: "reviewer", agentScope?: "user" | "project" } - delete the scope's custom agent file and/or settings override, restoring the bundled default
53
+ • { action: "grant-spawn-budget", additional: 10 } - add bounded capacity from the root interactive parent after native user confirmation; grants are rejected while children are active and cumulative grants cannot exceed the original configured cap
46
54
  • Use chainName for chain operations; packaged chains also use dotted runtime names
47
55
 
48
56
  CONTROL:
@@ -50,8 +58,9 @@ CONTROL:
50
58
  • { action: "status", view: "fleet" } - read-only active foreground/async fleet view with transcript commands
51
59
  • { action: "status", id: "...", view: "transcript", index?: 0, lines?: 80 } - tail a run or child output/session transcript
52
60
  • { action: "interrupt", id?: "..." } - soft-interrupt the current child turn and leave the run paused
53
- • { action: "resume", id: "...", message: "...", index?: 0 } - interrupt then follow up with a live async child, or revive a completed async/foreground child from its session
54
- • { action: "steer", id: "...", message: "...", index?: 0 } - queue non-terminal guidance for a live/queued async Pi child when supported
61
+ • { action: "stop", id: "..." } - stop a current-session top-level async run; stopped runs finish with state "stopped"
62
+ • { action: "resume", id: "...", message: "...", index?: 0 } - revive a paused, completed, or failed async/foreground child from its session; stopped runs are non-resumable; routed nested runs may accept live follow-ups; use steer for a live top-level async child
63
+ • { action: "steer", id: "...", message: "...", index?: 0 } - await correlated child-Pi input acceptance for up to 3 seconds; returns delivered, scheduled, pending, partial, recovered, or failed with a request id. Only top-level single runs may recover after a further 15-second pause/revival bound; chain, parallel, and nested runs never auto-interrupt.
55
64
  • { action: "append-step", id: "...", chain: [{agent:"agent-c", task:"Use {previous}"}] } - append one step to the tail of a running async chain
56
65
 
57
66
  SCHEDULE (opt-in; requires { "scheduledRuns": { "enabled": true } } in config.json):
@@ -72,15 +81,17 @@ EXECUTE:
72
81
  • SINGLE {agent, task?}; PARALLEL {tasks:[{agent,task,count?,output?,reads?,progress?}], concurrency?, worktree?}; CHAIN {chain:[{agent,task?},{parallel:[...]}]}.
73
82
  • context can be "fresh" or "fork"; omitted uses each agent defaultContext, otherwise fresh. timeoutMs/maxRuntimeMs apply to foreground and async/background runs.
74
83
  • Chain templates may use {task}, {previous}, {chain_dir}, and named outputs. Parallel worktree isolation requires a clean git repo.
84
+ • Chain example: { chain: [{agent:"agent-a", task:"Analyze {task}"}, {parallel: [{agent:"agent-b", task:"Check {previous}", count: 3}]}] }
75
85
  • If list shows proactive skill subagent suggestions, use a small fresh-context fanout only when the task is broad enough.
76
86
 
77
87
  MANAGE / CONTROL:
78
- • Use action without execution fields: list, get, models, create, update, delete, eject, disable, enable, reset, doctor.
79
- • Async control actions: status, interrupt, resume, steer, append-step. Use status view:"fleet" for active-run overview, view:"transcript" to tail child output, and steer for non-terminal live guidance. Use id/runId prefixes carefully; use index for a specific child.
88
+ • Use action without execution fields: list, get, models, create, update, delete, eject, disable, enable, reset, grant-spawn-budget, doctor, watchdog.status, watchdog.check, watchdog.recommend-model, watchdog.configure.
89
+ • Agent acceptanceRole (read-only or writer) affects inferred acceptance only, never tools. Explicit task intent wins; omission keeps name heuristics. Update with false or an empty string to clear it.
90
+ • Async control actions: status, interrupt, stop, resume, steer, append-step. Use stop with an id for current-session top-level async runs. Use status view:"fleet" for active-run overview, view:"transcript" to tail child output, steer for acknowledged top-level live async guidance, and resume for paused/completed/failed revival or a routed nested follow-up. Stopped runs are non-resumable. Steering delivery means Pi accepted the correlated user input, not model compliance; use index for a specific child.
80
91
  • Opt-in schedule actions: schedule, schedule-list, schedule-status, schedule-cancel. Schedule only explicit delayed runs the user asked for.
81
92
 
82
93
  ASYNC / WAIT:
83
- • async:true detaches background work. Do not sleep or poll just to wait; use the wait tool only when this turn must block. Otherwise continue useful work or respond and let completion notifications arrive.
94
+ • async:true detaches background work. Do not sleep or poll just to wait. In an interactive session, normally return control to the user and let Pi wake you on completion; do not call subagent_wait merely to wait. Override that default with subagent_wait only for run-to-completion requests (the user asked for results back this turn, or a skill must finish in one turn). Non-interactive runs (pi -p) auto-drain current-session work at agent_end; call subagent_wait when this turn must receive results before it ends. Otherwise continue useful work or respond.
84
95
  • Status and artifacts live under asyncId/asyncDir with status.json, events.jsonl, output logs, session files, and { action:"status", id:"..." }.
85
96
 
86
97
  SAFETY:
@@ -21,8 +21,9 @@ const DEFAULT_INTERCOM_BRIDGE_TEMPLATE = `The inherited thread is reference-only
21
21
 
22
22
  Use contact_supervisor first. It resolves the supervisor session "{orchestratorTarget}" and run metadata automatically.
23
23
  - Need a decision, blocked, approval, or product/API/scope ambiguity: contact_supervisor({ reason: "need_decision", message: "<question>" })
24
- - After contact_supervisor with reason "need_decision", stay alive and continue only after the reply arrives. Do not finish your final response with a choose-one question.
25
- - Do not ask for clarification when the only conflict is review-only/no-edit versus progress-writing or artifact-writing instructions. Review-only/no-edit wins; leave files unchanged and mention the conflict in your final result only if it matters.
24
+ - Need structured supervisor input rather than a freeform reply: contact_supervisor({ reason: "interview_request", message: "<what input is needed>", interview: { title: "...", questions: [] } })
25
+ - After contact_supervisor with reason "need_decision" or "interview_request", stay alive and continue only after the reply arrives. Do not finish your final response with a choose-one question.
26
+ - Do not ask for clarification when the only conflict is review-only/no-edit versus progress-writing or artifact-writing instructions. If an output path is configured but no write-capable tool is available, return the complete artifact in your final response; the runtime will persist it. Do not contact the supervisor merely because you cannot write that output path directly.
26
27
  - Meaningful progress or unexpected discoveries that change the plan: contact_supervisor({ reason: "progress_update", message: "UPDATE: <summary>" })
27
28
  - Generic intercom is lower-level plumbing/fallback only: intercom({ action: "ask", to: "{orchestratorTarget}", message: "<question>" })
28
29
 
@@ -12,7 +12,7 @@ import {
12
12
  SUBAGENT_RUN_ID_ENV,
13
13
  SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV,
14
14
  } from "../runs/shared/pi-args.ts";
15
- import { POLL_INTERVAL_MS, TEMP_ROOT_DIR, type SubagentState } from "../shared/types.ts";
15
+ import { INTERCOM_DETACH_REQUEST_EVENT, POLL_INTERVAL_MS, TEMP_ROOT_DIR, type IntercomEventBus, type SubagentState } from "../shared/types.ts";
16
16
  import { writeAtomicJson } from "../shared/atomic-json.ts";
17
17
 
18
18
  const SUPERVISOR_CHANNEL_ROOT = path.join(TEMP_ROOT_DIR, "supervisor-channels");
@@ -646,6 +646,14 @@ export function createNativeSupervisorChannel(pi: ExtensionAPI, state: SubagentS
646
646
  childIndex: request.childIndex,
647
647
  },
648
648
  });
649
+ if (request.expectsReply) {
650
+ (pi as { events?: IntercomEventBus }).events?.emit(INTERCOM_DETACH_REQUEST_EVENT, {
651
+ requestId: request.id,
652
+ runId: request.runId,
653
+ agent: request.agent,
654
+ childIndex: request.childIndex,
655
+ });
656
+ }
649
657
  }
650
658
  };
651
659
 
@@ -22,6 +22,7 @@ export function resolveSubagentResultStatus(input: {
22
22
  detached?: boolean;
23
23
  }): SubagentResultStatus {
24
24
  if (input.detached) return "detached";
25
+ if (input.state === "stopped") return "stopped";
25
26
  if (input.interrupted || input.state === "paused") return "paused";
26
27
  if (typeof input.success === "boolean") return input.success ? "completed" : "failed";
27
28
  if (input.state === "complete") return "completed";
@@ -35,6 +36,7 @@ function countStatuses(children: SubagentResultIntercomChild[]): Record<Subagent
35
36
  completed: 0,
36
37
  failed: 0,
37
38
  paused: 0,
39
+ stopped: 0,
38
40
  detached: 0,
39
41
  };
40
42
  for (const child of children) {
@@ -47,6 +49,7 @@ function formatStatusCounts(counts: Record<SubagentResultStatus, number>): strin
47
49
  const parts = [
48
50
  counts.completed ? `${counts.completed} completed` : undefined,
49
51
  counts.failed ? `${counts.failed} failed` : undefined,
52
+ counts.stopped ? `${counts.stopped} stopped` : undefined,
50
53
  counts.paused ? `${counts.paused} paused` : undefined,
51
54
  counts.detached ? `${counts.detached} detached` : undefined,
52
55
  ].filter((part): part is string => Boolean(part));
@@ -56,6 +59,7 @@ function formatStatusCounts(counts: Record<SubagentResultStatus, number>): strin
56
59
  function resolveGroupedStatus(children: SubagentResultIntercomChild[]): SubagentResultStatus {
57
60
  const counts = countStatuses(children);
58
61
  if (counts.failed > 0) return "failed";
62
+ if (counts.stopped > 0) return "stopped";
59
63
  if (counts.paused > 0) return "paused";
60
64
  if (counts.completed > 0) return "completed";
61
65
  if (counts.detached > 0) return "detached";