zeitlich 0.2.22 → 0.2.24

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 +278 -59
  2. package/dist/adapters/sandbox/bedrock/index.cjs +427 -0
  3. package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -0
  4. package/dist/adapters/sandbox/bedrock/index.d.cts +23 -0
  5. package/dist/adapters/sandbox/bedrock/index.d.ts +23 -0
  6. package/dist/adapters/sandbox/bedrock/index.js +424 -0
  7. package/dist/adapters/sandbox/bedrock/index.js.map +1 -0
  8. package/dist/adapters/sandbox/bedrock/workflow.cjs +33 -0
  9. package/dist/adapters/sandbox/bedrock/workflow.cjs.map +1 -0
  10. package/dist/adapters/sandbox/bedrock/workflow.d.cts +29 -0
  11. package/dist/adapters/sandbox/bedrock/workflow.d.ts +29 -0
  12. package/dist/adapters/sandbox/bedrock/workflow.js +31 -0
  13. package/dist/adapters/sandbox/bedrock/workflow.js.map +1 -0
  14. package/dist/adapters/sandbox/daytona/index.cjs +4 -1
  15. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  16. package/dist/adapters/sandbox/daytona/index.d.cts +2 -1
  17. package/dist/adapters/sandbox/daytona/index.d.ts +2 -1
  18. package/dist/adapters/sandbox/daytona/index.js +4 -1
  19. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  20. package/dist/adapters/sandbox/daytona/workflow.cjs +1 -0
  21. package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -1
  22. package/dist/adapters/sandbox/daytona/workflow.d.cts +1 -1
  23. package/dist/adapters/sandbox/daytona/workflow.d.ts +1 -1
  24. package/dist/adapters/sandbox/daytona/workflow.js +1 -0
  25. package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
  26. package/dist/adapters/sandbox/inmemory/index.cjs +16 -2
  27. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  28. package/dist/adapters/sandbox/inmemory/index.d.cts +3 -2
  29. package/dist/adapters/sandbox/inmemory/index.d.ts +3 -2
  30. package/dist/adapters/sandbox/inmemory/index.js +16 -2
  31. package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
  32. package/dist/adapters/sandbox/inmemory/workflow.cjs +1 -0
  33. package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -1
  34. package/dist/adapters/sandbox/inmemory/workflow.d.cts +1 -1
  35. package/dist/adapters/sandbox/inmemory/workflow.d.ts +1 -1
  36. package/dist/adapters/sandbox/inmemory/workflow.js +1 -0
  37. package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
  38. package/dist/adapters/sandbox/virtual/index.cjs +45 -11
  39. package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
  40. package/dist/adapters/sandbox/virtual/index.d.cts +6 -5
  41. package/dist/adapters/sandbox/virtual/index.d.ts +6 -5
  42. package/dist/adapters/sandbox/virtual/index.js +45 -11
  43. package/dist/adapters/sandbox/virtual/index.js.map +1 -1
  44. package/dist/adapters/sandbox/virtual/workflow.cjs +1 -0
  45. package/dist/adapters/sandbox/virtual/workflow.cjs.map +1 -1
  46. package/dist/adapters/sandbox/virtual/workflow.d.cts +3 -3
  47. package/dist/adapters/sandbox/virtual/workflow.d.ts +3 -3
  48. package/dist/adapters/sandbox/virtual/workflow.js +1 -0
  49. package/dist/adapters/sandbox/virtual/workflow.js.map +1 -1
  50. package/dist/adapters/thread/google-genai/index.d.cts +3 -3
  51. package/dist/adapters/thread/google-genai/index.d.ts +3 -3
  52. package/dist/adapters/thread/google-genai/workflow.d.cts +3 -3
  53. package/dist/adapters/thread/google-genai/workflow.d.ts +3 -3
  54. package/dist/adapters/thread/langchain/index.d.cts +3 -3
  55. package/dist/adapters/thread/langchain/index.d.ts +3 -3
  56. package/dist/adapters/thread/langchain/workflow.d.cts +3 -3
  57. package/dist/adapters/thread/langchain/workflow.d.ts +3 -3
  58. package/dist/index.cjs +443 -71
  59. package/dist/index.cjs.map +1 -1
  60. package/dist/index.d.cts +64 -10
  61. package/dist/index.d.ts +64 -10
  62. package/dist/index.js +442 -71
  63. package/dist/index.js.map +1 -1
  64. package/dist/{queries-Bw6WEPMw.d.cts → queries-BYGBImeC.d.cts} +1 -1
  65. package/dist/{queries-C27raDaB.d.ts → queries-DwBe2CAA.d.ts} +1 -1
  66. package/dist/{types-C5bkx6kQ.d.ts → types-7PeMi1bD.d.cts} +167 -36
  67. package/dist/{types-BJ8itUAl.d.cts → types-Bf8KV0Ci.d.cts} +6 -6
  68. package/dist/{types-HBosetv3.d.cts → types-ChAMwU3q.d.cts} +2 -0
  69. package/dist/{types-HBosetv3.d.ts → types-ChAMwU3q.d.ts} +2 -0
  70. package/dist/{types-YbL7JpEA.d.cts → types-D_igp10o.d.cts} +11 -0
  71. package/dist/{types-YbL7JpEA.d.ts → types-D_igp10o.d.ts} +11 -0
  72. package/dist/types-DhTCEMhr.d.cts +64 -0
  73. package/dist/{types-ENYCKFBk.d.ts → types-LVKmCNds.d.ts} +6 -6
  74. package/dist/types-d9NznUqd.d.ts +64 -0
  75. package/dist/{types-ClsHhtwL.d.cts → types-hmferhc2.d.ts} +167 -36
  76. package/dist/workflow.cjs +308 -63
  77. package/dist/workflow.cjs.map +1 -1
  78. package/dist/workflow.d.cts +54 -32
  79. package/dist/workflow.d.ts +54 -32
  80. package/dist/workflow.js +306 -61
  81. package/dist/workflow.js.map +1 -1
  82. package/package.json +27 -2
  83. package/src/adapters/sandbox/bedrock/filesystem.ts +313 -0
  84. package/src/adapters/sandbox/bedrock/index.ts +259 -0
  85. package/src/adapters/sandbox/bedrock/proxy.ts +56 -0
  86. package/src/adapters/sandbox/bedrock/types.ts +24 -0
  87. package/src/adapters/sandbox/daytona/filesystem.ts +1 -1
  88. package/src/adapters/sandbox/daytona/index.ts +4 -0
  89. package/src/adapters/sandbox/daytona/proxy.ts +4 -3
  90. package/src/adapters/sandbox/e2b/index.ts +5 -0
  91. package/src/adapters/sandbox/inmemory/index.ts +24 -4
  92. package/src/adapters/sandbox/inmemory/proxy.ts +2 -2
  93. package/src/adapters/sandbox/virtual/filesystem.ts +44 -18
  94. package/src/adapters/sandbox/virtual/provider.ts +13 -0
  95. package/src/adapters/sandbox/virtual/proxy.ts +1 -0
  96. package/src/adapters/sandbox/virtual/types.ts +9 -4
  97. package/src/adapters/sandbox/virtual/virtual-sandbox.test.ts +26 -0
  98. package/src/index.ts +2 -1
  99. package/src/lib/lifecycle.ts +57 -0
  100. package/src/lib/sandbox/manager.ts +13 -1
  101. package/src/lib/sandbox/node-fs.ts +115 -0
  102. package/src/lib/sandbox/types.ts +13 -4
  103. package/src/lib/session/index.ts +1 -0
  104. package/src/lib/session/session-edge-cases.integration.test.ts +447 -33
  105. package/src/lib/session/session.integration.test.ts +149 -32
  106. package/src/lib/session/session.ts +138 -33
  107. package/src/lib/session/types.ts +56 -17
  108. package/src/lib/skills/fs-provider.ts +65 -4
  109. package/src/lib/skills/handler.ts +43 -1
  110. package/src/lib/skills/index.ts +0 -1
  111. package/src/lib/skills/register.ts +17 -1
  112. package/src/lib/skills/skills.integration.test.ts +308 -24
  113. package/src/lib/skills/types.ts +6 -0
  114. package/src/lib/subagent/define.ts +5 -4
  115. package/src/lib/subagent/handler.ts +143 -14
  116. package/src/lib/subagent/index.ts +3 -0
  117. package/src/lib/subagent/register.ts +10 -3
  118. package/src/lib/subagent/signals.ts +8 -0
  119. package/src/lib/subagent/subagent.integration.test.ts +853 -150
  120. package/src/lib/subagent/tool.ts +2 -2
  121. package/src/lib/subagent/types.ts +77 -19
  122. package/src/lib/subagent/workflow.ts +83 -12
  123. package/src/lib/tool-router/router.integration.test.ts +137 -4
  124. package/src/lib/tool-router/router.ts +19 -6
  125. package/src/lib/tool-router/types.ts +11 -0
  126. package/src/lib/workflow.test.ts +89 -21
  127. package/src/lib/workflow.ts +33 -18
  128. package/src/workflow.ts +6 -1
  129. package/tsup.config.ts +3 -0
package/README.md CHANGED
@@ -28,20 +28,42 @@ Temporal solves these problems for workflows. Zeitlich brings these guarantees t
28
28
  - **Type-safe tools** — Define tools with Zod schemas, get full TypeScript inference
29
29
  - **Lifecycle hooks** — Pre/post tool execution, session start/end
30
30
  - **Subagent support** — Spawn child agents as Temporal child workflows
31
+ - **Skills** — First-class [agentskills.io](https://agentskills.io) support with progressive disclosure
31
32
  - **Filesystem utilities** — In-memory or custom providers for file operations
32
- - **Model flexibility** — Framework-agnostic model invocation with adapters for LangChain (and more coming)
33
+ - **Model flexibility** — Framework-agnostic model invocation with adapters for LangChain, Vercel AI SDK, or provider-specific SDKs
33
34
 
34
35
  ## LLM Integration
35
36
 
36
- Zeitlich's core is framework-agnostic — it defines generic interfaces (`ModelInvoker`, `ThreadOps`, `MessageContent`) that work with any LLM SDK. Concrete implementations are provided via adapter packages.
37
+ Zeitlich's core is framework-agnostic — it defines generic interfaces (`ModelInvoker`, `ThreadOps`, `MessageContent`) that work with any LLM SDK. You choose a **thread adapter** (for conversation storage and model invocation) and a **sandbox adapter** (for filesystem operations), then wire them together.
37
38
 
38
- ### LangChain Adapter (`zeitlich/adapters/thread/langchain`)
39
+ ### Thread Adapters
39
40
 
40
- The built-in LangChain adapter gives you:
41
+ A thread adapter bundles two concerns:
42
+ 1. **Thread management** — Storing and retrieving conversation messages in Redis
43
+ 2. **Model invocation** — Calling the LLM with the conversation history and tools
41
44
 
42
- - **Provider flexibility** Use Anthropic, OpenAI, Google, Azure, AWS Bedrock, or any LangChain-supported provider
43
- - **Consistent interface** — Same tool calling and message format regardless of provider
44
- - **Easy model swapping** Change models without rewriting agent logic
45
+ Each adapter exposes the same shape: `createActivities(scope)` for Temporal worker registration, and an `invoker` for model calls. Pick the one matching your preferred SDK:
46
+
47
+ | Adapter | Import | SDK |
48
+ |---------|--------|-----|
49
+ | LangChain | `zeitlich/adapters/thread/langchain` | `@langchain/core` + any provider package |
50
+ | Google GenAI | `zeitlich/adapters/thread/google-genai` | `@google/genai` |
51
+
52
+ Vercel AI SDK and other provider-specific adapters can be built by implementing the `ThreadOps` and `ModelInvoker` interfaces.
53
+
54
+ ### Sandbox Adapters
55
+
56
+ A sandbox adapter provides filesystem access for tools like `Bash`, `Read`, `Write`, and `Edit`:
57
+
58
+ | Adapter | Import | Use case |
59
+ |---------|--------|----------|
60
+ | In-memory | `zeitlich/adapters/sandbox/inmemory` | Tests and lightweight agents |
61
+ | Virtual | `zeitlich/adapters/sandbox/virtual` | Custom resolvers with path-only ops |
62
+ | Daytona | `zeitlich/adapters/sandbox/daytona` | Remote Daytona workspaces |
63
+ | E2B | `zeitlich/adapters/sandbox/e2b` | E2B cloud sandboxes |
64
+ | Bedrock | `zeitlich/adapters/sandbox/bedrock` | AWS Bedrock AgentCore Code Interpreter |
65
+
66
+ ### Example: LangChain Adapter
45
67
 
46
68
  ```typescript
47
69
  import { ChatAnthropic } from "@langchain/anthropic";
@@ -55,20 +77,13 @@ const adapter = createLangChainAdapter({
55
77
 
56
78
  export function createActivities(client: WorkflowClient) {
57
79
  return {
58
- // scope must match the workflow name (used by the proxy to resolve activity names)
59
80
  ...adapter.createActivities("myAgentWorkflow"),
60
81
  runAgent: createRunAgentActivity(client, adapter.invoker),
61
82
  };
62
83
  }
63
84
  ```
64
85
 
65
- Install the LangChain package for your chosen provider:
66
-
67
- ```bash
68
- npm install @langchain/core @langchain/anthropic # Anthropic
69
- npm install @langchain/core @langchain/openai # OpenAI
70
- npm install @langchain/core @langchain/google-genai # Google
71
- ```
86
+ All adapters follow the same pattern — `createActivities(scope)` for worker registration and `invoker` for model calls.
72
87
 
73
88
  ## Installation
74
89
 
@@ -79,7 +94,9 @@ npm install zeitlich ioredis
79
94
  **Peer dependencies:**
80
95
 
81
96
  - `ioredis` >= 5.0.0
82
- - `@langchain/core` >= 1.0.0 (optional — only needed when using `zeitlich/adapters/thread/langchain`)
97
+ - `@langchain/core` >= 1.0.0 (optional — only when using the LangChain adapter)
98
+ - `@google/genai` >= 1.0.0 (optional — only when using the Google GenAI adapter)
99
+ - `@aws-sdk/client-bedrock-agentcore` >= 3.900.0 (optional — only when using the Bedrock adapter)
83
100
 
84
101
  **Required infrastructure:**
85
102
 
@@ -91,7 +108,7 @@ npm install zeitlich ioredis
91
108
  Zeitlich uses separate entry points for workflow-side and activity-side code:
92
109
 
93
110
  ```typescript
94
- // In workflow files — no external dependencies (Redis, LangChain, etc.)
111
+ // In workflow files — no external dependencies (Redis, LLM SDKs, etc.)
95
112
  import {
96
113
  createSession,
97
114
  createAgentStateManager,
@@ -99,7 +116,7 @@ import {
99
116
  bashTool,
100
117
  } from "zeitlich/workflow";
101
118
 
102
- // Adapter-specific workflow proxies (auto-scoped to current workflow)
119
+ // Adapter workflow proxies (auto-scoped to current workflow)
103
120
  import { proxyLangChainThreadOps } from "zeitlich/adapters/thread/langchain/workflow";
104
121
  import { proxyInMemorySandboxOps } from "zeitlich/adapters/sandbox/inmemory/workflow";
105
122
 
@@ -111,7 +128,7 @@ import {
111
128
  bashHandler,
112
129
  } from "zeitlich";
113
130
 
114
- // LangChain adapter — activity-side (thread management + model invocation)
131
+ // Thread adapter — activity-side
115
132
  import { createLangChainAdapter } from "zeitlich/adapters/thread/langchain";
116
133
  ```
117
134
 
@@ -146,7 +163,7 @@ export const searchTool: ToolDefinition<"Search", typeof searchSchema> = {
146
163
 
147
164
  ### 2. Create the Workflow
148
165
 
149
- The system prompt is set via `createAgentStateManager`'s `initialState`, and agent config fields (`agentName`, `maxTurns`, etc.) are spread into `createSession`.
166
+ The workflow wires together a **thread adapter** (for conversation storage / model calls) and a **sandbox adapter** (for filesystem tools). Both are pluggable swap the proxy import to switch providers.
150
167
 
151
168
  ```typescript
152
169
  import { proxyActivities, workflowInfo } from "@temporalio/workflow";
@@ -158,10 +175,12 @@ import {
158
175
  bashTool,
159
176
  defineTool,
160
177
  } from "zeitlich/workflow";
161
- import { proxyLangChainThreadOps } from "zeitlich/adapters/thread/langchain/workflow";
162
178
  import { searchTool } from "./tools";
163
179
  import type { MyActivities } from "./activities";
164
180
 
181
+ import { proxyLangChainThreadOps } from "zeitlich/adapters/thread/langchain/workflow";
182
+ import { proxyInMemorySandboxOps } from "zeitlich/adapters/sandbox/inmemory/workflow";
183
+
165
184
  const {
166
185
  runAgentActivity,
167
186
  searchHandlerActivity,
@@ -190,12 +209,12 @@ export const myAgentWorkflow = defineWorkflow(
190
209
  agentName: "my-agent",
191
210
  });
192
211
 
193
- // threadOps auto-scopes to "myAgentWorkflow" via workflowInfo().workflowType
194
212
  const session = await createSession({
195
213
  agentName: "my-agent",
196
214
  maxTurns: 20,
197
- threadId: runId,
215
+ thread: { mode: "new", threadId: runId },
198
216
  threadOps: proxyLangChainThreadOps(),
217
+ sandboxOps: proxyInMemorySandboxOps(),
199
218
  runAgent: runAgentActivity,
200
219
  buildContextMessage: () => [{ type: "text", text: prompt }],
201
220
  tools: {
@@ -228,20 +247,26 @@ export const myAgentWorkflow = defineWorkflow(
228
247
 
229
248
  ### 3. Create Activities
230
249
 
231
- Activities are factory functions that receive infrastructure dependencies (`redis`, `client`). Each returns an object of activity functions registered with the Temporal worker.
250
+ Activities are factory functions that receive infrastructure dependencies (`redis`, `client`). The thread adapter and sandbox provider are configured here swap imports to change LLM or sandbox backend.
232
251
 
233
252
  ```typescript
234
253
  import type Redis from "ioredis";
235
254
  import type { WorkflowClient } from "@temporalio/client";
236
255
  import { ChatAnthropic } from "@langchain/anthropic";
237
256
  import {
257
+ SandboxManager,
238
258
  withSandbox,
239
259
  bashHandler,
240
260
  createAskUserQuestionHandler,
241
261
  createRunAgentActivity,
242
262
  } from "zeitlich";
263
+ import { InMemorySandboxProvider } from "zeitlich/adapters/sandbox/inmemory";
264
+
243
265
  import { createLangChainAdapter } from "zeitlich/adapters/thread/langchain";
244
266
 
267
+ const sandboxProvider = new InMemorySandboxProvider();
268
+ const sandboxManager = new SandboxManager(sandboxProvider);
269
+
245
270
  export const createActivities = ({
246
271
  redis,
247
272
  client,
@@ -258,8 +283,8 @@ export const createActivities = ({
258
283
  });
259
284
 
260
285
  return {
261
- // "myAgentWorkflow" must match the workflow name in defineWorkflow()
262
286
  ...adapter.createActivities("myAgentWorkflow"),
287
+ ...sandboxManager.createActivities("myAgentWorkflow"),
263
288
  runAgentActivity: createRunAgentActivity(client, adapter.invoker),
264
289
  searchHandlerActivity: async (args: { query: string }) => ({
265
290
  toolResponse: JSON.stringify(await performSearch(args.query)),
@@ -411,7 +436,7 @@ export const researcherWorkflow = defineSubagentWorkflow(
411
436
  });
412
437
 
413
438
  const session = await createSession({
414
- ...sessionInput, // spreads agentName, threadId, continueThread, sandboxId
439
+ ...sessionInput, // spreads agentName, thread, sandbox, sandboxShutdown
415
440
  threadOps: proxyLangChainThreadOps(), // auto-scoped to "Researcher"
416
441
  runAgent: runResearcherActivity,
417
442
  buildContextMessage: () => [{ type: "text", text: prompt }],
@@ -439,7 +464,7 @@ export const researcherSubagent = defineSubagent(researcherWorkflow);
439
464
 
440
465
  // Optionally override parent-specific config
441
466
  export const researcherSubagent = defineSubagent(researcherWorkflow, {
442
- allowThreadContinuation: true,
467
+ thread: "fork",
443
468
  sandbox: "own",
444
469
  hooks: {
445
470
  onPostExecution: ({ result }) => console.log("researcher done", result),
@@ -454,39 +479,218 @@ const session = await createSession({
454
479
 
455
480
  The `Subagent` tool is automatically added when subagents are configured, allowing the LLM to spawn child workflows.
456
481
 
457
- ### Thread Continuation
482
+ ### Skills
483
+
484
+ Zeitlich has first-class support for the [agentskills.io](https://agentskills.io) specification. Skills are reusable instruction sets that an agent can load on-demand via the built-in `ReadSkill` tool — progressive disclosure keeps token usage low while giving agents access to rich, domain-specific guidance.
485
+
486
+ #### Defining a Skill
487
+
488
+ Each skill lives in its own directory as a `SKILL.md` file with YAML frontmatter. A skill directory can also contain **resource files** — supporting documents, templates, or data that the agent can read from the sandbox filesystem:
489
+
490
+ ```
491
+ skills/
492
+ ├── code-review/
493
+ │ ├── SKILL.md
494
+ │ └── resources/
495
+ │ └── checklist.md
496
+ ├── pdf-processing/
497
+ │ ├── SKILL.md
498
+ │ └── templates/
499
+ │ └── extraction-prompt.txt
500
+ ```
501
+
502
+ ```markdown
503
+ ---
504
+ name: code-review
505
+ description: Review pull requests for correctness, style, and security issues
506
+ allowed-tools: Bash Grep Read
507
+ license: MIT
508
+ ---
509
+
510
+ ## Instructions
511
+
512
+ When reviewing code, follow these steps:
513
+ 1. Read the diff with `Bash`
514
+ 2. Search for related tests with `Grep`
515
+ 3. Read the checklist from `resources/checklist.md`
516
+ 4. ...
517
+ ```
518
+
519
+ Required fields: `name` and `description`. Optional: `license`, `compatibility`, `allowed-tools` (space-delimited), `metadata` (key-value map).
520
+
521
+ Resource files are any non-`SKILL.md` files inside the skill directory (discovered recursively). When loaded via `FileSystemSkillProvider`, their contents are stored in `skill.resourceContents` — a `Record<string, string>` keyed by relative path (e.g. `"resources/checklist.md"`).
522
+
523
+ #### Loading Skills
524
+
525
+ Use `FileSystemSkillProvider` to load skills from a directory. It accepts any `SandboxFileSystem` implementation. `loadAll()` eagerly reads `SKILL.md` instructions **and** all resource file contents into each `Skill` object:
526
+
527
+ ```typescript
528
+ import { FileSystemSkillProvider } from "zeitlich";
529
+ import { InMemorySandboxProvider } from "zeitlich/adapters/sandbox/inmemory";
530
+
531
+ const provider = new InMemorySandboxProvider();
532
+ const { sandbox } = await provider.create({});
533
+
534
+ const skillProvider = new FileSystemSkillProvider(sandbox.fs, "/skills");
535
+ const skills = await skillProvider.loadAll();
536
+ // Each skill has: { name, description, instructions, resourceContents }
537
+ // resourceContents: { "resources/checklist.md": "...", ... }
538
+ ```
458
539
 
459
- By default, each session initializes a fresh thread. To continue from an existing thread (e.g., resuming a conversation after a workflow completes), pass `continueThread: true` along with the previous `threadId`:
540
+ **Loading from the local filesystem (activity-side):** Use `NodeFsSandboxFileSystem` to read skills from the worker's disk. This is the simplest option when skill files are bundled alongside your application code:
541
+
542
+ ```typescript
543
+ import { NodeFsSandboxFileSystem, FileSystemSkillProvider } from "zeitlich";
544
+ import { fileURLToPath } from "node:url";
545
+ import { dirname, join } from "node:path";
546
+
547
+ const __dirname = dirname(fileURLToPath(import.meta.url));
548
+ const fs = new NodeFsSandboxFileSystem(join(__dirname, "skills"));
549
+ const skillProvider = new FileSystemSkillProvider(fs, "/");
550
+ const skills = await skillProvider.loadAll();
551
+ ```
552
+
553
+ For lightweight discovery without reading file contents, use `listSkills()`:
554
+
555
+ ```typescript
556
+ const metadata = await skillProvider.listSkills();
557
+ // SkillMetadata[] — name, description, location only
558
+ ```
559
+
560
+ Or parse a single file directly:
561
+
562
+ ```typescript
563
+ import { parseSkillFile } from "zeitlich/workflow";
564
+
565
+ const { frontmatter, body } = parseSkillFile(rawMarkdown);
566
+ // frontmatter: SkillMetadata, body: instruction text
567
+ ```
568
+
569
+ #### Passing Skills to a Session
570
+
571
+ Pass loaded skills to `createSession`. Zeitlich automatically:
572
+
573
+ 1. Registers a `ReadSkill` tool whose description lists all available skills — the agent discovers them through the tool definition and loads instructions on demand.
574
+ 2. Seeds `resourceContents` into the sandbox as `initialFiles` (when `sandboxOps` is configured), so the agent can read resource files with its `Read` tool without any extra setup.
460
575
 
461
576
  ```typescript
462
577
  import { createSession } from "zeitlich/workflow";
463
578
 
464
- // First run — threadId defaults to getShortId() if omitted
465
579
  const session = await createSession({
466
- // threadId is optional, auto-generated if not provided
467
580
  // ... other config
581
+ skills, // Skill[] — loaded via FileSystemSkillProvider or manually
468
582
  });
583
+ ```
584
+
585
+ The `ReadSkill` tool accepts a `skill_name` parameter (constrained to an enum of available names) and returns the full instruction body plus a list of available resource file paths. The handler runs directly in the workflow — no activity needed. Resource file contents are not included in the `ReadSkill` response (progressive disclosure); the agent reads them from the sandbox filesystem on demand.
586
+
587
+ #### Building Skills Manually
588
+
589
+ For advanced use cases, you can construct the tool and handler independently:
590
+
591
+ ```typescript
592
+ import { createReadSkillTool, createReadSkillHandler } from "zeitlich/workflow";
593
+
594
+ const tool = createReadSkillTool(skills); // ToolDefinition with enum schema
595
+ const handler = createReadSkillHandler(skills); // Returns skill instructions
596
+ ```
469
597
 
470
- // Later new workflow forks the previous thread
598
+ ### Thread & Sandbox Lifecycle
599
+
600
+ Every session has a **thread** (conversation history) and an optional **sandbox** (filesystem environment). Both are configured with explicit lifecycle types that control how they are initialized and torn down.
601
+
602
+ #### Thread Initialization (`ThreadInit`)
603
+
604
+ The `thread` field on `SessionConfig` (and `WorkflowInput`) accepts one of three modes:
605
+
606
+ | Mode | Description |
607
+ |------|-------------|
608
+ | `{ mode: "new" }` | Start a fresh thread (default). Optionally pass `threadId` to choose the ID. |
609
+ | `{ mode: "fork", threadId }` | Copy all messages from an existing thread into a new one and continue there. The original is never mutated. |
610
+ | `{ mode: "continue", threadId }` | Append directly to an existing thread in-place. |
611
+
612
+ ```typescript
613
+ import { createSession } from "zeitlich/workflow";
614
+
615
+ // First run — fresh thread
616
+ const session = await createSession({
617
+ thread: { mode: "new" },
618
+ // ... other config
619
+ });
620
+
621
+ // Later — fork the previous conversation
471
622
  const resumedSession = await createSession({
472
- threadId: savedThreadId, // the thread to continue from
473
- continueThread: true, // fork into a new thread with the old messages
623
+ thread: { mode: "fork", threadId: savedThreadId },
474
624
  // ... other config
475
625
  });
476
- ```
477
626
 
478
- When `continueThread` is true the session **forks** the provided thread — it copies all messages into a new thread and operates on the copy. The original thread is never mutated, so multiple sessions can safely continue from the same thread in parallel.
627
+ // Or append directly to the existing thread
628
+ const continuedSession = await createSession({
629
+ thread: { mode: "continue", threadId: savedThreadId },
630
+ // ... other config
631
+ });
632
+ ```
479
633
 
480
634
  `getShortId()` produces compact, workflow-deterministic IDs (~12 base-62 chars) that are more token-efficient than UUIDs.
481
635
 
482
- #### Subagent Thread Continuation
636
+ #### Sandbox Initialization (`SandboxInit`)
637
+
638
+ The `sandbox` field controls how a sandbox is created or reused:
639
+
640
+ | Mode | Description |
641
+ |------|-------------|
642
+ | `{ mode: "new" }` | Create a fresh sandbox (default when `sandboxOps` is provided). |
643
+ | `{ mode: "continue", sandboxId }` | Resume a previously-paused sandbox. This session takes ownership. |
644
+ | `{ mode: "fork", sandboxId }` | Fork from an existing sandbox. A new sandbox is created and owned by this session. |
645
+ | `{ mode: "inherit", sandboxId }` | Use a sandbox owned by someone else (e.g. a parent agent). Shutdown policy is ignored. |
646
+
647
+ #### Sandbox Shutdown (`SandboxShutdown`)
648
+
649
+ The `sandboxShutdown` field controls what happens to the sandbox when the session exits:
650
+
651
+ | Value | Description |
652
+ |-------|-------------|
653
+ | `"destroy"` | Tear down the sandbox entirely (default). |
654
+ | `"pause"` | Pause the sandbox so it can be resumed later. |
655
+ | `"keep"` | Leave the sandbox running (no-op on exit). |
656
+
657
+ Subagents also support `"pause-until-parent-close"` — pause on exit, then wait for the parent workflow to signal when to destroy it.
483
658
 
484
- Subagents can opt in to thread continuation via `allowThreadContinuation`. When enabled, the parent agent can pass a `threadId` to resume a previous subagent conversation:
659
+ #### Subagent Thread & Sandbox Config
660
+
661
+ Subagents configure thread and sandbox strategies via `defineSubagent`:
485
662
 
486
663
  ```typescript
487
- import { defineSubagentWorkflow, defineSubagent, createSession } from "zeitlich/workflow";
488
- import { proxyLangChainThreadOps } from "zeitlich/adapters/thread/langchain/workflow";
664
+ import { defineSubagent } from "zeitlich/workflow";
665
+ import { researcherWorkflow } from "./researcher.workflow";
489
666
 
667
+ // Fresh thread each time, no sandbox (defaults)
668
+ export const researcherSubagent = defineSubagent(researcherWorkflow);
669
+
670
+ // Allow the parent to continue a previous conversation via fork
671
+ export const researcherSubagent = defineSubagent(researcherWorkflow, {
672
+ thread: "fork",
673
+ });
674
+
675
+ // Own sandbox with pause-on-exit
676
+ export const researcherSubagent = defineSubagent(researcherWorkflow, {
677
+ thread: "fork",
678
+ sandbox: { source: "own", shutdown: "pause" },
679
+ });
680
+
681
+ // Inherit the parent's sandbox
682
+ export const researcherSubagent = defineSubagent(researcherWorkflow, {
683
+ sandbox: "inherit",
684
+ });
685
+ ```
686
+
687
+ The `thread` field accepts `"new"` (default), `"fork"`, or `"continue"`. When set to `"fork"` or `"continue"`, the parent agent can pass a `threadId` in a subsequent `Task` tool call to resume the conversation. The subagent returns its `threadId` in the response (surfaced as `[Thread ID: ...]`), which the parent can use for continuation.
688
+
689
+ The `sandbox` field accepts `"none"` (default), `"inherit"`, `"own"`, or `{ source: "own", shutdown }` for explicit shutdown policy.
690
+
691
+ The subagent workflow receives lifecycle fields via `sessionInput`:
692
+
693
+ ```typescript
490
694
  export const researcherWorkflow = defineSubagentWorkflow(
491
695
  {
492
696
  name: "Researcher",
@@ -494,28 +698,17 @@ export const researcherWorkflow = defineSubagentWorkflow(
494
698
  },
495
699
  async (prompt, sessionInput) => {
496
700
  const session = await createSession({
497
- ...sessionInput, // threadId/continueThread are provided by parent when resuming
701
+ ...sessionInput, // spreads agentName, thread, sandbox, sandboxShutdown
498
702
  threadOps: proxyLangChainThreadOps(),
499
703
  // ... other config
500
704
  });
501
705
 
502
706
  const { threadId, finalMessage } = await session.runSession({ stateManager });
503
- return {
504
- toolResponse: finalMessage ? extractText(finalMessage) : "No response",
505
- data: null,
506
- threadId,
507
- };
707
+ return { toolResponse: extractText(finalMessage), data: null, threadId };
508
708
  },
509
709
  );
510
-
511
- // Enable thread continuation in the parent registration
512
- export const researcherSubagent = defineSubagent(researcherWorkflow, {
513
- allowThreadContinuation: true,
514
- });
515
710
  ```
516
711
 
517
- The subagent returns its `threadId` in the response, which the handler surfaces to the parent LLM as `[Thread ID: ...]`. The parent can then pass that ID back in a subsequent `Subagent` tool call to continue the conversation.
518
-
519
712
  ### Filesystem Utilities
520
713
 
521
714
  Built-in support for file operations with in-memory or custom filesystem providers (e.g. from [`just-bash`](https://github.com/nicholasgasior/just-bash)).
@@ -632,6 +825,7 @@ Zeitlich provides ready-to-use tool definitions and handlers for common agent op
632
825
  | `Grep` | Search file contents with regex patterns |
633
826
  | `Bash` | Execute shell commands |
634
827
  | `AskUserQuestion` | Ask the user questions during execution with structured options |
828
+ | `ReadSkill` | Load skill instructions on demand (see [Skills](#skills)) |
635
829
  | `Task` | Launch subagents as child workflows (see [Subagents](#subagents)) |
636
830
 
637
831
  ```typescript
@@ -691,7 +885,10 @@ Safe for use in Temporal workflow files:
691
885
  | `getShortId` | Generate a compact, workflow-deterministic identifier (base-62, 12 chars) |
692
886
  | Tool definitions | `askUserQuestionTool`, `globTool`, `grepTool`, `readFileTool`, `writeFileTool`, `editTool`, `bashTool` |
693
887
  | Task tools | `taskCreateTool`, `taskGetTool`, `taskListTool`, `taskUpdateTool` for workflow task management |
694
- | Types | `SubagentDefinition`, `SubagentConfig`, `ToolDefinition`, `ToolWithHandler`, `RouterContext`, `SessionConfig`, etc. |
888
+ | Skill utilities | `parseSkillFile`, `createReadSkillTool`, `createReadSkillHandler` |
889
+ | `defineWorkflow` | Wraps a main workflow function, translating `WorkflowInput` into session-compatible fields |
890
+ | Lifecycle types | `ThreadInit`, `SandboxInit`, `SandboxShutdown`, `SubagentSandboxShutdown`, `SubagentSandboxConfig` |
891
+ | Types | `Skill`, `SkillMetadata`, `SkillProvider`, `SubagentDefinition`, `SubagentConfig`, `ToolDefinition`, `ToolWithHandler`, `RouterContext`, `SessionConfig`, `WorkflowConfig`, `WorkflowInput`, etc. |
695
892
 
696
893
  ### Activity Entry Point (`zeitlich`)
697
894
 
@@ -704,11 +901,13 @@ Framework-agnostic utilities for activities, worker setup, and Node.js code:
704
901
  | `createThreadManager` | Generic Redis-backed thread manager factory |
705
902
  | `toTree` | Generate file tree string from an `IFileSystem` instance |
706
903
  | `withSandbox` | Wraps a handler to auto-resolve sandbox from context (pairs with `withAutoAppend`) |
904
+ | `NodeFsSandboxFileSystem` | `node:fs` adapter for `SandboxFileSystem` — read skills from the worker's local disk |
905
+ | `FileSystemSkillProvider` | Load skills from a directory following the agentskills.io layout |
707
906
  | Tool handlers | `bashHandler`, `editHandler`, `globHandler`, `readFileHandler`, `writeFileHandler`, `createAskUserQuestionHandler` |
708
907
 
709
- ### LangChain Adapter Entry Point (`zeitlich/adapters/thread/langchain`)
908
+ ### Thread Adapter Entry Points
710
909
 
711
- LangChain-specific implementations:
910
+ **LangChain** (`zeitlich/adapters/thread/langchain`):
712
911
 
713
912
  | Export | Description |
714
913
  | ----------------------------------- | ---------------------------------------------------------------------- |
@@ -717,6 +916,15 @@ LangChain-specific implementations:
717
916
  | `invokeLangChainModel` | One-shot model invocation convenience function |
718
917
  | `createLangChainThreadManager` | Thread manager with LangChain `StoredMessage` helpers |
719
918
 
919
+ **Google GenAI** (`zeitlich/adapters/thread/google-genai`):
920
+
921
+ | Export | Description |
922
+ | ----------------------------------- | ---------------------------------------------------------------------- |
923
+ | `createGoogleGenAIAdapter` | Unified adapter returning `createActivities`, `invoker`, `createModelInvoker` |
924
+ | `createGoogleGenAIModelInvoker` | Factory that returns a `ModelInvoker` backed by the `@google/genai` SDK |
925
+ | `invokeGoogleGenAIModel` | One-shot model invocation convenience function |
926
+ | `createGoogleGenAIThreadManager` | Thread manager with Google GenAI `Content` helpers |
927
+
720
928
  ### Types
721
929
 
722
930
  | Export | Description |
@@ -731,6 +939,11 @@ LangChain-specific implementations:
731
939
  | `RouterContext` | Base context every tool handler receives (`threadId`, `toolCallId`, `toolName`, `sandboxId?`) |
732
940
  | `Hooks` | Combined session lifecycle + tool execution hooks |
733
941
  | `ToolRouterHooks` | Narrowed hook interface for tool execution only (pre/post/failure) |
942
+ | `ThreadInit` | Thread initialization strategy: `"new"`, `"continue"`, or `"fork"` |
943
+ | `SandboxInit` | Sandbox initialization strategy: `"new"`, `"continue"`, `"fork"`, or `"inherit"` |
944
+ | `SandboxShutdown` | Sandbox exit policy: `"destroy" \| "pause" \| "keep"` |
945
+ | `SubagentSandboxShutdown` | Extended shutdown with `"pause-until-parent-close"` |
946
+ | `SubagentSandboxConfig` | Subagent sandbox strategy: `"none" \| "inherit" \| "own" \| { source, shutdown }` |
734
947
  | `SubagentDefinition` | Callable subagent workflow with embedded metadata (from `defineSubagentWorkflow`) |
735
948
  | `SubagentConfig` | Resolved subagent configuration consumed by `createSession` |
736
949
  | `AgentState` | Generic agent state type |
@@ -748,6 +961,7 @@ LangChain-specific implementations:
748
961
  │ │ │ • Turns │ │ • Tool routing & hooks │ │ │
749
962
  │ │ │ • Custom state │ │ • Prompts (system, context) │ │ │
750
963
  │ │ └────────────────┘ │ • Subagent coordination │ │ │
964
+ │ │ │ • Skills (progressive load) │ │ │
751
965
  │ │ └───────────────────────────────┘ │ │
752
966
  │ └──────────────────────────────────────────────────────────┘ │
753
967
  │ │ │
@@ -758,9 +972,14 @@ LangChain-specific implementations:
758
972
  │ └──────────────────────────────────────────────────────────┘ │
759
973
  │ │ │
760
974
  │ ┌──────────────────────────────────────────────────────────┐ │
761
- │ │ LLM Adapter (zeitlich/adapters/thread/langchain) │ │
762
- │ │ • createLangChainAdapter (thread ops + model invoker) │ │
763
- │ │ • createLangChainThreadManager (message helpers) │ │
975
+ │ │ Thread Adapter (zeitlich/adapters/thread/*) │ │
976
+ │ │ • LangChain, Google GenAI, or custom │ │
977
+ │ │ • Thread ops (message storage) + model invoker │ │
978
+ │ └──────────────────────────────────────────────────────────┘ │
979
+ │ ┌──────────────────────────────────────────────────────────┐ │
980
+ │ │ Sandbox Adapter (zeitlich/adapters/sandbox/*) │ │
981
+ │ │ • In-memory, Virtual, Daytona, E2B, Bedrock, or custom │ │
982
+ │ │ • Filesystem ops for agent tools │ │
764
983
  │ └──────────────────────────────────────────────────────────┘ │
765
984
  └─────────────────────────────────────────────────────────────────┘
766
985