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.
- package/README.md +303 -105
- package/dist/adapters/sandbox/daytona/index.cjs +7 -1
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +3 -1
- package/dist/adapters/sandbox/daytona/index.d.ts +3 -1
- package/dist/adapters/sandbox/daytona/index.js +7 -1
- package/dist/adapters/sandbox/daytona/index.js.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.cjs +33 -0
- package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -0
- package/dist/adapters/sandbox/daytona/workflow.d.cts +27 -0
- package/dist/adapters/sandbox/daytona/workflow.d.ts +27 -0
- package/dist/adapters/sandbox/daytona/workflow.js +31 -0
- package/dist/adapters/sandbox/daytona/workflow.js.map +1 -0
- package/dist/adapters/sandbox/inmemory/index.cjs +18 -1
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.d.cts +4 -2
- package/dist/adapters/sandbox/inmemory/index.d.ts +4 -2
- package/dist/adapters/sandbox/inmemory/index.js +18 -1
- package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.cjs +33 -0
- package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -0
- package/dist/adapters/sandbox/inmemory/workflow.d.cts +25 -0
- package/dist/adapters/sandbox/inmemory/workflow.d.ts +25 -0
- package/dist/adapters/sandbox/inmemory/workflow.js +31 -0
- package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -0
- package/dist/adapters/sandbox/virtual/index.cjs +36 -9
- package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
- package/dist/adapters/sandbox/virtual/index.d.cts +8 -5
- package/dist/adapters/sandbox/virtual/index.d.ts +8 -5
- package/dist/adapters/sandbox/virtual/index.js +36 -9
- package/dist/adapters/sandbox/virtual/index.js.map +1 -1
- package/dist/adapters/sandbox/virtual/workflow.cjs +33 -0
- package/dist/adapters/sandbox/virtual/workflow.cjs.map +1 -0
- package/dist/adapters/sandbox/virtual/workflow.d.cts +27 -0
- package/dist/adapters/sandbox/virtual/workflow.d.ts +27 -0
- package/dist/adapters/sandbox/virtual/workflow.js +31 -0
- package/dist/adapters/sandbox/virtual/workflow.js.map +1 -0
- package/dist/adapters/thread/google-genai/index.cjs +9 -1
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +31 -19
- package/dist/adapters/thread/google-genai/index.d.ts +31 -19
- package/dist/adapters/thread/google-genai/index.js +9 -1
- package/dist/adapters/thread/google-genai/index.js.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.cjs +33 -0
- package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -0
- package/dist/adapters/thread/google-genai/workflow.d.cts +32 -0
- package/dist/adapters/thread/google-genai/workflow.d.ts +32 -0
- package/dist/adapters/thread/google-genai/workflow.js +31 -0
- package/dist/adapters/thread/google-genai/workflow.js.map +1 -0
- package/dist/adapters/thread/langchain/index.cjs +9 -1
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- package/dist/adapters/thread/langchain/index.d.cts +27 -16
- package/dist/adapters/thread/langchain/index.d.ts +27 -16
- package/dist/adapters/thread/langchain/index.js +9 -1
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/adapters/thread/langchain/workflow.cjs +33 -0
- package/dist/adapters/thread/langchain/workflow.cjs.map +1 -0
- package/dist/adapters/thread/langchain/workflow.d.cts +32 -0
- package/dist/adapters/thread/langchain/workflow.d.ts +32 -0
- package/dist/adapters/thread/langchain/workflow.js +31 -0
- package/dist/adapters/thread/langchain/workflow.js.map +1 -0
- package/dist/index.cjs +282 -90
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -16
- package/dist/index.d.ts +38 -16
- package/dist/index.js +281 -87
- package/dist/index.js.map +1 -1
- package/dist/queries-DModcWRy.d.cts +44 -0
- package/dist/queries-byD0jr1Y.d.ts +44 -0
- package/dist/{types-BkAYmc96.d.ts → types-B50pBPEV.d.ts} +190 -38
- package/dist/{types-YbL7JpEA.d.cts → types-Bll19FZJ.d.cts} +7 -0
- package/dist/{types-YbL7JpEA.d.ts → types-Bll19FZJ.d.ts} +7 -0
- package/dist/{queries-6Avfh74U.d.ts → types-BuXdFhaZ.d.cts} +7 -48
- package/dist/{types-BMRzfELQ.d.cts → types-ChAMwU3q.d.cts} +17 -1
- package/dist/{types-BMRzfELQ.d.ts → types-ChAMwU3q.d.ts} +17 -1
- package/dist/{types-CES_30qx.d.cts → types-DQW8l7pY.d.cts} +190 -38
- package/dist/{queries-CHa2iv_I.d.cts → types-GZ76HZSj.d.ts} +7 -48
- package/dist/workflow.cjs +244 -86
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +54 -65
- package/dist/workflow.d.ts +54 -65
- package/dist/workflow.js +243 -83
- package/dist/workflow.js.map +1 -1
- package/package.json +54 -2
- package/src/adapters/sandbox/daytona/filesystem.ts +1 -1
- package/src/adapters/sandbox/daytona/index.ts +8 -0
- package/src/adapters/sandbox/daytona/proxy.ts +56 -0
- package/src/adapters/sandbox/e2b/filesystem.ts +147 -0
- package/src/adapters/sandbox/e2b/index.ts +164 -0
- package/src/adapters/sandbox/e2b/types.ts +23 -0
- package/src/adapters/sandbox/inmemory/index.ts +27 -3
- package/src/adapters/sandbox/inmemory/proxy.ts +53 -0
- package/src/adapters/sandbox/virtual/filesystem.ts +41 -17
- package/src/adapters/sandbox/virtual/provider.ts +9 -1
- package/src/adapters/sandbox/virtual/proxy.ts +53 -0
- package/src/adapters/sandbox/virtual/types.ts +9 -4
- package/src/adapters/thread/google-genai/activities.ts +51 -17
- package/src/adapters/thread/google-genai/index.ts +1 -0
- package/src/adapters/thread/google-genai/proxy.ts +61 -0
- package/src/adapters/thread/langchain/activities.ts +47 -14
- package/src/adapters/thread/langchain/index.ts +1 -0
- package/src/adapters/thread/langchain/proxy.ts +61 -0
- package/src/lib/lifecycle.ts +57 -0
- package/src/lib/sandbox/manager.ts +52 -6
- package/src/lib/sandbox/sandbox.test.ts +12 -11
- package/src/lib/sandbox/types.ts +31 -4
- package/src/lib/session/index.ts +4 -5
- package/src/lib/session/session-edge-cases.integration.test.ts +491 -66
- package/src/lib/session/session.integration.test.ts +92 -80
- package/src/lib/session/session.ts +108 -96
- package/src/lib/session/types.ts +87 -17
- package/src/lib/subagent/define.ts +6 -5
- package/src/lib/subagent/handler.ts +148 -16
- package/src/lib/subagent/index.ts +4 -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 +893 -128
- package/src/lib/subagent/tool.ts +2 -2
- package/src/lib/subagent/types.ts +84 -21
- package/src/lib/subagent/workflow.ts +83 -12
- package/src/lib/tool-router/router-edge-cases.integration.test.ts +4 -1
- package/src/lib/tool-router/router.integration.test.ts +141 -5
- package/src/lib/tool-router/router.ts +13 -3
- package/src/lib/tool-router/types.ts +7 -0
- package/src/lib/workflow.test.ts +104 -27
- package/src/lib/workflow.ts +37 -19
- package/src/tools/bash/bash.test.ts +16 -7
- package/src/workflow.ts +11 -14
- package/tsup.config.ts +6 -0
package/README.md
CHANGED
|
@@ -28,20 +28,41 @@ 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
|
+
|
|
65
|
+
### Example: LangChain Adapter
|
|
45
66
|
|
|
46
67
|
```typescript
|
|
47
68
|
import { ChatAnthropic } from "@langchain/anthropic";
|
|
@@ -55,19 +76,13 @@ const adapter = createLangChainAdapter({
|
|
|
55
76
|
|
|
56
77
|
export function createActivities(client: WorkflowClient) {
|
|
57
78
|
return {
|
|
58
|
-
...adapter.
|
|
79
|
+
...adapter.createActivities("myAgentWorkflow"),
|
|
59
80
|
runAgent: createRunAgentActivity(client, adapter.invoker),
|
|
60
81
|
};
|
|
61
82
|
}
|
|
62
83
|
```
|
|
63
84
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
npm install @langchain/core @langchain/anthropic # Anthropic
|
|
68
|
-
npm install @langchain/core @langchain/openai # OpenAI
|
|
69
|
-
npm install @langchain/core @langchain/google-genai # Google
|
|
70
|
-
```
|
|
85
|
+
All adapters follow the same pattern — `createActivities(scope)` for worker registration and `invoker` for model calls.
|
|
71
86
|
|
|
72
87
|
## Installation
|
|
73
88
|
|
|
@@ -78,7 +93,8 @@ npm install zeitlich ioredis
|
|
|
78
93
|
**Peer dependencies:**
|
|
79
94
|
|
|
80
95
|
- `ioredis` >= 5.0.0
|
|
81
|
-
- `@langchain/core` >= 1.0.0 (optional — only
|
|
96
|
+
- `@langchain/core` >= 1.0.0 (optional — only when using the LangChain adapter)
|
|
97
|
+
- `@google/genai` >= 1.0.0 (optional — only when using the Google GenAI adapter)
|
|
82
98
|
|
|
83
99
|
**Required infrastructure:**
|
|
84
100
|
|
|
@@ -87,40 +103,40 @@ npm install zeitlich ioredis
|
|
|
87
103
|
|
|
88
104
|
## Import Paths
|
|
89
105
|
|
|
90
|
-
Zeitlich
|
|
106
|
+
Zeitlich uses separate entry points for workflow-side and activity-side code:
|
|
91
107
|
|
|
92
108
|
```typescript
|
|
93
|
-
// In workflow files — no external dependencies (Redis,
|
|
109
|
+
// In workflow files — no external dependencies (Redis, LLM SDKs, etc.)
|
|
94
110
|
import {
|
|
95
111
|
createSession,
|
|
96
112
|
createAgentStateManager,
|
|
97
|
-
askUserQuestionTool,
|
|
98
|
-
bashTool,
|
|
99
113
|
defineTool,
|
|
100
|
-
|
|
101
|
-
defineSubagent,
|
|
102
|
-
type ModelInvoker,
|
|
114
|
+
bashTool,
|
|
103
115
|
} from "zeitlich/workflow";
|
|
104
116
|
|
|
117
|
+
// Adapter workflow proxies (auto-scoped to current workflow)
|
|
118
|
+
import { proxyLangChainThreadOps } from "zeitlich/adapters/thread/langchain/workflow";
|
|
119
|
+
import { proxyInMemorySandboxOps } from "zeitlich/adapters/sandbox/inmemory/workflow";
|
|
120
|
+
|
|
105
121
|
// In activity files and worker setup — framework-agnostic core
|
|
106
122
|
import {
|
|
107
123
|
createRunAgentActivity,
|
|
108
|
-
|
|
124
|
+
SandboxManager,
|
|
109
125
|
withSandbox,
|
|
110
126
|
bashHandler,
|
|
111
|
-
createAskUserQuestionHandler,
|
|
112
|
-
toTree,
|
|
113
127
|
} from "zeitlich";
|
|
114
128
|
|
|
115
|
-
//
|
|
129
|
+
// Thread adapter — activity-side
|
|
116
130
|
import { createLangChainAdapter } from "zeitlich/adapters/thread/langchain";
|
|
117
131
|
```
|
|
118
132
|
|
|
119
|
-
**
|
|
133
|
+
**Entry points:**
|
|
120
134
|
|
|
121
135
|
- `zeitlich/workflow` — Pure TypeScript, safe for Temporal's V8 sandbox
|
|
136
|
+
- `zeitlich/adapters/*/workflow` — Workflow-side proxies that auto-scope activities to the current workflow
|
|
122
137
|
- `zeitlich` — Activity-side utilities (Redis, filesystem), framework-agnostic
|
|
123
|
-
- `zeitlich/adapters/thread
|
|
138
|
+
- `zeitlich/adapters/thread/*` — Activity-side adapters (thread management + model invocation)
|
|
139
|
+
- `zeitlich/adapters/sandbox/*` — Activity-side sandbox providers
|
|
124
140
|
|
|
125
141
|
## Examples
|
|
126
142
|
|
|
@@ -145,13 +161,14 @@ export const searchTool: ToolDefinition<"Search", typeof searchSchema> = {
|
|
|
145
161
|
|
|
146
162
|
### 2. Create the Workflow
|
|
147
163
|
|
|
148
|
-
The
|
|
164
|
+
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.
|
|
149
165
|
|
|
150
166
|
```typescript
|
|
151
167
|
import { proxyActivities, workflowInfo } from "@temporalio/workflow";
|
|
152
168
|
import {
|
|
153
169
|
createAgentStateManager,
|
|
154
170
|
createSession,
|
|
171
|
+
defineWorkflow,
|
|
155
172
|
askUserQuestionTool,
|
|
156
173
|
bashTool,
|
|
157
174
|
defineTool,
|
|
@@ -159,6 +176,9 @@ import {
|
|
|
159
176
|
import { searchTool } from "./tools";
|
|
160
177
|
import type { MyActivities } from "./activities";
|
|
161
178
|
|
|
179
|
+
import { proxyLangChainThreadOps } from "zeitlich/adapters/thread/langchain/workflow";
|
|
180
|
+
import { proxyInMemorySandboxOps } from "zeitlich/adapters/sandbox/inmemory/workflow";
|
|
181
|
+
|
|
162
182
|
const {
|
|
163
183
|
runAgentActivity,
|
|
164
184
|
searchHandlerActivity,
|
|
@@ -175,64 +195,76 @@ const {
|
|
|
175
195
|
heartbeatTimeout: "5m",
|
|
176
196
|
});
|
|
177
197
|
|
|
178
|
-
export
|
|
179
|
-
|
|
198
|
+
export const myAgentWorkflow = defineWorkflow(
|
|
199
|
+
{ name: "myAgentWorkflow" },
|
|
200
|
+
async ({ prompt }: { prompt: string }, sessionInput) => {
|
|
201
|
+
const { runId } = workflowInfo();
|
|
180
202
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
203
|
+
const stateManager = createAgentStateManager({
|
|
204
|
+
initialState: {
|
|
205
|
+
systemPrompt: "You are a helpful assistant.",
|
|
206
|
+
},
|
|
207
|
+
agentName: "my-agent",
|
|
208
|
+
});
|
|
187
209
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
210
|
+
const session = await createSession({
|
|
211
|
+
agentName: "my-agent",
|
|
212
|
+
maxTurns: 20,
|
|
213
|
+
thread: { mode: "new", threadId: runId },
|
|
214
|
+
threadOps: proxyLangChainThreadOps(),
|
|
215
|
+
sandboxOps: proxyInMemorySandboxOps(),
|
|
216
|
+
runAgent: runAgentActivity,
|
|
217
|
+
buildContextMessage: () => [{ type: "text", text: prompt }],
|
|
218
|
+
tools: {
|
|
219
|
+
Search: defineTool({
|
|
220
|
+
...searchTool,
|
|
221
|
+
handler: searchHandlerActivity,
|
|
222
|
+
}),
|
|
223
|
+
AskUserQuestion: defineTool({
|
|
224
|
+
...askUserQuestionTool,
|
|
225
|
+
handler: askUserQuestionHandlerActivity,
|
|
226
|
+
hooks: {
|
|
227
|
+
onPostToolUse: () => {
|
|
228
|
+
stateManager.waitForInput();
|
|
229
|
+
},
|
|
205
230
|
},
|
|
206
|
-
},
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
231
|
+
}),
|
|
232
|
+
Bash: defineTool({
|
|
233
|
+
...bashTool,
|
|
234
|
+
handler: bashHandlerActivity,
|
|
235
|
+
}),
|
|
236
|
+
},
|
|
237
|
+
...sessionInput,
|
|
238
|
+
});
|
|
214
239
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
240
|
+
const result = await session.runSession({ stateManager });
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
);
|
|
218
244
|
```
|
|
219
245
|
|
|
220
246
|
### 3. Create Activities
|
|
221
247
|
|
|
222
|
-
Activities are factory functions that receive infrastructure dependencies (`redis`, `client`).
|
|
248
|
+
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.
|
|
223
249
|
|
|
224
250
|
```typescript
|
|
225
251
|
import type Redis from "ioredis";
|
|
226
252
|
import type { WorkflowClient } from "@temporalio/client";
|
|
227
253
|
import { ChatAnthropic } from "@langchain/anthropic";
|
|
228
254
|
import {
|
|
255
|
+
SandboxManager,
|
|
229
256
|
withSandbox,
|
|
230
257
|
bashHandler,
|
|
231
258
|
createAskUserQuestionHandler,
|
|
232
259
|
createRunAgentActivity,
|
|
233
260
|
} from "zeitlich";
|
|
261
|
+
import { InMemorySandboxProvider } from "zeitlich/adapters/sandbox/inmemory";
|
|
262
|
+
|
|
234
263
|
import { createLangChainAdapter } from "zeitlich/adapters/thread/langchain";
|
|
235
264
|
|
|
265
|
+
const sandboxProvider = new InMemorySandboxProvider();
|
|
266
|
+
const sandboxManager = new SandboxManager(sandboxProvider);
|
|
267
|
+
|
|
236
268
|
export const createActivities = ({
|
|
237
269
|
redis,
|
|
238
270
|
client,
|
|
@@ -240,7 +272,7 @@ export const createActivities = ({
|
|
|
240
272
|
redis: Redis;
|
|
241
273
|
client: WorkflowClient;
|
|
242
274
|
}) => {
|
|
243
|
-
const
|
|
275
|
+
const adapter = createLangChainAdapter({
|
|
244
276
|
redis,
|
|
245
277
|
model: new ChatAnthropic({
|
|
246
278
|
model: "claude-sonnet-4-20250514",
|
|
@@ -249,8 +281,9 @@ export const createActivities = ({
|
|
|
249
281
|
});
|
|
250
282
|
|
|
251
283
|
return {
|
|
252
|
-
...
|
|
253
|
-
|
|
284
|
+
...adapter.createActivities("myAgentWorkflow"),
|
|
285
|
+
...sandboxManager.createActivities("myAgentWorkflow"),
|
|
286
|
+
runAgentActivity: createRunAgentActivity(client, adapter.invoker),
|
|
254
287
|
searchHandlerActivity: async (args: { query: string }) => ({
|
|
255
288
|
toolResponse: JSON.stringify(await performSearch(args.query)),
|
|
256
289
|
data: null,
|
|
@@ -382,6 +415,7 @@ import {
|
|
|
382
415
|
createSession,
|
|
383
416
|
defineSubagentWorkflow,
|
|
384
417
|
} from "zeitlich/workflow";
|
|
418
|
+
import { proxyLangChainThreadOps } from "zeitlich/adapters/thread/langchain/workflow";
|
|
385
419
|
import type { createResearcherActivities } from "./activities";
|
|
386
420
|
|
|
387
421
|
const { runResearcherActivity } = proxyActivities<
|
|
@@ -400,7 +434,8 @@ export const researcherWorkflow = defineSubagentWorkflow(
|
|
|
400
434
|
});
|
|
401
435
|
|
|
402
436
|
const session = await createSession({
|
|
403
|
-
...sessionInput, // spreads agentName,
|
|
437
|
+
...sessionInput, // spreads agentName, thread, sandbox, sandboxShutdown
|
|
438
|
+
threadOps: proxyLangChainThreadOps(), // auto-scoped to "Researcher"
|
|
404
439
|
runAgent: runResearcherActivity,
|
|
405
440
|
buildContextMessage: () => [{ type: "text", text: prompt }],
|
|
406
441
|
});
|
|
@@ -427,7 +462,7 @@ export const researcherSubagent = defineSubagent(researcherWorkflow);
|
|
|
427
462
|
|
|
428
463
|
// Optionally override parent-specific config
|
|
429
464
|
export const researcherSubagent = defineSubagent(researcherWorkflow, {
|
|
430
|
-
|
|
465
|
+
thread: "fork",
|
|
431
466
|
sandbox: "own",
|
|
432
467
|
hooks: {
|
|
433
468
|
onPostExecution: ({ result }) => console.log("researcher done", result),
|
|
@@ -442,38 +477,186 @@ const session = await createSession({
|
|
|
442
477
|
|
|
443
478
|
The `Subagent` tool is automatically added when subagents are configured, allowing the LLM to spawn child workflows.
|
|
444
479
|
|
|
445
|
-
###
|
|
480
|
+
### Skills
|
|
481
|
+
|
|
482
|
+
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.
|
|
483
|
+
|
|
484
|
+
#### Defining a Skill
|
|
485
|
+
|
|
486
|
+
Each skill lives in its own directory as a `SKILL.md` file with YAML frontmatter:
|
|
487
|
+
|
|
488
|
+
```
|
|
489
|
+
skills/
|
|
490
|
+
├── code-review/
|
|
491
|
+
│ └── SKILL.md
|
|
492
|
+
├── pdf-processing/
|
|
493
|
+
│ └── SKILL.md
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
```markdown
|
|
497
|
+
---
|
|
498
|
+
name: code-review
|
|
499
|
+
description: Review pull requests for correctness, style, and security issues
|
|
500
|
+
allowed-tools: Bash Grep Read
|
|
501
|
+
license: MIT
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## Instructions
|
|
505
|
+
|
|
506
|
+
When reviewing code, follow these steps:
|
|
507
|
+
1. Read the diff with `Bash`
|
|
508
|
+
2. Search for related tests with `Grep`
|
|
509
|
+
3. ...
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Required fields: `name` and `description`. Optional: `license`, `compatibility`, `allowed-tools` (space-delimited), `metadata` (key-value map).
|
|
513
|
+
|
|
514
|
+
#### Loading Skills
|
|
515
|
+
|
|
516
|
+
Use `FileSystemSkillProvider` to load skills from a directory (works with any sandbox filesystem):
|
|
517
|
+
|
|
518
|
+
```typescript
|
|
519
|
+
import { FileSystemSkillProvider } from "zeitlich";
|
|
520
|
+
import { InMemorySandboxProvider } from "zeitlich/adapters/sandbox/inmemory";
|
|
521
|
+
|
|
522
|
+
const provider = new InMemorySandboxProvider();
|
|
523
|
+
const { sandbox } = await provider.create({});
|
|
524
|
+
|
|
525
|
+
const skillProvider = new FileSystemSkillProvider(sandbox.fs, "/skills");
|
|
526
|
+
const skills = await skillProvider.loadAll();
|
|
527
|
+
```
|
|
446
528
|
|
|
447
|
-
|
|
529
|
+
Or parse a single file directly:
|
|
530
|
+
|
|
531
|
+
```typescript
|
|
532
|
+
import { parseSkillFile } from "zeitlich/workflow";
|
|
533
|
+
|
|
534
|
+
const { frontmatter, body } = parseSkillFile(rawMarkdown);
|
|
535
|
+
// frontmatter: SkillMetadata, body: instruction text
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
#### Passing Skills to a Session
|
|
539
|
+
|
|
540
|
+
Pass loaded skills to `createSession`. Zeitlich automatically registers a `ReadSkill` tool whose description lists all available skills — the agent discovers them through the tool definition and loads instructions on demand:
|
|
448
541
|
|
|
449
542
|
```typescript
|
|
450
543
|
import { createSession } from "zeitlich/workflow";
|
|
451
544
|
|
|
452
|
-
// First run — threadId defaults to getShortId() if omitted
|
|
453
545
|
const session = await createSession({
|
|
454
|
-
// threadId is optional, auto-generated if not provided
|
|
455
546
|
// ... other config
|
|
547
|
+
skills, // Skill[] — loaded via FileSystemSkillProvider or manually
|
|
456
548
|
});
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
The `ReadSkill` tool accepts a `skill_name` parameter (constrained to an enum of available names) and returns the full instruction body. The handler runs directly in the workflow — no activity needed.
|
|
457
552
|
|
|
458
|
-
|
|
553
|
+
#### Building Skills Manually
|
|
554
|
+
|
|
555
|
+
For advanced use cases, you can construct the tool and handler independently:
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
import { createReadSkillTool, createReadSkillHandler } from "zeitlich/workflow";
|
|
559
|
+
|
|
560
|
+
const tool = createReadSkillTool(skills); // ToolDefinition with enum schema
|
|
561
|
+
const handler = createReadSkillHandler(skills); // Returns skill instructions
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### Thread & Sandbox Lifecycle
|
|
565
|
+
|
|
566
|
+
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.
|
|
567
|
+
|
|
568
|
+
#### Thread Initialization (`ThreadInit`)
|
|
569
|
+
|
|
570
|
+
The `thread` field on `SessionConfig` (and `WorkflowInput`) accepts one of three modes:
|
|
571
|
+
|
|
572
|
+
| Mode | Description |
|
|
573
|
+
|------|-------------|
|
|
574
|
+
| `{ mode: "new" }` | Start a fresh thread (default). Optionally pass `threadId` to choose the ID. |
|
|
575
|
+
| `{ mode: "fork", threadId }` | Copy all messages from an existing thread into a new one and continue there. The original is never mutated. |
|
|
576
|
+
| `{ mode: "continue", threadId }` | Append directly to an existing thread in-place. |
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
import { createSession } from "zeitlich/workflow";
|
|
580
|
+
|
|
581
|
+
// First run — fresh thread
|
|
582
|
+
const session = await createSession({
|
|
583
|
+
thread: { mode: "new" },
|
|
584
|
+
// ... other config
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
// Later — fork the previous conversation
|
|
459
588
|
const resumedSession = await createSession({
|
|
460
|
-
|
|
461
|
-
continueThread: true, // fork into a new thread with the old messages
|
|
589
|
+
thread: { mode: "fork", threadId: savedThreadId },
|
|
462
590
|
// ... other config
|
|
463
591
|
});
|
|
464
|
-
```
|
|
465
592
|
|
|
466
|
-
|
|
593
|
+
// Or append directly to the existing thread
|
|
594
|
+
const continuedSession = await createSession({
|
|
595
|
+
thread: { mode: "continue", threadId: savedThreadId },
|
|
596
|
+
// ... other config
|
|
597
|
+
});
|
|
598
|
+
```
|
|
467
599
|
|
|
468
600
|
`getShortId()` produces compact, workflow-deterministic IDs (~12 base-62 chars) that are more token-efficient than UUIDs.
|
|
469
601
|
|
|
470
|
-
####
|
|
602
|
+
#### Sandbox Initialization (`SandboxInit`)
|
|
603
|
+
|
|
604
|
+
The `sandbox` field controls how a sandbox is created or reused:
|
|
605
|
+
|
|
606
|
+
| Mode | Description |
|
|
607
|
+
|------|-------------|
|
|
608
|
+
| `{ mode: "new" }` | Create a fresh sandbox (default when `sandboxOps` is provided). |
|
|
609
|
+
| `{ mode: "continue", sandboxId }` | Resume a previously-paused sandbox. This session takes ownership. |
|
|
610
|
+
| `{ mode: "fork", sandboxId }` | Fork from an existing sandbox. A new sandbox is created and owned by this session. |
|
|
611
|
+
| `{ mode: "inherit", sandboxId }` | Use a sandbox owned by someone else (e.g. a parent agent). Shutdown policy is ignored. |
|
|
612
|
+
|
|
613
|
+
#### Sandbox Shutdown (`SandboxShutdown`)
|
|
614
|
+
|
|
615
|
+
The `sandboxShutdown` field controls what happens to the sandbox when the session exits:
|
|
471
616
|
|
|
472
|
-
|
|
617
|
+
| Value | Description |
|
|
618
|
+
|-------|-------------|
|
|
619
|
+
| `"destroy"` | Tear down the sandbox entirely (default). |
|
|
620
|
+
| `"pause"` | Pause the sandbox so it can be resumed later. |
|
|
621
|
+
| `"keep"` | Leave the sandbox running (no-op on exit). |
|
|
622
|
+
|
|
623
|
+
Subagents also support `"pause-until-parent-close"` — pause on exit, then wait for the parent workflow to signal when to destroy it.
|
|
624
|
+
|
|
625
|
+
#### Subagent Thread & Sandbox Config
|
|
626
|
+
|
|
627
|
+
Subagents configure thread and sandbox strategies via `defineSubagent`:
|
|
473
628
|
|
|
474
629
|
```typescript
|
|
475
|
-
import {
|
|
630
|
+
import { defineSubagent } from "zeitlich/workflow";
|
|
631
|
+
import { researcherWorkflow } from "./researcher.workflow";
|
|
632
|
+
|
|
633
|
+
// Fresh thread each time, no sandbox (defaults)
|
|
634
|
+
export const researcherSubagent = defineSubagent(researcherWorkflow);
|
|
476
635
|
|
|
636
|
+
// Allow the parent to continue a previous conversation via fork
|
|
637
|
+
export const researcherSubagent = defineSubagent(researcherWorkflow, {
|
|
638
|
+
thread: "fork",
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
// Own sandbox with pause-on-exit
|
|
642
|
+
export const researcherSubagent = defineSubagent(researcherWorkflow, {
|
|
643
|
+
thread: "fork",
|
|
644
|
+
sandbox: { source: "own", shutdown: "pause" },
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
// Inherit the parent's sandbox
|
|
648
|
+
export const researcherSubagent = defineSubagent(researcherWorkflow, {
|
|
649
|
+
sandbox: "inherit",
|
|
650
|
+
});
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
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.
|
|
654
|
+
|
|
655
|
+
The `sandbox` field accepts `"none"` (default), `"inherit"`, `"own"`, or `{ source: "own", shutdown }` for explicit shutdown policy.
|
|
656
|
+
|
|
657
|
+
The subagent workflow receives lifecycle fields via `sessionInput`:
|
|
658
|
+
|
|
659
|
+
```typescript
|
|
477
660
|
export const researcherWorkflow = defineSubagentWorkflow(
|
|
478
661
|
{
|
|
479
662
|
name: "Researcher",
|
|
@@ -481,27 +664,17 @@ export const researcherWorkflow = defineSubagentWorkflow(
|
|
|
481
664
|
},
|
|
482
665
|
async (prompt, sessionInput) => {
|
|
483
666
|
const session = await createSession({
|
|
484
|
-
...sessionInput, //
|
|
667
|
+
...sessionInput, // spreads agentName, thread, sandbox, sandboxShutdown
|
|
668
|
+
threadOps: proxyLangChainThreadOps(),
|
|
485
669
|
// ... other config
|
|
486
670
|
});
|
|
487
671
|
|
|
488
672
|
const { threadId, finalMessage } = await session.runSession({ stateManager });
|
|
489
|
-
return {
|
|
490
|
-
toolResponse: finalMessage ? extractText(finalMessage) : "No response",
|
|
491
|
-
data: null,
|
|
492
|
-
threadId,
|
|
493
|
-
};
|
|
673
|
+
return { toolResponse: extractText(finalMessage), data: null, threadId };
|
|
494
674
|
},
|
|
495
675
|
);
|
|
496
|
-
|
|
497
|
-
// Enable thread continuation in the parent registration
|
|
498
|
-
export const researcherSubagent = defineSubagent(researcherWorkflow, {
|
|
499
|
-
allowThreadContinuation: true,
|
|
500
|
-
});
|
|
501
676
|
```
|
|
502
677
|
|
|
503
|
-
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.
|
|
504
|
-
|
|
505
678
|
### Filesystem Utilities
|
|
506
679
|
|
|
507
680
|
Built-in support for file operations with in-memory or custom filesystem providers (e.g. from [`just-bash`](https://github.com/nicholasgasior/just-bash)).
|
|
@@ -547,7 +720,8 @@ import {
|
|
|
547
720
|
const sandboxManager = new SandboxManager(provider);
|
|
548
721
|
|
|
549
722
|
export const createActivities = ({ redis, client }) => ({
|
|
550
|
-
|
|
723
|
+
// scope auto-prepends the provider id (e.g. "inMemory", "virtual")
|
|
724
|
+
...sandboxManager.createActivities("MyAgentWorkflow"),
|
|
551
725
|
globHandlerActivity: withSandbox(sandboxManager, globHandler),
|
|
552
726
|
editHandlerActivity: withSandbox(sandboxManager, editHandler),
|
|
553
727
|
bashHandlerActivity: withSandbox(sandboxManager, bashHandler),
|
|
@@ -617,6 +791,7 @@ Zeitlich provides ready-to-use tool definitions and handlers for common agent op
|
|
|
617
791
|
| `Grep` | Search file contents with regex patterns |
|
|
618
792
|
| `Bash` | Execute shell commands |
|
|
619
793
|
| `AskUserQuestion` | Ask the user questions during execution with structured options |
|
|
794
|
+
| `ReadSkill` | Load skill instructions on demand (see [Skills](#skills)) |
|
|
620
795
|
| `Task` | Launch subagents as child workflows (see [Subagents](#subagents)) |
|
|
621
796
|
|
|
622
797
|
```typescript
|
|
@@ -676,7 +851,10 @@ Safe for use in Temporal workflow files:
|
|
|
676
851
|
| `getShortId` | Generate a compact, workflow-deterministic identifier (base-62, 12 chars) |
|
|
677
852
|
| Tool definitions | `askUserQuestionTool`, `globTool`, `grepTool`, `readFileTool`, `writeFileTool`, `editTool`, `bashTool` |
|
|
678
853
|
| Task tools | `taskCreateTool`, `taskGetTool`, `taskListTool`, `taskUpdateTool` for workflow task management |
|
|
679
|
-
|
|
|
854
|
+
| Skill utilities | `parseSkillFile`, `createReadSkillTool`, `createReadSkillHandler` |
|
|
855
|
+
| `defineWorkflow` | Wraps a main workflow function, translating `WorkflowInput` into session-compatible fields |
|
|
856
|
+
| Lifecycle types | `ThreadInit`, `SandboxInit`, `SandboxShutdown`, `SubagentSandboxShutdown`, `SubagentSandboxConfig` |
|
|
857
|
+
| Types | `Skill`, `SkillMetadata`, `SkillProvider`, `SubagentDefinition`, `SubagentConfig`, `ToolDefinition`, `ToolWithHandler`, `RouterContext`, `SessionConfig`, `WorkflowConfig`, `WorkflowInput`, etc. |
|
|
680
858
|
|
|
681
859
|
### Activity Entry Point (`zeitlich`)
|
|
682
860
|
|
|
@@ -689,19 +867,29 @@ Framework-agnostic utilities for activities, worker setup, and Node.js code:
|
|
|
689
867
|
| `createThreadManager` | Generic Redis-backed thread manager factory |
|
|
690
868
|
| `toTree` | Generate file tree string from an `IFileSystem` instance |
|
|
691
869
|
| `withSandbox` | Wraps a handler to auto-resolve sandbox from context (pairs with `withAutoAppend`) |
|
|
870
|
+
| `FileSystemSkillProvider` | Load skills from a directory following the agentskills.io layout |
|
|
692
871
|
| Tool handlers | `bashHandler`, `editHandler`, `globHandler`, `readFileHandler`, `writeFileHandler`, `createAskUserQuestionHandler` |
|
|
693
872
|
|
|
694
|
-
###
|
|
873
|
+
### Thread Adapter Entry Points
|
|
695
874
|
|
|
696
|
-
LangChain
|
|
875
|
+
**LangChain** (`zeitlich/adapters/thread/langchain`):
|
|
697
876
|
|
|
698
877
|
| Export | Description |
|
|
699
878
|
| ----------------------------------- | ---------------------------------------------------------------------- |
|
|
700
|
-
| `createLangChainAdapter` | Unified adapter returning `
|
|
879
|
+
| `createLangChainAdapter` | Unified adapter returning `createActivities`, `invoker`, `createModelInvoker` |
|
|
701
880
|
| `createLangChainModelInvoker` | Factory that returns a `ModelInvoker` backed by a LangChain chat model |
|
|
702
881
|
| `invokeLangChainModel` | One-shot model invocation convenience function |
|
|
703
882
|
| `createLangChainThreadManager` | Thread manager with LangChain `StoredMessage` helpers |
|
|
704
883
|
|
|
884
|
+
**Google GenAI** (`zeitlich/adapters/thread/google-genai`):
|
|
885
|
+
|
|
886
|
+
| Export | Description |
|
|
887
|
+
| ----------------------------------- | ---------------------------------------------------------------------- |
|
|
888
|
+
| `createGoogleGenAIAdapter` | Unified adapter returning `createActivities`, `invoker`, `createModelInvoker` |
|
|
889
|
+
| `createGoogleGenAIModelInvoker` | Factory that returns a `ModelInvoker` backed by the `@google/genai` SDK |
|
|
890
|
+
| `invokeGoogleGenAIModel` | One-shot model invocation convenience function |
|
|
891
|
+
| `createGoogleGenAIThreadManager` | Thread manager with Google GenAI `Content` helpers |
|
|
892
|
+
|
|
705
893
|
### Types
|
|
706
894
|
|
|
707
895
|
| Export | Description |
|
|
@@ -716,6 +904,11 @@ LangChain-specific implementations:
|
|
|
716
904
|
| `RouterContext` | Base context every tool handler receives (`threadId`, `toolCallId`, `toolName`, `sandboxId?`) |
|
|
717
905
|
| `Hooks` | Combined session lifecycle + tool execution hooks |
|
|
718
906
|
| `ToolRouterHooks` | Narrowed hook interface for tool execution only (pre/post/failure) |
|
|
907
|
+
| `ThreadInit` | Thread initialization strategy: `"new"`, `"continue"`, or `"fork"` |
|
|
908
|
+
| `SandboxInit` | Sandbox initialization strategy: `"new"`, `"continue"`, `"fork"`, or `"inherit"` |
|
|
909
|
+
| `SandboxShutdown` | Sandbox exit policy: `"destroy" \| "pause" \| "keep"` |
|
|
910
|
+
| `SubagentSandboxShutdown` | Extended shutdown with `"pause-until-parent-close"` |
|
|
911
|
+
| `SubagentSandboxConfig` | Subagent sandbox strategy: `"none" \| "inherit" \| "own" \| { source, shutdown }` |
|
|
719
912
|
| `SubagentDefinition` | Callable subagent workflow with embedded metadata (from `defineSubagentWorkflow`) |
|
|
720
913
|
| `SubagentConfig` | Resolved subagent configuration consumed by `createSession` |
|
|
721
914
|
| `AgentState` | Generic agent state type |
|
|
@@ -743,9 +936,14 @@ LangChain-specific implementations:
|
|
|
743
936
|
│ └──────────────────────────────────────────────────────────┘ │
|
|
744
937
|
│ │ │
|
|
745
938
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
746
|
-
│ │
|
|
747
|
-
│ │ •
|
|
748
|
-
│ │ •
|
|
939
|
+
│ │ Thread Adapter (zeitlich/adapters/thread/*) │ │
|
|
940
|
+
│ │ • LangChain, Google GenAI, or custom │ │
|
|
941
|
+
│ │ • Thread ops (message storage) + model invoker │ │
|
|
942
|
+
│ └──────────────────────────────────────────────────────────┘ │
|
|
943
|
+
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
944
|
+
│ │ Sandbox Adapter (zeitlich/adapters/sandbox/*) │ │
|
|
945
|
+
│ │ • In-memory, Virtual, Daytona, E2B, or custom │ │
|
|
946
|
+
│ │ • Filesystem ops for agent tools │ │
|
|
749
947
|
│ └──────────────────────────────────────────────────────────┘ │
|
|
750
948
|
└─────────────────────────────────────────────────────────────────┘
|
|
751
949
|
│
|
|
@@ -47,7 +47,7 @@ var DaytonaSandboxFileSystem = class {
|
|
|
47
47
|
await this.sandbox.fs.uploadFiles(
|
|
48
48
|
files.map((f) => ({
|
|
49
49
|
source: Buffer.from(f.content),
|
|
50
|
-
destination: f.path
|
|
50
|
+
destination: this.normalisePath(f.path)
|
|
51
51
|
}))
|
|
52
52
|
);
|
|
53
53
|
}
|
|
@@ -231,6 +231,12 @@ var DaytonaSandboxProvider = class {
|
|
|
231
231
|
} catch {
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
|
+
async pause(_sandboxId, _ttlSeconds) {
|
|
235
|
+
throw new SandboxNotSupportedError("pause");
|
|
236
|
+
}
|
|
237
|
+
async fork(_sandboxId) {
|
|
238
|
+
throw new Error("Not implemented");
|
|
239
|
+
}
|
|
234
240
|
async snapshot(_sandboxId) {
|
|
235
241
|
throw new SandboxNotSupportedError(
|
|
236
242
|
"snapshot (use Daytona's native snapshot API directly)"
|