UNPKG

workflow

Version:

Workflow SDK - Build durable, resilient, and observable workflows

895 lines (714 loc) • 25.1 kB
--- title: DurableAgent description: Deprecated DurableAgent API reference; use WorkflowAgent for new durable agents. type: reference summary: "Deprecated: use AI SDK's WorkflowAgent instead of DurableAgent." prerequisites: - /docs/ai related: - /docs/ai/defining-tools --- <Callout type="warn"> `DurableAgent` is deprecated. Use AI SDK's [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) for new durable agents. See the [migration guide](https://ai-sdk.dev/v7/docs/agents/workflow-agent#migrating-from-durableagent). </Callout> This reference is kept for existing applications that still import `DurableAgent` from `@workflow/ai/agent`. Do not use `DurableAgent` for new code. For current examples and implementation guidance, see AI SDK's [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) docs. For legacy code, the API surface below documents the existing `DurableAgent` exports. ## API signature ### Class <TSDoc definition={` import { DurableAgent } from "@workflow/ai/agent"; export default DurableAgent;`} /> ### DurableAgentOptions <TSDoc definition={` import type { DurableAgentOptions } from "@workflow/ai/agent"; export default DurableAgentOptions;`} /> ### DurableAgentStreamOptions <TSDoc definition={` import type { DurableAgentStreamOptions } from "@workflow/ai/agent"; export default DurableAgentStreamOptions;`} /> ### DurableAgentStreamResult The result returned from the `stream()` method: <TSDoc definition={` import type { DurableAgentStreamResult } from "@workflow/ai/agent"; export default DurableAgentStreamResult;`} /> ### GenerationSettings Settings that control model generation behavior. These can be set on the constructor or overridden per-stream call: <TSDoc definition={` import type { GenerationSettings } from "@workflow/ai/agent"; export default GenerationSettings;`} /> ### PrepareStepInfo Information passed to the `prepareStep` callback: <TSDoc definition={` import type { PrepareStepInfo } from "@workflow/ai/agent"; export default PrepareStepInfo;`} /> ### PrepareStepResult Return type from the `prepareStep` callback: <TSDoc definition={` import type { PrepareStepResult } from "@workflow/ai/agent"; export default PrepareStepResult;`} /> ### TelemetrySettings Configuration for observability and telemetry: <TSDoc definition={` import type { TelemetrySettings } from "@workflow/ai/agent"; export default TelemetrySettings;`} /> ### Callbacks #### StreamTextOnFinishCallback Called when streaming completes: <TSDoc definition={` import type { StreamTextOnFinishCallback } from "@workflow/ai/agent"; export default StreamTextOnFinishCallback;`} /> #### StreamTextOnErrorCallback Called when an error occurs: <TSDoc definition={` import type { StreamTextOnErrorCallback } from "@workflow/ai/agent"; export default StreamTextOnErrorCallback;`} /> #### StreamTextOnAbortCallback Called when the operation is aborted: <TSDoc definition={` import type { StreamTextOnAbortCallback } from "@workflow/ai/agent"; export default StreamTextOnAbortCallback;`} /> ### Advanced types #### ToolCallRepairFunction Function to repair malformed tool calls: <TSDoc definition={` import type { ToolCallRepairFunction } from "@workflow/ai/agent"; export default ToolCallRepairFunction;`} /> #### StreamTextTransform Transform applied to the stream: <TSDoc definition={` import type { StreamTextTransform } from "@workflow/ai/agent"; export default StreamTextTransform;`} /> #### OutputSpecification Specification for structured output parsing: <TSDoc definition={` import type { OutputSpecification } from "@workflow/ai/agent"; export default OutputSpecification;`} /> ## Key features - **Durable execution**: Agents can be interrupted and resumed without losing state. - **Flexible tool implementation**: Tools can be implemented as workflow steps for automatic retries or as regular workflow-level logic. - **Stream processing**: Handles streaming responses and tool calls in a structured way. - **Workflow native**: Fully integrated with Workflow SDK for production reliability. - **AI SDK parity**: Supports the same options as AI SDK's `streamText`, including generation settings, callbacks, and structured output. ## Good to know - Tools can be implemented as workflow steps (using `"use step"` for automatic retries) or as regular workflow-level logic. - Tools can use core library features like `sleep()` and hooks within their `execute` functions. - The agent processes tool calls iteratively until completion or until `maxSteps` is reached. - **Default `maxSteps` is unlimited**: Set a value to limit the number of large language model (LLM) calls. - The `stream()` method returns `{ messages, steps, toolCalls, toolResults, experimental_output, uiMessages }` containing the full conversation history, step details, tool call details, optional structured output, and optionally accumulated user interface (UI) messages. - Use `collectUIMessages: true` to accumulate `UIMessage[]` during streaming, which is useful for persisting conversation state without re-reading the stream. - The `prepareStep` callback runs before each step and can modify the model, messages, generation settings, tool choice, and context. - Generation settings (`temperature`, `maxOutputTokens`, and others) can be set on the constructor and overridden per-stream call. - Use `activeTools` to limit which tools are available for a specific stream call. - The `onFinish` callback is called when all steps complete; `onAbort` is called if aborted. ## Examples ### Basic agent with tools ```typescript import { DurableAgent } from "@workflow/ai/agent"; import { getWritable } from "workflow"; import { z } from "zod"; import type { UIMessageChunk } from "ai"; async function getWeather({ location }: { location: string }) { "use step"; // Fetch weather data const response = await fetch(`https://api.weather.com?location=${location}`); return response.json(); } async function weatherAgentWorkflow(userQuery: string) { "use workflow"; const agent = new DurableAgent({ model: "spacexai/grok-4.6", tools: { getWeather: { description: "Get current weather for a location", inputSchema: z.object({ location: z.string() }), execute: getWeather, }, }, instructions: "You are a helpful weather assistant. Always provide accurate weather information.", }); await agent.stream({ messages: [ { role: "user", content: userQuery, }, ], writable: getWritable<UIMessageChunk>(), }); } ``` ### Multiple tools ```typescript import { DurableAgent } from "@workflow/ai/agent"; import { getWritable } from "workflow"; import { z } from "zod"; import type { UIMessageChunk } from "ai"; async function getWeather({ location }: { location: string }) { "use step"; return `Weather in ${location}: Sunny, 72°F`; } async function searchEvents({ location, category }: { location: string; category: string }) { "use step"; return `Found 5 ${category} events in ${location}`; } async function multiToolAgentWorkflow(userQuery: string) { "use workflow"; const agent = new DurableAgent({ model: "spacexai/grok-4.6", tools: { getWeather: { description: "Get weather for a location", inputSchema: z.object({ location: z.string() }), execute: getWeather, }, searchEvents: { description: "Search for upcoming events in a location", inputSchema: z.object({ location: z.string(), category: z.string() }), execute: searchEvents, }, }, }); await agent.stream({ messages: [ { role: "user", content: userQuery, }, ], writable: getWritable<UIMessageChunk>(), }); } ``` ### Multi-turn conversation ```typescript import { DurableAgent } from "@workflow/ai/agent"; import { getWritable } from "workflow"; import type { UIMessageChunk } from "ai"; import { z } from "zod"; async function searchProducts({ query }: { query: string }) { "use step"; // Search product database return `Found 3 products matching "${query}"`; } async function multiTurnAgentWorkflow() { "use workflow"; const agent = new DurableAgent({ model: "spacexai/grok-4.6", tools: { searchProducts: { description: "Search for products", inputSchema: z.object({ query: z.string() }), execute: searchProducts, }, }, }); const writable = getWritable<UIMessageChunk>(); // First user message // - Result is streamed to the provided `writable` stream // - Message history is returned in `messages` for LLM context let { messages } = await agent.stream({ messages: [ { role: "user", content: "Find me some laptops" } ], writable, }); // Continue the conversation with the accumulated message history const result = await agent.stream({ messages: [ ...messages, { role: "user", content: "Which one has the best battery life?" } ], writable, }); // result.messages now contains the complete conversation history return result.messages; } ``` ### Tools with Workflow library features ```typescript import { DurableAgent } from "@workflow/ai/agent"; import { sleep, defineHook, getWritable } from "workflow"; import { z } from "zod"; import type { UIMessageChunk } from "ai"; // Define a reusable hook type const approvalHook = defineHook<{ approved: boolean; reason: string }>(); async function scheduleTask({ delaySeconds }: { delaySeconds: number }) { // Note: No "use step" for this tool call, // since `sleep()` is a workflow level function await sleep(`${delaySeconds}s`); return `Slept for ${delaySeconds} seconds`; } async function requestApproval({ message }: { message: string }) { // Note: No "use step" for this tool call either, // since hooks are awaited at the workflow level // Use a Hook for Human-in-the-loop approval const hook = approvalHook.create({ metadata: { message } }); console.log(`Approval needed - token: ${hook.token}`); // Wait for the approval payload const approval = await hook; if (approval.approved) { return `Request approved: ${approval.reason}`; } else { throw new Error(`Request denied: ${approval.reason}`); } } async function agentWithLibraryFeaturesWorkflow(userRequest: string) { "use workflow"; const agent = new DurableAgent({ model: "spacexai/grok-4.6", tools: { scheduleTask: { description: "Pause the workflow for the specified number of seconds", inputSchema: z.object({ delaySeconds: z.number(), }), execute: scheduleTask, }, requestApproval: { description: "Request approval for an action", inputSchema: z.object({ message: z.string() }), execute: requestApproval, }, }, }); await agent.stream({ messages: [{ role: "user", content: userRequest }], writable: getWritable<UIMessageChunk>(), }); } ``` ### Dynamic context with prepareStep Use `prepareStep` to modify settings before each step in the agent loop: ```typescript import { DurableAgent } from "@workflow/ai/agent"; import { getWritable } from "workflow"; import type { UIMessageChunk } from "ai"; async function agentWithPrepareStep(userMessage: string) { "use workflow"; const agent = new DurableAgent({ model: "spacexai/grok-4.6", // Default model instructions: "You are a helpful assistant.", }); await agent.stream({ messages: [{ role: "user", content: userMessage }], writable: getWritable<UIMessageChunk>(), prepareStep: async ({ stepNumber, messages }) => { // Switch to a stronger model for complex reasoning after initial steps if (stepNumber > 2 && messages.length > 10) { return { model: "anthropic/claude-sonnet-4.5", }; } // Trim context if messages grow too large if (messages.length > 20) { return { messages: [ messages[0], // Keep system message ...messages.slice(-10), // Keep last 10 messages ], }; } return {}; // No changes }, }); } ``` ### Message injection with prepareStep Inject messages from external sources (like hooks) before each LLM call: ```typescript import { DurableAgent } from "@workflow/ai/agent"; import { getWritable, defineHook } from "workflow"; import type { UIMessageChunk } from "ai"; const messageHook = defineHook<{ message: string }>(); async function agentWithMessageQueue(initialMessage: string) { "use workflow"; const messageQueue: Array<{ role: "user"; content: string }> = []; // Listen for incoming messages via hook const hook = messageHook.create(); hook.then(({ message }) => { messageQueue.push({ role: "user", content: message }); }); const agent = new DurableAgent({ model: "spacexai/grok-4.6", instructions: "You are a helpful assistant.", }); await agent.stream({ messages: [{ role: "user", content: initialMessage }], writable: getWritable<UIMessageChunk>(), prepareStep: ({ messages }) => { // Inject queued messages before the next step if (messageQueue.length > 0) { const newMessages = messageQueue.splice(0); return { messages: [ ...messages, ...newMessages.map(m => ({ role: m.role, content: [{ type: "text" as const, text: m.content }], })), ], }; } return {}; }, }); } ``` ### Generation settings Configure model generation parameters at the constructor or stream level: ```typescript import { DurableAgent } from "@workflow/ai/agent"; import { getWritable } from "workflow"; import type { UIMessageChunk } from "ai"; async function agentWithGenerationSettings() { "use workflow"; // Set default generation settings in constructor const agent = new DurableAgent({ model: "spacexai/grok-4.6", temperature: 0.7, maxOutputTokens: 2000, topP: 0.9, }); // Override settings per-stream call await agent.stream({ messages: [{ role: "user", content: "Write a creative story" }], writable: getWritable<UIMessageChunk>(), temperature: 0.9, // More creative for this call maxSteps: 1, }); // Use different settings for a different task await agent.stream({ messages: [{ role: "user", content: "Summarize this document precisely" }], writable: getWritable<UIMessageChunk>(), temperature: 0.1, // More deterministic maxSteps: 1, }); } ``` ### Limiting steps with maxSteps By default, the agent loops until completion. Use `maxSteps` to limit the number of LLM calls: ```typescript import { DurableAgent } from "@workflow/ai/agent"; import { getWritable } from "workflow"; import { z } from "zod"; import type { UIMessageChunk } from "ai"; async function searchWeb({ query }: { query: string }) { "use step"; return `Results for "${query}": ...`; } async function analyzeResults({ data }: { data: string }) { "use step"; return `Analysis: ${data}`; } async function multiStepAgent() { "use workflow"; const agent = new DurableAgent({ model: "spacexai/grok-4.6", tools: { searchWeb: { description: "Search the web for information", inputSchema: z.object({ query: z.string() }), execute: searchWeb, }, analyzeResults: { description: "Analyze search results", inputSchema: z.object({ data: z.string() }), execute: analyzeResults, }, }, }); // Limit to 10 steps for safety on complex research tasks const result = await agent.stream({ messages: [{ role: "user", content: "Research the latest AI trends and provide an analysis" }], writable: getWritable<UIMessageChunk>(), maxSteps: 10, }); // Access step-by-step details console.log(`Completed in ${result.steps.length} steps`); } ``` ### Callbacks for monitoring Use callbacks to monitor streaming progress, handle errors, and react to completion: ```typescript import { DurableAgent } from "@workflow/ai/agent"; import { getWritable } from "workflow"; import type { UIMessageChunk } from "ai"; async function agentWithCallbacks() { "use workflow"; const agent = new DurableAgent({ model: "spacexai/grok-4.6", }); await agent.stream({ messages: [{ role: "user", content: "Hello!" }], writable: getWritable<UIMessageChunk>(), maxSteps: 5, // Called after each step completes onStepFinish: async (step) => { console.log(`Step finished: ${step.finishReason}`); console.log(`Tokens used: ${step.usage.totalTokens}`); }, // Called when streaming completes onFinish: async ({ steps, messages }) => { console.log(`Completed with ${steps.length} steps`); console.log(`Final message count: ${messages.length}`); }, // Called on errors onError: async ({ error }) => { console.error("Stream error:", error); }, }); } ``` ### Structured output Parse structured data from the LLM response using `Output.object`: ```typescript import { DurableAgent, Output } from "@workflow/ai/agent"; import { getWritable } from "workflow"; import { z } from "zod"; import type { UIMessageChunk } from "ai"; async function agentWithStructuredOutput() { "use workflow"; const agent = new DurableAgent({ model: "spacexai/grok-4.6", }); const result = await agent.stream({ messages: [{ role: "user", content: "Analyze the sentiment of: 'I love this product!'" }], writable: getWritable<UIMessageChunk>(), experimental_output: Output.object({ schema: z.object({ sentiment: z.enum(["positive", "negative", "neutral"]), confidence: z.number().min(0).max(1), reasoning: z.string(), }), }), }); // Access the parsed structured output console.log(result.experimental_output); // { sentiment: "positive", confidence: 0.95, reasoning: "..." } } ``` ### Tool choice control Control when and which tools the model can use: ```typescript import { DurableAgent } from "@workflow/ai/agent"; import { getWritable } from "workflow"; import { z } from "zod"; import type { UIMessageChunk } from "ai"; async function agentWithToolChoice() { "use workflow"; const agent = new DurableAgent({ model: "spacexai/grok-4.6", tools: { calculator: { description: "Perform calculations", inputSchema: z.object({ expression: z.string() }), execute: async ({ expression }) => `Calculated: ${expression}`, }, search: { description: "Search for information", inputSchema: z.object({ query: z.string() }), execute: async ({ query }) => `Results for: ${query}`, }, }, toolChoice: "auto", // Default: model decides }); // Force the model to use a tool await agent.stream({ messages: [{ role: "user", content: "What is 2 + 2?" }], writable: getWritable<UIMessageChunk>(), toolChoice: "required", maxSteps: 2, }); // Prevent tool usage await agent.stream({ messages: [{ role: "user", content: "Just chat with me" }], writable: getWritable<UIMessageChunk>(), toolChoice: "none", }); // Force a specific tool await agent.stream({ messages: [{ role: "user", content: "Calculate something" }], writable: getWritable<UIMessageChunk>(), toolChoice: { type: "tool", toolName: "calculator" }, maxSteps: 2, }); // Limit available tools for this call await agent.stream({ messages: [{ role: "user", content: "Just search, don't calculate" }], writable: getWritable<UIMessageChunk>(), activeTools: ["search"], maxSteps: 2, }); } ``` ### Passing context to tools Use `experimental_context` to pass shared context to tool executions: ```typescript import { DurableAgent } from "@workflow/ai/agent"; import { getWritable } from "workflow"; import { z } from "zod"; import type { UIMessageChunk } from "ai"; interface UserContext { userId: string; permissions: string[]; } async function agentWithContext(userId: string) { "use workflow"; const agent = new DurableAgent({ model: "spacexai/grok-4.6", tools: { getUserData: { description: "Get user data", inputSchema: z.object({}), execute: async (_, { experimental_context }) => { const ctx = experimental_context as UserContext; return { userId: ctx.userId, permissions: ctx.permissions }; }, }, }, }); await agent.stream({ messages: [{ role: "user", content: "What are my permissions?" }], writable: getWritable<UIMessageChunk>(), maxSteps: 2, experimental_context: { userId, permissions: ["read", "write"], } as UserContext, }); } ``` ### Collecting UI messages Use `collectUIMessages` to accumulate `UIMessage[]` during streaming. This is useful when you need to persist the conversation without re-reading the run's output stream: ```typescript lineNumbers import { DurableAgent } from "@workflow/ai/agent"; import { getWritable } from "workflow"; import type { UIMessage, UIMessageChunk } from "ai"; async function agentWithUIMessages(userMessage: string) { "use workflow"; const agent = new DurableAgent({ model: "spacexai/grok-4.6", instructions: "You are a helpful assistant.", }); const result = await agent.stream({ messages: [{ role: "user", content: userMessage }], writable: getWritable<UIMessageChunk>(), collectUIMessages: true, // [!code highlight] }); // Access the accumulated UI messages const uiMessages: UIMessage[] = result.uiMessages ?? []; // [!code highlight] // Persist messages to a database await saveConversation(uiMessages); return result; } async function saveConversation(messages: UIMessage[]) { "use step"; // Save to database... } ``` <Callout type="info"> The `uiMessages` property is only available when `collectUIMessages` is set to `true`. When disabled, `uiMessages` is `undefined`. </Callout> ### Machine-readable tool results `stream()` returns tool call information you can inspect programmatically. Compare `toolCalls` with `toolResults` to find unresolved tool calls that need client-side handling: ```typescript lineNumbers import { DurableAgent } from "@workflow/ai/agent"; import { getWritable } from "workflow"; import { z } from "zod"; import type { UIMessageChunk } from "ai"; async function checkOrderStatus({ orderId }: { orderId: string }) { "use step"; return `Order ${orderId}: shipped`; } async function agentWithToolInspection(userMessage: string) { "use workflow"; const agent = new DurableAgent({ model: "spacexai/grok-4.6", tools: { checkOrderStatus: { description: "Check order status", inputSchema: z.object({ orderId: z.string() }), execute: checkOrderStatus, }, }, }); const result = await agent.stream({ messages: [{ role: "user", content: userMessage }], writable: getWritable<UIMessageChunk>(), }); const unresolved = result.toolCalls.filter( // [!code highlight] (tc) => !result.toolResults.some((tr) => tr.toolCallId === tc.toolCallId) // [!code highlight] ); // [!code highlight] if (unresolved.length > 0) { return { status: "needs-client-tools", unresolved, }; } return { status: "complete", messages: result.messages, toolResults: result.toolResults, }; } ``` <Callout type="info"> `toolCalls` and `toolResults` reflect the *last step* of the agent loop. Tools without an `execute` function will appear in `toolCalls` but not in `toolResults`, which is how you detect calls that need client-side handling. </Callout> ### Aborting long-running streams Use `timeout` to abort a stream automatically after a fixed duration: <Callout type="warn"> `abortSignal` is not yet supported and will be available in a future release. Use `timeout` for now. </Callout> ```typescript lineNumbers import { DurableAgent } from "@workflow/ai/agent"; import { getWritable } from "workflow"; import type { UIMessageChunk } from "ai"; async function agentWithTimeout(userMessage: string) { "use workflow"; const agent = new DurableAgent({ model: "spacexai/grok-4.6", }); await agent.stream({ messages: [{ role: "user", content: userMessage }], writable: getWritable<UIMessageChunk>(), timeout: 30_000, // [!code highlight] }); } ``` ## See also - [Building Durable AI Agents](/docs/ai): Complete guide to creating durable agents - [Queueing User Messages](/docs/ai/message-queueing): Using `prepareStep` for message injection - [WorkflowChatTransport](/docs/api-reference/workflow-ai/workflow-chat-transport): Transport layer for AI SDK streams - [Workflows and Steps](/docs/foundations/workflows-and-steps): Understanding workflow fundamentals - [AI SDK Loop Control](https://ai-sdk.dev/docs/agents/loop-control): AI SDK's agent loop control patterns