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.
Files changed (53) hide show
  1. package/README.md +313 -126
  2. package/dist/adapters/langchain/index.cjs +270 -0
  3. package/dist/adapters/langchain/index.cjs.map +1 -0
  4. package/dist/adapters/langchain/index.d.cts +132 -0
  5. package/dist/adapters/langchain/index.d.ts +132 -0
  6. package/dist/adapters/langchain/index.js +265 -0
  7. package/dist/adapters/langchain/index.js.map +1 -0
  8. package/dist/index.cjs +406 -301
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +88 -45
  11. package/dist/index.d.ts +88 -45
  12. package/dist/index.js +375 -274
  13. package/dist/index.js.map +1 -1
  14. package/dist/{workflow-C2ShwjC7.d.cts → model-invoker-C5-N-5TC.d.cts} +92 -435
  15. package/dist/{workflow-C2ShwjC7.d.ts → model-invoker-C5-N-5TC.d.ts} +92 -435
  16. package/dist/thread-manager-qc0g5Rvd.d.cts +39 -0
  17. package/dist/thread-manager-qc0g5Rvd.d.ts +39 -0
  18. package/dist/workflow.cjs +294 -126
  19. package/dist/workflow.cjs.map +1 -1
  20. package/dist/workflow.d.cts +459 -5
  21. package/dist/workflow.d.ts +459 -5
  22. package/dist/workflow.js +266 -103
  23. package/dist/workflow.js.map +1 -1
  24. package/package.json +30 -15
  25. package/src/adapters/langchain/activities.ts +120 -0
  26. package/src/adapters/langchain/index.ts +38 -0
  27. package/src/adapters/langchain/model-invoker.ts +102 -0
  28. package/src/adapters/langchain/thread-manager.ts +142 -0
  29. package/src/index.ts +26 -22
  30. package/src/lib/fs.ts +25 -0
  31. package/src/lib/model-invoker.ts +14 -74
  32. package/src/lib/session.ts +60 -23
  33. package/src/lib/skills/fs-provider.ts +84 -0
  34. package/src/lib/skills/index.ts +3 -0
  35. package/src/lib/skills/parse.ts +117 -0
  36. package/src/lib/skills/types.ts +41 -0
  37. package/src/lib/state-manager.ts +65 -31
  38. package/src/lib/thread-id.ts +25 -0
  39. package/src/lib/thread-manager.ts +63 -128
  40. package/src/lib/tool-router.ts +33 -23
  41. package/src/lib/types.ts +48 -15
  42. package/src/lib/workflow-helpers.ts +50 -0
  43. package/src/tools/ask-user-question/handler.ts +25 -1
  44. package/src/tools/bash/handler.ts +13 -0
  45. package/src/tools/read-skill/handler.ts +31 -0
  46. package/src/tools/read-skill/tool.ts +47 -0
  47. package/src/tools/subagent/handler.ts +37 -9
  48. package/src/tools/subagent/tool.ts +38 -34
  49. package/src/tools/task-create/tool.ts +1 -1
  50. package/src/workflow.ts +39 -11
  51. package/tsup.config.ts +1 -0
  52. package/src/activities.ts +0 -91
  53. package/src/plugin.ts +0 -28
@@ -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
+ };
@@ -1,11 +1,14 @@
1
- import { executeChild, workflowInfo, uuid4 } from "@temporalio/workflow";
1
+ import { executeChild, workflowInfo } from "@temporalio/workflow";
2
+ import { getShortId } from "../../lib/thread-id";
2
3
  import type { ToolHandlerResponse } from "../../lib/tool-router";
4
+ import type { ToolMessageContent } from "../../lib/types";
3
5
  import type {
4
6
  InferSubagentResult,
5
7
  SubagentConfig,
6
8
  SubagentInput,
7
9
  } from "../../lib/types";
8
10
  import type { SubagentArgs } from "./tool";
11
+ import type { z } from "zod";
9
12
 
10
13
  /**
11
14
  * Creates a Subagent tool handler that spawns child workflows for configured subagents.
@@ -39,33 +42,58 @@ export function createSubagentHandler<
39
42
  );
40
43
  }
41
44
 
42
- const childWorkflowId = `${args.subagent}-${uuid4()}`;
45
+ const childWorkflowId = `${args.subagent}-${getShortId()}`;
43
46
 
44
- // Execute the child workflow
45
47
  const input: SubagentInput = {
46
48
  prompt: args.prompt,
47
49
  ...(config.context && { context: config.context }),
50
+ ...(args.threadId &&
51
+ config.allowThreadContinuation && { threadId: args.threadId }),
48
52
  };
49
53
 
50
54
  const childOpts = {
51
55
  workflowId: childWorkflowId,
52
- args: [input],
56
+ args: [input] as const,
53
57
  taskQueue: config.taskQueue ?? parentTaskQueue,
54
58
  };
55
59
 
56
- const { toolResponse, data, usage } =
60
+ const { toolResponse, data, usage, threadId: childThreadId } =
57
61
  typeof config.workflow === "string"
58
62
  ? await executeChild(config.workflow, childOpts)
59
63
  : await executeChild(config.workflow, childOpts);
60
64
 
65
+ if (!toolResponse) {
66
+ return {
67
+ toolResponse: "Subagent workflow returned no response",
68
+ data: null,
69
+ ...(usage && { usage }),
70
+ };
71
+ }
72
+
61
73
  // Validate result if schema provided, otherwise pass through as-is
62
74
  const validated = (
63
- config.resultSchema ? config.resultSchema.parse(data) : null
64
- ) as InferSubagentResult<T[number]> | null;
75
+ config.resultSchema ? config.resultSchema.safeParse(data) : null
76
+ ) as z.ZodSafeParseResult<InferSubagentResult<T[number]>> | null;
77
+
78
+ if (validated && !validated.success) {
79
+ return {
80
+ toolResponse: `Subagent workflow returned invalid data: ${validated.error.message}`,
81
+ data: null,
82
+ ...(usage && { usage }),
83
+ };
84
+ }
85
+
86
+ let finalToolResponse: ToolMessageContent = toolResponse;
87
+ if (config.allowThreadContinuation && childThreadId) {
88
+ finalToolResponse =
89
+ typeof toolResponse === "string"
90
+ ? `${toolResponse}\n\n[Thread ID: ${childThreadId}]`
91
+ : toolResponse;
92
+ }
65
93
 
66
94
  return {
67
- toolResponse,
68
- data: validated,
95
+ toolResponse: finalToolResponse,
96
+ data: validated ? validated.data : data,
69
97
  ...(usage && { usage }),
70
98
  };
71
99
  };
@@ -8,29 +8,19 @@ export const SUBAGENT_TOOL_NAME = "Subagent" as const;
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) => {
12
+ const continuation = s.allowThreadContinuation
13
+ ? "\n*(Supports thread continuation — pass a threadId to resume a previous conversation)*"
14
+ : "";
15
+ return `## ${s.agentName}\n${s.description}${continuation}`;
16
+ })
17
+ .join("\n\n");
13
18
 
14
- return `Launch a new agent to handle complex tasks autonomously.
15
-
16
- The ${SUBAGENT_TOOL_NAME} 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:
19
+ 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
20
 
21
+ # Available subagents:
20
22
  ${subagentList}
21
-
22
- When using the ${SUBAGENT_TOOL_NAME} 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.`;
23
+ `;
34
24
  }
35
25
 
36
26
  /**
@@ -52,30 +42,43 @@ Usage notes:
52
42
  export function createSubagentTool<T extends SubagentConfig[]>(
53
43
  subagents: T
54
44
  ): {
55
- name: string;
56
- description: string;
57
- schema: z.ZodObject<{
58
- subagent: z.ZodEnum<Record<string, string>>;
59
- description: z.ZodString;
60
- prompt: z.ZodString;
61
- }>;
45
+ readonly name: typeof SUBAGENT_TOOL_NAME;
46
+ readonly description: string;
47
+ readonly schema: z.ZodObject<z.ZodRawShape>;
62
48
  } {
63
49
  if (subagents.length === 0) {
64
50
  throw new Error("createTaskTool requires at least one subagent");
65
51
  }
66
52
 
67
53
  const names = subagents.map((s) => s.agentName);
54
+ const hasThreadContinuation = subagents.some(
55
+ (s) => s.allowThreadContinuation
56
+ );
57
+
58
+ const baseFields = {
59
+ subagent: z.enum(names).describe("The type of subagent to launch"),
60
+ description: z
61
+ .string()
62
+ .describe("A short (3-5 word) description of the task"),
63
+ prompt: z.string().describe("The task for the agent to perform"),
64
+ };
65
+
66
+ const schema = hasThreadContinuation
67
+ ? z.object({
68
+ ...baseFields,
69
+ threadId: z
70
+ .string()
71
+ .nullable()
72
+ .describe(
73
+ "Thread ID to continue an existing conversation, or null to start a new one"
74
+ ),
75
+ })
76
+ : z.object(baseFields);
68
77
 
69
78
  return {
70
79
  name: SUBAGENT_TOOL_NAME,
71
80
  description: buildSubagentDescription(subagents),
72
- schema: z.object({
73
- subagent: z.enum(names).describe("The type of subagent to launch"),
74
- description: z
75
- .string()
76
- .describe("A short (3-5 word) description of the task"),
77
- prompt: z.string().describe("The task for the agent to perform"),
78
- }),
81
+ schema,
79
82
  } as const;
80
83
  }
81
84
 
@@ -86,4 +89,5 @@ export type SubagentArgs = {
86
89
  subagent: string;
87
90
  description: string;
88
91
  prompt: string;
92
+ threadId?: string;
89
93
  };
@@ -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
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 '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
@@ -10,20 +10,23 @@
10
10
  * import {
11
11
  * createSession,
12
12
  * createAgentStateManager,
13
- * createToolRouter,
13
+ * askUserQuestionTool,
14
+ * bashTool,
15
+ * defineTool,
16
+ * type SubagentWorkflow,
14
17
  * } from 'zeitlich/workflow';
15
18
  * ```
16
19
  */
17
20
 
18
21
  // Session
19
22
  export { createSession, proxyDefaultThreadOps } from "./lib/session";
23
+
24
+ // Thread utilities
25
+ export { getShortId } from "./lib/thread-id";
20
26
  export type { ZeitlichSession, SessionLifecycleHooks } from "./lib/session";
21
27
 
22
28
  // State management
23
- export {
24
- createAgentStateManager,
25
- AGENT_HANDLER_NAMES,
26
- } from "./lib/state-manager";
29
+ export { createAgentStateManager } from "./lib/state-manager";
27
30
  export type {
28
31
  AgentState,
29
32
  AgentStateManager,
@@ -62,23 +65,30 @@ export type {
62
65
  ToolCallResultUnion,
63
66
  InferToolResults,
64
67
  // Other
65
- ToolMessageContent,
66
68
  AppendToolResultFn,
67
69
  ProcessToolCallsContext,
68
70
  } from "./lib/tool-router";
69
71
 
70
72
  // Types
71
73
  export type {
74
+ // Message types (framework-agnostic)
75
+ ContentPart,
76
+ MessageContent,
77
+ ToolMessageContent,
78
+ TokenUsage,
79
+ // Agent types
72
80
  AgentStatus,
73
81
  BaseAgentState,
74
82
  AgentFile,
75
83
  AgentResponse,
76
84
  ThreadOps,
77
85
  AgentConfig,
86
+ SessionConfig,
78
87
  RunAgentConfig,
79
88
  RunAgentActivity,
80
89
  ToolResultConfig,
81
90
  SessionExitReason,
91
+ // Hook types
82
92
  PreToolUseHook,
83
93
  PreToolUseHookContext,
84
94
  PreToolUseHookResult,
@@ -87,26 +97,44 @@ export type {
87
97
  PostToolUseFailureHook,
88
98
  PostToolUseFailureHookContext,
89
99
  PostToolUseFailureHookResult,
100
+ PreHumanMessageAppendHook,
101
+ PreHumanMessageAppendHookContext,
102
+ PostHumanMessageAppendHook,
103
+ PostHumanMessageAppendHookContext,
104
+ Hooks,
90
105
  ToolHooks,
91
106
  SessionStartHook,
92
107
  SessionStartHookContext,
93
108
  SessionEndHook,
94
109
  SessionEndHookContext,
110
+ // Subagent types
95
111
  SubagentConfig,
96
112
  SubagentHooks,
97
113
  SubagentInput,
114
+ // Task types
98
115
  TaskStatus,
99
116
  WorkflowTask,
100
117
  } from "./lib/types";
101
- export { isTerminalStatus } from "./lib/types";
118
+ export {
119
+ isTerminalStatus,
120
+ agentQueryName,
121
+ agentStateChangeUpdateName,
122
+ } from "./lib/types";
123
+
124
+ // Model invoker contract
125
+ export type { ModelInvoker, ModelInvokerConfig } from "./lib/model-invoker";
102
126
 
103
127
  // Subagent support
104
128
  export { createSubagentTool } from "./tools/subagent/tool";
105
129
  export type { SubagentArgs } from "./tools/subagent/tool";
130
+ export type { SubagentWorkflow } from "./lib/types";
106
131
 
107
- // Activity type interfaces (types only, no runtime code)
108
- // These are safe to import in workflows for typing proxyActivities
109
- export type { ZeitlichSharedActivities } from "./activities";
132
+ // Skills (types + workflow-safe utilities)
133
+ export type { Skill, SkillMetadata, SkillProvider } from "./lib/skills/types";
134
+ export { parseSkillFile } from "./lib/skills/parse";
135
+ export { createReadSkillTool } from "./tools/read-skill/tool";
136
+ export { createReadSkillHandler } from "./tools/read-skill/handler";
137
+ export type { ReadSkillArgs } from "./tools/read-skill/tool";
110
138
 
111
139
  // Tool definitions (schemas only - no handlers)
112
140
  export { globTool } from "./tools/glob/tool";
package/tsup.config.ts CHANGED
@@ -4,6 +4,7 @@ export default defineConfig({
4
4
  entry: {
5
5
  index: "src/index.ts",
6
6
  workflow: "src/workflow.ts",
7
+ "adapters/langchain/index": "src/adapters/langchain/index.ts",
7
8
  },
8
9
  format: ["esm", "cjs"],
9
10
  dts: true,
package/src/activities.ts DELETED
@@ -1,91 +0,0 @@
1
- import type Redis from "ioredis";
2
- import { createThreadManager } from "./lib/thread-manager";
3
- import type { ToolResultConfig } from "./lib/types";
4
- import {
5
- type MessageContent,
6
- type StoredMessage,
7
- } from "@langchain/core/messages";
8
- /**
9
- * Shared Zeitlich activities - thread management and message handling
10
- * Note: runAgent is workflow-specific and should be created per-workflow
11
- */
12
- export interface ZeitlichSharedActivities {
13
- /**
14
- * Append a tool result to the thread.
15
- * Handles JSON serialization and optional cache points.
16
- */
17
- appendToolResult(config: ToolResultConfig): Promise<void>;
18
-
19
- /**
20
- * Initialize an empty thread.
21
- */
22
- initializeThread(threadId: string): Promise<void>;
23
-
24
- /**
25
- * Append messages to a thread.
26
- */
27
- appendThreadMessages(
28
- threadId: string,
29
- messages: StoredMessage[]
30
- ): Promise<void>;
31
-
32
- /**
33
- * Append a human message to a thread.
34
- */
35
- appendHumanMessage(
36
- threadId: string,
37
- content: string | MessageContent
38
- ): Promise<void>;
39
-
40
- /**
41
- * Append a system message to a thread.
42
- */
43
- appendSystemMessage(threadId: string, content: string): Promise<void>;
44
- }
45
-
46
- /**
47
- * Creates shared Temporal activities for thread management
48
- *
49
- * @returns An object containing the shared activity functions
50
- *
51
- * @experimental The Zeitlich integration is an experimental feature; APIs may change without notice.
52
- */
53
- export function createSharedActivities(redis: Redis): ZeitlichSharedActivities {
54
- return {
55
- async appendToolResult(config: ToolResultConfig): Promise<void> {
56
- const { threadId, toolCallId, content } = config;
57
- const thread = createThreadManager({ redis, threadId });
58
-
59
- await thread.appendToolMessage(content, toolCallId);
60
- },
61
-
62
- async initializeThread(threadId: string): Promise<void> {
63
- const thread = createThreadManager({ redis, threadId });
64
- await thread.initialize();
65
- },
66
-
67
- async appendThreadMessages(
68
- threadId: string,
69
- messages: StoredMessage[]
70
- ): Promise<void> {
71
- const thread = createThreadManager({ redis, threadId });
72
- await thread.append(messages);
73
- },
74
-
75
- async appendHumanMessage(
76
- threadId: string,
77
- content: string | MessageContent
78
- ): Promise<void> {
79
- const thread = createThreadManager({ redis, threadId });
80
- await thread.appendHumanMessage(content);
81
- },
82
-
83
- async appendSystemMessage(
84
- threadId: string,
85
- content: string
86
- ): Promise<void> {
87
- const thread = createThreadManager({ redis, threadId });
88
- await thread.appendSystemMessage(content);
89
- },
90
- };
91
- }
package/src/plugin.ts DELETED
@@ -1,28 +0,0 @@
1
- import { SimplePlugin } from "@temporalio/plugin";
2
- import { createSharedActivities } from "./activities";
3
- import type Redis from "ioredis";
4
-
5
- /**
6
- * Options for the Zeitlich plugin
7
- *
8
- * @experimental The Zeitlich plugin is an experimental feature; APIs may change without notice.
9
- */
10
- export interface ZeitlichPluginOptions {
11
- redis: Redis;
12
- }
13
-
14
- /**
15
- * A Temporal plugin that integrates Zeitlich for use in workflows.
16
- * This plugin creates shared activities for thread management.
17
- * Workflow-specific activities (like runAgent) should be created separately.
18
- *
19
- * @experimental The Zeitlich plugin is an experimental feature; APIs may change without notice.
20
- */
21
- export class ZeitlichPlugin extends SimplePlugin {
22
- constructor(options: ZeitlichPluginOptions) {
23
- super({
24
- name: "ZeitlichPlugin",
25
- activities: createSharedActivities(options.redis),
26
- });
27
- }
28
- }