diff --git a/docs/content/docs/v4/api-reference/workflow-ai/durable-agent.mdx b/docs/content/docs/v4/api-reference/workflow-ai/durable-agent.mdx
index edd95a8985..a54cdd8d94 100644
--- a/docs/content/docs/v4/api-reference/workflow-ai/durable-agent.mdx
+++ b/docs/content/docs/v4/api-reference/workflow-ai/durable-agent.mdx
@@ -11,6 +11,10 @@ related:
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.
+
+**Deprecation notice:** `DurableAgent` will be deprecated soon. For new durable agent workflows, use [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) from `@ai-sdk/workflow` instead. See [Migrating from DurableAgent](https://ai-sdk.dev/v7/docs/agents/workflow-agent#migrating-from-durableagent) in the AI SDK docs.
+
+
Tool calls can be implemented as workflow steps for automatic retries, or as regular workflow-level logic utilizing core library features such as [`sleep()`](/docs/api-reference/workflow/sleep) and [Hooks](/docs/foundations/hooks).
```typescript lineNumbers
diff --git a/docs/content/docs/v4/cookbook/agent-patterns/durable-agent.mdx b/docs/content/docs/v4/cookbook/agent-patterns/durable-agent.mdx
index 3ef5dca9f3..86be220197 100644
--- a/docs/content/docs/v4/cookbook/agent-patterns/durable-agent.mdx
+++ b/docs/content/docs/v4/cookbook/agent-patterns/durable-agent.mdx
@@ -7,6 +7,10 @@ summary: Convert an AI SDK Agent into a DurableAgent backed by a workflow, with
Use this pattern to make any AI SDK agent durable. The agent becomes a workflow, tools become steps, and the framework handles retries, streaming, and state persistence automatically.
+
+**Deprecation notice:** `DurableAgent` will be deprecated soon. For new durable agent workflows, use [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) from `@ai-sdk/workflow` instead. See [Migrating from DurableAgent](https://ai-sdk.dev/v7/docs/agents/workflow-agent#migrating-from-durableagent) in the AI SDK docs.
+
+
## When to use this
- Any AI agent with tool calls that should survive crashes and restarts
diff --git a/docs/content/docs/v4/cookbook/index.mdx b/docs/content/docs/v4/cookbook/index.mdx
index ea1ecbb724..570ed0b90c 100644
--- a/docs/content/docs/v4/cookbook/index.mdx
+++ b/docs/content/docs/v4/cookbook/index.mdx
@@ -8,7 +8,7 @@ A curated collection of workflow patterns with clean, copy-paste code examples f
## Agent Patterns
-- [**Durable Agent**](/cookbook/agent-patterns/durable-agent) — Replace a stateless AI agent with one that survives crashes and retries tool calls
+- [**Durable Agent**](/cookbook/agent-patterns/durable-agent) — Legacy durable agent pattern (deprecated soon — prefer [WorkflowAgent](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent))
- [**Human-in-the-Loop**](/cookbook/agent-patterns/human-in-the-loop) — Pause an agent for human approval, then resume based on the decision
- [**Agent Cancellation**](/cookbook/agent-patterns/agent-cancellation) — Stop a running agent immediately via `run.cancel()` or gracefully via a hook + `Promise.race`
diff --git a/docs/content/docs/v4/cookbook/integrations/ai-sdk.mdx b/docs/content/docs/v4/cookbook/integrations/ai-sdk.mdx
index a5676e0b92..4cb1df56c8 100644
--- a/docs/content/docs/v4/cookbook/integrations/ai-sdk.mdx
+++ b/docs/content/docs/v4/cookbook/integrations/ai-sdk.mdx
@@ -2,7 +2,7 @@
title: AI SDK
description: Use AI SDK's streamText directly inside durable workflows for lower-level control over model calls and tool execution.
type: guide
-summary: Use streamText() inside a workflow for full control over model options, stop conditions, and output schemas — while tools remain durable steps.
+summary: Use streamText() inside a workflow for full control over model options, stop conditions, and output schemas. The per-turn step is durable; individual tool calls inside it are not.
related:
- /docs/ai
- /docs/ai/chat-session-modeling
@@ -11,17 +11,17 @@ related:
- /docs/api-reference/workflow-ai/durable-agent
---
-[AI SDK](https://ai-sdk.dev/) is Vercel's framework-agnostic TypeScript toolkit for building AI-powered apps and agents — unified provider access, streaming, tool calling, structured output, and UI hooks. Workflow SDK complements it by making those calls durable: the model request, the tool loop, and the multi-turn conversation all survive restarts and timeouts.
+[AI SDK](https://ai-sdk.dev/) is Vercel's framework-agnostic TypeScript toolkit for building AI-powered apps and agents — unified provider access, streaming, tool calling, structured output, and UI hooks. Workflow SDK complements it by making the multi-turn loop durable: the conversation state, hooks, and per-turn responses survive restarts and timeouts. Note that in this pattern the durability boundary is the entire turn — individual tool calls inside a turn are **not** durable on their own (see [Pitfalls](#tools-are-not-individually-durable) below).
For the full AI SDK reference (providers, `streamText`, `generateObject`, `useChat`, tool calling, etc.) see the [AI SDK docs](https://ai-sdk.dev/docs). This page covers the Workflow-specific integration points.
-For most agent use cases, prefer [`DurableAgent`](/cookbook/agent-patterns/durable-agent) which wraps [`streamText`](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text) and manages the tool loop automatically. This page covers using `streamText()` directly when you need lower-level control.
+For most agent use cases, prefer [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) from `@ai-sdk/workflow`, which manages the tool loop automatically and runs tools as durable workflow steps. This page covers using `streamText()` directly when you need AI SDK features that require lower-level control — accepting that tools inside a turn are no longer individually durable unless you wrap them yourself.
## When to use streamText directly
-Use [`streamText()`](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text) instead of `DurableAgent` when you need:
+Use [`streamText()`](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text) instead of [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) or the legacy `DurableAgent` when you need:
* **Custom stop conditions** — [`stopWhen`](https://ai-sdk.dev/docs/ai-sdk-core/agents#stop-conditions), [`prepareStep`](https://ai-sdk.dev/docs/ai-sdk-core/agents#prepare-step), or [`onStepFinish`](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text#on-step-finish) callbacks
* **Structured output** — [`Output.object()`](https://ai-sdk.dev/docs/ai-sdk-core/generating-structured-data) or `Output.array()` alongside tool calling
@@ -52,14 +52,16 @@ export const turnHook = defineHook({ // [!code highlight]
schema: z.object({ message: z.string() }),
});
+// Tools are plain async functions in this pattern. `streamText` calls them
+// from inside `runTurn` (a step), and the `"use step"` directive is a no-op
+// when called from another step — see the "Tools are not individually durable"
+// pitfall below. Make side-effectful tools idempotent.
async function lookupOrder({ orderId }: { orderId: string }) {
- "use step";
const res = await fetch(`https://api.store.com/orders/${orderId}`);
return res.json();
}
async function processRefund({ orderId, reason }: { orderId: string; reason: string }) {
- "use step";
const res = await fetch("https://api.store.com/refunds", {
method: "POST",
body: JSON.stringify({ orderId, reason }),
@@ -294,16 +296,32 @@ export function SupportChat() {
## How it works
1. **One workflow = one conversation.** The workflow loops on a hook, keeping `allMessages`, tool history, and state alive across turns.
-2. **Hook is created once.** `turnHook.create({ token: workflowRunId })` outside the loop — calling it twice with the same token throws `HookConflictError`.
-3. **`preventClose: true`** on `pipeTo` keeps the durable writable open so the next turn can write to it.
-4. **`sliceUntilFinish`** in the API reads chunks until `type === "finish"`, then closes the HTTP response. The source reader is released — not cancelled — so the workflow stream keeps flowing.
-5. **`startIndex: tailIndex + 1`** gives each follow-up response only the new chunks, avoiding replay of previous turns.
-6. **`/done`** resumes the hook so the workflow exits cleanly, then returns a synthetic `start` + `finish` so `useChat` transitions out of "streaming".
+2. **`runTurn` is the durability boundary.** Each turn is one step. The model request and all tool calls inside it run as plain inline functions within that step. If anything throws mid-turn, the whole `runTurn` retries — individual tool calls are not separately durable. See [Pitfalls](#tools-are-not-individually-durable).
+3. **Hook is created once.** `turnHook.create({ token: workflowRunId })` outside the loop — calling it twice with the same token throws `HookConflictError`.
+4. **`preventClose: true`** on `pipeTo` keeps the durable writable open so the next turn can write to it.
+5. **`sliceUntilFinish`** in the API reads chunks until `type === "finish"`, then closes the HTTP response. The source reader is released — not cancelled — so the workflow stream keeps flowing.
+6. **`startIndex: tailIndex + 1`** gives each follow-up response only the new chunks, avoiding replay of previous turns.
+7. **`/done`** resumes the hook so the workflow exits cleanly, then returns a synthetic `start` + `finish` so `useChat` transitions out of "streaming".
## Pitfalls
Non-obvious correctness details worth knowing before adapting this pattern.
+### Tools are not individually durable
+
+`streamText()` is invoked from inside `runTurn` (a `"use step"` function), and the AI SDK calls each tool by directly invoking its `execute` function in that same step. Even if a tool body has its own `"use step"` directive, that directive is a [no-op when called from another step](/docs/foundations/workflows-and-steps#step-functions) — the function just runs inline.
+
+The consequences:
+
+- The atomic retry unit is the entire `runTurn`, not the individual tool call.
+- If `processRefund` succeeds and then the model call (or a later tool) throws, the whole turn retries, and `processRefund` will run again.
+- Tool calls do not appear as separate entries in the event log or observability dashboard.
+
+**Mitigations:**
+
+- Make side-effectful tool implementations idempotent — dedupe server-side on a stable key (e.g. `orderId`, an `Idempotency-Key` header, etc.).
+- Or use [`DurableAgent`](/docs/api-reference/workflow-ai/durable-agent), which runs tools at workflow scope — each tool can be marked `"use step"` to become its own durable, retryable step, or stay at workflow level to use primitives like `sleep()` and hooks.
+
### Snapshot `tailIndex` *before* resuming the hook
{/* @skip-typecheck - fragment referencing variables from the surrounding multi-turn pattern */}
@@ -332,32 +350,33 @@ In `sliceUntilFinish`, use `reader.releaseLock()` in the `finally` block rather
Clients can send a `runId` from a long-gone workflow (localStorage, back button, server restart). Wrap the follow-up path in a try/catch for `not found` / `expired` and fall through to the first-turn code path to start a fresh workflow.
-## streamText vs DurableAgent
+## streamText vs WorkflowAgent
-| | `streamText()` | `DurableAgent` |
+| | `streamText()` | [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) |
|---|---|---|
-| **Tool loop** | AI SDK handles via `stopWhen` | DurableAgent handles internally |
+| **Tool loop** | AI SDK handles via `stopWhen` | WorkflowAgent handles internally |
| **LLM call durability** | Re-executes on replay | Each LLM call is a durable step |
-| **Stop conditions** | `stopWhen`, `prepareStep` | `prepareStep` only |
-| **Structured output** | `Output.object()`, `Output.array()` | Not available |
-| **Step callbacks** | `onStepFinish`, `onChunk` | Not available |
-| **Setup** | Manual stream piping | Automatic |
+| **Tool call durability** | Not individually durable — re-executes with the parent turn | Per tool — mark `"use step"` for a durable, retryable step |
+| **Stop conditions** | `stopWhen`, `prepareStep` | `stopWhen`, `prepareStep` |
+| **Structured output** | `Output.object()`, `Output.array()` | `Output.object()` via `stream()` |
+| **Step callbacks** | `onStepFinish`, `onChunk` | Lifecycle callbacks on the agent |
+| **Setup** | Manual stream piping | `getWritable()` + `createModelCallToUIChunkTransform()` |
-Use `DurableAgent` for most agent use cases. Use `streamText` when you need the additional control.
+Use [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) for most agent use cases. Use `streamText` when you need the additional control. The legacy [`DurableAgent`](/docs/api-reference/workflow-ai/durable-agent) docs remain available but will be deprecated soon.
## Key APIs
**AI SDK** ([docs](https://ai-sdk.dev/docs))
* [`streamText()`](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text) — core streaming function; `toUIMessageStream()` pipes into the durable writable
-* [`tool()` / tool calling](https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling) — tools wrap `"use step"` functions so each tool call is replayed from the log, not re-executed
+* [`tool()` / tool calling](https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling) — tools are plain async functions invoked by `streamText` inside the turn step; they are **not** individually durable in this pattern (see [Pitfalls](#tools-are-not-individually-durable))
* [`stepCountIs()` / `stopWhen`](https://ai-sdk.dev/docs/ai-sdk-core/agents#stop-conditions) — bound the agent loop inside each turn
* [`convertToModelMessages()`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/convert-to-model-messages) / [`createUIMessageStreamResponse()`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/create-ui-message-stream-response) — UI ↔ model message conversion at the API boundary
* [`useChat()`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat) — React hook that consumes the UI message stream on the client
**Workflow SDK**
-* [`"use step"`](/docs/api-reference/workflow/use-step) — makes tool executions durable
+* [`"use step"`](/docs/api-reference/workflow/use-step) — applied to `runTurn` to make each turn a durable, retryable unit
* [`defineHook()`](/docs/api-reference/workflow/define-hook) — suspension point for follow-up messages
* [`getWritable()`](/docs/api-reference/workflow/get-writable) — resumable stream output
* [`getRun()`](/docs/api-reference/workflow-api/get-run) — `run.getReadable({ startIndex })` for slicing per-turn streams
diff --git a/docs/content/docs/v4/foundations/streaming.mdx b/docs/content/docs/v4/foundations/streaming.mdx
index b21a0ea01c..6d33c71092 100644
--- a/docs/content/docs/v4/foundations/streaming.mdx
+++ b/docs/content/docs/v4/foundations/streaming.mdx
@@ -347,6 +347,10 @@ export async function batchProcessingWorkflow(items: string[]) {
### Streaming AI Responses with `DurableAgent`
+
+**Deprecation notice:** `DurableAgent` will be deprecated soon. For new durable agent workflows, use [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) from `@ai-sdk/workflow` instead.
+
+
Stream AI-generated content using [`DurableAgent`](/docs/api-reference/workflow-ai/durable-agent) from `@workflow/ai`. Tools can also emit progress updates to the same stream using [data chunks](https://ai-sdk.dev/docs/ai-sdk-ui/streaming-data#streaming-custom-data) with the [`UIMessageChunk`](https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol) type from the AI SDK:
```typescript title="workflows/ai-assistant.ts" lineNumbers
diff --git a/docs/content/docs/v5/api-reference/workflow-ai/durable-agent.mdx b/docs/content/docs/v5/api-reference/workflow-ai/durable-agent.mdx
index edd95a8985..a54cdd8d94 100644
--- a/docs/content/docs/v5/api-reference/workflow-ai/durable-agent.mdx
+++ b/docs/content/docs/v5/api-reference/workflow-ai/durable-agent.mdx
@@ -11,6 +11,10 @@ related:
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.
+
+**Deprecation notice:** `DurableAgent` will be deprecated soon. For new durable agent workflows, use [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) from `@ai-sdk/workflow` instead. See [Migrating from DurableAgent](https://ai-sdk.dev/v7/docs/agents/workflow-agent#migrating-from-durableagent) in the AI SDK docs.
+
+
Tool calls can be implemented as workflow steps for automatic retries, or as regular workflow-level logic utilizing core library features such as [`sleep()`](/docs/api-reference/workflow/sleep) and [Hooks](/docs/foundations/hooks).
```typescript lineNumbers
diff --git a/docs/content/docs/v5/cookbook/agent-patterns/durable-agent.mdx b/docs/content/docs/v5/cookbook/agent-patterns/durable-agent.mdx
index 3ef5dca9f3..86be220197 100644
--- a/docs/content/docs/v5/cookbook/agent-patterns/durable-agent.mdx
+++ b/docs/content/docs/v5/cookbook/agent-patterns/durable-agent.mdx
@@ -7,6 +7,10 @@ summary: Convert an AI SDK Agent into a DurableAgent backed by a workflow, with
Use this pattern to make any AI SDK agent durable. The agent becomes a workflow, tools become steps, and the framework handles retries, streaming, and state persistence automatically.
+
+**Deprecation notice:** `DurableAgent` will be deprecated soon. For new durable agent workflows, use [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) from `@ai-sdk/workflow` instead. See [Migrating from DurableAgent](https://ai-sdk.dev/v7/docs/agents/workflow-agent#migrating-from-durableagent) in the AI SDK docs.
+
+
## When to use this
- Any AI agent with tool calls that should survive crashes and restarts
diff --git a/docs/content/docs/v5/cookbook/index.mdx b/docs/content/docs/v5/cookbook/index.mdx
index 309f087c21..93d01db06b 100644
--- a/docs/content/docs/v5/cookbook/index.mdx
+++ b/docs/content/docs/v5/cookbook/index.mdx
@@ -8,7 +8,7 @@ A curated collection of workflow patterns with clean, copy-paste code examples f
## Agent Patterns
-- [**Durable Agent**](/cookbook/agent-patterns/durable-agent) — Replace a stateless AI agent with one that survives crashes and retries tool calls
+- [**Durable Agent**](/cookbook/agent-patterns/durable-agent) — Legacy durable agent pattern (deprecated soon — prefer [WorkflowAgent](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent))
- [**Human-in-the-Loop**](/cookbook/agent-patterns/human-in-the-loop) — Pause an agent for human approval, then resume based on the decision
- [**Agent Cancellation**](/cookbook/agent-patterns/agent-cancellation) — Stop a running agent immediately via `run.cancel()` or gracefully via a hook + `Promise.race`
diff --git a/docs/content/docs/v5/cookbook/integrations/ai-sdk.mdx b/docs/content/docs/v5/cookbook/integrations/ai-sdk.mdx
index a5676e0b92..4cb1df56c8 100644
--- a/docs/content/docs/v5/cookbook/integrations/ai-sdk.mdx
+++ b/docs/content/docs/v5/cookbook/integrations/ai-sdk.mdx
@@ -2,7 +2,7 @@
title: AI SDK
description: Use AI SDK's streamText directly inside durable workflows for lower-level control over model calls and tool execution.
type: guide
-summary: Use streamText() inside a workflow for full control over model options, stop conditions, and output schemas — while tools remain durable steps.
+summary: Use streamText() inside a workflow for full control over model options, stop conditions, and output schemas. The per-turn step is durable; individual tool calls inside it are not.
related:
- /docs/ai
- /docs/ai/chat-session-modeling
@@ -11,17 +11,17 @@ related:
- /docs/api-reference/workflow-ai/durable-agent
---
-[AI SDK](https://ai-sdk.dev/) is Vercel's framework-agnostic TypeScript toolkit for building AI-powered apps and agents — unified provider access, streaming, tool calling, structured output, and UI hooks. Workflow SDK complements it by making those calls durable: the model request, the tool loop, and the multi-turn conversation all survive restarts and timeouts.
+[AI SDK](https://ai-sdk.dev/) is Vercel's framework-agnostic TypeScript toolkit for building AI-powered apps and agents — unified provider access, streaming, tool calling, structured output, and UI hooks. Workflow SDK complements it by making the multi-turn loop durable: the conversation state, hooks, and per-turn responses survive restarts and timeouts. Note that in this pattern the durability boundary is the entire turn — individual tool calls inside a turn are **not** durable on their own (see [Pitfalls](#tools-are-not-individually-durable) below).
For the full AI SDK reference (providers, `streamText`, `generateObject`, `useChat`, tool calling, etc.) see the [AI SDK docs](https://ai-sdk.dev/docs). This page covers the Workflow-specific integration points.
-For most agent use cases, prefer [`DurableAgent`](/cookbook/agent-patterns/durable-agent) which wraps [`streamText`](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text) and manages the tool loop automatically. This page covers using `streamText()` directly when you need lower-level control.
+For most agent use cases, prefer [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) from `@ai-sdk/workflow`, which manages the tool loop automatically and runs tools as durable workflow steps. This page covers using `streamText()` directly when you need AI SDK features that require lower-level control — accepting that tools inside a turn are no longer individually durable unless you wrap them yourself.
## When to use streamText directly
-Use [`streamText()`](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text) instead of `DurableAgent` when you need:
+Use [`streamText()`](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text) instead of [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) or the legacy `DurableAgent` when you need:
* **Custom stop conditions** — [`stopWhen`](https://ai-sdk.dev/docs/ai-sdk-core/agents#stop-conditions), [`prepareStep`](https://ai-sdk.dev/docs/ai-sdk-core/agents#prepare-step), or [`onStepFinish`](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text#on-step-finish) callbacks
* **Structured output** — [`Output.object()`](https://ai-sdk.dev/docs/ai-sdk-core/generating-structured-data) or `Output.array()` alongside tool calling
@@ -52,14 +52,16 @@ export const turnHook = defineHook({ // [!code highlight]
schema: z.object({ message: z.string() }),
});
+// Tools are plain async functions in this pattern. `streamText` calls them
+// from inside `runTurn` (a step), and the `"use step"` directive is a no-op
+// when called from another step — see the "Tools are not individually durable"
+// pitfall below. Make side-effectful tools idempotent.
async function lookupOrder({ orderId }: { orderId: string }) {
- "use step";
const res = await fetch(`https://api.store.com/orders/${orderId}`);
return res.json();
}
async function processRefund({ orderId, reason }: { orderId: string; reason: string }) {
- "use step";
const res = await fetch("https://api.store.com/refunds", {
method: "POST",
body: JSON.stringify({ orderId, reason }),
@@ -294,16 +296,32 @@ export function SupportChat() {
## How it works
1. **One workflow = one conversation.** The workflow loops on a hook, keeping `allMessages`, tool history, and state alive across turns.
-2. **Hook is created once.** `turnHook.create({ token: workflowRunId })` outside the loop — calling it twice with the same token throws `HookConflictError`.
-3. **`preventClose: true`** on `pipeTo` keeps the durable writable open so the next turn can write to it.
-4. **`sliceUntilFinish`** in the API reads chunks until `type === "finish"`, then closes the HTTP response. The source reader is released — not cancelled — so the workflow stream keeps flowing.
-5. **`startIndex: tailIndex + 1`** gives each follow-up response only the new chunks, avoiding replay of previous turns.
-6. **`/done`** resumes the hook so the workflow exits cleanly, then returns a synthetic `start` + `finish` so `useChat` transitions out of "streaming".
+2. **`runTurn` is the durability boundary.** Each turn is one step. The model request and all tool calls inside it run as plain inline functions within that step. If anything throws mid-turn, the whole `runTurn` retries — individual tool calls are not separately durable. See [Pitfalls](#tools-are-not-individually-durable).
+3. **Hook is created once.** `turnHook.create({ token: workflowRunId })` outside the loop — calling it twice with the same token throws `HookConflictError`.
+4. **`preventClose: true`** on `pipeTo` keeps the durable writable open so the next turn can write to it.
+5. **`sliceUntilFinish`** in the API reads chunks until `type === "finish"`, then closes the HTTP response. The source reader is released — not cancelled — so the workflow stream keeps flowing.
+6. **`startIndex: tailIndex + 1`** gives each follow-up response only the new chunks, avoiding replay of previous turns.
+7. **`/done`** resumes the hook so the workflow exits cleanly, then returns a synthetic `start` + `finish` so `useChat` transitions out of "streaming".
## Pitfalls
Non-obvious correctness details worth knowing before adapting this pattern.
+### Tools are not individually durable
+
+`streamText()` is invoked from inside `runTurn` (a `"use step"` function), and the AI SDK calls each tool by directly invoking its `execute` function in that same step. Even if a tool body has its own `"use step"` directive, that directive is a [no-op when called from another step](/docs/foundations/workflows-and-steps#step-functions) — the function just runs inline.
+
+The consequences:
+
+- The atomic retry unit is the entire `runTurn`, not the individual tool call.
+- If `processRefund` succeeds and then the model call (or a later tool) throws, the whole turn retries, and `processRefund` will run again.
+- Tool calls do not appear as separate entries in the event log or observability dashboard.
+
+**Mitigations:**
+
+- Make side-effectful tool implementations idempotent — dedupe server-side on a stable key (e.g. `orderId`, an `Idempotency-Key` header, etc.).
+- Or use [`DurableAgent`](/docs/api-reference/workflow-ai/durable-agent), which runs tools at workflow scope — each tool can be marked `"use step"` to become its own durable, retryable step, or stay at workflow level to use primitives like `sleep()` and hooks.
+
### Snapshot `tailIndex` *before* resuming the hook
{/* @skip-typecheck - fragment referencing variables from the surrounding multi-turn pattern */}
@@ -332,32 +350,33 @@ In `sliceUntilFinish`, use `reader.releaseLock()` in the `finally` block rather
Clients can send a `runId` from a long-gone workflow (localStorage, back button, server restart). Wrap the follow-up path in a try/catch for `not found` / `expired` and fall through to the first-turn code path to start a fresh workflow.
-## streamText vs DurableAgent
+## streamText vs WorkflowAgent
-| | `streamText()` | `DurableAgent` |
+| | `streamText()` | [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) |
|---|---|---|
-| **Tool loop** | AI SDK handles via `stopWhen` | DurableAgent handles internally |
+| **Tool loop** | AI SDK handles via `stopWhen` | WorkflowAgent handles internally |
| **LLM call durability** | Re-executes on replay | Each LLM call is a durable step |
-| **Stop conditions** | `stopWhen`, `prepareStep` | `prepareStep` only |
-| **Structured output** | `Output.object()`, `Output.array()` | Not available |
-| **Step callbacks** | `onStepFinish`, `onChunk` | Not available |
-| **Setup** | Manual stream piping | Automatic |
+| **Tool call durability** | Not individually durable — re-executes with the parent turn | Per tool — mark `"use step"` for a durable, retryable step |
+| **Stop conditions** | `stopWhen`, `prepareStep` | `stopWhen`, `prepareStep` |
+| **Structured output** | `Output.object()`, `Output.array()` | `Output.object()` via `stream()` |
+| **Step callbacks** | `onStepFinish`, `onChunk` | Lifecycle callbacks on the agent |
+| **Setup** | Manual stream piping | `getWritable()` + `createModelCallToUIChunkTransform()` |
-Use `DurableAgent` for most agent use cases. Use `streamText` when you need the additional control.
+Use [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) for most agent use cases. Use `streamText` when you need the additional control. The legacy [`DurableAgent`](/docs/api-reference/workflow-ai/durable-agent) docs remain available but will be deprecated soon.
## Key APIs
**AI SDK** ([docs](https://ai-sdk.dev/docs))
* [`streamText()`](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text) — core streaming function; `toUIMessageStream()` pipes into the durable writable
-* [`tool()` / tool calling](https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling) — tools wrap `"use step"` functions so each tool call is replayed from the log, not re-executed
+* [`tool()` / tool calling](https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling) — tools are plain async functions invoked by `streamText` inside the turn step; they are **not** individually durable in this pattern (see [Pitfalls](#tools-are-not-individually-durable))
* [`stepCountIs()` / `stopWhen`](https://ai-sdk.dev/docs/ai-sdk-core/agents#stop-conditions) — bound the agent loop inside each turn
* [`convertToModelMessages()`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/convert-to-model-messages) / [`createUIMessageStreamResponse()`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/create-ui-message-stream-response) — UI ↔ model message conversion at the API boundary
* [`useChat()`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat) — React hook that consumes the UI message stream on the client
**Workflow SDK**
-* [`"use step"`](/docs/api-reference/workflow/use-step) — makes tool executions durable
+* [`"use step"`](/docs/api-reference/workflow/use-step) — applied to `runTurn` to make each turn a durable, retryable unit
* [`defineHook()`](/docs/api-reference/workflow/define-hook) — suspension point for follow-up messages
* [`getWritable()`](/docs/api-reference/workflow/get-writable) — resumable stream output
* [`getRun()`](/docs/api-reference/workflow-api/get-run) — `run.getReadable({ startIndex })` for slicing per-turn streams
diff --git a/docs/content/docs/v5/foundations/streaming.mdx b/docs/content/docs/v5/foundations/streaming.mdx
index b21a0ea01c..6d33c71092 100644
--- a/docs/content/docs/v5/foundations/streaming.mdx
+++ b/docs/content/docs/v5/foundations/streaming.mdx
@@ -347,6 +347,10 @@ export async function batchProcessingWorkflow(items: string[]) {
### Streaming AI Responses with `DurableAgent`
+
+**Deprecation notice:** `DurableAgent` will be deprecated soon. For new durable agent workflows, use [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) from `@ai-sdk/workflow` instead.
+
+
Stream AI-generated content using [`DurableAgent`](/docs/api-reference/workflow-ai/durable-agent) from `@workflow/ai`. Tools can also emit progress updates to the same stream using [data chunks](https://ai-sdk.dev/docs/ai-sdk-ui/streaming-data#streaming-custom-data) with the [`UIMessageChunk`](https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol) type from the AI SDK:
```typescript title="workflows/ai-assistant.ts" lineNumbers
diff --git a/docs/lib/cookbook-tree.ts b/docs/lib/cookbook-tree.ts
index 25e6748bd5..437ae1becd 100644
--- a/docs/lib/cookbook-tree.ts
+++ b/docs/lib/cookbook-tree.ts
@@ -133,7 +133,7 @@ export const recipes: Record = {
slug: 'durable-agent',
title: 'Durable Agent',
description:
- 'Replace a stateless AI agent with a durable one that survives crashes, retries tool calls, and streams output.',
+ 'Legacy durable agent pattern (deprecated soon — prefer WorkflowAgent from @ai-sdk/workflow). Survives crashes, retries tool calls, and streams output.',
category: 'agent-patterns',
},
'human-in-the-loop': {
@@ -179,7 +179,7 @@ export const recipes: Record = {
slug: 'child-workflows',
title: 'Child Workflows',
description:
- 'Spawn and orchestrate child workflows from a parent, polling for completion and handling partial failures.',
+ 'Spawn and orchestrate child workflows from a parent, waiting for completion via hook resume and handling partial failures.',
category: 'advanced',
},
'distributed-abort-controller': {
diff --git a/packages/ai/src/agent/durable-agent.ts b/packages/ai/src/agent/durable-agent.ts
index b3fbfe6e3c..83f7fc9862 100644
--- a/packages/ai/src/agent/durable-agent.ts
+++ b/packages/ai/src/agent/durable-agent.ts
@@ -751,6 +751,10 @@ export interface DurableAgentStreamResult<
/**
* A class for building durable AI agents within workflows.
*
+ * @deprecated `DurableAgent` will be deprecated soon. For new durable agent
+ * workflows, use `WorkflowAgent` from `@ai-sdk/workflow` instead.
+ * https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent
+ *
* DurableAgent enables you to create AI-powered agents that can maintain state
* across workflow steps, call tools, and gracefully handle interruptions and resumptions.
* It integrates seamlessly with the AI SDK and the Workflow SDK for
diff --git a/skills/workflow/SKILL.md b/skills/workflow/SKILL.md
index 40d9993fad..6f8e096b2e 100644
--- a/skills/workflow/SKILL.md
+++ b/skills/workflow/SKILL.md
@@ -132,6 +132,8 @@ export async function myWorkflow() {
## DurableAgent — AI Agents in Workflows
+> **Deprecation notice:** `DurableAgent` will be deprecated soon. For new durable agent workflows, use [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) from `@ai-sdk/workflow` instead.
+
Use `DurableAgent` to build AI agents that maintain state and survive interruptions. It handles the workflow sandbox automatically (no manual `globalThis.fetch` needed).
```typescript