zeitlich 0.2.37 → 0.2.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/README.md +18 -0
  2. package/dist/{activities-Bb-nAjwQ.d.ts → activities-Bmu7XnaG.d.ts} +4 -4
  3. package/dist/{activities-vkI4_3CC.d.cts → activities-ByBFLvm2.d.cts} +4 -4
  4. package/dist/adapter-id-BB-mmrts.d.cts +17 -0
  5. package/dist/adapter-id-BB-mmrts.d.ts +17 -0
  6. package/dist/adapter-id-CMwVrVqv.d.cts +17 -0
  7. package/dist/adapter-id-CMwVrVqv.d.ts +17 -0
  8. package/dist/adapter-id-CbY2zeSt.d.cts +17 -0
  9. package/dist/adapter-id-CbY2zeSt.d.ts +17 -0
  10. package/dist/adapters/sandbox/bedrock/index.cjs +3 -3
  11. package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -1
  12. package/dist/adapters/sandbox/bedrock/index.d.cts +6 -6
  13. package/dist/adapters/sandbox/bedrock/index.d.ts +6 -6
  14. package/dist/adapters/sandbox/bedrock/index.js +3 -3
  15. package/dist/adapters/sandbox/bedrock/index.js.map +1 -1
  16. package/dist/adapters/sandbox/bedrock/workflow.d.cts +2 -2
  17. package/dist/adapters/sandbox/bedrock/workflow.d.ts +2 -2
  18. package/dist/adapters/sandbox/daytona/index.cjs +3 -3
  19. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  20. package/dist/adapters/sandbox/daytona/index.d.cts +4 -4
  21. package/dist/adapters/sandbox/daytona/index.d.ts +4 -4
  22. package/dist/adapters/sandbox/daytona/index.js +3 -3
  23. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  24. package/dist/adapters/sandbox/daytona/workflow.d.cts +1 -1
  25. package/dist/adapters/sandbox/daytona/workflow.d.ts +1 -1
  26. package/dist/adapters/sandbox/e2b/index.cjs +26 -14
  27. package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
  28. package/dist/adapters/sandbox/e2b/index.d.cts +24 -4
  29. package/dist/adapters/sandbox/e2b/index.d.ts +24 -4
  30. package/dist/adapters/sandbox/e2b/index.js +26 -14
  31. package/dist/adapters/sandbox/e2b/index.js.map +1 -1
  32. package/dist/adapters/sandbox/e2b/workflow.d.cts +1 -1
  33. package/dist/adapters/sandbox/e2b/workflow.d.ts +1 -1
  34. package/dist/adapters/sandbox/inmemory/index.cjs +3 -3
  35. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  36. package/dist/adapters/sandbox/inmemory/index.d.cts +4 -4
  37. package/dist/adapters/sandbox/inmemory/index.d.ts +4 -4
  38. package/dist/adapters/sandbox/inmemory/index.js +3 -3
  39. package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
  40. package/dist/adapters/sandbox/inmemory/workflow.d.cts +1 -1
  41. package/dist/adapters/sandbox/inmemory/workflow.d.ts +1 -1
  42. package/dist/adapters/thread/anthropic/index.cjs +150 -13
  43. package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
  44. package/dist/adapters/thread/anthropic/index.d.cts +9 -8
  45. package/dist/adapters/thread/anthropic/index.d.ts +9 -8
  46. package/dist/adapters/thread/anthropic/index.js +150 -14
  47. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  48. package/dist/adapters/thread/anthropic/workflow.cjs +9 -3
  49. package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -1
  50. package/dist/adapters/thread/anthropic/workflow.d.cts +6 -5
  51. package/dist/adapters/thread/anthropic/workflow.d.ts +6 -5
  52. package/dist/adapters/thread/anthropic/workflow.js +9 -4
  53. package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
  54. package/dist/adapters/thread/google-genai/index.cjs +154 -13
  55. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  56. package/dist/adapters/thread/google-genai/index.d.cts +6 -5
  57. package/dist/adapters/thread/google-genai/index.d.ts +6 -5
  58. package/dist/adapters/thread/google-genai/index.js +154 -14
  59. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  60. package/dist/adapters/thread/google-genai/workflow.cjs +9 -3
  61. package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
  62. package/dist/adapters/thread/google-genai/workflow.d.cts +6 -5
  63. package/dist/adapters/thread/google-genai/workflow.d.ts +6 -5
  64. package/dist/adapters/thread/google-genai/workflow.js +9 -4
  65. package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
  66. package/dist/adapters/thread/index.cjs +16 -0
  67. package/dist/adapters/thread/index.cjs.map +1 -0
  68. package/dist/adapters/thread/index.d.cts +34 -0
  69. package/dist/adapters/thread/index.d.ts +34 -0
  70. package/dist/adapters/thread/index.js +12 -0
  71. package/dist/adapters/thread/index.js.map +1 -0
  72. package/dist/adapters/thread/langchain/index.cjs +149 -14
  73. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  74. package/dist/adapters/thread/langchain/index.d.cts +9 -8
  75. package/dist/adapters/thread/langchain/index.d.ts +9 -8
  76. package/dist/adapters/thread/langchain/index.js +149 -15
  77. package/dist/adapters/thread/langchain/index.js.map +1 -1
  78. package/dist/adapters/thread/langchain/workflow.cjs +9 -3
  79. package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
  80. package/dist/adapters/thread/langchain/workflow.d.cts +6 -5
  81. package/dist/adapters/thread/langchain/workflow.d.ts +6 -5
  82. package/dist/adapters/thread/langchain/workflow.js +9 -4
  83. package/dist/adapters/thread/langchain/workflow.js.map +1 -1
  84. package/dist/index.cjs +367 -59
  85. package/dist/index.cjs.map +1 -1
  86. package/dist/index.d.cts +11 -11
  87. package/dist/index.d.ts +11 -11
  88. package/dist/index.js +365 -61
  89. package/dist/index.js.map +1 -1
  90. package/dist/{proxy-DEtowJyd.d.cts → proxy-BAKzNGRq.d.cts} +1 -1
  91. package/dist/{proxy-0smGKvx8.d.ts → proxy-DO_MXbY4.d.ts} +1 -1
  92. package/dist/{thread-manager-C-C4pI2z.d.ts → thread-manager-CcRXasqs.d.ts} +2 -2
  93. package/dist/{thread-manager-D4vgzYrh.d.cts → thread-manager-ClwSaUnj.d.cts} +2 -2
  94. package/dist/{thread-manager-3fszQih4.d.ts → thread-manager-D-7lp1JK.d.ts} +2 -2
  95. package/dist/{thread-manager-CzYln2OC.d.cts → thread-manager-Y8Ucf0Tf.d.cts} +2 -2
  96. package/dist/{types-CPKDl-y_.d.ts → types-Bcbiq8iv.d.cts} +195 -22
  97. package/dist/{types-CNuWnvy9.d.ts → types-DAsQ21Rt.d.ts} +1 -1
  98. package/dist/{types-B37hKoWA.d.ts → types-DpHTX-iO.d.ts} +58 -1
  99. package/dist/{types-BO7Yju20.d.cts → types-Dt8-HBBT.d.ts} +195 -22
  100. package/dist/{types-D08CXPh8.d.cts → types-hFFi-Zd9.d.cts} +58 -1
  101. package/dist/{types-DWEUmYAJ.d.cts → types-lm8tMNJQ.d.cts} +1 -1
  102. package/dist/{types-tQL9njTu.d.cts → types-yx0LzPGn.d.cts} +21 -7
  103. package/dist/{types-tQL9njTu.d.ts → types-yx0LzPGn.d.ts} +21 -7
  104. package/dist/{workflow-CjXHbZZc.d.ts → workflow-Bmf9EtDW.d.ts} +83 -3
  105. package/dist/{workflow-Do_lzJpT.d.cts → workflow-Bx9utBwb.d.cts} +83 -3
  106. package/dist/workflow.cjs +266 -39
  107. package/dist/workflow.cjs.map +1 -1
  108. package/dist/workflow.d.cts +3 -3
  109. package/dist/workflow.d.ts +3 -3
  110. package/dist/workflow.js +264 -41
  111. package/dist/workflow.js.map +1 -1
  112. package/package.json +12 -2
  113. package/src/adapters/sandbox/bedrock/index.ts +12 -3
  114. package/src/adapters/sandbox/daytona/index.ts +12 -3
  115. package/src/adapters/sandbox/e2b/index.ts +36 -14
  116. package/src/adapters/sandbox/e2b/types.ts +16 -0
  117. package/src/adapters/sandbox/inmemory/index.ts +12 -3
  118. package/src/adapters/thread/adapter-id.test.ts +42 -0
  119. package/src/adapters/thread/anthropic/activities.ts +40 -5
  120. package/src/adapters/thread/anthropic/adapter-id.ts +16 -0
  121. package/src/adapters/thread/anthropic/fork-transform.test.ts +291 -0
  122. package/src/adapters/thread/anthropic/index.ts +3 -0
  123. package/src/adapters/thread/anthropic/model-invoker.ts +7 -1
  124. package/src/adapters/thread/anthropic/proxy.ts +3 -2
  125. package/src/adapters/thread/anthropic/thread-manager.ts +27 -1
  126. package/src/adapters/thread/google-genai/activities.ts +44 -5
  127. package/src/adapters/thread/google-genai/adapter-id.ts +16 -0
  128. package/src/adapters/thread/google-genai/fork-transform.test.ts +149 -0
  129. package/src/adapters/thread/google-genai/index.ts +3 -0
  130. package/src/adapters/thread/google-genai/model-invoker.ts +8 -2
  131. package/src/adapters/thread/google-genai/proxy.ts +3 -2
  132. package/src/adapters/thread/google-genai/thread-manager.ts +27 -1
  133. package/src/adapters/thread/index.ts +39 -0
  134. package/src/adapters/thread/langchain/activities.ts +40 -5
  135. package/src/adapters/thread/langchain/adapter-id.ts +16 -0
  136. package/src/adapters/thread/langchain/fork-transform.test.ts +142 -0
  137. package/src/adapters/thread/langchain/index.ts +3 -0
  138. package/src/adapters/thread/langchain/model-invoker.ts +7 -1
  139. package/src/adapters/thread/langchain/proxy.ts +3 -2
  140. package/src/adapters/thread/langchain/thread-manager.ts +27 -1
  141. package/src/lib/lifecycle.ts +14 -5
  142. package/src/lib/model/types.ts +7 -0
  143. package/src/lib/sandbox/manager.ts +26 -18
  144. package/src/lib/sandbox/types.ts +27 -7
  145. package/src/lib/session/session-edge-cases.integration.test.ts +336 -4
  146. package/src/lib/session/session.integration.test.ts +192 -2
  147. package/src/lib/session/session.ts +102 -8
  148. package/src/lib/session/types.ts +66 -3
  149. package/src/lib/state/index.ts +1 -0
  150. package/src/lib/state/manager.integration.test.ts +109 -0
  151. package/src/lib/state/manager.ts +38 -8
  152. package/src/lib/state/types.ts +25 -0
  153. package/src/lib/subagent/handler.ts +124 -11
  154. package/src/lib/subagent/index.ts +5 -1
  155. package/src/lib/subagent/subagent.integration.test.ts +628 -104
  156. package/src/lib/subagent/types.ts +63 -14
  157. package/src/lib/subagent/workflow.ts +29 -2
  158. package/src/lib/thread/index.ts +5 -0
  159. package/src/lib/thread/keys.test.ts +101 -0
  160. package/src/lib/thread/keys.ts +94 -0
  161. package/src/lib/thread/manager.test.ts +139 -0
  162. package/src/lib/thread/manager.ts +105 -9
  163. package/src/lib/thread/proxy.ts +3 -0
  164. package/src/lib/thread/types.ts +64 -1
  165. package/src/lib/tool-router/index.ts +2 -0
  166. package/src/lib/tool-router/router-edge-cases.integration.test.ts +92 -0
  167. package/src/lib/tool-router/router.integration.test.ts +12 -0
  168. package/src/lib/tool-router/router.ts +89 -16
  169. package/src/lib/tool-router/types.ts +42 -1
  170. package/src/lib/types.ts +12 -0
  171. package/src/workflow.ts +14 -1
  172. package/tsup.config.ts +1 -0
@@ -36,7 +36,7 @@ vi.mock("@temporalio/workflow", () => {
36
36
  condition: vi.fn(async (fn: () => boolean) => {
37
37
  if (!fn()) throw new Error("condition predicate was not satisfied");
38
38
  }),
39
- startChild: vi.fn(
39
+ executeChild: vi.fn(
40
40
  async (
41
41
  _workflow: unknown,
42
42
  opts: { workflowId: string; args: unknown[] }
@@ -51,11 +51,7 @@ vi.mock("@temporalio/workflow", () => {
51
51
  usage: { inputTokens: 100, outputTokens: 50 },
52
52
  };
53
53
 
54
- return {
55
- signal: vi.fn(),
56
- result: () => Promise.resolve(result),
57
- workflowId: opts.workflowId,
58
- };
54
+ return result;
59
55
  }
60
56
  ),
61
57
  getExternalWorkflowHandle: vi.fn((_id: string) => ({
@@ -378,8 +374,8 @@ describe("createSubagentHandler", () => {
378
374
  });
379
375
 
380
376
  it("passes sandbox inherit to child when sandbox is inherit", async () => {
381
- const { startChild } = await import("@temporalio/workflow");
382
- const startMock = startChild as ReturnType<typeof vi.fn>;
377
+ const { executeChild } = await import("@temporalio/workflow");
378
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
383
379
 
384
380
  const inheritSubagent: SubagentConfig = {
385
381
  agentName: "inherit-agent",
@@ -404,8 +400,8 @@ describe("createSubagentHandler", () => {
404
400
  }
405
401
  );
406
402
 
407
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
408
- if (!lastCall) throw new Error("expected startChild call");
403
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
404
+ if (!lastCall) throw new Error("expected executeChild call");
409
405
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
410
406
  expect(workflowInput.sandbox).toEqual({
411
407
  mode: "inherit",
@@ -436,8 +432,8 @@ describe("createSubagentHandler", () => {
436
432
  });
437
433
 
438
434
  it("does not pass sandboxId to child when sandbox is own (first call)", async () => {
439
- const { startChild } = await import("@temporalio/workflow");
440
- const startMock = startChild as ReturnType<typeof vi.fn>;
435
+ const { executeChild } = await import("@temporalio/workflow");
436
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
441
437
 
442
438
  const ownSubagent: SubagentConfig = {
443
439
  agentName: "own-agent",
@@ -458,15 +454,15 @@ describe("createSubagentHandler", () => {
458
454
  }
459
455
  );
460
456
 
461
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
462
- if (!lastCall) throw new Error("expected startChild call");
457
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
458
+ if (!lastCall) throw new Error("expected executeChild call");
463
459
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
464
460
  expect(workflowInput.sandbox).toBeUndefined();
465
461
  });
466
462
 
467
463
  it("resolves context function at invocation time", async () => {
468
- const { startChild } = await import("@temporalio/workflow");
469
- const startMock = startChild as ReturnType<typeof vi.fn>;
464
+ const { executeChild } = await import("@temporalio/workflow");
465
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
470
466
 
471
467
  let counter = 0;
472
468
  const dynamicSubagent: SubagentConfig = {
@@ -486,15 +482,15 @@ describe("createSubagentHandler", () => {
486
482
  { threadId: "t", toolCallId: "tc", toolName: "Subagent" }
487
483
  );
488
484
 
489
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
490
- if (!lastCall) throw new Error("expected startChild call");
485
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
486
+ if (!lastCall) throw new Error("expected executeChild call");
491
487
  const context = lastCall[1].args[2] as Record<string, unknown>;
492
488
  expect(context).toEqual({ invocation: 1 });
493
489
  });
494
490
 
495
491
  it("passes static context unchanged", async () => {
496
- const { startChild } = await import("@temporalio/workflow");
497
- const startMock = startChild as ReturnType<typeof vi.fn>;
492
+ const { executeChild } = await import("@temporalio/workflow");
493
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
498
494
 
499
495
  const staticSubagent: SubagentConfig = {
500
496
  agentName: "static-ctx",
@@ -510,15 +506,15 @@ describe("createSubagentHandler", () => {
510
506
  { threadId: "t", toolCallId: "tc", toolName: "Subagent" }
511
507
  );
512
508
 
513
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
514
- if (!lastCall) throw new Error("expected startChild call");
509
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
510
+ if (!lastCall) throw new Error("expected executeChild call");
515
511
  const context = lastCall[1].args[2] as Record<string, unknown>;
516
512
  expect(context).toEqual({ key: "value" });
517
513
  });
518
514
 
519
515
  it("does not pass sandbox init when sandbox is own without prior sandbox", async () => {
520
- const { startChild } = await import("@temporalio/workflow");
521
- const startMock = startChild as ReturnType<typeof vi.fn>;
516
+ const { executeChild } = await import("@temporalio/workflow");
517
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
522
518
 
523
519
  const ownSubagent: SubagentConfig = {
524
520
  agentName: "own-agent",
@@ -539,8 +535,8 @@ describe("createSubagentHandler", () => {
539
535
  }
540
536
  );
541
537
 
542
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
543
- if (!lastCall) throw new Error("expected startChild call");
538
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
539
+ if (!lastCall) throw new Error("expected executeChild call");
544
540
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
545
541
  expect(workflowInput.sandbox).toBeUndefined();
546
542
  });
@@ -548,8 +544,8 @@ describe("createSubagentHandler", () => {
548
544
  // --- Thread mode ---
549
545
 
550
546
  it("passes thread fork when thread is fork and threadId provided", async () => {
551
- const { startChild } = await import("@temporalio/workflow");
552
- const startMock = startChild as ReturnType<typeof vi.fn>;
547
+ const { executeChild } = await import("@temporalio/workflow");
548
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
553
549
 
554
550
  const contSubagent: SubagentConfig = {
555
551
  agentName: "cont",
@@ -570,8 +566,8 @@ describe("createSubagentHandler", () => {
570
566
  { threadId: "t", toolCallId: "tc", toolName: "Subagent" }
571
567
  );
572
568
 
573
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
574
- if (!lastCall) throw new Error("expected startChild call");
569
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
570
+ if (!lastCall) throw new Error("expected executeChild call");
575
571
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
576
572
  expect(workflowInput.thread).toEqual({
577
573
  mode: "fork",
@@ -580,8 +576,8 @@ describe("createSubagentHandler", () => {
580
576
  });
581
577
 
582
578
  it("passes thread continue when thread is continue", async () => {
583
- const { startChild } = await import("@temporalio/workflow");
584
- const startMock = startChild as ReturnType<typeof vi.fn>;
579
+ const { executeChild } = await import("@temporalio/workflow");
580
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
585
581
 
586
582
  const contSubagent: SubagentConfig = {
587
583
  agentName: "cont-mode",
@@ -602,8 +598,8 @@ describe("createSubagentHandler", () => {
602
598
  { threadId: "t", toolCallId: "tc", toolName: "Subagent" }
603
599
  );
604
600
 
605
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
606
- if (!lastCall) throw new Error("expected startChild call");
601
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
602
+ if (!lastCall) throw new Error("expected executeChild call");
607
603
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
608
604
  expect(workflowInput.thread).toEqual({
609
605
  mode: "continue",
@@ -612,8 +608,8 @@ describe("createSubagentHandler", () => {
612
608
  });
613
609
 
614
610
  it("does not pass thread when thread is new", async () => {
615
- const { startChild } = await import("@temporalio/workflow");
616
- const startMock = startChild as ReturnType<typeof vi.fn>;
611
+ const { executeChild } = await import("@temporalio/workflow");
612
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
617
613
 
618
614
  const noContSubagent: SubagentConfig = {
619
615
  agentName: "no-cont",
@@ -633,8 +629,8 @@ describe("createSubagentHandler", () => {
633
629
  { threadId: "t", toolCallId: "tc", toolName: "Subagent" }
634
630
  );
635
631
 
636
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
637
- if (!lastCall) throw new Error("expected startChild call");
632
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
633
+ if (!lastCall) throw new Error("expected executeChild call");
638
634
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
639
635
  expect(workflowInput.thread).toBeUndefined();
640
636
  });
@@ -642,8 +638,8 @@ describe("createSubagentHandler", () => {
642
638
  // --- Sandbox continuation ---
643
639
 
644
640
  it("does not pass sandbox when thread is fork (own sandbox)", async () => {
645
- const { startChild } = await import("@temporalio/workflow");
646
- const startMock = startChild as ReturnType<typeof vi.fn>;
641
+ const { executeChild } = await import("@temporalio/workflow");
642
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
647
643
 
648
644
  const contSandboxSubagent: SubagentConfig = {
649
645
  agentName: "sb-cont",
@@ -664,15 +660,15 @@ describe("createSubagentHandler", () => {
664
660
  }
665
661
  );
666
662
 
667
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
668
- if (!lastCall) throw new Error("expected startChild call");
663
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
664
+ if (!lastCall) throw new Error("expected executeChild call");
669
665
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
670
666
  expect(workflowInput.sandbox).toBeUndefined();
671
667
  });
672
668
 
673
669
  it("tracks sandbox ID and passes sandbox fork on continuation", async () => {
674
- const { startChild } = await import("@temporalio/workflow");
675
- const startMock = startChild as ReturnType<typeof vi.fn>;
670
+ const { executeChild } = await import("@temporalio/workflow");
671
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
676
672
 
677
673
  nextStartChildResult = () => ({
678
674
  toolResponse: "first run done",
@@ -713,8 +709,8 @@ describe("createSubagentHandler", () => {
713
709
  { threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
714
710
  );
715
711
 
716
- const secondCall = startMock.mock.calls[startMock.mock.calls.length - 1];
717
- if (!secondCall) throw new Error("expected second startChild call");
712
+ const secondCall = execMock.mock.calls[execMock.mock.calls.length - 1];
713
+ if (!secondCall) throw new Error("expected second executeChild call");
718
714
  const workflowInput = secondCall[1].args[1] as SubagentWorkflowInput;
719
715
  expect(workflowInput.thread).toEqual({
720
716
  mode: "fork",
@@ -727,8 +723,8 @@ describe("createSubagentHandler", () => {
727
723
  });
728
724
 
729
725
  it("does not pass sandbox fork without thread continuation", async () => {
730
- const { startChild } = await import("@temporalio/workflow");
731
- const startMock = startChild as ReturnType<typeof vi.fn>;
726
+ const { executeChild } = await import("@temporalio/workflow");
727
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
732
728
 
733
729
  nextStartChildResult = () => ({
734
730
  toolResponse: "done",
@@ -764,8 +760,8 @@ describe("createSubagentHandler", () => {
764
760
  { threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
765
761
  );
766
762
 
767
- const secondCall = startMock.mock.calls[startMock.mock.calls.length - 1];
768
- if (!secondCall) throw new Error("expected startChild call");
763
+ const secondCall = execMock.mock.calls[execMock.mock.calls.length - 1];
764
+ if (!secondCall) throw new Error("expected executeChild call");
769
765
  const workflowInput = secondCall[1].args[1] as SubagentWorkflowInput;
770
766
  expect(workflowInput.sandbox).toBeUndefined();
771
767
  expect(workflowInput.thread).toBeUndefined();
@@ -882,8 +878,8 @@ describe("createSubagentHandler", () => {
882
878
  });
883
879
 
884
880
  it("does not pass sandboxId when sandbox is none (default)", async () => {
885
- const { startChild } = await import("@temporalio/workflow");
886
- const startMock = startChild as ReturnType<typeof vi.fn>;
881
+ const { executeChild } = await import("@temporalio/workflow");
882
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
887
883
 
888
884
  const noneSubagent: SubagentConfig = {
889
885
  agentName: "none-agent",
@@ -903,15 +899,15 @@ describe("createSubagentHandler", () => {
903
899
  }
904
900
  );
905
901
 
906
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
907
- if (!lastCall) throw new Error("expected startChild call");
902
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
903
+ if (!lastCall) throw new Error("expected executeChild call");
908
904
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
909
905
  expect(workflowInput.sandbox).toBeUndefined();
910
906
  });
911
907
 
912
908
  it("does not pass sandboxId when sandbox is explicitly none", async () => {
913
- const { startChild } = await import("@temporalio/workflow");
914
- const startMock = startChild as ReturnType<typeof vi.fn>;
909
+ const { executeChild } = await import("@temporalio/workflow");
910
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
915
911
 
916
912
  const noneSubagent: SubagentConfig = {
917
913
  agentName: "none-agent",
@@ -932,8 +928,8 @@ describe("createSubagentHandler", () => {
932
928
  }
933
929
  );
934
930
 
935
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
936
- if (!lastCall) throw new Error("expected startChild call");
931
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
932
+ if (!lastCall) throw new Error("expected executeChild call");
937
933
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
938
934
  expect(workflowInput.sandbox).toBeUndefined();
939
935
  });
@@ -961,8 +957,8 @@ describe("createSubagentHandler", () => {
961
957
  // --- inherit + continuation: fork ---
962
958
 
963
959
  it("forks from parent sandbox when inherit + continuation=fork", async () => {
964
- const { startChild } = await import("@temporalio/workflow");
965
- const startMock = startChild as ReturnType<typeof vi.fn>;
960
+ const { executeChild } = await import("@temporalio/workflow");
961
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
966
962
 
967
963
  const config: SubagentConfig = {
968
964
  agentName: "inherit-fork",
@@ -987,8 +983,8 @@ describe("createSubagentHandler", () => {
987
983
  }
988
984
  );
989
985
 
990
- const lastCall = startMock.mock.calls.at(-1);
991
- if (!lastCall) throw new Error("expected startChild call");
986
+ const lastCall = execMock.mock.calls.at(-1);
987
+ if (!lastCall) throw new Error("expected executeChild call");
992
988
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
993
989
  expect(workflowInput.sandbox).toEqual({
994
990
  mode: "fork",
@@ -999,8 +995,8 @@ describe("createSubagentHandler", () => {
999
995
  // --- own + continuation: continue ---
1000
996
 
1001
997
  it("passes sandbox continue on thread continuation with continuation=continue", async () => {
1002
- const { startChild } = await import("@temporalio/workflow");
1003
- const startMock = startChild as ReturnType<typeof vi.fn>;
998
+ const { executeChild } = await import("@temporalio/workflow");
999
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1004
1000
 
1005
1001
  nextStartChildResult = () => ({
1006
1002
  toolResponse: "first",
@@ -1045,8 +1041,8 @@ describe("createSubagentHandler", () => {
1045
1041
  { threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
1046
1042
  );
1047
1043
 
1048
- const secondCall = startMock.mock.calls.at(-1);
1049
- if (!secondCall) throw new Error("expected startChild call");
1044
+ const secondCall = execMock.mock.calls.at(-1);
1045
+ if (!secondCall) throw new Error("expected executeChild call");
1050
1046
  const workflowInput = secondCall[1].args[1] as SubagentWorkflowInput;
1051
1047
  expect(workflowInput.sandbox).toEqual({
1052
1048
  mode: "continue",
@@ -1058,8 +1054,8 @@ describe("createSubagentHandler", () => {
1058
1054
  // --- own + init: once + continuation: fork ---
1059
1055
 
1060
1056
  it("stores sandbox on first call and forks from it on second call (init=once, continuation=fork)", async () => {
1061
- const { startChild } = await import("@temporalio/workflow");
1062
- const startMock = startChild as ReturnType<typeof vi.fn>;
1057
+ const { executeChild } = await import("@temporalio/workflow");
1058
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1063
1059
 
1064
1060
  nextStartChildResult = () => ({
1065
1061
  toolResponse: "first",
@@ -1088,8 +1084,8 @@ describe("createSubagentHandler", () => {
1088
1084
  );
1089
1085
 
1090
1086
  // First call: no sandbox init (child creates fresh), forced pause-until-parent-close
1091
- const firstCall = startMock.mock.calls.at(-1);
1092
- if (!firstCall) throw new Error("expected startChild call");
1087
+ const firstCall = execMock.mock.calls.at(-1);
1088
+ if (!firstCall) throw new Error("expected executeChild call");
1093
1089
  const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
1094
1090
  expect(firstInput.sandbox).toBeUndefined();
1095
1091
  expect(firstInput.sandboxShutdown).toBe("pause-until-parent-close");
@@ -1107,8 +1103,8 @@ describe("createSubagentHandler", () => {
1107
1103
  { threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
1108
1104
  );
1109
1105
 
1110
- const secondCall = startMock.mock.calls.at(-1);
1111
- if (!secondCall) throw new Error("expected startChild call");
1106
+ const secondCall = execMock.mock.calls.at(-1);
1107
+ if (!secondCall) throw new Error("expected executeChild call");
1112
1108
  const secondInput = secondCall[1].args[1] as SubagentWorkflowInput;
1113
1109
  expect(secondInput.sandbox).toEqual({
1114
1110
  mode: "fork",
@@ -1119,8 +1115,8 @@ describe("createSubagentHandler", () => {
1119
1115
  // --- own + init: once + continuation: continue ---
1120
1116
 
1121
1117
  it("stores sandbox on first call and continues it on second call (init=once, continuation=continue)", async () => {
1122
- const { startChild } = await import("@temporalio/workflow");
1123
- const startMock = startChild as ReturnType<typeof vi.fn>;
1118
+ const { executeChild } = await import("@temporalio/workflow");
1119
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1124
1120
 
1125
1121
  nextStartChildResult = () => ({
1126
1122
  toolResponse: "first",
@@ -1160,8 +1156,8 @@ describe("createSubagentHandler", () => {
1160
1156
  { threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
1161
1157
  );
1162
1158
 
1163
- const secondCall = startMock.mock.calls.at(-1);
1164
- if (!secondCall) throw new Error("expected startChild call");
1159
+ const secondCall = execMock.mock.calls.at(-1);
1160
+ if (!secondCall) throw new Error("expected executeChild call");
1165
1161
  const secondInput = secondCall[1].args[1] as SubagentWorkflowInput;
1166
1162
  expect(secondInput.sandbox).toEqual({
1167
1163
  mode: "continue",
@@ -1398,8 +1394,8 @@ describe("createSubagentHandler", () => {
1398
1394
  // --- mustSurvive does not override user shutdown ---
1399
1395
 
1400
1396
  it("does not override keep-until-parent-close with pause-until-parent-close for init=once", async () => {
1401
- const { startChild } = await import("@temporalio/workflow");
1402
- const startMock = startChild as ReturnType<typeof vi.fn>;
1397
+ const { executeChild } = await import("@temporalio/workflow");
1398
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1403
1399
 
1404
1400
  nextStartChildResult = () => ({
1405
1401
  toolResponse: "first",
@@ -1428,15 +1424,15 @@ describe("createSubagentHandler", () => {
1428
1424
  { threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
1429
1425
  );
1430
1426
 
1431
- const firstCall = startMock.mock.calls.at(-1);
1432
- if (!firstCall) throw new Error("expected startChild call");
1427
+ const firstCall = execMock.mock.calls.at(-1);
1428
+ if (!firstCall) throw new Error("expected executeChild call");
1433
1429
  const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
1434
1430
  expect(firstInput.sandboxShutdown).toBe("keep-until-parent-close");
1435
1431
  });
1436
1432
 
1437
1433
  it("does not override pause with pause-until-parent-close for init=once", async () => {
1438
- const { startChild } = await import("@temporalio/workflow");
1439
- const startMock = startChild as ReturnType<typeof vi.fn>;
1434
+ const { executeChild } = await import("@temporalio/workflow");
1435
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1440
1436
 
1441
1437
  nextStartChildResult = () => ({
1442
1438
  toolResponse: "first",
@@ -1465,15 +1461,15 @@ describe("createSubagentHandler", () => {
1465
1461
  { threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
1466
1462
  );
1467
1463
 
1468
- const firstCall = startMock.mock.calls.at(-1);
1469
- if (!firstCall) throw new Error("expected startChild call");
1464
+ const firstCall = execMock.mock.calls.at(-1);
1465
+ if (!firstCall) throw new Error("expected executeChild call");
1470
1466
  const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
1471
1467
  expect(firstInput.sandboxShutdown).toBe("pause");
1472
1468
  });
1473
1469
 
1474
1470
  it("does not override keep with pause for continuation=continue", async () => {
1475
- const { startChild } = await import("@temporalio/workflow");
1476
- const startMock = startChild as ReturnType<typeof vi.fn>;
1471
+ const { executeChild } = await import("@temporalio/workflow");
1472
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1477
1473
 
1478
1474
  nextStartChildResult = () => ({
1479
1475
  toolResponse: "first",
@@ -1502,15 +1498,15 @@ describe("createSubagentHandler", () => {
1502
1498
  { threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
1503
1499
  );
1504
1500
 
1505
- const firstCall = startMock.mock.calls.at(-1);
1506
- if (!firstCall) throw new Error("expected startChild call");
1501
+ const firstCall = execMock.mock.calls.at(-1);
1502
+ if (!firstCall) throw new Error("expected executeChild call");
1507
1503
  const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
1508
1504
  expect(firstInput.sandboxShutdown).toBe("keep");
1509
1505
  });
1510
1506
 
1511
1507
  it("still overrides destroy with pause for continuation=continue", async () => {
1512
- const { startChild } = await import("@temporalio/workflow");
1513
- const startMock = startChild as ReturnType<typeof vi.fn>;
1508
+ const { executeChild } = await import("@temporalio/workflow");
1509
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1514
1510
 
1515
1511
  nextStartChildResult = () => ({
1516
1512
  toolResponse: "first",
@@ -1539,8 +1535,8 @@ describe("createSubagentHandler", () => {
1539
1535
  { threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
1540
1536
  );
1541
1537
 
1542
- const firstCall = startMock.mock.calls.at(-1);
1543
- if (!firstCall) throw new Error("expected startChild call");
1538
+ const firstCall = execMock.mock.calls.at(-1);
1539
+ if (!firstCall) throw new Error("expected executeChild call");
1544
1540
  const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
1545
1541
  expect(firstInput.sandboxShutdown).toBe("pause");
1546
1542
  });
@@ -1548,8 +1544,8 @@ describe("createSubagentHandler", () => {
1548
1544
  // --- snapshot continuation ---
1549
1545
 
1550
1546
  it("forces sandboxShutdown=snapshot and passes no sandbox on first call (continuation=snapshot)", async () => {
1551
- const { startChild } = await import("@temporalio/workflow");
1552
- const startMock = startChild as ReturnType<typeof vi.fn>;
1547
+ const { executeChild } = await import("@temporalio/workflow");
1548
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1553
1549
 
1554
1550
  nextStartChildResult = () => ({
1555
1551
  toolResponse: "first",
@@ -1589,16 +1585,16 @@ describe("createSubagentHandler", () => {
1589
1585
  { threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
1590
1586
  );
1591
1587
 
1592
- const firstCall = startMock.mock.calls.at(-1);
1593
- if (!firstCall) throw new Error("expected startChild call");
1588
+ const firstCall = execMock.mock.calls.at(-1);
1589
+ if (!firstCall) throw new Error("expected executeChild call");
1594
1590
  const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
1595
1591
  expect(firstInput.sandbox).toBeUndefined();
1596
1592
  expect(firstInput.sandboxShutdown).toBe("snapshot");
1597
1593
  });
1598
1594
 
1599
1595
  it("boots follow-up from stored thread snapshot on continuation=snapshot", async () => {
1600
- const { startChild } = await import("@temporalio/workflow");
1601
- const startMock = startChild as ReturnType<typeof vi.fn>;
1596
+ const { executeChild } = await import("@temporalio/workflow");
1597
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1602
1598
 
1603
1599
  nextStartChildResult = () => ({
1604
1600
  toolResponse: "first",
@@ -1660,8 +1656,8 @@ describe("createSubagentHandler", () => {
1660
1656
  { threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
1661
1657
  );
1662
1658
 
1663
- const secondCall = startMock.mock.calls.at(-1);
1664
- if (!secondCall) throw new Error("expected startChild call");
1659
+ const secondCall = execMock.mock.calls.at(-1);
1660
+ if (!secondCall) throw new Error("expected executeChild call");
1665
1661
  const secondInput = secondCall[1].args[1] as SubagentWorkflowInput;
1666
1662
  expect(secondInput.sandbox).toEqual({
1667
1663
  mode: "from-snapshot",
@@ -1671,8 +1667,8 @@ describe("createSubagentHandler", () => {
1671
1667
  });
1672
1668
 
1673
1669
  it("uses per-agent base snapshot for a new thread when init=once + continuation=snapshot", async () => {
1674
- const { startChild } = await import("@temporalio/workflow");
1675
- const startMock = startChild as ReturnType<typeof vi.fn>;
1670
+ const { executeChild } = await import("@temporalio/workflow");
1671
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1676
1672
 
1677
1673
  // First call — establishes base snapshot.
1678
1674
  nextStartChildResult = () => ({
@@ -1731,8 +1727,8 @@ describe("createSubagentHandler", () => {
1731
1727
  { threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
1732
1728
  );
1733
1729
 
1734
- const secondCall = startMock.mock.calls.at(-1);
1735
- if (!secondCall) throw new Error("expected startChild call");
1730
+ const secondCall = execMock.mock.calls.at(-1);
1731
+ if (!secondCall) throw new Error("expected executeChild call");
1736
1732
  const secondInput = secondCall[1].args[1] as SubagentWorkflowInput;
1737
1733
  expect(secondInput.sandbox).toEqual({
1738
1734
  mode: "from-snapshot",
@@ -1814,6 +1810,173 @@ describe("createSubagentHandler", () => {
1814
1810
  expect(deletedTags.sort()).toEqual(["base", "exit-2"]);
1815
1811
  });
1816
1812
 
1813
+ it("publishes persistentBaseSnapshot from childSandboxReadySignal before child completes", async () => {
1814
+ const { setHandler, executeChild } = await import("@temporalio/workflow");
1815
+ const setHandlerMock = setHandler as ReturnType<typeof vi.fn>;
1816
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1817
+
1818
+ const signalBase = {
1819
+ sandboxId: "sb-signal",
1820
+ providerId: "test",
1821
+ data: { tag: "signal-base" },
1822
+ createdAt: new Date().toISOString(),
1823
+ };
1824
+
1825
+ const config: SubagentConfig = {
1826
+ agentName: "snap-agent",
1827
+ description: "Snapshot-driven",
1828
+ workflow: mockWorkflow(),
1829
+ thread: "continue",
1830
+ sandbox: {
1831
+ source: "own",
1832
+ init: "once",
1833
+ continuation: "snapshot",
1834
+ proxy: noopSandboxProxy,
1835
+ },
1836
+ };
1837
+
1838
+ const { handler } = createSubagentHandler([config]);
1839
+
1840
+ // Fire the signal from inside executeChild so persistentBaseSnapshot is
1841
+ // set before the child's own result is returned. The child result itself
1842
+ // intentionally omits baseSnapshot — only the signal path can populate
1843
+ // the map for this call.
1844
+ execMock.mockImplementationOnce(
1845
+ async (_wf: unknown, opts: { workflowId: string; args: unknown[] }) => {
1846
+ const reg = setHandlerMock.mock.calls
1847
+ .filter(
1848
+ ([sig]) => (sig as { name?: string })?.name === "childSandboxReady"
1849
+ )
1850
+ .at(-1);
1851
+ const signalHandler = reg?.[1] as
1852
+ | ((p: {
1853
+ childWorkflowId: string;
1854
+ sandboxId: string;
1855
+ baseSnapshot?: unknown;
1856
+ }) => void)
1857
+ | undefined;
1858
+ signalHandler?.({
1859
+ childWorkflowId: opts.workflowId,
1860
+ sandboxId: "sb-signal",
1861
+ baseSnapshot: signalBase,
1862
+ });
1863
+ return {
1864
+ toolResponse: "first",
1865
+ data: null,
1866
+ threadId: "child-sig-1",
1867
+ };
1868
+ }
1869
+ );
1870
+
1871
+ await handler(
1872
+ { subagent: "snap-agent", description: "test", prompt: "first" },
1873
+ { threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
1874
+ );
1875
+
1876
+ // Second call on a new thread must boot from the signal-published base.
1877
+ nextStartChildResult = () => ({
1878
+ toolResponse: "second",
1879
+ data: null,
1880
+ threadId: "child-sig-2",
1881
+ });
1882
+
1883
+ await handler(
1884
+ { subagent: "snap-agent", description: "test", prompt: "second" },
1885
+ { threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
1886
+ );
1887
+
1888
+ const secondCall = execMock.mock.calls.at(-1);
1889
+ if (!secondCall) throw new Error("expected executeChild call");
1890
+ const secondInput = secondCall[1].args[1] as SubagentWorkflowInput;
1891
+ expect(secondInput.sandbox).toEqual({
1892
+ mode: "from-snapshot",
1893
+ snapshot: expect.objectContaining({ data: { tag: "signal-base" } }),
1894
+ });
1895
+ });
1896
+
1897
+ it("ignores signal baseSnapshot for non-snapshot-base creators", async () => {
1898
+ const { setHandler, executeChild } = await import("@temporalio/workflow");
1899
+ const setHandlerMock = setHandler as ReturnType<typeof vi.fn>;
1900
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1901
+
1902
+ const config: SubagentConfig = {
1903
+ agentName: "lazy-fork-agent",
1904
+ description: "Lazy fork (not snapshot)",
1905
+ workflow: mockWorkflow(),
1906
+ sandbox: {
1907
+ source: "own",
1908
+ init: "once",
1909
+ continuation: "fork",
1910
+ proxy: noopSandboxProxy,
1911
+ },
1912
+ };
1913
+
1914
+ const { handler } = createSubagentHandler([config]);
1915
+
1916
+ execMock.mockImplementationOnce(
1917
+ async (_wf: unknown, opts: { workflowId: string; args: unknown[] }) => {
1918
+ const reg = setHandlerMock.mock.calls
1919
+ .filter(
1920
+ ([sig]) => (sig as { name?: string })?.name === "childSandboxReady"
1921
+ )
1922
+ .at(-1);
1923
+ const signalHandler = reg?.[1] as
1924
+ | ((p: {
1925
+ childWorkflowId: string;
1926
+ sandboxId: string;
1927
+ baseSnapshot?: unknown;
1928
+ }) => void)
1929
+ | undefined;
1930
+ // Stray baseSnapshot on a non-snapshot path must not land in
1931
+ // persistentBaseSnapshot, or it would corrupt a different agent's
1932
+ // snapshot flow.
1933
+ signalHandler?.({
1934
+ childWorkflowId: opts.workflowId,
1935
+ sandboxId: "sb-lazy",
1936
+ baseSnapshot: {
1937
+ sandboxId: "sb-lazy",
1938
+ providerId: "test",
1939
+ data: { tag: "should-be-ignored" },
1940
+ createdAt: new Date().toISOString(),
1941
+ },
1942
+ });
1943
+ return {
1944
+ toolResponse: "first",
1945
+ data: null,
1946
+ threadId: "child-lazy-1",
1947
+ sandboxId: "sb-lazy",
1948
+ };
1949
+ }
1950
+ );
1951
+
1952
+ await handler(
1953
+ { subagent: "lazy-fork-agent", description: "test", prompt: "first" },
1954
+ { threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
1955
+ );
1956
+
1957
+ // Second call should fork from the lazy-published sandbox, not restore
1958
+ // from any snapshot.
1959
+ nextStartChildResult = () => ({
1960
+ toolResponse: "second",
1961
+ data: null,
1962
+ threadId: "child-lazy-2",
1963
+ sandboxId: "sb-lazy",
1964
+ });
1965
+
1966
+ await handler(
1967
+ { subagent: "lazy-fork-agent", description: "test", prompt: "second" },
1968
+ { threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
1969
+ );
1970
+
1971
+ const secondCall = execMock.mock.calls.at(-1);
1972
+ if (!secondCall) throw new Error("expected executeChild call");
1973
+ const secondInput = secondCall[1].args[1] as SubagentWorkflowInput;
1974
+ expect(secondInput.sandbox).toEqual({
1975
+ mode: "fork",
1976
+ sandboxId: "sb-lazy",
1977
+ });
1978
+ });
1979
+
1817
1980
  it("does not call deleteSandboxSnapshot for children that produced no snapshots", async () => {
1818
1981
  const opsMock = makeMockSandboxOps();
1819
1982
  const config: SubagentConfig = {
@@ -1847,6 +2010,195 @@ describe("createSubagentHandler", () => {
1847
2010
 
1848
2011
  expect(opsMock.deleteSandboxSnapshot).not.toHaveBeenCalled();
1849
2012
  });
2013
+
2014
+ // -------------------------------------------------------------------------
2015
+ // Child workflow failure propagation
2016
+ // -------------------------------------------------------------------------
2017
+
2018
+ it("propagates executeChild failure to the caller instead of hanging", async () => {
2019
+ const { executeChild } = await import("@temporalio/workflow");
2020
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
2021
+ execMock.mockImplementationOnce(async () => {
2022
+ throw new Error("Child Workflow execution failed: timeout");
2023
+ });
2024
+
2025
+ const { handler } = createSubagentHandler([
2026
+ {
2027
+ agentName: "researcher",
2028
+ description: "Researches topics",
2029
+ workflow: mockWorkflow("researcherWorkflow"),
2030
+ },
2031
+ ]);
2032
+
2033
+ await expect(
2034
+ handler(
2035
+ { subagent: "researcher", description: "test", prompt: "hi" },
2036
+ { threadId: "t", toolCallId: "tc", toolName: "Subagent" }
2037
+ )
2038
+ ).rejects.toThrow("Child Workflow execution failed: timeout");
2039
+ });
2040
+
2041
+ it("applies a default workflowRunTimeout when workflowOptions is omitted", async () => {
2042
+ const { executeChild } = await import("@temporalio/workflow");
2043
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
2044
+
2045
+ const { DEFAULT_SUBAGENT_WORKFLOW_RUN_TIMEOUT } = await import("./handler");
2046
+
2047
+ const { handler } = createSubagentHandler([
2048
+ {
2049
+ agentName: "researcher",
2050
+ description: "Researches topics",
2051
+ workflow: mockWorkflow("researcherWorkflow"),
2052
+ },
2053
+ ]);
2054
+
2055
+ await handler(
2056
+ { subagent: "researcher", description: "test", prompt: "hi" },
2057
+ { threadId: "t", toolCallId: "tc", toolName: "Subagent" }
2058
+ );
2059
+
2060
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
2061
+ if (!lastCall) throw new Error("expected executeChild call");
2062
+ expect(lastCall[1].workflowRunTimeout).toBe(
2063
+ DEFAULT_SUBAGENT_WORKFLOW_RUN_TIMEOUT
2064
+ );
2065
+ });
2066
+
2067
+ it("lets workflowOptions.workflowRunTimeout override the default", async () => {
2068
+ const { executeChild } = await import("@temporalio/workflow");
2069
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
2070
+
2071
+ const { handler } = createSubagentHandler([
2072
+ {
2073
+ agentName: "researcher",
2074
+ description: "Researches topics",
2075
+ workflow: mockWorkflow("researcherWorkflow"),
2076
+ workflowOptions: { workflowRunTimeout: "30s" },
2077
+ },
2078
+ ]);
2079
+
2080
+ await handler(
2081
+ { subagent: "researcher", description: "test", prompt: "hi" },
2082
+ { threadId: "t", toolCallId: "tc", toolName: "Subagent" }
2083
+ );
2084
+
2085
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
2086
+ if (!lastCall) throw new Error("expected executeChild call");
2087
+ expect(lastCall[1].workflowRunTimeout).toBe("30s");
2088
+ });
2089
+
2090
+ it("forwards workflowOptions to executeChild", async () => {
2091
+ const { executeChild } = await import("@temporalio/workflow");
2092
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
2093
+
2094
+ const { handler } = createSubagentHandler([
2095
+ {
2096
+ agentName: "researcher",
2097
+ description: "Researches topics",
2098
+ workflow: mockWorkflow("researcherWorkflow"),
2099
+ workflowOptions: {
2100
+ workflowRunTimeout: "5m",
2101
+ workflowTaskTimeout: "30s",
2102
+ retry: { maximumAttempts: 1 },
2103
+ },
2104
+ },
2105
+ ]);
2106
+
2107
+ await handler(
2108
+ { subagent: "researcher", description: "test", prompt: "hi" },
2109
+ { threadId: "t", toolCallId: "tc", toolName: "Subagent" }
2110
+ );
2111
+
2112
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
2113
+ if (!lastCall) throw new Error("expected executeChild call");
2114
+ expect(lastCall[1]).toMatchObject({
2115
+ workflowRunTimeout: "5m",
2116
+ workflowTaskTimeout: "30s",
2117
+ retry: { maximumAttempts: 1 },
2118
+ });
2119
+ });
2120
+
2121
+ it("does not let workflowOptions override workflowId, taskQueue, or args", async () => {
2122
+ const { executeChild } = await import("@temporalio/workflow");
2123
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
2124
+
2125
+ const { handler } = createSubagentHandler([
2126
+ {
2127
+ agentName: "researcher",
2128
+ description: "Researches topics",
2129
+ workflow: mockWorkflow("researcherWorkflow"),
2130
+ taskQueue: "my-queue",
2131
+ workflowOptions: {
2132
+ // Intentionally violates the public Omit<> type to prove the
2133
+ // handler still wins at runtime. Cast removes the type error.
2134
+ ...({
2135
+ workflowId: "forbidden-id",
2136
+ taskQueue: "forbidden-queue",
2137
+ args: ["forbidden"],
2138
+ } as Record<string, unknown>),
2139
+ },
2140
+ },
2141
+ ]);
2142
+
2143
+ await handler(
2144
+ { subagent: "researcher", description: "test", prompt: "hello" },
2145
+ { threadId: "t", toolCallId: "tc", toolName: "Subagent" }
2146
+ );
2147
+
2148
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
2149
+ if (!lastCall) throw new Error("expected executeChild call");
2150
+ expect(lastCall[1].workflowId).not.toBe("forbidden-id");
2151
+ expect(lastCall[1].workflowId).toMatch(/^researcher-/);
2152
+ expect(lastCall[1].taskQueue).toBe("my-queue");
2153
+ expect(lastCall[1].args[0]).toBe("hello");
2154
+ });
2155
+
2156
+ it("clears lazy-creator bookkeeping on failure so the next call can re-try", async () => {
2157
+ const opsMock = makeMockSandboxOps();
2158
+ const { executeChild } = await import("@temporalio/workflow");
2159
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
2160
+
2161
+ const lazySubagent: SubagentConfig = {
2162
+ agentName: "lazy",
2163
+ description: "Lazy sandbox init",
2164
+ workflow: mockWorkflow(),
2165
+ sandbox: {
2166
+ source: "own",
2167
+ init: "once",
2168
+ continuation: "fork",
2169
+ proxy: () => opsMock,
2170
+ },
2171
+ };
2172
+
2173
+ const { handler } = createSubagentHandler([lazySubagent]);
2174
+
2175
+ execMock.mockImplementationOnce(async () => {
2176
+ throw new Error("init failed");
2177
+ });
2178
+
2179
+ await expect(
2180
+ handler(
2181
+ { subagent: "lazy", description: "test", prompt: "first" },
2182
+ { threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
2183
+ )
2184
+ ).rejects.toThrow("init failed");
2185
+
2186
+ // A second call must be able to take the creator role again (no stranded
2187
+ // "creating" flag) and succeed.
2188
+ nextStartChildResult = () => ({
2189
+ toolResponse: "ok",
2190
+ data: null,
2191
+ threadId: "child-t-2",
2192
+ sandboxId: "child-sb-2",
2193
+ });
2194
+
2195
+ const result = await handler(
2196
+ { subagent: "lazy", description: "test", prompt: "second" },
2197
+ { threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
2198
+ );
2199
+
2200
+ expect(result.toolResponse).toBe("ok");
2201
+ });
1850
2202
  });
1851
2203
 
1852
2204
  // ---------------------------------------------------------------------------
@@ -2054,6 +2406,7 @@ describe("defineSubagentWorkflow", () => {
2054
2406
  sandboxShutdown: "destroy",
2055
2407
  thread: { mode: "fork", threadId: "prev-42" },
2056
2408
  onSandboxReady: expect.any(Function),
2409
+ onSessionExit: expect.any(Function),
2057
2410
  });
2058
2411
  });
2059
2412
 
@@ -2073,6 +2426,7 @@ describe("defineSubagentWorkflow", () => {
2073
2426
  sandboxShutdown: "destroy",
2074
2427
  sandbox: { mode: "inherit", sandboxId: "sb-123" },
2075
2428
  onSandboxReady: expect.any(Function),
2429
+ onSessionExit: expect.any(Function),
2076
2430
  });
2077
2431
  });
2078
2432
 
@@ -2092,6 +2446,7 @@ describe("defineSubagentWorkflow", () => {
2092
2446
  sandboxShutdown: "destroy",
2093
2447
  sandbox: { mode: "fork", sandboxId: "prev-sb-1" },
2094
2448
  onSandboxReady: expect.any(Function),
2449
+ onSessionExit: expect.any(Function),
2095
2450
  });
2096
2451
  });
2097
2452
 
@@ -2115,6 +2470,7 @@ describe("defineSubagentWorkflow", () => {
2115
2470
  thread: { mode: "fork", threadId: "prev-t" },
2116
2471
  sandbox: { mode: "fork", sandboxId: "prev-sb" },
2117
2472
  onSandboxReady: expect.any(Function),
2473
+ onSessionExit: expect.any(Function),
2118
2474
  });
2119
2475
  });
2120
2476
 
@@ -2185,6 +2541,7 @@ describe("defineSubagentWorkflow", () => {
2185
2541
  agentName: "test",
2186
2542
  sandboxShutdown: "destroy",
2187
2543
  onSandboxReady: expect.any(Function),
2544
+ onSessionExit: expect.any(Function),
2188
2545
  });
2189
2546
  });
2190
2547
 
@@ -2207,4 +2564,171 @@ describe("defineSubagentWorkflow", () => {
2207
2564
 
2208
2565
  expect(capturedSession?.sandboxShutdown).toBe("keep-until-parent-close");
2209
2566
  });
2567
+
2568
+ // -------------------------------------------------------------------------
2569
+ // Auto-forwarding of session outputs + signal payload
2570
+ // -------------------------------------------------------------------------
2571
+
2572
+ it("auto-forwards baseSnapshot captured via onSandboxReady", async () => {
2573
+ const baseSnapshot = {
2574
+ sandboxId: "sb-1",
2575
+ providerId: "test",
2576
+ data: { tag: "base" },
2577
+ createdAt: new Date().toISOString(),
2578
+ };
2579
+ const workflow = defineSubagentWorkflow(
2580
+ { name: "test", description: "test agent" },
2581
+ async (_prompt, sessionInput) => {
2582
+ sessionInput.onSandboxReady?.({ sandboxId: "sb-1", baseSnapshot });
2583
+ return { toolResponse: "ok", data: null, threadId: "t" };
2584
+ }
2585
+ );
2586
+
2587
+ const result = await workflow("go", {});
2588
+ expect(result.baseSnapshot).toEqual(baseSnapshot);
2589
+ });
2590
+
2591
+ it("auto-forwards sandboxId and snapshot captured via onSessionExit", async () => {
2592
+ const snapshot = {
2593
+ sandboxId: "sb-1",
2594
+ providerId: "test",
2595
+ data: { tag: "exit" },
2596
+ createdAt: new Date().toISOString(),
2597
+ };
2598
+ const workflow = defineSubagentWorkflow(
2599
+ { name: "test", description: "test agent" },
2600
+ async (_prompt, sessionInput) => {
2601
+ sessionInput.onSessionExit?.({
2602
+ sandboxId: "sb-1",
2603
+ snapshot,
2604
+ threadId: "t",
2605
+ });
2606
+ return { toolResponse: "ok", data: null, threadId: "t" };
2607
+ }
2608
+ );
2609
+
2610
+ const result = await workflow("go", {});
2611
+ expect(result.sandboxId).toBe("sb-1");
2612
+ expect(result.snapshot).toEqual(snapshot);
2613
+ });
2614
+
2615
+ it("fn-explicit sandbox outputs win over captured session outputs", async () => {
2616
+ const workflow = defineSubagentWorkflow(
2617
+ { name: "test", description: "test agent" },
2618
+ async (_prompt, sessionInput) => {
2619
+ sessionInput.onSessionExit?.({
2620
+ sandboxId: "session-sb",
2621
+ snapshot: {
2622
+ sandboxId: "session-sb",
2623
+ providerId: "test",
2624
+ data: { tag: "session" },
2625
+ createdAt: new Date().toISOString(),
2626
+ },
2627
+ threadId: "t",
2628
+ });
2629
+ return {
2630
+ toolResponse: "ok",
2631
+ data: null,
2632
+ threadId: "t",
2633
+ sandboxId: "explicit-sb",
2634
+ snapshot: {
2635
+ sandboxId: "explicit-sb",
2636
+ providerId: "test",
2637
+ data: { tag: "explicit" },
2638
+ createdAt: new Date().toISOString(),
2639
+ },
2640
+ };
2641
+ }
2642
+ );
2643
+
2644
+ const result = await workflow("go", {});
2645
+ expect(result.sandboxId).toBe("explicit-sb");
2646
+ expect(
2647
+ (result.snapshot as { data: { tag: string } } | undefined)?.data
2648
+ ).toEqual({ tag: "explicit" });
2649
+ });
2650
+
2651
+ it("signals parent with baseSnapshot via childSandboxReadySignal", async () => {
2652
+ const { getExternalWorkflowHandle } = await import("@temporalio/workflow");
2653
+ const ghMock = getExternalWorkflowHandle as ReturnType<typeof vi.fn>;
2654
+ const baseSnapshot = {
2655
+ sandboxId: "sb-1",
2656
+ providerId: "test",
2657
+ data: { tag: "base" },
2658
+ createdAt: new Date().toISOString(),
2659
+ };
2660
+
2661
+ const workflow = defineSubagentWorkflow(
2662
+ { name: "test", description: "test agent" },
2663
+ async (_prompt, sessionInput) => {
2664
+ sessionInput.onSandboxReady?.({ sandboxId: "sb-1", baseSnapshot });
2665
+ return { toolResponse: "ok", data: null, threadId: "t" };
2666
+ }
2667
+ );
2668
+
2669
+ await workflow("go", {});
2670
+
2671
+ const handle = ghMock.mock.results.at(-1)?.value as {
2672
+ signal: ReturnType<typeof vi.fn>;
2673
+ };
2674
+ expect(handle.signal).toHaveBeenCalledWith(
2675
+ expect.objectContaining({ name: "childSandboxReady" }),
2676
+ expect.objectContaining({
2677
+ childWorkflowId: "child-wf-1",
2678
+ sandboxId: "sb-1",
2679
+ baseSnapshot,
2680
+ })
2681
+ );
2682
+ });
2683
+
2684
+ it("omits baseSnapshot from signal when session did not capture one", async () => {
2685
+ const { getExternalWorkflowHandle } = await import("@temporalio/workflow");
2686
+ const ghMock = getExternalWorkflowHandle as ReturnType<typeof vi.fn>;
2687
+
2688
+ const workflow = defineSubagentWorkflow(
2689
+ { name: "test", description: "test agent" },
2690
+ async (_prompt, sessionInput) => {
2691
+ sessionInput.onSandboxReady?.({ sandboxId: "sb-1" });
2692
+ return { toolResponse: "ok", data: null, threadId: "t" };
2693
+ }
2694
+ );
2695
+
2696
+ await workflow("go", {});
2697
+
2698
+ const handle = ghMock.mock.results.at(-1)?.value as {
2699
+ signal: ReturnType<typeof vi.fn>;
2700
+ };
2701
+ const payload = handle.signal.mock.calls.at(-1)?.[1] as {
2702
+ childWorkflowId: string;
2703
+ sandboxId: string;
2704
+ baseSnapshot?: unknown;
2705
+ };
2706
+ expect(payload).toEqual({
2707
+ childWorkflowId: "child-wf-1",
2708
+ sandboxId: "sb-1",
2709
+ });
2710
+ expect(payload.baseSnapshot).toBeUndefined();
2711
+ });
2712
+
2713
+ it("skips the signal when the sandbox is reused (continue mode)", async () => {
2714
+ const { getExternalWorkflowHandle } = await import("@temporalio/workflow");
2715
+ const ghMock = getExternalWorkflowHandle as ReturnType<typeof vi.fn>;
2716
+
2717
+ const workflow = defineSubagentWorkflow(
2718
+ { name: "test", description: "test agent" },
2719
+ async (_prompt, sessionInput) => {
2720
+ sessionInput.onSandboxReady?.({ sandboxId: "sb-1" });
2721
+ return { toolResponse: "ok", data: null, threadId: "t" };
2722
+ }
2723
+ );
2724
+
2725
+ await workflow("go", {
2726
+ sandbox: { mode: "continue", sandboxId: "sb-1" },
2727
+ });
2728
+
2729
+ const handle = ghMock.mock.results.at(-1)?.value as {
2730
+ signal: ReturnType<typeof vi.fn>;
2731
+ };
2732
+ expect(handle.signal).not.toHaveBeenCalled();
2733
+ });
2210
2734
  });