zeitlich 0.2.3 → 0.2.5

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 CHANGED
@@ -1,5 +1,6 @@
1
1
  [![npm version](https://img.shields.io/npm/v/zeitlich.svg?style=flat-square)](https://www.npmjs.org/package/zeitlich)
2
2
  [![npm downloads](https://img.shields.io/npm/dm/zeitlich.svg?style=flat-square)](https://npm-stat.com/charts.html?package=zeitlich)
3
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/bead-ai/zeitlich)
3
4
 
4
5
  # Zeitlich
5
6
 
@@ -51,7 +52,7 @@ const google = new ChatGoogleGenerativeAI({ model: "gemini-1.5-pro" });
51
52
 
52
53
  // Pass to invokeModel in your activity
53
54
  return {
54
- runAgent: (config) => invokeModel(redis, config, anthropic),
55
+ runAgent: (config) => invokeModel({ config, model: anthropic, redis, client }),
55
56
  };
56
57
  ```
57
58
 
@@ -121,17 +122,25 @@ export const searchTool: ToolDefinition<"Search", typeof searchSchema> = {
121
122
 
122
123
  ```typescript
123
124
  import type Redis from "ioredis";
125
+ import type { WorkflowClient } from "@temporalio/client";
124
126
  import { ChatAnthropic } from "@langchain/anthropic";
125
- import { invokeModel } from "zeitlich";
126
-
127
- export const createActivities = (redis: Redis) => {
127
+ import { invokeModel, type InvokeModelConfig } from "zeitlich";
128
+
129
+ export const createActivities = ({
130
+ redis,
131
+ client,
132
+ }: {
133
+ redis: Redis;
134
+ client: WorkflowClient;
135
+ }) => {
128
136
  const model = new ChatAnthropic({
129
137
  model: "claude-sonnet-4-20250514",
130
138
  maxTokens: 4096,
131
139
  });
132
140
 
133
141
  return {
134
- runAgent: (config) => invokeModel(redis, config, model),
142
+ runAgent: (config: InvokeModelConfig) =>
143
+ invokeModel({ config, model, redis, client }),
135
144
 
136
145
  handleSearchResult: async ({ args }) => {
137
146
  const results = await performSearch(args.query);
@@ -147,12 +156,24 @@ export type MyActivities = ReturnType<typeof createActivities>;
147
156
 
148
157
  ```typescript
149
158
  import { proxyActivities, workflowInfo } from "@temporalio/workflow";
150
- import { createAgentStateManager, createSession } from "zeitlich/workflow";
159
+ import {
160
+ createAgentStateManager,
161
+ createSession,
162
+ defineTool,
163
+ proxyDefaultThreadOps,
164
+ } from "zeitlich/workflow";
151
165
  import { searchTool } from "./tools";
152
166
  import type { MyActivities } from "./activities";
153
167
 
154
168
  const { runAgent, handleSearchResult } = proxyActivities<MyActivities>({
155
169
  startToCloseTimeout: "30m",
170
+ retry: {
171
+ maximumAttempts: 6,
172
+ initialInterval: "5s",
173
+ maximumInterval: "15m",
174
+ backoffCoefficient: 4,
175
+ },
176
+ heartbeatTimeout: "5m",
156
177
  });
157
178
 
158
179
  export async function myAgentWorkflow({ prompt }: { prompt: string }) {
@@ -164,13 +185,15 @@ export async function myAgentWorkflow({ prompt }: { prompt: string }) {
164
185
  threadId: runId,
165
186
  agentName: "my-agent",
166
187
  maxTurns: 20,
188
+ systemPrompt: "You are a helpful assistant.",
167
189
  runAgent,
190
+ threadOps: proxyDefaultThreadOps(),
168
191
  buildContextMessage: () => [{ type: "text", text: prompt }],
169
192
  tools: {
170
- Search: {
193
+ Search: defineTool({
171
194
  ...searchTool,
172
195
  handler: handleSearchResult,
173
- },
196
+ }),
174
197
  },
175
198
  });
176
199
 
@@ -183,6 +206,7 @@ export async function myAgentWorkflow({ prompt }: { prompt: string }) {
183
206
 
184
207
  ```typescript
185
208
  import { Worker, NativeConnection } from "@temporalio/worker";
209
+ import { Client } from "@temporalio/client";
186
210
  import { ZeitlichPlugin } from "zeitlich";
187
211
  import Redis from "ioredis";
188
212
  import { createActivities } from "./activities";
@@ -191,6 +215,7 @@ async function run() {
191
215
  const connection = await NativeConnection.connect({
192
216
  address: "localhost:7233",
193
217
  });
218
+ const client = new Client({ connection });
194
219
  const redis = new Redis({ host: "localhost", port: 6379 });
195
220
 
196
221
  const worker = await Worker.create({
@@ -198,7 +223,7 @@ async function run() {
198
223
  connection,
199
224
  taskQueue: "my-agent",
200
225
  workflowsPath: require.resolve("./workflows"),
201
- activities: createActivities(redis),
226
+ activities: createActivities({ redis, client: client.workflow }),
202
227
  });
203
228
 
204
229
  await worker.run();
@@ -242,14 +267,14 @@ const searchTool: ToolDefinition<"Search", typeof searchSchema> = {
242
267
  schema: z.object({ query: z.string() }),
243
268
  };
244
269
 
245
- // In workflow - combine tool definition with handler
270
+ // In workflow - combine tool definition with handler using defineTool()
246
271
  const session = await createSession({
247
272
  // ... other config
248
273
  tools: {
249
- Search: {
274
+ Search: defineTool({
250
275
  ...searchTool,
251
276
  handler: handleSearchResult, // Activity that implements the tool
252
- },
277
+ }),
253
278
  },
254
279
  });
255
280
  ```
@@ -288,20 +313,17 @@ const session = await createSession({
288
313
  Spawn child agents as Temporal child workflows:
289
314
 
290
315
  ```typescript
316
+ // Define subagent workflows (each is a Temporal workflow that returns string | null)
317
+ export const researcherSubagent = {
318
+ name: "researcher",
319
+ description: "Researches topics and returns findings",
320
+ workflow: researcherWorkflow,
321
+ };
322
+
323
+ // In the main agent workflow
291
324
  const session = await createSession({
292
325
  // ... other config
293
- subagents: [
294
- {
295
- name: "researcher",
296
- description: "Researches topics and returns findings",
297
- workflow: "researcherWorkflow",
298
- },
299
- {
300
- name: "code-reviewer",
301
- description: "Reviews code for quality and best practices",
302
- workflow: "codeReviewerWorkflow",
303
- },
304
- ],
326
+ subagents: [researcherSubagent, codeReviewerSubagent],
305
327
  });
306
328
  ```
307
329
 
@@ -327,18 +349,26 @@ const session = await createSession({
327
349
  });
328
350
  ```
329
351
 
330
- For more advanced file operations, use the built-in tool handlers:
352
+ For more advanced file operations, use the built-in tool handler factories:
331
353
 
332
354
  ```typescript
333
- import { globHandler, editHandler, toTree } from "zeitlich";
355
+ import { createGlobHandler, createEditHandler, toTree } from "zeitlich";
334
356
 
335
357
  export const createActivities = () => ({
336
358
  generateFileTree: () => toTree("/workspace"),
337
- handleGlob: (args) => globHandler(args),
338
- handleEdit: (args) => editHandler(args, { basePath: "/workspace" }),
359
+ globHandlerActivity: createGlobHandler("/workspace"),
360
+ editHandlerActivity: createEditHandler("/workspace"),
339
361
  });
340
362
  ```
341
363
 
364
+ `toTree` also accepts an in-memory filesystem object (e.g. from [`just-bash`](https://github.com/nicholasgasior/just-bash)):
365
+
366
+ ```typescript
367
+ import { toTree } from "zeitlich";
368
+
369
+ const fileTree = toTree(inMemoryFileSystem);
370
+ ```
371
+
342
372
  ### Built-in Tools
343
373
 
344
374
  Zeitlich provides ready-to-use tool definitions and handlers for common agent operations.
@@ -366,12 +396,12 @@ import {
366
396
  askUserQuestionTool,
367
397
  } from "zeitlich/workflow";
368
398
 
369
- // Import handlers in activities
399
+ // Import handler factories in activities
370
400
  import {
371
- editHandler,
372
- globHandler,
373
- handleBashTool,
374
- handleAskUserQuestionToolResult,
401
+ createEditHandler,
402
+ createGlobHandler,
403
+ createBashHandler,
404
+ createAskUserQuestionHandler,
375
405
  } from "zeitlich";
376
406
  ```
377
407
 
@@ -381,14 +411,14 @@ All tools are passed via `tools`. The Bash tool's description is automatically e
381
411
  const session = await createSession({
382
412
  // ... other config
383
413
  tools: {
384
- AskUserQuestion: {
414
+ AskUserQuestion: defineTool({
385
415
  ...askUserQuestionTool,
386
- handler: handleAskUserQuestionToolResult,
387
- },
388
- Bash: {
416
+ handler: askUserQuestionHandlerActivity,
417
+ }),
418
+ Bash: defineTool({
389
419
  ...bashTool,
390
- handler: handleBashTool(bashOptions),
391
- },
420
+ handler: bashHandlerActivity,
421
+ }),
392
422
  },
393
423
  });
394
424
  ```
@@ -419,7 +449,7 @@ For use in activities, worker setup, and Node.js code:
419
449
  | `createSharedActivities` | Creates thread management activities |
420
450
  | `invokeModel` | Core LLM invocation utility (requires Redis + LangChain) |
421
451
  | `toTree` | Generate file tree string from a directory path |
422
- | Tool handlers | `globHandler`, `editHandler`, `handleBashTool`, `handleAskUserQuestionToolResult` |
452
+ | Tool handlers | `createGlobHandler`, `createEditHandler`, `createBashHandler`, `createAskUserQuestionHandler` |
423
453
 
424
454
  ### Types
425
455
 
package/dist/index.cjs CHANGED
@@ -14,38 +14,38 @@ var z3__default = /*#__PURE__*/_interopDefault(z3);
14
14
  var crypto__default = /*#__PURE__*/_interopDefault(crypto);
15
15
 
16
16
  // src/lib/session.ts
17
- var TASK_TOOL = "Task";
18
- function buildTaskDescription(subagents) {
19
- const subagentList = subagents.map((s) => `- **${s.name}**: ${s.description}`).join("\n");
20
- return `Launch a new agent to handle complex, multi-step tasks autonomously.
17
+ var SUBAGENT_TOOL = "Subagent";
18
+ function buildSubagentDescription(subagents) {
19
+ const subagentList = subagents.map((s) => `- **${s.agentName}**: ${s.description}`).join("\n");
20
+ return `Launch a new agent to handle complex tasks autonomously.
21
21
 
22
- The ${TASK_TOOL} tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
22
+ The ${SUBAGENT_TOOL} tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
23
23
 
24
24
  Available agent types:
25
25
 
26
26
  ${subagentList}
27
27
 
28
- When using the ${TASK_TOOL} tool, you must specify a subagent parameter to select which agent type to use.
28
+ When using the ${SUBAGENT_TOOL} tool, you must specify a subagent parameter to select which agent type to use.
29
29
 
30
30
  Usage notes:
31
31
 
32
32
  - Always include a short description (3-5 words) summarizing what the agent will do
33
33
  - Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
34
- - When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.
34
+ - When the agent is done, it will return a single message back to you.
35
35
  - Each invocation starts fresh - provide a detailed task description with all necessary context.
36
36
  - Provide clear, detailed prompts so the agent can work autonomously and return exactly the information you need.
37
37
  - The agent's outputs should generally be trusted
38
38
  - Clearly tell the agent what type of work you expect since it is not aware of the user's intent
39
39
  - If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.`;
40
40
  }
41
- function createTaskTool(subagents) {
41
+ function createSubagentTool(subagents) {
42
42
  if (subagents.length === 0) {
43
43
  throw new Error("createTaskTool requires at least one subagent");
44
44
  }
45
- const names = subagents.map((s) => s.name);
45
+ const names = subagents.map((s) => s.agentName);
46
46
  return {
47
- name: TASK_TOOL,
48
- description: buildTaskDescription(subagents),
47
+ name: SUBAGENT_TOOL,
48
+ description: buildSubagentDescription(subagents),
49
49
  schema: z3__default.default.object({
50
50
  subagent: z3__default.default.enum(names).describe("The type of subagent to launch"),
51
51
  description: z3__default.default.string().describe("A short (3-5 word) description of the task"),
@@ -53,16 +53,16 @@ function createTaskTool(subagents) {
53
53
  })
54
54
  };
55
55
  }
56
- function createTaskHandler(subagents) {
57
- const { workflowId: parentWorkflowId, taskQueue: parentTaskQueue } = workflow.workflowInfo();
56
+ function createSubagentHandler(subagents) {
57
+ const { taskQueue: parentTaskQueue } = workflow.workflowInfo();
58
58
  return async (args) => {
59
- const config = subagents.find((s) => s.name === args.subagent);
59
+ const config = subagents.find((s) => s.agentName === args.subagent);
60
60
  if (!config) {
61
61
  throw new Error(
62
- `Unknown subagent: ${args.subagent}. Available: ${subagents.map((s) => s.name).join(", ")}`
62
+ `Unknown subagent: ${args.subagent}. Available: ${subagents.map((s) => s.agentName).join(", ")}`
63
63
  );
64
64
  }
65
- const childWorkflowId = `${parentWorkflowId}-${args.subagent}-${workflow.uuid4()}`;
65
+ const childWorkflowId = `${args.subagent}-${workflow.uuid4()}`;
66
66
  const input = {
67
67
  prompt: args.prompt,
68
68
  ...config.context && { context: config.context }
@@ -72,15 +72,11 @@ function createTaskHandler(subagents) {
72
72
  args: [input],
73
73
  taskQueue: config.taskQueue ?? parentTaskQueue
74
74
  };
75
- const childResult = typeof config.workflow === "string" ? await workflow.executeChild(config.workflow, childOpts) : await workflow.executeChild(config.workflow, childOpts);
76
- const validated = config.resultSchema ? config.resultSchema.parse(childResult) : childResult;
77
- const toolResponse = typeof validated === "string" ? validated : JSON.stringify(validated, null, 2);
75
+ const { toolResponse, data } = typeof config.workflow === "string" ? await workflow.executeChild(config.workflow, childOpts) : await workflow.executeChild(config.workflow, childOpts);
76
+ const validated = config.resultSchema ? config.resultSchema.parse(data) : null;
78
77
  return {
79
78
  toolResponse,
80
- data: {
81
- result: validated,
82
- childWorkflowId
83
- }
79
+ data: validated
84
80
  };
85
81
  };
86
82
  }
@@ -94,31 +90,36 @@ function createToolRouter(options) {
94
90
  }
95
91
  const isEnabled = (tool) => tool.enabled !== false;
96
92
  if (options.subagents) {
97
- const subagentHooksMap = /* @__PURE__ */ new Map();
98
- for (const s of options.subagents) {
99
- if (s.hooks) subagentHooksMap.set(s.name, s.hooks);
100
- }
101
- const resolveSubagentName = (args) => args.subagent;
102
- toolMap.set("Task", {
103
- ...createTaskTool(options.subagents),
104
- handler: createTaskHandler(options.subagents),
105
- ...subagentHooksMap.size > 0 && {
106
- hooks: {
107
- onPreToolUse: async (ctx) => {
108
- const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
109
- return hooks?.onPreExecution?.(ctx) ?? {};
110
- },
111
- onPostToolUse: async (ctx) => {
112
- const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
113
- await hooks?.onPostExecution?.(ctx);
114
- },
115
- onPostToolUseFailure: async (ctx) => {
116
- const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
117
- return hooks?.onExecutionFailure?.(ctx) ?? {};
93
+ const enabledSubagents = options.subagents.filter(
94
+ (s) => s.enabled !== false
95
+ );
96
+ if (enabledSubagents.length > 0) {
97
+ const subagentHooksMap = /* @__PURE__ */ new Map();
98
+ for (const s of enabledSubagents) {
99
+ if (s.hooks) subagentHooksMap.set(s.agentName, s.hooks);
100
+ }
101
+ const resolveSubagentName = (args) => args.subagent;
102
+ toolMap.set("Subagent", {
103
+ ...createSubagentTool(enabledSubagents),
104
+ handler: createSubagentHandler(enabledSubagents),
105
+ ...subagentHooksMap.size > 0 && {
106
+ hooks: {
107
+ onPreToolUse: async (ctx) => {
108
+ const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
109
+ return hooks?.onPreExecution?.(ctx) ?? {};
110
+ },
111
+ onPostToolUse: async (ctx) => {
112
+ const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
113
+ await hooks?.onPostExecution?.(ctx);
114
+ },
115
+ onPostToolUseFailure: async (ctx) => {
116
+ const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
117
+ return hooks?.onExecutionFailure?.(ctx) ?? {};
118
+ }
118
119
  }
119
120
  }
120
- }
121
- });
121
+ });
122
+ }
122
123
  }
123
124
  async function processToolCall(toolCall, turn, handlerContext) {
124
125
  const startTime = Date.now();
@@ -389,7 +390,11 @@ function withAutoAppend(threadHandler, handler) {
389
390
  toolName,
390
391
  content: response.toolResponse
391
392
  });
392
- return { toolResponse: "", data: response.data, resultAppended: true };
393
+ return {
394
+ toolResponse: "Response appended via withAutoAppend",
395
+ data: response.data,
396
+ resultAppended: true
397
+ };
393
398
  };
394
399
  }
395
400
  function defineTool(tool) {
@@ -414,11 +419,19 @@ var createSession = async ({
414
419
  subagents,
415
420
  tools = {},
416
421
  processToolsInParallel = true,
417
- hooks = {}
422
+ hooks = {},
423
+ appendSystemPrompt = true,
424
+ systemPrompt
418
425
  }) => {
426
+ const {
427
+ appendToolResult,
428
+ appendHumanMessage,
429
+ initializeThread,
430
+ appendSystemMessage
431
+ } = threadOps ?? proxyDefaultThreadOps();
419
432
  const toolRouter = createToolRouter({
420
433
  tools,
421
- appendToolResult: threadOps.appendToolResult,
434
+ appendToolResult,
422
435
  threadId,
423
436
  hooks,
424
437
  subagents,
@@ -445,35 +458,32 @@ var createSession = async ({
445
458
  });
446
459
  }
447
460
  stateManager.setTools(toolRouter.getToolDefinitions());
448
- await threadOps.initializeThread(threadId);
449
- await threadOps.appendHumanMessage(threadId, await buildContextMessage());
461
+ await initializeThread(threadId);
462
+ if (appendSystemPrompt && systemPrompt && systemPrompt.trim() !== "") {
463
+ await appendSystemMessage(threadId, systemPrompt);
464
+ }
465
+ await appendHumanMessage(threadId, await buildContextMessage());
450
466
  let exitReason = "completed";
451
467
  try {
452
468
  while (stateManager.isRunning() && !stateManager.isTerminal() && stateManager.getTurns() < maxTurns) {
453
469
  stateManager.incrementTurns();
454
470
  const currentTurn = stateManager.getTurns();
455
- const { message, stopReason } = await runAgent({
471
+ const { message, rawToolCalls } = await runAgent({
456
472
  threadId,
457
473
  agentName,
458
474
  metadata
459
475
  });
460
- if (stopReason === "end_turn") {
461
- stateManager.complete();
462
- exitReason = "completed";
463
- return message;
464
- }
465
- if (!toolRouter.hasTools()) {
476
+ if (!toolRouter.hasTools() || rawToolCalls.length === 0) {
466
477
  stateManager.complete();
467
478
  exitReason = "completed";
468
479
  return message;
469
480
  }
470
- const rawToolCalls = await threadOps.parseToolCalls(message);
471
481
  const parsedToolCalls = [];
472
482
  for (const tc of rawToolCalls) {
473
483
  try {
474
484
  parsedToolCalls.push(toolRouter.parseToolCall(tc));
475
485
  } catch (error) {
476
- await threadOps.appendToolResult({
486
+ await appendToolResult({
477
487
  threadId,
478
488
  toolCallId: tc.id ?? "",
479
489
  toolName: tc.name,
@@ -521,7 +531,7 @@ function proxyDefaultThreadOps(options) {
521
531
  initializeThread: activities.initializeThread,
522
532
  appendHumanMessage: activities.appendHumanMessage,
523
533
  appendToolResult: activities.appendToolResult,
524
- parseToolCalls: activities.parseToolCalls
534
+ appendSystemMessage: activities.appendSystemMessage
525
535
  };
526
536
  }
527
537
 
@@ -1100,6 +1110,8 @@ function createThreadManager(config) {
1100
1110
  };
1101
1111
  return Object.assign(base, helpers);
1102
1112
  }
1113
+
1114
+ // src/activities.ts
1103
1115
  function createSharedActivities(redis) {
1104
1116
  return {
1105
1117
  async appendToolResult(config) {
@@ -1119,14 +1131,9 @@ function createSharedActivities(redis) {
1119
1131
  const thread = createThreadManager({ redis, threadId });
1120
1132
  await thread.appendHumanMessage(content);
1121
1133
  },
1122
- async parseToolCalls(storedMessage) {
1123
- const message = messages.mapStoredMessageToChatMessage(storedMessage);
1124
- const toolCalls = message.tool_calls ?? [];
1125
- return toolCalls.map((toolCall) => ({
1126
- id: toolCall.id,
1127
- name: toolCall.name,
1128
- args: toolCall.args
1129
- }));
1134
+ async appendSystemMessage(threadId, content) {
1135
+ const thread = createThreadManager({ redis, threadId });
1136
+ await thread.appendSystemMessage(content);
1130
1137
  }
1131
1138
  };
1132
1139
  }
@@ -1164,9 +1171,14 @@ async function invokeModel({
1164
1171
  }
1165
1172
  );
1166
1173
  await thread.append([response.toDict()]);
1174
+ const toolCalls = response.tool_calls ?? [];
1167
1175
  return {
1168
1176
  message: response.toDict(),
1169
- stopReason: response.response_metadata?.stop_reason ?? null,
1177
+ rawToolCalls: toolCalls.map((tc) => ({
1178
+ id: tc.id,
1179
+ name: tc.name,
1180
+ args: tc.args
1181
+ })),
1170
1182
  usage: {
1171
1183
  input_tokens: response.usage_metadata?.input_tokens,
1172
1184
  output_tokens: response.usage_metadata?.output_tokens,
@@ -1368,10 +1380,10 @@ exports.createEditHandler = createEditHandler;
1368
1380
  exports.createGlobHandler = createGlobHandler;
1369
1381
  exports.createSession = createSession;
1370
1382
  exports.createSharedActivities = createSharedActivities;
1383
+ exports.createSubagentTool = createSubagentTool;
1371
1384
  exports.createTaskCreateHandler = createTaskCreateHandler;
1372
1385
  exports.createTaskGetHandler = createTaskGetHandler;
1373
1386
  exports.createTaskListHandler = createTaskListHandler;
1374
- exports.createTaskTool = createTaskTool;
1375
1387
  exports.createTaskUpdateHandler = createTaskUpdateHandler;
1376
1388
  exports.createThreadManager = createThreadManager;
1377
1389
  exports.createToolRouter = createToolRouter;