Update to Vercel
Snapshot Sep 30, 2026 · 23:18 UTC · version 0.21.4
Collection source: not recorded for this historical snapshot. These snapshots do not have a confirmed matching collection source. Differences in file lists alone do not establish changes to the package.
Supporting file metadata differs
Newly listed paths: agents/openai.yaml. This compares saved file lists, not package contents; a different collection source can change the list.
Observed in package metadata. These changes alone do not establish a new customer-facing feature.
Supporting files
[{"relative_path":"references/durable-agent-patterns.md","size_in_bytes":2522}]
[{"relative_path":"agents/openai.yaml","size_in_bytes":106},{"relative_path":"references/durable-agent-patterns.md","size_in_bytes":2522}]
Compare saved observations
Download comparison JSONFull technical diff · 1 changed fields
changed /included_files
[
{
"relative_path": "references/durable-agent-patterns.md",
"size_in_bytes": 2522
}
][
{
"relative_path": "agents/openai.yaml",
"size_in_bytes": 106
},
{
"relative_path": "references/durable-agent-patterns.md",
"size_in_bytes": 2522
}
]Full snapshot data
{
"name": "workflow",
"description": "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.",
"included_files": [
{
"relative_path": "agents/openai.yaml",
"size_in_bytes": 106
},
{
"relative_path": "references/durable-agent-patterns.md",
"size_in_bytes": 2522
}
],
"skill_md_contents": "---\nname: workflow\ndescription: 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.\nmetadata:\n priority: 9\n docs:\n - \"https://vercel.com/docs/workflow\"\n - \"https://useworkflow.dev\"\n sitemap: \"https://vercel.com/sitemap/docs.xml\"\n pathPatterns:\n - 'lib/workflow/**'\n - 'src/lib/workflow/**'\n - 'lib/workflow.*'\n - 'src/lib/workflow.*'\n - 'workflow.*'\n - '*workflow*'\n importPatterns:\n - '@vercel/workflow'\n - 'workflow'\n - '@workflow/*'\n - '*workflow*'\n bashPatterns:\n - '\\bnpm\\s+(install|i|add)\\s+[^\\n]*@vercel/workflow\\b'\n - '\\bpnpm\\s+(install|i|add)\\s+[^\\n]*@vercel/workflow\\b'\n - '\\bbun\\s+(install|i|add)\\s+[^\\n]*@vercel/workflow\\b'\n - '\\byarn\\s+add\\s+[^\\n]*@vercel/workflow\\b'\n - '\\bnpm\\s+(install|i|add)\\s+[^\\n]*\\bworkflow\\b'\n - '\\bpnpm\\s+(install|i|add)\\s+[^\\n]*\\bworkflow\\b'\n - '\\bbun\\s+(install|i|add)\\s+[^\\n]*\\bworkflow\\b'\n - '\\byarn\\s+add\\s+[^\\n]*\\bworkflow\\b'\n - '\\bnpm\\s+(install|i|add)\\s+[^\\n]*@workflow/'\n - '\\bpnpm\\s+(install|i|add)\\s+[^\\n]*@workflow/'\n - '\\bbun\\s+(install|i|add)\\s+[^\\n]*@workflow/'\n - '\\byarn\\s+add\\s+[^\\n]*@workflow/'\n - '\\bnpx\\s+workflow(?:@latest)?\\b'\n - '\\bbunx\\s+workflow(?:@latest)?\\b'\n promptSignals:\n phrases:\n # Direct workflow mentions\n - \"vercel workflow\"\n - \"workflow devkit\"\n - \"durable workflow\"\n - \"durable execution\"\n - \"durable function\"\n - \"durable pipeline\"\n - \"durable process\"\n - \"durable agent\"\n - \"durable chat\"\n - \"step function\"\n - \"step functions\"\n - \"use workflow\"\n - \"use step\"\n # Pipeline / multi-step language (the BIG gap — natural product prompts)\n - \"multi-step pipeline\"\n - \"multi step pipeline\"\n - \"multi-step process\"\n - \"multi step process\"\n - \"multi-step creation\"\n - \"multi-step generation\"\n - \"processing pipeline\"\n - \"creation pipeline\"\n - \"generation pipeline\"\n - \"content pipeline\"\n - \"production pipeline\"\n - \"approval pipeline\"\n - \"ingestion pipeline\"\n - \"streams progress\"\n - \"stream progress\"\n - \"streams each phase\"\n - \"streams each step\"\n - \"streams each\"\n - \"stream each\"\n # Reliability / durability language (missed in customer-support eval)\n - \"survive page reload\"\n - \"survive page reloads\"\n - \"survive a crash\"\n - \"survive crashes\"\n - \"survive network\"\n - \"fault-tolerant\"\n - \"fault tolerant\"\n - \"crash-safe\"\n - \"crash safe\"\n - \"automatically retry\"\n - \"auto retry\"\n - \"retry on failure\"\n - \"retry on error\"\n - \"reliable and retry\"\n - \"reliable processing\"\n - \"individually reliable\"\n - \"each step reliable\"\n - \"each step should be reliable\"\n - \"steps should be reliable\"\n - \"reliable with automatic retry\"\n - \"reliable with retry\"\n - \"retry on transient\"\n - \"transient failures\"\n - \"session persistence\"\n - \"session should persist\"\n - \"session survives\"\n - \"reconnect automatically\"\n - \"auto reconnect\"\n - \"reconnect if the network\"\n - \"reconnect on disconnect\"\n - \"resume after failure\"\n - \"resume after crash\"\n - \"resume on reconnect\"\n # Human-in-the-loop / approval patterns\n - \"human-in-the-loop\"\n - \"human in the loop\"\n - \"wait for approval\"\n - \"approval step\"\n - \"approval before\"\n - \"editorial approval\"\n - \"manual approval\"\n - \"wait for user\"\n - \"pause until\"\n - \"wait for response\"\n - \"callback url\"\n - \"webhook callback\"\n # Conversational AI with durability\n - \"chat should survive\"\n - \"chat survives\"\n - \"conversation should persist\"\n - \"conversation persists\"\n - \"conversation should survive\"\n # Sequential / chain / trigger orchestration language\n - \"sequential chain\"\n - \"email chain\"\n - \"chain of emails\"\n - \"chain of steps\"\n - \"chain engine\"\n - \"chain with triggers\"\n - \"trigger chain\"\n - \"triggered chain\"\n - \"webhook chain\"\n - \"webhook pipeline\"\n - \"webhook orchestration\"\n - \"multi-service trigger\"\n - \"cross-service trigger\"\n - \"various triggers\"\n - \"different triggers\"\n - \"triggers from different\"\n - \"triggers from various\"\n - \"sequential steps\"\n - \"sequential pipeline\"\n - \"sequential process\"\n - \"sequential emails\"\n - \"escalation chain\"\n - \"escalation pipeline\"\n - \"state machine\"\n - \"step-based\"\n - \"step based\"\n - \"delay between steps\"\n - \"delay between emails\"\n - \"delayed steps\"\n - \"conditional steps\"\n - \"skip steps\"\n - \"branch based on\"\n - \"wait for webhook\"\n - \"wait for trigger\"\n - \"wait for event\"\n - \"orchestrate emails\"\n - \"orchestrate webhooks\"\n - \"orchestrate services\"\n - \"chain across services\"\n # Debugging\n - \"workflow stuck\"\n - \"workflow hung\"\n - \"workflow hanging\"\n - \"workflow waiting\"\n - \"workflow failing\"\n - \"workflow timeout\"\n - \"workflow not running\"\n - \"workflow error\"\n - \"check workflow\"\n - \"workflow logs\"\n - \"workflow run status\"\n - \"debug workflow\"\n - \"workflow not finishing\"\n - \"workflow not responding\"\n - \"workflow stalled\"\n - \"workflow pending\"\n - \"step is stuck\"\n - \"step is hanging\"\n - \"why is my workflow\"\n - \"workflow run\"\n - \"step failed\"\n - \"run status\"\n - \"run failed\"\n - \"run logs\"\n - \"workflow run failed\"\n - \"workflow step failed\"\n allOf:\n - [workflow, durable]\n - [workflow, retry]\n - [workflow, resume]\n - [pause, resume]\n - [survive, crash]\n - [survive, reload]\n - [survive, disconnect]\n - [pipeline, stream]\n - [pipeline, step]\n - [pipeline, durable]\n - [pipeline, reliable]\n - [pipeline, retry]\n - [multi-step, stream]\n - [multi-step, reliable]\n - [generation, pipeline]\n - [creation, pipeline]\n - [process, stream]\n - [process, reliable]\n - [process, retry]\n - [retry, failure]\n - [retry, error]\n - [retry, automatically]\n - [retry, transient]\n - [reliable, retry]\n - [individually, reliable]\n - [steps, reliable]\n - [sandbox, reliable]\n - [sandbox, retry]\n - [reconnect, network]\n - [reconnect, drop]\n - [reconnect, disconnect]\n - [session, persist]\n - [session, survive]\n - [session, reload]\n - [session, reconnect]\n - [chat, survive]\n - [chat, persist]\n - [chat, reconnect]\n - [chat, durable]\n - [chat, fault]\n - [conversation, persist]\n - [conversation, survive]\n - [approval, wait]\n - [approval, human]\n - [each, step]\n - [each, phase]\n - [each, stage]\n - [step, reliable]\n - [step, retry]\n # Chain / trigger / sequential orchestration\n - [chain, trigger]\n - [chain, sequential]\n - [chain, email]\n - [chain, webhook]\n - [chain, delay]\n - [chain, step]\n - [chain, escalat]\n - [sequential, trigger]\n - [sequential, email]\n - [sequential, step]\n - [sequential, webhook]\n - [trigger, orchestrat]\n - [trigger, service]\n - [trigger, delay]\n - [trigger, sequential]\n - [webhook, chain]\n - [webhook, orchestrat]\n - [webhook, pipeline]\n - [webhook, sequential]\n - [email, trigger]\n - [email, pipeline]\n - [email, sequential]\n - [email, delay]\n - [email, escalat]\n - [escalat, trigger]\n - [escalat, step]\n - [escalat, email]\n - [state, machine]\n - [conditional, step]\n - [conditional, skip]\n - [branch, condition]\n - [wait, webhook]\n - [wait, trigger]\n - [wait, event]\n - [workflow, stuck]\n - [workflow, hung]\n - [workflow, timeout]\n - [workflow, error]\n - [workflow, logs]\n - [workflow, debug]\n - [workflow, check]\n - [workflow, failing]\n - [workflow, status]\n - [run, status]\n - [step, failed]\n - [step, stuck]\n - [step, timeout]\n - [workflow, run]\n - [run, logs]\n anyOf:\n - \"long-running\"\n - \"long running\"\n - \"multi-step\"\n - \"multi step\"\n - \"pipeline\"\n - \"orchestration\"\n - \"step-by-step\"\n - \"step by step\"\n - \"each piece\"\n - \"each step\"\n - \"each phase\"\n - \"each stage\"\n - \"phase\"\n - \"phases\"\n - \"stage\"\n - \"stages\"\n - \"durable\"\n - \"reliable\"\n - \"fault-tolerant\"\n - \"retry\"\n - \"reconnect\"\n - \"survive\"\n - \"persist\"\n - \"approval\"\n - \"chain\"\n - \"sequential\"\n - \"trigger\"\n - \"webhook\"\n - \"escalation\"\n - \"state machine\"\n - \"orchestrate\"\n - \"orchestration\"\n noneOf:\n - \"github actions\"\n - \".github/workflows\"\n - \"ci workflow\"\n - \"aws step functions\"\n minScore: 4\n---\n\n## *CRITICAL*: Always Use Correct `workflow` Documentation\n\nYour knowledge of `workflow` is outdated.\n\nThe `workflow` documentation outlined below matches the installed version of the Workflow DevKit.\nFollow these instructions before starting on any `workflow`-related tasks:\n\nSearch the bundled documentation in `node_modules/workflow/docs/`:\n\n1. **Find docs**: `glob \"node_modules/workflow/docs/**/*.mdx\"`\n2. **Search content**: `grep \"your query\" node_modules/workflow/docs/`\n\nDocumentation structure in `node_modules/workflow/docs/`:\n\n- `getting-started/` - Framework setup (next.mdx, express.mdx, hono.mdx, etc.)\n- `foundations/` - Core concepts (workflows-and-steps.mdx, hooks.mdx, streaming.mdx, etc.)\n- `api-reference/workflow/` - API docs (sleep.mdx, create-hook.mdx, fatal-error.mdx, etc.)\n- `api-reference/workflow-api/` - Client API (start.mdx, get-run.mdx, resume-hook.mdx, etc.)\n- `ai/` - AI SDK integration docs\n- `errors/` - Error code documentation\n\nRelated packages also include bundled docs:\n\n- `@workflow/ai`: `node_modules/@workflow/ai/docs/` - DurableAgent and AI integration\n- `@workflow/core`: `node_modules/@workflow/core/docs/` - Core runtime (foundations, how-it-works)\n- `@workflow/next`: `node_modules/@workflow/next/docs/` - Next.js integration\n\n**When in doubt, update to the latest version of the Workflow DevKit.**\n\n### Official Resources\n\n- **Website**: https://useworkflow.dev\n- **GitHub**: https://github.com/vercel/workflow\n\n### Quick Reference\n\n**Directives:**\n\n```typescript\n\"use workflow\"; // First line - makes async function durable\n\"use step\"; // First line - makes function a cached, retryable unit\n```\n\n**Essential imports:**\n\n```typescript\n// Workflow primitives\nimport { sleep, fetch, createHook, createWebhook, getWritable } from \"workflow\";\nimport { FatalError, RetryableError } from \"workflow\";\nimport { getWorkflowMetadata, getStepMetadata } from \"workflow\";\n\n// API operations\nimport { start, getRun, resumeHook, resumeWebhook } from \"workflow/api\";\n\n// Framework integrations\nimport { withWorkflow } from \"workflow/next\";\nimport { workflow } from \"workflow/vite\";\nimport { workflow } from \"workflow/astro\";\n// Or use modules: [\"workflow/nitro\"] for Nitro/Nuxt\n\n// AI agent\nimport { DurableAgent } from \"@workflow/ai/agent\";\n```\n\n## Prefer Step Functions to Avoid Sandbox Errors\n\n`\"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.\n\n```typescript\n// Steps have full Node.js and npm access\nasync function fetchUserData(userId: string) {\n \"use step\";\n const response = await fetch(`https://api.example.com/users/${userId}`);\n return response.json();\n}\n\nasync function processWithAI(data: any) {\n \"use step\";\n // AI SDK works in steps without workarounds\n return await generateText({\n model: openai(\"gpt-4\"),\n prompt: `Process: ${JSON.stringify(data)}`,\n });\n}\n\n// Workflow orchestrates steps - no sandbox issues\nexport async function dataProcessingWorkflow(userId: string) {\n \"use workflow\";\n const data = await fetchUserData(userId);\n const processed = await processWithAI(data);\n return { success: true, processed };\n}\n```\n\n**Benefits:** Steps have automatic retry, results are persisted for replay, and no sandbox restrictions.\n\n## Workflow Sandbox Limitations\n\nWhen you need logic directly in a workflow function (not in a step), these restrictions apply:\n\n| Limitation | Workaround |\n|------------|------------|\n| No `fetch()` | `import { fetch } from \"workflow\"` then `globalThis.fetch = fetch` |\n| No `setTimeout`/`setInterval` | Use `sleep(\"5s\")` from `\"workflow\"` |\n| No Node.js modules (fs, crypto, etc.) | Move to a step function |\n\n**Example - Using fetch in workflow context:**\n\n```typescript\nimport { fetch } from \"workflow\";\n\nexport async function myWorkflow() {\n \"use workflow\";\n globalThis.fetch = fetch; // Required for AI SDK and HTTP libraries\n // Now generateText() and other libraries work\n}\n```\n\n**Note:** `DurableAgent` from `@workflow/ai` handles the fetch assignment automatically.\n\n## DurableAgent — AI Agents in Workflows\n\nUse `DurableAgent` to build AI agents that maintain state and survive interruptions. It handles the workflow sandbox automatically (no manual `globalThis.fetch` needed).\n\n```typescript\nimport { DurableAgent } from \"@workflow/ai/agent\";\nimport { getWritable } from \"workflow\";\nimport { z } from \"zod\";\nimport type { UIMessageChunk } from \"ai\";\n\nasync function lookupData({ query }: { query: string }) {\n \"use step\";\n // Step functions have full Node.js access\n return `Results for \"${query}\"`;\n}\n\nexport async function myAgentWorkflow(userMessage: string) {\n \"use workflow\";\n\n const agent = new DurableAgent({\n model: \"anthropic/claude-sonnet-4-5\",\n system: \"You are a helpful assistant.\",\n tools: {\n lookupData: {\n description: \"Search for information\",\n inputSchema: z.object({ query: z.string() }),\n execute: lookupData,\n },\n },\n });\n\n const result = await agent.stream({\n messages: [{ role: \"user\", content: userMessage }],\n writable: getWritable<UIMessageChunk>(),\n maxSteps: 10,\n });\n\n return result.messages;\n}\n```\n\n**Key points:**\n- `getWritable<UIMessageChunk>()` streams output to the workflow run's default stream\n- Tool `execute` functions that need Node.js/npm access should use `\"use step\"`\n- Tool `execute` functions that use workflow primitives (`sleep()`, `createHook()`) should **NOT** use `\"use step\"` — they run at the workflow level\n- `maxSteps` limits the number of LLM calls (default is unlimited)\n- Multi-turn: pass `result.messages` plus new user messages to subsequent `agent.stream()` calls\n\n**For more details on `DurableAgent`, check the AI docs in `node_modules/@workflow/ai/docs/`.**\n\n## Starting Workflows & Child Workflows\n\nUse `start()` to launch workflows from API routes. **`start()` cannot be called directly in workflow context** — wrap it in a step function.\n\n```typescript\nimport { start } from \"workflow/api\";\n\n// From an API route — works directly\nexport async function POST() {\n const run = await start(myWorkflow, [arg1, arg2]);\n return Response.json({ runId: run.runId });\n}\n\n// No-args workflow\nconst run = await start(noArgWorkflow);\n```\n\n**Starting child workflows from inside a workflow — must use a step:**\n\n```typescript\nimport { start } from \"workflow/api\";\n\n// Wrap start() in a step function\nasync function triggerChild(data: string) {\n \"use step\";\n const run = await start(childWorkflow, [data]);\n return run.runId;\n}\n\nexport async function parentWorkflow() {\n \"use workflow\";\n const childRunId = await triggerChild(\"some data\"); // Fire-and-forget via step\n await sleep(\"1h\");\n}\n```\n\n`start()` returns immediately — it doesn't wait for the workflow to complete. Use `run.returnValue` to await completion.\n\n## Hooks — Pause & Resume with External Events\n\nHooks 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()`.\n\n### Single event\n\n```typescript\nimport { createHook } from \"workflow\";\n\nexport async function approvalWorkflow() {\n \"use workflow\";\n\n const hook = createHook<{ approved: boolean }>({\n token: \"approval-123\", // deterministic token for external systems\n });\n\n const result = await hook; // Workflow suspends here\n return result.approved;\n}\n```\n\n### Multiple events (iterable hooks)\n\nHooks implement `AsyncIterable` — use `for await...of` to receive multiple events:\n\n```typescript\nimport { createHook } from \"workflow\";\n\nexport async function chatWorkflow(channelId: string) {\n \"use workflow\";\n\n const hook = createHook<{ text: string; done?: boolean }>({\n token: `chat-${channelId}`,\n });\n\n for await (const event of hook) {\n await processMessage(event.text);\n if (event.done) break;\n }\n}\n```\n\nEach `resumeHook(token, payload)` call delivers the next value to the loop.\n\n### Resuming from API routes\n\n```typescript\nimport { resumeHook } from \"workflow/api\";\n\nexport async function POST(req: Request) {\n const { token, data } = await req.json();\n await resumeHook(token, data);\n return new Response(\"ok\");\n}\n```\n\n## Error Handling\n\nUse `FatalError` for permanent failures (no retry), `RetryableError` for transient failures:\n\n```typescript\nimport { FatalError, RetryableError } from \"workflow\";\n\nif (res.status >= 400 && res.status < 500) {\n throw new FatalError(`Client error: ${res.status}`);\n}\nif (res.status === 429) {\n throw new RetryableError(\"Rate limited\", { retryAfter: \"5m\" });\n}\n```\n\n## Serialization\n\nAll data passed to/from workflows and steps must be serializable.\n\n**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.\n\n**Not supported:** Functions, class instances, Symbols, WeakMap/WeakSet. Pass data, not callbacks.\n\n## Streaming\n\nUse `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.\n\n**Get the stream in a workflow, pass it to a step:**\n```typescript\nimport { getWritable } from \"workflow\";\n\nexport async function myWorkflow() {\n \"use workflow\";\n const writable = getWritable();\n await writeData(writable, \"hello world\");\n}\n\nasync function writeData(writable: WritableStream, chunk: string) {\n \"use step\";\n const writer = writable.getWriter();\n try {\n await writer.write(chunk);\n } finally {\n writer.releaseLock();\n }\n}\n```\n\n**Call `getWritable()` directly inside a step (no need to pass it):**\n```typescript\nimport { getWritable } from \"workflow\";\n\nasync function streamData(chunk: string) {\n \"use step\";\n const writer = getWritable().getWriter();\n try {\n await writer.write(chunk);\n } finally {\n writer.releaseLock();\n }\n}\n```\n\n### Namespaced Streams\n\nUse `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.\n\n**Example: Log levels and agent output separation:**\n```typescript\nimport { getWritable } from \"workflow\";\n\ntype LogEntry = { level: \"debug\" | \"info\" | \"warn\" | \"error\"; message: string; timestamp: number };\ntype AgentOutput = { type: \"thought\" | \"action\" | \"result\"; content: string };\n\nasync function logDebug(message: string) {\n \"use step\";\n const writer = getWritable<LogEntry>({ namespace: \"logs:debug\" }).getWriter();\n try {\n await writer.write({ level: \"debug\", message, timestamp: Date.now() });\n } finally {\n writer.releaseLock();\n }\n}\n\nasync function logInfo(message: string) {\n \"use step\";\n const writer = getWritable<LogEntry>({ namespace: \"logs:info\" }).getWriter();\n try {\n await writer.write({ level: \"info\", message, timestamp: Date.now() });\n } finally {\n writer.releaseLock();\n }\n}\n\nasync function emitAgentThought(thought: string) {\n \"use step\";\n const writer = getWritable<AgentOutput>({ namespace: \"agent:thoughts\" }).getWriter();\n try {\n await writer.write({ type: \"thought\", content: thought });\n } finally {\n writer.releaseLock();\n }\n}\n\nasync function emitAgentResult(result: string) {\n \"use step\";\n // Important results go to the default stream for easy replay\n const writer = getWritable<AgentOutput>().getWriter();\n try {\n await writer.write({ type: \"result\", content: result });\n } finally {\n writer.releaseLock();\n }\n}\n\nexport async function agentWorkflow(task: string) {\n \"use workflow\";\n \n await logInfo(`Starting task: ${task}`);\n await logDebug(\"Initializing agent context\");\n await emitAgentThought(\"Analyzing the task requirements...\");\n \n // ... agent processing ...\n \n await emitAgentResult(\"Task completed successfully\");\n await logInfo(\"Workflow finished\");\n}\n```\n\n**Consuming namespaced streams:**\n```typescript\nimport { start, getRun } from \"workflow/api\";\nimport { agentWorkflow } from \"./workflows/agent\";\n\nexport async function POST(request: Request) {\n const run = await start(agentWorkflow, [\"process data\"]);\n\n // Access specific streams by namespace\n const results = run.getReadable({ namespace: undefined }); // Default stream (important results)\n const infoLogs = run.getReadable({ namespace: \"logs:info\" });\n const debugLogs = run.getReadable({ namespace: \"logs:debug\" });\n const thoughts = run.getReadable({ namespace: \"agent:thoughts\" });\n\n // Return only important results for most clients\n return new Response(results, { headers: { \"Content-Type\": \"application/json\" } });\n}\n\n// Resume from a specific point (useful for long sessions)\nexport async function GET(request: Request) {\n const { searchParams } = new URL(request.url);\n const runId = searchParams.get(\"runId\")!;\n const startIndex = parseInt(searchParams.get(\"startIndex\") || \"0\", 10);\n \n const run = getRun(runId);\n // Resume only the important stream, skip verbose debug logs\n const stream = run.getReadable({ startIndex });\n \n return new Response(stream);\n}\n```\n\n**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.\n\n## Debugging\n\n```bash\n# Check workflow endpoints are reachable\nnpx workflow health\nnpx workflow health --port 3001 # Non-default port\n\n# Visual dashboard for runs\nnpx workflow web\nnpx workflow web <run_id>\n\n# CLI inspection (use --json for machine-readable output, --help for full usage)\nnpx workflow inspect runs\nnpx workflow inspect run <run_id>\n\n# For Vercel-deployed projects, specify backend and project\nnpx workflow inspect runs --backend vercel --project <project-name> --team <team-slug>\nnpx workflow inspect run <run_id> --backend vercel --project <project-name> --team <team-slug>\n\n# Open Vercel dashboard in browser for a specific run\nnpx workflow inspect run <run_id> --web\nnpx workflow web <run_id> --backend vercel --project <project-name> --team <team-slug>\n\n# Cancel a running workflow\nnpx workflow cancel <run_id>\nnpx workflow cancel <run_id> --backend vercel --project <project-name> --team <team-slug>\n# --env defaults to \"production\"; use --env preview for preview deployments\n```\n\n**Debugging tips:**\n- Use `--json` (`-j`) on any command for machine-readable output\n- Use `--web` to open the Vercel Observability dashboard in your browser\n- Use `--help` on any command for full usage details\n- Only import workflow APIs you actually use. Unused imports can cause 500 errors.\n\n## Testing Workflows\n\nWorkflow DevKit provides a Vitest plugin for testing workflows in-process — no running server required.\n\n**Unit testing steps:** Steps are just functions; without the compiler, `\"use step\"` is a no-op. Test them directly:\n\n```typescript\nimport { describe, it, expect } from \"vitest\";\nimport { createUser } from \"./user-signup\";\n\ndescribe(\"createUser step\", () => {\n it(\"should create a user\", async () => {\n const user = await createUser(\"test@example.com\");\n expect(user.email).toBe(\"test@example.com\");\n });\n});\n```\n\n**Integration testing:** Use `@workflow/vitest` for workflows using `sleep()`, hooks, webhooks, or retries:\n\n```typescript\n// vitest.integration.config.ts\nimport { defineConfig } from \"vitest/config\";\nimport { workflow } from \"@workflow/vitest\";\n\nexport default defineConfig({\n plugins: [workflow()],\n test: {\n include: [\"**/*.integration.test.ts\"],\n testTimeout: 60_000,\n },\n});\n```\n\n```typescript\n// approval.integration.test.ts\nimport { describe, it, expect } from \"vitest\";\nimport { start, getRun, resumeHook } from \"workflow/api\";\nimport { waitForHook, waitForSleep } from \"@workflow/vitest\";\nimport { approvalWorkflow } from \"./approval\";\n\ndescribe(\"approvalWorkflow\", () => {\n it(\"should publish when approved\", async () => {\n const run = await start(approvalWorkflow, [\"doc-123\"]);\n\n // Wait for the hook, then resume it\n await waitForHook(run, { token: \"approval:doc-123\" });\n await resumeHook(\"approval:doc-123\", { approved: true, reviewer: \"alice\" });\n\n // Wait for sleep, then wake it up\n const sleepId = await waitForSleep(run);\n await getRun(run.runId).wakeUp({ correlationIds: [sleepId] });\n\n const result = await run.returnValue;\n expect(result).toEqual({ status: \"published\", reviewer: \"alice\" });\n });\n});\n```\n\n**Testing webhooks:** Use `resumeWebhook()` with a `Request` object — no HTTP server needed:\n\n```typescript\nimport { start, resumeWebhook } from \"workflow/api\";\nimport { waitForHook } from \"@workflow/vitest\";\n\nconst run = await start(ingestWorkflow, [\"ep-1\"]);\nconst hook = await waitForHook(run); // Discovers the random webhook token\nawait resumeWebhook(hook.token, new Request(\"https://example.com/webhook\", {\n method: \"POST\",\n body: JSON.stringify({ event: \"order.created\" }),\n}));\n```\n\n**Key APIs:**\n- `start()` — trigger a workflow\n- `run.returnValue` — await workflow completion\n- `waitForHook(run, { token? })` / `waitForSleep(run)` — wait for workflow to reach a pause point\n- `resumeHook(token, data)` / `resumeWebhook(token, request)` — resume paused workflows\n- `getRun(runId).wakeUp({ correlationIds })` — skip `sleep()` calls\n\n**Best practices:**\n- Keep unit tests (no plugin) and integration tests (`workflow()` plugin) in separate configs\n- Use deterministic hook tokens based on test data for easier resumption\n- Set generous `testTimeout` — workflows may run longer than typical unit tests\n- `vi.mock()` does **not** work in integration tests — step dependencies are bundled by esbuild\n"
}SHA-256: f7fd9aca40475ba4aba0650eae05db162b11f5d875633c6c6f78a562c0518a4f