惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
Docker
F
Fortinet All Blogs
爱范儿
爱范儿
博客园 - Franky
MyScale Blog
MyScale Blog
罗磊的独立博客
博客园 - 三生石上(FineUI控件)
B
Blog
P
Proofpoint News Feed
IT之家
IT之家
宝玉的分享
宝玉的分享
D
DataBreaches.Net
S
SegmentFault 最新的问题
Microsoft Azure Blog
Microsoft Azure Blog
GbyAI
GbyAI
M
MIT News - Artificial intelligence
L
LangChain Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
月光博客
月光博客
雷峰网
雷峰网
Stack Overflow Blog
Stack Overflow Blog
量子位
V
V2EX
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻

Workflow SDK Documentation

Patterns for Defining Tools Human-in-the-Loop Building Durable AI Agents Queueing User Messages Resumable Streams Sleep, Suspense, and Scheduling Streaming Updates from Tools API Reference Workflow Globals Changelog Resilient run start Cookbook Building a World Deploying Astro Express Fastify Hono Getting Started NestJS Next.js Nitro Nuxt Python SvelteKit Vite corrupted-event-log fetch-in-workflow hook-conflict Errors
AI SDK
2026-05-31 · via Workflow SDK Documentation

Use AI SDK's streamText directly inside durable workflows when you need the raw AI SDK API or a per-turn durability boundary.

AI SDK 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 below).

For the full AI SDK reference (providers, streamText, generateObject, useChat, tool calling, etc.) see the AI SDK docs. This page covers the Workflow-specific integration points.

For most agent use cases, prefer DurableAgent, which implements the same agent loop as streamText, manages tool calling automatically, and runs tools at workflow scope — each tool can be marked "use step" for per-call durability and retries, or stay at workflow level to use primitives like sleep() and hooks. Use this page's raw streamText() pattern when you want the exact AI SDK API (for example toUIMessageStream(), onChunk, or generateText), or when the durability boundary should be an entire user turn in one step — accepting that tool calls inside that turn are not individually durable.

When to use streamText directly

Use streamText() instead of DurableAgent when you need:

  • The raw AI SDK APIstreamText().toUIMessageStream(), onChunk, smoothStream, or other options that map directly to the streamText return value rather than DurableAgent.stream()
  • Per-turn durability — wrap the entire agent response (model + tools) in a single "use step" function so one user turn is the atomic retry unit; useful when you want all tool calls inside a turn to re-execute together
  • Custom multi-turn orchestration — manual hook loops, per-turn stream slicing (sliceUntilFinish), or other workflow patterns shown below that don't map cleanly to DurableAgent

DurableAgent already supports stopWhen, prepareStep, onStepFinish, structured output (experimental_output), per-step model switching, and provider options. See the DurableAgent reference.

One workflow run = one full conversation. The workflow suspends between turns on a hook and resumes when the next user message arrives. Conversation state, tool history, and intermediate computation all live inside the run.

Because the conversation is one workflow run, it stays on the deployment that started it. If each turn should run on the latest deployment while preserving selected state or streams, see Versioning for the child-run continuation pattern.

  1. One workflow = one conversation. The workflow loops on a hook, keeping allMessages, tool history, and state alive across turns.
  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.
  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".

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 — 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, 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

const tailIndex = await probe.getTailIndex(); // FIRST
await probe.cancel();
await turnHook.resume(runId, { message: text }); // THEN
const stream = run.getReadable({ startIndex: tailIndex + 1 });

Reversing the order races the workflow: by the time you read tailIndex, the next turn has already written its start chunk, and your startIndex + 1 skips past it.

Don't call writable.close() inside a workflow function

I/O operations like closing streams must happen inside a "use step" function. Calling writable.close() directly in the workflow body throws Not supported in workflow functions. When the workflow returns, the runtime closes the underlying writable for you.

Don't use TransformStream.terminate() to slice the stream

A TransformStream with controller.terminate() on the finish chunk seems like the obvious fit for sliceUntilFinish, but throws Invalid state: TransformStream has been terminated when late-arriving chunks hit the transform callback. Manual pumping through a custom ReadableStream (as shown above) sidesteps the problem entirely.

Release the source reader, don't cancel it

In sliceUntilFinish, use reader.releaseLock() in the finally block rather than source.cancel(). Cancelling propagates upstream and closes the durable writable, breaking the next turn. Releasing the lock just detaches our reader; the durable stream keeps flowing.

Handle stale runId gracefully

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() (this pattern)DurableAgent
Tool loopAI SDK handles via stopWhenHandles internally (AI SDK–compatible options)
LLM call durabilityRe-executes with the parent turnEach LLM call is a durable step
Tool call durabilityNot individually durable — re-executes with the parent turnPer tool — mark "use step" for a durable, retryable step, or keep at workflow level for sleep() / hooks
Stop conditionsstopWhen, prepareStepstopWhen, prepareStep
Structured outputOutput.object(), Output.array()experimental_output (Output.object(), Output.text())
Step callbacksonStepFinish, onChunk, etc.onStepFinish, onFinish, onError, onAbort (onChunk not available)
SetupManual stream piping and turn slicingAutomatic

Use DurableAgent for most agent use cases. Use streamText when you need the raw AI SDK surface or a per-turn durability boundary.

AI SDK (docs)

Workflow SDK