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.
- package/README.md +278 -59
- package/dist/adapters/sandbox/bedrock/index.cjs +427 -0
- package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -0
- package/dist/adapters/sandbox/bedrock/index.d.cts +23 -0
- package/dist/adapters/sandbox/bedrock/index.d.ts +23 -0
- package/dist/adapters/sandbox/bedrock/index.js +424 -0
- package/dist/adapters/sandbox/bedrock/index.js.map +1 -0
- package/dist/adapters/sandbox/bedrock/workflow.cjs +33 -0
- package/dist/adapters/sandbox/bedrock/workflow.cjs.map +1 -0
- package/dist/adapters/sandbox/bedrock/workflow.d.cts +29 -0
- package/dist/adapters/sandbox/bedrock/workflow.d.ts +29 -0
- package/dist/adapters/sandbox/bedrock/workflow.js +31 -0
- package/dist/adapters/sandbox/bedrock/workflow.js.map +1 -0
- package/dist/adapters/sandbox/daytona/index.cjs +4 -1
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +2 -1
- package/dist/adapters/sandbox/daytona/index.d.ts +2 -1
- package/dist/adapters/sandbox/daytona/index.js +4 -1
- package/dist/adapters/sandbox/daytona/index.js.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.cjs +1 -0
- package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/daytona/workflow.js +1 -0
- package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.cjs +16 -2
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.d.cts +3 -2
- package/dist/adapters/sandbox/inmemory/index.d.ts +3 -2
- package/dist/adapters/sandbox/inmemory/index.js +16 -2
- package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.cjs +1 -0
- package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.js +1 -0
- package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
- package/dist/adapters/sandbox/virtual/index.cjs +45 -11
- package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
- package/dist/adapters/sandbox/virtual/index.d.cts +6 -5
- package/dist/adapters/sandbox/virtual/index.d.ts +6 -5
- package/dist/adapters/sandbox/virtual/index.js +45 -11
- package/dist/adapters/sandbox/virtual/index.js.map +1 -1
- package/dist/adapters/sandbox/virtual/workflow.cjs +1 -0
- package/dist/adapters/sandbox/virtual/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/virtual/workflow.d.cts +3 -3
- package/dist/adapters/sandbox/virtual/workflow.d.ts +3 -3
- package/dist/adapters/sandbox/virtual/workflow.js +1 -0
- package/dist/adapters/sandbox/virtual/workflow.js.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +3 -3
- package/dist/adapters/thread/google-genai/index.d.ts +3 -3
- package/dist/adapters/thread/google-genai/workflow.d.cts +3 -3
- package/dist/adapters/thread/google-genai/workflow.d.ts +3 -3
- package/dist/adapters/thread/langchain/index.d.cts +3 -3
- package/dist/adapters/thread/langchain/index.d.ts +3 -3
- package/dist/adapters/thread/langchain/workflow.d.cts +3 -3
- package/dist/adapters/thread/langchain/workflow.d.ts +3 -3
- package/dist/index.cjs +443 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +64 -10
- package/dist/index.d.ts +64 -10
- package/dist/index.js +442 -71
- package/dist/index.js.map +1 -1
- package/dist/{queries-Bw6WEPMw.d.cts → queries-BYGBImeC.d.cts} +1 -1
- package/dist/{queries-C27raDaB.d.ts → queries-DwBe2CAA.d.ts} +1 -1
- package/dist/{types-C5bkx6kQ.d.ts → types-7PeMi1bD.d.cts} +167 -36
- package/dist/{types-BJ8itUAl.d.cts → types-Bf8KV0Ci.d.cts} +6 -6
- package/dist/{types-HBosetv3.d.cts → types-ChAMwU3q.d.cts} +2 -0
- package/dist/{types-HBosetv3.d.ts → types-ChAMwU3q.d.ts} +2 -0
- package/dist/{types-YbL7JpEA.d.cts → types-D_igp10o.d.cts} +11 -0
- package/dist/{types-YbL7JpEA.d.ts → types-D_igp10o.d.ts} +11 -0
- package/dist/types-DhTCEMhr.d.cts +64 -0
- package/dist/{types-ENYCKFBk.d.ts → types-LVKmCNds.d.ts} +6 -6
- package/dist/types-d9NznUqd.d.ts +64 -0
- package/dist/{types-ClsHhtwL.d.cts → types-hmferhc2.d.ts} +167 -36
- package/dist/workflow.cjs +308 -63
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +54 -32
- package/dist/workflow.d.ts +54 -32
- package/dist/workflow.js +306 -61
- package/dist/workflow.js.map +1 -1
- package/package.json +27 -2
- package/src/adapters/sandbox/bedrock/filesystem.ts +313 -0
- package/src/adapters/sandbox/bedrock/index.ts +259 -0
- package/src/adapters/sandbox/bedrock/proxy.ts +56 -0
- package/src/adapters/sandbox/bedrock/types.ts +24 -0
- package/src/adapters/sandbox/daytona/filesystem.ts +1 -1
- package/src/adapters/sandbox/daytona/index.ts +4 -0
- package/src/adapters/sandbox/daytona/proxy.ts +4 -3
- package/src/adapters/sandbox/e2b/index.ts +5 -0
- package/src/adapters/sandbox/inmemory/index.ts +24 -4
- package/src/adapters/sandbox/inmemory/proxy.ts +2 -2
- package/src/adapters/sandbox/virtual/filesystem.ts +44 -18
- package/src/adapters/sandbox/virtual/provider.ts +13 -0
- package/src/adapters/sandbox/virtual/proxy.ts +1 -0
- package/src/adapters/sandbox/virtual/types.ts +9 -4
- package/src/adapters/sandbox/virtual/virtual-sandbox.test.ts +26 -0
- package/src/index.ts +2 -1
- package/src/lib/lifecycle.ts +57 -0
- package/src/lib/sandbox/manager.ts +13 -1
- package/src/lib/sandbox/node-fs.ts +115 -0
- package/src/lib/sandbox/types.ts +13 -4
- package/src/lib/session/index.ts +1 -0
- package/src/lib/session/session-edge-cases.integration.test.ts +447 -33
- package/src/lib/session/session.integration.test.ts +149 -32
- package/src/lib/session/session.ts +138 -33
- package/src/lib/session/types.ts +56 -17
- package/src/lib/skills/fs-provider.ts +65 -4
- package/src/lib/skills/handler.ts +43 -1
- package/src/lib/skills/index.ts +0 -1
- package/src/lib/skills/register.ts +17 -1
- package/src/lib/skills/skills.integration.test.ts +308 -24
- package/src/lib/skills/types.ts +6 -0
- package/src/lib/subagent/define.ts +5 -4
- package/src/lib/subagent/handler.ts +143 -14
- package/src/lib/subagent/index.ts +3 -0
- package/src/lib/subagent/register.ts +10 -3
- package/src/lib/subagent/signals.ts +8 -0
- package/src/lib/subagent/subagent.integration.test.ts +853 -150
- package/src/lib/subagent/tool.ts +2 -2
- package/src/lib/subagent/types.ts +77 -19
- package/src/lib/subagent/workflow.ts +83 -12
- package/src/lib/tool-router/router.integration.test.ts +137 -4
- package/src/lib/tool-router/router.ts +19 -6
- package/src/lib/tool-router/types.ts +11 -0
- package/src/lib/workflow.test.ts +89 -21
- package/src/lib/workflow.ts +33 -18
- package/src/workflow.ts +6 -1
- 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
|
|
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.
|
|
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
|
-
###
|
|
39
|
+
### Thread Adapters
|
|
39
40
|
|
|
40
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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`).
|
|
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,
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
####
|
|
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
|
-
|
|
659
|
+
#### Subagent Thread & Sandbox Config
|
|
660
|
+
|
|
661
|
+
Subagents configure thread and sandbox strategies via `defineSubagent`:
|
|
485
662
|
|
|
486
663
|
```typescript
|
|
487
|
-
import {
|
|
488
|
-
import {
|
|
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, //
|
|
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
|
-
|
|
|
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
|
-
###
|
|
908
|
+
### Thread Adapter Entry Points
|
|
710
909
|
|
|
711
|
-
LangChain
|
|
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
|
-
│ │
|
|
762
|
-
│ │ •
|
|
763
|
-
│ │ •
|
|
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
|
│
|