npx skills add ...
npx skills add openai/plugins --skill workflow
Vercel Workflow DevKit (WDK) expert guidance. Use when building durable workflows, long-running tasks, API routes or agents that need pause/resume, retries, step-based execution, or crash-safe orchestration with Vercel Workflow.
npx skills add openai/plugins --skill workflow
workflow DocumentationYour knowledge of workflow is outdated.
The workflow documentation outlined below matches the installed version of the Workflow DevKit.
Follow these instructions before starting on any workflow-related tasks:
Search the bundled documentation in node_modules/workflow/docs/:
glob "node_modules/workflow/docs/**/*.mdx"grep "your query" node_modules/workflow/docs/Documentation structure in node_modules/workflow/docs/:
getting-started/ - Framework setup (next.mdx, express.mdx, hono.mdx, etc.)foundations/ - Core concepts (workflows-and-steps.mdx, hooks.mdx, streaming.mdx, etc.)api-reference/workflow/ - API docs (sleep.mdx, create-hook.mdx, fatal-error.mdx, etc.)api-reference/workflow-api/ - Client API (start.mdx, get-run.mdx, resume-hook.mdx, etc.)ai/ - AI SDK integration docserrors/ - Error code documentationRelated packages also include bundled docs:
@workflow/ai: node_modules/@workflow/ai/docs/ - DurableAgent and AI integration@workflow/core: node_modules/@workflow/core/docs/ - Core runtime (foundations, how-it-works)@workflow/next: node_modules/@workflow/next/docs/ - Next.js integrationWhen in doubt, update to the latest version of the Workflow DevKit.
Directives:
Essential imports:
"use workflow" functions run in a sandboxed VM. "use step" functions have full Node.js access. Put your logic in steps and use the workflow function purely for orchestration.
Benefits: Steps have automatic retry, results are persisted for replay, and no sandbox restrictions.
When you need logic directly in a workflow function (not in a step), these restrictions apply:
| Limitation | Workaround |
|---|---|
No fetch() | import { fetch } from "workflow" then globalThis.fetch = fetch |
No setTimeout/setInterval | Use sleep("5s") from "workflow" |
| No Node.js modules (fs, crypto, etc.) | Move to a step function |
Example - Using fetch in workflow context:
Note: DurableAgent from @workflow/ai handles the fetch assignment automatically.
Use DurableAgent to build AI agents that maintain state and survive interruptions. It handles the workflow sandbox automatically (no manual globalThis.fetch needed).
Key points:
getWritable<UIMessageChunk>() streams output to the workflow run's default streamexecute functions that need Node.js/npm access should use "use step"execute functions that use workflow primitives (sleep(), createHook()) should NOT use "use step" — they run at the workflow levelmaxSteps limits the number of LLM calls (default is unlimited)result.messages plus new user messages to subsequent agent.stream() callsFor more details on DurableAgent, check the AI docs in node_modules/@workflow/ai/docs/.
Use start() to launch workflows from API routes. start() cannot be called directly in workflow context — wrap it in a step function.
Starting child workflows from inside a workflow — must use a step:
start() returns immediately — it doesn't wait for the workflow to complete. Use run.returnValue to await completion.
Hooks let workflows wait for external data. Use createHook() inside a workflow and resumeHook() from API routes. Deterministic tokens are for createHook() + resumeHook() (server-side) only. createWebhook() always generates random tokens — do not pass a token option to createWebhook().
Hooks implement AsyncIterable — use for await...of to receive multiple events:
Each resumeHook(token, payload) call delivers the next value to the loop.
Use FatalError for permanent failures (no retry), RetryableError for transient failures:
All data passed to/from workflows and steps must be serializable.
Supported types: string, number, boolean, null, undefined, bigint, plain objects, arrays, Date, RegExp, URL, URLSearchParams, Map, Set, Headers, ArrayBuffer, typed arrays, Request, Response, ReadableStream, WritableStream.
Not supported: Functions, class instances, Symbols, WeakMap/WeakSet. Pass data, not callbacks.
Use getWritable() to stream data from workflows. getWritable() can be called in both workflow and step contexts, but you cannot interact with the stream (call getWriter(), write(), close()) directly in a workflow function. The stream must be passed to step functions for actual I/O, or steps can call getWritable() themselves.
Get the stream in a workflow, pass it to a step:
Call getWritable() directly inside a step (no need to pass it):
Use getWritable({ namespace: 'name' }) to create multiple independent streams for different types of data. This is useful for separating logs from primary output, different log levels, agent outputs, metrics, or any distinct data channels. Long-running workflows benefit from namespaced streams because you can replay only the important events (e.g., final results) while keeping verbose logs in a separate stream.
Example: Log levels and agent output separation:
Consuming namespaced streams:
Pro tip: For very long-running sessions (50+ minutes), namespaced streams help manage replay performance. Put verbose/debug output in separate namespaces so you can replay just the important events quickly.
Debugging tips:
--json (-j) on any command for machine-readable output--web to open the Vercel Observability dashboard in your browser--help on any command for full usage detailsWorkflow DevKit provides a Vitest plugin for testing workflows in-process — no running server required.
Unit testing steps: Steps are just functions; without the compiler, "use step" is a no-op. Test them directly:
Integration testing: Use @workflow/vitest for workflows using sleep(), hooks, webhooks, or retries:
Testing webhooks: Use resumeWebhook() with a Request object — no HTTP server needed:
Key APIs:
start() — trigger a workflowrun.returnValue — await workflow completionwaitForHook(run, { token? }) / waitForSleep(run) — wait for workflow to reach a pause pointresumeHook(token, data) / resumeWebhook(token, request) — resume paused workflowsgetRun(runId).wakeUp({ correlationIds }) — skip sleep() callsBest practices:
workflow() plugin) in separate configstestTimeout — workflows may run longer than typical unit testsvi.mock() does not work in integration tests — step dependencies are bundled by esbuild***