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.',
    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

NameTypeDescription
modelany
toolsany
systemany
generate() => void
stream(options: DurableAgentStreamOptions) => Promise<void>

DurableAgentOptions

NameTypeDescription
modelstringThe model identifier to use for the agent. This should be a string compatible with the AI SDK (e.g., 'anthropic/claude-opus').
toolsToolSetA 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.
systemstringOptional system prompt to guide the agent's behavior.

DurableAgentStreamOptions

NameTypeDescription
messagesModelMessage[]The conversation messages to process. Should follow the AI SDK's ModelMessage format.
systemstringOptional system prompt override. If provided, overrides the system prompt from the constructor.
writableWritableStream<UIMessageChunk>Optional custom writable stream for handling message chunks. If not provided, a default writable stream will be created using getWritable().

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 their execute functions
  • The agent processes tool calls iteratively until completion
  • When no custom writable stream 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,
      },
    ],
  });
}

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