zeitlich 0.2.4 → 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
@@ -16,7 +16,7 @@ var crypto__default = /*#__PURE__*/_interopDefault(crypto);
16
16
  // src/lib/session.ts
17
17
  var SUBAGENT_TOOL = "Subagent";
18
18
  function buildSubagentDescription(subagents) {
19
- const subagentList = subagents.map((s) => `- **${s.name}**: ${s.description}`).join("\n");
19
+ const subagentList = subagents.map((s) => `- **${s.agentName}**: ${s.description}`).join("\n");
20
20
  return `Launch a new agent to handle complex tasks autonomously.
21
21
 
22
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.
@@ -42,7 +42,7 @@ 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
47
  name: SUBAGENT_TOOL,
48
48
  description: buildSubagentDescription(subagents),
@@ -54,15 +54,15 @@ function createSubagentTool(subagents) {
54
54
  };
55
55
  }
56
56
  function createSubagentHandler(subagents) {
57
- const { workflowId: parentWorkflowId, taskQueue: parentTaskQueue } = workflow.workflowInfo();
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 }
@@ -90,31 +90,36 @@ function createToolRouter(options) {
90
90
  }
91
91
  const isEnabled = (tool) => tool.enabled !== false;
92
92
  if (options.subagents) {
93
- const subagentHooksMap = /* @__PURE__ */ new Map();
94
- for (const s of options.subagents) {
95
- if (s.hooks) subagentHooksMap.set(s.name, s.hooks);
96
- }
97
- const resolveSubagentName = (args) => args.subagent;
98
- toolMap.set("Subagent", {
99
- ...createSubagentTool(options.subagents),
100
- handler: createSubagentHandler(options.subagents),
101
- ...subagentHooksMap.size > 0 && {
102
- hooks: {
103
- onPreToolUse: async (ctx) => {
104
- const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
105
- return hooks?.onPreExecution?.(ctx) ?? {};
106
- },
107
- onPostToolUse: async (ctx) => {
108
- const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
109
- await hooks?.onPostExecution?.(ctx);
110
- },
111
- onPostToolUseFailure: async (ctx) => {
112
- const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
113
- 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
+ }
114
119
  }
115
120
  }
116
- }
117
- });
121
+ });
122
+ }
118
123
  }
119
124
  async function processToolCall(toolCall, turn, handlerContext) {
120
125
  const startTime = Date.now();
@@ -385,7 +390,11 @@ function withAutoAppend(threadHandler, handler) {
385
390
  toolName,
386
391
  content: response.toolResponse
387
392
  });
388
- return { toolResponse: "", data: response.data, resultAppended: true };
393
+ return {
394
+ toolResponse: "Response appended via withAutoAppend",
395
+ data: response.data,
396
+ resultAppended: true
397
+ };
389
398
  };
390
399
  }
391
400
  function defineTool(tool) {
@@ -410,11 +419,19 @@ var createSession = async ({
410
419
  subagents,
411
420
  tools = {},
412
421
  processToolsInParallel = true,
413
- hooks = {}
422
+ hooks = {},
423
+ appendSystemPrompt = true,
424
+ systemPrompt
414
425
  }) => {
426
+ const {
427
+ appendToolResult,
428
+ appendHumanMessage,
429
+ initializeThread,
430
+ appendSystemMessage
431
+ } = threadOps ?? proxyDefaultThreadOps();
415
432
  const toolRouter = createToolRouter({
416
433
  tools,
417
- appendToolResult: threadOps.appendToolResult,
434
+ appendToolResult,
418
435
  threadId,
419
436
  hooks,
420
437
  subagents,
@@ -441,8 +458,11 @@ var createSession = async ({
441
458
  });
442
459
  }
443
460
  stateManager.setTools(toolRouter.getToolDefinitions());
444
- await threadOps.initializeThread(threadId);
445
- 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());
446
466
  let exitReason = "completed";
447
467
  try {
448
468
  while (stateManager.isRunning() && !stateManager.isTerminal() && stateManager.getTurns() < maxTurns) {
@@ -463,7 +483,7 @@ var createSession = async ({
463
483
  try {
464
484
  parsedToolCalls.push(toolRouter.parseToolCall(tc));
465
485
  } catch (error) {
466
- await threadOps.appendToolResult({
486
+ await appendToolResult({
467
487
  threadId,
468
488
  toolCallId: tc.id ?? "",
469
489
  toolName: tc.name,
@@ -510,7 +530,8 @@ function proxyDefaultThreadOps(options) {
510
530
  return {
511
531
  initializeThread: activities.initializeThread,
512
532
  appendHumanMessage: activities.appendHumanMessage,
513
- appendToolResult: activities.appendToolResult
533
+ appendToolResult: activities.appendToolResult,
534
+ appendSystemMessage: activities.appendSystemMessage
514
535
  };
515
536
  }
516
537
 
@@ -1109,6 +1130,10 @@ function createSharedActivities(redis) {
1109
1130
  async appendHumanMessage(threadId, content) {
1110
1131
  const thread = createThreadManager({ redis, threadId });
1111
1132
  await thread.appendHumanMessage(content);
1133
+ },
1134
+ async appendSystemMessage(threadId, content) {
1135
+ const thread = createThreadManager({ redis, threadId });
1136
+ await thread.appendSystemMessage(content);
1112
1137
  }
1113
1138
  };
1114
1139
  }