@ian-pascoe/pi-minimal-subagents 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -141,22 +141,24 @@ the complete read-only discovery bundle.
141
141
  `agent_message` reports whether a message was delivered through an active
142
142
  parent wait, queued for the recipient, or failed. `subagent_wait` can return an
143
143
  intermediate Wait Event containing a Coordination Message before the child turn
144
- settles; call it again for the terminal turn result. Pass optional `turn_id` to
145
- address an older retained turn exactly. Without it, waits select the oldest
146
- observable claimed or pending turn before the active/latest turn. A caller may
147
- have only one outstanding wait for the same source turn; a concurrent duplicate
148
- is rejected instead of competing for one Wait Event.
144
+ settles. That event claims only its message, so later unconsumed messages and the
145
+ terminal result retain automatic fallback. If the turn has already settled, one
146
+ wait returns its terminal result with queued messages in `messages`. Pass
147
+ optional `turn_id` to address an older retained turn exactly. Without it, waits
148
+ select the oldest observable claimed or pending turn before the active/latest
149
+ turn. A caller may have only one outstanding wait for the same source turn; a
150
+ concurrent duplicate is rejected instead of competing for one Wait Event.
149
151
 
150
152
  The persisted Delivery Ledger records Coordination Messages, terminal results,
151
153
  globally increasing sequence, and wait ownership before delivery. Existing
152
154
  items retain their sequence; gaps from skipped malformed records are valid.
153
- Claims can name only active, latest, or retained turns. Once a wait returns an
154
- intermediate message, that wait path owns the rest of the source turn across
155
- reloads, forks, and newer turns. Automatic fallback retains its ordered queue
156
- reservation, treats idle notifications as advisory, and rechecks actual
157
- recipient idleness before injecting a message. Destination-session Delivery
158
- Evidence settles and compacts ledger items, preventing duplicate delivery and
159
- unbounded checkpoint growth. The pure Delivery Ledger state machine retains at
155
+ Claims can name only active, latest, or retained turns. Wait-returned messages
156
+ retain individual delivery evidence; terminal wait ownership remains durable
157
+ across reloads, forks, and newer turns. Automatic fallback retains its ordered
158
+ queue reservation and sends every unclaimed Coordination Message and terminal
159
+ result as a Pi steer, including while the recipient is active. Destination-session
160
+ Delivery Evidence settles and compacts ledger items, preventing duplicate delivery
161
+ and unbounded checkpoint growth. The pure Delivery Ledger state machine retains at
160
162
  most 20 pending wait-only terminal results per source agent; Coordination
161
163
  Messages are not removed by that terminal-retention limit. Delivered messages
162
164
  include stable delivery, source-agent, and source-turn identities in persisted
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ian-pascoe/pi-minimal-subagents",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "private": false,
5
5
  "description": "Persistent nested subagents with bounded delegation for Pi",
6
6
  "keywords": [
@@ -53,6 +53,7 @@ import type {
53
53
  StatusResult,
54
54
  TurnId,
55
55
  TurnResult,
56
+ WaitMessageResult,
56
57
  WaitResult,
57
58
  } from "./minimal-subagents-types.js";
58
59
 
@@ -85,11 +86,6 @@ interface PendingParentMessage {
85
86
  cancelGrace?: () => void;
86
87
  }
87
88
 
88
- interface CancelableWait {
89
- promise: Promise<void>;
90
- cancel: () => void;
91
- }
92
-
93
89
  function agentDeliveryKey(agentId: string, turnId: string): string {
94
90
  return `${agentId}\u0000${turnId}`;
95
91
  }
@@ -109,8 +105,9 @@ function terminalTurnResult(
109
105
  };
110
106
  }
111
107
 
112
- function terminalWaitResult(result: TurnResult): WaitResult {
113
- return { event: "turn", ...structuredClone(result) };
108
+ function terminalWaitResult(result: TurnResult, messages: WaitMessageResult[] = []): WaitResult {
109
+ const terminal = { event: "turn" as const, ...structuredClone(result) };
110
+ return messages.length === 0 ? terminal : { ...terminal, messages: structuredClone(messages) };
114
111
  }
115
112
 
116
113
  /** One root-owned coordinator for persistent nested Pi child sessions. */
@@ -124,11 +121,9 @@ export class MinimalSubagentsCoordinator {
124
121
  private readonly waiters = new Map<string, Set<TurnWaiter>>();
125
122
  private readonly pendingParentMessages = new Map<string, PendingParentMessage[]>();
126
123
  private readonly recipientQueues = new Map<string, Promise<unknown>>();
127
- private readonly recipientIdleWaiters = new Map<string, Set<() => void>>();
128
124
  private readonly automaticDeliveryKeys = new Set<string>();
129
125
  private readonly automaticCoordinationDeliveryIds = new Set<string>();
130
126
  private readonly waitHandedDeliveryIds = new Set<string>();
131
- private readonly automaticDeliveryClaimWaiters = new Map<string, Set<() => void>>();
132
127
  private readonly backgroundOperations = new Set<Promise<void>>();
133
128
  private acceptingOperations = true;
134
129
  private lifecycleEpoch = 0;
@@ -363,15 +358,16 @@ export class MinimalSubagentsCoordinator {
363
358
  new Error(`Minimal subagents duplicate wait: ${callerId} is already waiting for ${turnId}`),
364
359
  );
365
360
  }
366
- const pendingMessage = this.claimPendingParentMessage(callerId, agentId, turnId);
367
- if (pendingMessage) return Promise.resolve(pendingMessage);
368
361
  const retainedResult =
369
362
  findTerminalDelivery(this.deliveryLedger, agentId, turnId)?.result ??
370
363
  (agent.latest_result?.turn_id === turnId ? agent.latest_result : undefined);
371
364
  if (retainedResult) {
365
+ const messages = this.drainPendingParentMessages(callerId, agentId, turnId);
372
366
  this.claimTerminalDelivery(callerId, retainedResult);
373
- return Promise.resolve(terminalWaitResult(retainedResult));
367
+ return Promise.resolve(terminalWaitResult(retainedResult, messages));
374
368
  }
369
+ const pendingMessage = this.claimPendingParentMessage(callerId, agentId, turnId);
370
+ if (pendingMessage) return Promise.resolve(pendingMessage);
375
371
  if (agent.active_turn_id !== turnId) {
376
372
  return Promise.reject(
377
373
  new Error(`Minimal subagents wait: turn ${turnId} is no longer retained for ${agentId}`),
@@ -543,7 +539,6 @@ export class MinimalSubagentsCoordinator {
543
539
  this.agents.clear();
544
540
  this.deliveryLedger = createDeliveryLedger();
545
541
  this.pendingParentMessages.clear();
546
- this.releaseAllRecipientIdleWaiters();
547
542
  this.recipientQueues.clear();
548
543
  this.backgroundOperations.clear();
549
544
  await Promise.allSettled(
@@ -558,12 +553,10 @@ export class MinimalSubagentsCoordinator {
558
553
  this.waiters.clear();
559
554
  this.pendingParentMessages.clear();
560
555
  this.waitHandedDeliveryIds.clear();
561
- this.releaseAllRecipientIdleWaiters();
562
556
  this.recipientQueues.clear();
563
557
  this.backgroundOperations.clear();
564
558
  this.automaticDeliveryKeys.clear();
565
559
  this.automaticCoordinationDeliveryIds.clear();
566
- this.automaticDeliveryClaimWaiters.clear();
567
560
  this.deliveryLedger = createDeliveryLedger({
568
561
  deliveries: snapshot.deliveries,
569
562
  coordination_deliveries: snapshot.coordination_deliveries,
@@ -720,13 +713,6 @@ export class MinimalSubagentsCoordinator {
720
713
  await Promise.allSettled(scheduled);
721
714
  }
722
715
 
723
- /** Release ordered automatic deliveries after one recipient conversation becomes idle. */
724
- markRecipientIdle(agentId: string): void {
725
- const waiters = this.recipientIdleWaiters.get(agentId);
726
- this.recipientIdleWaiters.delete(agentId);
727
- for (const resolve of waiters ?? []) resolve();
728
- }
729
-
730
716
  /** Clone complete child leaves for root fork ownership without ever sharing source session paths. */
731
717
  async prepareFork(sourceRootSessionFile: string): Promise<ForkSnapshot> {
732
718
  const activeRootChildren = this.childrenOf("root");
@@ -785,7 +771,6 @@ export class MinimalSubagentsCoordinator {
785
771
  shutdown(): Promise<void> {
786
772
  if (this.shutdownPromise) return this.shutdownPromise;
787
773
  this.acceptingOperations = false;
788
- this.releaseAllRecipientIdleWaiters();
789
774
  this.shutdownPromise = this.finishShutdown();
790
775
  return this.shutdownPromise;
791
776
  }
@@ -999,7 +984,6 @@ export class MinimalSubagentsCoordinator {
999
984
  });
1000
985
  }
1001
986
  if (result.status !== "completed") this.removeSettledEmptyTurnClaim(agent.agent_id, turnId);
1002
- this.markRecipientIdle(agent.agent_id);
1003
987
  }
1004
988
 
1005
989
  private async deliverAutomaticResult(
@@ -1024,15 +1008,6 @@ export class MinimalSubagentsCoordinator {
1024
1008
  this.settleDelivery(delivery);
1025
1009
  return;
1026
1010
  }
1027
- while (!this.isRecipientIdle(delivery.destination_agent_id)) {
1028
- const idleWait = this.createRecipientIdleWait(delivery.destination_agent_id);
1029
- const claimWait = this.createAutomaticDeliveryClaimWait(deliveryKey);
1030
- await Promise.race([idleWait.promise, claimWait.promise]);
1031
- idleWait.cancel();
1032
- claimWait.cancel();
1033
- if (!this.acceptingOperations || this.shouldStopAutomaticTerminalDelivery(delivery))
1034
- return;
1035
- }
1036
1011
  if (!this.acceptingOperations || this.shouldStopAutomaticTerminalDelivery(delivery)) return;
1037
1012
  if (this.hasDeliveryEvidence(delivery)) {
1038
1013
  this.settleDelivery(delivery);
@@ -1051,11 +1026,8 @@ export class MinimalSubagentsCoordinator {
1051
1026
  usage: result.usage,
1052
1027
  },
1053
1028
  };
1054
- await this.deliverToRecipient(
1055
- delivery.destination_agent_id,
1056
- message,
1057
- () => this.isTerminalDeliveryCurrent(delivery),
1058
- true,
1029
+ await this.deliverToRecipient(delivery.destination_agent_id, message, () =>
1030
+ this.isTerminalDeliveryCurrent(delivery),
1059
1031
  );
1060
1032
  });
1061
1033
  } catch (error) {
@@ -1263,7 +1235,6 @@ export class MinimalSubagentsCoordinator {
1263
1235
  targetId: string,
1264
1236
  message: CoordinatorMessage,
1265
1237
  isCurrentDelivery: () => boolean = () => true,
1266
- requireIdleRecipient = false,
1267
1238
  ): Promise<void> {
1268
1239
  if (!isCurrentDelivery()) {
1269
1240
  throw new Error("Minimal subagents delivery abandoned after session branch change");
@@ -1281,9 +1252,6 @@ export class MinimalSubagentsCoordinator {
1281
1252
  throw new Error("Minimal subagents delivery abandoned after session branch change");
1282
1253
  }
1283
1254
  const visibleMessage = addCoordinatorMessageEnvelope(message);
1284
- if (requireIdleRecipient && (target.active_turn_id || runtime.isRunning)) {
1285
- throw new Error(`Minimal subagents automatic delivery recipient became active: ${targetId}`);
1286
- }
1287
1255
  if (target.active_turn_id || runtime.isRunning) {
1288
1256
  await runtime.queueCoordinatorMessage(visibleMessage);
1289
1257
  return;
@@ -1367,8 +1335,6 @@ export class MinimalSubagentsCoordinator {
1367
1335
  private applyDeliveryLedgerTransition(transition: DeliveryLedgerTransition): void {
1368
1336
  this.deliveryLedger = transition.ledger;
1369
1337
  for (const delivery of transition.prunedTerminalDeliveries) {
1370
- const key = agentDeliveryKey(delivery.source_agent_id, delivery.source_turn_id);
1371
- this.releaseAutomaticDeliveryClaimWaiters(key);
1372
1338
  this.dependencies.registry.append(
1373
1339
  createRegistryEvent(this.dependencies.registry.rootSessionId, "delivery-pruned", {
1374
1340
  source_agent_id: delivery.source_agent_id,
@@ -1504,16 +1470,8 @@ export class MinimalSubagentsCoordinator {
1504
1470
  }
1505
1471
 
1506
1472
  private pruneDeliveryStateForDeletedAgent(agentId: string): void {
1507
- const previousTerminalDeliveries = this.deliveryLedger.terminalDeliveries;
1508
1473
  const previousCoordinationDeliveries = this.deliveryLedger.coordinationDeliveries;
1509
1474
  this.deliveryLedger = pruneDeliveryLedgerAgents(this.deliveryLedger, [agentId]).ledger;
1510
- for (const delivery of previousTerminalDeliveries) {
1511
- if (!this.isTerminalDeliveryCurrent(delivery)) {
1512
- this.releaseAutomaticDeliveryClaimWaiters(
1513
- agentDeliveryKey(delivery.source_agent_id, delivery.source_turn_id),
1514
- );
1515
- }
1516
- }
1517
1475
  for (const delivery of previousCoordinationDeliveries) {
1518
1476
  if (!this.isCoordinationDeliveryCurrent(delivery)) {
1519
1477
  this.waitHandedDeliveryIds.delete(delivery.delivery_id);
@@ -1673,7 +1631,6 @@ export class MinimalSubagentsCoordinator {
1673
1631
  (candidate) => candidate.callerId === destinationAgentId,
1674
1632
  );
1675
1633
  if (!waiter) return false;
1676
- this.claimDeliveryTurn(message.details.source_agent_id, message.details.source_turn_id);
1677
1634
  this.deliveryLedger = setCoordinationDeliveryPath(
1678
1635
  this.deliveryLedger,
1679
1636
  delivery.delivery_id,
@@ -1698,7 +1655,7 @@ export class MinimalSubagentsCoordinator {
1698
1655
  callerId: string,
1699
1656
  sourceAgentId: string,
1700
1657
  sourceTurnId: string,
1701
- ): WaitResult | undefined {
1658
+ ): WaitMessageResult | undefined {
1702
1659
  const key = agentDeliveryKey(sourceAgentId, sourceTurnId);
1703
1660
  const pendingMessages = this.pendingParentMessages.get(key);
1704
1661
  const index = pendingMessages?.findIndex(
@@ -1715,7 +1672,6 @@ export class MinimalSubagentsCoordinator {
1715
1672
  )
1716
1673
  .sort((left, right) => left.sequence - right.sequence)[0];
1717
1674
  if (!retained) return undefined;
1718
- this.claimDeliveryTurn(sourceAgentId, sourceTurnId);
1719
1675
  this.deliveryLedger = setCoordinationDeliveryPath(
1720
1676
  this.deliveryLedger,
1721
1677
  retained.delivery_id,
@@ -1735,7 +1691,6 @@ export class MinimalSubagentsCoordinator {
1735
1691
  }
1736
1692
  const pending = pendingMessages[index];
1737
1693
  if (!pending) return undefined;
1738
- this.claimDeliveryTurn(sourceAgentId, sourceTurnId);
1739
1694
  const delivery = findCoordinationDelivery(this.deliveryLedger, pending.deliveryId);
1740
1695
  if (delivery) {
1741
1696
  this.deliveryLedger = setCoordinationDeliveryPath(
@@ -1747,10 +1702,6 @@ export class MinimalSubagentsCoordinator {
1747
1702
  if (currentDelivery) this.persistCoordinationDeliveryState(currentDelivery);
1748
1703
  this.waitHandedDeliveryIds.add(delivery.delivery_id);
1749
1704
  }
1750
- const sourceResult = this.agents.get(sourceAgentId)?.latest_result;
1751
- if (sourceResult?.turn_id === sourceTurnId) {
1752
- this.setTerminalDeliveryPathToWait(callerId, sourceResult);
1753
- }
1754
1705
  pending.claimed = true;
1755
1706
  pending.cancelGrace?.();
1756
1707
  pending.releaseClaim();
@@ -1766,6 +1717,20 @@ export class MinimalSubagentsCoordinator {
1766
1717
  };
1767
1718
  }
1768
1719
 
1720
+ private drainPendingParentMessages(
1721
+ callerId: string,
1722
+ sourceAgentId: string,
1723
+ sourceTurnId: string,
1724
+ ): WaitMessageResult[] {
1725
+ const messages: WaitMessageResult[] = [];
1726
+ let message = this.claimPendingParentMessage(callerId, sourceAgentId, sourceTurnId);
1727
+ while (message) {
1728
+ messages.push(message);
1729
+ message = this.claimPendingParentMessage(callerId, sourceAgentId, sourceTurnId);
1730
+ }
1731
+ return messages;
1732
+ }
1733
+
1769
1734
  private queuePendingParentMessage(
1770
1735
  targetId: string,
1771
1736
  message: CoordinatorMessage,
@@ -1821,20 +1786,11 @@ export class MinimalSubagentsCoordinator {
1821
1786
  pending.cancelGrace?.();
1822
1787
  pending.cancelGrace = undefined;
1823
1788
  if (!this.acceptingOperations || pending.claimed || turnClaimed()) return;
1824
- while (!this.isRecipientIdle(targetId)) {
1825
- const idleWait = this.createRecipientIdleWait(targetId);
1826
- await Promise.race([pending.claimPromise, idleWait.promise]);
1827
- idleWait.cancel();
1828
- if (!this.acceptingOperations || pending.claimed || turnClaimed()) return;
1829
- }
1830
1789
  if (!this.acceptingOperations || pending.claimed || turnClaimed()) return;
1831
1790
  this.removePendingParentMessage(key, pending);
1832
1791
  this.waitHandedDeliveryIds.add(delivery.delivery_id);
1833
- await this.deliverToRecipient(
1834
- targetId,
1835
- message,
1836
- () => this.isCoordinationDeliveryCurrent(delivery),
1837
- true,
1792
+ await this.deliverToRecipient(targetId, message, () =>
1793
+ this.isCoordinationDeliveryCurrent(delivery),
1838
1794
  );
1839
1795
  });
1840
1796
  void operation.catch((cause) => {
@@ -1874,14 +1830,12 @@ export class MinimalSubagentsCoordinator {
1874
1830
  }
1875
1831
 
1876
1832
  private setTerminalDeliveryPathToWait(callerId: string, result: TurnResult): void {
1877
- const key = agentDeliveryKey(result.agent_id, result.turn_id);
1878
1833
  const delivery = findTerminalDelivery(this.deliveryLedger, result.agent_id, result.turn_id);
1879
1834
  if (!delivery || delivery.destination_agent_id !== callerId || delivery.path === "wait") return;
1880
1835
  this.applyDeliveryLedgerTransition(
1881
1836
  setTerminalDeliveryPath(this.deliveryLedger, result.agent_id, result.turn_id, "wait"),
1882
1837
  );
1883
1838
  const retained = findTerminalDelivery(this.deliveryLedger, result.agent_id, result.turn_id);
1884
- this.releaseAutomaticDeliveryClaimWaiters(key);
1885
1839
  if (!retained) return;
1886
1840
  this.dependencies.registry.append(
1887
1841
  createRegistryEvent(this.dependencies.registry.rootSessionId, "delivery-pending", {
@@ -1890,57 +1844,6 @@ export class MinimalSubagentsCoordinator {
1890
1844
  );
1891
1845
  }
1892
1846
 
1893
- private isRecipientIdle(agentId: string): boolean {
1894
- if (agentId === "root") return this.dependencies.root.isIdle();
1895
- const agent = this.agents.get(agentId);
1896
- if (!agent || agent.active_turn_id) return false;
1897
- return !this.runtimes.get(agentId)?.isRunning;
1898
- }
1899
-
1900
- private createRecipientIdleWait(agentId: string): CancelableWait {
1901
- const { promise, resolve: release } = Promise.withResolvers<void>();
1902
- const waiters = this.recipientIdleWaiters.get(agentId) ?? new Set();
1903
- waiters.add(release);
1904
- this.recipientIdleWaiters.set(agentId, waiters);
1905
- return {
1906
- promise,
1907
- cancel: () => {
1908
- const currentWaiters = this.recipientIdleWaiters.get(agentId);
1909
- currentWaiters?.delete(release);
1910
- if (currentWaiters?.size === 0) this.recipientIdleWaiters.delete(agentId);
1911
- },
1912
- };
1913
- }
1914
-
1915
- private createAutomaticDeliveryClaimWait(deliveryKey: string): CancelableWait {
1916
- const { promise, resolve: release } = Promise.withResolvers<void>();
1917
- const waiters = this.automaticDeliveryClaimWaiters.get(deliveryKey) ?? new Set();
1918
- waiters.add(release);
1919
- this.automaticDeliveryClaimWaiters.set(deliveryKey, waiters);
1920
- return {
1921
- promise,
1922
- cancel: () => {
1923
- const currentWaiters = this.automaticDeliveryClaimWaiters.get(deliveryKey);
1924
- currentWaiters?.delete(release);
1925
- if (currentWaiters?.size === 0) this.automaticDeliveryClaimWaiters.delete(deliveryKey);
1926
- },
1927
- };
1928
- }
1929
-
1930
- private releaseAutomaticDeliveryClaimWaiters(deliveryKey: string): void {
1931
- const waiters = this.automaticDeliveryClaimWaiters.get(deliveryKey);
1932
- this.automaticDeliveryClaimWaiters.delete(deliveryKey);
1933
- for (const resolve of waiters ?? []) resolve();
1934
- }
1935
-
1936
- private releaseAllRecipientIdleWaiters(): void {
1937
- const allWaiters = [...this.recipientIdleWaiters.values()];
1938
- this.recipientIdleWaiters.clear();
1939
- for (const waiters of allWaiters) {
1940
- for (const resolve of waiters) resolve();
1941
- }
1942
- }
1943
-
1944
1847
  private async cancelDuringShutdown(agentId: string): Promise<void> {
1945
1848
  const target = this.agents.get(agentId);
1946
1849
  if (!target) return;
@@ -5,7 +5,6 @@ import {
5
5
  getAgentDir,
6
6
  SessionManager,
7
7
  SettingsManager,
8
- type AgentSettledEvent,
9
8
  type ExtensionAPI,
10
9
  type ExtensionContext,
11
10
  type ExtensionFactory,
@@ -107,7 +106,6 @@ function createRootConversationEndpoint(
107
106
  sourceTurnId,
108
107
  deliveryId,
109
108
  ),
110
- isIdle: () => context.isIdle(),
111
109
  };
112
110
  }
113
111
 
@@ -331,7 +329,6 @@ export class MinimalSubagentsLifecycleController {
331
329
  this.pi.on("session_before_fork", (event, context) => this.prepareSessionFork(event, context));
332
330
  this.pi.on("session_tree", (event, context) => this.restoreSessionTree(event, context));
333
331
  this.pi.on("message_end", (event, context) => this.reconcileMessageDelivery(event, context));
334
- this.pi.on("agent_settled", (event, context) => this.releaseSettledRecipient(event, context));
335
332
  this.pi.on("session_shutdown", (event, context) => this.shutdownSession(event, context));
336
333
  }
337
334
 
@@ -554,12 +551,6 @@ export class MinimalSubagentsLifecycleController {
554
551
  }
555
552
  }
556
553
 
557
- private releaseSettledRecipient(_event: AgentSettledEvent, context: ExtensionContext): void {
558
- if (!this.coordinator) return;
559
- if (context.isIdle()) this.coordinator.markRecipientIdle("root");
560
- this.uiController?.refresh();
561
- }
562
-
563
554
  private async shutdownSession(
564
555
  event: SessionShutdownEvent,
565
556
  context: ExtensionContext,
@@ -132,6 +132,7 @@ const WaitMessageDetailsSchema = Type.Object({
132
132
  agent_id: Type.String(),
133
133
  turn_id: Type.String(),
134
134
  message_id: Type.String(),
135
+ delivery_id: Type.Optional(Type.String()),
135
136
  message: Type.String(),
136
137
  elapsed_ms: Type.Optional(Type.Number()),
137
138
  usage: Type.Optional(RenderUsageSchema),
@@ -145,6 +146,7 @@ const WaitTurnDetailsSchema = Type.Object({
145
146
  error: Type.Optional(Type.String()),
146
147
  elapsed_ms: Type.Optional(Type.Number()),
147
148
  usage: Type.Optional(RenderUsageSchema),
149
+ messages: Type.Optional(Type.Array(WaitMessageDetailsSchema)),
148
150
  });
149
151
  const StatusDetailsSchema = Type.Union([
150
152
  Type.Object({
@@ -341,9 +341,12 @@ function renderWaitResult(
341
341
  : details.status;
342
342
  const duration = formatSubagentDuration(details.elapsed_ms);
343
343
  const tokens = formatSubagentTokenCount(details.usage?.totalTokens);
344
- const metrics = [duration, tokens ? `${tokens} tokens` : undefined].filter(
345
- (metric): metric is string => metric !== undefined,
346
- );
344
+ const drainedMessageCount = details.event === "message" ? 0 : (details.messages?.length ?? 0);
345
+ const metrics = [
346
+ duration,
347
+ tokens ? `${tokens} tokens` : undefined,
348
+ drainedMessageCount > 0 ? `${drainedMessageCount} messages` : undefined,
349
+ ].filter((metric): metric is string => metric !== undefined);
347
350
  const summary = renderSubagentSummary(theme, status, agentId, metrics);
348
351
  if (options.isPartial || !options.expanded) {
349
352
  return new Text(`${summary}${options.isPartial ? "" : collapsedExpansionHint(theme)}`, 0, 0);
@@ -356,6 +359,14 @@ function renderWaitResult(
356
359
  container.addChild(renderLabelValue(theme, "Message ID", details.message_id));
357
360
  return container;
358
361
  }
362
+ if (details.messages && details.messages.length > 0) {
363
+ appendTextSection(
364
+ container,
365
+ theme,
366
+ "Messages",
367
+ details.messages.map((message) => message.message).join("\n\n"),
368
+ );
369
+ }
359
370
  const output = details.output ?? "";
360
371
  if (status === "completed") {
361
372
  if (output.length > 0) {
@@ -28,6 +28,11 @@ export const ForkOwnershipRecordSchema = Type.Object({
28
28
  direct_parent_id: Type.String(),
29
29
  });
30
30
 
31
+ const DeliveryEvidenceMessageSchema = Type.Object({
32
+ delivery_id: Type.Optional(Type.String()),
33
+ message_id: Type.Optional(Type.String()),
34
+ });
35
+
31
36
  /** Parses durable custom-message and wait-tool Delivery Evidence details. */
32
37
  export const DeliveryEvidenceDetailsSchema = Type.Object({
33
38
  event: Type.Optional(Type.String()),
@@ -35,6 +40,7 @@ export const DeliveryEvidenceDetailsSchema = Type.Object({
35
40
  source_turn_id: Type.String(),
36
41
  delivery_id: Type.Optional(Type.String()),
37
42
  message_id: Type.Optional(Type.String()),
43
+ messages: Type.Optional(Type.Array(DeliveryEvidenceMessageSchema)),
38
44
  });
39
45
 
40
46
  export type ChildSessionIdentityRecord = Static<typeof ChildSessionIdentityRecordSchema>;
@@ -411,7 +411,11 @@ export function findDeliveryEvidence(
411
411
  return (
412
412
  details.source_agent_id === sourceAgentId &&
413
413
  details.source_turn_id === sourceTurnId &&
414
- (details.delivery_id === deliveryId || details.message_id === deliveryId)
414
+ (details.delivery_id === deliveryId ||
415
+ details.message_id === deliveryId ||
416
+ details.messages?.some(
417
+ (message) => message.delivery_id === deliveryId || message.message_id === deliveryId,
418
+ ))
415
419
  );
416
420
  }
417
421
  if (waitToolResult && details.event === "message") return false;
@@ -202,7 +202,7 @@ export function createCoordinatorToolDefinitions(
202
202
  name: "subagent_wait",
203
203
  label: "Subagent Wait",
204
204
  description:
205
- "Wait for one direct child's oldest observable turn, or select an exact retained turn_id. A Wait Event containing a Coordination Message may arrive first as event=message; call again for the terminal turn result. A successful wait durably claims that turn so later messages and its terminal result return through wait without duplicate automatic delivery. Timeout never cancels the child.",
205
+ "Wait for one direct child's oldest observable turn, or select an exact retained turn_id. An active child may first return event=message; later unconsumed items still fall back automatically. An already settled turn returns event=turn once with queued messages in messages. Timeout never cancels the child.",
206
206
  promptSnippet: "Wait for one direct child's exact turn",
207
207
  parameters: options.schemas.subagent_wait,
208
208
  async execute(_toolCallId, parameters, signal, onUpdate) {
@@ -75,8 +75,14 @@ export interface WaitMessageResult {
75
75
  message: string;
76
76
  }
77
77
 
78
+ /** Reports one terminal child turn and any earlier messages drained by the same wait. */
79
+ export interface WaitTurnResult extends TurnResult {
80
+ event: "turn";
81
+ messages?: WaitMessageResult[];
82
+ }
83
+
78
84
  /** Reports one terminal child turn returned by subagent_wait. */
79
- export type WaitResult = WaitMessageResult | ({ event: "turn" } & TurnResult);
85
+ export type WaitResult = WaitMessageResult | WaitTurnResult;
80
86
 
81
87
  /** Provides bounded hierarchy, usage, and best-known Runtime Profile data for one persistent agent. */
82
88
  export interface AgentSummary extends RuntimeProfile {
@@ -243,8 +249,6 @@ export interface RootConversationEndpoint {
243
249
  /** Queue one typed coordinator message into the root conversation. */
244
250
  queueCoordinatorMessage(message: CoordinatorMessage): Promise<void>;
245
251
  hasDeliveryEvidence(sourceAgentId: string, sourceTurnId: string, deliveryId?: string): boolean;
246
- /** Report whether automatic delivery can start a new root turn without racing an active wait. */
247
- isIdle(): boolean;
248
252
  }
249
253
 
250
254
  /** Stores root-owned agent identity, launch contract, availability, and latest activity. */