zeitlich 0.2.35 → 0.2.37

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 (199) hide show
  1. package/README.md +146 -92
  2. package/dist/{activities-BVI2lTwr.d.ts → activities-Bb-nAjwQ.d.ts} +2 -2
  3. package/dist/{activities-hd4aNnZE.d.cts → activities-vkI4_3CC.d.cts} +2 -2
  4. package/dist/adapters/sandbox/bedrock/index.cjs +14 -11
  5. package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -1
  6. package/dist/adapters/sandbox/bedrock/index.d.cts +4 -3
  7. package/dist/adapters/sandbox/bedrock/index.d.ts +4 -3
  8. package/dist/adapters/sandbox/bedrock/index.js +14 -11
  9. package/dist/adapters/sandbox/bedrock/index.js.map +1 -1
  10. package/dist/adapters/sandbox/bedrock/workflow.cjs +2 -0
  11. package/dist/adapters/sandbox/bedrock/workflow.cjs.map +1 -1
  12. package/dist/adapters/sandbox/bedrock/workflow.d.cts +2 -2
  13. package/dist/adapters/sandbox/bedrock/workflow.d.ts +2 -2
  14. package/dist/adapters/sandbox/bedrock/workflow.js +2 -0
  15. package/dist/adapters/sandbox/bedrock/workflow.js.map +1 -1
  16. package/dist/adapters/sandbox/daytona/index.cjs +35 -6
  17. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  18. package/dist/adapters/sandbox/daytona/index.d.cts +3 -1
  19. package/dist/adapters/sandbox/daytona/index.d.ts +3 -1
  20. package/dist/adapters/sandbox/daytona/index.js +35 -6
  21. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  22. package/dist/adapters/sandbox/daytona/workflow.cjs +2 -0
  23. package/dist/adapters/sandbox/daytona/workflow.cjs.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/daytona/workflow.js +2 -0
  27. package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
  28. package/dist/adapters/sandbox/e2b/index.cjs +59 -10
  29. package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
  30. package/dist/adapters/sandbox/e2b/index.d.cts +5 -3
  31. package/dist/adapters/sandbox/e2b/index.d.ts +5 -3
  32. package/dist/adapters/sandbox/e2b/index.js +59 -10
  33. package/dist/adapters/sandbox/e2b/index.js.map +1 -1
  34. package/dist/adapters/sandbox/e2b/workflow.cjs +2 -0
  35. package/dist/adapters/sandbox/e2b/workflow.cjs.map +1 -1
  36. package/dist/adapters/sandbox/e2b/workflow.d.cts +1 -1
  37. package/dist/adapters/sandbox/e2b/workflow.d.ts +1 -1
  38. package/dist/adapters/sandbox/e2b/workflow.js +2 -0
  39. package/dist/adapters/sandbox/e2b/workflow.js.map +1 -1
  40. package/dist/adapters/sandbox/inmemory/index.cjs +5 -0
  41. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  42. package/dist/adapters/sandbox/inmemory/index.d.cts +2 -1
  43. package/dist/adapters/sandbox/inmemory/index.d.ts +2 -1
  44. package/dist/adapters/sandbox/inmemory/index.js +5 -0
  45. package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
  46. package/dist/adapters/sandbox/inmemory/workflow.cjs +2 -0
  47. package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -1
  48. package/dist/adapters/sandbox/inmemory/workflow.d.cts +1 -1
  49. package/dist/adapters/sandbox/inmemory/workflow.d.ts +1 -1
  50. package/dist/adapters/sandbox/inmemory/workflow.js +2 -0
  51. package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
  52. package/dist/adapters/thread/anthropic/index.cjs +71 -36
  53. package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
  54. package/dist/adapters/thread/anthropic/index.d.cts +5 -5
  55. package/dist/adapters/thread/anthropic/index.d.ts +5 -5
  56. package/dist/adapters/thread/anthropic/index.js +71 -36
  57. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  58. package/dist/adapters/thread/anthropic/workflow.cjs +5 -1
  59. package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -1
  60. package/dist/adapters/thread/anthropic/workflow.d.cts +5 -5
  61. package/dist/adapters/thread/anthropic/workflow.d.ts +5 -5
  62. package/dist/adapters/thread/anthropic/workflow.js +5 -1
  63. package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
  64. package/dist/adapters/thread/google-genai/index.cjs +50 -25
  65. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  66. package/dist/adapters/thread/google-genai/index.d.cts +5 -5
  67. package/dist/adapters/thread/google-genai/index.d.ts +5 -5
  68. package/dist/adapters/thread/google-genai/index.js +50 -25
  69. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  70. package/dist/adapters/thread/google-genai/workflow.cjs +5 -1
  71. package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
  72. package/dist/adapters/thread/google-genai/workflow.d.cts +5 -5
  73. package/dist/adapters/thread/google-genai/workflow.d.ts +5 -5
  74. package/dist/adapters/thread/google-genai/workflow.js +5 -1
  75. package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
  76. package/dist/adapters/thread/langchain/index.cjs +34 -7
  77. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  78. package/dist/adapters/thread/langchain/index.d.cts +5 -5
  79. package/dist/adapters/thread/langchain/index.d.ts +5 -5
  80. package/dist/adapters/thread/langchain/index.js +34 -7
  81. package/dist/adapters/thread/langchain/index.js.map +1 -1
  82. package/dist/adapters/thread/langchain/workflow.cjs +5 -1
  83. package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
  84. package/dist/adapters/thread/langchain/workflow.d.cts +5 -5
  85. package/dist/adapters/thread/langchain/workflow.d.ts +5 -5
  86. package/dist/adapters/thread/langchain/workflow.js +5 -1
  87. package/dist/adapters/thread/langchain/workflow.js.map +1 -1
  88. package/dist/index.cjs +206 -120
  89. package/dist/index.cjs.map +1 -1
  90. package/dist/index.d.cts +17 -11
  91. package/dist/index.d.ts +17 -11
  92. package/dist/index.js +207 -121
  93. package/dist/index.js.map +1 -1
  94. package/dist/{proxy-BjdFGPTm.d.ts → proxy-0smGKvx8.d.ts} +1 -1
  95. package/dist/{proxy-7RnVaPdJ.d.cts → proxy-DEtowJyd.d.cts} +1 -1
  96. package/dist/{thread-manager-DjN5JYul.d.ts → thread-manager-3fszQih4.d.ts} +2 -2
  97. package/dist/{thread-manager-CbpiGq1L.d.ts → thread-manager-C-C4pI2z.d.ts} +2 -2
  98. package/dist/{thread-manager-BBzNgQWH.d.cts → thread-manager-CzYln2OC.d.cts} +2 -2
  99. package/dist/{thread-manager-DzXm9eeI.d.cts → thread-manager-D4vgzYrh.d.cts} +2 -2
  100. package/dist/{types-yiXmqedU.d.ts → types-B37hKoWA.d.ts} +1 -1
  101. package/dist/{types-DQ1l_gXL.d.cts → types-BO7Yju20.d.cts} +63 -14
  102. package/dist/{types-wiGLvxWf.d.ts → types-CNuWnvy9.d.ts} +1 -1
  103. package/dist/{types-CADc5V_P.d.ts → types-CPKDl-y_.d.ts} +63 -14
  104. package/dist/{types-Mc_4BCfT.d.cts → types-D08CXPh8.d.cts} +1 -1
  105. package/dist/{types-CBH54cwr.d.cts → types-DWEUmYAJ.d.cts} +1 -1
  106. package/dist/{types-DxCpFNv_.d.cts → types-tQL9njTu.d.cts} +25 -0
  107. package/dist/{types-DxCpFNv_.d.ts → types-tQL9njTu.d.ts} +25 -0
  108. package/dist/{workflow-P2pTSfKu.d.ts → workflow-CjXHbZZc.d.ts} +2 -2
  109. package/dist/{workflow-DhtWRovz.d.cts → workflow-Do_lzJpT.d.cts} +2 -2
  110. package/dist/workflow.cjs +182 -114
  111. package/dist/workflow.cjs.map +1 -1
  112. package/dist/workflow.d.cts +3 -3
  113. package/dist/workflow.d.ts +3 -3
  114. package/dist/workflow.js +183 -115
  115. package/dist/workflow.js.map +1 -1
  116. package/package.json +1 -1
  117. package/src/adapters/sandbox/bedrock/filesystem.ts +6 -12
  118. package/src/adapters/sandbox/bedrock/index.ts +10 -8
  119. package/src/adapters/sandbox/bedrock/proxy.ts +2 -0
  120. package/src/adapters/sandbox/daytona/filesystem.ts +29 -6
  121. package/src/adapters/sandbox/daytona/index.ts +6 -0
  122. package/src/adapters/sandbox/daytona/proxy.ts +2 -0
  123. package/src/adapters/sandbox/e2b/filesystem.ts +5 -4
  124. package/src/adapters/sandbox/e2b/index.ts +63 -12
  125. package/src/adapters/sandbox/e2b/proxy.ts +2 -0
  126. package/src/adapters/sandbox/inmemory/index.ts +5 -0
  127. package/src/adapters/sandbox/inmemory/proxy.ts +2 -0
  128. package/src/adapters/thread/anthropic/activities.ts +49 -26
  129. package/src/adapters/thread/anthropic/model-invoker.ts +15 -6
  130. package/src/adapters/thread/anthropic/proxy.ts +6 -2
  131. package/src/adapters/thread/anthropic/thread-manager.test.ts +26 -7
  132. package/src/adapters/thread/anthropic/thread-manager.ts +60 -46
  133. package/src/adapters/thread/google-genai/activities.ts +7 -2
  134. package/src/adapters/thread/google-genai/model-invoker.ts +26 -8
  135. package/src/adapters/thread/google-genai/proxy.ts +6 -2
  136. package/src/adapters/thread/google-genai/thread-manager.test.ts +13 -3
  137. package/src/adapters/thread/google-genai/thread-manager.ts +54 -33
  138. package/src/adapters/thread/langchain/activities.ts +46 -24
  139. package/src/adapters/thread/langchain/hooks.test.ts +36 -49
  140. package/src/adapters/thread/langchain/hooks.ts +18 -5
  141. package/src/adapters/thread/langchain/model-invoker.ts +3 -3
  142. package/src/adapters/thread/langchain/proxy.ts +6 -2
  143. package/src/adapters/thread/langchain/thread-manager.test.ts +5 -1
  144. package/src/adapters/thread/langchain/thread-manager.ts +20 -9
  145. package/src/index.ts +4 -1
  146. package/src/lib/activity.ts +16 -6
  147. package/src/lib/hooks/types.ts +6 -6
  148. package/src/lib/lifecycle.ts +9 -1
  149. package/src/lib/model/proxy.ts +2 -2
  150. package/src/lib/observability/hooks.ts +4 -5
  151. package/src/lib/observability/index.ts +1 -4
  152. package/src/lib/sandbox/manager.ts +21 -4
  153. package/src/lib/sandbox/node-fs.ts +3 -6
  154. package/src/lib/sandbox/sandbox.test.ts +36 -3
  155. package/src/lib/sandbox/tree.integration.test.ts +10 -3
  156. package/src/lib/sandbox/types.ts +35 -1
  157. package/src/lib/session/session-edge-cases.integration.test.ts +51 -13
  158. package/src/lib/session/session.integration.test.ts +139 -0
  159. package/src/lib/session/session.ts +50 -19
  160. package/src/lib/session/types.ts +13 -5
  161. package/src/lib/skills/fs-provider.ts +12 -8
  162. package/src/lib/skills/handler.ts +1 -1
  163. package/src/lib/skills/parse.ts +3 -1
  164. package/src/lib/skills/register.ts +1 -3
  165. package/src/lib/skills/skills.integration.test.ts +25 -15
  166. package/src/lib/state/manager.integration.test.ts +12 -2
  167. package/src/lib/subagent/define.ts +1 -1
  168. package/src/lib/subagent/handler.ts +186 -71
  169. package/src/lib/subagent/index.ts +1 -5
  170. package/src/lib/subagent/register.ts +3 -2
  171. package/src/lib/subagent/signals.ts +1 -10
  172. package/src/lib/subagent/subagent.integration.test.ts +438 -156
  173. package/src/lib/subagent/tool.ts +4 -3
  174. package/src/lib/subagent/types.ts +50 -20
  175. package/src/lib/subagent/workflow.ts +9 -49
  176. package/src/lib/thread/id.test.ts +1 -1
  177. package/src/lib/thread/id.ts +1 -2
  178. package/src/lib/thread/proxy.ts +3 -4
  179. package/src/lib/thread/types.ts +11 -3
  180. package/src/lib/tool-router/index.ts +1 -5
  181. package/src/lib/tool-router/router-edge-cases.integration.test.ts +1 -1
  182. package/src/lib/tool-router/router.ts +3 -2
  183. package/src/lib/tool-router/types.ts +11 -3
  184. package/src/lib/tool-router/with-sandbox.ts +19 -5
  185. package/src/lib/virtual-fs/filesystem.ts +1 -1
  186. package/src/lib/virtual-fs/index.ts +5 -1
  187. package/src/lib/virtual-fs/mutations.ts +2 -4
  188. package/src/lib/virtual-fs/queries.ts +9 -5
  189. package/src/lib/virtual-fs/types.ts +4 -1
  190. package/src/lib/virtual-fs/virtual-fs.test.ts +9 -11
  191. package/src/lib/workflow.test.ts +7 -4
  192. package/src/lib/workflow.ts +1 -5
  193. package/src/tools/ask-user-question/tool.ts +1 -3
  194. package/src/tools/glob/handler.ts +1 -4
  195. package/src/tools/task-get/handler.ts +4 -5
  196. package/src/tools/task-list/handler.ts +1 -4
  197. package/src/tools/task-update/handler.ts +4 -5
  198. package/src/workflow.ts +20 -7
  199. package/tsup.config.ts +9 -6
@@ -1,9 +1,10 @@
1
1
  import {
2
- startChild,
3
2
  workflowInfo,
4
3
  setHandler,
5
4
  condition,
6
5
  log,
6
+ ApplicationFailure,
7
+ executeChild,
7
8
  } from "@temporalio/workflow";
8
9
  import { getShortId } from "../thread/id";
9
10
  import type { ToolHandlerResponse, RouterContext } from "../tool-router";
@@ -11,7 +12,6 @@ import type { JsonValue } from "../state/types";
11
12
  import type {
12
13
  InferSubagentResult,
13
14
  SubagentConfig,
14
- SubagentHandlerResponse,
15
15
  SubagentSandboxConfig,
16
16
  SubagentWorkflowInput,
17
17
  } from "./types";
@@ -22,17 +22,14 @@ import type {
22
22
  SandboxInit,
23
23
  SubagentSandboxShutdown,
24
24
  } from "../lifecycle";
25
- import {
26
- childResultSignal,
27
- childSandboxReadySignal,
28
- destroySandboxSignal,
29
- } from "./signals";
25
+ import type { SandboxOps, SandboxSnapshot } from "../sandbox/types";
26
+ import { childSandboxReadySignal } from "./signals";
30
27
 
31
28
  /** Normalized sandbox config after resolving the union. */
32
29
  interface ResolvedSandboxConfig {
33
30
  source: "none" | "inherit" | "own";
34
31
  init: "per-call" | "once";
35
- continuation: "continue" | "fork";
32
+ continuation: "continue" | "fork" | "snapshot";
36
33
  shutdown?: SubagentSandboxShutdown;
37
34
  }
38
35
 
@@ -61,9 +58,9 @@ function resolveSandboxConfig(
61
58
  /**
62
59
  * Creates a Subagent tool handler that spawns child workflows for configured subagents.
63
60
  *
64
- * Child workflows signal their result back via `childResultSignal` instead of
65
- * returning it as the workflow return value. The handler awaits the signal
66
- * before continuing.
61
+ * Sandbox and snapshot cleanup happens inside the parent via each subagent's
62
+ * `sandbox.proxy` the proxy factory is invoked once per subagent with
63
+ * `scope = agentName` so it resolves to the same activities the child uses.
67
64
  *
68
65
  * @param subagents - Array of subagent configurations
69
66
  * @returns A tool handler function that can be used with the tool router
@@ -78,13 +75,27 @@ export function createSubagentHandler<
78
75
  context: RouterContext
79
76
  ) => Promise<ToolHandlerResponse<InferSubagentResult<T[number]> | null>>;
80
77
  destroySubagentSandboxes: () => Promise<void>;
78
+ cleanupSubagentSnapshots: () => Promise<void>;
81
79
  } {
82
80
  const { taskQueue: parentTaskQueue } = workflowInfo();
83
81
 
84
- const childResults = new Map<string, SubagentHandlerResponse>();
82
+ /** Sandbox ops proxy per subagent, built eagerly from `sandbox.proxy` factories. */
83
+ const agentSandboxOps = new Map<string, SandboxOps>();
84
+ for (const cfg of subagents) {
85
+ if (cfg.sandbox && cfg.sandbox !== "none") {
86
+ agentSandboxOps.set(cfg.agentName, cfg.sandbox.proxy(cfg.agentName));
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Sandboxes that outlived their child session and must be destroyed by the
92
+ * parent at shutdown (shutdown = `pause-until-parent-close` /
93
+ * `keep-until-parent-close`). Keyed by `persistent:<agent>` for lazy
94
+ * shared sandboxes and by childWorkflowId otherwise.
95
+ */
85
96
  const pendingDestroys = new Map<
86
97
  string,
87
- Awaited<ReturnType<typeof startChild>>
98
+ { agentName: string; sandboxId: string }
88
99
  >();
89
100
  /** Maps childThreadId → sandboxId for sandbox continuation across invocations (init: per-call) */
90
101
  const threadSandboxes = new Map<string, string>();
@@ -94,21 +105,26 @@ export function createSubagentHandler<
94
105
  const persistentSandboxCreating = new Set<string>();
95
106
  /** Reverse lookup: childWorkflowId → agentName for in-flight lazy creators */
96
107
  const lazyCreatorAgent = new Map<string, string>();
97
-
98
- setHandler(childResultSignal, ({ childWorkflowId, result }) => {
99
- childResults.set(childWorkflowId, result);
100
- });
101
-
102
- setHandler(
103
- childSandboxReadySignal,
104
- ({ childWorkflowId, sandboxId }) => {
105
- const agentName = lazyCreatorAgent.get(childWorkflowId);
106
- if (agentName && !persistentSandboxes.has(agentName)) {
107
- persistentSandboxes.set(agentName, sandboxId);
108
- lazyCreatorAgent.delete(childWorkflowId);
109
- }
108
+ /** Maps childThreadId → latest snapshot for sandbox continuation via snapshots */
109
+ const threadSnapshots = new Map<
110
+ string,
111
+ {
112
+ agentName: string;
113
+ snapshot: SandboxSnapshot;
110
114
  }
111
- );
115
+ >();
116
+ /** Maps agentName → reusable base snapshot captured on first-ever call (init: once + continuation: "snapshot") */
117
+ const persistentBaseSnapshot = new Map<string, SandboxSnapshot>();
118
+ /** Tracks agents whose first snapshot-backed sandbox creation is in-flight */
119
+ const persistentBaseSnapshotCreating = new Set<string>();
120
+
121
+ setHandler(childSandboxReadySignal, ({ childWorkflowId, sandboxId }) => {
122
+ const agentName = lazyCreatorAgent.get(childWorkflowId);
123
+ if (agentName && !persistentSandboxes.has(agentName)) {
124
+ persistentSandboxes.set(agentName, sandboxId);
125
+ lazyCreatorAgent.delete(childWorkflowId);
126
+ }
127
+ });
112
128
 
113
129
  const handler = async (
114
130
  args: SubagentArgs,
@@ -127,6 +143,16 @@ export function createSubagentHandler<
127
143
  const { sandboxId: parentSandboxId } = context;
128
144
  const sandboxCfg = resolveSandboxConfig(config.sandbox);
129
145
 
146
+ if (
147
+ sandboxCfg.source !== "none" &&
148
+ !agentSandboxOps.has(config.agentName)
149
+ ) {
150
+ throw ApplicationFailure.create({
151
+ message: `Subagent "${config.agentName}" uses a sandbox but no \`sandbox.proxy\` is configured on its SubagentConfig`,
152
+ nonRetryable: true,
153
+ });
154
+ }
155
+
130
156
  if (sandboxCfg.source === "inherit" && !parentSandboxId) {
131
157
  throw new Error(
132
158
  `Subagent "${config.agentName}" is configured with sandbox: "inherit" but the parent has no sandbox`
@@ -151,13 +177,50 @@ export function createSubagentHandler<
151
177
  let sandbox: SandboxInit | undefined;
152
178
  let sandboxShutdownOverride: SubagentSandboxShutdown | undefined;
153
179
  let isLazyCreator = false;
180
+ let isSnapshotBaseCreator = false;
154
181
 
155
182
  if (sandboxCfg.source === "inherit" && parentSandboxId) {
156
183
  if (sandboxCfg.continuation === "fork") {
157
184
  sandbox = { mode: "fork", sandboxId: parentSandboxId };
185
+ } else if (sandboxCfg.continuation === "snapshot") {
186
+ throw new Error(
187
+ `Subagent "${config.agentName}" has sandbox source "inherit" with continuation "snapshot" — snapshot continuation is only supported for source "own"`
188
+ );
158
189
  } else {
159
190
  sandbox = { mode: "inherit", sandboxId: parentSandboxId };
160
191
  }
192
+ } else if (
193
+ sandboxCfg.source === "own" &&
194
+ sandboxCfg.continuation === "snapshot"
195
+ ) {
196
+ // Snapshot-driven continuation: each call boots a fresh sandbox from a
197
+ // stored snapshot (per-thread, or a per-agent base for new threads with
198
+ // init: "once"). The session destroys its sandbox inline on exit;
199
+ // stored snapshot IDs are cleaned up by the parent at shutdown.
200
+ const isLazy = sandboxCfg.init === "once";
201
+
202
+ let baseSnap: SandboxSnapshot | undefined;
203
+ if (continuationThreadId) {
204
+ baseSnap = threadSnapshots.get(continuationThreadId)?.snapshot;
205
+ }
206
+
207
+ if (!baseSnap && isLazy) {
208
+ baseSnap = persistentBaseSnapshot.get(config.agentName);
209
+ if (!baseSnap) {
210
+ if (persistentBaseSnapshotCreating.has(config.agentName)) {
211
+ await condition(() => persistentBaseSnapshot.has(config.agentName));
212
+ baseSnap = persistentBaseSnapshot.get(config.agentName);
213
+ } else {
214
+ persistentBaseSnapshotCreating.add(config.agentName);
215
+ isSnapshotBaseCreator = true;
216
+ }
217
+ }
218
+ }
219
+
220
+ if (baseSnap) {
221
+ sandbox = { mode: "from-snapshot", snapshot: baseSnap };
222
+ }
223
+ sandboxShutdownOverride = "snapshot";
161
224
  } else if (sandboxCfg.source === "own") {
162
225
  const isLazy = sandboxCfg.init === "once";
163
226
 
@@ -246,45 +309,11 @@ export function createSubagentHandler<
246
309
  sandboxSource: sandboxCfg.source,
247
310
  });
248
311
 
249
- const childHandle = await startChild(config.workflow, childOpts);
312
+ const childResult = await executeChild(config.workflow, childOpts);
250
313
 
251
- // Track child handles that need signaling at parent shutdown.
252
314
  const effectiveShutdown =
253
315
  sandboxShutdownOverride ?? sandboxCfg.shutdown ?? "destroy";
254
316
 
255
- if (
256
- effectiveShutdown === "pause-until-parent-close" ||
257
- effectiveShutdown === "keep-until-parent-close"
258
- ) {
259
- const key = isLazyCreator
260
- ? `persistent:${config.agentName}`
261
- : childWorkflowId;
262
- pendingDestroys.set(key, childHandle);
263
- }
264
-
265
- // Wait for signal from child; race with child completion to propagate failures
266
- await Promise.race([
267
- condition(() => childResults.has(childWorkflowId)),
268
- childHandle.result(),
269
- ]);
270
- if (!childResults.has(childWorkflowId)) {
271
- await condition(() => childResults.has(childWorkflowId));
272
- }
273
-
274
- const childResult = childResults.get(childWorkflowId);
275
- childResults.delete(childWorkflowId);
276
-
277
- if (!childResult) {
278
- log.warn("subagent returned no result", {
279
- subagent: config.agentName,
280
- childWorkflowId,
281
- });
282
- return {
283
- toolResponse: "Subagent workflow did not signal a result",
284
- data: null,
285
- };
286
- }
287
-
288
317
  log.info("subagent completed", {
289
318
  subagent: config.agentName,
290
319
  childWorkflowId,
@@ -297,27 +326,71 @@ export function createSubagentHandler<
297
326
  usage,
298
327
  threadId: childThreadId,
299
328
  sandboxId: childSandboxId,
329
+ snapshot: childSnapshot,
330
+ baseSnapshot: childBaseSnapshot,
300
331
  metadata,
301
332
  } = childResult;
302
333
 
303
- // Store sandbox ID for future continuation/fork
304
334
  if (childSandboxId) {
305
335
  if (
306
336
  sandboxCfg.source === "own" &&
307
337
  sandboxCfg.init === "once" &&
338
+ sandboxCfg.continuation !== "snapshot" &&
308
339
  !persistentSandboxes.has(config.agentName)
309
340
  ) {
310
341
  // Fallback: signal may have already set this via childSandboxReadySignal
311
342
  persistentSandboxes.set(config.agentName, childSandboxId);
312
- } else if (allowsContinuation && childThreadId) {
343
+ } else if (
344
+ allowsContinuation &&
345
+ childThreadId &&
346
+ sandboxCfg.source === "own" &&
347
+ sandboxCfg.continuation !== "snapshot"
348
+ ) {
313
349
  threadSandboxes.set(childThreadId, childSandboxId);
314
350
  }
315
351
  }
316
352
 
353
+ // Track sandboxes that must be destroyed by the parent at shutdown.
354
+ if (
355
+ childSandboxId &&
356
+ (effectiveShutdown === "pause-until-parent-close" ||
357
+ effectiveShutdown === "keep-until-parent-close")
358
+ ) {
359
+ const key = isLazyCreator
360
+ ? `persistent:${config.agentName}`
361
+ : childWorkflowId;
362
+ pendingDestroys.set(key, {
363
+ agentName: config.agentName,
364
+ sandboxId: childSandboxId,
365
+ });
366
+ }
367
+
368
+ // Store snapshots for future snapshot-driven continuation and final sweep.
369
+ // Tag each with `agentName` so `cleanupSubagentSnapshots` knows which
370
+ // sandbox ops to call for deletion.
371
+ if (sandboxCfg.source === "own" && sandboxCfg.continuation === "snapshot") {
372
+ if (childSnapshot && childThreadId) {
373
+ threadSnapshots.set(childThreadId, {
374
+ agentName: config.agentName,
375
+ snapshot: childSnapshot,
376
+ });
377
+ }
378
+ if (
379
+ isSnapshotBaseCreator &&
380
+ childBaseSnapshot &&
381
+ !persistentBaseSnapshot.has(config.agentName)
382
+ ) {
383
+ persistentBaseSnapshot.set(config.agentName, childBaseSnapshot);
384
+ }
385
+ }
386
+
317
387
  if (isLazyCreator) {
318
388
  persistentSandboxCreating.delete(config.agentName);
319
389
  lazyCreatorAgent.delete(childWorkflowId);
320
390
  }
391
+ if (isSnapshotBaseCreator) {
392
+ persistentBaseSnapshotCreating.delete(config.agentName);
393
+ }
321
394
 
322
395
  if (!toolResponse) {
323
396
  return {
@@ -355,7 +428,9 @@ export function createSubagentHandler<
355
428
 
356
429
  return {
357
430
  toolResponse: finalToolResponse,
358
- data: validated ? validated.data : data,
431
+ data: validated
432
+ ? validated.data
433
+ : (data as InferSubagentResult<T[number]> | null),
359
434
  ...(usage && { usage }),
360
435
  ...(childSandboxId && { sandboxId: childSandboxId }),
361
436
  ...(metadata && { metadata }),
@@ -363,15 +438,55 @@ export function createSubagentHandler<
363
438
  };
364
439
 
365
440
  const destroySubagentSandboxes = async (): Promise<void> => {
366
- const handles = [...pendingDestroys.values()];
441
+ const entries = [...pendingDestroys.values()];
367
442
  pendingDestroys.clear();
368
443
  await Promise.all(
369
- handles.map(async (handle) => {
444
+ entries.map(async ({ agentName, sandboxId }) => {
445
+ const ops = agentSandboxOps.get(agentName);
446
+ if (!ops) {
447
+ log.warn(
448
+ "Skipping sandbox destroy — no sandbox.proxy registered for agent",
449
+ { agentName, sandboxId }
450
+ );
451
+ return;
452
+ }
453
+ try {
454
+ await ops.destroySandbox(sandboxId);
455
+ } catch (err) {
456
+ log.warn("Failed to destroy subagent sandbox", {
457
+ agentName,
458
+ sandboxId,
459
+ error: err,
460
+ });
461
+ }
462
+ })
463
+ );
464
+ };
465
+
466
+ const cleanupSubagentSnapshots = async (): Promise<void> => {
467
+ const tagged = [];
468
+ for (const entry of threadSnapshots.values()) tagged.push(entry);
469
+ for (const [agentName, snapshot] of persistentBaseSnapshot.entries()) {
470
+ tagged.push({ agentName, snapshot });
471
+ }
472
+ threadSnapshots.clear();
473
+ persistentBaseSnapshot.clear();
474
+
475
+ await Promise.all(
476
+ tagged.map(async ({ agentName, snapshot }) => {
477
+ const ops = agentSandboxOps.get(agentName);
478
+ if (!ops) {
479
+ log.warn(
480
+ "Skipping snapshot delete — no sandbox.proxy registered for agent",
481
+ { agentName }
482
+ );
483
+ return;
484
+ }
370
485
  try {
371
- await handle.signal(destroySandboxSignal);
372
- await handle.result();
486
+ await ops.deleteSandboxSnapshot(snapshot);
373
487
  } catch (err) {
374
- log.warn("Failed to signal destroySandbox to child workflow", {
488
+ log.warn("Failed to delete subagent snapshot", {
489
+ agentName,
375
490
  error: err,
376
491
  });
377
492
  }
@@ -379,5 +494,5 @@ export function createSubagentHandler<
379
494
  );
380
495
  };
381
496
 
382
- return { handler, destroySubagentSandboxes };
497
+ return { handler, destroySubagentSandboxes, cleanupSubagentSnapshots };
383
498
  }
@@ -17,8 +17,4 @@ export { createSubagentHandler } from "./handler";
17
17
  export { defineSubagent } from "./define";
18
18
  export { defineSubagentWorkflow } from "./workflow";
19
19
  export { buildSubagentRegistration } from "./register";
20
- export {
21
- childResultSignal,
22
- childSandboxReadySignal,
23
- destroySandboxSignal,
24
- } from "./signals";
20
+ export { childSandboxReadySignal } from "./signals";
@@ -26,6 +26,7 @@ import { createSubagentHandler } from "./handler";
26
26
  export function buildSubagentRegistration(subagents: SubagentConfig[]): {
27
27
  registration: ToolMap[string];
28
28
  destroySubagentSandboxes: () => Promise<void>;
29
+ cleanupSubagentSnapshots: () => Promise<void>;
29
30
  } | null {
30
31
  if (subagents.length === 0) return null;
31
32
 
@@ -42,7 +43,7 @@ export function buildSubagentRegistration(subagents: SubagentConfig[]): {
42
43
  const resolveSubagentName = (args: unknown): string =>
43
44
  (args as SubagentArgs).subagent;
44
45
 
45
- const { handler, destroySubagentSandboxes } =
46
+ const { handler, destroySubagentSandboxes, cleanupSubagentSnapshots } =
46
47
  createSubagentHandler(subagents);
47
48
 
48
49
  const registration: ToolMap[string] = {
@@ -72,5 +73,5 @@ export function buildSubagentRegistration(subagents: SubagentConfig[]): {
72
73
  }),
73
74
  };
74
75
 
75
- return { registration, destroySubagentSandboxes };
76
+ return { registration, destroySubagentSandboxes, cleanupSubagentSnapshots };
76
77
  }
@@ -1,15 +1,6 @@
1
1
  import { defineSignal } from "@temporalio/workflow";
2
- import type {
3
- ChildResultSignalPayload,
4
- ChildSandboxReadySignalPayload,
5
- } from "./types";
6
-
7
- export const childResultSignal =
8
- defineSignal<[ChildResultSignalPayload]>("childResult");
2
+ import type { ChildSandboxReadySignalPayload } from "./types";
9
3
 
10
4
  /** Sent by a child workflow as soon as its sandbox is created, before the agent loop starts. */
11
5
  export const childSandboxReadySignal =
12
6
  defineSignal<[ChildSandboxReadySignalPayload]>("childSandboxReady");
13
-
14
- /** Sent by the parent to tell a subagent it may destroy its sandbox. */
15
- export const destroySandboxSignal = defineSignal("destroySandbox");