zeitlich 0.2.36 → 0.2.38

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 (204) hide show
  1. package/README.md +146 -92
  2. package/dist/{activities-BVI2lTwr.d.ts → activities-BKhMtKDd.d.ts} +4 -2
  3. package/dist/{activities-hd4aNnZE.d.cts → activities-CDcwkRZs.d.cts} +4 -2
  4. package/dist/adapters/sandbox/bedrock/index.cjs +17 -14
  5. package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -1
  6. package/dist/adapters/sandbox/bedrock/index.d.cts +7 -6
  7. package/dist/adapters/sandbox/bedrock/index.d.ts +7 -6
  8. package/dist/adapters/sandbox/bedrock/index.js +17 -14
  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 +11 -3
  17. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  18. package/dist/adapters/sandbox/daytona/index.d.cts +5 -4
  19. package/dist/adapters/sandbox/daytona/index.d.ts +5 -4
  20. package/dist/adapters/sandbox/daytona/index.js +11 -3
  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 +73 -12
  29. package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
  30. package/dist/adapters/sandbox/e2b/index.d.cts +26 -4
  31. package/dist/adapters/sandbox/e2b/index.d.ts +26 -4
  32. package/dist/adapters/sandbox/e2b/index.js +73 -12
  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 +8 -3
  41. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  42. package/dist/adapters/sandbox/inmemory/index.d.cts +5 -4
  43. package/dist/adapters/sandbox/inmemory/index.d.ts +5 -4
  44. package/dist/adapters/sandbox/inmemory/index.js +8 -3
  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 +94 -39
  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 +94 -39
  57. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  58. package/dist/adapters/thread/anthropic/workflow.cjs +7 -2
  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 +7 -2
  63. package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
  64. package/dist/adapters/thread/google-genai/index.cjs +77 -28
  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 +77 -28
  69. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  70. package/dist/adapters/thread/google-genai/workflow.cjs +7 -2
  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 +7 -2
  75. package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
  76. package/dist/adapters/thread/langchain/index.cjs +57 -10
  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 +57 -10
  81. package/dist/adapters/thread/langchain/index.js.map +1 -1
  82. package/dist/adapters/thread/langchain/workflow.cjs +7 -2
  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 +7 -2
  87. package/dist/adapters/thread/langchain/workflow.js.map +1 -1
  88. package/dist/index.cjs +322 -146
  89. package/dist/index.cjs.map +1 -1
  90. package/dist/index.d.cts +20 -14
  91. package/dist/index.d.ts +20 -14
  92. package/dist/index.js +323 -147
  93. package/dist/index.js.map +1 -1
  94. package/dist/{proxy-BjdFGPTm.d.ts → proxy-CUlKSvZS.d.ts} +1 -1
  95. package/dist/{proxy-7RnVaPdJ.d.cts → proxy-D_3x7RN4.d.cts} +1 -1
  96. package/dist/{thread-manager-CbpiGq1L.d.ts → thread-manager-CVu7o2cs.d.ts} +4 -2
  97. package/dist/{thread-manager-DzXm9eeI.d.cts → thread-manager-HSwyh28L.d.cts} +4 -2
  98. package/dist/{thread-manager-BBzNgQWH.d.cts → thread-manager-c1gPopAG.d.ts} +4 -2
  99. package/dist/{thread-manager-DjN5JYul.d.ts → thread-manager-wGi-LqIP.d.cts} +4 -2
  100. package/dist/{types-Mc_4BCfT.d.cts → types-BH_IRryz.d.ts} +10 -1
  101. package/dist/{types-yiXmqedU.d.ts → types-BaOw4hKI.d.cts} +10 -1
  102. package/dist/{types-DQ1l_gXL.d.cts → types-C06FwR96.d.cts} +121 -17
  103. package/dist/{types-wiGLvxWf.d.ts → types-DAsQ21Rt.d.ts} +1 -1
  104. package/dist/{types-CADc5V_P.d.ts → types-DNr31FzL.d.ts} +121 -17
  105. package/dist/{types-CBH54cwr.d.cts → types-lm8tMNJQ.d.cts} +1 -1
  106. package/dist/{types-DxCpFNv_.d.cts → types-yx0LzPGn.d.cts} +44 -5
  107. package/dist/{types-DxCpFNv_.d.ts → types-yx0LzPGn.d.ts} +44 -5
  108. package/dist/{workflow-DhtWRovz.d.cts → workflow-CSCkpwAL.d.ts} +2 -2
  109. package/dist/{workflow-P2pTSfKu.d.ts → workflow-DuvMZ8Vm.d.cts} +2 -2
  110. package/dist/workflow.cjs +274 -130
  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 +275 -131
  115. package/dist/workflow.js.map +1 -1
  116. package/package.json +2 -2
  117. package/src/adapters/sandbox/bedrock/filesystem.ts +6 -12
  118. package/src/adapters/sandbox/bedrock/index.ts +22 -11
  119. package/src/adapters/sandbox/bedrock/proxy.ts +2 -0
  120. package/src/adapters/sandbox/daytona/index.ts +18 -3
  121. package/src/adapters/sandbox/daytona/proxy.ts +2 -0
  122. package/src/adapters/sandbox/e2b/filesystem.ts +5 -4
  123. package/src/adapters/sandbox/e2b/index.ts +87 -14
  124. package/src/adapters/sandbox/e2b/proxy.ts +2 -0
  125. package/src/adapters/sandbox/e2b/types.ts +16 -0
  126. package/src/adapters/sandbox/inmemory/index.ts +17 -3
  127. package/src/adapters/sandbox/inmemory/proxy.ts +2 -0
  128. package/src/adapters/thread/anthropic/activities.ts +58 -26
  129. package/src/adapters/thread/anthropic/model-invoker.ts +18 -7
  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 +63 -46
  133. package/src/adapters/thread/google-genai/activities.ts +20 -2
  134. package/src/adapters/thread/google-genai/model-invoker.ts +27 -7
  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 +57 -33
  138. package/src/adapters/thread/langchain/activities.ts +55 -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 +5 -4
  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 +23 -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 +18 -3
  149. package/src/lib/model/proxy.ts +2 -2
  150. package/src/lib/model/types.ts +10 -0
  151. package/src/lib/observability/hooks.ts +4 -5
  152. package/src/lib/observability/index.ts +1 -4
  153. package/src/lib/sandbox/manager.ts +45 -20
  154. package/src/lib/sandbox/node-fs.ts +3 -6
  155. package/src/lib/sandbox/sandbox.test.ts +36 -3
  156. package/src/lib/sandbox/tree.integration.test.ts +10 -3
  157. package/src/lib/sandbox/types.ts +60 -6
  158. package/src/lib/session/session-edge-cases.integration.test.ts +316 -14
  159. package/src/lib/session/session.integration.test.ts +161 -1
  160. package/src/lib/session/session.ts +106 -21
  161. package/src/lib/session/types.ts +25 -5
  162. package/src/lib/skills/fs-provider.ts +12 -8
  163. package/src/lib/skills/handler.ts +1 -1
  164. package/src/lib/skills/parse.ts +3 -1
  165. package/src/lib/skills/register.ts +1 -3
  166. package/src/lib/skills/skills.integration.test.ts +25 -15
  167. package/src/lib/state/manager.integration.test.ts +12 -2
  168. package/src/lib/subagent/define.ts +1 -1
  169. package/src/lib/subagent/handler.ts +186 -71
  170. package/src/lib/subagent/index.ts +1 -5
  171. package/src/lib/subagent/register.ts +3 -2
  172. package/src/lib/subagent/signals.ts +1 -10
  173. package/src/lib/subagent/subagent.integration.test.ts +526 -248
  174. package/src/lib/subagent/tool.ts +4 -3
  175. package/src/lib/subagent/types.ts +50 -20
  176. package/src/lib/subagent/workflow.ts +9 -49
  177. package/src/lib/thread/id.test.ts +1 -1
  178. package/src/lib/thread/id.ts +1 -2
  179. package/src/lib/thread/manager.ts +18 -0
  180. package/src/lib/thread/proxy.ts +4 -4
  181. package/src/lib/thread/types.ts +20 -3
  182. package/src/lib/tool-router/index.ts +3 -5
  183. package/src/lib/tool-router/router-edge-cases.integration.test.ts +93 -1
  184. package/src/lib/tool-router/router.integration.test.ts +12 -0
  185. package/src/lib/tool-router/router.ts +90 -16
  186. package/src/lib/tool-router/types.ts +45 -4
  187. package/src/lib/tool-router/with-sandbox.ts +19 -5
  188. package/src/lib/virtual-fs/filesystem.ts +1 -1
  189. package/src/lib/virtual-fs/index.ts +5 -1
  190. package/src/lib/virtual-fs/mutations.ts +2 -4
  191. package/src/lib/virtual-fs/queries.ts +9 -5
  192. package/src/lib/virtual-fs/types.ts +4 -1
  193. package/src/lib/virtual-fs/virtual-fs.test.ts +9 -11
  194. package/src/lib/workflow.test.ts +7 -4
  195. package/src/lib/workflow.ts +1 -5
  196. package/src/tools/ask-user-question/tool.ts +1 -3
  197. package/src/tools/glob/handler.ts +1 -4
  198. package/src/tools/task-get/handler.ts +4 -5
  199. package/src/tools/task-list/handler.ts +1 -4
  200. package/src/tools/task-update/handler.ts +4 -5
  201. package/src/workflow.ts +22 -7
  202. package/tsup.config.ts +9 -6
  203. package/src/lib/.env +0 -1
  204. package/src/tools/bash/.env +0 -1
@@ -47,7 +47,10 @@ Body content here.`;
47
47
  expect(frontmatter.license).toBe("MIT");
48
48
  expect(frontmatter.compatibility).toBe("linux-only");
49
49
  expect(frontmatter.allowedTools).toEqual(["bash", "grep", "read-file"]);
50
- expect(frontmatter.metadata).toEqual({ author: "test-author", version: "1.0" });
50
+ expect(frontmatter.metadata).toEqual({
51
+ author: "test-author",
52
+ version: "1.0",
53
+ });
51
54
  expect(body).toBe("Body content here.");
52
55
  });
53
56
 
@@ -76,7 +79,7 @@ Body`;
76
79
 
77
80
  it("throws when frontmatter is missing", () => {
78
81
  expect(() => parseSkillFile("No frontmatter here")).toThrow(
79
- "SKILL.md must start with YAML frontmatter",
82
+ "SKILL.md must start with YAML frontmatter"
80
83
  );
81
84
  });
82
85
 
@@ -87,7 +90,7 @@ description: Missing name
87
90
  Body`;
88
91
 
89
92
  expect(() => parseSkillFile(raw)).toThrow(
90
- "SKILL.md frontmatter must include a 'name' field",
93
+ "SKILL.md frontmatter must include a 'name' field"
91
94
  );
92
95
  });
93
96
 
@@ -98,7 +101,7 @@ name: no-desc
98
101
  Body`;
99
102
 
100
103
  expect(() => parseSkillFile(raw)).toThrow(
101
- "SKILL.md frontmatter must include a 'description' field",
104
+ "SKILL.md frontmatter must include a 'description' field"
102
105
  );
103
106
  });
104
107
 
@@ -114,7 +117,8 @@ description: No body content
114
117
  });
115
118
 
116
119
  it("handles CRLF line endings", () => {
117
- const raw = "---\r\nname: crlf-skill\r\ndescription: CRLF test\r\n---\r\nBody with CRLF";
120
+ const raw =
121
+ "---\r\nname: crlf-skill\r\ndescription: CRLF test\r\n---\r\nBody with CRLF";
118
122
 
119
123
  const { frontmatter, body } = parseSkillFile(raw);
120
124
  expect(frontmatter.name).toBe("crlf-skill");
@@ -204,7 +208,7 @@ describe("createReadSkillTool", () => {
204
208
 
205
209
  it("throws when no skills are provided", () => {
206
210
  expect(() => createReadSkillTool([])).toThrow(
207
- "createReadSkillTool requires at least one skill",
211
+ "createReadSkillTool requires at least one skill"
208
212
  );
209
213
  });
210
214
  });
@@ -242,7 +246,9 @@ describe("createReadSkillHandler", () => {
242
246
 
243
247
  const text = result.toolResponse as string;
244
248
  expect(text).toContain("Skill directory: /skills/skill-a");
245
- expect(text).toContain("Relative paths in this skill resolve against the skill directory above.");
249
+ expect(text).toContain(
250
+ "Relative paths in this skill resolve against the skill directory above."
251
+ );
246
252
  });
247
253
 
248
254
  it("lists resources derived from resourceContents keys", () => {
@@ -298,17 +304,21 @@ describe("createReadSkillHandler", () => {
298
304
  const result = handler({ skill_name: "nonexistent" });
299
305
 
300
306
  expect(typeof result.toolResponse).toBe("string");
301
- expect((result.toolResponse as string)).toContain("not found");
307
+ expect(result.toolResponse as string).toContain("not found");
302
308
  expect(result.data).toBeNull();
303
309
  });
304
310
 
305
311
  it("handles single skill", () => {
306
312
  const skills: Skill[] = [
307
- { name: "skill-a", description: "First", instructions: "Instructions for A" },
313
+ {
314
+ name: "skill-a",
315
+ description: "First",
316
+ instructions: "Instructions for A",
317
+ },
308
318
  ];
309
319
  const handler = createReadSkillHandler(skills);
310
320
  const result = handler({ skill_name: "skill-a" });
311
- expect((result.toolResponse as string)).toContain("Instructions for A");
321
+ expect(result.toolResponse as string).toContain("Instructions for A");
312
322
  });
313
323
  });
314
324
 
@@ -350,7 +360,9 @@ describe("buildSkillRegistration", () => {
350
360
  { name: "dupe", description: "First", instructions: "A" },
351
361
  { name: "dupe", description: "Second", instructions: "B" },
352
362
  ];
353
- expect(() => buildSkillRegistration(skills)).toThrow("Duplicate skill names: dupe");
363
+ expect(() => buildSkillRegistration(skills)).toThrow(
364
+ "Duplicate skill names: dupe"
365
+ );
354
366
  });
355
367
 
356
368
  it("returns a complete tool entry with handler", () => {
@@ -389,7 +401,7 @@ describe("buildSkillRegistration", () => {
389
401
  if (!registration) return;
390
402
  const result = registration.handler(
391
403
  { skill_name: "test-skill" },
392
- { threadId: "t-1", toolCallId: "tc-1", toolName: "ReadSkill" },
404
+ { threadId: "t-1", toolCallId: "tc-1", toolName: "ReadSkill" }
393
405
  );
394
406
 
395
407
  if (result instanceof Promise) {
@@ -414,9 +426,7 @@ describe("buildSkillRegistration", () => {
414
426
  // FileSystemSkillProvider — resource discovery
415
427
  // ---------------------------------------------------------------------------
416
428
 
417
- function createMockFs(
418
- tree: Record<string, string | "DIR">,
419
- ): SandboxFileSystem {
429
+ function createMockFs(tree: Record<string, string | "DIR">): SandboxFileSystem {
420
430
  const dir = (entries: DirentEntry[]): DirentEntry[] => entries;
421
431
 
422
432
  return {
@@ -11,7 +11,13 @@ vi.mock("@temporalio/workflow", () => {
11
11
  setHandler: (_def: unknown, _handler: unknown) => {},
12
12
  uuid4: () =>
13
13
  `00000000-0000-0000-0000-${String(++idCounter).padStart(12, "0")}`,
14
- log: { trace: () => {}, debug: () => {}, info: () => {}, warn: () => {}, error: () => {} },
14
+ log: {
15
+ trace: () => {},
16
+ debug: () => {},
17
+ info: () => {},
18
+ warn: () => {},
19
+ error: () => {},
20
+ },
15
21
  };
16
22
  });
17
23
 
@@ -148,7 +154,11 @@ describe("createAgentStateManager integration", () => {
148
154
  const sm = createAgentStateManager({});
149
155
 
150
156
  sm.updateUsage({ inputTokens: 100, outputTokens: 50 });
151
- sm.updateUsage({ inputTokens: 200, outputTokens: 100, cachedWriteTokens: 30 });
157
+ sm.updateUsage({
158
+ inputTokens: 200,
159
+ outputTokens: 100,
160
+ cachedWriteTokens: 30,
161
+ });
152
162
  sm.updateUsage({ cachedReadTokens: 20, reasonTokens: 10 });
153
163
 
154
164
  expect(sm.getTotalUsage()).toEqual({
@@ -45,7 +45,7 @@ export function defineSubagent<
45
45
  taskQueue?: string;
46
46
  thread?: "new" | "fork" | "continue";
47
47
  sandbox?: SubagentSandboxConfig;
48
- },
48
+ }
49
49
  ): SubagentConfig<TResult> {
50
50
  return {
51
51
  agentName: definition.agentName,
@@ -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");