zeitlich 0.2.9 → 0.2.12
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 +313 -126
- package/dist/adapters/langchain/index.cjs +270 -0
- package/dist/adapters/langchain/index.cjs.map +1 -0
- package/dist/adapters/langchain/index.d.cts +132 -0
- package/dist/adapters/langchain/index.d.ts +132 -0
- package/dist/adapters/langchain/index.js +265 -0
- package/dist/adapters/langchain/index.js.map +1 -0
- package/dist/index.cjs +406 -301
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +88 -45
- package/dist/index.d.ts +88 -45
- package/dist/index.js +375 -274
- package/dist/index.js.map +1 -1
- package/dist/{workflow-C2ShwjC7.d.cts → model-invoker-C5-N-5TC.d.cts} +92 -435
- package/dist/{workflow-C2ShwjC7.d.ts → model-invoker-C5-N-5TC.d.ts} +92 -435
- package/dist/thread-manager-qc0g5Rvd.d.cts +39 -0
- package/dist/thread-manager-qc0g5Rvd.d.ts +39 -0
- package/dist/workflow.cjs +294 -126
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +459 -5
- package/dist/workflow.d.ts +459 -5
- package/dist/workflow.js +266 -103
- package/dist/workflow.js.map +1 -1
- package/package.json +30 -15
- package/src/adapters/langchain/activities.ts +120 -0
- package/src/adapters/langchain/index.ts +38 -0
- package/src/adapters/langchain/model-invoker.ts +102 -0
- package/src/adapters/langchain/thread-manager.ts +142 -0
- package/src/index.ts +26 -22
- package/src/lib/fs.ts +25 -0
- package/src/lib/model-invoker.ts +14 -74
- package/src/lib/session.ts +60 -23
- package/src/lib/skills/fs-provider.ts +84 -0
- package/src/lib/skills/index.ts +3 -0
- package/src/lib/skills/parse.ts +117 -0
- package/src/lib/skills/types.ts +41 -0
- package/src/lib/state-manager.ts +65 -31
- package/src/lib/thread-id.ts +25 -0
- package/src/lib/thread-manager.ts +63 -128
- package/src/lib/tool-router.ts +33 -23
- package/src/lib/types.ts +48 -15
- package/src/lib/workflow-helpers.ts +50 -0
- package/src/tools/ask-user-question/handler.ts +25 -1
- package/src/tools/bash/handler.ts +13 -0
- package/src/tools/read-skill/handler.ts +31 -0
- package/src/tools/read-skill/tool.ts +47 -0
- package/src/tools/subagent/handler.ts +37 -9
- package/src/tools/subagent/tool.ts +38 -34
- package/src/tools/task-create/tool.ts +1 -1
- package/src/workflow.ts +39 -11
- package/tsup.config.ts +1 -0
- package/src/activities.ts +0 -91
- package/src/plugin.ts +0 -28
package/README.md
CHANGED
|
@@ -29,39 +29,44 @@ Temporal solves these problems for workflows. Zeitlich brings these guarantees t
|
|
|
29
29
|
- **Lifecycle hooks** — Pre/post tool execution, session start/end
|
|
30
30
|
- **Subagent support** — Spawn child agents as Temporal child workflows
|
|
31
31
|
- **Filesystem utilities** — In-memory or custom providers for file operations
|
|
32
|
-
- **Model flexibility** —
|
|
32
|
+
- **Model flexibility** — Framework-agnostic model invocation with adapters for LangChain (and more coming)
|
|
33
33
|
|
|
34
34
|
## LLM Integration
|
|
35
35
|
|
|
36
|
-
Zeitlich
|
|
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
|
+
|
|
38
|
+
### LangChain Adapter (`zeitlich/adapters/langchain`)
|
|
39
|
+
|
|
40
|
+
The built-in LangChain adapter gives you:
|
|
37
41
|
|
|
38
42
|
- **Provider flexibility** — Use Anthropic, OpenAI, Google, Azure, AWS Bedrock, or any LangChain-supported provider
|
|
39
43
|
- **Consistent interface** — Same tool calling and message format regardless of provider
|
|
40
44
|
- **Easy model swapping** — Change models without rewriting agent logic
|
|
41
|
-
- **Soon** — Support native provider SDKs directly
|
|
42
45
|
|
|
43
46
|
```typescript
|
|
44
47
|
import { ChatAnthropic } from "@langchain/anthropic";
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
48
|
+
import { createLangChainAdapter } from "zeitlich/adapters/langchain";
|
|
49
|
+
import { createRunAgentActivity } from "zeitlich";
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
const adapter = createLangChainAdapter({
|
|
52
|
+
redis,
|
|
53
|
+
model: new ChatAnthropic({ model: "claude-sonnet-4-20250514" }),
|
|
54
|
+
});
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
export function createActivities(client: WorkflowClient) {
|
|
57
|
+
return {
|
|
58
|
+
...adapter.threadOps,
|
|
59
|
+
runAgent: createRunAgentActivity(client, adapter.invoker),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
57
62
|
```
|
|
58
63
|
|
|
59
64
|
Install the LangChain package for your chosen provider:
|
|
60
65
|
|
|
61
66
|
```bash
|
|
62
|
-
npm install @langchain/anthropic # Anthropic
|
|
63
|
-
npm install @langchain/openai # OpenAI
|
|
64
|
-
npm install @langchain/google-genai # Google
|
|
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
|
|
65
70
|
```
|
|
66
71
|
|
|
67
72
|
## Installation
|
|
@@ -73,6 +78,7 @@ npm install zeitlich ioredis
|
|
|
73
78
|
**Peer dependencies:**
|
|
74
79
|
|
|
75
80
|
- `ioredis` >= 5.0.0
|
|
81
|
+
- `@langchain/core` >= 1.0.0 (optional — only needed when using `zeitlich/adapters/langchain`)
|
|
76
82
|
|
|
77
83
|
**Required infrastructure:**
|
|
78
84
|
|
|
@@ -81,21 +87,37 @@ npm install zeitlich ioredis
|
|
|
81
87
|
|
|
82
88
|
## Import Paths
|
|
83
89
|
|
|
84
|
-
Zeitlich provides
|
|
90
|
+
Zeitlich provides three entry points:
|
|
85
91
|
|
|
86
92
|
```typescript
|
|
87
|
-
// In workflow files
|
|
93
|
+
// In workflow files — no external dependencies (Redis, LangChain, etc.)
|
|
88
94
|
import {
|
|
89
95
|
createSession,
|
|
90
96
|
createAgentStateManager,
|
|
91
97
|
askUserQuestionTool,
|
|
98
|
+
bashTool,
|
|
99
|
+
defineTool,
|
|
100
|
+
type SubagentWorkflow,
|
|
101
|
+
type ModelInvoker,
|
|
92
102
|
} from "zeitlich/workflow";
|
|
93
103
|
|
|
94
|
-
// In activity files and worker setup -
|
|
95
|
-
import {
|
|
104
|
+
// In activity files and worker setup — framework-agnostic core
|
|
105
|
+
import {
|
|
106
|
+
createRunAgentActivity,
|
|
107
|
+
createBashHandler,
|
|
108
|
+
createAskUserQuestionHandler,
|
|
109
|
+
toTree,
|
|
110
|
+
} from "zeitlich";
|
|
111
|
+
|
|
112
|
+
// LangChain adapter — unified adapter for LLM invocation and thread management
|
|
113
|
+
import { createLangChainAdapter } from "zeitlich/adapters/langchain";
|
|
96
114
|
```
|
|
97
115
|
|
|
98
|
-
**Why
|
|
116
|
+
**Why three entry points?**
|
|
117
|
+
|
|
118
|
+
- `zeitlich/workflow` — Pure TypeScript, safe for Temporal's V8 sandbox
|
|
119
|
+
- `zeitlich` — Activity-side utilities (Redis, filesystem), framework-agnostic
|
|
120
|
+
- `zeitlich/adapters/langchain` — LangChain-specific adapter (model invocation + thread management)
|
|
99
121
|
|
|
100
122
|
## Examples
|
|
101
123
|
|
|
@@ -118,54 +140,28 @@ export const searchTool: ToolDefinition<"Search", typeof searchSchema> = {
|
|
|
118
140
|
};
|
|
119
141
|
```
|
|
120
142
|
|
|
121
|
-
### 2. Create
|
|
143
|
+
### 2. Create the Workflow
|
|
122
144
|
|
|
123
|
-
|
|
124
|
-
import type Redis from "ioredis";
|
|
125
|
-
import type { WorkflowClient } from "@temporalio/client";
|
|
126
|
-
import { ChatAnthropic } from "@langchain/anthropic";
|
|
127
|
-
import { invokeModel, type InvokeModelConfig } from "zeitlich";
|
|
128
|
-
|
|
129
|
-
export const createActivities = ({
|
|
130
|
-
redis,
|
|
131
|
-
client,
|
|
132
|
-
}: {
|
|
133
|
-
redis: Redis;
|
|
134
|
-
client: WorkflowClient;
|
|
135
|
-
}) => {
|
|
136
|
-
const model = new ChatAnthropic({
|
|
137
|
-
model: "claude-sonnet-4-20250514",
|
|
138
|
-
maxTokens: 4096,
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
return {
|
|
142
|
-
runAgent: (config: InvokeModelConfig) =>
|
|
143
|
-
invokeModel({ config, model, redis, client }),
|
|
144
|
-
|
|
145
|
-
handleSearchResult: async ({ args }) => {
|
|
146
|
-
const results = await performSearch(args.query);
|
|
147
|
-
return { result: { results } };
|
|
148
|
-
},
|
|
149
|
-
};
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
export type MyActivities = ReturnType<typeof createActivities>;
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
### 3. Create the Workflow
|
|
145
|
+
The system prompt is set via `createAgentStateManager`'s `initialState`, and agent config fields (`agentName`, `maxTurns`, etc.) are spread into `createSession`.
|
|
156
146
|
|
|
157
147
|
```typescript
|
|
158
148
|
import { proxyActivities, workflowInfo } from "@temporalio/workflow";
|
|
159
149
|
import {
|
|
160
150
|
createAgentStateManager,
|
|
161
151
|
createSession,
|
|
152
|
+
askUserQuestionTool,
|
|
153
|
+
bashTool,
|
|
162
154
|
defineTool,
|
|
163
|
-
proxyDefaultThreadOps,
|
|
164
155
|
} from "zeitlich/workflow";
|
|
165
156
|
import { searchTool } from "./tools";
|
|
166
157
|
import type { MyActivities } from "./activities";
|
|
167
158
|
|
|
168
|
-
const {
|
|
159
|
+
const {
|
|
160
|
+
runAgentActivity,
|
|
161
|
+
searchHandlerActivity,
|
|
162
|
+
bashHandlerActivity,
|
|
163
|
+
askUserQuestionHandlerActivity,
|
|
164
|
+
} = proxyActivities<MyActivities>({
|
|
169
165
|
startToCloseTimeout: "30m",
|
|
170
166
|
retry: {
|
|
171
167
|
maximumAttempts: 6,
|
|
@@ -179,51 +175,109 @@ const { runAgent, handleSearchResult } = proxyActivities<MyActivities>({
|
|
|
179
175
|
export async function myAgentWorkflow({ prompt }: { prompt: string }) {
|
|
180
176
|
const { runId } = workflowInfo();
|
|
181
177
|
|
|
182
|
-
const stateManager = createAgentStateManager({
|
|
178
|
+
const stateManager = createAgentStateManager({
|
|
179
|
+
initialState: {
|
|
180
|
+
systemPrompt: "You are a helpful assistant.",
|
|
181
|
+
},
|
|
182
|
+
agentName: "my-agent",
|
|
183
|
+
});
|
|
183
184
|
|
|
184
185
|
const session = await createSession({
|
|
185
|
-
threadId: runId,
|
|
186
186
|
agentName: "my-agent",
|
|
187
187
|
maxTurns: 20,
|
|
188
|
-
|
|
189
|
-
runAgent,
|
|
190
|
-
threadOps: proxyDefaultThreadOps(),
|
|
188
|
+
threadId: runId,
|
|
189
|
+
runAgent: runAgentActivity,
|
|
191
190
|
buildContextMessage: () => [{ type: "text", text: prompt }],
|
|
192
191
|
tools: {
|
|
193
192
|
Search: defineTool({
|
|
194
193
|
...searchTool,
|
|
195
|
-
handler:
|
|
194
|
+
handler: searchHandlerActivity,
|
|
195
|
+
}),
|
|
196
|
+
AskUserQuestion: defineTool({
|
|
197
|
+
...askUserQuestionTool,
|
|
198
|
+
handler: askUserQuestionHandlerActivity,
|
|
199
|
+
hooks: {
|
|
200
|
+
onPostToolUse: () => {
|
|
201
|
+
stateManager.waitForInput();
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
}),
|
|
205
|
+
Bash: defineTool({
|
|
206
|
+
...bashTool,
|
|
207
|
+
handler: bashHandlerActivity,
|
|
196
208
|
}),
|
|
197
209
|
},
|
|
198
210
|
});
|
|
199
211
|
|
|
200
|
-
await session.runSession({ stateManager });
|
|
201
|
-
return
|
|
212
|
+
const result = await session.runSession({ stateManager });
|
|
213
|
+
return result;
|
|
202
214
|
}
|
|
203
215
|
```
|
|
204
216
|
|
|
217
|
+
### 3. Create Activities
|
|
218
|
+
|
|
219
|
+
Activities are factory functions that receive infrastructure dependencies (`redis`, `client`). Each returns an object of activity functions registered with the Temporal worker.
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
import type Redis from "ioredis";
|
|
223
|
+
import type { WorkflowClient } from "@temporalio/client";
|
|
224
|
+
import { ChatAnthropic } from "@langchain/anthropic";
|
|
225
|
+
import {
|
|
226
|
+
createBashHandler,
|
|
227
|
+
createAskUserQuestionHandler,
|
|
228
|
+
createRunAgentActivity,
|
|
229
|
+
} from "zeitlich";
|
|
230
|
+
import { createLangChainAdapter } from "zeitlich/adapters/langchain";
|
|
231
|
+
|
|
232
|
+
export const createActivities = ({
|
|
233
|
+
redis,
|
|
234
|
+
client,
|
|
235
|
+
}: {
|
|
236
|
+
redis: Redis;
|
|
237
|
+
client: WorkflowClient;
|
|
238
|
+
}) => {
|
|
239
|
+
const { threadOps, invoker } = createLangChainAdapter({
|
|
240
|
+
redis,
|
|
241
|
+
model: new ChatAnthropic({
|
|
242
|
+
model: "claude-sonnet-4-20250514",
|
|
243
|
+
maxTokens: 4096,
|
|
244
|
+
}),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
...threadOps,
|
|
249
|
+
runAgentActivity: createRunAgentActivity(client, invoker),
|
|
250
|
+
searchHandlerActivity: async (args: { query: string }) => ({
|
|
251
|
+
toolResponse: JSON.stringify(await performSearch(args.query)),
|
|
252
|
+
data: null,
|
|
253
|
+
}),
|
|
254
|
+
bashHandlerActivity: createBashHandler({ fs: inMemoryFileSystem }),
|
|
255
|
+
askUserQuestionHandlerActivity: createAskUserQuestionHandler(),
|
|
256
|
+
};
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
export type MyActivities = ReturnType<typeof createActivities>;
|
|
260
|
+
```
|
|
261
|
+
|
|
205
262
|
### 4. Set Up the Worker
|
|
206
263
|
|
|
207
264
|
```typescript
|
|
208
265
|
import { Worker, NativeConnection } from "@temporalio/worker";
|
|
209
|
-
import { Client } from "@temporalio/client";
|
|
210
|
-
import { ZeitlichPlugin } from "zeitlich";
|
|
211
266
|
import Redis from "ioredis";
|
|
267
|
+
import { fileURLToPath } from "node:url";
|
|
212
268
|
import { createActivities } from "./activities";
|
|
213
269
|
|
|
214
270
|
async function run() {
|
|
215
271
|
const connection = await NativeConnection.connect({
|
|
216
272
|
address: "localhost:7233",
|
|
217
273
|
});
|
|
218
|
-
const client = new Client({ connection });
|
|
219
274
|
const redis = new Redis({ host: "localhost", port: 6379 });
|
|
220
275
|
|
|
221
276
|
const worker = await Worker.create({
|
|
222
|
-
plugins: [new ZeitlichPlugin({ redis })],
|
|
223
277
|
connection,
|
|
224
278
|
taskQueue: "my-agent",
|
|
225
|
-
workflowsPath:
|
|
226
|
-
activities: createActivities({ redis, client
|
|
279
|
+
workflowsPath: fileURLToPath(new URL("./workflows.ts", import.meta.url)),
|
|
280
|
+
activities: createActivities({ redis, client }),
|
|
227
281
|
});
|
|
228
282
|
|
|
229
283
|
await worker.run();
|
|
@@ -234,13 +288,17 @@ async function run() {
|
|
|
234
288
|
|
|
235
289
|
### Agent State Manager
|
|
236
290
|
|
|
237
|
-
Manages workflow state with automatic versioning and status tracking:
|
|
291
|
+
Manages workflow state with automatic versioning and status tracking. Requires `agentName` to register Temporal query/update handlers, and accepts an optional `initialState` for system prompt and custom fields:
|
|
238
292
|
|
|
239
293
|
```typescript
|
|
240
294
|
import { createAgentStateManager } from "zeitlich/workflow";
|
|
241
295
|
|
|
242
296
|
const stateManager = createAgentStateManager({
|
|
243
|
-
|
|
297
|
+
initialState: {
|
|
298
|
+
systemPrompt: "You are a helpful assistant.",
|
|
299
|
+
customField: "value",
|
|
300
|
+
},
|
|
301
|
+
agentName: "my-agent",
|
|
244
302
|
});
|
|
245
303
|
|
|
246
304
|
// State operations
|
|
@@ -310,63 +368,174 @@ const session = await createSession({
|
|
|
310
368
|
|
|
311
369
|
### Subagents
|
|
312
370
|
|
|
313
|
-
Spawn child agents as Temporal child workflows
|
|
371
|
+
Spawn child agents as Temporal child workflows. Each subagent is a workflow typed with `SubagentWorkflow` that returns `{ toolResponse, data }`:
|
|
314
372
|
|
|
315
373
|
```typescript
|
|
316
|
-
|
|
374
|
+
import { proxyActivities, workflowInfo } from "@temporalio/workflow";
|
|
375
|
+
import {
|
|
376
|
+
createAgentStateManager,
|
|
377
|
+
createSession,
|
|
378
|
+
type SubagentWorkflow,
|
|
379
|
+
} from "zeitlich/workflow";
|
|
380
|
+
import { agentConfig } from "./config";
|
|
381
|
+
import type { createResearcherActivities } from "./activities";
|
|
382
|
+
|
|
383
|
+
const { runResearcherActivity } = proxyActivities<
|
|
384
|
+
ReturnType<typeof createResearcherActivities>
|
|
385
|
+
>({ startToCloseTimeout: "30m", heartbeatTimeout: "5m" });
|
|
386
|
+
|
|
387
|
+
// Subagent workflow typed as SubagentWorkflow
|
|
388
|
+
export const researcherSubagentWorkflow: SubagentWorkflow = async ({
|
|
389
|
+
prompt,
|
|
390
|
+
}) => {
|
|
391
|
+
const { runId } = workflowInfo();
|
|
392
|
+
|
|
393
|
+
const stateManager = createAgentStateManager({
|
|
394
|
+
initialState: { systemPrompt: agentConfig.systemPrompt },
|
|
395
|
+
agentName: agentConfig.agentName,
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const session = await createSession({
|
|
399
|
+
...agentConfig,
|
|
400
|
+
threadId: runId,
|
|
401
|
+
runAgent: runResearcherActivity,
|
|
402
|
+
buildContextMessage: () => [{ type: "text", text: prompt }],
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const { finalMessage } = await session.runSession({ stateManager });
|
|
406
|
+
return {
|
|
407
|
+
toolResponse: finalMessage ? extractText(finalMessage) : "No response",
|
|
408
|
+
data: null,
|
|
409
|
+
};
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
// Register the subagent for the parent workflow
|
|
317
413
|
export const researcherSubagent = {
|
|
318
|
-
|
|
319
|
-
description:
|
|
320
|
-
workflow:
|
|
414
|
+
agentName: agentConfig.agentName,
|
|
415
|
+
description: agentConfig.description,
|
|
416
|
+
workflow: researcherSubagentWorkflow,
|
|
321
417
|
};
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
In the parent workflow, pass subagents to `createSession`:
|
|
322
421
|
|
|
323
|
-
|
|
422
|
+
```typescript
|
|
324
423
|
const session = await createSession({
|
|
325
424
|
// ... other config
|
|
326
425
|
subagents: [researcherSubagent, codeReviewerSubagent],
|
|
327
426
|
});
|
|
328
427
|
```
|
|
329
428
|
|
|
330
|
-
The `
|
|
429
|
+
The `Subagent` tool is automatically added when subagents are configured, allowing the LLM to spawn child workflows.
|
|
331
430
|
|
|
332
|
-
###
|
|
431
|
+
### Thread Continuation
|
|
333
432
|
|
|
334
|
-
|
|
433
|
+
By default, each session initializes a fresh thread. To continue an existing thread (e.g., resuming a conversation after a workflow completes), pass `continueThread: true` along with the previous `threadId`:
|
|
335
434
|
|
|
336
435
|
```typescript
|
|
337
|
-
|
|
338
|
-
export const createActivities = () => ({
|
|
339
|
-
generateFileTree: async (): Promise<string> => {
|
|
340
|
-
// Return a formatted file tree string
|
|
341
|
-
return toTree("/path/to/workspace");
|
|
342
|
-
},
|
|
343
|
-
});
|
|
436
|
+
import { createSession } from "zeitlich/workflow";
|
|
344
437
|
|
|
345
|
-
//
|
|
438
|
+
// First run — threadId defaults to getShortId() if omitted
|
|
346
439
|
const session = await createSession({
|
|
440
|
+
// threadId is optional, auto-generated if not provided
|
|
441
|
+
// ... other config
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Later — new workflow picks up the same thread
|
|
445
|
+
const resumedSession = await createSession({
|
|
446
|
+
threadId: savedThreadId, // pass the ID from the first run
|
|
447
|
+
continueThread: true, // skip thread init + system prompt
|
|
347
448
|
// ... other config
|
|
348
|
-
buildFileTree: generateFileTree, // Called at session start
|
|
349
449
|
});
|
|
350
450
|
```
|
|
351
451
|
|
|
352
|
-
|
|
452
|
+
`getShortId()` produces compact, workflow-deterministic IDs (~12 base-62 chars) that are more token-efficient than UUIDs.
|
|
453
|
+
|
|
454
|
+
#### Subagent Thread Continuation
|
|
455
|
+
|
|
456
|
+
Subagents can opt in to thread continuation via `allowThreadContinuation`. When enabled, the parent agent can pass a `threadId` to resume a previous subagent conversation:
|
|
353
457
|
|
|
354
458
|
```typescript
|
|
355
|
-
import {
|
|
459
|
+
import { getShortId, type SubagentWorkflow } from "zeitlich/workflow";
|
|
356
460
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
})
|
|
461
|
+
// Subagent workflow that supports continuation
|
|
462
|
+
export const researcherWorkflow: SubagentWorkflow = async ({
|
|
463
|
+
prompt,
|
|
464
|
+
threadId,
|
|
465
|
+
}) => {
|
|
466
|
+
const effectiveThreadId = threadId ?? getShortId();
|
|
467
|
+
|
|
468
|
+
const session = await createSession({
|
|
469
|
+
threadId: effectiveThreadId,
|
|
470
|
+
continueThread: !!threadId,
|
|
471
|
+
// ... other config
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
const { finalMessage } = await session.runSession({ stateManager });
|
|
475
|
+
return {
|
|
476
|
+
toolResponse: finalMessage ? extractText(finalMessage) : "No response",
|
|
477
|
+
data: null,
|
|
478
|
+
threadId: effectiveThreadId,
|
|
479
|
+
};
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
// Register with allowThreadContinuation
|
|
483
|
+
export const researcherSubagent = {
|
|
484
|
+
agentName: "Researcher",
|
|
485
|
+
description: "Researches topics and gathers information",
|
|
486
|
+
workflow: researcherWorkflow,
|
|
487
|
+
allowThreadContinuation: true,
|
|
488
|
+
};
|
|
362
489
|
```
|
|
363
490
|
|
|
364
|
-
`
|
|
491
|
+
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.
|
|
492
|
+
|
|
493
|
+
### Filesystem Utilities
|
|
494
|
+
|
|
495
|
+
Built-in support for file operations with in-memory or custom filesystem providers (e.g. from [`just-bash`](https://github.com/nicholasgasior/just-bash)).
|
|
496
|
+
|
|
497
|
+
`toTree` generates a file tree string from an `IFileSystem` instance:
|
|
365
498
|
|
|
366
499
|
```typescript
|
|
367
500
|
import { toTree } from "zeitlich";
|
|
368
501
|
|
|
369
|
-
|
|
502
|
+
// In activities - generate a file tree string for agent context
|
|
503
|
+
export const createActivities = ({ redis, client }) => ({
|
|
504
|
+
generateFileTreeActivity: async () => toTree(inMemoryFileSystem),
|
|
505
|
+
// ...
|
|
506
|
+
});
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
Use the tree in `buildContextMessage` to give the agent filesystem awareness:
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
// In workflow
|
|
513
|
+
const fileTree = await generateFileTreeActivity();
|
|
514
|
+
|
|
515
|
+
const session = await createSession({
|
|
516
|
+
// ... other config
|
|
517
|
+
buildContextMessage: () => [
|
|
518
|
+
{ type: "text", text: `Files in the filesystem: ${fileTree}` },
|
|
519
|
+
{ type: "text", text: prompt },
|
|
520
|
+
],
|
|
521
|
+
});
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
For file operations, use the built-in tool handler factories. All handlers accept an `IFileSystem`:
|
|
525
|
+
|
|
526
|
+
```typescript
|
|
527
|
+
import {
|
|
528
|
+
createGlobHandler,
|
|
529
|
+
createEditHandler,
|
|
530
|
+
createBashHandler,
|
|
531
|
+
} from "zeitlich";
|
|
532
|
+
|
|
533
|
+
export const createActivities = ({ redis, client }) => ({
|
|
534
|
+
generateFileTreeActivity: async () => toTree(inMemoryFileSystem),
|
|
535
|
+
globHandlerActivity: createGlobHandler(inMemoryFileSystem),
|
|
536
|
+
editHandlerActivity: createEditHandler(inMemoryFileSystem),
|
|
537
|
+
bashHandlerActivity: createBashHandler({ fs: inMemoryFileSystem }),
|
|
538
|
+
});
|
|
370
539
|
```
|
|
371
540
|
|
|
372
541
|
### Built-in Tools
|
|
@@ -429,33 +598,50 @@ const session = await createSession({
|
|
|
429
598
|
|
|
430
599
|
Safe for use in Temporal workflow files:
|
|
431
600
|
|
|
432
|
-
| Export | Description
|
|
433
|
-
| ------------------------- |
|
|
434
|
-
| `createSession` | Creates an agent session with tools, prompts, subagents, and hooks
|
|
435
|
-
| `createAgentStateManager` | Creates a state manager for workflow state
|
|
436
|
-
| `createToolRouter` | Creates a tool router (used internally by session, or for advanced use)
|
|
437
|
-
| `
|
|
438
|
-
|
|
|
439
|
-
|
|
|
440
|
-
|
|
|
601
|
+
| Export | Description |
|
|
602
|
+
| ------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
603
|
+
| `createSession` | Creates an agent session with tools, prompts, subagents, and hooks |
|
|
604
|
+
| `createAgentStateManager` | Creates a state manager for workflow state with query/update handlers |
|
|
605
|
+
| `createToolRouter` | Creates a tool router (used internally by session, or for advanced use) |
|
|
606
|
+
| `defineTool` | Identity function for type-safe tool definition with handler and hooks |
|
|
607
|
+
| `defineSubagent` | Identity function for type-safe subagent configuration |
|
|
608
|
+
| `getShortId` | Generate a compact, workflow-deterministic identifier (base-62, 12 chars) |
|
|
609
|
+
| `createSubagentTool` | Creates the Subagent tool for spawning child workflows |
|
|
610
|
+
| Tool definitions | `askUserQuestionTool`, `globTool`, `grepTool`, `readFileTool`, `writeFileTool`, `editTool`, `bashTool` |
|
|
611
|
+
| Task tools | `taskCreateTool`, `taskGetTool`, `taskListTool`, `taskUpdateTool` for workflow task management |
|
|
612
|
+
| Types | `SubagentWorkflow`, `ToolDefinition`, `ToolWithHandler`, `AgentConfig`, `SessionConfig`, etc. |
|
|
441
613
|
|
|
442
614
|
### Activity Entry Point (`zeitlich`)
|
|
443
615
|
|
|
444
|
-
|
|
616
|
+
Framework-agnostic utilities for activities, worker setup, and Node.js code:
|
|
617
|
+
|
|
618
|
+
| Export | Description |
|
|
619
|
+
| ------------------------- | --------------------------------------------------------------------------------------------- |
|
|
620
|
+
| `createRunAgentActivity` | Wraps a `ModelInvoker` into a `RunAgentActivity` with automatic tool loading |
|
|
621
|
+
| `createThreadManager` | Generic Redis-backed thread manager factory |
|
|
622
|
+
| `toTree` | Generate file tree string from an `IFileSystem` instance |
|
|
623
|
+
| Tool handlers | `createGlobHandler`, `createEditHandler`, `createBashHandler`, `createAskUserQuestionHandler` |
|
|
624
|
+
|
|
625
|
+
### LangChain Adapter Entry Point (`zeitlich/adapters/langchain`)
|
|
445
626
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
|
449
|
-
|
|
|
450
|
-
| `
|
|
451
|
-
| `
|
|
452
|
-
|
|
|
627
|
+
LangChain-specific implementations:
|
|
628
|
+
|
|
629
|
+
| Export | Description |
|
|
630
|
+
| ----------------------------------- | ---------------------------------------------------------------------- |
|
|
631
|
+
| `createLangChainAdapter` | Unified adapter returning `threadOps`, `invoker`, `createModelInvoker` |
|
|
632
|
+
| `createLangChainModelInvoker` | Factory that returns a `ModelInvoker` backed by a LangChain chat model |
|
|
633
|
+
| `invokeLangChainModel` | One-shot model invocation convenience function |
|
|
634
|
+
| `createLangChainThreadManager` | Thread manager with LangChain `StoredMessage` helpers |
|
|
453
635
|
|
|
454
636
|
### Types
|
|
455
637
|
|
|
456
638
|
| Export | Description |
|
|
457
639
|
| ----------------------- | ---------------------------------------------------------------------------- |
|
|
458
640
|
| `AgentStatus` | `"RUNNING" \| "WAITING_FOR_INPUT" \| "COMPLETED" \| "FAILED" \| "CANCELLED"` |
|
|
641
|
+
| `MessageContent` | Framework-agnostic message content (`string \| ContentPart[]`) |
|
|
642
|
+
| `ToolMessageContent` | Content returned by a tool handler (`string`) |
|
|
643
|
+
| `ModelInvoker` | Generic model invocation contract |
|
|
644
|
+
| `ModelInvokerConfig` | Configuration passed to a model invoker |
|
|
459
645
|
| `ToolDefinition` | Tool definition with name, description, and Zod schema |
|
|
460
646
|
| `ToolWithHandler` | Tool definition combined with its handler |
|
|
461
647
|
| `SubagentConfig` | Configuration for subagent workflows |
|
|
@@ -468,25 +654,26 @@ For use in activities, worker setup, and Node.js code:
|
|
|
468
654
|
┌─────────────────────────────────────────────────────────────────┐
|
|
469
655
|
│ Temporal Worker │
|
|
470
656
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
471
|
-
│ │
|
|
472
|
-
│ │ • Registers shared activities (thread management) │ │
|
|
473
|
-
│ └──────────────────────────────────────────────────────────┘ │
|
|
474
|
-
│ │ │
|
|
475
|
-
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
476
|
-
│ │ Workflow │ │
|
|
657
|
+
│ │ Workflow (zeitlich/workflow) │ │
|
|
477
658
|
│ │ ┌────────────────┐ ┌───────────────────────────────┐ │ │
|
|
478
659
|
│ │ │ State Manager │ │ Session │ │ │
|
|
479
660
|
│ │ │ • Status │ │ • Agent loop │ │ │
|
|
480
661
|
│ │ │ • Turns │ │ • Tool routing & hooks │ │ │
|
|
481
662
|
│ │ │ • Custom state │ │ • Prompts (system, context) │ │ │
|
|
482
663
|
│ │ └────────────────┘ │ • Subagent coordination │ │ │
|
|
483
|
-
│ │
|
|
664
|
+
│ │ └───────────────────────────────┘ │ │
|
|
484
665
|
│ └──────────────────────────────────────────────────────────┘ │
|
|
485
666
|
│ │ │
|
|
486
667
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
487
|
-
│ │
|
|
488
|
-
│ │ • runAgent (LLM invocation) │ │
|
|
668
|
+
│ │ Activities (zeitlich) │ │
|
|
489
669
|
│ │ • Tool handlers (search, file ops, bash, etc.) │ │
|
|
670
|
+
│ │ • Generic thread manager (BaseThreadManager<T>) │ │
|
|
671
|
+
│ └──────────────────────────────────────────────────────────┘ │
|
|
672
|
+
│ │ │
|
|
673
|
+
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
674
|
+
│ │ LLM Adapter (zeitlich/adapters/langchain) │ │
|
|
675
|
+
│ │ • createLangChainAdapter (thread ops + model invoker) │ │
|
|
676
|
+
│ │ • createLangChainThreadManager (message helpers) │ │
|
|
490
677
|
│ └──────────────────────────────────────────────────────────┘ │
|
|
491
678
|
└─────────────────────────────────────────────────────────────────┘
|
|
492
679
|
│
|