@bastani/atomic 0.9.15 → 0.9.16-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +10 -0
  3. package/dist/builtin/intercom/README.md +8 -8
  4. package/dist/builtin/intercom/contact-supervisor-tool.ts +68 -32
  5. package/dist/builtin/intercom/index-heavy.ts +1 -0
  6. package/dist/builtin/intercom/index.ts +21 -15
  7. package/dist/builtin/intercom/intercom-tool.ts +66 -10
  8. package/dist/builtin/intercom/package.json +1 -1
  9. package/dist/builtin/intercom/parent-ask-handoff.ts +72 -0
  10. package/dist/builtin/mcp/package.json +1 -1
  11. package/dist/builtin/subagents/CHANGELOG.md +13 -0
  12. package/dist/builtin/subagents/README.md +23 -12
  13. package/dist/builtin/subagents/agents/worker.md +2 -2
  14. package/dist/builtin/subagents/package.json +1 -1
  15. package/dist/builtin/subagents/skills/subagent/SKILL.md +10 -17
  16. package/dist/builtin/subagents/src/extension/index.ts +5 -30
  17. package/dist/builtin/subagents/src/extension/notification-content.ts +1 -1
  18. package/dist/builtin/subagents/src/extension/schemas.ts +2 -14
  19. package/dist/builtin/subagents/src/extension/tool-description.ts +1 -2
  20. package/dist/builtin/subagents/src/extension/tool-rendering.ts +53 -0
  21. package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +1 -1
  22. package/dist/builtin/subagents/src/intercom/result-intercom.ts +4 -4
  23. package/dist/builtin/subagents/src/runs/foreground/execution-parent-ask-handoff.ts +78 -0
  24. package/dist/builtin/subagents/src/runs/foreground/inprocess-run-sync.ts +7 -5
  25. package/dist/builtin/subagents/src/runs/foreground/notify.ts +5 -6
  26. package/dist/builtin/subagents/src/runs/foreground/parent-ask-output.ts +48 -0
  27. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-burst-display.ts +46 -0
  28. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-burst.ts +403 -0
  29. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-context.ts +48 -9
  30. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-input.ts +3 -1
  31. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-live-update.ts +11 -0
  32. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +164 -93
  33. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel.ts +36 -29
  34. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parent-ask-projection.ts +11 -0
  35. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +42 -17
  36. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-status.ts +3 -121
  37. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-types.ts +7 -2
  38. package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +30 -220
  39. package/dist/builtin/subagents/src/runs/inprocess/control-status.ts +0 -34
  40. package/dist/builtin/subagents/src/runs/inprocess/index.ts +0 -12
  41. package/dist/builtin/subagents/src/runs/inprocess/prompt-behavior.ts +1 -1
  42. package/dist/builtin/subagents/src/runs/inprocess/runner.ts +2 -141
  43. package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +8 -0
  44. package/dist/builtin/subagents/src/shared/status-format.ts +3 -3
  45. package/dist/builtin/subagents/src/shared/types-config.ts +41 -0
  46. package/dist/builtin/subagents/src/shared/types-foreground-state.ts +7 -15
  47. package/dist/builtin/subagents/src/shared/types-results.ts +3 -1
  48. package/dist/builtin/subagents/src/shared/types-runtime.ts +1 -11
  49. package/dist/builtin/subagents/src/tui/render-progress.ts +2 -2
  50. package/dist/builtin/subagents/src/tui/render-result-compact.ts +2 -2
  51. package/dist/builtin/subagents/src/tui/render-result.ts +38 -14
  52. package/dist/builtin/subagents/src/tui/render-status-progress.ts +1 -1
  53. package/dist/builtin/web-access/package.json +1 -1
  54. package/dist/builtin/workflows/CHANGELOG.md +10 -0
  55. package/dist/builtin/workflows/README.md +1 -1
  56. package/dist/builtin/workflows/package.json +1 -1
  57. package/dist/builtin/workflows/src/durable/dbos-embedded-postgres-root.ts +548 -39
  58. package/dist/builtin/workflows/src/durable/dbos-embedded-postgres.ts +529 -65
  59. package/dist/builtin/workflows/src/durable/dbos-lifecycle.ts +30 -8
  60. package/dist/builtin/workflows/src/durable/dbos-local-postgres.ts +36 -3
  61. package/dist/builtin/workflows/src/durable/local-command.ts +68 -10
  62. package/dist/builtin/workflows/src/extension/dispatcher.ts +7 -1
  63. package/dist/builtin/workflows/src/extension/extension-factory.ts +2 -2
  64. package/dist/builtin/workflows/src/extension/index.bundle.mjs +1094 -298
  65. package/dist/builtin/workflows/src/extension/public-types.ts +2 -2
  66. package/dist/builtin/workflows/src/extension/render-result.ts +16 -2
  67. package/dist/builtin/workflows/src/extension/runtime.ts +8 -1
  68. package/dist/builtin/workflows/src/extension/workflow-request-abort.ts +15 -0
  69. package/dist/builtin/workflows/src/extension/workflow-tool-content.ts +4 -3
  70. package/dist/builtin/workflows/src/extension/workflow-tool-registration.ts +107 -13
  71. package/dist/builtin/workflows/src/extension/workflow-tool.ts +22 -12
  72. package/dist/builtin/workflows/src/tui/widget.ts +25 -6
  73. package/dist/core/event-bus.d.ts.map +1 -1
  74. package/dist/core/event-bus.js +19 -3
  75. package/dist/core/event-bus.js.map +1 -1
  76. package/docs/intercom.md +28 -26
  77. package/docs/subagents.md +15 -11
  78. package/docs/workflows.md +27 -10
  79. package/npm-shrinkwrap.json +32 -32
  80. package/package.json +3 -3
  81. package/dist/builtin/subagents/src/runs/inprocess/attempt-handles.ts +0 -104
  82. /package/dist/builtin/subagents/src/runs/foreground/{subagent-executor-resume.ts → subagent-executor-cwd.ts} +0 -0
@@ -161,7 +161,7 @@ Workflow invocations receive a stable, non-`default` Intercom group automaticall
161
161
 
162
162
  ## Where running subagents show up
163
163
 
164
- Foreground runs stream progress in the conversation while they run. Parallel calls keep their grouped task shape in progress and results, and status/control actions can inspect or interrupt retained foreground children.
164
+ Foreground runs stream progress in the conversation while they run. Parallel calls keep their grouped task shape in progress and results, and status/control actions can inspect or interrupt live foreground children.
165
165
 
166
166
  You can ask naturally:
167
167
 
@@ -193,7 +193,7 @@ Use the optional prompt shortcuts below when you want the pattern to be repeatab
193
193
 
194
194
  Packaged `worker` defaults to forked context when a launch omits `context`; every other builtin runs fresh. Pass `context: "fresh"` when you intentionally want a fresh `worker` run.
195
195
 
196
- Child-safety boundaries are enforced at runtime by typed admission policy. In-process child sessions load bundled extensions through normal discovery. The `subagent` tool may therefore be registered when the child's active tool selection permits it, including the default no-allowlist case; an explicit allowlist may omit it. Tool presence does not grant fanout: fanout is authorized only when the resolved builtin `tools` list includes `subagent`. Typed admission policy lets a non-fanout child use only `list`, `get`, `status`, and `doctor`; delegation, `resume`, and `interrupt` receive the fanout refusal. A management-restricted child is also refused `create`, `update`, and `delete`. The bundled `pi-subagents` skill remains parent-only and is stripped from child prompts, including fanout-authorized children. No admitted child may delegate: launches, `resume`, and `interrupt` are refused for every child regardless of its fanout authorization. Children receive boundary instructions that they are not the parent orchestrator and must complete their assigned task directly. Forked child context filtering also removes parent-only subagent artifacts (including old hidden orchestration-instruction messages, slash/status/control messages, and prior parent `subagent` tool-call/tool-result history) while preserving ordinary prose and unrelated tool calls/results.
196
+ Child-safety boundaries are enforced at runtime by typed admission policy. In-process child sessions load bundled extensions through normal discovery. The `subagent` tool may therefore be registered when the child's active tool selection permits it, including the default no-allowlist case; an explicit allowlist may omit it. Tool presence does not grant fanout: fanout is authorized only when the resolved builtin `tools` list includes `subagent`. Typed admission policy lets a non-fanout child use only `list`, `get`, `status`, and `doctor`; delegation and `interrupt` receive the fanout refusal. A management-restricted child is also refused `create`, `update`, and `delete`. The bundled `pi-subagents` skill remains parent-only and is stripped from child prompts, including fanout-authorized children. No admitted child may delegate or control another child: launches and `interrupt` are refused for every child regardless of its fanout authorization. Children receive boundary instructions that they are not the parent orchestrator and must complete their assigned task directly. Forked child context filtering also removes parent-only subagent artifacts (including old hidden orchestration-instruction messages, slash/status/control messages, and prior parent `subagent` tool-call/tool-result history) while preserving ordinary prose and unrelated tool calls/results.
197
197
 
198
198
  ## Optional shortcuts
199
199
 
@@ -233,12 +233,15 @@ Ask codebase-analyzer to review this plan. If it sees a decision I need to make,
233
233
 
234
234
  The child can use one dedicated coordination tool:
235
235
 
236
- - `contact_supervisor`: the child contacts the parent/supervisor session that delegated the task. Use `reason: "need_decision"` for blocking decisions or clarification, and `reason: "progress_update"` for short non-blocking updates when a discovery changes the plan. Do not ask for clarification when the only conflict is review-only/no-edit versus progress-writing or artifact-writing instructions; no-edit wins.
236
+ - `contact_supervisor`: the child contacts the parent/supervisor session that delegated the task. Use `reason: "need_decision"` for a blocking decision, `reason: "interview_request"` for structured questions, and `reason: "progress_update"` for a short non-blocking update when a discovery changes the plan. Do not ask for clarification when the only conflict is review-only/no-edit versus progress-writing or artifact-writing instructions; no-edit wins.
237
237
 
238
- Child-side routine completion handoffs are still not expected. With the intercom bridge active, parent-side Atomic sends grouped completion results through `pi-intercom`: one grouped message per foreground parent `subagent` run and one per detached child completion. Intercom-confirmed delivery returns a compact receipt with artifact/session paths; without that confirmation, the normal full output is preserved. Grouped messages include child intercom targets and full child summaries.
239
- When the companion is enabled and available, the bridge gives eligible children deterministic Intercom identities and coordination tools without connecting them automatically. Parent and child connections remain tool-driven: if a child may need live coordination, the parent model should invoke `intercom({ action: "status" })` before launch, and the child connects when it invokes `contact_supervisor` or `intercom`. Foreground launches and management-only actions do not force Intercom loading or broker startup.
238
+ Child-side routine completion handoffs are still not expected. With the Intercom bridge active, a blocking decision or interview from the exact foreground child ends at the source before broker send or reply-waiter admission. The parent `subagent` call returns the verbatim question, ordered attachments with duplicates preserved, agent identity, terminal run ID, and a dynamic `[TASK_CONTEXT]` handoff. `intercom.ask` does the same only when its resolved target is the launching parent. The parent answers by launching a fresh child with a new run identity and the supervisor answer in its task.
240
239
 
241
- For foreground runs, Intercom uses a targeted probe/reservation before delivery: only the exact live child can claim its message. Atomic then commits detach for that child and waits for its acknowledgement before placing claimed asks, sends, decisions, interviews, and progress updates in the parent's model-visible steering queue, so cancellation between phases cannot surface an orphaned request. Blocking calls remain alive for an exact threaded reply and then resume; fire-and-forget calls create no waiter. The retained child later replaces its detached status and artifacts with the real result. Cancellation/replacement invalidates stale handshakes, duplicate delivery cannot recommit, and unmatched messages retain queued-until-idle behavior.
240
+ For parallel runs, the claim interrupts every active sibling and prevents queued work from starting or requesting authorization. The sibling set, sessions, and worktrees are not retained for continuation. Any follow-up starts fresh SINGLE or PARALLEL children explicitly. `intercom.send`, progress updates, and asks to siblings or other peers retain the exact-child probe/commit detach and ordinary Intercom delivery paths.
241
+
242
+ With the Intercom bridge active, the parent may load and connect its Intercom runtime before initial child execution to issue the exact child's broker capability. The child connection remains tool-driven. A claimed `contact_supervisor` decision or interview still yields before child send or reply-waiter admission; `intercom.ask` connects the child to resolve both targets.
243
+
244
+ Parent-side Atomic still sends grouped completion results through Intercom: one grouped message per foreground parent `subagent` run and one per detached child completion. Intercom-confirmed delivery returns a compact receipt with artifact/session paths; without that confirmation, the normal full output is preserved. Grouped messages include child Intercom targets and full child summaries.
242
245
 
243
246
  If a child appears stalled, needs-attention notices can show up in the parent session with useful next actions, such as checking `subagent({ action: "status" })`, interrupting the run, or nudging the child.
244
247
 
@@ -537,6 +540,16 @@ These are the parameters the LLM passes when it calls the `subagent` tool. Most
537
540
  ], worktree: true }
538
541
  ```
539
542
 
543
+ ### Sibling execution calls
544
+
545
+ If one assistant response emits several sibling execution-mode `subagent` calls, Atomic collects the synchronous burst before any child starts and runs it through one indexed parallel set. Each original tool call receives only its own children in source order, and its live result, progress, control, and artifact updates are projected to that same route without sibling data. The TUI redraws the shared run as one aggregate parallel widget rather than retaining one widget per original call. A solitary call keeps its original mode, sequential awaited calls stay independent, and management actions bypass collection. A call that arrives after a child has started still gets the existing in-progress rejection. Prefer one explicit `{ tasks: [...] }` call when you intend parallel work; burst collection handles model-emitted sibling calls.
546
+
547
+ In a collected burst, a call's top-level `agent` task comes first, followed by its `tasks` array. Duplicates and array order are preserved, and `count` expands in place. The task cap applies after all calls are flattened and counts are expanded, with the same hard maximum of 50. Each call-level `cwd` selects that call's agent-discovery scope and child base directory. A task-level `cwd` stays relative to that base and affects child execution, not agent discovery. This per-origin rule applies only to collected sibling calls; an ordinary explicit `{ tasks: [...] }` call keeps one discovery scope from its top-level `cwd`. Per-call and per-task `group` values also remain attached to their children, and a task-level group wins.
548
+
549
+ For a collected `worktree: true` burst, every call-level `cwd` must resolve to the same path. That common path becomes the shared worktree root; differing origins reject the burst before launch, and any task-level `cwd` must still resolve to that root. Each projected caller result keeps shared worktree diff text and terminal control guidance while its child results and standard child-output sections remain route-local.
550
+
551
+ One parallel run also needs one value for each run-wide option. Sibling calls must agree on `concurrency`, `worktree`, `context`, `share`, `control`, `sessionDir`, `maxOutput`, `artifacts`, `includeProgress`, and `agentScope`. If any value differs, Atomic rejects the full burst before launch and names the field instead of mixing settings.
552
+
540
553
  ### Management actions
541
554
 
542
555
  Agent definitions are not loaded into context by default. Management actions let the LLM discover, inspect, create, update, and delete agents at runtime.
@@ -580,7 +593,7 @@ Agent definitions are not loaded into context by default. Management actions let
580
593
  |-------|------|---------|-------------|
581
594
  | `agent` | string | - | Agent name for single mode, or target for management actions. |
582
595
  | `task` | string | - | Task string for single mode. |
583
- | `action` | string | - | `list`, `get`, `create`, `update`, `delete`, `status`, `interrupt`, `resume`, or `doctor`. |
596
+ | `action` | string | - | `list`, `get`, `create`, `update`, `delete`, `status`, `interrupt`, or `doctor`. |
584
597
  | `config` | object/string | - | Agent config for create/update. |
585
598
  | `output` | `string \| false` | agent default | Override single-agent output file. |
586
599
  | `outputMode` | `"inline" \| "file-only"` | `inline` | Return saved output inline or as a concise saved-file reference. `file-only` requires an `output` path. |
@@ -611,12 +624,10 @@ Status and control actions:
611
624
  subagent({ action: "status" })
612
625
  subagent({ action: "status", id: "<run-id>" })
613
626
  subagent({ action: "interrupt", id: "<run-id>" })
614
- subagent({ action: "resume", id: "<run-id>", message: "follow-up question" })
615
- subagent({ action: "resume", id: "<run-id>", index: 1, message: "follow-up for child 2" })
616
627
  subagent({ action: "doctor" })
617
628
  ```
618
629
 
619
- `resume` sends the follow-up directly when a child is still reachable. After completion or eviction, it cold-reloads the same canonical child identity from the stored session file. Remembered foreground single or parallel runs can be revived by passing `index` to choose the child; no new OS process is created.
630
+ Completed, interrupted, and parent-question children are terminal for continuation. A prior run ID cannot revive a child or parallel sibling set. Start a fresh subagent call with an explicit context handoff for follow-up work.
620
631
 
621
632
  ## Worktree isolation
622
633
 
@@ -640,7 +651,7 @@ Requirements:
640
651
  - the main repository's Husky or populated `.git/hooks` directory is shared through `core.hooksPath`
641
652
  - gitignored files matched by `.worktreeinclude` are copied into the worktree
642
653
 
643
- After a worktree parallel step completes, per-agent diff stats are appended to the output and full patch files are written to artifacts. Cleanup forcibly removes each worktree, waits briefly for Git's lock release, and deletes its `worktree-*` branch; the same cleanup runs after post-creation setup failures.
654
+ After a worktree parallel step reaches any terminal result, per-agent diff stats are appended to the output and full patch files are written to artifacts. A parent-directed ask terminally ends the active set, captures its staged and unstaged changes in the same handoff result, and then cleans up every worktree and `worktree-*` branch after a brief Git lock-release wait. The same cleanup runs after post-creation setup failures.
644
655
 
645
656
  ## Configuration
646
657
 
@@ -772,7 +783,7 @@ This is disabled by default. Session data may contain source code, paths, enviro
772
783
 
773
784
  ## Delegation boundary
774
785
 
775
- Delegation is exactly one level deep, and nothing configures it. A top-level session — main chat or a workflow stage — may call `subagent`. A session that was itself admitted as a subagent child may not: every launch, `resume`, and `interrupt` it attempts is refused with guidance to complete its assigned task directly. The observing actions `list`, `get`, `status`, and `doctor` stay available to a child.
786
+ Delegation is exactly one level deep, and nothing configures it. A top-level session — main chat or a workflow stage — may call `subagent`. A session that was itself admitted as a subagent child may not: every launch and `interrupt` it attempts is refused with guidance to complete its assigned task directly. The observing actions `list`, `get`, `status`, and `doctor` stay available to a child.
776
787
 
777
788
  There is no configuration option, agent frontmatter field, or tool parameter for the delegation level. The rule is enforced twice: the subagent executor refuses a child before any run starts, and the Rust `SubagentControl` admission door refuses a child deeper than the single permitted level. Admitted depth is typed admission state and is not inherited through an environment variable.
778
789
 
@@ -20,9 +20,9 @@ Treat an approved handoff or execution plan as the contract. Inspect inherited c
20
20
 
21
21
  ## Decision and escalation contract
22
22
 
23
- Do not silently make a new product, architecture, or scope decision. When implementation reveals an unapproved decision required to continue safely, pause and use the live coordination route supplied at runtime. Use `contact_supervisor` with `reason: "need_decision"` and stay alive for the reply. Use `reason: "progress_update"` only for a concise, non-blocking update when helpful or explicitly requested. Fall back to `intercom` only when `contact_supervisor` is unavailable.
23
+ Do not silently make a new product, architecture, or scope decision. When implementation reveals an unapproved decision required to continue safely, use the live coordination route supplied at runtime. Use `contact_supervisor` with `reason: "need_decision"`; a claimed request ends this child and gives the supervisor a fresh-subagent handoff, so do not wait for a reply in this run. Use `reason: "progress_update"` only for a concise, non-blocking update when helpful or explicitly requested. Fall back to `intercom` only when `contact_supervisor` is unavailable.
24
24
 
25
- Do not end with a question requiring the supervisor to choose before work can continue. Do not send routine completion handoffs; return the normal task result when coordination is unnecessary. If you sent a blocked or progress update through `contact_supervisor`, keep it short and still provide the full structured result.
25
+ Do not end with a question requiring the supervisor to choose before work can continue. Do not send routine completion handoffs; return the normal task result when coordination is unnecessary. If you sent a progress update through `contact_supervisor`, keep it short and still provide the full structured result.
26
26
 
27
27
  ## Work and validation
28
28
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/subagents",
3
- "version": "0.9.15",
3
+ "version": "0.9.16-alpha.1",
4
4
  "private": true,
5
5
  "description": "Atomic extension for delegating tasks to subagents with parallel execution. Fork of: https://github.com/nicobailon/pi-subagents",
6
6
  "contributors": [
@@ -251,24 +251,17 @@ Avoid duplicate output paths in parallel tasks. Concurrent children should not w
251
251
  Concurrent writers conflict. `code-simplifier` and `debugger` change files. Do not run two writers in parallel against the same worktree unless you isolate them with `worktree: true`.
252
252
 
253
253
 
254
- ### Foreground execution and resume
254
+ ### Foreground execution and fresh follow-ups
255
255
 
256
256
  All subagent execution runs in the foreground and returns its result to the parent call. Parallel tasks may still run concurrently within one foreground invocation, and forked context still creates branched child sessions.
257
257
 
258
- Use `resume` for a follow-up on a retained child:
258
+ Completed, interrupted, and parent-question children are terminal for continuation. Do not address a prior child or sibling set by run ID. Start follow-up work with the normal launch form and an explicit handoff:
259
259
 
260
260
  ```typescript
261
- subagent({ action: "resume", id: "run-id", message: "Follow up on this point." })
262
- subagent({ action: "resume", id: "run-id", index: 1, message: "Continue reviewer 2." })
261
+ subagent({ agent: "worker", task: "[TASK_CONTEXT] Continue with this supervisor answer: ..." })
263
262
  ```
264
263
 
265
- Resume behavior:
266
-
267
- - If a child is still running and reachable, `resume` sends the follow-up through its intercom route when available.
268
- - Completed foreground single and parallel runs can be revived by `index` while their run metadata remains in extension state.
269
- - A revived child starts a new in-process attempt from its persisted session file.
270
- - Multi-child runs require `index` unless only one child is selectable.
271
- - If the chosen child has no persisted `.jsonl` session file, resume fails and reports that directly.
264
+ A parent-ask handoff supplies the original question, ordered attachments, previous agent identity, and dynamic task context. The fresh launch receives a new run identity.
272
265
 
273
266
  Use diagnostics when setup or child startup looks wrong:
274
267
 
@@ -280,7 +273,7 @@ Humans can use `/subagents-doctor` for the same read-only report. It checks runt
280
273
 
281
274
  ### Subagent control
282
275
 
283
- Subagent control is the runtime visibility and intervention layer for delegated runs. It is separate from lifecycle status. Lifecycle status says whether a child is `queued`, `running`, `paused`, `complete`, or `failed`. Activity reporting is factual: it tracks the last observed activity time and the current tool when known. It does not pretend to know that a child is truly stuck.
276
+ Subagent control is the runtime visibility and intervention layer for delegated runs. Lifecycle status distinguishes queued and running children from terminal completed, failed, or interrupted results. Activity reporting is factual: it tracks the last observed activity time and the current tool when known. It does not pretend to know that a child is truly stuck.
284
277
 
285
278
  Default behavior is intentionally conservative. When no activity has been observed past the configured threshold, the run emits a `needs_attention` control event. Foreground runs push this as a `subagent:control-event` event, and notification-worthy control events are inserted into the visible transcript so both the user and the parent agent can see them, with a proactive hint plus concrete `nudge`, `status`, and `interrupt` options. Visible notifications fire once per child run and attention state.
286
279
 
@@ -296,7 +289,7 @@ Pass `id` when targeting a specific controllable run:
296
289
  subagent({ action: "interrupt", id: "abc123" })
297
290
  ```
298
291
 
299
- A soft interrupt cancels the current child turn and leaves the run paused. It does not mean the delegated task succeeded or failed. After an interrupt, decide the next explicit action: resume with clearer instructions, replace the task, ask the user, or stop the workflow.
292
+ A soft interrupt cancels the current child turn and terminally records the child as interrupted. It does not mean the delegated task succeeded. Decide the next explicit action: launch a fresh child with the relevant task context, replace the task, ask the user, or stop the workflow.
300
293
 
301
294
  Per-run control thresholds can be overridden when a task legitimately runs without observable output for longer than usual:
302
295
 
@@ -339,7 +332,7 @@ subagent({
339
332
 
340
333
  Atomic subagents work without intercom. When Atomic's bundled intercom companion or upstream `pi-intercom` is installed and enabled, the bridge can give eligible child agents a private coordination tool back to the parent session without connecting either session automatically. If a child may need live coordination, invoke `intercom({ action: "status" })` in the parent before launching it; the child connects when it first invokes `contact_supervisor` or `intercom`.
341
334
 
342
- The builtin `debugger` and `worker` agents declare `intercom` and `contact_supervisor`. With an active bridge route, they can send progress or pause to ask the parent for a decision. Other builtin specialists finish their pass and return without live coordination; use a custom agent with bridge tools when another role needs that ability.
335
+ The builtin `debugger` and `worker` agents declare `intercom` and `contact_supervisor`. With an active bridge route, they can send progress or terminally hand a parent-directed question back to the supervisor. Other builtin specialists finish their pass and return without live coordination; use a custom agent with bridge tools when another role needs that ability.
343
336
 
344
337
  Custom agents that do have the bridge tool can ask the parent for a decision:
345
338
 
@@ -461,8 +454,8 @@ If a prompt-template extension is installed, additional user prompt templates ca
461
454
 
462
455
  - **Forking requires a persisted parent session.** If the current session does not have a persisted session file, forked runs fail.
463
456
  - **Forked runs inherit parent history.** They are branched threads, not fresh filtered contexts. Use fresh context for adversarial review unless the user explicitly asks for forked context.
464
- - **Delegation is one level deep and not configurable.** A subagent cannot call `subagent`: every launch, `resume`, and `interrupt` from inside a child is refused. Only `list`, `get`, `status`, and `doctor` stay available to a child.
465
- - **Attention signals are not lifecycle state.** `needs_attention` means no activity has been observed past the configured threshold. `paused` means the child turn was intentionally interrupted or is awaiting direction; it is not the same as `failed`.
457
+ - **Delegation is one level deep and not configurable.** A subagent cannot call `subagent`: every launch and `interrupt` from inside a child is refused. Only `list`, `get`, `status`, and `doctor` stay available to a child.
458
+ - **Attention signals are not lifecycle state.** `needs_attention` means no activity has been observed past the configured threshold. `interrupted` means the child turn ended before completion; it is terminal for continuation and is not the same as `failed`.
466
459
  - **Builtin coordination varies by agent.** `debugger` and `worker` declare `intercom` and `contact_supervisor`; the other builtin specialists do not. For agents without bridge tools, decide the task up front or use a custom agent when mid-run coordination is required.
467
460
  - **Intercom asks are blocking.** A session can only maintain one pending outbound ask wait state at a time.
468
461
  - **Keep conversational authority clear.** Advisory specialists should not silently become second decision-makers.
@@ -550,7 +543,7 @@ For complex or risky changes, increase review and validation fanout when user in
550
543
 
551
544
  For very large work, split into serial milestones instead of launching a swarm of writers. Each milestone gets one writer, a validation contract, fresh-context review, a fix pass, and parent approval before the next milestone starts. Use parallel subagents inside a milestone for read-only context, research, and review only.
552
545
 
553
- Keep orchestration authority in the parent session. Child subagents cannot launch more subagents or run their own orchestration loops: delegation is one level deep and nothing configures it. This skill is parent-only and is stripped from every child prompt. A child may still have the `subagent` extension tool registered, because bundled extensions load through normal discovery; registration is not authority. Typed admission policy lets a child use only `list`, `get`, `status`, and `doctor`, and refuses delegation, `resume`, and `interrupt`. Spawned children also do not receive parent-only status/control/slash messages or prior parent `subagent` tool-call/tool-result artifacts, and child context filtering strips old hidden orchestration-instruction messages when they appear in inherited history. Every child also receives a boundary instruction that says the parent owns orchestration, that the `subagent` tool refuses every launch, `resume`, and `interrupt` from inside a subagent, and that writer children must call real edit/write tools instead of printing pseudo tool calls. Pass children concrete role-specific work instead.
546
+ Keep orchestration authority in the parent session. Child subagents cannot launch more subagents or run their own orchestration loops: delegation is one level deep and nothing configures it. This skill is parent-only and is stripped from every child prompt. A child may still have the `subagent` extension tool registered, because bundled extensions load through normal discovery; registration is not authority. Typed admission policy lets a child use only `list`, `get`, `status`, and `doctor`, and refuses delegation and `interrupt`. Spawned children also do not receive parent-only status/control/slash messages or prior parent `subagent` tool-call/tool-result artifacts, and child context filtering strips old hidden orchestration-instruction messages when they appear in inherited history. Every child also receives a boundary instruction that says the parent owns orchestration, that the `subagent` tool refuses every launch and `interrupt` from inside a subagent, and that writer children must call real edit/write tools instead of printing pseudo tool calls. Pass children concrete role-specific work instead.
554
547
 
555
548
  1. Clarify only when needed. Use existing context first; gather missing code or research context selectively, then ask only unresolved questions that materially affect scope, completion criteria, constraints, or non-goals.
556
549
  2. Define the validation contract. State completion expectations before implementation: expected behavior, checks to run, user flows to exercise, and evidence required in the writer handoff. For UI, CLI, integration, or workflow changes, include at least one validator angle that uses the product the way a user would rather than only reading code.
@@ -38,7 +38,6 @@ import {
38
38
  } from "../slash/slash-live-state.js";
39
39
  import {
40
40
  advanceResultPulseFrame,
41
- renderLiveSubagentResult,
42
41
  renderSubagentResult,
43
42
  type SubagentResultRenderState,
44
43
  stopResultAnimations,
@@ -48,6 +47,7 @@ import { parseSubagentNotifyContent } from "./notification-content.js";
48
47
  import { DEFAULT_PROMPT_GUIDANCE } from "./prompt-guidance.js";
49
48
  import { SubagentParams } from "./schemas.js";
50
49
  import { SUBAGENT_TOOL_DESCRIPTION } from "./tool-description.js";
50
+ import { renderSubagentToolCall, renderSubagentToolResult } from "./tool-rendering.js";
51
51
 
52
52
  export {
53
53
  PROMPT_TEMPLATE_SUBAGENT_CANCEL_EVENT,
@@ -157,7 +157,7 @@ export function renderSubagentNotification(
157
157
  const icon =
158
158
  details.status === "completed"
159
159
  ? theme.fg("success", "✓")
160
- : details.status === "paused"
160
+ : details.status === "interrupted"
161
161
  ? theme.fg("warning", "■")
162
162
  : theme.fg("error", "✗");
163
163
  const parts: string[] = [];
@@ -218,7 +218,6 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
218
218
  baseCwd: "",
219
219
  currentSessionId: null,
220
220
  subagentInProgress: false,
221
- foregroundRuns: new Map(),
222
221
  foregroundControls: new Map(),
223
222
  lastForegroundControlId: null,
224
223
  pendingForegroundControlNotices: new Map(),
@@ -326,14 +325,6 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
326
325
  promptTemplateBridge.cancelAll();
327
326
  promptTemplateBridge.dispose();
328
327
  });
329
- function effectiveParallelTaskCount(tasks: Array<{ count?: unknown }> | undefined): number {
330
- if (!tasks || tasks.length === 0) return 0;
331
- return tasks.reduce((total, task) => {
332
- const count =
333
- typeof task.count === "number" && Number.isInteger(task.count) && task.count >= 1 ? task.count : 1;
334
- return total + count;
335
- }, 0);
336
- }
337
328
  const tool: ToolDefinition<typeof SubagentParams, Details, SubagentToolRenderState> = {
338
329
  name: "subagent",
339
330
  label: "Subagent",
@@ -344,27 +335,11 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
344
335
  const executionSignal = signal ?? ctx.signal ?? new AbortController().signal;
345
336
  return executeSubagentCollapsed(id, params as SubagentParamsLike, executionSignal, onUpdate, ctx);
346
337
  },
347
- renderCall(args, theme) {
348
- if (args.action) {
349
- const target = args.agent || "";
350
- return new Text(
351
- `${theme.fg("toolTitle", theme.bold("subagent "))}${args.action}${target ? ` ${theme.fg("accent", target)}` : ""}`,
352
- 0,
353
- 0,
354
- );
355
- }
356
- const isParallel = (args.tasks?.length ?? 0) > 0;
357
- const parallelCount = effectiveParallelTaskCount(args.tasks as Array<{ count?: unknown }> | undefined);
358
- return new Text(
359
- isParallel
360
- ? `${theme.fg("toolTitle", theme.bold("subagent "))}parallel (${parallelCount})`
361
- : `${theme.fg("toolTitle", theme.bold("subagent "))}${theme.fg("accent", args.agent || "?")}`,
362
- 0,
363
- 0,
364
- );
338
+ renderCall(args, theme, context) {
339
+ return renderSubagentToolCall(args as SubagentParamsLike, theme, context);
365
340
  },
366
341
  renderResult(result, options, theme, context) {
367
- return renderLiveSubagentResult(result, options, theme, context);
342
+ return renderSubagentToolResult(result, options, theme, context);
368
343
  },
369
344
  };
370
345
  pi.registerTool(tool);
@@ -1,6 +1,6 @@
1
1
  import type { SubagentNotifyDetails } from "../runs/foreground/notify.js";
2
2
 
3
- const HEADER_STATUSES = ["completed", "failed", "paused"] as const;
3
+ const HEADER_STATUSES = ["completed", "failed", "interrupted"] as const;
4
4
 
5
5
  function isHeaderLineTerminator(character: string): boolean {
6
6
  return character === "\r" || character === "\u2028" || character === "\u2029";
@@ -124,23 +124,11 @@ export const SubagentParams = Type.Object(
124
124
  description: "Management/control action. Omit for execution mode.",
125
125
  }),
126
126
  ),
127
- id: Type.Optional(
128
- Type.String({
129
- description: "Run id or prefix for action='status', action='interrupt', or action='resume'.",
130
- }),
131
- ),
127
+ id: Type.Optional(Type.String({ description: "Run id or prefix for action='status' or action='interrupt'." })),
132
128
  runId: Type.Optional(
133
129
  Type.String({
134
130
  description:
135
- "Target run ID for action='interrupt' or action='resume'. Defaults to the most recently active controllable run for interrupt. Prefer id for new calls.",
136
- }),
137
- ),
138
- index: Type.Optional(
139
- Type.Integer({ minimum: 0, description: "Zero-based child index for actions that target a specific child." }),
140
- ),
141
- message: Type.Optional(
142
- Type.String({
143
- description: "Follow-up message for action='resume'. Use index to choose a child from multi-child runs.",
131
+ "Target run ID for action='interrupt'. Defaults to the most recently active controllable run. Prefer id for new calls.",
144
132
  }),
145
133
  ),
146
134
  config: Type.Optional(
@@ -14,7 +14,6 @@ MANAGEMENT (use action field, omit agent/task/tasks):
14
14
  • { action: "delete", agent: "code-analysis.custom-agent" }
15
15
  CONTROL:
16
16
  • { action: "status", id: "..." } - inspect an in-process run by id or prefix
17
- • { action: "interrupt", id?: "..." } - soft-interrupt the current child turn and leave the run paused
18
- • { action: "resume", id: "...", message: "...", index?: 0 } - follow up with a live child or revive a retained foreground child
17
+ • { action: "interrupt", id?: "..." } - soft-interrupt the current child turn; the child is terminal for continuation, so follow-up work starts a fresh launch
19
18
  DIAGNOSTICS:
20
19
  • { action: "doctor" } - read-only report for runtime paths, discovery, sessions, and intercom`;
@@ -0,0 +1,53 @@
1
+ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
2
+ import { type Component, Container, Text } from "@earendil-works/pi-tui";
3
+ import { getBurstDisplay } from "../runs/foreground/subagent-executor-burst-display.js";
4
+ import type { SubagentParamsLike } from "../runs/foreground/subagent-executor-types.js";
5
+ import type { Details } from "../shared/types.js";
6
+ import { renderLiveSubagentResult } from "../tui/render.js";
7
+
8
+ type Theme = Parameters<typeof renderLiveSubagentResult>[2];
9
+ type RenderContext = Parameters<typeof renderLiveSubagentResult>[3] & { toolCallId: string };
10
+
11
+ function effectiveParallelTaskCount(tasks: Array<{ count?: unknown }> | undefined): number {
12
+ if (!tasks || tasks.length === 0) return 0;
13
+ return tasks.reduce((total, task) => {
14
+ const count = typeof task.count === "number" && Number.isInteger(task.count) && task.count >= 1 ? task.count : 1;
15
+ return total + count;
16
+ }, 0);
17
+ }
18
+
19
+ export function renderSubagentToolCall(args: SubagentParamsLike, theme: Theme, context: RenderContext): Component {
20
+ const burst = getBurstDisplay(context.toolCallId);
21
+ if (burst && !burst.owner) return new Container();
22
+ if (args.action) {
23
+ const target = args.agent || "";
24
+ return new Text(
25
+ `${theme.fg("toolTitle", theme.bold("subagent "))}${args.action}${target ? ` ${theme.fg("accent", target)}` : ""}`,
26
+ 0,
27
+ 0,
28
+ );
29
+ }
30
+ if (burst) {
31
+ return new Text(`${theme.fg("toolTitle", theme.bold("subagent "))}parallel (${burst.taskCount})`, 0, 0);
32
+ }
33
+ const isParallel = (args.tasks?.length ?? 0) > 0;
34
+ const parallelCount = effectiveParallelTaskCount(args.tasks as Array<{ count?: unknown }> | undefined);
35
+ return new Text(
36
+ isParallel
37
+ ? `${theme.fg("toolTitle", theme.bold("subagent "))}parallel (${parallelCount})`
38
+ : `${theme.fg("toolTitle", theme.bold("subagent "))}${theme.fg("accent", args.agent || "?")}`,
39
+ 0,
40
+ 0,
41
+ );
42
+ }
43
+
44
+ export function renderSubagentToolResult(
45
+ result: AgentToolResult<Details>,
46
+ options: { expanded: boolean; isPartial: boolean },
47
+ theme: Theme,
48
+ context: RenderContext,
49
+ ): Component {
50
+ const burst = getBurstDisplay(context.toolCallId);
51
+ if (burst && !burst.owner) return new Container();
52
+ return renderLiveSubagentResult(burst?.result ?? result, options, theme, context);
53
+ }
@@ -37,7 +37,7 @@ const DEFAULT_INTERCOM_BRIDGE_TEMPLATE = `The inherited thread is reference-only
37
37
 
38
38
  Use contact_supervisor first. It resolves the supervisor session "{orchestratorTarget}" and run metadata automatically.
39
39
  - Need a decision, blocked, approval, or product/API/scope ambiguity: contact_supervisor({ reason: "need_decision", message: "<question>" })
40
- - 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.
40
+ - A claimed decision or interview ends this child. Do not wait for or expect a reply in this run; the supervisor receives a dynamic [TASK_CONTEXT] handoff and may launch a fresh subagent with a new run identity and the answer.
41
41
  - 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.
42
42
  - Meaningful progress or unexpected discoveries that change the plan: contact_supervisor({ reason: "progress_update", message: "UPDATE: <summary>" })
43
43
  - Generic intercom is lower-level plumbing/fallback only: intercom({ action: "ask", to: "{orchestratorTarget}", message: "<question>" })
@@ -21,7 +21,7 @@ export function resolveSubagentResultStatus(input: {
21
21
  detached?: boolean;
22
22
  }): SubagentResultStatus {
23
23
  if (input.detached || input.status === "continued") return "detached";
24
- if (input.interrupted || input.status === "interrupted" || input.state === "paused") return "paused";
24
+ if (input.interrupted || input.status === "interrupted" || input.state === "interrupted") return "interrupted";
25
25
  if (input.status === "ok" || input.success === true || input.state === "complete") return "completed";
26
26
  if (input.status === "error" || input.status === "skipped" || input.success === false || input.state === "failed")
27
27
  return "failed";
@@ -32,7 +32,7 @@ function countStatuses(children: SubagentResultIntercomChild[]): Record<Subagent
32
32
  const counts: Record<SubagentResultStatus, number> = {
33
33
  completed: 0,
34
34
  failed: 0,
35
- paused: 0,
35
+ interrupted: 0,
36
36
  detached: 0,
37
37
  };
38
38
  for (const child of children) {
@@ -45,7 +45,7 @@ function formatStatusCounts(counts: Record<SubagentResultStatus, number>): strin
45
45
  const parts = [
46
46
  counts.completed ? `${counts.completed} completed` : undefined,
47
47
  counts.failed ? `${counts.failed} failed` : undefined,
48
- counts.paused ? `${counts.paused} paused` : undefined,
48
+ counts.interrupted ? `${counts.interrupted} interrupted` : undefined,
49
49
  counts.detached ? `${counts.detached} detached` : undefined,
50
50
  ].filter((part): part is string => Boolean(part));
51
51
  return parts.length ? parts.join(", ") : "0 results";
@@ -54,7 +54,7 @@ function formatStatusCounts(counts: Record<SubagentResultStatus, number>): strin
54
54
  function resolveGroupedStatus(children: SubagentResultIntercomChild[]): SubagentResultStatus {
55
55
  const counts = countStatuses(children);
56
56
  if (counts.failed > 0) return "failed";
57
- if (counts.paused > 0) return "paused";
57
+ if (counts.interrupted > 0) return "interrupted";
58
58
  if (counts.completed > 0) return "completed";
59
59
  if (counts.detached > 0) return "detached";
60
60
  return "failed";
@@ -0,0 +1,78 @@
1
+ import {
2
+ PARENT_ASK_HANDOFF_REQUEST_EVENT,
3
+ type ParentAskHandoffRequest,
4
+ type RunSyncOptions,
5
+ } from "../../shared/types.js";
6
+
7
+ interface ExecutionParentAskState {
8
+ readonly agent: string;
9
+ readonly isUnavailable: () => boolean;
10
+ }
11
+
12
+ const PROCESS_PARENT_ASK_CLAIMS = Symbol.for("atomic/subagents/parent-ask-claims@1");
13
+
14
+ type ProcessParentAskClaimHandler = (payload: unknown) => void;
15
+
16
+ interface ProcessParentAskClaimRegistry {
17
+ handlers: Set<ProcessParentAskClaimHandler>;
18
+ }
19
+
20
+ function processClaimSlots(): typeof globalThis & Record<symbol, ProcessParentAskClaimRegistry | undefined> {
21
+ return globalThis as typeof globalThis & Record<symbol, ProcessParentAskClaimRegistry | undefined>;
22
+ }
23
+
24
+ function registerProcessClaimHandler(handler: ProcessParentAskClaimHandler): () => void {
25
+ const slots = processClaimSlots();
26
+ const registry = slots[PROCESS_PARENT_ASK_CLAIMS] ?? { handlers: new Set<ProcessParentAskClaimHandler>() };
27
+ slots[PROCESS_PARENT_ASK_CLAIMS] = registry;
28
+ registry.handlers.add(handler);
29
+ return () => {
30
+ registry.handlers.delete(handler);
31
+ if (registry.handlers.size === 0 && slots[PROCESS_PARENT_ASK_CLAIMS] === registry) {
32
+ delete slots[PROCESS_PARENT_ASK_CLAIMS];
33
+ }
34
+ };
35
+ }
36
+
37
+ function isParentAskHandoffRequest(payload: unknown): payload is ParentAskHandoffRequest {
38
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return false;
39
+ const request = payload as Partial<ParentAskHandoffRequest>;
40
+ return (
41
+ typeof request.runId === "string" &&
42
+ typeof request.index === "number" &&
43
+ typeof request.agent === "string" &&
44
+ typeof request.childIntercomTarget === "string" &&
45
+ typeof request.orchestratorTarget === "string" &&
46
+ (request.kind === "decision" || request.kind === "interview" || request.kind === "intercom") &&
47
+ typeof request.question === "string" &&
48
+ typeof request.claimed === "boolean"
49
+ );
50
+ }
51
+
52
+ /** Claim a blocking ask only for the exact live child attempt that issued it. */
53
+ export function registerExecutionParentAskHandoff(options: RunSyncOptions, state: ExecutionParentAskState): () => void {
54
+ if (!options.onParentAskHandoff) return () => {};
55
+ const handleRequest = (payload: unknown): void => {
56
+ if (state.isUnavailable() || !isParentAskHandoffRequest(payload)) return;
57
+ if (
58
+ payload.claimed ||
59
+ payload.runId !== options.runId ||
60
+ payload.index !== (options.index ?? 0) ||
61
+ payload.agent !== state.agent ||
62
+ payload.childIntercomTarget !== options.intercomSessionName ||
63
+ payload.orchestratorTarget !== options.orchestratorIntercomTarget
64
+ )
65
+ return;
66
+ payload.claimed = true;
67
+ options.onParentAskHandoff?.(payload);
68
+ };
69
+ const eventCleanup = options.intercomEvents?.on(PARENT_ASK_HANDOFF_REQUEST_EVENT, handleRequest) ?? (() => {});
70
+ const processCleanup = registerProcessClaimHandler(handleRequest);
71
+ let cleaned = false;
72
+ return () => {
73
+ if (cleaned) return;
74
+ cleaned = true;
75
+ eventCleanup();
76
+ processCleanup();
77
+ };
78
+ }
@@ -23,6 +23,7 @@ import type { AttemptOutcome, ChildSpec, ParentContext } from "../inprocess/runn
23
23
  import { filterSpawnableModelCandidates } from "../shared/model-candidate-filter.js";
24
24
  import { buildModelCandidates } from "../shared/model-fallback.js";
25
25
  import { registerExecutionIntercomDetach } from "./execution-intercom-detach.js";
26
+ import { registerExecutionParentAskHandoff } from "./execution-parent-ask-handoff.js";
26
27
 
27
28
  function emptyUsage(): Usage {
28
29
  return {
@@ -349,11 +350,6 @@ export async function runSingleInProcess(
349
350
  lastActivityAt: Date.now(),
350
351
  });
351
352
  }
352
- control.registerAttempt(options.runId, running, {
353
- model: resolvedCandidate?.model,
354
- modelId: candidate,
355
- thinkingLevel: spec.thinkingLevel,
356
- });
357
353
  let detached = false;
358
354
  let resolveContinuation!: () => void;
359
355
  const continuation = new Promise<void>((resolve) => {
@@ -369,10 +365,15 @@ export async function runSingleInProcess(
369
365
  resolveContinuation();
370
366
  },
371
367
  });
368
+ const parentAskCleanup = registerExecutionParentAskHandoff(options, {
369
+ agent: agent.name,
370
+ isUnavailable: () => running.status !== "running" || detached,
371
+ });
372
372
  const terminal = running.promise.then((value) => ({ kind: "terminal" as const, value }));
373
373
  const winner = await Promise.race([terminal, continuation.then(() => ({ kind: "continued" as const }))]);
374
374
  if (winner.kind === "continued") {
375
375
  detachCleanup();
376
+ parentAskCleanup();
376
377
  void running.promise.then(async (continuedOutcome) => {
377
378
  const recovered = resultFromOutcome(agent, task, continuedOutcome, startedAt, artifactPaths, {
378
379
  cwd,
@@ -441,6 +442,7 @@ export async function runSingleInProcess(
441
442
  return continuedResult;
442
443
  }
443
444
  detachCleanup();
445
+ parentAskCleanup();
444
446
  const outcome = winner.value;
445
447
  const result = resultFromOutcome(agent, task, outcome, startedAt, artifactPaths, {
446
448
  cwd,
@@ -21,7 +21,7 @@ interface SubagentStepResult {
21
21
 
22
22
  export interface SubagentNotifyDetails {
23
23
  agent: string;
24
- status: "completed" | "failed" | "paused";
24
+ status: "completed" | "failed" | "interrupted";
25
25
  taskInfo?: string;
26
26
  resultPreview: string;
27
27
  durationMs?: number;
@@ -211,14 +211,13 @@ export default function registerSubagentNotify(pi: ExtensionAPI): () => void {
211
211
  inFlight.set(key, ownership.promise);
212
212
  const agent = result.agent ?? "unknown";
213
213
  const summary = typeof result.summary === "string" ? result.summary : "";
214
- const paused =
215
- result.status === "interrupted" || result.state === "paused" || summary.startsWith("Paused after interrupt.");
216
- const status = paused
217
- ? "paused"
214
+ const interrupted = result.status === "interrupted" || result.state === "interrupted";
215
+ const status = interrupted
216
+ ? "interrupted"
218
217
  : result.status === "ok"
219
218
  ? "completed"
220
219
  : result.status === "continued"
221
- ? "paused"
220
+ ? "interrupted"
222
221
  : "failed";
223
222
 
224
223
  const taskInfo =