zeitlich 0.2.8 → 0.2.11

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.
@@ -10,11 +10,18 @@ import type {
10
10
  ToolHooks,
11
11
  ToolResultConfig,
12
12
  } from "./types";
13
+ import type { Skill } from "./skills/types";
13
14
  import type { SubagentArgs } from "../tools/subagent/tool";
14
15
 
15
16
  import type { z } from "zod";
16
- import { createSubagentTool } from "../tools/subagent/tool";
17
+ import { createSubagentTool, SUBAGENT_TOOL_NAME } from "../tools/subagent/tool";
17
18
  import { createSubagentHandler } from "../tools/subagent/handler";
19
+ import {
20
+ createReadSkillTool,
21
+ READ_SKILL_TOOL_NAME,
22
+ } from "../tools/read-skill/tool";
23
+ import { createReadSkillHandler } from "../tools/read-skill/handler";
24
+ import { ApplicationFailure } from "@temporalio/workflow";
18
25
 
19
26
  export type { ToolMessageContent };
20
27
 
@@ -54,7 +61,7 @@ export interface ToolWithHandler<
54
61
  strict?: boolean;
55
62
  max_uses?: number;
56
63
  /** Whether this tool is available to the agent (default: true). Disabled tools are excluded from definitions and rejected at parse time. */
57
- enabled?: () => boolean;
64
+ enabled?: boolean;
58
65
  /** Per-tool lifecycle hooks (run in addition to global hooks) */
59
66
  hooks?: ToolHooks<z.infer<TSchema>, TResult>;
60
67
  }
@@ -77,7 +84,7 @@ export type ToolMap = Record<
77
84
  handler: ToolHandler<any, any, any>;
78
85
  strict?: boolean;
79
86
  max_uses?: number;
80
- enabled?: () => boolean;
87
+ enabled?: boolean;
81
88
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
82
89
  hooks?: ToolHooks<any, any>;
83
90
  }
@@ -250,6 +257,8 @@ export interface ToolRouterOptions<T extends ToolMap> {
250
257
  hooks?: Hooks<T, ToolCallResultUnion<InferToolResults<T>>>;
251
258
  /** Subagent configurations */
252
259
  subagents?: SubagentConfig[];
260
+ /** Skills available to the agent (auto-adds ReadSkill tool when non-empty) */
261
+ skills?: Skill[];
253
262
  }
254
263
 
255
264
  /**
@@ -416,8 +425,8 @@ export function createToolRouter<T extends ToolMap>(
416
425
  }
417
426
 
418
427
  /** Check if a tool is enabled (defaults to true when not specified) */
419
- const isEnabled = (tool: ToolMap[string]): boolean =>
420
- tool.enabled?.() ?? true;
428
+ const isEnabled = (tool: ToolMap[string] | SubagentConfig): boolean =>
429
+ tool.enabled ?? true;
421
430
 
422
431
  if (options.subagents) {
423
432
  if (options.subagents.length > 0) {
@@ -430,7 +439,7 @@ export function createToolRouter<T extends ToolMap>(
430
439
  const resolveSubagentName = (args: unknown): string =>
431
440
  (args as SubagentArgs).subagent;
432
441
 
433
- toolMap.set("Subagent", {
442
+ toolMap.set(SUBAGENT_TOOL_NAME, {
434
443
  ...createSubagentTool(options.subagents),
435
444
  handler: createSubagentHandler(options.subagents),
436
445
  ...(subagentHooksMap.size > 0 && {
@@ -455,6 +464,13 @@ export function createToolRouter<T extends ToolMap>(
455
464
  }
456
465
  }
457
466
 
467
+ if (options.skills && options.skills.length > 0) {
468
+ toolMap.set(READ_SKILL_TOOL_NAME, {
469
+ ...createReadSkillTool(options.skills),
470
+ handler: createReadSkillHandler(options.skills),
471
+ });
472
+ }
473
+
458
474
  async function processToolCall(
459
475
  toolCall: ParsedToolCallUnion<T>,
460
476
  turn: number,
@@ -577,7 +593,9 @@ export function createToolRouter<T extends ToolMap>(
577
593
  }
578
594
 
579
595
  if (!recovered) {
580
- throw error;
596
+ throw ApplicationFailure.fromError(error, {
597
+ nonRetryable: true,
598
+ });
581
599
  }
582
600
  }
583
601
 
@@ -657,15 +675,30 @@ export function createToolRouter<T extends ToolMap>(
657
675
  },
658
676
 
659
677
  getToolDefinitions(): ToolDefinition[] {
660
- return Array.from(toolMap)
661
- .filter(([, tool]) => isEnabled(tool))
662
- .map(([name, tool]) => ({
663
- name,
664
- description: tool.description,
665
- schema: tool.schema,
666
- strict: tool.strict,
667
- max_uses: tool.max_uses,
668
- }));
678
+ const activeSubagents =
679
+ options.subagents?.filter((subagent) => isEnabled(subagent)) ?? [];
680
+ const activeSkills = options.skills ?? [];
681
+
682
+ return [
683
+ ...Array.from(toolMap)
684
+ .filter(
685
+ ([, tool]) =>
686
+ isEnabled(tool) &&
687
+ tool.name !== SUBAGENT_TOOL_NAME &&
688
+ tool.name !== READ_SKILL_TOOL_NAME
689
+ )
690
+ .map(([name, tool]) => ({
691
+ name,
692
+ description: tool.description,
693
+ schema: tool.schema,
694
+ strict: tool.strict,
695
+ max_uses: tool.max_uses,
696
+ })),
697
+ ...(activeSubagents.length > 0
698
+ ? [createSubagentTool(activeSubagents)]
699
+ : []),
700
+ ...(activeSkills.length > 0 ? [createReadSkillTool(activeSkills)] : []),
701
+ ];
669
702
  },
670
703
 
671
704
  // --- Methods for processing tool calls ---
package/src/lib/types.ts CHANGED
@@ -4,12 +4,13 @@ import type {
4
4
  ParsedToolCallUnion,
5
5
  RawToolCall,
6
6
  ToolCallResultUnion,
7
+ ToolHandlerResponse,
7
8
  ToolMap,
8
9
  } from "./tool-router";
10
+ import type { Skill } from "./skills/types";
9
11
 
10
12
  import type { MessageContent, StoredMessage } from "@langchain/core/messages";
11
13
  import type { Duration } from "@temporalio/common";
12
- import type { Workflow } from "@temporalio/workflow";
13
14
  import type { z } from "zod";
14
15
 
15
16
  /**
@@ -31,7 +32,7 @@ export interface BaseAgentState {
31
32
  version: number;
32
33
  turns: number;
33
34
  tasks: Map<string, WorkflowTask>;
34
- systemPrompt: string;
35
+ systemPrompt?: string;
35
36
  totalInputTokens: number;
36
37
  totalOutputTokens: number;
37
38
  cachedWriteTokens: number;
@@ -98,8 +99,6 @@ export interface AgentConfig {
98
99
  agentName: string;
99
100
  /** Description, used for sub agents */
100
101
  description?: string;
101
- /** The system prompt to append to the thread */
102
- systemPrompt?: string;
103
102
  }
104
103
 
105
104
  /**
@@ -122,6 +121,8 @@ export interface SessionConfig<T extends ToolMap, M = StoredMessage> {
122
121
  tools?: T;
123
122
  /** Subagent configurations */
124
123
  subagents?: SubagentConfig[];
124
+ /** Skills available to this agent (metadata + instructions, loaded activity-side) */
125
+ skills?: Skill[];
125
126
  /** Session lifecycle hooks */
126
127
  hooks?: Hooks<T, ToolCallResultUnion<InferToolResults<T>>>;
127
128
  /** Whether to process tools in parallel */
@@ -181,6 +182,10 @@ export interface ToolResultConfig {
181
182
  // Subagent Configuration
182
183
  // ============================================================================
183
184
 
185
+ export type SubagentWorkflow<TResult extends z.ZodType = z.ZodType> = (
186
+ input: SubagentInput
187
+ ) => Promise<ToolHandlerResponse<TResult | null>>;
188
+
184
189
  /** Infer the z.infer'd result type from a SubagentConfig, or null if no schema */
185
190
  export type InferSubagentResult<T extends SubagentConfig> =
186
191
  T extends SubagentConfig<infer S> ? z.infer<S> : null;
@@ -196,9 +201,9 @@ export interface SubagentConfig<TResult extends z.ZodType = z.ZodType> {
196
201
  /** Description shown to the parent agent explaining what this subagent does */
197
202
  description: string;
198
203
  /** Whether this subagent is available (default: true). Disabled subagents are excluded from the Subagent tool. */
199
- enabled?: () => boolean;
204
+ enabled?: boolean;
200
205
  /** Temporal workflow function or type name (used with executeChild) */
201
- workflow: string | Workflow;
206
+ workflow: string | SubagentWorkflow<TResult>;
202
207
  /** Optional task queue - defaults to parent's queue if not specified */
203
208
  taskQueue?: string;
204
209
  /** Optional Zod schema to validate the child workflow's result. If omitted, result is passed through as-is. */
@@ -499,6 +504,18 @@ export interface Hooks<T extends ToolMap, TResult = unknown> {
499
504
  onSessionEnd?: SessionEndHook;
500
505
  }
501
506
 
507
+ // ============================================================================
508
+ // Agent Query/Update Name Helpers
509
+ // ============================================================================
510
+
511
+ /** Derives the query name for an agent's state (usable in both workflow and activity code) */
512
+ export const agentQueryName = (agentName: string) =>
513
+ `get${agentName}State` as const;
514
+
515
+ /** Derives the update name for waiting on an agent's state change */
516
+ export const agentStateChangeUpdateName = (agentName: string) =>
517
+ `waitFor${agentName}StateChange` as const;
518
+
502
519
  /**
503
520
  * Helper to check if status is terminal
504
521
  */
@@ -1,11 +1,11 @@
1
- import type { ToolHandler } from "../../lib/tool-router";
1
+ import type { ActivityToolHandler } from "../../lib/tool-router";
2
2
  import type { AskUserQuestionArgs } from "./tool";
3
3
 
4
4
  /**
5
5
  * Creates handler for user interaction tool - creates AI messages for display.
6
6
  */
7
7
  export const createAskUserQuestionHandler =
8
- (): ToolHandler<
8
+ (): ActivityToolHandler<
9
9
  AskUserQuestionArgs,
10
10
  {
11
11
  questions: {
@@ -16,7 +16,7 @@ export const createAskUserQuestionHandler =
16
16
  }[];
17
17
  }
18
18
  > =>
19
- (args) => {
19
+ async (args) => {
20
20
  return {
21
21
  toolResponse: "Question submitted",
22
22
  data: { questions: args.questions },
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import type { ToolDefinition } from "../../lib/tool-router";
3
3
 
4
- export const readTool = {
4
+ export const readFileTool = {
5
5
  name: "FileRead" as const,
6
6
  description: `Read file contents with optional pagination.
7
7
 
@@ -31,4 +31,4 @@ The tool returns the file content in an appropriate format:
31
31
  strict: true,
32
32
  } satisfies ToolDefinition;
33
33
 
34
- export type FileReadArgs = z.infer<typeof readTool.schema>;
34
+ export type FileReadArgs = z.infer<typeof readFileTool.schema>;
@@ -0,0 +1,31 @@
1
+ import type { Skill } from "../../lib/skills/types";
2
+ import type { ToolHandlerResponse } from "../../lib/tool-router";
3
+ import type { ReadSkillArgs } from "./tool";
4
+
5
+ /**
6
+ * Creates a ReadSkill handler that looks up skills from an in-memory array.
7
+ * Runs directly in the workflow (like task tools) — no activity needed.
8
+ */
9
+ export function createReadSkillHandler(
10
+ skills: Skill[]
11
+ ): (args: ReadSkillArgs) => ToolHandlerResponse<null> {
12
+ const skillMap = new Map(skills.map((s) => [s.name, s]));
13
+
14
+ return (args: ReadSkillArgs): ToolHandlerResponse<null> => {
15
+ const skill = skillMap.get(args.skill_name);
16
+
17
+ if (!skill) {
18
+ return {
19
+ toolResponse: JSON.stringify({
20
+ error: `Skill "${args.skill_name}" not found`,
21
+ }),
22
+ data: null,
23
+ };
24
+ }
25
+
26
+ return {
27
+ toolResponse: skill.instructions,
28
+ data: null,
29
+ };
30
+ };
31
+ }
@@ -0,0 +1,47 @@
1
+ import z from "zod";
2
+ import type { Skill } from "../../lib/skills/types";
3
+
4
+ export const READ_SKILL_TOOL_NAME = "ReadSkill" as const;
5
+
6
+ function buildReadSkillDescription(skills: Skill[]): string {
7
+ const skillList = skills
8
+ .map((s) => `- **${s.name}**: ${s.description}`)
9
+ .join("\n");
10
+
11
+ return `Load the full instructions for a skill. Read the skill before following its instructions.
12
+
13
+ # Available skills:
14
+ ${skillList}
15
+ `;
16
+ }
17
+
18
+ /**
19
+ * Creates a ReadSkill tool configured with the available skills.
20
+ * The tool description embeds skill metadata so the agent discovers
21
+ * skills purely through the tool definition.
22
+ */
23
+ export function createReadSkillTool(skills: Skill[]): {
24
+ name: string;
25
+ description: string;
26
+ schema: z.ZodObject<{
27
+ skill_name: z.ZodEnum<Record<string, string>>;
28
+ }>;
29
+ } {
30
+ if (skills.length === 0) {
31
+ throw new Error("createReadSkillTool requires at least one skill");
32
+ }
33
+
34
+ const names = skills.map((s) => s.name);
35
+
36
+ return {
37
+ name: READ_SKILL_TOOL_NAME,
38
+ description: buildReadSkillDescription(skills),
39
+ schema: z.object({
40
+ skill_name: z.enum(names).describe("The name of the skill to load"),
41
+ }),
42
+ } as const;
43
+ }
44
+
45
+ export type ReadSkillArgs = {
46
+ skill_name: string;
47
+ };
@@ -6,6 +6,7 @@ import type {
6
6
  SubagentInput,
7
7
  } from "../../lib/types";
8
8
  import type { SubagentArgs } from "./tool";
9
+ import type { z } from "zod";
9
10
 
10
11
  /**
11
12
  * Creates a Subagent tool handler that spawns child workflows for configured subagents.
@@ -49,7 +50,7 @@ export function createSubagentHandler<
49
50
 
50
51
  const childOpts = {
51
52
  workflowId: childWorkflowId,
52
- args: [input],
53
+ args: [input] as const,
53
54
  taskQueue: config.taskQueue ?? parentTaskQueue,
54
55
  };
55
56
 
@@ -58,14 +59,30 @@ export function createSubagentHandler<
58
59
  ? await executeChild(config.workflow, childOpts)
59
60
  : await executeChild(config.workflow, childOpts);
60
61
 
62
+ if (!toolResponse) {
63
+ return {
64
+ toolResponse: "Subagent workflow returned no response",
65
+ data: null,
66
+ ...(usage && { usage }),
67
+ };
68
+ }
69
+
61
70
  // Validate result if schema provided, otherwise pass through as-is
62
71
  const validated = (
63
- config.resultSchema ? config.resultSchema.parse(data) : null
64
- ) as InferSubagentResult<T[number]> | null;
72
+ config.resultSchema ? config.resultSchema.safeParse(data) : null
73
+ ) as z.ZodSafeParseResult<InferSubagentResult<T[number]>> | null;
74
+
75
+ if (validated && !validated.success) {
76
+ return {
77
+ toolResponse: `Subagent workflow returned invalid data: ${validated.error.message}`,
78
+ data: null,
79
+ ...(usage && { usage }),
80
+ };
81
+ }
65
82
 
66
83
  return {
67
84
  toolResponse,
68
- data: validated,
85
+ data: validated ? validated.data : data,
69
86
  ...(usage && { usage }),
70
87
  };
71
88
  };
@@ -1,36 +1,21 @@
1
1
  import z from "zod";
2
2
  import type { SubagentConfig } from "../../lib/types";
3
3
 
4
- const SUBAGENT_TOOL = "Subagent" as const;
4
+ export const SUBAGENT_TOOL_NAME = "Subagent" as const;
5
5
 
6
6
  /**
7
7
  * Builds the tool description with available subagent information
8
8
  */
9
9
  function buildSubagentDescription(subagents: SubagentConfig[]): string {
10
10
  const subagentList = subagents
11
- .map((s) => `- **${s.agentName}**: ${s.description}`)
12
- .join("\n");
11
+ .map((s) => `## ${s.agentName}\n${s.description}`)
12
+ .join("\n\n");
13
13
 
14
- return `Launch a new agent to handle complex tasks autonomously.
15
-
16
- The ${SUBAGENT_TOOL} tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
17
-
18
- Available agent types:
14
+ return `The ${SUBAGENT_TOOL_NAME} tool launches specialized agents (subagents) that autonomously handle complex work. Each agent type has specific capabilities and tools available to it.
19
15
 
16
+ # Available subagents:
20
17
  ${subagentList}
21
-
22
- When using the ${SUBAGENT_TOOL} tool, you must specify a subagent parameter to select which agent type to use.
23
-
24
- Usage notes:
25
-
26
- - Always include a short description (3-5 words) summarizing what the agent will do
27
- - Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
28
- - When the agent is done, it will return a single message back to you.
29
- - Each invocation starts fresh - provide a detailed task description with all necessary context.
30
- - Provide clear, detailed prompts so the agent can work autonomously and return exactly the information you need.
31
- - The agent's outputs should generally be trusted
32
- - Clearly tell the agent what type of work you expect since it is not aware of the user's intent
33
- - 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.`;
18
+ `;
34
19
  }
35
20
 
36
21
  /**
@@ -67,7 +52,7 @@ export function createSubagentTool<T extends SubagentConfig[]>(
67
52
  const names = subagents.map((s) => s.agentName);
68
53
 
69
54
  return {
70
- name: SUBAGENT_TOOL,
55
+ name: SUBAGENT_TOOL_NAME,
71
56
  description: buildSubagentDescription(subagents),
72
57
  schema: z.object({
73
58
  subagent: z.enum(names).describe("The type of subagent to launch"),
@@ -3,7 +3,7 @@ import type { ToolDefinition } from "../../lib/tool-router";
3
3
 
4
4
  export const taskCreateTool = {
5
5
  name: "TaskCreate" as const,
6
- description: `Use this tool to create a structured task list for the control test. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
6
+ description: `Use this tool to create a structured task list. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
7
7
  It also helps the user understand the progress of the task and overall progress of their requests.
8
8
 
9
9
  ## When to Use This Tool
@@ -1,25 +1,24 @@
1
1
  import { z } from "zod";
2
2
  import type { ToolDefinition } from "../../lib/tool-router";
3
3
 
4
- export const writeTool = {
4
+ export const writeFileTool = {
5
5
  name: "FileWrite" as const,
6
6
  description: `Create or overwrite a file with new content.
7
7
 
8
8
  Usage:
9
- - Provide the absolute path to the file
10
9
  - The file will be created if it doesn't exist
11
10
  - If the file exists, it will be completely overwritten
12
11
 
13
12
  IMPORTANT:
14
13
  - You must read the file first (in this session) before writing to it
15
14
  - This is an atomic write operation - the entire file is replaced
16
- - Path must be absolute (e.g., "/docs/readme.md", not "docs/readme.md")
15
+ - Path must be relative to the root of the file system (e.g., "docs/readme.md", not "/docs/readme.md")
17
16
  `,
18
17
  schema: z.object({
19
- file_path: z.string().describe("The absolute path to the file to write"),
18
+ file_path: z.string().describe("The path to the file to write"),
20
19
  content: z.string().describe("The content to write to the file"),
21
20
  }),
22
21
  strict: true,
23
22
  } satisfies ToolDefinition;
24
23
 
25
- export type FileWriteArgs = z.infer<typeof writeTool.schema>;
24
+ export type FileWriteArgs = z.infer<typeof writeFileTool.schema>;
package/src/workflow.ts CHANGED
@@ -20,10 +20,7 @@ export { createSession, proxyDefaultThreadOps } from "./lib/session";
20
20
  export type { ZeitlichSession, SessionLifecycleHooks } from "./lib/session";
21
21
 
22
22
  // State management
23
- export {
24
- createAgentStateManager,
25
- AGENT_HANDLER_NAMES,
26
- } from "./lib/state-manager";
23
+ export { createAgentStateManager } from "./lib/state-manager";
27
24
  export type {
28
25
  AgentState,
29
26
  AgentStateManager,
@@ -98,11 +95,23 @@ export type {
98
95
  TaskStatus,
99
96
  WorkflowTask,
100
97
  } from "./lib/types";
101
- export { isTerminalStatus } from "./lib/types";
98
+ export {
99
+ isTerminalStatus,
100
+ agentQueryName,
101
+ agentStateChangeUpdateName,
102
+ } from "./lib/types";
102
103
 
103
104
  // Subagent support
104
105
  export { createSubagentTool } from "./tools/subagent/tool";
105
106
  export type { SubagentArgs } from "./tools/subagent/tool";
107
+ export type { SubagentWorkflow } from "./lib/types";
108
+
109
+ // Skills (types + workflow-safe utilities)
110
+ export type { Skill, SkillMetadata, SkillProvider } from "./lib/skills/types";
111
+ export { parseSkillFile } from "./lib/skills/parse";
112
+ export { createReadSkillTool } from "./tools/read-skill/tool";
113
+ export { createReadSkillHandler } from "./tools/read-skill/handler";
114
+ export type { ReadSkillArgs } from "./tools/read-skill/tool";
106
115
 
107
116
  // Activity type interfaces (types only, no runtime code)
108
117
  // These are safe to import in workflows for typing proxyActivities
@@ -113,9 +122,9 @@ export { globTool } from "./tools/glob/tool";
113
122
  export type { GlobArgs } from "./tools/glob/tool";
114
123
  export { grepTool } from "./tools/grep/tool";
115
124
  export type { GrepArgs } from "./tools/grep/tool";
116
- export { readTool } from "./tools/read-file/tool";
125
+ export { readFileTool } from "./tools/read-file/tool";
117
126
  export type { FileReadArgs } from "./tools/read-file/tool";
118
- export { writeTool } from "./tools/write-file/tool";
127
+ export { writeFileTool } from "./tools/write-file/tool";
119
128
  export type { FileWriteArgs } from "./tools/write-file/tool";
120
129
  export { editTool } from "./tools/edit/tool";
121
130
  export type { FileEditArgs } from "./tools/edit/tool";