zeitlich 0.2.21 → 0.2.23

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 (129) hide show
  1. package/README.md +303 -105
  2. package/dist/adapters/sandbox/daytona/index.cjs +7 -1
  3. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  4. package/dist/adapters/sandbox/daytona/index.d.cts +3 -1
  5. package/dist/adapters/sandbox/daytona/index.d.ts +3 -1
  6. package/dist/adapters/sandbox/daytona/index.js +7 -1
  7. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  8. package/dist/adapters/sandbox/daytona/workflow.cjs +33 -0
  9. package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -0
  10. package/dist/adapters/sandbox/daytona/workflow.d.cts +27 -0
  11. package/dist/adapters/sandbox/daytona/workflow.d.ts +27 -0
  12. package/dist/adapters/sandbox/daytona/workflow.js +31 -0
  13. package/dist/adapters/sandbox/daytona/workflow.js.map +1 -0
  14. package/dist/adapters/sandbox/inmemory/index.cjs +18 -1
  15. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  16. package/dist/adapters/sandbox/inmemory/index.d.cts +4 -2
  17. package/dist/adapters/sandbox/inmemory/index.d.ts +4 -2
  18. package/dist/adapters/sandbox/inmemory/index.js +18 -1
  19. package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
  20. package/dist/adapters/sandbox/inmemory/workflow.cjs +33 -0
  21. package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -0
  22. package/dist/adapters/sandbox/inmemory/workflow.d.cts +25 -0
  23. package/dist/adapters/sandbox/inmemory/workflow.d.ts +25 -0
  24. package/dist/adapters/sandbox/inmemory/workflow.js +31 -0
  25. package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -0
  26. package/dist/adapters/sandbox/virtual/index.cjs +36 -9
  27. package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
  28. package/dist/adapters/sandbox/virtual/index.d.cts +8 -5
  29. package/dist/adapters/sandbox/virtual/index.d.ts +8 -5
  30. package/dist/adapters/sandbox/virtual/index.js +36 -9
  31. package/dist/adapters/sandbox/virtual/index.js.map +1 -1
  32. package/dist/adapters/sandbox/virtual/workflow.cjs +33 -0
  33. package/dist/adapters/sandbox/virtual/workflow.cjs.map +1 -0
  34. package/dist/adapters/sandbox/virtual/workflow.d.cts +27 -0
  35. package/dist/adapters/sandbox/virtual/workflow.d.ts +27 -0
  36. package/dist/adapters/sandbox/virtual/workflow.js +31 -0
  37. package/dist/adapters/sandbox/virtual/workflow.js.map +1 -0
  38. package/dist/adapters/thread/google-genai/index.cjs +9 -1
  39. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  40. package/dist/adapters/thread/google-genai/index.d.cts +31 -19
  41. package/dist/adapters/thread/google-genai/index.d.ts +31 -19
  42. package/dist/adapters/thread/google-genai/index.js +9 -1
  43. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  44. package/dist/adapters/thread/google-genai/workflow.cjs +33 -0
  45. package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -0
  46. package/dist/adapters/thread/google-genai/workflow.d.cts +32 -0
  47. package/dist/adapters/thread/google-genai/workflow.d.ts +32 -0
  48. package/dist/adapters/thread/google-genai/workflow.js +31 -0
  49. package/dist/adapters/thread/google-genai/workflow.js.map +1 -0
  50. package/dist/adapters/thread/langchain/index.cjs +9 -1
  51. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  52. package/dist/adapters/thread/langchain/index.d.cts +27 -16
  53. package/dist/adapters/thread/langchain/index.d.ts +27 -16
  54. package/dist/adapters/thread/langchain/index.js +9 -1
  55. package/dist/adapters/thread/langchain/index.js.map +1 -1
  56. package/dist/adapters/thread/langchain/workflow.cjs +33 -0
  57. package/dist/adapters/thread/langchain/workflow.cjs.map +1 -0
  58. package/dist/adapters/thread/langchain/workflow.d.cts +32 -0
  59. package/dist/adapters/thread/langchain/workflow.d.ts +32 -0
  60. package/dist/adapters/thread/langchain/workflow.js +31 -0
  61. package/dist/adapters/thread/langchain/workflow.js.map +1 -0
  62. package/dist/index.cjs +282 -90
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +38 -16
  65. package/dist/index.d.ts +38 -16
  66. package/dist/index.js +281 -87
  67. package/dist/index.js.map +1 -1
  68. package/dist/queries-DModcWRy.d.cts +44 -0
  69. package/dist/queries-byD0jr1Y.d.ts +44 -0
  70. package/dist/{types-BkAYmc96.d.ts → types-B50pBPEV.d.ts} +190 -38
  71. package/dist/{types-YbL7JpEA.d.cts → types-Bll19FZJ.d.cts} +7 -0
  72. package/dist/{types-YbL7JpEA.d.ts → types-Bll19FZJ.d.ts} +7 -0
  73. package/dist/{queries-6Avfh74U.d.ts → types-BuXdFhaZ.d.cts} +7 -48
  74. package/dist/{types-BMRzfELQ.d.cts → types-ChAMwU3q.d.cts} +17 -1
  75. package/dist/{types-BMRzfELQ.d.ts → types-ChAMwU3q.d.ts} +17 -1
  76. package/dist/{types-CES_30qx.d.cts → types-DQW8l7pY.d.cts} +190 -38
  77. package/dist/{queries-CHa2iv_I.d.cts → types-GZ76HZSj.d.ts} +7 -48
  78. package/dist/workflow.cjs +244 -86
  79. package/dist/workflow.cjs.map +1 -1
  80. package/dist/workflow.d.cts +54 -65
  81. package/dist/workflow.d.ts +54 -65
  82. package/dist/workflow.js +243 -83
  83. package/dist/workflow.js.map +1 -1
  84. package/package.json +54 -2
  85. package/src/adapters/sandbox/daytona/filesystem.ts +1 -1
  86. package/src/adapters/sandbox/daytona/index.ts +8 -0
  87. package/src/adapters/sandbox/daytona/proxy.ts +56 -0
  88. package/src/adapters/sandbox/e2b/filesystem.ts +147 -0
  89. package/src/adapters/sandbox/e2b/index.ts +164 -0
  90. package/src/adapters/sandbox/e2b/types.ts +23 -0
  91. package/src/adapters/sandbox/inmemory/index.ts +27 -3
  92. package/src/adapters/sandbox/inmemory/proxy.ts +53 -0
  93. package/src/adapters/sandbox/virtual/filesystem.ts +41 -17
  94. package/src/adapters/sandbox/virtual/provider.ts +9 -1
  95. package/src/adapters/sandbox/virtual/proxy.ts +53 -0
  96. package/src/adapters/sandbox/virtual/types.ts +9 -4
  97. package/src/adapters/thread/google-genai/activities.ts +51 -17
  98. package/src/adapters/thread/google-genai/index.ts +1 -0
  99. package/src/adapters/thread/google-genai/proxy.ts +61 -0
  100. package/src/adapters/thread/langchain/activities.ts +47 -14
  101. package/src/adapters/thread/langchain/index.ts +1 -0
  102. package/src/adapters/thread/langchain/proxy.ts +61 -0
  103. package/src/lib/lifecycle.ts +57 -0
  104. package/src/lib/sandbox/manager.ts +52 -6
  105. package/src/lib/sandbox/sandbox.test.ts +12 -11
  106. package/src/lib/sandbox/types.ts +31 -4
  107. package/src/lib/session/index.ts +4 -5
  108. package/src/lib/session/session-edge-cases.integration.test.ts +491 -66
  109. package/src/lib/session/session.integration.test.ts +92 -80
  110. package/src/lib/session/session.ts +108 -96
  111. package/src/lib/session/types.ts +87 -17
  112. package/src/lib/subagent/define.ts +6 -5
  113. package/src/lib/subagent/handler.ts +148 -16
  114. package/src/lib/subagent/index.ts +4 -0
  115. package/src/lib/subagent/register.ts +10 -3
  116. package/src/lib/subagent/signals.ts +8 -0
  117. package/src/lib/subagent/subagent.integration.test.ts +893 -128
  118. package/src/lib/subagent/tool.ts +2 -2
  119. package/src/lib/subagent/types.ts +84 -21
  120. package/src/lib/subagent/workflow.ts +83 -12
  121. package/src/lib/tool-router/router-edge-cases.integration.test.ts +4 -1
  122. package/src/lib/tool-router/router.integration.test.ts +141 -5
  123. package/src/lib/tool-router/router.ts +13 -3
  124. package/src/lib/tool-router/types.ts +7 -0
  125. package/src/lib/workflow.test.ts +104 -27
  126. package/src/lib/workflow.ts +37 -19
  127. package/src/tools/bash/bash.test.ts +16 -7
  128. package/src/workflow.ts +11 -14
  129. package/tsup.config.ts +6 -0
@@ -1,13 +1,11 @@
1
1
  import {
2
- proxyActivities,
3
2
  condition,
4
3
  defineUpdate,
5
4
  setHandler,
6
5
  ApplicationFailure,
7
- type ActivityInterfaceFor,
8
6
  } from "@temporalio/workflow";
9
7
  import type { SessionExitReason, MessageContent } from "../types";
10
- import type { ThreadOps, SessionConfig, ZeitlichSession } from "./types";
8
+ import type { SessionConfig, ZeitlichSession } from "./types";
11
9
  import type { SandboxOps } from "../sandbox/types";
12
10
  import { type AgentStateManager, type JsonSerializable } from "../state/types";
13
11
  import { createToolRouter } from "../tool-router/router";
@@ -21,22 +19,22 @@ import { uuid4 } from "@temporalio/workflow";
21
19
  * Creates an agent session that manages the agent loop: LLM invocation,
22
20
  * tool routing, subagent coordination, and lifecycle hooks.
23
21
  *
22
+ * When `sandboxOps` is provided the returned session result is guaranteed to
23
+ * include `sandboxId: string`. Without it, `sandboxId` is `undefined`.
24
+ *
24
25
  * @param config - Session and agent configuration (merged `SessionConfig` and `AgentConfig`)
25
26
  * @returns A session object with `runSession()` to start the agent loop
26
27
  *
27
28
  * @example
28
29
  * ```typescript
29
30
  * import { createSession, createAgentStateManager, defineTool, bashTool } from 'zeitlich/workflow';
30
- *
31
- * const stateManager = createAgentStateManager({
32
- * initialState: { systemPrompt: "You are a helpful assistant." },
33
- * agentName: "my-agent",
34
- * });
31
+ * import { proxyGoogleGenAIThreadOps } from 'zeitlich/adapters/thread/google-genai/workflow';
35
32
  *
36
33
  * const session = await createSession({
37
34
  * agentName: "my-agent",
38
35
  * maxTurns: 20,
39
- * threadId: runId,
36
+ * thread: { mode: "new" },
37
+ * threadOps: proxyGoogleGenAIThreadOps(),
40
38
  * runAgent: runAgentActivity,
41
39
  * buildContextMessage: () => [{ type: "text", text: prompt }],
42
40
  * subagents: [researcherSubagent],
@@ -48,8 +46,13 @@ import { uuid4 } from "@temporalio/workflow";
48
46
  * const { finalMessage, exitReason } = await session.runSession({ stateManager });
49
47
  * ```
50
48
  */
51
- export const createSession = async <T extends ToolMap, M = unknown>({
52
- threadId: providedThreadId,
49
+ export async function createSession<T extends ToolMap, M = unknown>(
50
+ config: SessionConfig<T, M> & { sandboxOps: SandboxOps }
51
+ ): Promise<ZeitlichSession<M, true>>;
52
+ export async function createSession<T extends ToolMap, M = unknown>(
53
+ config: SessionConfig<T, M>
54
+ ): Promise<ZeitlichSession<M, false>>;
55
+ export async function createSession<T extends ToolMap, M = unknown>({
53
56
  agentName,
54
57
  maxTurns = 50,
55
58
  metadata = {},
@@ -62,16 +65,33 @@ export const createSession = async <T extends ToolMap, M = unknown>({
62
65
  processToolsInParallel = true,
63
66
  hooks = {},
64
67
  appendSystemPrompt = true,
65
- continueThread = false,
66
68
  waitForInputTimeout = "48h",
67
- sandbox: sandboxOps,
68
- sandboxId: inheritedSandboxId,
69
- }: SessionConfig<T, M>): Promise<ZeitlichSession<M>> => {
70
- const sourceThreadId = continueThread ? providedThreadId : undefined;
71
- const threadId =
72
- continueThread && providedThreadId
73
- ? getShortId()
74
- : (providedThreadId ?? getShortId());
69
+ sandboxOps,
70
+ thread: threadInit,
71
+ sandbox: sandboxInit,
72
+ sandboxShutdown = "destroy",
73
+ }: SessionConfig<T, M>): Promise<ZeitlichSession<M, boolean>> {
74
+ // ---------------------------------------------------------------------------
75
+ // Thread resolution
76
+ // ---------------------------------------------------------------------------
77
+ const threadMode = threadInit?.mode ?? "new";
78
+ let threadId: string;
79
+ let sourceThreadId: string | undefined;
80
+
81
+ switch (threadMode) {
82
+ case "new":
83
+ threadId = threadInit?.mode === "new" && threadInit.threadId
84
+ ? threadInit.threadId
85
+ : getShortId();
86
+ break;
87
+ case "continue":
88
+ threadId = (threadInit as { mode: "continue"; threadId: string }).threadId;
89
+ break;
90
+ case "fork":
91
+ sourceThreadId = (threadInit as { mode: "fork"; threadId: string }).threadId;
92
+ threadId = getShortId();
93
+ break;
94
+ }
75
95
 
76
96
  const {
77
97
  appendToolResult,
@@ -79,12 +99,16 @@ export const createSession = async <T extends ToolMap, M = unknown>({
79
99
  initializeThread,
80
100
  appendSystemMessage,
81
101
  forkThread,
82
- } = threadOps ?? proxyDefaultThreadOps();
102
+ } = threadOps;
83
103
 
84
104
  const plugins: ToolMap[string][] = [];
105
+ let destroySubagentSandboxes: (() => Promise<void>) | undefined;
85
106
  if (subagents) {
86
- const reg = buildSubagentRegistration(subagents);
87
- if (reg) plugins.push(reg);
107
+ const result = buildSubagentRegistration(subagents);
108
+ if (result) {
109
+ plugins.push(result.registration);
110
+ destroySubagentSandboxes = result.destroySubagentSandboxes;
111
+ }
88
112
  }
89
113
  if (skills) {
90
114
  const reg = buildSkillRegistration(skills);
@@ -120,12 +144,7 @@ export const createSession = async <T extends ToolMap, M = unknown>({
120
144
  stateManager,
121
145
  }: {
122
146
  stateManager: AgentStateManager<TState>;
123
- }): Promise<{
124
- threadId: string;
125
- finalMessage: M | null;
126
- exitReason: SessionExitReason;
127
- usage: ReturnType<AgentStateManager<TState>["getTotalUsage"]>;
128
- }> => {
147
+ }) => {
129
148
  setHandler(
130
149
  defineUpdate<unknown, [MessageContent]>(`add${agentName}Message`),
131
150
  async (message: MessageContent) => {
@@ -146,12 +165,43 @@ export const createSession = async <T extends ToolMap, M = unknown>({
146
165
  }
147
166
  );
148
167
 
149
- // --- Sandbox lifecycle: create or inherit ---
150
- let sandboxId: string | undefined = inheritedSandboxId;
151
- const ownsSandbox = !sandboxId && !!sandboxOps;
152
- if (ownsSandbox) {
153
- const result = await sandboxOps.createSandbox({ id: threadId });
168
+ // --- Sandbox lifecycle: create, continue, fork, or inherit ----------
169
+ const sandboxMode = sandboxInit?.mode;
170
+ let sandboxId: string | undefined;
171
+ let sandboxOwned = false;
172
+
173
+ if (sandboxMode === "inherit") {
174
+ sandboxId = (sandboxInit as { mode: "inherit"; sandboxId: string }).sandboxId;
175
+ if (!sandboxOps) {
176
+ throw ApplicationFailure.create({
177
+ message: "sandboxId provided but no sandboxOps — cannot manage sandbox lifecycle",
178
+ nonRetryable: true,
179
+ });
180
+ }
181
+ } else if (sandboxMode === "continue") {
182
+ if (!sandboxOps) {
183
+ throw ApplicationFailure.create({
184
+ message: "No sandboxOps provided — cannot continue sandbox",
185
+ nonRetryable: true,
186
+ });
187
+ }
188
+ sandboxId = (sandboxInit as { mode: "continue"; sandboxId: string }).sandboxId;
189
+ sandboxOwned = true;
190
+ } else if (sandboxMode === "fork") {
191
+ if (!sandboxOps) {
192
+ throw ApplicationFailure.create({
193
+ message: "No sandboxOps provided — cannot fork sandbox",
194
+ nonRetryable: true,
195
+ });
196
+ }
197
+ sandboxId = await sandboxOps.forkSandbox(
198
+ (sandboxInit as { mode: "fork"; sandboxId: string }).sandboxId
199
+ );
200
+ sandboxOwned = true;
201
+ } else if (sandboxOps) {
202
+ const result = await sandboxOps.createSandbox();
154
203
  sandboxId = result.sandboxId;
204
+ sandboxOwned = true;
155
205
  if (result.stateUpdate) {
156
206
  stateManager.mergeUpdate(result.stateUpdate as Partial<TState>);
157
207
  }
@@ -167,8 +217,11 @@ export const createSession = async <T extends ToolMap, M = unknown>({
167
217
 
168
218
  const systemPrompt = stateManager.getSystemPrompt();
169
219
 
170
- if (continueThread && sourceThreadId) {
220
+ // --- Thread lifecycle: new, continue, or fork ----------------------
221
+ if (threadMode === "fork" && sourceThreadId) {
171
222
  await forkThread(sourceThreadId, threadId);
223
+ } else if (threadMode === "continue") {
224
+ // "continue" — thread already exists, just append the new message
172
225
  } else {
173
226
  if (appendSystemPrompt) {
174
227
  if (!systemPrompt || systemPrompt.trim() === "") {
@@ -215,7 +268,8 @@ export const createSession = async <T extends ToolMap, M = unknown>({
215
268
  finalMessage: message,
216
269
  exitReason,
217
270
  usage: stateManager.getTotalUsage(),
218
- };
271
+ sandboxId,
272
+ } as Awaited<ReturnType<ZeitlichSession<M, boolean>["runSession"]>>;
219
273
  }
220
274
 
221
275
  const parsedToolCalls: ParsedToolCallUnion<T>[] = [];
@@ -271,8 +325,22 @@ export const createSession = async <T extends ToolMap, M = unknown>({
271
325
  } finally {
272
326
  await callSessionEnd(exitReason, stateManager.getTurns());
273
327
 
274
- if (ownsSandbox && sandboxId && sandboxOps) {
275
- await sandboxOps.destroySandbox(sandboxId);
328
+ if (sandboxOwned && sandboxId && sandboxOps) {
329
+ switch (sandboxShutdown) {
330
+ case "destroy":
331
+ await sandboxOps.destroySandbox(sandboxId);
332
+ break;
333
+ case "pause":
334
+ case "pause-until-parent-close":
335
+ await sandboxOps.pauseSandbox(sandboxId);
336
+ break;
337
+ case "keep":
338
+ break;
339
+ }
340
+ }
341
+
342
+ if (destroySubagentSandboxes) {
343
+ await destroySubagentSandboxes();
276
344
  }
277
345
  }
278
346
 
@@ -281,64 +349,8 @@ export const createSession = async <T extends ToolMap, M = unknown>({
281
349
  finalMessage: null,
282
350
  exitReason,
283
351
  usage: stateManager.getTotalUsage(),
284
- };
352
+ sandboxId,
353
+ } as Awaited<ReturnType<ZeitlichSession<M, boolean>["runSession"]>>;
285
354
  },
286
355
  };
287
- };
288
-
289
- /**
290
- * Proxy the adapter's thread operations as Temporal activities.
291
- * Call this in workflow code to delegate thread operations to the
292
- * adapter-provided activities registered on the worker.
293
- *
294
- * @example
295
- * ```typescript
296
- * const session = await createSession({
297
- * threadOps: proxyDefaultThreadOps(),
298
- * // ...
299
- * });
300
- * ```
301
- */
302
- export function proxyDefaultThreadOps(
303
- options?: Parameters<typeof proxyActivities>[0]
304
- ): ActivityInterfaceFor<ThreadOps> {
305
- return proxyActivities<ThreadOps>(
306
- options ?? {
307
- startToCloseTimeout: "10s",
308
- retry: {
309
- maximumAttempts: 6,
310
- initialInterval: "5s",
311
- maximumInterval: "15m",
312
- backoffCoefficient: 4,
313
- },
314
- }
315
- );
316
- }
317
-
318
- /**
319
- * Proxy sandbox lifecycle operations as Temporal activities.
320
- * Call this in workflow code when the agent needs a sandbox.
321
- *
322
- * @example
323
- * ```typescript
324
- * const session = await createSession({
325
- * sandbox: proxySandboxOps(),
326
- * // ...
327
- * });
328
- * ```
329
- */
330
- export function proxySandboxOps(
331
- options?: Parameters<typeof proxyActivities>[0]
332
- ): SandboxOps {
333
- return proxyActivities<SandboxOps>(
334
- options ?? {
335
- startToCloseTimeout: "30s",
336
- retry: {
337
- maximumAttempts: 3,
338
- initialInterval: "2s",
339
- maximumInterval: "30s",
340
- backoffCoefficient: 2,
341
- },
342
- }
343
- );
344
356
  }
@@ -16,6 +16,7 @@ import type { SandboxOps } from "../sandbox/types";
16
16
  import type { RunAgentActivity } from "../model/types";
17
17
  import type { AgentStateManager, JsonSerializable } from "../state/types";
18
18
  import type { ActivityInterfaceFor } from "@temporalio/workflow";
19
+ import type { ThreadInit, SandboxInit, SubagentSandboxShutdown } from "../lifecycle";
19
20
 
20
21
  /**
21
22
  * Thread operations required by a session.
@@ -42,14 +43,43 @@ export interface ThreadOps {
42
43
  forkThread(sourceThreadId: string, targetThreadId: string): Promise<void>;
43
44
  }
44
45
 
46
+ /**
47
+ * Composes an adapter prefix + workflow scope for activity naming.
48
+ *
49
+ * The adapter prefix stays first (camelCase); the workflow scope is
50
+ * capitalised and appended. When `TScope` is empty the adapter prefix
51
+ * is used as-is.
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * ScopedPrefix<"codingAgent", "googleGenAI"> // "googleGenAICodingAgent"
56
+ * ScopedPrefix<"", "googleGenAI"> // "googleGenAI"
57
+ * ```
58
+ */
59
+ export type ScopedPrefix<
60
+ TScope extends string,
61
+ TAdapter extends string,
62
+ > = TScope extends "" ? TAdapter : `${TAdapter}${Capitalize<TScope>}`;
63
+
64
+ /**
65
+ * Maps generic {@link ThreadOps} method names to adapter-prefixed names.
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * type GoogleOps = PrefixedThreadOps<"googleGenAI">;
70
+ * // → { googleGenAIInitializeThread, googleGenAIAppendHumanMessage, … }
71
+ * ```
72
+ */
73
+ export type PrefixedThreadOps<TPrefix extends string> = {
74
+ [K in keyof ThreadOps as `${TPrefix}${Capitalize<K & string>}`]: ThreadOps[K];
75
+ };
76
+
45
77
  /**
46
78
  * Configuration for a Zeitlich agent session
47
79
  */
48
80
  export interface SessionConfig<T extends ToolMap, M = unknown> {
49
81
  /** The name of the agent, should be unique within the workflows */
50
82
  agentName: string;
51
- /** The thread ID to use for the session (defaults to a short generated ID) */
52
- threadId?: string;
53
83
  /** Metadata for the session */
54
84
  metadata?: Record<string, unknown>;
55
85
  /** Whether to append the system prompt as message to the thread */
@@ -59,7 +89,7 @@ export interface SessionConfig<T extends ToolMap, M = unknown> {
59
89
  /** Workflow-specific runAgent activity (with tools pre-bound) */
60
90
  runAgent: RunAgentActivity<M>;
61
91
  /** Thread operations (initialize, append messages, parse tool calls) */
62
- threadOps?: ActivityInterfaceFor<ThreadOps>;
92
+ threadOps: ActivityInterfaceFor<ThreadOps>;
63
93
  /** Tool router for processing tool calls (optional if agent has no tools) */
64
94
  tools?: T;
65
95
  /** Subagent configurations */
@@ -75,27 +105,67 @@ export interface SessionConfig<T extends ToolMap, M = unknown> {
75
105
  * Returns MessageContent array for the initial HumanMessage.
76
106
  */
77
107
  buildContextMessage: () => MessageContent | Promise<MessageContent>;
78
- /** When true, skip thread initialization and system prompt — append only the new human message to the existing thread. */
79
- continueThread?: boolean;
80
108
  /** How long to wait for input before cancelling the workflow */
81
109
  waitForInputTimeout?: Duration;
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // Thread lifecycle
113
+ // ---------------------------------------------------------------------------
114
+
115
+ /**
116
+ * Thread initialization strategy (default: `{ mode: "new" }`).
117
+ *
118
+ * - `{ mode: "new" }` — start a fresh thread.
119
+ * - `{ mode: "new", threadId: "..." }` — start a fresh thread with a specific ID.
120
+ * - `{ mode: "continue", threadId: "..." }` — append to an existing thread in-place.
121
+ * - `{ mode: "fork", threadId: "..." }` — fork an existing thread and continue in the copy.
122
+ */
123
+ thread?: ThreadInit;
124
+
125
+ // ---------------------------------------------------------------------------
126
+ // Sandbox lifecycle
127
+ // ---------------------------------------------------------------------------
128
+
82
129
  /** Sandbox lifecycle operations (optional — omit for agents that don't need a sandbox) */
83
- sandbox?: SandboxOps;
130
+ sandboxOps?: SandboxOps;
84
131
  /**
85
- * Pre-existing sandbox ID to reuse (e.g. inherited from a parent agent).
86
- * When set, the session skips `createSandbox` and will not destroy the
87
- * sandbox on exit (the owner is responsible for cleanup).
132
+ * Sandbox initialization strategy.
133
+ *
134
+ * - `{ mode: "new" }` create a fresh sandbox.
135
+ * - `{ mode: "continue", sandboxId: "..." }` — resume a paused sandbox (session owns it).
136
+ * - `{ mode: "fork", sandboxId: "..." }` — fork from an existing sandbox.
137
+ * - `{ mode: "inherit", sandboxId: "..." }` — use a parent's sandbox without ownership.
138
+ *
139
+ * When omitted and `sandboxOps` is provided, defaults to `{ mode: "new" }`.
88
140
  */
89
- sandboxId?: string;
141
+ sandbox?: SandboxInit;
142
+ /**
143
+ * What to do with the sandbox when this session exits.
144
+ *
145
+ * Defaults to `"destroy"` when omitted.
146
+ * Has no effect when the sandbox is inherited (`sandbox.mode === "inherit"`).
147
+ */
148
+ sandboxShutdown?: SubagentSandboxShutdown;
90
149
  }
91
150
 
92
- export interface ZeitlichSession<M = unknown> {
151
+ export type SessionResult<
152
+ M,
153
+ TState extends JsonSerializable<TState>,
154
+ HasSandbox extends boolean = boolean,
155
+ > = {
156
+ threadId: string;
157
+ finalMessage: M | null;
158
+ exitReason: SessionExitReason;
159
+ usage: ReturnType<AgentStateManager<TState>["getTotalUsage"]>;
160
+ } & (HasSandbox extends true
161
+ ? { sandboxId: string }
162
+ : { sandboxId?: undefined });
163
+
164
+ export interface ZeitlichSession<
165
+ M = unknown,
166
+ HasSandbox extends boolean = boolean,
167
+ > {
93
168
  runSession<T extends JsonSerializable<T>>(args: {
94
169
  stateManager: AgentStateManager<T>;
95
- }): Promise<{
96
- threadId: string;
97
- finalMessage: M | null;
98
- exitReason: SessionExitReason;
99
- usage: ReturnType<AgentStateManager<T>["getTotalUsage"]>;
100
- }>;
170
+ }): Promise<SessionResult<M, T, HasSandbox>>;
101
171
  }
@@ -3,6 +3,7 @@ import type {
3
3
  SubagentConfig,
4
4
  SubagentDefinition,
5
5
  SubagentHooks,
6
+ SubagentSandboxConfig,
6
7
  SubagentWorkflow,
7
8
  } from "./types";
8
9
  import type { SubagentArgs } from "./tool";
@@ -19,8 +20,8 @@ import type { SubagentArgs } from "./tool";
19
20
  *
20
21
  * // With parent-specific overrides
21
22
  * export const researcher = defineSubagent(researcherWorkflow, {
22
- * allowThreadContinuation: true,
23
- * sandbox: "own",
23
+ * thread: "fork",
24
+ * sandbox: { source: "own", shutdown: "pause" },
24
25
  * hooks: {
25
26
  * onPostExecution: ({ result }) => console.log(result),
26
27
  * },
@@ -38,12 +39,12 @@ export function defineSubagent<
38
39
  >(
39
40
  definition: SubagentDefinition<TResult, TContext>,
40
41
  overrides?: {
41
- context?: TContext;
42
+ context?: TContext | (() => TContext);
42
43
  hooks?: SubagentHooks<SubagentArgs, z.infer<TResult>>;
43
44
  enabled?: boolean | (() => boolean);
44
45
  taskQueue?: string;
45
- allowThreadContinuation?: boolean;
46
- sandbox?: "inherit" | "own";
46
+ thread?: "new" | "fork" | "continue";
47
+ sandbox?: SubagentSandboxConfig;
47
48
  },
48
49
  ): SubagentConfig<TResult> {
49
50
  return {