zeitlich 0.2.4 → 0.2.6

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.
@@ -23,10 +23,11 @@ export interface InvokeModelConfig {
23
23
  /**
24
24
  * Core model invocation logic - shared utility for workflow-specific activities
25
25
  *
26
- * @param redis - Redis client for thread management
27
- * @param config - Model invocation configuration
28
- * @param model - Pre-instantiated LangChain chat model
29
- * @param invocationConfig - Per-invocation configuration (system prompt, etc.)
26
+ * @param options - Named options object
27
+ * @param options.redis - Redis client for thread management
28
+ * @param options.config - Model invocation configuration (threadId, agentName)
29
+ * @param options.model - Pre-instantiated LangChain chat model
30
+ * @param options.client - Temporal WorkflowClient for querying workflow state
30
31
  * @returns Agent response with message and metadata
31
32
  */
32
33
  export async function invokeModel({
@@ -42,10 +42,18 @@ export const createSession = async <T extends ToolMap, M = unknown>({
42
42
  tools = {} as T,
43
43
  processToolsInParallel = true,
44
44
  hooks = {},
45
+ appendSystemPrompt = true,
46
+ systemPrompt,
45
47
  }: ZeitlichAgentConfig<T, M>): Promise<ZeitlichSession<M>> => {
48
+ const {
49
+ appendToolResult,
50
+ appendHumanMessage,
51
+ initializeThread,
52
+ appendSystemMessage,
53
+ } = threadOps ?? proxyDefaultThreadOps();
46
54
  const toolRouter = createToolRouter({
47
55
  tools,
48
- appendToolResult: threadOps.appendToolResult,
56
+ appendToolResult,
49
57
  threadId,
50
58
  hooks,
51
59
  subagents,
@@ -77,10 +85,14 @@ export const createSession = async <T extends ToolMap, M = unknown>({
77
85
  metadata,
78
86
  });
79
87
  }
88
+
80
89
  stateManager.setTools(toolRouter.getToolDefinitions());
81
90
 
82
- await threadOps.initializeThread(threadId);
83
- await threadOps.appendHumanMessage(threadId, await buildContextMessage());
91
+ await initializeThread(threadId);
92
+ if (appendSystemPrompt && systemPrompt && systemPrompt.trim() !== "") {
93
+ await appendSystemMessage(threadId, systemPrompt);
94
+ }
95
+ await appendHumanMessage(threadId, await buildContextMessage());
84
96
 
85
97
  let exitReason: SessionExitReason = "completed";
86
98
 
@@ -112,7 +124,7 @@ export const createSession = async <T extends ToolMap, M = unknown>({
112
124
  try {
113
125
  parsedToolCalls.push(toolRouter.parseToolCall(tc));
114
126
  } catch (error) {
115
- await threadOps.appendToolResult({
127
+ await appendToolResult({
116
128
  threadId,
117
129
  toolCallId: tc.id ?? "",
118
130
  toolName: tc.name,
@@ -183,5 +195,6 @@ export function proxyDefaultThreadOps(
183
195
  initializeThread: activities.initializeThread,
184
196
  appendHumanMessage: activities.appendHumanMessage,
185
197
  appendToolResult: activities.appendToolResult,
198
+ appendSystemMessage: activities.appendSystemMessage,
186
199
  };
187
200
  }
@@ -7,6 +7,7 @@ import {
7
7
  type MessageContent,
8
8
  type MessageStructure,
9
9
  type StoredMessage,
10
+ SystemMessage,
10
11
  ToolMessage,
11
12
  } from "@langchain/core/messages";
12
13
  import { v4 as uuidv4 } from "uuid";
@@ -50,21 +51,20 @@ export interface BaseThreadManager<T> {
50
51
  export interface ThreadManager extends BaseThreadManager<StoredMessage> {
51
52
  /** Create a HumanMessage (returns StoredMessage for storage) */
52
53
  createHumanMessage(content: string | MessageContent): StoredMessage;
53
-
54
54
  /** Create an AIMessage with optional additional kwargs */
55
55
  createAIMessage(
56
56
  content: string | MessageContent,
57
57
  kwargs?: { header?: string; options?: string[]; multiSelect?: boolean }
58
58
  ): StoredMessage;
59
-
60
59
  /** Create a ToolMessage */
61
60
  createToolMessage(
62
61
  content: ToolMessageContent,
63
62
  toolCallId: string
64
63
  ): StoredMessage;
65
-
66
64
  /** Create and append a HumanMessage */
67
65
  appendHumanMessage(content: string | MessageContent): Promise<void>;
66
+ /** Create and append a SystemMessage */
67
+ appendSystemMessage(content: string): Promise<void>;
68
68
  /** Create and append a ToolMessage */
69
69
  appendToolMessage(
70
70
  content: ToolMessageContent,
@@ -127,6 +127,13 @@ export function createThreadManager<T>(
127
127
  }).toDict();
128
128
  },
129
129
 
130
+ createSystemMessage(content: string): StoredMessage {
131
+ return new SystemMessage({
132
+ id: uuidv4(),
133
+ content: content as string,
134
+ }).toDict();
135
+ },
136
+
130
137
  createAIMessage(
131
138
  content: string,
132
139
  kwargs?: { header?: string; options?: string[]; multiSelect?: boolean }
@@ -171,6 +178,11 @@ export function createThreadManager<T>(
171
178
  const message = helpers.createAIMessage(content as string);
172
179
  await (base as BaseThreadManager<StoredMessage>).append([message]);
173
180
  },
181
+
182
+ async appendSystemMessage(content: string): Promise<void> {
183
+ const message = helpers.createSystemMessage(content);
184
+ await (base as BaseThreadManager<StoredMessage>).append([message]);
185
+ },
174
186
  };
175
187
 
176
188
  return Object.assign(base, helpers);
@@ -415,37 +415,42 @@ export function createToolRouter<T extends ToolMap>(
415
415
  const isEnabled = (tool: ToolMap[string]): boolean => tool.enabled !== false;
416
416
 
417
417
  if (options.subagents) {
418
- // Build per-subagent hook dispatcher keyed by subagent name
419
- const subagentHooksMap = new Map<string, SubagentHooks>();
420
- for (const s of options.subagents) {
421
- if (s.hooks) subagentHooksMap.set(s.name, s.hooks);
422
- }
418
+ const enabledSubagents = options.subagents.filter(
419
+ (s) => s.enabled !== false
420
+ );
421
+ if (enabledSubagents.length > 0) {
422
+ // Build per-subagent hook dispatcher keyed by subagent name
423
+ const subagentHooksMap = new Map<string, SubagentHooks>();
424
+ for (const s of enabledSubagents) {
425
+ if (s.hooks) subagentHooksMap.set(s.agentName, s.hooks);
426
+ }
423
427
 
424
- const resolveSubagentName = (args: unknown): string =>
425
- (args as SubagentArgs).subagent;
426
-
427
- toolMap.set("Subagent", {
428
- ...createSubagentTool(options.subagents),
429
- handler: createSubagentHandler(options.subagents),
430
- ...(subagentHooksMap.size > 0 && {
431
- hooks: {
432
- onPreToolUse: async (ctx): Promise<PreToolUseHookResult> => {
433
- const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
434
- return hooks?.onPreExecution?.(ctx) ?? {};
435
- },
436
- onPostToolUse: async (ctx): Promise<void> => {
437
- const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
438
- await hooks?.onPostExecution?.(ctx);
439
- },
440
- onPostToolUseFailure: async (
441
- ctx
442
- ): Promise<PostToolUseFailureHookResult> => {
443
- const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
444
- return hooks?.onExecutionFailure?.(ctx) ?? {};
445
- },
446
- } satisfies ToolHooks,
447
- }),
448
- });
428
+ const resolveSubagentName = (args: unknown): string =>
429
+ (args as SubagentArgs).subagent;
430
+
431
+ toolMap.set("Subagent", {
432
+ ...createSubagentTool(enabledSubagents),
433
+ handler: createSubagentHandler(enabledSubagents),
434
+ ...(subagentHooksMap.size > 0 && {
435
+ hooks: {
436
+ onPreToolUse: async (ctx): Promise<PreToolUseHookResult> => {
437
+ const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
438
+ return hooks?.onPreExecution?.(ctx) ?? {};
439
+ },
440
+ onPostToolUse: async (ctx): Promise<void> => {
441
+ const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
442
+ await hooks?.onPostExecution?.(ctx);
443
+ },
444
+ onPostToolUseFailure: async (
445
+ ctx
446
+ ): Promise<PostToolUseFailureHookResult> => {
447
+ const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
448
+ return hooks?.onExecutionFailure?.(ctx) ?? {};
449
+ },
450
+ } satisfies ToolHooks,
451
+ }),
452
+ });
453
+ }
449
454
  }
450
455
 
451
456
  async function processToolCall(
@@ -837,7 +842,11 @@ export function withAutoAppend<
837
842
  });
838
843
 
839
844
  // Return with empty toolResponse to keep the Temporal payload small
840
- return { toolResponse: "", data: response.data, resultAppended: true };
845
+ return {
846
+ toolResponse: "Response appended via withAutoAppend",
847
+ data: response.data,
848
+ resultAppended: true,
849
+ };
841
850
  };
842
851
  }
843
852
 
package/src/lib/types.ts CHANGED
@@ -77,6 +77,8 @@ export interface ThreadOps {
77
77
  ): Promise<void>;
78
78
  /** Append a tool result to the thread */
79
79
  appendToolResult(config: ToolResultConfig): Promise<void>;
80
+ /** Append a system message to the thread */
81
+ appendSystemMessage(threadId: string, content: string): Promise<void>;
80
82
  }
81
83
 
82
84
  /**
@@ -85,12 +87,16 @@ export interface ThreadOps {
85
87
  export interface ZeitlichAgentConfig<T extends ToolMap, M = StoredMessage> {
86
88
  threadId: string;
87
89
  agentName: string;
90
+ /** Description, used for sub agents */
91
+ description?: string;
92
+ systemPrompt?: string;
88
93
  metadata?: Record<string, unknown>;
94
+ appendSystemPrompt?: boolean;
89
95
  maxTurns?: number;
90
96
  /** Workflow-specific runAgent activity (with tools pre-bound) */
91
97
  runAgent: RunAgentActivity<M>;
92
98
  /** Thread operations (initialize, append messages, parse tool calls) */
93
- threadOps: ThreadOps;
99
+ threadOps?: ThreadOps;
94
100
  /** Tool router for processing tool calls (optional if agent has no tools) */
95
101
  tools?: T;
96
102
  /** Subagent configurations */
@@ -161,9 +167,11 @@ export type InferSubagentResult<T extends SubagentConfig> =
161
167
  */
162
168
  export interface SubagentConfig<TResult extends z.ZodType = z.ZodType> {
163
169
  /** Identifier used in Task tool's subagent parameter */
164
- name: string;
170
+ agentName: string;
165
171
  /** Description shown to the parent agent explaining what this subagent does */
166
172
  description: string;
173
+ /** Whether this subagent is available (default: true). Disabled subagents are excluded from the Subagent tool. */
174
+ enabled?: boolean;
167
175
  /** Temporal workflow function or type name (used with executeChild) */
168
176
  workflow: string | Workflow;
169
177
  /** Optional task queue - defaults to parent's queue if not specified */
@@ -26,21 +26,20 @@ import type { SubagentArgs } from "./tool";
26
26
  export function createSubagentHandler<
27
27
  const T extends readonly SubagentConfig[],
28
28
  >(subagents: [...T]) {
29
- const { workflowId: parentWorkflowId, taskQueue: parentTaskQueue } =
30
- workflowInfo();
29
+ const { taskQueue: parentTaskQueue } = workflowInfo();
31
30
 
32
31
  return async (
33
32
  args: SubagentArgs
34
33
  ): Promise<ToolHandlerResponse<InferSubagentResult<T[number]> | null>> => {
35
- const config = subagents.find((s) => s.name === args.subagent);
34
+ const config = subagents.find((s) => s.agentName === args.subagent);
36
35
 
37
36
  if (!config) {
38
37
  throw new Error(
39
- `Unknown subagent: ${args.subagent}. Available: ${subagents.map((s) => s.name).join(", ")}`
38
+ `Unknown subagent: ${args.subagent}. Available: ${subagents.map((s) => s.agentName).join(", ")}`
40
39
  );
41
40
  }
42
41
 
43
- const childWorkflowId = `${parentWorkflowId}-${args.subagent}-${uuid4()}`;
42
+ const childWorkflowId = `${args.subagent}-${uuid4()}`;
44
43
 
45
44
  // Execute the child workflow
46
45
  const input: SubagentInput = {
@@ -8,7 +8,7 @@ const SUBAGENT_TOOL = "Subagent" as const;
8
8
  */
9
9
  function buildSubagentDescription(subagents: SubagentConfig[]): string {
10
10
  const subagentList = subagents
11
- .map((s) => `- **${s.name}**: ${s.description}`)
11
+ .map((s) => `- **${s.agentName}**: ${s.description}`)
12
12
  .join("\n");
13
13
 
14
14
  return `Launch a new agent to handle complex tasks autonomously.
@@ -42,7 +42,7 @@ Usage notes:
42
42
  * @example
43
43
  * const subagentTool = createSubagentTool([
44
44
  * {
45
- * name: "researcher",
45
+ * agentName: "researcher",
46
46
  * description: "Researches topics and gathers information",
47
47
  * workflow: "researcherWorkflow",
48
48
  * resultSchema: z.object({ findings: z.string() }),
@@ -64,7 +64,7 @@ export function createSubagentTool<T extends SubagentConfig[]>(
64
64
  throw new Error("createTaskTool requires at least one subagent");
65
65
  }
66
66
 
67
- const names = subagents.map((s) => s.name);
67
+ const names = subagents.map((s) => s.agentName);
68
68
 
69
69
  return {
70
70
  name: SUBAGENT_TOOL,
package/src/workflow.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Workflow-safe exports for use in Temporal workflow code.
3
3
  *
4
- * Import from '@bead-ai/zeitlich/workflow' in workflow files.
4
+ * Import from 'zeitlich/workflow' in workflow files.
5
5
  * These exports have no external dependencies (no Redis, no LangChain).
6
6
  *
7
7
  * @example
@@ -11,7 +11,7 @@
11
11
  * createSession,
12
12
  * createAgentStateManager,
13
13
  * createToolRouter,
14
- * } from '@bead-ai/zeitlich/workflow';
14
+ * } from 'zeitlich/workflow';
15
15
  * ```
16
16
  */
17
17