← VercelCONTENT HISTORY

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.

WHAT CHANGED · RULE-BASED ANALYSIS

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

Before

[{"relative_path":"references/durable-agent-patterns.md","size_in_bytes":2522}]

After

[{"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 JSON
Full technical diff · 1 changed fields

changed /included_files

BEFORE
[
  {
    "relative_path": "references/durable-agent-patterns.md",
    "size_in_bytes": 2522
  }
]
AFTER
[
  {
    "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