---
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.
metadata:
  priority: 9
  docs:
    - "https://vercel.com/docs/workflow"
    - "https://useworkflow.dev"
  sitemap: "https://vercel.com/sitemap/docs.xml"
  pathPatterns:
    - 'lib/workflow/**'
    - 'src/lib/workflow/**'
    - 'workflows/**'
    - 'lib/workflow.*'
    - 'src/lib/workflow.*'
    - 'workflow.*'
    - '*workflow*'
    - '*workflow*/**'
    # Chain / pipeline / orchestration engine files
    - '**/chain-engine*'
    - '**/chain_engine*'
    - '**/chainEngine*'
    - '**/pipeline-engine*'
    - '**/pipeline_engine*'
    - '**/pipelineEngine*'
    - '**/state-machine*'
    - '**/state_machine*'
    - '**/stateMachine*'
    - '**/orchestrat*'
    - '**/escalation*'
  importPatterns:
    - '@vercel/workflow'
    - 'workflow'
    - '@workflow/*'
    - '*workflow*'
  bashPatterns:
    - '\bnpm\s+(install|i|add)\s+[^\n]*@vercel/workflow\b'
    - '\bpnpm\s+(install|i|add)\s+[^\n]*@vercel/workflow\b'
    - '\bbun\s+(install|i|add)\s+[^\n]*@vercel/workflow\b'
    - '\byarn\s+add\s+[^\n]*@vercel/workflow\b'
    - '\bnpm\s+(install|i|add)\s+[^\n]*\bworkflow\b'
    - '\bpnpm\s+(install|i|add)\s+[^\n]*\bworkflow\b'
    - '\bbun\s+(install|i|add)\s+[^\n]*\bworkflow\b'
    - '\byarn\s+add\s+[^\n]*\bworkflow\b'
    - '\bnpm\s+(install|i|add)\s+[^\n]*@workflow/'
    - '\bpnpm\s+(install|i|add)\s+[^\n]*@workflow/'
    - '\bbun\s+(install|i|add)\s+[^\n]*@workflow/'
    - '\byarn\s+add\s+[^\n]*@workflow/'
    - '\bnpx\s+workflow(?:@latest)?\b'
    - '\bbunx\s+workflow(?:@latest)?\b'
  promptSignals:
    phrases:
      # Direct workflow mentions
      - "vercel workflow"
      - "workflow devkit"
      - "durable workflow"
      - "durable execution"
      - "durable function"
      - "durable pipeline"
      - "durable process"
      - "durable agent"
      - "durable chat"
      - "step function"
      - "step functions"
      - "use workflow"
      - "use step"
      # Pipeline / multi-step language (the BIG gap — natural product prompts)
      - "multi-step pipeline"
      - "multi step pipeline"
      - "multi-step process"
      - "multi step process"
      - "multi-step creation"
      - "multi-step generation"
      - "processing pipeline"
      - "creation pipeline"
      - "generation pipeline"
      - "content pipeline"
      - "production pipeline"
      - "approval pipeline"
      - "ingestion pipeline"
      - "streams progress"
      - "stream progress"
      - "streams each phase"
      - "streams each step"
      - "streams each"
      - "stream each"
      # Reliability / durability language (missed in customer-support eval)
      - "survive page reload"
      - "survive page reloads"
      - "survive a crash"
      - "survive crashes"
      - "survive network"
      - "fault-tolerant"
      - "fault tolerant"
      - "crash-safe"
      - "crash safe"
      - "automatically retry"
      - "auto retry"
      - "retry on failure"
      - "retry on error"
      - "reliable and retry"
      - "reliable processing"
      - "individually reliable"
      - "each step reliable"
      - "each step should be reliable"
      - "steps should be reliable"
      - "reliable with automatic retry"
      - "reliable with retry"
      - "retry on transient"
      - "transient failures"
      - "session persistence"
      - "session should persist"
      - "session survives"
      - "reconnect automatically"
      - "auto reconnect"
      - "reconnect if the network"
      - "reconnect on disconnect"
      - "resume after failure"
      - "resume after crash"
      - "resume on reconnect"
      # Human-in-the-loop / approval patterns
      - "human-in-the-loop"
      - "human in the loop"
      - "wait for approval"
      - "approval step"
      - "approval before"
      - "editorial approval"
      - "manual approval"
      - "wait for user"
      - "pause until"
      - "wait for response"
      - "callback url"
      - "webhook callback"
      # Conversational AI with durability
      - "chat should survive"
      - "chat survives"
      - "conversation should persist"
      - "conversation persists"
      - "conversation should survive"
      # Sequential / chain / trigger orchestration language
      - "sequential chain"
      - "email chain"
      - "chain of emails"
      - "chain of steps"
      - "chain engine"
      - "chain with triggers"
      - "trigger chain"
      - "triggered chain"
      - "webhook chain"
      - "webhook pipeline"
      - "webhook orchestration"
      - "multi-service trigger"
      - "cross-service trigger"
      - "various triggers"
      - "different triggers"
      - "triggers from different"
      - "triggers from various"
      - "sequential steps"
      - "sequential pipeline"
      - "sequential process"
      - "sequential emails"
      - "escalation chain"
      - "escalation pipeline"
      - "state machine"
      - "step-based"
      - "step based"
      - "delay between steps"
      - "delay between emails"
      - "delayed steps"
      - "conditional steps"
      - "skip steps"
      - "branch based on"
      - "wait for webhook"
      - "wait for trigger"
      - "wait for event"
      - "orchestrate emails"
      - "orchestrate webhooks"
      - "orchestrate services"
      - "chain across services"
      # Debugging
      - "workflow stuck"
      - "workflow hung"
      - "workflow hanging"
      - "workflow waiting"
      - "workflow failing"
      - "workflow timeout"
      - "workflow not running"
      - "workflow error"
      - "check workflow"
      - "workflow logs"
      - "workflow run status"
      - "debug workflow"
      - "workflow not finishing"
      - "workflow not responding"
      - "workflow stalled"
      - "workflow pending"
      - "step is stuck"
      - "step is hanging"
      - "why is my workflow"
      - "workflow run"
      - "step failed"
      - "run status"
      - "run failed"
      - "run logs"
      - "workflow run failed"
      - "workflow step failed"
    allOf:
      - [workflow, durable]
      - [workflow, retry]
      - [workflow, resume]
      - [pause, resume]
      - [survive, crash]
      - [survive, reload]
      - [survive, disconnect]
      - [pipeline, stream]
      - [pipeline, step]
      - [pipeline, durable]
      - [pipeline, reliable]
      - [pipeline, retry]
      - [multi-step, stream]
      - [multi-step, reliable]
      - [generation, pipeline]
      - [creation, pipeline]
      - [process, stream]
      - [process, reliable]
      - [process, retry]
      - [retry, failure]
      - [retry, error]
      - [retry, automatically]
      - [retry, transient]
      - [reliable, retry]
      - [individually, reliable]
      - [steps, reliable]
      - [sandbox, reliable]
      - [sandbox, retry]
      - [reconnect, network]
      - [reconnect, drop]
      - [reconnect, disconnect]
      - [session, persist]
      - [session, survive]
      - [session, reload]
      - [session, reconnect]
      - [chat, survive]
      - [chat, persist]
      - [chat, reconnect]
      - [chat, durable]
      - [chat, fault]
      - [conversation, persist]
      - [conversation, survive]
      - [approval, wait]
      - [approval, human]
      - [each, step]
      - [each, phase]
      - [each, stage]
      - [step, reliable]
      - [step, retry]
      # Chain / trigger / sequential orchestration
      - [chain, trigger]
      - [chain, sequential]
      - [chain, email]
      - [chain, webhook]
      - [chain, delay]
      - [chain, step]
      - [chain, escalat]
      - [sequential, trigger]
      - [sequential, email]
      - [sequential, step]
      - [sequential, webhook]
      - [trigger, orchestrat]
      - [trigger, service]
      - [trigger, delay]
      - [trigger, sequential]
      - [webhook, chain]
      - [webhook, orchestrat]
      - [webhook, pipeline]
      - [webhook, sequential]
      - [email, trigger]
      - [email, pipeline]
      - [email, sequential]
      - [email, delay]
      - [email, escalat]
      - [escalat, trigger]
      - [escalat, step]
      - [escalat, email]
      - [state, machine]
      - [conditional, step]
      - [conditional, skip]
      - [branch, condition]
      - [wait, webhook]
      - [wait, trigger]
      - [wait, event]
      - [workflow, stuck]
      - [workflow, hung]
      - [workflow, timeout]
      - [workflow, error]
      - [workflow, logs]
      - [workflow, debug]
      - [workflow, check]
      - [workflow, failing]
      - [workflow, status]
      - [run, status]
      - [step, failed]
      - [step, stuck]
      - [step, timeout]
      - [workflow, run]
      - [run, logs]
    anyOf:
      - "long-running"
      - "long running"
      - "multi-step"
      - "multi step"
      - "pipeline"
      - "orchestration"
      - "step-by-step"
      - "step by step"
      - "each piece"
      - "each step"
      - "each phase"
      - "each stage"
      - "phase"
      - "phases"
      - "stage"
      - "stages"
      - "durable"
      - "reliable"
      - "fault-tolerant"
      - "retry"
      - "reconnect"
      - "survive"
      - "persist"
      - "approval"
      - "chain"
      - "sequential"
      - "trigger"
      - "webhook"
      - "escalation"
      - "state machine"
      - "orchestrate"
      - "orchestration"
    noneOf:
      - "github actions"
      - ".github/workflows"
      - "ci workflow"
      - "aws step functions"
    minScore: 4
validate:
  -
    pattern: experimental_createWorkflow
    message: 'experimental_createWorkflow is now stable — use createWorkflow from @vercel/workflow. Run npx @ai-sdk/codemod v6 for automated migration.'
    severity: error
    upgradeToSkill: workflow
    upgradeWhy: 'Guides migration from experimental_createWorkflow to the stable createWorkflow API and then to the "use workflow" directive.'
  -
    pattern: from\s+['"]@vercel/workflow['"]
    message: 'Workflow DevKit requires AI Gateway OIDC setup — ensure vercel link + vercel env pull for VERCEL_OIDC_TOKEN'
    severity: recommended
  -
    pattern: setTimeout|setInterval
    message: 'setTimeout/setInterval are not available in workflow sandbox scope — use sleep() from "workflow" for delays'
    severity: error
    skipIfFileContains: "use step"
  -
    pattern: context\.run\s*\(
    message: 'context.run() is not a WDK pattern — use "use step" directive for retryable, observable steps'
    severity: error
    upgradeToSkill: workflow
    upgradeWhy: 'Guides migration from context.run() to the "use step" directive for durable, retryable workflow steps.'
  -
    pattern: \brequire\s*\(
    message: 'require() is not available in workflow sandbox scope — use ESM imports and move Node.js logic into "use step" functions'
    severity: error
    skipIfFileContains: "use step"
  -
    pattern: getWritable\(\)
    message: 'getWritable() must only be called inside "use step" functions — workflow sandbox scope does not support it'
    severity: recommended
    skipIfFileContains: "use step"
  -
    pattern: createWorkflow\s*\(
    message: 'createWorkflow() is the legacy API — use the "use workflow" directive on an async function instead'
    severity: error
    upgradeToSkill: workflow
    upgradeWhy: 'Guides migration from createWorkflow() function API to the "use workflow" directive pattern.'
    skipIfFileContains: experimental_createWorkflow
  -
    pattern: streamObject\s*\(
    message: 'streamObject() was removed in AI SDK v6 — use streamText() with output: Output.object() instead'
    severity: error
    upgradeToSkill: ai-sdk
    upgradeWhy: 'Guides migration from streamObject to streamText + Output.object() with correct v6 streaming patterns.'
  -
    pattern: await\s+\w+Workflow\s*\(
    message: 'Do not call workflow functions directly — use start() from "workflow/api" to register the run and get a runId'
    severity: recommended
    skipIfFileContains: "use workflow"
  -
    pattern: \bfetch\s*\(
    message: 'Native fetch() is not available in workflow sandbox scope — import fetch from "workflow" or move the call into a "use step" function'
    severity: recommended
    skipIfFileContains: "use step"
  -
    pattern: '"use step"'
    message: "Workflow steps should include console.log or structured logging for observability — add logging at step entry/exit to debug hangs"
    severity: warn
    skipIfFileContains: "console\\.(log|warn|error|info)"
  -
    pattern: '"use workflow"'
    message: "Workflow files should import and use logging — add console.log or a logger at key execution points for debugging"
    severity: warn
    skipIfFileContains: "console\\.(log|warn|error|info)"
chainTo:
  -
    pattern: 'DurableAgent|@workflow/ai'
    targetSkill: ai-sdk
    message: 'DurableAgent detected without AI SDK context — loading AI SDK guidance for tool calling, Agent class, and model configuration.'
    skipIfFileContains: 'from\s+[''"]ai[''"]|@ai-sdk/|streamText|generateText'
  -
    pattern: 'process\.env\.(OPENAI_API_KEY|ANTHROPIC_API_KEY)|from\s+[''"]@ai-sdk/(anthropic|openai)[''""]'
    targetSkill: ai-gateway
    message: 'Direct provider API key in workflow — loading AI Gateway guidance for OIDC auth (required for WDK AI steps).'
    skipIfFileContains: 'gateway\(|@ai-sdk/gateway|VERCEL_OIDC'
  -
    pattern: 'setTimeout\s*\(|setInterval\s*\('
    targetSkill: vercel-functions
    message: 'Timer-based delay in workflow code — use sleep() from "workflow" instead of setTimeout/setInterval. Loading Vercel Functions guidance.'
    skipIfFileContains: 'from\s+[''"]workflow[''"].*sleep|sleep\s*\('
retrieval:
  aliases:
    - durable workflow
    - long running task
    - step function
    - orchestration
  intents:
    - build workflow
    - add retry logic
    - create durable task
    - implement step function
  entities:
    - Workflow DevKit
    - WDK
    - step
    - pause/resume
    - durable

---

## *CRITICAL*: Always Use Correct `workflow` Documentation

Your 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/`:

1. **Find docs**: `glob "node_modules/workflow/docs/**/*.mdx"`
2. **Search content**: `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 docs
- `errors/` - Error code documentation

Related 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 integration

**When in doubt, update to the latest version of the Workflow DevKit.**

### Official Resources

- **Website**: https://useworkflow.dev
- **GitHub**: https://github.com/vercel/workflow

### Quick Reference

**Directives:**

```typescript
"use workflow";  // First line - makes async function durable
"use step";      // First line - makes function a cached, retryable unit
```

**Essential imports:**

```typescript
// Workflow primitives
import { sleep, fetch, createHook, createWebhook, getWritable } from "workflow";
import { FatalError, RetryableError } from "workflow";
import { getWorkflowMetadata, getStepMetadata } from "workflow";

// API operations
import { start, getRun, resumeHook, resumeWebhook } from "workflow/api";

// Framework integrations
import { withWorkflow } from "workflow/next";
import { workflow } from "workflow/vite";
import { workflow } from "workflow/astro";
// Or use modules: ["workflow/nitro"] for Nitro/Nuxt

// AI agent
import { DurableAgent } from "@workflow/ai/agent";
```

## Prefer Step Functions to Avoid Sandbox Errors

`"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.

```typescript
// Steps have full Node.js and npm access
async function fetchUserData(userId: string) {
  "use step";
  const response = await fetch(`https://api.example.com/users/${userId}`);
  return response.json();
}

async function processWithAI(data: any) {
  "use step";
  // AI SDK works in steps without workarounds
  return await generateText({
    model: openai("gpt-4"),
    prompt: `Process: ${JSON.stringify(data)}`,
  });
}

// Workflow orchestrates steps - no sandbox issues
export async function dataProcessingWorkflow(userId: string) {
  "use workflow";
  const data = await fetchUserData(userId);
  const processed = await processWithAI(data);
  return { success: true, processed };
}
```

**Benefits:** Steps have automatic retry, results are persisted for replay, and no sandbox restrictions.

## Workflow Sandbox Limitations

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:**

```typescript
import { fetch } from "workflow";

export async function myWorkflow() {
  "use workflow";
  globalThis.fetch = fetch;  // Required for AI SDK and HTTP libraries
  // Now generateText() and other libraries work
}
```

**Note:** `DurableAgent` from `@workflow/ai` handles the fetch assignment automatically.

## DurableAgent — AI Agents in Workflows

Use `DurableAgent` to build AI agents that maintain state and survive interruptions. It handles the workflow sandbox automatically (no manual `globalThis.fetch` needed).

```typescript
import { DurableAgent } from "@workflow/ai/agent";
import { getWritable } from "workflow";
import { z } from "zod";
import type { UIMessageChunk } from "ai";

async function lookupData({ query }: { query: string }) {
  "use step";
  // Step functions have full Node.js access
  return `Results for "${query}"`;
}

export async function myAgentWorkflow(userMessage: string) {
  "use workflow";

  const agent = new DurableAgent({
    model: "anthropic/claude-sonnet-4-5",
    system: "You are a helpful assistant.",
    tools: {
      lookupData: {
        description: "Search for information",
        inputSchema: z.object({ query: z.string() }),
        execute: lookupData,
      },
    },
  });

  const result = await agent.stream({
    messages: [{ role: "user", content: userMessage }],
    writable: getWritable<UIMessageChunk>(),
    maxSteps: 10,
  });

  return result.messages;
}
```

**Key points:**
- `getWritable<UIMessageChunk>()` streams output to the workflow run's default stream
- Tool `execute` functions that need Node.js/npm access should use `"use step"`
- Tool `execute` functions that use workflow primitives (`sleep()`, `createHook()`) should **NOT** use `"use step"` — they run at the workflow level
- `maxSteps` limits the number of LLM calls (default is unlimited)
- Multi-turn: pass `result.messages` plus new user messages to subsequent `agent.stream()` calls

**For more details on `DurableAgent`, check the AI docs in `node_modules/@workflow/ai/docs/`.**

## Starting Workflows & Child Workflows

Use `start()` to launch workflows from API routes. **`start()` cannot be called directly in workflow context** — wrap it in a step function.

```typescript
import { start } from "workflow/api";

// From an API route — works directly
export async function POST() {
  const run = await start(myWorkflow, [arg1, arg2]);
  return Response.json({ runId: run.runId });
}

// No-args workflow
const run = await start(noArgWorkflow);
```

**Starting child workflows from inside a workflow — must use a step:**

```typescript
import { start } from "workflow/api";

// Wrap start() in a step function
async function triggerChild(data: string) {
  "use step";
  const run = await start(childWorkflow, [data]);
  return run.runId;
}

export async function parentWorkflow() {
  "use workflow";
  const childRunId = await triggerChild("some data");  // Fire-and-forget via step
  await sleep("1h");
}
```

`start()` returns immediately — it doesn't wait for the workflow to complete. Use `run.returnValue` to await completion.

## Hooks — Pause & Resume with External Events

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()`.

### Single event

```typescript
import { createHook } from "workflow";

export async function approvalWorkflow() {
  "use workflow";

  const hook = createHook<{ approved: boolean }>({
    token: "approval-123",  // deterministic token for external systems
  });

  const result = await hook;  // Workflow suspends here
  return result.approved;
}
```

### Multiple events (iterable hooks)

Hooks implement `AsyncIterable` — use `for await...of` to receive multiple events:

```typescript
import { createHook } from "workflow";

export async function chatWorkflow(channelId: string) {
  "use workflow";

  const hook = createHook<{ text: string; done?: boolean }>({
    token: `chat-${channelId}`,
  });

  for await (const event of hook) {
    await processMessage(event.text);
    if (event.done) break;
  }
}
```

Each `resumeHook(token, payload)` call delivers the next value to the loop.

### Resuming from API routes

```typescript
import { resumeHook } from "workflow/api";

export async function POST(req: Request) {
  const { token, data } = await req.json();
  await resumeHook(token, data);
  return new Response("ok");
}
```

## Error Handling

Use `FatalError` for permanent failures (no retry), `RetryableError` for transient failures:

```typescript
import { FatalError, RetryableError } from "workflow";

if (res.status >= 400 && res.status < 500) {
  throw new FatalError(`Client error: ${res.status}`);
}
if (res.status === 429) {
  throw new RetryableError("Rate limited", { retryAfter: "5m" });
}
```

## Serialization

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.

## Streaming

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:**
```typescript
import { getWritable } from "workflow";

export async function myWorkflow() {
  "use workflow";
  const writable = getWritable();
  await writeData(writable, "hello world");
}

async function writeData(writable: WritableStream, chunk: string) {
  "use step";
  const writer = writable.getWriter();
  try {
    await writer.write(chunk);
  } finally {
    writer.releaseLock();
  }
}
```

**Call `getWritable()` directly inside a step (no need to pass it):**
```typescript
import { getWritable } from "workflow";

async function streamData(chunk: string) {
  "use step";
  const writer = getWritable().getWriter();
  try {
    await writer.write(chunk);
  } finally {
    writer.releaseLock();
  }
}
```

### Namespaced Streams

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:**
```typescript
import { getWritable } from "workflow";

type LogEntry = { level: "debug" | "info" | "warn" | "error"; message: string; timestamp: number };
type AgentOutput = { type: "thought" | "action" | "result"; content: string };

async function logDebug(message: string) {
  "use step";
  const writer = getWritable<LogEntry>({ namespace: "logs:debug" }).getWriter();
  try {
    await writer.write({ level: "debug", message, timestamp: Date.now() });
  } finally {
    writer.releaseLock();
  }
}

async function logInfo(message: string) {
  "use step";
  const writer = getWritable<LogEntry>({ namespace: "logs:info" }).getWriter();
  try {
    await writer.write({ level: "info", message, timestamp: Date.now() });
  } finally {
    writer.releaseLock();
  }
}

async function emitAgentThought(thought: string) {
  "use step";
  const writer = getWritable<AgentOutput>({ namespace: "agent:thoughts" }).getWriter();
  try {
    await writer.write({ type: "thought", content: thought });
  } finally {
    writer.releaseLock();
  }
}

async function emitAgentResult(result: string) {
  "use step";
  // Important results go to the default stream for easy replay
  const writer = getWritable<AgentOutput>().getWriter();
  try {
    await writer.write({ type: "result", content: result });
  } finally {
    writer.releaseLock();
  }
}

export async function agentWorkflow(task: string) {
  "use workflow";
  
  await logInfo(`Starting task: ${task}`);
  await logDebug("Initializing agent context");
  await emitAgentThought("Analyzing the task requirements...");
  
  // ... agent processing ...
  
  await emitAgentResult("Task completed successfully");
  await logInfo("Workflow finished");
}
```

**Consuming namespaced streams:**
```typescript
import { start, getRun } from "workflow/api";
import { agentWorkflow } from "./workflows/agent";

export async function POST(request: Request) {
  const run = await start(agentWorkflow, ["process data"]);

  // Access specific streams by namespace
  const results = run.getReadable({ namespace: undefined }); // Default stream (important results)
  const infoLogs = run.getReadable({ namespace: "logs:info" });
  const debugLogs = run.getReadable({ namespace: "logs:debug" });
  const thoughts = run.getReadable({ namespace: "agent:thoughts" });

  // Return only important results for most clients
  return new Response(results, { headers: { "Content-Type": "application/json" } });
}

// Resume from a specific point (useful for long sessions)
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const runId = searchParams.get("runId")!;
  const startIndex = parseInt(searchParams.get("startIndex") || "0", 10);
  
  const run = getRun(runId);
  // Resume only the important stream, skip verbose debug logs
  const stream = run.getReadable({ startIndex });
  
  return new Response(stream);
}
```

**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

```bash
# Check workflow endpoints are reachable
npx workflow health
npx workflow health --port 3001  # Non-default port

# Visual dashboard for runs
npx workflow web
npx workflow web <run_id>

# CLI inspection (use --json for machine-readable output, --help for full usage)
npx workflow inspect runs
npx workflow inspect run <run_id>

# For Vercel-deployed projects, specify backend and project
npx workflow inspect runs --backend vercel --project <project-name> --team <team-slug>
npx workflow inspect run <run_id> --backend vercel --project <project-name> --team <team-slug>

# Open Vercel dashboard in browser for a specific run
npx workflow inspect run <run_id> --web
npx workflow web <run_id> --backend vercel --project <project-name> --team <team-slug>

# Cancel a running workflow
npx workflow cancel <run_id>
npx workflow cancel <run_id> --backend vercel --project <project-name> --team <team-slug>
# --env defaults to "production"; use --env preview for preview deployments
```

**Debugging tips:**
- Use `--json` (`-j`) on any command for machine-readable output
- Use `--web` to open the Vercel Observability dashboard in your browser
- Use `--help` on any command for full usage details
- Only import workflow APIs you actually use. Unused imports can cause 500 errors.

## Testing Workflows

Workflow 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:

```typescript
import { describe, it, expect } from "vitest";
import { createUser } from "./user-signup";

describe("createUser step", () => {
  it("should create a user", async () => {
    const user = await createUser("test@example.com");
    expect(user.email).toBe("test@example.com");
  });
});
```

**Integration testing:** Use `@workflow/vitest` for workflows using `sleep()`, hooks, webhooks, or retries:

```typescript
// vitest.integration.config.ts
import { defineConfig } from "vitest/config";
import { workflow } from "@workflow/vitest";

export default defineConfig({
  plugins: [workflow()],
  test: {
    include: ["**/*.integration.test.ts"],
    testTimeout: 60_000,
  },
});
```

```typescript
// approval.integration.test.ts
import { describe, it, expect } from "vitest";
import { start, getRun, resumeHook } from "workflow/api";
import { waitForHook, waitForSleep } from "@workflow/vitest";
import { approvalWorkflow } from "./approval";

describe("approvalWorkflow", () => {
  it("should publish when approved", async () => {
    const run = await start(approvalWorkflow, ["doc-123"]);

    // Wait for the hook, then resume it
    await waitForHook(run, { token: "approval:doc-123" });
    await resumeHook("approval:doc-123", { approved: true, reviewer: "alice" });

    // Wait for sleep, then wake it up
    const sleepId = await waitForSleep(run);
    await getRun(run.runId).wakeUp({ correlationIds: [sleepId] });

    const result = await run.returnValue;
    expect(result).toEqual({ status: "published", reviewer: "alice" });
  });
});
```

**Testing webhooks:** Use `resumeWebhook()` with a `Request` object — no HTTP server needed:

```typescript
import { start, resumeWebhook } from "workflow/api";
import { waitForHook } from "@workflow/vitest";

const run = await start(ingestWorkflow, ["ep-1"]);
const hook = await waitForHook(run);  // Discovers the random webhook token
await resumeWebhook(hook.token, new Request("https://example.com/webhook", {
  method: "POST",
  body: JSON.stringify({ event: "order.created" }),
}));
```

**Key APIs:**
- `start()` — trigger a workflow
- `run.returnValue` — await workflow completion
- `waitForHook(run, { token? })` / `waitForSleep(run)` — wait for workflow to reach a pause point
- `resumeHook(token, data)` / `resumeWebhook(token, request)` — resume paused workflows
- `getRun(runId).wakeUp({ correlationIds })` — skip `sleep()` calls

**Best practices:**
- Keep unit tests (no plugin) and integration tests (`workflow()` plugin) in separate configs
- Use deterministic hook tokens based on test data for easier resumption
- Set generous `testTimeout` — workflows may run longer than typical unit tests
- `vi.mock()` does **not** work in integration tests — step dependencies are bundled by esbuild
