shariq-pi-extensions 0.2.5 → 0.2.7

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.
@@ -38,7 +38,7 @@ The goal extension adds persistent, branch-safe objectives, progress evidence, b
38
38
 
39
39
  The subagent extension runs flat Pi child agents with profiles, capability policies, continuation, result delivery, optional worktrees, and a dashboard. Configuration lives in `<agent-dir>/subagents.json`; trusted projects may override it through their Pi config directory. The configured concurrency ceiling is 50.
40
40
 
41
- The extension supplies tools including `spawn_agent`, `task`, `check_agent`, `list_agents`, `wait_agent`, `send_message`, `close_agent`, `reply_question`, and `apply_agent_changes`. Child settlement automatically sends a follow-up that starts the next parent turn; status tools are for explicit inspection, not waiting.
41
+ The extension supplies tools including `spawn_agent`, `task`, `check_agent`, `list_agents`, `wait_agent`, `send_message`, `close_agent`, `reply_question`, and `apply_agent_changes`. Child settlement is handed to Pi immediately as an extension-originated user follow-up, so Pi queues it while the parent is active or starts a new parent turn when idle with the summary guaranteed in model context; status tools are for explicit inspection, not waiting.
42
42
 
43
43
  ### [Orchestration](../extensions/orchestration/README.md)
44
44
 
@@ -50,7 +50,7 @@ The model-facing `create_orchestration` tool starts planning only after an expli
50
50
 
51
51
  Managed PTYs support servers, watchers, long builds, downloads, and interactive processes. The extension tracks up to eight concurrent terminals, retains bounded output, stores full logs in restrictive temporary directories, and stops process groups during shutdown or reload.
52
52
 
53
- Its tools are `start_terminal`, `read_terminal`, `write_terminal`, `list_terminals`, and `stop_terminal`. A model-started terminal automatically sends a completion or failure follow-up and starts the next parent turn. Reading a terminal no longer suppresses that delivery; agents should inspect only for explicit progress requests or immediate interaction.
53
+ Its tools are `start_terminal`, `read_terminal`, `write_terminal`, `list_terminals`, and `stop_terminal`. A model-started terminal immediately hands its completion or failure to Pi as an extension-originated user follow-up, which Pi queues while the parent is active or uses to start a new parent turn when idle with the bounded output guaranteed in model context. Reading a terminal does not suppress that delivery; agents should inspect only for explicit progress requests or immediate interaction.
54
54
 
55
55
  ## Web access
56
56
 
@@ -10,7 +10,7 @@ Session-scoped background pseudo-terminals for Pi. The extension combines Codex-
10
10
  - `list_terminals` — list running and settled terminals.
11
11
  - `stop_terminal` — stop complete process groups with TERM-to-KILL escalation.
12
12
 
13
- Each output response carries a byte cursor. Pass it to the next read/write operation to avoid repeating output. Long or uncertain commands should use `start_terminal` instead of a large blocking `bash` timeout. Completion wakes the parent automatically, so it can continue other work or end its turn rather than poll.
13
+ Each output response carries a byte cursor. Pass it to the next read/write operation to avoid repeating output. Long or uncertain commands should use `start_terminal` instead of a large blocking `bash` timeout. Settlement is handed to Pi immediately as an extension-originated user follow-up: it queues while the parent is active or starts a new parent turn when idle with bounded output guaranteed in model context, so the parent can continue other work or end its turn rather than poll.
14
14
 
15
15
  ## User interface
16
16
 
@@ -34,7 +34,7 @@ Each output response carries a byte cursor. Pass it to the next read/write opera
34
34
  - Output is sanitized before TUI or model rendering.
35
35
  - Processes run in their own PTY process group and are stopped on session shutdown, replacement, or reload.
36
36
  - Shutdown and stop operations are bounded and escalate from SIGTERM to SIGKILL.
37
- - Model-started terminals automatically deliver one completion/failure follow-up that starts the next parent turn.
37
+ - Model-started terminals immediately hand one model-visible completion/failure follow-up to Pi; Pi queues it while the parent is active or starts the next parent turn when idle.
38
38
  - Reading settled output does not consume or suppress the automatic completion delivery.
39
39
  - Completion delivery is keyed by terminal id to prevent duplicate follow-ups.
40
40
 
@@ -3,12 +3,12 @@ import * as path from "node:path";
3
3
  import type {
4
4
  ExtensionAPI,
5
5
  ExtensionCommandContext,
6
- ExtensionContext,
7
6
  ExtensionUIContext,
8
7
  } from "@earendil-works/pi-coding-agent";
9
8
  import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
10
9
  import { Markdown, Text } from "@earendil-works/pi-tui";
11
10
  import { Type } from "typebox";
11
+ import { deliverSettlement } from "../shared/settlement-delivery.ts";
12
12
  import { oneLine, sanitizeTerminalText, stateLabel } from "../shared/tui-dashboard.ts";
13
13
  import { TerminalManager, MAX_RUNNING_TERMINALS } from "./src/manager.ts";
14
14
  import {
@@ -37,12 +37,12 @@ function resolveCwd(base: string, requested?: string): string {
37
37
 
38
38
  export default function backgroundTerminals(pi: ExtensionAPI) {
39
39
  let manager: TerminalManager | undefined;
40
- let sessionContext: ExtensionContext | undefined;
41
40
  let ui: ExtensionUIContext | undefined;
42
41
  let unsubscribe: (() => void) | undefined;
43
42
  let lastStatus = "";
44
43
  const pendingResults = new Map<string, TerminalSnapshot>();
45
44
  const modelOwned = new Set<string>();
45
+ const starting = new Set<string>();
46
46
 
47
47
  const getManager = (): TerminalManager => {
48
48
  if (manager) return manager;
@@ -56,7 +56,7 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
56
56
  return;
57
57
  }
58
58
  pendingResults.set(snapshot.id, snapshot);
59
- if (sessionContext?.isIdle()) flushResults();
59
+ if (!starting.has(snapshot.id)) flushResult(snapshot.id);
60
60
  });
61
61
  unsubscribe = manager.view.subscribe(updateStatus);
62
62
  updateStatus();
@@ -85,40 +85,31 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
85
85
  }
86
86
  }
87
87
 
88
- function deliver(snapshot: TerminalSnapshot): void {
89
- pi.sendMessage(
90
- {
91
- customType: "background-terminal-result",
92
- content: formatCompletion(snapshot),
93
- display: true,
94
- details: {
95
- id: snapshot.id,
96
- title: snapshot.title,
97
- status: snapshot.status,
98
- exitCode: snapshot.exitCode,
99
- },
88
+ function flushResult(id: string): void {
89
+ const snapshot = pendingResults.get(id);
90
+ if (!snapshot) return;
91
+ pendingResults.delete(id);
92
+ deliverSettlement(pi, {
93
+ customType: "background-terminal-result",
94
+ content: formatCompletion(snapshot),
95
+ display: true,
96
+ details: {
97
+ id: snapshot.id,
98
+ title: snapshot.title,
99
+ status: snapshot.status,
100
+ exitCode: snapshot.exitCode,
100
101
  },
101
- { deliverAs: "followUp", triggerTurn: true },
102
- );
103
- }
104
-
105
- function flushResults(): void {
106
- const results = [...pendingResults.values()];
107
- pendingResults.clear();
108
- for (const snapshot of results) deliver(snapshot);
102
+ });
109
103
  }
110
104
 
111
105
  pi.on("session_start", (_event, ctx) => {
112
- sessionContext = ctx;
113
106
  if (ctx.hasUI) ui = ctx.ui;
114
107
  });
115
108
 
116
- pi.on("agent_settled", flushResults);
117
-
118
109
  pi.on("session_shutdown", async () => {
119
- sessionContext = undefined;
120
110
  pendingResults.clear();
121
111
  modelOwned.clear();
112
+ starting.clear();
122
113
  unsubscribe?.();
123
114
  unsubscribe = undefined;
124
115
  ui?.setStatus(STATUS_KEY, undefined);
@@ -140,7 +131,7 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
140
131
  promptSnippet: "Start an interactive or long-running command in a managed background PTY.",
141
132
  promptGuidelines: [
142
133
  "Use start_terminal by default for servers, watchers, downloads, long or uncertain builds and tests, interactive shells, and any command that should not occupy the main turn; reserve bash for short commands whose result is needed immediately. Never use a large bash timeout merely to wait for long work.",
143
- "After start_terminal returns, continue only genuinely independent work. If none remains, end the turn immediately. The terminal completion automatically sends a follow-up and starts the next parent turn; do not call read_terminal, list_terminals, or start a timer merely to check whether it finished.",
134
+ "After start_terminal returns, continue only genuinely independent work. If none remains, end the turn immediately. Terminal settlement is handed to Pi immediately: it queues a follow-up while the parent is active or starts the next parent turn when idle. When that result invokes the parent, continue the original task immediately without waiting for the user or rereading the same terminal; do not call read_terminal, list_terminals, or start a timer merely to check whether it finished.",
144
135
  "Use stop_terminal when a managed process is no longer needed. Background terminals are session-scoped and are stopped during session shutdown or reload.",
145
136
  ],
146
137
  parameters: Type.Object({
@@ -158,6 +149,7 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
158
149
  cwd: resolveCwd(ctx.cwd, params.working_dir),
159
150
  });
160
151
  modelOwned.add(terminal.id);
152
+ starting.add(terminal.id);
161
153
  let result: TerminalReadResult;
162
154
  try {
163
155
  result = await getManager().read(terminal.id, {
@@ -166,12 +158,15 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
166
158
  signal,
167
159
  });
168
160
  } catch (error) {
169
- await getManager().kill([terminal.id]).catch(() => undefined);
161
+ starting.delete(terminal.id);
170
162
  modelOwned.delete(terminal.id);
163
+ await getManager().kill([terminal.id]).catch(() => undefined);
171
164
  consume(terminal.id);
172
165
  throw error;
173
166
  }
167
+ starting.delete(terminal.id);
174
168
  if (result.snapshot.status !== "running") consume(terminal.id);
169
+ else flushResult(terminal.id);
175
170
  let text = `Started background terminal ${terminal.id} "${oneLine(terminal.title)}" (pid ${terminal.pid}). Use cursor ${result.cursor} for incremental reads.`;
176
171
  if (result.text || result.snapshot.status !== "running") {
177
172
  text += `\n\n${formatReadResult(result)}`;
@@ -8,6 +8,7 @@ Internal runtime utilities used by more than one extension. This directory is no
8
8
  - `child-session.ts` owns trust-aware child resources and bounded session shutdown.
9
9
  - `context-utilization.ts` formats model-context usage and capacity.
10
10
  - `dashboard-state.ts` keeps list selection stable as live rows change.
11
+ - `settlement-delivery.ts` immediately hands asynchronous results to Pi as extension-originated user follow-ups, guaranteeing model-visible settlement context.
11
12
  - `tool-call-timeout.ts` applies cancellation-aware execution limits to registered tools.
12
13
  - `tui-dashboard.ts` provides bounded, sanitized terminal-dashboard rendering helpers.
13
14
 
@@ -0,0 +1,19 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+
3
+ type SettlementMessage = Parameters<ExtensionAPI["sendMessage"]>[0];
4
+
5
+ /**
6
+ * Hand settlement to Pi as an extension-originated user follow-up. A custom
7
+ * message can wake the parent yet fail to appear in the invoked model turn;
8
+ * sendUserMessage guarantees that the bounded result is model-visible. Pi
9
+ * starts a turn when idle and queues the same input while the parent is active.
10
+ */
11
+ export function deliverSettlement(pi: ExtensionAPI, message: SettlementMessage): void {
12
+ const content = typeof message.content === "string"
13
+ ? message.content
14
+ : message.content
15
+ .filter((part) => part.type === "text")
16
+ .map((part) => part.text)
17
+ .join("\n");
18
+ pi.sendUserMessage(content, { deliverAs: "followUp" });
19
+ }
@@ -19,7 +19,7 @@ The system is deliberately flat. Only the main Pi thread can spawn subagents. Ch
19
19
  - `reply_question` — answer a child’s blocking `ask_parent` request
20
20
  - `task` — atomically reserve capacity for up to the configured limit (maximum 50), start the fan-out in the background, and return child ids immediately
21
21
 
22
- Child sessions receive `message_parent`, `ask_parent`, `list_peers`, and `message_peer`. Peer messages are routed through the main-thread manager and can steer a running child or continue a settled one; they cannot create agents. Background completions wake the parent automatically, so the main turn can continue independent work or end and remain available to the user.
22
+ Child sessions receive `message_parent`, `ask_parent`, `list_peers`, and `message_peer`. Peer messages are routed through the main-thread manager and can steer a running child or continue a settled one; they cannot create agents. Child settlement is handed to Pi immediately as an extension-originated user follow-up: it queues while the parent is active or starts a new parent turn when idle with the summary guaranteed in model context, so the main turn can continue independent work or end and remain available to the user.
23
23
 
24
24
  ## Profiles and capabilities
25
25
 
@@ -86,4 +86,3 @@ Children share the requested workspace by default. Use `isolation: "worktree"` o
86
86
  - `src/worktree.ts` — isolated worktree creation, inspection, conflict preflight, integration, and cleanup
87
87
  - `src/runtime.ts` — managed runtime and Pi backend registry
88
88
  - `src/ui/` — dashboard, transcript, and takeover components
89
- - `src/result-delivery.ts` — deferred automatic result delivery
@@ -26,6 +26,7 @@ import {
26
26
  } from "@earendil-works/pi-coding-agent";
27
27
  import { Markdown, Text } from "@earendil-works/pi-tui";
28
28
  import { Type } from "typebox";
29
+ import { deliverSettlement } from "../shared/settlement-delivery.ts";
29
30
  import {
30
31
  formatElapsed,
31
32
  latestText,
@@ -56,7 +57,6 @@ import {
56
57
  SUBAGENT_WAIT_TOOL_DESCRIPTION,
57
58
  WORKTREE_ISOLATION_DESCRIPTION,
58
59
  } from "./src/prompt.ts";
59
- import { createDeferredResultDelivery } from "./src/result-delivery.ts";
60
60
  import {
61
61
  CAPABILITY_MODES,
62
62
  ISOLATION_MODES,
@@ -197,10 +197,8 @@ function resolveChildProjectTrust(options: {
197
197
  export default function (pi: ExtensionAPI) {
198
198
  let runtime: SubagentRuntime | undefined;
199
199
  let managerPromise: Promise<SubagentManagerShape> | undefined;
200
- let sessionContext: ExtensionContext | undefined;
201
200
  let ui: ExtensionUIContext | undefined;
202
201
  let unsubStatus: (() => void) | undefined;
203
- const resultDelivery = createDeferredResultDelivery<SubagentSnapshot>();
204
202
  const archived = new Map<string, ArchivedSubagent>();
205
203
  const peerHistory: PeerMessage[] = [];
206
204
  const pendingQuestions = new Map<
@@ -524,35 +522,24 @@ export default function (pi: ExtensionAPI) {
524
522
  };
525
523
 
526
524
  const deliverResult = (snap: SubagentSnapshot) => {
527
- pi.sendMessage(
528
- {
529
- customType: "subagent-result",
530
- content: buildSubagentResultMessage({
531
- id: snap.id,
532
- title: snap.title,
533
- status: snap.status,
534
- errorText: snap.errorText,
535
- output: truncatedOutput(snap),
536
- }),
537
- display: true,
538
- details: { id: snap.id, title: snap.title, status: snap.status },
539
- },
540
- { deliverAs: "followUp", triggerTurn: true },
541
- );
542
- };
543
-
544
- const flushResults = () => {
545
- for (const snap of resultDelivery.drain()) deliverResult(snap);
525
+ deliverSettlement(pi, {
526
+ customType: "subagent-result",
527
+ content: buildSubagentResultMessage({
528
+ id: snap.id,
529
+ title: snap.title,
530
+ status: snap.status,
531
+ errorText: snap.errorText,
532
+ output: truncatedOutput(snap),
533
+ }),
534
+ display: true,
535
+ details: { id: snap.id, title: snap.title, status: snap.status },
536
+ });
546
537
  };
547
538
 
548
539
  const onSettled = (snap: SubagentSnapshot, consumed: boolean) => {
549
540
  persistSnapshot(snap);
550
- if (snap.meta.origin === "orchestration") {
551
- resultDelivery.consume([snap.id]);
552
- return;
553
- }
541
+ if (snap.meta.origin === "orchestration") return;
554
542
  if (snap.meta.origin === "btw") {
555
- resultDelivery.consume([snap.id]);
556
543
  pi.appendEntry<BtwEntryData>(BTW_ENTRY_TYPE, {
557
544
  id: snap.id,
558
545
  title: snap.title,
@@ -562,16 +549,10 @@ export default function (pi: ExtensionAPI) {
562
549
  });
563
550
  return;
564
551
  }
565
- if (consumed) {
566
- resultDelivery.consume([snap.id]);
567
- return;
568
- }
569
- // Keep the result retractable while the parent is working. A later
570
- // wait_agent can consume it before agent_settled flushes follow-ups.
571
- // Defer a copy: the live snapshot keeps mutating if the subagent is
572
- // restarted before the deferred result flushes.
573
- resultDelivery.defer({ ...snap, meta: { ...snap.meta } });
574
- if (sessionContext?.isIdle()) flushResults();
552
+ if (consumed) return;
553
+ // Hand the immutable settlement to Pi immediately. Pi queues it when the
554
+ // parent is active and starts a new parent turn when idle.
555
+ deliverResult({ ...snap, meta: { ...snap.meta } });
575
556
  };
576
557
 
577
558
  const coordinator: SubagentCoordinator = {
@@ -641,7 +622,6 @@ export default function (pi: ExtensionAPI) {
641
622
  );
642
623
 
643
624
  pi.on("session_start", (_event, ctx) => {
644
- sessionContext = ctx;
645
625
  archived.clear();
646
626
  peerHistory.length = 0;
647
627
  for (const record of loadSubagentCatalog().values()) archived.set(record.id, record);
@@ -662,12 +642,8 @@ export default function (pi: ExtensionAPI) {
662
642
  if (ctx.hasUI) ui = ctx.ui;
663
643
  });
664
644
 
665
- pi.on("agent_settled", flushResults);
666
-
667
645
  pi.on("session_shutdown", async () => {
668
- sessionContext = undefined;
669
646
  unsubscribeCoordinator();
670
- resultDelivery.clear();
671
647
  for (const pending of pendingQuestions.values()) {
672
648
  pending.reject(new Error("Parent Pi session shut down before replying."));
673
649
  }
@@ -692,7 +668,7 @@ export default function (pi: ExtensionAPI) {
692
668
  promptSnippet: SUBAGENT_SPAWN_PROMPT_SNIPPET,
693
669
  promptGuidelines: [
694
670
  ...SUBAGENT_SPAWN_PROMPT_GUIDELINES,
695
- "After spawn_agent starts a child, continue only independent parent work or end the turn immediately. Do not call wait_agent, list_agents, or check_agent merely to watch it run; completion automatically starts the next parent turn.",
671
+ "After spawn_agent starts a child, continue only independent parent work or end the turn immediately. Do not call wait_agent, list_agents, or check_agent merely to watch it run. Settlement is handed to Pi immediately; when its attached summary invokes the parent, continue the original task without waiting for another user message.",
696
672
  ],
697
673
  parameters: Type.Object({
698
674
  message: Type.String({
@@ -832,7 +808,6 @@ export default function (pi: ExtensionAPI) {
832
808
  const snapshots = ids.map((id) => manager.view.get(id)!);
833
809
  const settled = snapshots.filter((snap) => snap.status !== "running");
834
810
  const pending = snapshots.filter((snap) => snap.status === "running");
835
- resultDelivery.consume(settled.map((snap) => snap.id));
836
811
 
837
812
  const sections: string[] = [];
838
813
  let remainingBytes = WAIT_OUTPUT_MAX_BYTES;
@@ -1121,7 +1096,7 @@ export default function (pi: ExtensionAPI) {
1121
1096
  label: "Start Pi Subagent Tasks",
1122
1097
  description: "Start independent subagent tasks together in the background and return their ids immediately. Completion notices automatically start the next parent turn, so the parent should end its current turn when no independent work remains instead of checking status.",
1123
1098
  promptGuidelines: [
1124
- "After task starts children, continue only independent parent work or end the turn immediately. Do not call wait_agent, list_agents, or check_agent merely to watch them run; completion automatically starts the next parent turn.",
1099
+ "After task starts children, continue only independent parent work or end the turn immediately. Do not call wait_agent, list_agents, or check_agent merely to watch them run. Settlements are handed to Pi immediately; when their attached summaries invoke the parent, continue the original task without waiting for another user message.",
1125
1100
  ],
1126
1101
  parameters: Type.Object({
1127
1102
  tasks: Type.Array(
@@ -19,6 +19,7 @@ export const SUBAGENT_SPAWN_PROMPT_GUIDELINES = [
19
19
  "Use resume_from to continue a completed child's existing context instead of restating its original task.",
20
20
  "After spawn_agent or task starts background work, continue useful parent work; otherwise end the turn so Pi remains available to the user.",
21
21
  "Do not poll background agents. Completion notices arrive automatically and wake the parent; wait_agent only collects results already available and reports running agents without blocking.",
22
+ "When a completion notice invokes the parent, use its attached summary and continue the original task immediately; do not wait for another user message or call a status tool for the same result.",
22
23
  ];
23
24
 
24
25
  export const SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS = {
@@ -51,7 +52,7 @@ export function buildSubagentSpawnResult(options: {
51
52
  ].filter(Boolean);
52
53
  return (
53
54
  `Started Pi subagent ${options.id} "${options.title}" (${attributes.join(", ")}).\n` +
54
- "It will report and wake the parent when finished. Continue useful work or end the turn instead of polling."
55
+ "It will report and wake the parent when finished. Continue useful work or end the turn instead of polling; when invoked by the result, continue the original task immediately."
55
56
  );
56
57
  }
57
58
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shariq-pi-extensions",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Cross-platform extension suite for the Pi coding agent.",
5
5
  "license": "MIT",
6
6
  "author": "Shariq Riaz",
@@ -24,7 +24,9 @@ Provide:
24
24
  - the working directory when it differs from the current directory;
25
25
  - an initial wait only when startup output is needed for the next decision.
26
26
 
27
- After startup, continue only genuinely useful independent work. If none remains, end the turn immediately. Terminal settlement automatically sends a follow-up and starts the next parent turn with the final status and bounded output. Do not keep the current turn alive to wait, invent monitoring work, or call terminal tools merely to see whether the process finished.
27
+ After startup, continue only genuinely useful independent work. If none remains, end the turn immediately. Ending the turn is the waiting mechanism: terminal settlement is handed to Pi immediately as an extension-originated user follow-up, queued if the parent is still active, and otherwise starts the next parent turn with the final status and bounded output visible in model context. Do not keep the current turn alive to wait, invent monitoring work, or call terminal tools merely to see whether the process finished.
28
+
29
+ When that completion follow-up invokes the next turn, treat its model-visible output as the terminal result and continue the original task immediately. Do not wait for another user message, announce that you are still waiting, or call `read_terminal` to retrieve the same result again. If `start_terminal` itself returns a settled result, the output is already synchronous and no second completion notice is needed.
28
30
 
29
31
  ## Inspect and interact only when necessary
30
32
 
@@ -24,14 +24,14 @@ This skill governs temporary Pi child agents. The `codex-thread-orchestrator` sk
24
24
 
25
25
  ## Wait by notification; inspect progress only when justified
26
26
 
27
- A successful `spawn_agent` or `task` call starts asynchronous work and returns control to the parent. When a child finishes, its completion notice automatically starts the next main-agent turn. The parent does not need to remain active or check once before ending its turn.
27
+ A successful `spawn_agent` or `task` call starts asynchronous work and returns control to the parent. When a child finishes, its settlement is handed to Pi immediately as an extension-originated user follow-up, queued if the parent is still active, and otherwise starts the next main-agent turn with the summary visible in model context. The parent does not need to remain active or check once before ending its turn.
28
28
 
29
29
  After dispatch:
30
30
 
31
31
  1. Continue only parent work that is independently useful to the requested result.
32
32
  2. If no such work remains, end the turn immediately. A short progress note is enough when the user needs one.
33
33
  3. Do not call `wait_agent`, `list_agents`, or `check_agent` in the same turn merely because the child was just launched. Ending the turn is the waiting mechanism.
34
- 4. Resume when a child completion notice invokes the main agent. Collect the completed result, launch any intentionally queued work if capacity requires waves, and otherwise keep waiting through notifications.
34
+ 4. When a child completion notice invokes the main agent, treat its model-visible summary as the child result and continue the original task immediately. Reconcile completed results, launch any intentionally queued work if capacity requires waves, and otherwise keep waiting through notifications. Do not wait for the user to prompt you again or call a status tool to retrieve the same result.
35
35
 
36
36
  A progress check is reasonable when the user asks for status, a child has run materially longer than expected for its task and model, an interruption left its state unclear, or current status will change an immediate coordination decision. Prefer `check_agent` for one known child and `list_agents` for a batch overview. Use `wait_agent` to collect results already expected to be available, not as a running-status probe.
37
37
 
@@ -1,20 +0,0 @@
1
- export function createDeferredResultDelivery<T extends { id: string }>() {
2
- const pending = new Map<string, T>();
3
-
4
- return {
5
- defer(result: T) {
6
- pending.set(result.id, result);
7
- },
8
- consume(ids: Iterable<string>) {
9
- for (const id of ids) pending.delete(id);
10
- },
11
- drain() {
12
- const results = [...pending.values()];
13
- pending.clear();
14
- return results;
15
- },
16
- clear() {
17
- pending.clear();
18
- },
19
- };
20
- }