@ian-pascoe/pi-minimal-subagents 0.1.1 → 0.2.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.
@@ -1,5 +1,4 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import type { AgentMessage } from "@earendil-works/pi-agent-core";
3
2
  import { assembleImportedContext, contextContainsImages } from "./minimal-subagents-context.js";
4
3
  import {
5
4
  canAgentContractSpawn,
@@ -8,9 +7,32 @@ import {
8
7
  getSubagentDepth,
9
8
  resolveOrdinaryToolSelection,
10
9
  } from "./minimal-subagents-capabilities.js";
10
+ import {
11
+ addCoordinationDelivery,
12
+ addTerminalDelivery,
13
+ claimDeliveryLedgerTurn,
14
+ createDeliveryLedger,
15
+ deliveryLedgerSnapshot,
16
+ findCoordinationDelivery,
17
+ findTerminalDelivery,
18
+ isDeliveryLedgerTurnClaimed,
19
+ pruneDeliveryLedgerAgents,
20
+ releaseEmptyDeliveryLedgerTurn,
21
+ selectObservableDeliveryTurn,
22
+ setCoordinationDeliveryError,
23
+ setCoordinationDeliveryPath,
24
+ setTerminalDeliveryError,
25
+ setTerminalDeliveryPath,
26
+ settleCoordinationDelivery,
27
+ settleTerminalDelivery,
28
+ type DeliveryLedger,
29
+ type DeliveryLedgerTransition,
30
+ } from "./minimal-subagents-delivery-ledger.js";
31
+ import { addCoordinatorMessageEnvelope } from "./minimal-subagents-message-envelope.js";
11
32
  import { createRegistryEvent } from "./minimal-subagents-registry.js";
12
33
  import type {
13
34
  AgentDetail,
35
+ AgentMessageDisposition,
14
36
  AgentMessageResult,
15
37
  AgentSessionFactory,
16
38
  AgentSummary,
@@ -23,6 +45,7 @@ import type {
23
45
  ForkSnapshot,
24
46
  HierarchyStatusResult,
25
47
  PersistedAgent,
48
+ PersistedCoordinationDelivery,
26
49
  PersistedDelivery,
27
50
  RegistrySnapshot,
28
51
  SpawnParameters,
@@ -30,6 +53,7 @@ import type {
30
53
  StatusResult,
31
54
  TurnId,
32
55
  TurnResult,
56
+ WaitResult,
33
57
  } from "./minimal-subagents-types.js";
34
58
 
35
59
  const FRIENDLY_AGENT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
@@ -44,13 +68,28 @@ interface MessageParameters {
44
68
 
45
69
  interface TurnWaiter {
46
70
  callerId: string;
47
- resolve: (result: TurnResult) => void;
71
+ resolve: (result: WaitResult) => void;
48
72
  reject: (error: Error) => void;
49
73
  timeout?: ReturnType<typeof setTimeout>;
50
74
  abortSignal?: AbortSignal;
51
75
  abortListener?: () => void;
52
76
  }
53
77
 
78
+ interface PendingParentMessage {
79
+ deliveryId: string;
80
+ message: CoordinatorMessage;
81
+ destinationAgentId: string;
82
+ claimed: boolean;
83
+ claimPromise: Promise<void>;
84
+ releaseClaim: () => void;
85
+ cancelGrace?: () => void;
86
+ }
87
+
88
+ interface CancelableWait {
89
+ promise: Promise<void>;
90
+ cancel: () => void;
91
+ }
92
+
54
93
  function agentDeliveryKey(agentId: string, turnId: string): string {
55
94
  return `${agentId}\u0000${turnId}`;
56
95
  }
@@ -70,19 +109,29 @@ function terminalTurnResult(
70
109
  };
71
110
  }
72
111
 
112
+ function terminalWaitResult(result: TurnResult): WaitResult {
113
+ return { event: "turn", ...structuredClone(result) };
114
+ }
115
+
73
116
  /** One root-owned coordinator for persistent nested Pi child sessions. */
74
117
  export class MinimalSubagentsCoordinator {
75
118
  private readonly agents = new Map<string, PersistedAgent>();
76
119
  private readonly runtimes = new Map<string, ChildAgentRuntime>();
77
120
  private readonly runtimeInitializations = new Map<string, Promise<ChildAgentRuntime>>();
78
- private readonly importedMessages = new Map<string, AgentMessage[]>();
79
121
  private readonly tombstones = new Set<string>();
80
122
  private readonly pendingAgentIds = new Set<string>();
81
- private readonly deliveries = new Map<string, PersistedDelivery>();
123
+ private deliveryLedger: DeliveryLedger = createDeliveryLedger();
82
124
  private readonly waiters = new Map<string, Set<TurnWaiter>>();
83
- private readonly recipientQueues = new Map<string, Promise<void>>();
125
+ private readonly pendingParentMessages = new Map<string, PendingParentMessage[]>();
126
+ private readonly recipientQueues = new Map<string, Promise<unknown>>();
127
+ private readonly recipientIdleWaiters = new Map<string, Set<() => void>>();
128
+ private readonly automaticDeliveryKeys = new Set<string>();
129
+ private readonly automaticCoordinationDeliveryIds = new Set<string>();
130
+ private readonly waitHandedDeliveryIds = new Set<string>();
131
+ private readonly automaticDeliveryClaimWaiters = new Map<string, Set<() => void>>();
84
132
  private readonly backgroundOperations = new Set<Promise<void>>();
85
133
  private acceptingOperations = true;
134
+ private lifecycleEpoch = 0;
86
135
  private shutdownPromise?: Promise<void>;
87
136
 
88
137
  constructor(private readonly dependencies: CoordinatorDependencies) {}
@@ -93,10 +142,11 @@ export class MinimalSubagentsCoordinator {
93
142
 
94
143
  /** Return a serializable complete hierarchy checkpoint without process-local runtimes. */
95
144
  snapshot(): RegistrySnapshot {
145
+ const ledger = deliveryLedgerSnapshot(this.deliveryLedger);
96
146
  return {
97
147
  agents: [...this.agents.values()].map((agent) => structuredClone(agent)),
98
148
  tombstones: [...this.tombstones],
99
- deliveries: [...this.deliveries.values()].map((delivery) => structuredClone(delivery)),
149
+ ...ledger,
100
150
  };
101
151
  }
102
152
 
@@ -191,10 +241,15 @@ export class MinimalSubagentsCoordinator {
191
241
  } finally {
192
242
  this.pendingAgentIds.delete(agentId);
193
243
  }
244
+ if (!identity.sessionLeafId) {
245
+ throw new Error(
246
+ `Minimal subagents persistent identity: no selected session leaf for ${agent.agent_id}`,
247
+ );
248
+ }
194
249
  agent.session_file = identity.sessionFile;
195
250
  agent.session_id = identity.sessionId;
251
+ agent.session_leaf_id = identity.sessionLeafId;
196
252
  this.agents.set(agentId, agent);
197
- this.importedMessages.set(agentId, imported.messages);
198
253
  this.dependencies.registry.append(
199
254
  createRegistryEvent(this.dependencies.registry.rootSessionId, "agent-created", { agent }),
200
255
  );
@@ -233,7 +288,7 @@ export class MinimalSubagentsCoordinator {
233
288
  };
234
289
  }
235
290
 
236
- /** Send one steer-only coordination message to an authorized adjacent agent. */
291
+ /** Send one coordination message to an authorized adjacent agent. */
237
292
  async sendAgentMessage(
238
293
  callerId: string,
239
294
  parameters: MessageParameters,
@@ -242,39 +297,88 @@ export class MinimalSubagentsCoordinator {
242
297
  this.assertAccepting();
243
298
  this.assertCallerExists(callerId);
244
299
  const targetId = this.resolveMessageTarget(callerId, parameters.agent_id);
300
+ const messageId = randomUUID();
301
+ const message = this.createExplicitMessage(
302
+ callerId,
303
+ targetId,
304
+ sourceTurnId,
305
+ messageId,
306
+ parameters.message,
307
+ );
308
+ const sourceAgent = callerId === "root" ? undefined : this.requireAgent(callerId);
309
+ const sentToDirectParent = sourceAgent?.parent_id === targetId;
310
+ const delivery = this.persistCoordinationDelivery(message, targetId);
245
311
  try {
246
- await this.enqueueRecipientDelivery(targetId, async () => {
247
- await this.deliverExplicitMessage(callerId, targetId, sourceTurnId, parameters.message);
248
- });
249
- return { agent_id: targetId, delivered: true };
312
+ this.recordRecentMessage(targetId, message);
313
+ if (sentToDirectParent) {
314
+ if (this.resolveWaiterWithMessage(message, delivery)) {
315
+ return { agent_id: targetId, message_id: messageId, disposition: "delivered-via-wait" };
316
+ }
317
+ this.queuePendingParentMessage(targetId, message, delivery);
318
+ return { agent_id: targetId, message_id: messageId, disposition: "queued" };
319
+ }
320
+ const disposition = await this.enqueueRecipientDelivery(targetId, async () =>
321
+ this.deliverExplicitMessage(message, targetId, delivery),
322
+ );
323
+ return { agent_id: targetId, message_id: messageId, disposition };
250
324
  } catch (error) {
325
+ const deliveryError = error instanceof Error ? error.message : String(error);
326
+ if (this.isCoordinationDeliveryCurrent(delivery)) {
327
+ this.deliveryLedger = setCoordinationDeliveryError(
328
+ this.deliveryLedger,
329
+ delivery.delivery_id,
330
+ deliveryError,
331
+ );
332
+ const current = findCoordinationDelivery(this.deliveryLedger, delivery.delivery_id);
333
+ if (current) this.persistCoordinationDeliveryState(current);
334
+ }
251
335
  return {
252
336
  agent_id: targetId,
253
- delivered: false,
254
- error: error instanceof Error ? error.message : String(error),
337
+ message_id: messageId,
338
+ disposition: "failed",
339
+ error: deliveryError,
255
340
  };
256
341
  }
257
342
  }
258
343
 
259
- /** Wait for the exact active turn captured at invocation, or return the latest idle result. */
344
+ /** Wait for one exact turn and claim its message/result delivery from automatic fallback. */
260
345
  wait(
261
346
  callerId: string,
262
347
  agentId: string,
263
348
  timeoutMs?: number,
264
349
  signal?: AbortSignal,
265
- ): Promise<TurnResult> {
350
+ requestedTurnId?: string,
351
+ ): Promise<WaitResult> {
266
352
  this.assertAccepting();
267
353
  this.assertCallerExists(callerId);
268
354
  this.assertCallerTargetsDirectChild(callerId, agentId, "wait");
269
355
  const agent = this.requireUsableAgent(agentId, "wait");
270
- if (!agent.active_turn_id) {
271
- if (agent.latest_result) return Promise.resolve(structuredClone(agent.latest_result));
356
+ const turnId = requestedTurnId ?? this.selectObservableTurnId(agentId, callerId);
357
+ if (!turnId) {
272
358
  return Promise.reject(new Error(`Minimal subagents wait: ${agentId} has no turn to observe`));
273
359
  }
274
- const turnId = agent.active_turn_id;
275
360
  const key = agentDeliveryKey(agentId, turnId);
361
+ if ([...(this.waiters.get(key) ?? [])].some((waiter) => waiter.callerId === callerId)) {
362
+ return Promise.reject(
363
+ new Error(`Minimal subagents duplicate wait: ${callerId} is already waiting for ${turnId}`),
364
+ );
365
+ }
366
+ const pendingMessage = this.claimPendingParentMessage(callerId, agentId, turnId);
367
+ if (pendingMessage) return Promise.resolve(pendingMessage);
368
+ const retainedResult =
369
+ findTerminalDelivery(this.deliveryLedger, agentId, turnId)?.result ??
370
+ (agent.latest_result?.turn_id === turnId ? agent.latest_result : undefined);
371
+ if (retainedResult) {
372
+ this.claimTerminalDelivery(callerId, retainedResult);
373
+ return Promise.resolve(terminalWaitResult(retainedResult));
374
+ }
375
+ if (agent.active_turn_id !== turnId) {
376
+ return Promise.reject(
377
+ new Error(`Minimal subagents wait: turn ${turnId} is no longer retained for ${agentId}`),
378
+ );
379
+ }
276
380
 
277
- return new Promise<TurnResult>((resolve, reject) => {
381
+ return new Promise<WaitResult>((resolve, reject) => {
278
382
  const waiter: TurnWaiter = { callerId, resolve, reject, abortSignal: signal };
279
383
  let turnWaiters = this.waiters.get(key);
280
384
  if (!turnWaiters) {
@@ -394,11 +498,12 @@ export class MinimalSubagentsCoordinator {
394
498
  runtime?.dispose();
395
499
  this.runtimes.delete(agent.agent_id);
396
500
  if (agent.session_file) {
397
- await this.dependencies.sessions.trashSessionFile(agent.session_file);
501
+ await this.dependencies.sessions.trashSession(agent);
398
502
  result.trashed_session_files.push(agent.session_file);
399
503
  }
400
504
  this.agents.delete(agent.agent_id);
401
- this.importedMessages.delete(agent.agent_id);
505
+ this.pruneDeliveryStateForDeletedAgent(agent.agent_id);
506
+ this.pruneRecentMessageProjectionsForDeletedAgent(agent.agent_id);
402
507
  this.tombstones.add(agent.agent_id);
403
508
  result.deleted_agent_ids.push(agent.agent_id);
404
509
  result.tombstoned_agent_ids.push(agent.agent_id);
@@ -411,10 +516,7 @@ export class MinimalSubagentsCoordinator {
411
516
  failedAncestors.add(agent.agent_id);
412
517
  if (runtime && agent.session_file) {
413
518
  try {
414
- this.runtimes.set(
415
- agent.agent_id,
416
- await this.dependencies.sessions.restoreRuntime(agent),
417
- );
519
+ this.runtimes.set(agent.agent_id, await this.dependencies.sessions.openRuntime(agent));
418
520
  } catch (restoreError) {
419
521
  agent.availability = "unavailable";
420
522
  agent.unavailable_reason = `Deletion recovery failed: ${
@@ -433,26 +535,46 @@ export class MinimalSubagentsCoordinator {
433
535
 
434
536
  /** Restore non-deleted descendants, interrupt unfinished work, and reconcile pending successful output. */
435
537
  async restore(snapshot: RegistrySnapshot): Promise<void> {
436
- for (const runtime of this.runtimes.values()) runtime.dispose();
538
+ const restoreEpoch = ++this.lifecycleEpoch;
539
+ this.rejectAllWaiters(
540
+ new Error("Minimal subagents wait cancelled because the session branch changed"),
541
+ );
542
+ const abandonedRuntimes = [...this.runtimes.values()];
437
543
  this.agents.clear();
544
+ this.deliveryLedger = createDeliveryLedger();
545
+ this.pendingParentMessages.clear();
546
+ this.releaseAllRecipientIdleWaiters();
547
+ this.recipientQueues.clear();
548
+ this.backgroundOperations.clear();
549
+ await Promise.allSettled(
550
+ abandonedRuntimes.map((runtime) => (runtime.isRunning ? runtime.abort() : Promise.resolve())),
551
+ );
552
+ for (const runtime of abandonedRuntimes) runtime.dispose();
438
553
  this.runtimes.clear();
439
554
  this.runtimeInitializations.clear();
440
- this.importedMessages.clear();
441
555
  this.pendingAgentIds.clear();
442
556
  this.tombstones.clear();
443
- this.deliveries.clear();
557
+ this.deliveryLedger = createDeliveryLedger();
444
558
  this.waiters.clear();
559
+ this.pendingParentMessages.clear();
560
+ this.waitHandedDeliveryIds.clear();
561
+ this.releaseAllRecipientIdleWaiters();
562
+ this.recipientQueues.clear();
563
+ this.backgroundOperations.clear();
564
+ this.automaticDeliveryKeys.clear();
565
+ this.automaticCoordinationDeliveryIds.clear();
566
+ this.automaticDeliveryClaimWaiters.clear();
567
+ this.deliveryLedger = createDeliveryLedger({
568
+ deliveries: snapshot.deliveries,
569
+ coordination_deliveries: snapshot.coordination_deliveries,
570
+ wait_claimed_turns: snapshot.wait_claimed_turns,
571
+ next_delivery_sequence: snapshot.next_delivery_sequence,
572
+ });
445
573
  this.acceptingOperations = true;
446
574
  this.shutdownPromise = undefined;
447
575
 
448
576
  for (const agent of snapshot.agents) this.agents.set(agent.agent_id, structuredClone(agent));
449
577
  for (const tombstone of snapshot.tombstones) this.tombstones.add(tombstone);
450
- for (const delivery of snapshot.deliveries) {
451
- this.deliveries.set(
452
- agentDeliveryKey(delivery.source_agent_id, delivery.source_turn_id),
453
- structuredClone(delivery),
454
- );
455
- }
456
578
 
457
579
  for (const agent of this.agents.values()) {
458
580
  if (agent.active_turn_id) {
@@ -471,28 +593,41 @@ export class MinimalSubagentsCoordinator {
471
593
  });
472
594
  }
473
595
  const previousAvailability = agent.availability;
474
- const missing = agent.clone_error
475
- ? [agent.clone_error]
476
- : await this.dependencies.sessions.resolveRestorationMissingDependencies(agent);
477
- if (missing.length > 0 || !agent.session_file) {
478
- agent.availability = "unavailable";
479
- if (previousAvailability !== "unavailable")
480
- agent.latest_activity_at = this.now().toISOString();
481
- agent.missing_dependencies = missing.length > 0 ? missing : agent.missing_dependencies;
482
- agent.unavailable_reason =
483
- agent.clone_error ??
484
- (missing.length > 0
485
- ? `Missing dependencies: ${missing.join(", ")}`
486
- : (agent.unavailable_reason ?? `No persistent session exists for ${agent.agent_id}`));
487
- this.dependencies.notify?.({
488
- type: "unavailable",
489
- agentId: agent.agent_id,
490
- message: `${agent.agent_id} unavailable: ${agent.unavailable_reason}`,
491
- });
492
- continue;
493
- }
494
596
  try {
495
- this.runtimes.set(agent.agent_id, await this.dependencies.sessions.restoreRuntime(agent));
597
+ const missing = agent.clone_error
598
+ ? [agent.clone_error]
599
+ : await this.dependencies.sessions.resolveRestorationMissingDependencies(agent);
600
+ if (restoreEpoch !== this.lifecycleEpoch) return;
601
+ if (missing.length > 0 || !agent.session_file) {
602
+ agent.availability = "unavailable";
603
+ if (previousAvailability !== "unavailable")
604
+ agent.latest_activity_at = this.now().toISOString();
605
+ agent.missing_dependencies = missing.length > 0 ? missing : agent.missing_dependencies;
606
+ agent.unavailable_reason =
607
+ agent.clone_error ??
608
+ (missing.length > 0
609
+ ? `Missing dependencies: ${missing.join(", ")}`
610
+ : (agent.unavailable_reason ?? `No persistent session exists for ${agent.agent_id}`));
611
+ this.dependencies.notify?.({
612
+ type: "unavailable",
613
+ agentId: agent.agent_id,
614
+ message: `${agent.agent_id} unavailable: ${agent.unavailable_reason}`,
615
+ });
616
+ continue;
617
+ }
618
+ const runtime = await this.dependencies.sessions.openRuntime(agent);
619
+ if (restoreEpoch !== this.lifecycleEpoch) {
620
+ runtime.dispose();
621
+ return;
622
+ }
623
+ if (!runtime.sessionLeafId) {
624
+ runtime.dispose();
625
+ throw new Error(
626
+ `Minimal subagents session restoration: no selected session leaf for ${agent.agent_id}`,
627
+ );
628
+ }
629
+ this.runtimes.set(agent.agent_id, runtime);
630
+ agent.session_leaf_id = runtime.sessionLeafId;
496
631
  agent.availability = "available";
497
632
  if (previousAvailability !== "available")
498
633
  agent.latest_activity_at = this.now().toISOString();
@@ -504,6 +639,7 @@ export class MinimalSubagentsCoordinator {
504
639
  message: `Restored ${agent.agent_id}`,
505
640
  });
506
641
  } catch (error) {
642
+ if (restoreEpoch !== this.lifecycleEpoch) return;
507
643
  agent.availability = "unavailable";
508
644
  if (previousAvailability !== "unavailable")
509
645
  agent.latest_activity_at = this.now().toISOString();
@@ -515,7 +651,7 @@ export class MinimalSubagentsCoordinator {
515
651
  });
516
652
  }
517
653
  }
518
- await this.reconcileDeliveries(true);
654
+ if (restoreEpoch === this.lifecycleEpoch) await this.reconcileDeliveries(true);
519
655
  }
520
656
 
521
657
  /** Schedule delivery reconciliation as coordinator-owned work drained during shutdown. */
@@ -525,8 +661,51 @@ export class MinimalSubagentsCoordinator {
525
661
 
526
662
  /** Reconcile durable destination evidence and replay only successful undelivered output. */
527
663
  async reconcileDeliveries(replayMissing = false): Promise<void> {
528
- for (const delivery of this.deliveries.values()) {
529
- if (delivery.settled) continue;
664
+ const pendingItems = [
665
+ ...this.deliveryLedger.coordinationDeliveries.map((delivery) => ({
666
+ kind: "coordination" as const,
667
+ sequence: delivery.sequence,
668
+ delivery,
669
+ })),
670
+ ...this.deliveryLedger.terminalDeliveries.map((delivery) => ({
671
+ kind: "terminal" as const,
672
+ sequence: delivery.sequence ?? Number.MAX_SAFE_INTEGER,
673
+ delivery,
674
+ })),
675
+ ].sort((left, right) => left.sequence - right.sequence);
676
+ const scheduled: Promise<void>[] = [];
677
+
678
+ for (const item of pendingItems) {
679
+ if (item.kind === "coordination") {
680
+ const delivery = item.delivery;
681
+ if (this.hasCoordinationDeliveryEvidence(delivery)) {
682
+ this.settleCoordinationDelivery(delivery);
683
+ continue;
684
+ }
685
+ if (!replayMissing || delivery.path === "wait") continue;
686
+ const source = this.agents.get(delivery.message.details.source_agent_id);
687
+ if (source?.parent_id === delivery.destination_agent_id) {
688
+ const key = agentDeliveryKey(
689
+ delivery.message.details.source_agent_id,
690
+ delivery.message.details.source_turn_id,
691
+ );
692
+ const alreadyQueued = this.pendingParentMessages
693
+ .get(key)
694
+ ?.some((pending) => pending.deliveryId === delivery.delivery_id);
695
+ if (!alreadyQueued && !this.waitHandedDeliveryIds.has(delivery.delivery_id)) {
696
+ this.queuePendingParentMessage(
697
+ delivery.destination_agent_id,
698
+ delivery.message,
699
+ delivery,
700
+ );
701
+ }
702
+ } else {
703
+ scheduled.push(this.replayCoordinationDelivery(delivery));
704
+ }
705
+ continue;
706
+ }
707
+
708
+ const delivery = item.delivery;
530
709
  const agent = this.agents.get(delivery.source_agent_id);
531
710
  const result = delivery.result ?? agent?.latest_result;
532
711
  if (!result || result.status !== "completed" || result.turn_id !== delivery.source_turn_id)
@@ -535,25 +714,24 @@ export class MinimalSubagentsCoordinator {
535
714
  this.settleDelivery(delivery);
536
715
  continue;
537
716
  }
538
- if (!replayMissing) continue;
539
- delivery.path = "message";
540
- this.dependencies.registry.append(
541
- createRegistryEvent(this.dependencies.registry.rootSessionId, "delivery-pending", {
542
- delivery,
543
- }),
544
- );
545
- await this.deliverAutomaticResult(result, delivery);
717
+ if (!replayMissing || delivery.path === "wait") continue;
718
+ scheduled.push(this.deliverAutomaticResult(result, delivery));
546
719
  }
720
+ await Promise.allSettled(scheduled);
721
+ }
722
+
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();
547
728
  }
548
729
 
549
730
  /** Clone complete child leaves for root fork ownership without ever sharing source session paths. */
550
731
  async prepareFork(sourceRootSessionFile: string): Promise<ForkSnapshot> {
551
- this.acceptingOperations = false;
552
732
  const activeRootChildren = this.childrenOf("root");
553
733
  for (const child of activeRootChildren) await this.cancelDuringShutdown(child.agent_id);
554
- await Promise.allSettled(this.runtimeInitializations.values());
555
- await Promise.allSettled(this.backgroundOperations);
556
- await Promise.allSettled(this.recipientQueues.values());
734
+ await this.waitForSettledOperations();
557
735
  const forkAgents: PersistedAgent[] = [];
558
736
  const failedSubtrees = new Set<string>();
559
737
 
@@ -569,10 +747,16 @@ export class MinimalSubagentsCoordinator {
569
747
  }
570
748
  try {
571
749
  const clone = await this.dependencies.sessions.cloneSession(agent);
750
+ if (!clone.sessionLeafId) {
751
+ throw new Error(
752
+ `Minimal subagents fork clone: no selected session leaf for ${agent.agent_id}`,
753
+ );
754
+ }
572
755
  forkAgents.push({
573
756
  ...structuredClone(agent),
574
757
  session_file: clone.sessionFile,
575
758
  session_id: clone.sessionId,
759
+ session_leaf_id: clone.sessionLeafId,
576
760
  active_turn_id: undefined,
577
761
  active_turn_started_at: undefined,
578
762
  });
@@ -588,11 +772,12 @@ export class MinimalSubagentsCoordinator {
588
772
  }
589
773
  }
590
774
 
775
+ const snapshot = this.snapshot();
591
776
  return {
777
+ ...snapshot,
592
778
  source_root_session_file: sourceRootSessionFile,
779
+ source_root_session_id: this.dependencies.registry.rootSessionId,
593
780
  agents: forkAgents,
594
- tombstones: [...this.tombstones],
595
- deliveries: [...this.deliveries.values()].map((delivery) => structuredClone(delivery)),
596
781
  };
597
782
  }
598
783
 
@@ -600,6 +785,7 @@ export class MinimalSubagentsCoordinator {
600
785
  shutdown(): Promise<void> {
601
786
  if (this.shutdownPromise) return this.shutdownPromise;
602
787
  this.acceptingOperations = false;
788
+ this.releaseAllRecipientIdleWaiters();
603
789
  this.shutdownPromise = this.finishShutdown();
604
790
  return this.shutdownPromise;
605
791
  }
@@ -684,7 +870,7 @@ export class MinimalSubagentsCoordinator {
684
870
  return;
685
871
  }
686
872
  const outcome = await runtime.runPrompt(task, compact, callerModel, callerThinkingLevel);
687
- if (agent.active_turn_id !== turnId) return;
873
+ if (this.agents.get(agentId) !== agent || agent.active_turn_id !== turnId) return;
688
874
  this.settleTurn(agent, turnId, terminalTurnResult(agentId, turnId, outcome));
689
875
  } catch (error) {
690
876
  if (this.agents.get(agentId) !== agent || agent.active_turn_id !== turnId) return;
@@ -708,19 +894,14 @@ export class MinimalSubagentsCoordinator {
708
894
  new Error(agent.clone_error ?? `No persistent session exists for ${agent.agent_id}`),
709
895
  );
710
896
  }
711
- const importedMessages = this.importedMessages.get(agent.agent_id);
712
- const initialization = (
713
- importedMessages
714
- ? this.dependencies.sessions.createRuntime({ agent, importedMessages })
715
- : this.dependencies.sessions.restoreRuntime(agent)
716
- )
897
+ const initialization = this.dependencies.sessions
898
+ .openRuntime(agent)
717
899
  .then((runtime) => {
718
900
  if (this.agents.get(agent.agent_id) !== agent) {
719
901
  runtime.dispose();
720
902
  throw new Error(`Minimal subagents runtime replaced while opening ${agent.agent_id}`);
721
903
  }
722
904
  this.runtimes.set(agent.agent_id, runtime);
723
- this.importedMessages.delete(agent.agent_id);
724
905
  return runtime;
725
906
  })
726
907
  .finally(() => {
@@ -733,6 +914,7 @@ export class MinimalSubagentsCoordinator {
733
914
  }
734
915
 
735
916
  private beginTurn(agent: PersistedAgent): TurnId {
917
+ // SAFETY: The generated value embeds the canonical agent ID and a fresh turn UUID before branding.
736
918
  const turnId = `${agent.agent_id}:turn-${randomUUID()}` as TurnId;
737
919
  const startedAt = this.now().toISOString();
738
920
  agent.active_turn_id = turnId;
@@ -764,34 +946,44 @@ export class MinimalSubagentsCoordinator {
764
946
  agent.active_turn_started_at = undefined;
765
947
  agent.latest_activity_at = settledAt.toISOString();
766
948
  agent.latest_result = structuredClone(result);
949
+ const runtimeLeafId = this.runtimes.get(agent.agent_id)?.sessionLeafId;
950
+ if (runtimeLeafId) agent.session_leaf_id = runtimeLeafId;
767
951
  this.dependencies.registry.append(
768
- createRegistryEvent(this.dependencies.registry.rootSessionId, "turn-settled", { result }),
952
+ createRegistryEvent(this.dependencies.registry.rootSessionId, "turn-settled", {
953
+ result,
954
+ settled_at: settledAt.toISOString(),
955
+ session_leaf_id: runtimeLeafId,
956
+ }),
769
957
  );
770
958
  const waiterKey = agentDeliveryKey(agent.agent_id, turnId);
771
959
  const turnWaiters = this.waiters.get(waiterKey);
772
960
  const directParentWaited = [...(turnWaiters ?? [])].some(
773
961
  (waiter) => waiter.callerId === agent.parent_id,
774
962
  );
963
+ if (directParentWaited && result.status === "completed") {
964
+ this.claimDeliveryTurn(agent.agent_id, turnId);
965
+ }
966
+ const parentClaimedTurn =
967
+ directParentWaited ||
968
+ isDeliveryLedgerTurnClaimed(this.deliveryLedger, agent.agent_id, turnId);
775
969
  for (const waiter of turnWaiters ?? []) {
776
970
  this.removeWaiter(waiterKey, waiter);
777
- waiter.resolve(structuredClone(result));
971
+ waiter.resolve(terminalWaitResult(result));
778
972
  }
779
973
  if (result.status === "completed") {
780
- const delivery: PersistedDelivery = {
781
- source_agent_id: agent.agent_id,
782
- source_turn_id: turnId,
783
- destination_agent_id: agent.parent_id,
784
- path: directParentWaited ? "wait" : "message",
785
- settled: false,
786
- result: structuredClone(result),
787
- };
788
- this.deliveries.set(waiterKey, delivery);
974
+ const added = addTerminalDelivery(this.deliveryLedger, {
975
+ destinationAgentId: agent.parent_id,
976
+ path: parentClaimedTurn ? "wait" : "message",
977
+ result,
978
+ });
979
+ this.applyDeliveryLedgerTransition(added);
980
+ const delivery = added.delivery;
789
981
  this.dependencies.registry.append(
790
982
  createRegistryEvent(this.dependencies.registry.rootSessionId, "delivery-pending", {
791
983
  delivery,
792
984
  }),
793
985
  );
794
- if (!directParentWaited) {
986
+ if (!parentClaimedTurn) {
795
987
  this.trackBackgroundOperation(this.deliverAutomaticResult(result, delivery));
796
988
  }
797
989
  this.dependencies.notify?.({
@@ -806,22 +998,46 @@ export class MinimalSubagentsCoordinator {
806
998
  message: `${agent.agent_id} failed: ${result.error ?? "unknown error"}`,
807
999
  });
808
1000
  }
1001
+ if (result.status !== "completed") this.removeSettledEmptyTurnClaim(agent.agent_id, turnId);
1002
+ this.markRecipientIdle(agent.agent_id);
809
1003
  }
810
1004
 
811
1005
  private async deliverAutomaticResult(
812
1006
  result: TurnResult,
813
1007
  delivery: PersistedDelivery,
814
1008
  ): Promise<void> {
815
- const graceMs =
816
- this.dependencies.automaticDeliveryGraceMs ?? DEFAULT_AUTOMATIC_DELIVERY_GRACE_MS;
817
- if (graceMs > 0) await new Promise((resolve) => setTimeout(resolve, graceMs));
818
- if (delivery.settled) return;
819
- if (this.hasDeliveryEvidence(delivery)) {
820
- this.settleDelivery(delivery);
821
- return;
822
- }
1009
+ const deliveryKey = agentDeliveryKey(delivery.source_agent_id, delivery.source_turn_id);
1010
+ if (this.automaticDeliveryKeys.has(deliveryKey)) return;
1011
+ this.automaticDeliveryKeys.add(deliveryKey);
1012
+ const graceMs = this.deliveryGraceMs();
823
1013
  try {
824
1014
  await this.enqueueRecipientDelivery(delivery.destination_agent_id, async () => {
1015
+ if (delivery.destination_agent_id !== "root") {
1016
+ await this.ensureRuntime(
1017
+ this.requireUsableAgent(delivery.destination_agent_id, "message"),
1018
+ );
1019
+ if (!this.isTerminalDeliveryCurrent(delivery)) return;
1020
+ }
1021
+ if (graceMs > 0) await new Promise((resolve) => setTimeout(resolve, graceMs));
1022
+ if (!this.acceptingOperations || this.shouldStopAutomaticTerminalDelivery(delivery)) return;
1023
+ if (this.hasDeliveryEvidence(delivery)) {
1024
+ this.settleDelivery(delivery);
1025
+ return;
1026
+ }
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
+ if (!this.acceptingOperations || this.shouldStopAutomaticTerminalDelivery(delivery)) return;
1037
+ if (this.hasDeliveryEvidence(delivery)) {
1038
+ this.settleDelivery(delivery);
1039
+ return;
1040
+ }
825
1041
  const message: CoordinatorMessage = {
826
1042
  customType: "minimal-subagents.result",
827
1043
  content: result.output,
@@ -829,82 +1045,265 @@ export class MinimalSubagentsCoordinator {
829
1045
  source_agent_id: delivery.source_agent_id,
830
1046
  destination_agent_id: delivery.destination_agent_id,
831
1047
  source_turn_id: result.turn_id,
1048
+ message_id: `result:${delivery.source_agent_id}:${result.turn_id}`,
832
1049
  status: result.status,
833
1050
  elapsed_ms: result.elapsed_ms,
834
1051
  usage: result.usage,
835
1052
  },
836
1053
  };
837
- await this.deliverToRecipient(delivery.destination_agent_id, message);
1054
+ await this.deliverToRecipient(
1055
+ delivery.destination_agent_id,
1056
+ message,
1057
+ () => this.isTerminalDeliveryCurrent(delivery),
1058
+ true,
1059
+ );
838
1060
  });
839
1061
  } catch (error) {
840
- delivery.error = error instanceof Error ? error.message : String(error);
1062
+ if (!this.isTerminalDeliveryCurrent(delivery)) return;
1063
+ const deliveryError = error instanceof Error ? error.message : String(error);
1064
+ this.deliveryLedger = setTerminalDeliveryError(
1065
+ this.deliveryLedger,
1066
+ delivery.source_agent_id,
1067
+ delivery.source_turn_id,
1068
+ deliveryError,
1069
+ );
841
1070
  this.dependencies.registry.append(
842
1071
  createRegistryEvent(this.dependencies.registry.rootSessionId, "delivery-settled", {
843
1072
  source_agent_id: delivery.source_agent_id,
844
1073
  source_turn_id: delivery.source_turn_id,
845
- error: delivery.error,
1074
+ error: deliveryError,
846
1075
  }),
847
1076
  );
1077
+ } finally {
1078
+ this.automaticDeliveryKeys.delete(deliveryKey);
848
1079
  }
849
1080
  }
850
1081
 
851
- private async deliverExplicitMessage(
1082
+ private persistCoordinationDelivery(
1083
+ message: CoordinatorMessage,
1084
+ destinationAgentId: string,
1085
+ ): PersistedCoordinationDelivery {
1086
+ const added = addCoordinationDelivery(this.deliveryLedger, {
1087
+ destinationAgentId,
1088
+ message,
1089
+ });
1090
+ this.deliveryLedger = added.ledger;
1091
+ message.details.delivery_id = added.delivery.delivery_id;
1092
+ this.persistCoordinationDeliveryState(added.delivery);
1093
+ return added.delivery;
1094
+ }
1095
+
1096
+ private persistCoordinationDeliveryState(delivery: PersistedCoordinationDelivery): void {
1097
+ this.dependencies.registry.append(
1098
+ createRegistryEvent(
1099
+ this.dependencies.registry.rootSessionId,
1100
+ "coordination-delivery-pending",
1101
+ { delivery },
1102
+ ),
1103
+ );
1104
+ }
1105
+
1106
+ private settleCoordinationDelivery(delivery: PersistedCoordinationDelivery): void {
1107
+ this.deliveryLedger = settleCoordinationDelivery(
1108
+ this.deliveryLedger,
1109
+ delivery.delivery_id,
1110
+ ).ledger;
1111
+ this.waitHandedDeliveryIds.delete(delivery.delivery_id);
1112
+ this.dependencies.registry.append(
1113
+ createRegistryEvent(
1114
+ this.dependencies.registry.rootSessionId,
1115
+ "coordination-delivery-settled",
1116
+ { delivery_id: delivery.delivery_id },
1117
+ ),
1118
+ );
1119
+ this.removeSettledEmptyTurnClaim(
1120
+ delivery.message.details.source_agent_id,
1121
+ delivery.message.details.source_turn_id,
1122
+ );
1123
+ }
1124
+
1125
+ private removeSettledEmptyTurnClaim(sourceAgentId: string, sourceTurnId: string): void {
1126
+ const transition = releaseEmptyDeliveryLedgerTurn(
1127
+ this.deliveryLedger,
1128
+ sourceAgentId,
1129
+ sourceTurnId,
1130
+ this.agents.get(sourceAgentId)?.active_turn_id === sourceTurnId,
1131
+ );
1132
+ this.deliveryLedger = transition.ledger;
1133
+ if (transition.changed) {
1134
+ this.dependencies.registry.append(
1135
+ createRegistryEvent(this.dependencies.registry.rootSessionId, "delivery-turn-released", {
1136
+ source_agent_id: sourceAgentId,
1137
+ source_turn_id: sourceTurnId,
1138
+ }),
1139
+ );
1140
+ }
1141
+ }
1142
+
1143
+ private claimDeliveryTurn(sourceAgentId: string, sourceTurnId: string): void {
1144
+ const transition = claimDeliveryLedgerTurn(this.deliveryLedger, sourceAgentId, sourceTurnId);
1145
+ this.deliveryLedger = transition.ledger;
1146
+ if (!transition.changed) return;
1147
+ this.dependencies.registry.append(
1148
+ createRegistryEvent(this.dependencies.registry.rootSessionId, "delivery-turn-claimed", {
1149
+ source_agent_id: sourceAgentId,
1150
+ source_turn_id: sourceTurnId,
1151
+ }),
1152
+ );
1153
+ }
1154
+
1155
+ private selectObservableTurnId(
1156
+ sourceAgentId: string,
1157
+ destinationAgentId: string,
1158
+ ): string | undefined {
1159
+ const agent = this.agents.get(sourceAgentId);
1160
+ return selectObservableDeliveryTurn(this.deliveryLedger, {
1161
+ sourceAgentId,
1162
+ destinationAgentId,
1163
+ waitHandedDeliveryIds: this.waitHandedDeliveryIds,
1164
+ activeTurnId: agent?.active_turn_id,
1165
+ latestResultTurnId: agent?.latest_result?.turn_id,
1166
+ });
1167
+ }
1168
+
1169
+ private createExplicitMessage(
852
1170
  callerId: string,
853
1171
  targetId: string,
854
1172
  sourceTurnId: string,
1173
+ messageId: string,
855
1174
  content: string,
856
- ): Promise<void> {
857
- const message: CoordinatorMessage = {
1175
+ ): CoordinatorMessage {
1176
+ return {
858
1177
  customType: "minimal-subagents.message",
859
1178
  content,
860
1179
  details: {
861
1180
  source_agent_id: callerId,
862
1181
  destination_agent_id: targetId,
863
1182
  source_turn_id: sourceTurnId,
1183
+ message_id: messageId,
864
1184
  },
865
1185
  };
1186
+ }
1187
+
1188
+ private recordRecentMessage(targetId: string, message: CoordinatorMessage): void {
866
1189
  if (targetId !== "root") {
867
1190
  const target = this.requireUsableAgent(targetId, "message");
868
- target.recent_messages.push({
869
- source_agent_id: callerId,
870
- turn_id: sourceTurnId,
871
- content,
872
- });
1191
+ const recentMessage = {
1192
+ source_agent_id: message.details.source_agent_id,
1193
+ turn_id: message.details.source_turn_id,
1194
+ content: message.content,
1195
+ };
1196
+ target.recent_messages.push(recentMessage);
873
1197
  if (target.recent_messages.length > RECENT_MESSAGE_LIMIT) target.recent_messages.shift();
1198
+ const recordedAt = this.now().toISOString();
1199
+ target.latest_activity_at = recordedAt;
1200
+ this.dependencies.registry.append(
1201
+ createRegistryEvent(
1202
+ this.dependencies.registry.rootSessionId,
1203
+ "agent-message-recorded",
1204
+ {
1205
+ agent_id: target.agent_id,
1206
+ message: recentMessage,
1207
+ recorded_at: recordedAt,
1208
+ },
1209
+ recordedAt,
1210
+ ),
1211
+ );
1212
+ }
1213
+ }
1214
+
1215
+ private async replayCoordinationDelivery(delivery: PersistedCoordinationDelivery): Promise<void> {
1216
+ if (this.automaticCoordinationDeliveryIds.has(delivery.delivery_id)) return;
1217
+ this.automaticCoordinationDeliveryIds.add(delivery.delivery_id);
1218
+ try {
1219
+ await this.enqueueRecipientDelivery(delivery.destination_agent_id, async () => {
1220
+ if (this.hasCoordinationDeliveryEvidence(delivery)) {
1221
+ this.settleCoordinationDelivery(delivery);
1222
+ return;
1223
+ }
1224
+ this.waitHandedDeliveryIds.add(delivery.delivery_id);
1225
+ await this.deliverToRecipient(delivery.destination_agent_id, delivery.message, () =>
1226
+ this.isCoordinationDeliveryCurrent(delivery),
1227
+ );
1228
+ });
1229
+ } catch (error) {
1230
+ this.waitHandedDeliveryIds.delete(delivery.delivery_id);
1231
+ if (!this.isCoordinationDeliveryCurrent(delivery)) return;
1232
+ const deliveryError = error instanceof Error ? error.message : String(error);
1233
+ this.deliveryLedger = setCoordinationDeliveryError(
1234
+ this.deliveryLedger,
1235
+ delivery.delivery_id,
1236
+ deliveryError,
1237
+ );
1238
+ const current = findCoordinationDelivery(this.deliveryLedger, delivery.delivery_id);
1239
+ if (current) this.persistCoordinationDeliveryState(current);
1240
+ } finally {
1241
+ this.automaticCoordinationDeliveryIds.delete(delivery.delivery_id);
874
1242
  }
875
- await this.deliverToRecipient(targetId, message);
876
1243
  }
877
1244
 
878
- private async deliverToRecipient(targetId: string, message: CoordinatorMessage): Promise<void> {
1245
+ private async deliverExplicitMessage(
1246
+ message: CoordinatorMessage,
1247
+ targetId: string,
1248
+ delivery: PersistedCoordinationDelivery,
1249
+ ): Promise<AgentMessageDisposition> {
1250
+ this.waitHandedDeliveryIds.add(delivery.delivery_id);
1251
+ try {
1252
+ await this.deliverToRecipient(targetId, message, () =>
1253
+ this.isCoordinationDeliveryCurrent(delivery),
1254
+ );
1255
+ return "queued";
1256
+ } catch (error) {
1257
+ this.waitHandedDeliveryIds.delete(delivery.delivery_id);
1258
+ throw error;
1259
+ }
1260
+ }
1261
+
1262
+ private async deliverToRecipient(
1263
+ targetId: string,
1264
+ message: CoordinatorMessage,
1265
+ isCurrentDelivery: () => boolean = () => true,
1266
+ requireIdleRecipient = false,
1267
+ ): Promise<void> {
1268
+ if (!isCurrentDelivery()) {
1269
+ throw new Error("Minimal subagents delivery abandoned after session branch change");
1270
+ }
879
1271
  if (!this.acceptingOperations) {
880
1272
  throw new Error("Minimal subagents delivery stopped during coordinator shutdown");
881
1273
  }
882
1274
  if (targetId === "root") {
883
- await this.dependencies.root.steerCoordinatorMessage(message);
1275
+ await this.dependencies.root.queueCoordinatorMessage(addCoordinatorMessageEnvelope(message));
884
1276
  return;
885
1277
  }
886
1278
  const target = this.requireUsableAgent(targetId, "message");
887
- const runtime = await this.ensureRuntime(target);
1279
+ const runtime = this.runtimes.get(targetId) ?? (await this.ensureRuntime(target));
1280
+ if (!isCurrentDelivery()) {
1281
+ throw new Error("Minimal subagents delivery abandoned after session branch change");
1282
+ }
1283
+ 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
+ }
888
1287
  if (target.active_turn_id || runtime.isRunning) {
889
- await runtime.steerCoordinatorMessage(message);
1288
+ await runtime.queueCoordinatorMessage(visibleMessage);
890
1289
  return;
891
1290
  }
892
1291
  const turnId = this.beginTurn(target);
893
1292
  const runMessage = runtime
894
- .runMessage(message)
1293
+ .runMessage(visibleMessage)
895
1294
  .then((outcome) => {
896
- if (target.active_turn_id === turnId) {
1295
+ if (this.agents.get(target.agent_id) === target && target.active_turn_id === turnId) {
897
1296
  this.settleTurn(target, turnId, terminalTurnResult(target.agent_id, turnId, outcome));
898
1297
  }
899
1298
  })
900
- .catch((error: unknown) => {
901
- if (target.active_turn_id === turnId) {
1299
+ .catch((cause) => {
1300
+ if (this.agents.get(target.agent_id) === target && target.active_turn_id === turnId) {
902
1301
  this.settleTurn(target, turnId, {
903
1302
  agent_id: target.agent_id,
904
1303
  turn_id: turnId,
905
1304
  status: "failed",
906
1305
  output: "",
907
- error: error instanceof Error ? error.message : String(error),
1306
+ error: cause instanceof Error ? cause.message : String(cause),
908
1307
  });
909
1308
  }
910
1309
  });
@@ -917,12 +1316,17 @@ export class MinimalSubagentsCoordinator {
917
1316
  void operation.then(cleanup, cleanup);
918
1317
  }
919
1318
 
920
- private enqueueRecipientDelivery(
921
- targetId: string,
922
- operation: () => Promise<void>,
923
- ): Promise<void> {
1319
+ private enqueueRecipientDelivery<T>(targetId: string, operation: () => Promise<T>): Promise<T> {
1320
+ const epoch = this.lifecycleEpoch;
924
1321
  const previous = this.recipientQueues.get(targetId) ?? Promise.resolve();
925
- const next = previous.catch(() => undefined).then(operation);
1322
+ const next = previous
1323
+ .catch(() => undefined)
1324
+ .then(() => {
1325
+ if (epoch !== this.lifecycleEpoch) {
1326
+ throw new Error("Minimal subagents delivery abandoned after session branch change");
1327
+ }
1328
+ return operation();
1329
+ });
926
1330
  this.recipientQueues.set(targetId, next);
927
1331
  const cleanup = () => {
928
1332
  if (this.recipientQueues.get(targetId) === next) this.recipientQueues.delete(targetId);
@@ -960,6 +1364,46 @@ export class MinimalSubagentsCoordinator {
960
1364
  return targetId;
961
1365
  }
962
1366
 
1367
+ private applyDeliveryLedgerTransition(transition: DeliveryLedgerTransition): void {
1368
+ this.deliveryLedger = transition.ledger;
1369
+ for (const delivery of transition.prunedTerminalDeliveries) {
1370
+ const key = agentDeliveryKey(delivery.source_agent_id, delivery.source_turn_id);
1371
+ this.releaseAutomaticDeliveryClaimWaiters(key);
1372
+ this.dependencies.registry.append(
1373
+ createRegistryEvent(this.dependencies.registry.rootSessionId, "delivery-pruned", {
1374
+ source_agent_id: delivery.source_agent_id,
1375
+ source_turn_id: delivery.source_turn_id,
1376
+ reason: "retention-limit",
1377
+ }),
1378
+ );
1379
+ }
1380
+ }
1381
+
1382
+ private isTerminalDeliveryCurrent(delivery: PersistedDelivery): boolean {
1383
+ const current = findTerminalDelivery(
1384
+ this.deliveryLedger,
1385
+ delivery.source_agent_id,
1386
+ delivery.source_turn_id,
1387
+ );
1388
+ return current?.sequence === delivery.sequence;
1389
+ }
1390
+
1391
+ private shouldStopAutomaticTerminalDelivery(delivery: PersistedDelivery): boolean {
1392
+ const current = findTerminalDelivery(
1393
+ this.deliveryLedger,
1394
+ delivery.source_agent_id,
1395
+ delivery.source_turn_id,
1396
+ );
1397
+ return current === undefined || current.path === "wait";
1398
+ }
1399
+
1400
+ private isCoordinationDeliveryCurrent(delivery: PersistedCoordinationDelivery): boolean {
1401
+ return (
1402
+ findCoordinationDelivery(this.deliveryLedger, delivery.delivery_id)?.sequence ===
1403
+ delivery.sequence
1404
+ );
1405
+ }
1406
+
963
1407
  private hasDeliveryEvidence(delivery: PersistedDelivery): boolean {
964
1408
  if (delivery.destination_agent_id === "root") {
965
1409
  return this.dependencies.root.hasDeliveryEvidence(
@@ -974,15 +1418,36 @@ export class MinimalSubagentsCoordinator {
974
1418
  );
975
1419
  }
976
1420
 
1421
+ private hasCoordinationDeliveryEvidence(delivery: PersistedCoordinationDelivery): boolean {
1422
+ const sourceAgentId = delivery.message.details.source_agent_id;
1423
+ const sourceTurnId = delivery.message.details.source_turn_id;
1424
+ if (delivery.destination_agent_id === "root") {
1425
+ return this.dependencies.root.hasDeliveryEvidence(
1426
+ sourceAgentId,
1427
+ sourceTurnId,
1428
+ delivery.delivery_id,
1429
+ );
1430
+ }
1431
+ return (
1432
+ this.runtimes
1433
+ .get(delivery.destination_agent_id)
1434
+ ?.hasDeliveryEvidence(sourceAgentId, sourceTurnId, delivery.delivery_id) ?? false
1435
+ );
1436
+ }
1437
+
977
1438
  private settleDelivery(delivery: PersistedDelivery): void {
978
- delivery.settled = true;
979
- delivery.error = undefined;
1439
+ this.deliveryLedger = settleTerminalDelivery(
1440
+ this.deliveryLedger,
1441
+ delivery.source_agent_id,
1442
+ delivery.source_turn_id,
1443
+ ).ledger;
980
1444
  this.dependencies.registry.append(
981
1445
  createRegistryEvent(this.dependencies.registry.rootSessionId, "delivery-settled", {
982
1446
  source_agent_id: delivery.source_agent_id,
983
1447
  source_turn_id: delivery.source_turn_id,
984
1448
  }),
985
1449
  );
1450
+ this.removeSettledEmptyTurnClaim(delivery.source_agent_id, delivery.source_turn_id);
986
1451
  }
987
1452
 
988
1453
  private buildAgentSummary(agent: PersistedAgent, includeDescendants = true): AgentSummary {
@@ -1027,7 +1492,7 @@ export class MinimalSubagentsCoordinator {
1027
1492
  return {
1028
1493
  ...summary,
1029
1494
  session_file: agent.session_file,
1030
- launch_contract: structuredClone(agent.launch_contract) as unknown as Record<string, unknown>,
1495
+ launch_contract: structuredClone(agent.launch_contract),
1031
1496
  capability_ceiling: [...agent.capability_ceiling],
1032
1497
  spawn_entry_id: agent.spawn_entry_id,
1033
1498
  recent_messages: structuredClone(agent.recent_messages),
@@ -1038,6 +1503,47 @@ export class MinimalSubagentsCoordinator {
1038
1503
  };
1039
1504
  }
1040
1505
 
1506
+ private pruneDeliveryStateForDeletedAgent(agentId: string): void {
1507
+ const previousTerminalDeliveries = this.deliveryLedger.terminalDeliveries;
1508
+ const previousCoordinationDeliveries = this.deliveryLedger.coordinationDeliveries;
1509
+ 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
+ for (const delivery of previousCoordinationDeliveries) {
1518
+ if (!this.isCoordinationDeliveryCurrent(delivery)) {
1519
+ this.waitHandedDeliveryIds.delete(delivery.delivery_id);
1520
+ }
1521
+ }
1522
+ for (const [key, messages] of this.pendingParentMessages) {
1523
+ for (const pending of messages) {
1524
+ if (!findCoordinationDelivery(this.deliveryLedger, pending.deliveryId)) {
1525
+ pending.claimed = true;
1526
+ pending.releaseClaim();
1527
+ }
1528
+ }
1529
+ const retained = messages.filter((pending) =>
1530
+ findCoordinationDelivery(this.deliveryLedger, pending.deliveryId),
1531
+ );
1532
+ if (retained.length === 0) this.pendingParentMessages.delete(key);
1533
+ else this.pendingParentMessages.set(key, retained);
1534
+ }
1535
+ }
1536
+
1537
+ private pruneRecentMessageProjectionsForDeletedAgent(agentId: string): void {
1538
+ const belongsToDeletedSubtree = (sourceAgentId: string) =>
1539
+ sourceAgentId === agentId || sourceAgentId.startsWith(`${agentId}.`);
1540
+ for (const agent of this.agents.values()) {
1541
+ agent.recent_messages = agent.recent_messages.filter(
1542
+ (message) => !belongsToDeletedSubtree(message.source_agent_id),
1543
+ );
1544
+ }
1545
+ }
1546
+
1041
1547
  private descendantsOf(agentId: string): PersistedAgent[] {
1042
1548
  const descendants: PersistedAgent[] = [];
1043
1549
  const queue = this.childrenOf(agentId);
@@ -1125,6 +1631,7 @@ export class MinimalSubagentsCoordinator {
1125
1631
  ...structuredClone(agent),
1126
1632
  session_file: undefined,
1127
1633
  session_id: undefined,
1634
+ session_leaf_id: undefined,
1128
1635
  clone_error: cloneError,
1129
1636
  active_turn_id: undefined,
1130
1637
  active_turn_started_at: undefined,
@@ -1135,6 +1642,15 @@ export class MinimalSubagentsCoordinator {
1135
1642
  };
1136
1643
  }
1137
1644
 
1645
+ private rejectAllWaiters(error: Error): void {
1646
+ for (const [key, waiters] of this.waiters) {
1647
+ for (const waiter of waiters) {
1648
+ this.removeWaiter(key, waiter);
1649
+ waiter.reject(error);
1650
+ }
1651
+ }
1652
+ }
1653
+
1138
1654
  private removeWaiter(key: string, waiter: TurnWaiter): void {
1139
1655
  const turnWaiters = this.waiters.get(key);
1140
1656
  turnWaiters?.delete(waiter);
@@ -1145,6 +1661,286 @@ export class MinimalSubagentsCoordinator {
1145
1661
  }
1146
1662
  }
1147
1663
 
1664
+ private resolveWaiterWithMessage(
1665
+ message: CoordinatorMessage,
1666
+ delivery: PersistedCoordinationDelivery,
1667
+ ): boolean {
1668
+ const destinationAgentId = message.details.destination_agent_id;
1669
+ if (!destinationAgentId) return false;
1670
+ const key = agentDeliveryKey(message.details.source_agent_id, message.details.source_turn_id);
1671
+ const turnWaiters = this.waiters.get(key);
1672
+ const waiter = [...(turnWaiters ?? [])].find(
1673
+ (candidate) => candidate.callerId === destinationAgentId,
1674
+ );
1675
+ if (!waiter) return false;
1676
+ this.claimDeliveryTurn(message.details.source_agent_id, message.details.source_turn_id);
1677
+ this.deliveryLedger = setCoordinationDeliveryPath(
1678
+ this.deliveryLedger,
1679
+ delivery.delivery_id,
1680
+ "wait",
1681
+ );
1682
+ const currentDelivery = findCoordinationDelivery(this.deliveryLedger, delivery.delivery_id);
1683
+ if (currentDelivery) this.persistCoordinationDeliveryState(currentDelivery);
1684
+ this.waitHandedDeliveryIds.add(delivery.delivery_id);
1685
+ this.removeWaiter(key, waiter);
1686
+ waiter.resolve({
1687
+ event: "message",
1688
+ agent_id: message.details.source_agent_id,
1689
+ turn_id: message.details.source_turn_id,
1690
+ message_id: message.details.message_id,
1691
+ delivery_id: delivery.delivery_id,
1692
+ message: message.content,
1693
+ });
1694
+ return true;
1695
+ }
1696
+
1697
+ private claimPendingParentMessage(
1698
+ callerId: string,
1699
+ sourceAgentId: string,
1700
+ sourceTurnId: string,
1701
+ ): WaitResult | undefined {
1702
+ const key = agentDeliveryKey(sourceAgentId, sourceTurnId);
1703
+ const pendingMessages = this.pendingParentMessages.get(key);
1704
+ const index = pendingMessages?.findIndex(
1705
+ (pending) => pending.destinationAgentId === callerId && !pending.claimed,
1706
+ );
1707
+ if (index === undefined || index < 0 || !pendingMessages) {
1708
+ const retained = this.deliveryLedger.coordinationDeliveries
1709
+ .filter(
1710
+ (delivery) =>
1711
+ delivery.destination_agent_id === callerId &&
1712
+ delivery.message.details.source_agent_id === sourceAgentId &&
1713
+ delivery.message.details.source_turn_id === sourceTurnId &&
1714
+ !this.waitHandedDeliveryIds.has(delivery.delivery_id),
1715
+ )
1716
+ .sort((left, right) => left.sequence - right.sequence)[0];
1717
+ if (!retained) return undefined;
1718
+ this.claimDeliveryTurn(sourceAgentId, sourceTurnId);
1719
+ this.deliveryLedger = setCoordinationDeliveryPath(
1720
+ this.deliveryLedger,
1721
+ retained.delivery_id,
1722
+ "wait",
1723
+ );
1724
+ const currentDelivery = findCoordinationDelivery(this.deliveryLedger, retained.delivery_id);
1725
+ if (currentDelivery) this.persistCoordinationDeliveryState(currentDelivery);
1726
+ this.waitHandedDeliveryIds.add(retained.delivery_id);
1727
+ return {
1728
+ event: "message",
1729
+ agent_id: sourceAgentId,
1730
+ turn_id: sourceTurnId,
1731
+ message_id: retained.message.details.message_id,
1732
+ delivery_id: retained.delivery_id,
1733
+ message: retained.message.content,
1734
+ };
1735
+ }
1736
+ const pending = pendingMessages[index];
1737
+ if (!pending) return undefined;
1738
+ this.claimDeliveryTurn(sourceAgentId, sourceTurnId);
1739
+ const delivery = findCoordinationDelivery(this.deliveryLedger, pending.deliveryId);
1740
+ if (delivery) {
1741
+ this.deliveryLedger = setCoordinationDeliveryPath(
1742
+ this.deliveryLedger,
1743
+ delivery.delivery_id,
1744
+ "wait",
1745
+ );
1746
+ const currentDelivery = findCoordinationDelivery(this.deliveryLedger, delivery.delivery_id);
1747
+ if (currentDelivery) this.persistCoordinationDeliveryState(currentDelivery);
1748
+ this.waitHandedDeliveryIds.add(delivery.delivery_id);
1749
+ }
1750
+ const sourceResult = this.agents.get(sourceAgentId)?.latest_result;
1751
+ if (sourceResult?.turn_id === sourceTurnId) {
1752
+ this.setTerminalDeliveryPathToWait(callerId, sourceResult);
1753
+ }
1754
+ pending.claimed = true;
1755
+ pending.cancelGrace?.();
1756
+ pending.releaseClaim();
1757
+ pendingMessages.splice(index, 1);
1758
+ if (pendingMessages.length === 0) this.pendingParentMessages.delete(key);
1759
+ return {
1760
+ event: "message",
1761
+ agent_id: sourceAgentId,
1762
+ turn_id: sourceTurnId,
1763
+ message_id: pending.message.details.message_id,
1764
+ delivery_id: pending.deliveryId,
1765
+ message: pending.message.content,
1766
+ };
1767
+ }
1768
+
1769
+ private queuePendingParentMessage(
1770
+ targetId: string,
1771
+ message: CoordinatorMessage,
1772
+ delivery: PersistedCoordinationDelivery,
1773
+ ): void {
1774
+ const key = agentDeliveryKey(message.details.source_agent_id, message.details.source_turn_id);
1775
+ const turnClaimed = () =>
1776
+ isDeliveryLedgerTurnClaimed(
1777
+ this.deliveryLedger,
1778
+ message.details.source_agent_id,
1779
+ message.details.source_turn_id,
1780
+ );
1781
+ const { promise: claimPromise, resolve: releaseClaim } = Promise.withResolvers<void>();
1782
+ const pending: PendingParentMessage = {
1783
+ deliveryId: delivery.delivery_id,
1784
+ message,
1785
+ destinationAgentId: targetId,
1786
+ claimed: false,
1787
+ claimPromise,
1788
+ releaseClaim,
1789
+ };
1790
+ const pendingMessages = this.pendingParentMessages.get(key) ?? [];
1791
+ pendingMessages.push(pending);
1792
+ this.pendingParentMessages.set(key, pendingMessages);
1793
+
1794
+ if (
1795
+ isDeliveryLedgerTurnClaimed(
1796
+ this.deliveryLedger,
1797
+ message.details.source_agent_id,
1798
+ message.details.source_turn_id,
1799
+ )
1800
+ )
1801
+ return;
1802
+
1803
+ const operation = this.enqueueRecipientDelivery(targetId, async () => {
1804
+ if (targetId !== "root") {
1805
+ await this.ensureRuntime(this.requireUsableAgent(targetId, "message"));
1806
+ if (!this.isCoordinationDeliveryCurrent(delivery)) return;
1807
+ }
1808
+ const graceMs = this.deliveryGraceMs();
1809
+ const gracePromise = new Promise<void>((resolve) => {
1810
+ if (graceMs <= 0) {
1811
+ resolve();
1812
+ return;
1813
+ }
1814
+ const timer = setTimeout(resolve, graceMs);
1815
+ pending.cancelGrace = () => {
1816
+ clearTimeout(timer);
1817
+ resolve();
1818
+ };
1819
+ });
1820
+ await Promise.race([pending.claimPromise, gracePromise]);
1821
+ pending.cancelGrace?.();
1822
+ pending.cancelGrace = undefined;
1823
+ 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
+ if (!this.acceptingOperations || pending.claimed || turnClaimed()) return;
1831
+ this.removePendingParentMessage(key, pending);
1832
+ this.waitHandedDeliveryIds.add(delivery.delivery_id);
1833
+ await this.deliverToRecipient(
1834
+ targetId,
1835
+ message,
1836
+ () => this.isCoordinationDeliveryCurrent(delivery),
1837
+ true,
1838
+ );
1839
+ });
1840
+ void operation.catch((cause) => {
1841
+ this.removePendingParentMessage(key, pending);
1842
+ this.waitHandedDeliveryIds.delete(delivery.delivery_id);
1843
+ if (!this.isCoordinationDeliveryCurrent(delivery)) return;
1844
+ const deliveryError = cause instanceof Error ? cause.message : String(cause);
1845
+ this.deliveryLedger = setCoordinationDeliveryError(
1846
+ this.deliveryLedger,
1847
+ delivery.delivery_id,
1848
+ deliveryError,
1849
+ );
1850
+ const current = findCoordinationDelivery(this.deliveryLedger, delivery.delivery_id);
1851
+ if (current) this.persistCoordinationDeliveryState(current);
1852
+ this.dependencies.notify?.({
1853
+ type: "failure",
1854
+ agentId: message.details.source_agent_id,
1855
+ message: `Could not queue message ${message.details.message_id}: ${
1856
+ cause instanceof Error ? cause.message : String(cause)
1857
+ }`,
1858
+ });
1859
+ });
1860
+ }
1861
+
1862
+ private removePendingParentMessage(key: string, pending: PendingParentMessage): void {
1863
+ const pendingMessages = this.pendingParentMessages.get(key);
1864
+ if (!pendingMessages) return;
1865
+ const index = pendingMessages.indexOf(pending);
1866
+ if (index >= 0) pendingMessages.splice(index, 1);
1867
+ if (pendingMessages.length === 0) this.pendingParentMessages.delete(key);
1868
+ }
1869
+
1870
+ private claimTerminalDelivery(callerId: string, result: TurnResult): void {
1871
+ if (!findTerminalDelivery(this.deliveryLedger, result.agent_id, result.turn_id)) return;
1872
+ this.claimDeliveryTurn(result.agent_id, result.turn_id);
1873
+ this.setTerminalDeliveryPathToWait(callerId, result);
1874
+ }
1875
+
1876
+ private setTerminalDeliveryPathToWait(callerId: string, result: TurnResult): void {
1877
+ const key = agentDeliveryKey(result.agent_id, result.turn_id);
1878
+ const delivery = findTerminalDelivery(this.deliveryLedger, result.agent_id, result.turn_id);
1879
+ if (!delivery || delivery.destination_agent_id !== callerId || delivery.path === "wait") return;
1880
+ this.applyDeliveryLedgerTransition(
1881
+ setTerminalDeliveryPath(this.deliveryLedger, result.agent_id, result.turn_id, "wait"),
1882
+ );
1883
+ const retained = findTerminalDelivery(this.deliveryLedger, result.agent_id, result.turn_id);
1884
+ this.releaseAutomaticDeliveryClaimWaiters(key);
1885
+ if (!retained) return;
1886
+ this.dependencies.registry.append(
1887
+ createRegistryEvent(this.dependencies.registry.rootSessionId, "delivery-pending", {
1888
+ delivery: retained,
1889
+ }),
1890
+ );
1891
+ }
1892
+
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
+
1148
1944
  private async cancelDuringShutdown(agentId: string): Promise<void> {
1149
1945
  const target = this.agents.get(agentId);
1150
1946
  if (!target) return;
@@ -1154,6 +1950,7 @@ export class MinimalSubagentsCoordinator {
1154
1950
  const turnId = agent.active_turn_id;
1155
1951
  const runtime = this.runtimes.get(agent.agent_id);
1156
1952
  if (runtime) await runtime.abort();
1953
+ if (this.agents.get(agent.agent_id) !== agent) continue;
1157
1954
  this.settleTurn(agent, turnId, {
1158
1955
  agent_id: agent.agent_id,
1159
1956
  turn_id: turnId,
@@ -1168,6 +1965,7 @@ export class MinimalSubagentsCoordinator {
1168
1965
  const turnId = agent.active_turn_id;
1169
1966
  const runtime = this.runtimes.get(agent.agent_id);
1170
1967
  if (runtime) await runtime.abort();
1968
+ if (this.agents.get(agent.agent_id) !== agent) return undefined;
1171
1969
  this.settleTurn(agent, turnId, {
1172
1970
  agent_id: agent.agent_id,
1173
1971
  turn_id: turnId,
@@ -1230,4 +2028,8 @@ export class MinimalSubagentsCoordinator {
1230
2028
  private now(): Date {
1231
2029
  return this.dependencies.now?.() ?? new Date();
1232
2030
  }
2031
+
2032
+ private deliveryGraceMs(): number {
2033
+ return this.dependencies.automaticDeliveryGraceMs ?? DEFAULT_AUTOMATIC_DELIVERY_GRACE_MS;
2034
+ }
1233
2035
  }