DurableAgent
The
@workflow/ai package is currently in active development and should be considered experimental.The DurableAgent class enables you to create AI-powered agents that can maintain state across workflow steps, call tools, and gracefully handle interruptions and resumptions.
Tool calls can be implemented as workflow steps for automatic retries, or as regular workflow-level logic utilizing core library features such as sleep() and Hooks.
import { DurableAgent } from '@workflow/ai/agent';
import { z } from 'zod';
async function getWeather({ city }: { city: string }) {
"use step";
return `Weather in ${city} is sunny`;
}
async function myAgent() {
"use workflow";
const agent = new DurableAgent({
model: 'anthropic/claude-haiku-4.5',
system: 'You are a helpful weather assistant.',
temperature: 0.7, // Control output randomness
maxOutputTokens: 1000, // Limit response length
tools: {
getWeather: {
description: 'Get weather for a city',
inputSchema: z.object({ city: z.string() }),
execute: getWeather,
},
},
});
await agent.stream({
messages: [{ role: 'user', content: 'How is the weather in San Francisco?' }],
});
}API Signature
Class
| Name | Type | Description |
|---|---|---|
model | any | |
tools | any | |
system | any | |
temperature | any | |
maxOutputTokens | any | |
topP | any | |
topK | any | |
presencePenalty | any | |
frequencyPenalty | any | |
stopSequences | any | |
seed | any | |
generate | () => void | |
stream | (options: DurableAgentStreamOptions) => Promise<void> |
DurableAgentOptions
| Name | Type | Description |
|---|---|---|
model | string | The model identifier to use for the agent. This should be a string compatible with the AI SDK (e.g., 'anthropic/claude-opus'). |
tools | ToolSet | A set of tools available to the agent. Tools can be implemented as workflow steps for automatic retries and persistence, or as regular workflow-level logic using core library features like sleep() and Hooks. |
system | string | Optional system prompt to guide the agent's behavior. |
temperature | number | Temperature setting. The range depends on the provider and model.
It is recommended to set either temperature or topP, but not both. |
maxOutputTokens | number | Maximum number of tokens to generate. |
topP | number | Nucleus sampling. This is a number between 0 and 1.
E.g. 0.1 would mean that only tokens with the top 10% probability mass
are considered.
It is recommended to set either temperature or topP, but not both. |
topK | number | Only sample from the top K options for each subsequent token. Used to remove "long tail" low probability responses. Recommended for advanced use cases only. You usually only need to use temperature. |
presencePenalty | number | Presence penalty setting. It affects the likelihood of the model to repeat information that is already in the prompt. The presence penalty is a number between -1 (increase repetition) and 1 (maximum penalty, decrease repetition). 0 means no penalty. |
frequencyPenalty | number | Frequency penalty setting. It affects the likelihood of the model to repeatedly use the same words or phrases. The frequency penalty is a number between -1 (increase repetition) and 1 (maximum penalty, decrease repetition). 0 means no penalty. |
stopSequences | string[] | Stop sequences. If set, the model will stop generating text when one of the stop sequences is generated. Providers may have limits on the number of stop sequences. |
seed | number | The seed (integer) to use for random sampling. If set and supported by the model, calls will generate deterministic results. |
DurableAgentStreamOptions
| Name | Type | Description |
|---|---|---|
messages | ModelMessage[] | The conversation messages to process. Should follow the AI SDK's ModelMessage format. |
system | string | Optional system prompt override. If provided, overrides the system prompt from the constructor. |
writable | WritableStream<UIMessageChunk> | Optional custom writable stream for handling message chunks. If not provided, a default writable stream will be created using getWritable(). |
preventClose | boolean | If true, prevents the writable stream from being closed after streaming completes. Defaults to false (stream will be closed). |
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 DevKit for production-grade reliability
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 theirexecutefunctions - The agent processes tool calls iteratively until completion
- When no custom
writablestream is provided, the agent uses the workflow's default writable stream
Examples
Basic Agent with Tools
import { DurableAgent } from '@workflow/ai/agent';
import { z } from 'zod';
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: 'anthropic/claude-haiku-4.5',
tools: {
getWeather: {
description: 'Get current weather for a location',
inputSchema: z.object({ location: z.string() }),
execute: getWeather,
},
},
system: 'You are a helpful weather assistant. Always provide accurate weather information.',
});
await agent.stream({
messages: [
{
role: 'user',
content: userQuery,
},
],
});
}Multiple Tools
import { DurableAgent } from '@workflow/ai/agent';
import { z } from 'zod';
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: 'anthropic/claude-haiku-4.5',
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,
},
],
});
}Advanced Configuration
import { DurableAgent } from '@workflow/ai/agent';
import { z } from 'zod';
async function calculateResult({ formula }: { formula: string }) {
"use step";
// Perform calculation
return "42";
}
async function advancedAgentWorkflow(userQuery: string) {
'use workflow';
const agent = new DurableAgent({
model: 'anthropic/claude-haiku-4.5',
system: 'You are a precise calculator assistant.',
// Model behavior controls
temperature: 0.3, // Lower temperature for more deterministic responses
maxOutputTokens: 500, // Limit response length
topP: 0.9, // Nucleus sampling for response variety
presencePenalty: 0.2, // Reduce repetition
frequencyPenalty: 0.2, // Reduce word repetition
seed: 12345, // For reproducible results
stopSequences: ['END'], // Stop generation at specific sequences
tools: {
calculateResult: {
description: 'Calculate a mathematical result',
inputSchema: z.object({ formula: z.string() }),
execute: calculateResult,
},
},
});
await agent.stream({
messages: [
{
role: 'user',
content: userQuery,
},
],
});
}Tools with Workflow Library Features
import { DurableAgent } from '@workflow/ai/agent';
import { sleep, defineHook } from 'workflow';
import { z } from 'zod';
// 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
// Utilize 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: 'anthropic/claude-haiku-4.5',
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 }],
});
}See Also
- WorkflowChatTransport - Transport layer for AI SDK streams
- Workflows and Steps - Understanding workflow fundamentals
- AI SDK Documentation - AI SDK documentation reference