[workflow] Adopt Workflow SDK branding (#133)

* [workflow] Adopt Workflow SDK branding

Retire user-facing DevKit and WDK terminology while retaining legacy matcher aliases and syncing the current upstream workflow skill.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [workflow] Add Analytics API import

Expose getWorld in the quick reference so users can access workflow analytics through the runtime World.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: narrow build-agents prompt suppression

Co-authored-by: mitul-s <19615826+mitul-s@users.noreply.github.com>

* Revert "fix: narrow build-agents prompt suppression"

Restore the dedicated Vercel Agent routing exclusion because the matcher change was unrelated to PR #133.

This reverts commit 248f11c044.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mitul-s <19615826+mitul-s@users.noreply.github.com>
This commit is contained in:
Mitul Shah
2026-08-12 16:10:12 -04:00
committed by GitHub
parent 49da55357d
commit 9d3018171b
17 changed files with 559 additions and 287 deletions
+6 -6
View File
@@ -1,6 +1,6 @@
---
name: benchmark-agents
description: Advanced AI agent benchmark scenarios that push Vercel's cutting-edge platform features — Workflow DevKit, AI Gateway, MCP, Chat SDK, Queues, Flags, Sandbox, and multi-agent orchestration. Designed to stress-test skill injection for complex, multi-system builds.
description: Advanced AI agent benchmark scenarios that push Vercel's cutting-edge platform features — Workflow SDK, AI Gateway, MCP, Chat SDK, Queues, Flags, Sandbox, and multi-agent orchestration. Designed to stress-test skill injection for complex, multi-system builds.
---
# Benchmark Agents — Advanced AI Systems
@@ -163,7 +163,7 @@ After sessions build, verify these patterns in the generated projects:
### Project structure
```bash
echo -n "src/: "; test -d "$base/src" && echo YES || echo NO # Should be NO for WDK projects
echo -n "src/: "; test -d "$base/src" && echo YES || echo NO # Should be NO for Workflow SDK projects
echo -n "workflows/: "; test -d "$base/workflows" && echo YES || echo NO
echo -n "withWorkflow: "; grep -q "withWorkflow" "$base"/next.config.* && echo YES || echo NO
echo -n "components.json: "; test -f "$base/components.json" && echo YES || echo NO
@@ -204,7 +204,7 @@ head -5 "$wf" # Should show: import { getWritable } from "workflow"
- "store all designs in a gallery"
### DON'T:
- "use Vercel Workflow DevKit with getWritable"
- "use Vercel Workflow SDK with getWritable"
- "use gateway('google/gemini-3.1-flash-image-preview')"
- "install npx ai-elements"
- "add withWorkflow to next.config.ts"
@@ -227,13 +227,13 @@ head -5 "$wf" # Should show: import { getWritable } from "workflow"
| Agent uses `dall-e-3` for images | Agent doesn't know about gemini image gen | PostToolUse validate warns, capabilities table in ai-sdk (v0.9.7) |
| Agent uses `experimental_generateImage` | Old API | PostToolUse validate warns, recommend `generateText` + `result.files` (v0.9.9) |
| Raw markdown rendering (`**bold**` visible) | Agent skips AI Elements | `MessageResponse` documented as universal renderer (v0.9.2) |
| `@/../../workflows/` broken import | Workflows outside `@` alias root | Canonical structure docs: no `src/` for WDK (v0.8.3) |
| `@/../../workflows/` broken import | Workflows outside `@` alias root | Canonical structure docs: no `src/` for Workflow SDK (v0.8.3) |
| `withWorkflow` missing from next.config | Agent skipped setup step | Marked as "Required" in workflow skill (v0.8.1) |
| `defineHook` but no resume route | Agent didn't wire the 3-piece pattern | Documented as 3 required pieces (v0.9.3) |
| `generateObject()` used (removed in v6) | Agent's training data | PostToolUse validate catches as error (v0.9.3) |
| `getWritable()` in workflow scope | Sandbox violation | Strengthened warning in skill (v0.8.1) |
| Missing `vercel link` + `vercel env pull` | No OIDC credentials | Added as "Required" setup step (v0.9.1) |
| `getStepMetadata().retryCount` undefined on first attempt | WDK quirk | Documented: guard with `?? 0` (v0.9.1) |
| `getStepMetadata().retryCount` undefined on first attempt | Workflow SDK quirk | Documented: guard with `?? 0` (v0.9.1) |
| shadcn not installed | No trigger for scaffolding | Added `create-next-app` bashPattern to shadcn (v0.8.0) |
| Skill cap too low (3) | Only 3 skills injected per tool call | Raised to 5 with 18KB budget (v0.8.0) |
@@ -295,7 +295,7 @@ The standard improvement cycle:
Scenarios 01, 04, 09 — AI SDK, Gateway, Sandbox, AI Elements without durable workflows.
### Tier 2 — Durable Agents (45-60 min)
Scenarios 02, 03, 06, 10 — Workflow DevKit, multi-step durability, agent orchestration.
Scenarios 02, 03, 06, 10 — Workflow SDK, multi-step durability, agent orchestration.
### Tier 3 — Platform Integration (45-60 min)
Scenarios 05, 07, 08, 11, 12 — Chat SDK, Queues, Flags, Firewall, cross-platform messaging.
+2 -2
View File
@@ -82,7 +82,7 @@ A text-form relational graph covering:
| `vercel-services` | Multiple frontends and backends in one project, with public rewrites and private service bindings |
| `vercel-storage` | Blob, Edge Config, Neon Postgres, Upstash Redis, migration from sunset packages |
| `verification` | Full-story verification — infers user story, verifies end-to-end browser → API → data → response |
| `workflow` | Workflow DevKit — durable execution, DurableAgent, steps, Worlds, pause/resume |
| `workflow` | Workflow SDK — durable execution, DurableAgent, steps, Worlds, pause/resume |
### Agents (3 specialists)
@@ -266,7 +266,7 @@ bun run build:from-skills # Stage 4: Resolve template includes
- AI SDK v6 (Agents, MCP, DevTools, Reranking, Image Editing)
- AI Elements (pre-built React components for AI interfaces)
- Chat SDK (multi-platform chat bots — Slack, Telegram, Teams, Discord)
- Workflow DevKit (DurableAgent, Worlds, open source)
- Workflow SDK (DurableAgent, Worlds, open source)
- AI Gateway (100+ models, provider routing, cost tracking)
- Vercel Functions (Fluid Compute, streaming, Cron Jobs)
- Vercel Services (multiple frontends and backends, public rewrites, private bindings)
+3 -3
View File
@@ -20,7 +20,7 @@ What does the AI feature need to do?
│ ├─ Single tool call → `generateText` with `tools` parameter
│ ├─ Multi-step reasoning with tools → AI SDK `ToolLoopAgent` class
│ │ ├─ Short-lived (< 60s) → Agent in Route Handler
│ │ └─ Long-running (minutes to hours) → Workflow DevKit `DurableAgent`
│ │ └─ Long-running (minutes to hours) → Workflow SDK `DurableAgent`
│ └─ MCP server integration → `@ai-sdk/mcp` StreamableHTTPClientTransport
├─ Process files / images / audio
@@ -34,7 +34,7 @@ What does the AI feature need to do?
│ └─ Generate with context → `generateText` with retrieved chunks in prompt
└─ Multi-agent system
├─ Agents share context? → Workflow DevKit `Worlds` (shared state)
├─ Agents share context? → Workflow SDK `Worlds` (shared state)
├─ Independent agents? → Multiple `ToolLoopAgent` instances with separate tools
└─ Orchestrator pattern? → Parent Agent delegates to child Agents via tools
```
@@ -368,7 +368,7 @@ Use when: Chat that can take actions (search, CRUD, calculations).
### Pattern 3: Background Agent
```
Client → Route Handler → Workflow DevKit (DurableAgent)
Client → Route Handler → Workflow SDK (DurableAgent)
↓ ↓ tool calls
Returns runId External APIs / DB
↓ ↓
+3 -3
View File
@@ -20,7 +20,7 @@ What does the AI feature need to do?
│ ├─ Single tool call → `generateText` with `tools` parameter
│ ├─ Multi-step reasoning with tools → AI SDK `ToolLoopAgent` class
│ │ ├─ Short-lived (< 60s) → Agent in Route Handler
│ │ └─ Long-running (minutes to hours) → Workflow DevKit `DurableAgent`
│ │ └─ Long-running (minutes to hours) → Workflow SDK `DurableAgent`
│ └─ MCP server integration → `@ai-sdk/mcp` StreamableHTTPClientTransport
├─ Process files / images / audio
@@ -34,7 +34,7 @@ What does the AI feature need to do?
│ └─ Generate with context → `generateText` with retrieved chunks in prompt
└─ Multi-agent system
├─ Agents share context? → Workflow DevKit `Worlds` (shared state)
├─ Agents share context? → Workflow SDK `Worlds` (shared state)
├─ Independent agents? → Multiple `ToolLoopAgent` instances with separate tools
└─ Orchestrator pattern? → Parent Agent delegates to child Agents via tools
```
@@ -165,7 +165,7 @@ Use when: Chat that can take actions (search, CRUD, calculations).
### Pattern 3: Background Agent
```
Client → Route Handler → Workflow DevKit (DurableAgent)
Client → Route Handler → Workflow SDK (DurableAgent)
↓ ↓ tool calls
Returns runId External APIs / DB
↓ ↓
+1 -1
View File
@@ -55,7 +55,7 @@ Build failed?
├─ Long-running task?
│ ├─ Under 5 min → Use Fluid Compute with streaming
│ ├─ Up to 15 min → Use Vercel Functions with `maxDuration` in vercel.json
│ └─ Hours/days → Use Workflow DevKit (DurableAgent or workflow steps)
│ └─ Hours/days → Use Workflow SDK (DurableAgent or workflow steps)
└─ DB query slow? → Add connection pooling, check cold start, use Edge Config
```
+13 -11
View File
@@ -586,7 +586,7 @@
{
"pattern": "DurableAgent|use workflow|use step|from\\s+[''\"]workflow[''\"]|@workflow/",
"targetSkill": "workflow",
"message": "Workflow DevKit pattern detected in AI code — loading WDK guidance for durable agent execution, step isolation, and crash-safe orchestration.",
"message": "Workflow SDK pattern detected in AI code — loading Workflow SDK guidance for durable agent execution, step isolation, and crash-safe orchestration.",
"skipIfFileContains": "createWorkflow|withWorkflow"
},
{
@@ -1376,7 +1376,7 @@
{
"pattern": "setTimeout\\s*\\(|setInterval\\s*\\(|while\\s*\\(\\s*true",
"targetSkill": "workflow",
"message": "Long-running or polling logic in chat bot — loading Workflow DevKit for durable execution that survives deploys.",
"message": "Long-running or polling logic in chat bot — loading Workflow SDK for durable execution that survives deploys.",
"skipIfFileContains": "use workflow|from\\s+[''\"]workflow[''\"]"
},
{
@@ -3817,11 +3817,11 @@
},
{
"pattern": "maxRetries\\s*[=:]|retryCount\\s*[=:]|retry\\s*\\(\\s*|for\\s*\\([^)]*retry|while\\s*\\([^)]*retry",
"message": "Manual retry logic detected. Use Vercel Workflow DevKit for automatic retries with durable execution.",
"message": "Manual retry logic detected. Use Vercel Workflow SDK for automatic retries with durable execution.",
"severity": "recommended",
"skipIfFileContains": "use workflow|use step|@vercel/workflow|from\\s+[''\"\"](workflow)[''\"\"]",
"upgradeToSkill": "workflow",
"upgradeWhy": "Replace manual retry loops with Workflow DevKit steps that provide automatic retries, crash safety, and observability.",
"upgradeWhy": "Replace manual retry loops with Workflow SDK steps that provide automatic retries, crash safety, and observability.",
"upgradeMode": "soft"
},
{
@@ -3843,7 +3843,7 @@
{
"pattern": "setTimeout\\s*\\(|setInterval\\s*\\(|await\\s+new\\s+Promise\\s*\\([^)]*setTimeout",
"targetSkill": "workflow",
"message": "Long-running or polling logic in serverless handler — loading Workflow DevKit for durable execution."
"message": "Long-running or polling logic in serverless handler — loading Workflow SDK for durable execution."
},
{
"pattern": "writeFile(Sync)?\\(|createWriteStream\\(|from\\s+[''\\\"](multer|formidable)[''\"]|fs\\.writeFile",
@@ -3863,7 +3863,7 @@
{
"pattern": "while\\s*\\(\\s*true\\s*\\)\\s*\\{|for\\s*\\(\\s*;\\s*;\\s*\\)\\s*\\{|setInterval\\s*\\(\\s*async",
"targetSkill": "workflow",
"message": "Polling loop in serverless function detected — loading Workflow DevKit for durable, crash-safe execution with pause/resume.",
"message": "Polling loop in serverless function detected — loading Workflow SDK for durable, crash-safe execution with pause/resume.",
"skipIfFileContains": "use workflow|use step|from\\\\s+['\\\"]workflow['\\\"]"
},
{
@@ -3881,7 +3881,7 @@
{
"pattern": "maxRetries\\s*[=:]|retryCount\\s*[=:]|retry\\s*\\(\\s*|for\\s*\\([^)]*retry|while\\s*\\([^)]*retry",
"targetSkill": "workflow",
"message": "Manual retry logic in serverless handler — loading Workflow DevKit guidance for automatic retries with durable execution.",
"message": "Manual retry logic in serverless handler — loading Workflow SDK guidance for automatic retries with durable execution.",
"skipIfFileContains": "use workflow|use step|@vercel/workflow|from\\s+[''\"\"](workflow)[''\"\"]"
}
],
@@ -4680,7 +4680,7 @@
"summary": "",
"docs": [
"https://vercel.com/docs/workflow",
"https://useworkflow.dev"
"https://workflow-sdk.dev"
],
"sitemap": "https://vercel.com/sitemap/docs.xml",
"pathPatterns": [
@@ -4767,7 +4767,7 @@
},
{
"pattern": "from\\s+['\"]@vercel/workflow['\"]",
"message": "Workflow DevKit requires AI Gateway OIDC setup — ensure vercel link + vercel env pull for VERCEL_OIDC_TOKEN",
"message": "Workflow SDK requires AI Gateway OIDC setup — ensure vercel link + vercel env pull for VERCEL_OIDC_TOKEN",
"severity": "recommended"
},
{
@@ -4778,7 +4778,7 @@
},
{
"pattern": "context\\.run\\s*\\(",
"message": "context.run() is not a WDK pattern — use \"use step\" directive for retryable, observable steps",
"message": "context.run() is not a Workflow SDK 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.",
@@ -4848,7 +4848,7 @@
{
"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).",
"message": "Direct provider API key in workflow — loading AI Gateway guidance for OIDC auth (required for Workflow SDK AI steps).",
"skipIfFileContains": "gateway\\(|@ai-sdk/gateway|VERCEL_OIDC"
},
{
@@ -4867,6 +4867,7 @@
"promptSignals": {
"phrases": [
"vercel workflow",
"workflow sdk",
"workflow devkit",
"durable workflow",
"durable execution",
@@ -5464,6 +5465,7 @@
"implement step function"
],
"entities": [
"Workflow SDK",
"Workflow DevKit",
"WDK",
"step",
+1 -1
View File
@@ -278,7 +278,7 @@ chainTo:
-
pattern: 'DurableAgent|use workflow|use step|from\s+[''"]workflow[''"]|@workflow/'
targetSkill: workflow
message: 'Workflow DevKit pattern detected in AI code — loading WDK guidance for durable agent execution, step isolation, and crash-safe orchestration.'
message: 'Workflow SDK pattern detected in AI code — loading Workflow SDK guidance for durable agent execution, step isolation, and crash-safe orchestration.'
skipIfFileContains: 'createWorkflow|withWorkflow'
-
pattern: "from\\s+['\"]langchain['\"]|from\\s+['\"]@langchain/"
+1 -1
View File
@@ -277,7 +277,7 @@ chainTo:
-
pattern: 'DurableAgent|use workflow|use step|from\s+[''"]workflow[''"]|@workflow/'
targetSkill: workflow
message: 'Workflow DevKit pattern detected in AI code — loading WDK guidance for durable agent execution, step isolation, and crash-safe orchestration.'
message: 'Workflow SDK pattern detected in AI code — loading Workflow SDK guidance for durable agent execution, step isolation, and crash-safe orchestration.'
skipIfFileContains: 'createWorkflow|withWorkflow'
-
pattern: "from\\s+['\"]langchain['\"]|from\\s+['\"]@langchain/"
+1 -1
View File
@@ -127,7 +127,7 @@ chainTo:
-
pattern: 'setTimeout\s*\(|setInterval\s*\(|while\s*\(\s*true'
targetSkill: workflow
message: 'Long-running or polling logic in chat bot — loading Workflow DevKit for durable execution that survives deploys.'
message: 'Long-running or polling logic in chat bot — loading Workflow SDK for durable execution that survives deploys.'
skipIfFileContains: 'use workflow|from\s+[''"]workflow[''"]'
-
pattern: 'process\.env\.(OPENAI_API_KEY|ANTHROPIC_API_KEY)|from\s+[''"]@ai-sdk/(anthropic|openai)[''""]'
+1 -1
View File
@@ -126,7 +126,7 @@ chainTo:
-
pattern: 'setTimeout\s*\(|setInterval\s*\(|while\s*\(\s*true'
targetSkill: workflow
message: 'Long-running or polling logic in chat bot — loading Workflow DevKit for durable execution that survives deploys.'
message: 'Long-running or polling logic in chat bot — loading Workflow SDK for durable execution that survives deploys.'
skipIfFileContains: 'use workflow|from\s+[''"]workflow[''"]'
-
pattern: 'process\.env\.(OPENAI_API_KEY|ANTHROPIC_API_KEY)|from\s+[''"]@ai-sdk/(anthropic|openai)[''""]'
+7 -7
View File
@@ -99,10 +99,10 @@ validate:
skipIfFileContains: 'getCache|from\s+[''""]\@vercel/functions[''""]'
-
pattern: 'maxRetries\s*[=:]|retryCount\s*[=:]|retry\s*\(\s*|for\s*\([^)]*retry|while\s*\([^)]*retry'
message: 'Manual retry logic detected. Use Vercel Workflow DevKit for automatic retries with durable execution.'
message: 'Manual retry logic detected. Use Vercel Workflow SDK for automatic retries with durable execution.'
severity: recommended
upgradeToSkill: workflow
upgradeWhy: 'Replace manual retry loops with Workflow DevKit steps that provide automatic retries, crash safety, and observability.'
upgradeWhy: 'Replace manual retry loops with Workflow SDK steps that provide automatic retries, crash safety, and observability.'
skipIfFileContains: 'use workflow|use step|@vercel/workflow|from\s+[''""](workflow)[''""]'
-
pattern: 'from\s+[''"](express)[''""]|require\s*\(\s*[''"](express)[''""\)]'
@@ -140,7 +140,7 @@ chainTo:
-
pattern: 'setTimeout\s*\(|setInterval\s*\(|await\s+new\s+Promise\s*\([^)]*setTimeout'
targetSkill: workflow
message: 'Long-running or polling logic in serverless handler — loading Workflow DevKit for durable execution.'
message: 'Long-running or polling logic in serverless handler — loading Workflow SDK for durable execution.'
-
pattern: 'writeFile(Sync)?\(|createWriteStream\(|from\s+[''\"](multer|formidable)[''"]|fs\.writeFile'
targetSkill: vercel-storage
@@ -156,7 +156,7 @@ chainTo:
-
pattern: 'while\s*\(\s*true\s*\)\s*\{|for\s*\(\s*;\s*;\s*\)\s*\{|setInterval\s*\(\s*async'
targetSkill: workflow
message: 'Polling loop in serverless function detected — loading Workflow DevKit for durable, crash-safe execution with pause/resume.'
message: 'Polling loop in serverless function detected — loading Workflow SDK for durable, crash-safe execution with pause/resume.'
skipIfFileContains: "use workflow|use step|from\\s+['\"]workflow['\"]"
-
pattern: "from\\s+['\"]express['\"]|require\\s*\\(\\s*['\"]express['\"]"
@@ -171,7 +171,7 @@ chainTo:
-
pattern: 'maxRetries\s*[=:]|retryCount\s*[=:]|retry\s*\(\s*|for\s*\([^)]*retry|while\s*\([^)]*retry'
targetSkill: workflow
message: 'Manual retry logic in serverless handler — loading Workflow DevKit guidance for automatic retries with durable execution.'
message: 'Manual retry logic in serverless handler — loading Workflow SDK guidance for automatic retries with durable execution.'
skipIfFileContains: 'use workflow|use step|@vercel/workflow|from\s+[''""](workflow)[''""]'
---
@@ -480,7 +480,7 @@ All plans now default to 300s execution time with Fluid Compute.
1. **Cold starts with DB connections**: Use connection pooling (e.g., Neon's `@neondatabase/serverless`)
2. **Edge limitations**: No `fs`, no native modules, limited `crypto` — use Node.js runtime if needed
3. **Timeout exceeded**: Use Fluid Compute for long-running tasks, or Workflow DevKit for very long processes
3. **Timeout exceeded**: Use Fluid Compute for long-running tasks, or Workflow SDK for very long processes
4. **Bundle size**: Functions support up to 5 GB package size on Fluid Compute (up from 250 MB); request bodies up to 100 MB (up from 4.5 MB)
5. **Environment variables**: Available in all functions automatically; use `vercel env pull` for local dev
@@ -495,7 +495,7 @@ All plans now default to 300s execution time with Fluid Compute.
├─ Long-running task?
│ ├─ Under 5 min → Use Fluid Compute with streaming
│ ├─ Up to 15 min → Use Vercel Functions with `maxDuration` in vercel.json
│ └─ Hours/days → Use Workflow DevKit (DurableAgent or workflow steps)
│ └─ Hours/days → Use Workflow SDK (DurableAgent or workflow steps)
└─ DB query slow? → Add connection pooling, check cold start, use Edge Config
```
+255 -12
View File
@@ -1,11 +1,11 @@
---
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.
description: Vercel Workflow SDK 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"
- "https://workflow-sdk.dev"
sitemap: "https://vercel.com/sitemap/docs.xml"
pathPatterns:
- 'lib/workflow/**'
@@ -38,6 +38,8 @@ metadata:
phrases:
# Direct workflow mentions
- "vercel workflow"
- "workflow sdk"
# Legacy product name retained only as an input matcher.
- "workflow devkit"
- "durable workflow"
- "durable execution"
@@ -338,7 +340,7 @@ validate:
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'
message: 'Workflow SDK requires AI Gateway OIDC setup — ensure vercel link + vercel env pull for VERCEL_OIDC_TOKEN'
severity: recommended
-
pattern: setTimeout|setInterval
@@ -347,7 +349,7 @@ validate:
skipIfFileContains: "use step"
-
pattern: context\.run\s*\(
message: 'context.run() is not a WDK pattern — use "use step" directive for retryable, observable steps'
message: 'context.run() is not a Workflow SDK 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.'
@@ -403,7 +405,7 @@ chainTo:
-
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).'
message: 'Direct provider API key in workflow — loading AI Gateway guidance for OIDC auth (required for Workflow SDK AI steps).'
skipIfFileContains: 'gateway\(|@ai-sdk/gateway|VERCEL_OIDC'
-
pattern: 'setTimeout\s*\(|setInterval\s*\('
@@ -422,6 +424,8 @@ retrieval:
- create durable task
- implement step function
entities:
- Workflow SDK
# Legacy product names retained only as retrieval aliases.
- Workflow DevKit
- WDK
- step
@@ -433,7 +437,7 @@ retrieval:
Your knowledge of `workflow` is outdated.
The `workflow` documentation outlined below matches the installed version of the Workflow DevKit.
The `workflow` documentation outlined below matches the installed version of the Workflow SDK.
Follow these instructions before starting on any `workflow`-related tasks:
Search the bundled documentation in `node_modules/workflow/docs/`:
@@ -447,6 +451,8 @@ Documentation structure in `node_modules/workflow/docs/`:
- `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.)
- `api-reference/workflow-runtime/` - Runtime API (get-world.mdx) and `world/` World SDK (storage.mdx, streams.mdx, queue.mdx)
- `api-reference/workflow-observability/` - Hydration and name parsing utilities (hydrate-resource-io.mdx, parse-workflow-name.mdx, etc.)
- `ai/` - AI SDK integration docs
- `errors/` - Error code documentation
@@ -456,11 +462,11 @@ Related packages also include bundled docs:
- `@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.**
**When in doubt, update to the latest version of the Workflow SDK.**
### Official Resources
- **Website**: https://useworkflow.dev
- **Website**: https://workflow-sdk.dev
- **GitHub**: https://github.com/vercel/workflow
### Quick Reference
@@ -483,6 +489,12 @@ import { getWorkflowMetadata, getStepMetadata } from "workflow";
// API operations
import { start, getRun, resumeHook, resumeWebhook } from "workflow/api";
// Analytics API
import { getWorld } from "workflow/runtime";
// Observability & data hydration
import { hydrateResourceIO, observabilityRevivers, parseStepName, parseWorkflowName } from "workflow/observability";
// Framework integrations
import { withWorkflow } from "workflow/next";
import { workflow } from "workflow/vite";
@@ -712,9 +724,66 @@ if (res.status === 429) {
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.
**Supported built-in 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.
**Not supported:** Functions, Symbols, WeakMap/WeakSet. Pass data, not callbacks.
### Custom Class Serialization
Class instances **can** be serialized across workflow/step boundaries by implementing the `@workflow/serde` protocol. This is essential when a class has instance methods with `"use step"` or when you want to pass class instances between steps.
**Install:** `@workflow/serde` must be a dependency of the package containing the class.
**Pattern:** Add two static methods inside the class body using computed property syntax:
```typescript
import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde";
export class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
// Serialize: return plain data (must be devalue-compatible types only)
static [WORKFLOW_SERIALIZE](instance: Point) {
return { x: instance.x, y: instance.y };
}
// Deserialize: reconstruct from plain data
static [WORKFLOW_DESERIALIZE](data: { x: number; y: number }) {
return new Point(data.x, data.y);
}
async computeDistance(other: Point) {
"use step";
return Math.sqrt((this.x - other.x) ** 2 + (this.y - other.y) ** 2);
}
}
```
**Critical rules:**
1. **Define serde methods INSIDE the class body** as static methods with computed property syntax (`static [WORKFLOW_SERIALIZE](...)`). The SWC plugin detects them by scanning the class. Do NOT assign them externally (e.g., `(MyClass as any)[WORKFLOW_SERIALIZE] = ...`) -- the compiler will not detect this.
2. **Serde methods must return only devalue-compatible types** (plain objects, arrays, primitives, Date, Map, Set, Uint8Array, etc.). No functions, no class instances, no Node.js-specific objects.
3. **Add `"use step"` to Node.js-dependent instance methods.** The SWC plugin strips `"use step"` method bodies from the workflow bundle. This is how you keep Node.js imports (fs, crypto, child_process, etc.) out of the workflow sandbox. The class shell with its serde methods remains in the workflow bundle; only the step method bodies are removed.
4. **Do NOT manually register classes.** The SWC plugin automatically generates registration code (an IIFE that sets `classId` and adds the class to the global registry). Manual calls to `registerSerializationClass()` are unnecessary and error-prone.
5. **Do NOT use dynamic imports to work around sandbox restrictions.** If a class method needs Node.js APIs, the correct solution is `"use step"`, not `/* @vite-ignore */ import(...)`.
**When serde works well:** Pure data classes, domain models, configuration objects, and classes where Node.js-dependent methods can be marked with `"use step"`.
**When to avoid serde:** If a class is fundamentally inseparable from Node.js APIs (every method needs `fs`, `net`, etc.) and cannot meaningfully exist as a shell in the workflow sandbox, keep it entirely in step functions and pass plain data objects across boundaries instead.
### Validating Serde Compliance
Use these tools to verify classes are correctly set up:
- **`workflow transform <file> --check-serde`** -- Shows the SWC transform output for a file and checks if serde classes are compliant (no Node.js imports remaining in the workflow bundle).
- **`workflow validate`** -- Scans all workflow files and reports serde compliance issues. Use `--json` for machine-readable output.
- **SWC Playground** -- The web playground at `workbench/swc-playground` shows a Serde Analysis panel when serde patterns are detected.
- **Build-time warnings** -- The builder automatically warns when serde classes have Node.js built-in imports remaining in the workflow bundle.
## Streaming
@@ -885,15 +954,45 @@ npx workflow cancel <run_id> --backend vercel --project <project-name> --team <t
# --env defaults to "production"; use --env preview for preview deployments
```
### Deep-linking to a run (share a URL, no browser)
Use `--url` to **print** the dashboard deep link and exit — no browser opens and
no local server starts. This is the right tool when you need to hand a user a
clickable link (PR comment, Slack message, debugging summary) rather than open a
UI. (`--web` opens the dashboard; `--url` only prints the link.)
```bash
# Vercel run — prints the Vercel dashboard URL for the run
npx workflow inspect run <run_id> --backend vercel --project <project> --team <team> --url
npx workflow web <run_id> --backend vercel --project <project> --team <team> --env preview --url
# Local run — prints the local web UI deep link
npx workflow inspect run <run_id> --url
# Machine-readable: --url --json prints { "url": "..." } to stdout
npx workflow inspect run <run_id> --backend vercel --url --json
```
URL formats produced:
- **Vercel:** `https://vercel.com/<team-slug>/<project-slug>/workflows/runs/<run_id>?environment=<production|preview>`
(`--env` selects the environment; defaults to `production`. Resolving the team
slug requires being logged in via `vercel login` with the project linked.)
- **Local:** `http://localhost:<port>?resource=run&id=<run_id>` (port defaults
to `3456`; the link works while the `npx workflow web` server is running).
stdout contains **only** the URL (or the JSON object) — all other output goes to
stderr — so you can capture it directly, e.g. `URL=$(npx workflow web <run_id> --backend vercel --url)`.
**Debugging tips:**
- Use `--json` (`-j`) on any command for machine-readable output
- Use `--web` to open the Vercel Observability dashboard in your browser
- Use `--web` to open the Vercel Observability dashboard in your browser, or `--url` to just print the deep link
- 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.
Workflow SDK 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:
@@ -976,3 +1075,147 @@ await resumeWebhook(hook.token, new Request("https://example.com/webhook", {
- 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
## Observability & World SDK
Use `await getWorld()` to build observability dashboards, admin panels, and inspect workflow state. `getWorld()` is asynchronous and returns `Promise<World>` (dynamic import / env-based setup).
**Key imports:**
```typescript
import { getWorld } from "workflow/runtime";
import { hydrateResourceIO, observabilityRevivers, parseStepName, parseWorkflowName } from "workflow/observability";
```
**Key docs** (grep `node_modules/workflow/docs/` for full details):
- `api-reference/workflow-runtime/world/storage.mdx` — events, runs, steps, hooks (events are source of truth; others are materialized views)
- `api-reference/workflow-observability/` — hydration and name parsing
### World SDK Method Signatures
⚠️ Pagination is nested: `{ pagination: { cursor } }` — NOT `{ cursor }` directly.
```typescript
const world = await getWorld();
// Runs
const { data, cursor } = await world.runs.list({ pagination: { cursor }, resolveData: 'all' | 'none' });
const run = await world.runs.get(runId, { resolveData: 'all' | 'none' });
// Cancel via event creation (no cancel() method on runs)
await world.events.create(runId, { eventType: 'run_cancelled' });
// Steps — runId is top-level, NOT inside pagination
const { data, cursor } = await world.steps.list({ runId, pagination: { cursor }, resolveData: 'all' | 'none' });
const step = await world.steps.get(runId, stepId, { resolveData: 'all' | 'none' });
// Events
const { data, cursor } = await world.events.list({ runId, pagination: { cursor } });
await world.events.create(runId, { eventType: 'run_cancelled' });
// Hooks
const hook = await world.hooks.get(hookId);
const hook = await world.hooks.getByToken(token);
// Streams (methods on world.streams)
await world.streams.write(runId, name, chunk);
await world.streams.writeMulti?.(runId, name, chunks);
const readable = await world.streams.get(runId, name, startIndex);
await world.streams.close(runId, name);
const streamNames = await world.streams.list(runId);
const chunks = await world.streams.getChunks(runId, name, { limit, cursor });
const info = await world.streams.getInfo(runId, name);
// Queue (methods live directly on world — internal SDK infrastructure)
await world.queue(queueName, payload, opts);
const deploymentId = await world.getDeploymentId();
```
### `resolveData` Parameter
Controls whether input/output data is **included** in the response. Accepts `'all'` (default) or `'none'`.
**IMPORTANT**: Even with `'all'`, data is still devalue-serialized. You MUST call `hydrateResourceIO()` to get usable JS values.
- **Use `'none'`** for status polling, progress dashboards, run listings
- **Use `'all'`** (or omit) when you need to inspect actual step I/O data — then **always hydrate**
```typescript
// Lightweight status check — no I/O loaded
const run = await world.runs.get(runId, { resolveData: 'none' });
console.log(run.status); // 'running' | 'completed' | 'failed' | 'cancelled'
// Full inspection — resolveData includes data, hydrateResourceIO deserializes it
const step = await world.steps.get(runId, stepId); // defaults to 'all'
const hydrated = hydrateResourceIO(step, observabilityRevivers);
```
> **Common mistake**: Checking `step.input !== undefined` after `resolveData: 'all'` and assuming
> the data is ready to use. The data exists but is serialized — always hydrate first.
### Data Hydration (Devalue Format)
Step I/O is serialized via [devalue](https://github.com/Rich-Harris/devalue) with a 4-byte format prefix (`devl`). Without hydration, `input`/`output` are Uint8Array-like objects with numeric keys:
`{"0":100,"1":101,"2":118,"3":108,...}` — these are NOT usable values.
**Always hydrate before using I/O data:**
```typescript
import { hydrateResourceIO, observabilityRevivers } from "workflow/observability";
const { data: steps } = await world.steps.list({ runId, resolveData: 'all' });
const hydrated = steps.map(s => hydrateResourceIO(s, observabilityRevivers));
// hydrated[0].input → [123, 2] (actual function arguments)
// hydrated[0].output → 125 (actual return value)
```
`hydrateResourceIO` works on both `Step` and `WorkflowRun` objects. For encrypted workflows, use `getEncryptionKeyForRun()` + `hydrateResourceIOWithKey()`.
### Name Parsing
`parseWorkflowName()`, `parseStepName()`, and `parseClassName()` return `{ shortName: string, moduleSpecifier: string } | null`. Always use optional chaining:
```typescript
const parsed = parseWorkflowName("workflow//./src/workflows/order//processOrder");
// parsed?.shortName → "processOrder"
// parsed?.moduleSpecifier → "./src/workflows/order"
// ⚠️ Returns null if format doesn't match
```
### Event Types
Events are the append-only source of truth. Runs/Steps/Hooks are materialized views.
| Category | Types |
|----------|-------|
| Run | `run_created`, `run_started`, `run_completed`, `run_failed`, `run_cancelled` |
| Step | `step_created`, `step_started`, `step_completed`, `step_failed`, `step_retrying` |
| Hook | `hook_created`, `hook_received`, `hook_disposed`, `hook_conflict` |
| Wait | `wait_created`, `wait_completed` |
## Error Handling Patterns
Three error strategies for different failure modes:
| Error Type | Use When | Behavior |
|------------|----------|----------|
| `FatalError` | Permanent failure (bad input, auth denied) | Terminates workflow immediately, no retry |
| `RetryableError` | Transient failure (rate limit, timeout) | Retries with optional `retryAfter` delay |
| `Promise.allSettled` | Parallel steps with mixed criticality | Continues even if some steps fail |
```typescript
import { FatalError, RetryableError } from "workflow";
// Permanent failure — workflow terminates
throw new FatalError("Invalid input: missing required field");
// Transient failure — will retry
throw new RetryableError("API rate limited", { retryAfter: "5m" });
// Mixed criticality parallel execution
const results = await Promise.allSettled([
criticalStep(data), // Must succeed
optionalStep(data), // OK to fail
enrichmentStep(data), // OK to fail
]);
const [critical, optional, enrichment] = results;
if (critical.status === "rejected") throw new FatalError(critical.reason);
```
+9 -5
View File
@@ -1,10 +1,10 @@
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.
description: Vercel Workflow SDK 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"
- "https://workflow-sdk.dev"
sitemap: "https://vercel.com/sitemap/docs.xml"
pathPatterns:
- 'lib/workflow/**'
@@ -37,6 +37,8 @@ metadata:
phrases:
# Direct workflow mentions
- "vercel workflow"
- "workflow sdk"
# Legacy product name retained only as an input matcher.
- "workflow devkit"
- "durable workflow"
- "durable execution"
@@ -337,7 +339,7 @@ validate:
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'
message: 'Workflow SDK requires AI Gateway OIDC setup — ensure vercel link + vercel env pull for VERCEL_OIDC_TOKEN'
severity: recommended
-
pattern: setTimeout|setInterval
@@ -346,7 +348,7 @@ validate:
skipIfFileContains: "use step"
-
pattern: context\.run\s*\(
message: 'context.run() is not a WDK pattern — use "use step" directive for retryable, observable steps'
message: 'context.run() is not a Workflow SDK 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.'
@@ -402,7 +404,7 @@ chainTo:
-
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).'
message: 'Direct provider API key in workflow — loading AI Gateway guidance for OIDC auth (required for Workflow SDK AI steps).'
skipIfFileContains: 'gateway\(|@ai-sdk/gateway|VERCEL_OIDC'
-
pattern: 'setTimeout\s*\(|setInterval\s*\('
@@ -421,6 +423,8 @@ retrieval:
- create durable task
- implement step function
entities:
- Workflow SDK
# Legacy product names retained only as retrieval aliases.
- Workflow DevKit
- WDK
- step
@@ -1,108 +0,0 @@
# Workflow DevKit — DurableAgent Patterns
## Basic DurableAgent
```ts
import { DurableAgent } from '@workflow/ai/agent'
import { openai } from '@ai-sdk/openai'
import { tool } from 'ai'
import { z } from 'zod'
const agent = new DurableAgent({
model: openai('gpt-5.2'),
system: 'You are a helpful research assistant.',
tools: {
searchWeb: tool({
description: 'Search the web for information',
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }) => {
// Search implementation
return { results: await webSearch(query) }
},
}),
writeReport: tool({
description: 'Write a report to a file',
inputSchema: z.object({
title: z.string(),
content: z.string(),
}),
execute: async ({ title, content }) => {
await writeFile(`reports/${title}.md`, content)
return { written: true }
},
}),
},
})
```
## Workflow Endpoint (Next.js)
```ts
// app/api/workflows/research/route.ts
'use workflow'
export async function POST(req: Request) {
const { topic } = await req.json()
const result = await agent.generateText({
prompt: `Research "${topic}" thoroughly and produce a comprehensive report.`,
})
return Response.json({ report: result.text })
}
```
## Workflow with Human-in-the-Loop
```ts
'use workflow'
export async function processApplication(applicationId: string) {
'use step'
const app = await getApplication(applicationId)
'use step'
const aiReview = await agent.generateText({
prompt: `Review this application: ${JSON.stringify(app)}`,
})
'use step'
await notifyReviewer(aiReview.text)
'use step'
// Pauses here until human approves — could be hours or days
const approval = await waitForEvent(`approval:${applicationId}`)
'use step'
if (approval.approved) {
await acceptApplication(applicationId)
} else {
await rejectApplication(applicationId, approval.reason)
}
}
```
## Workflow with Parallel Fan-Out
```ts
'use workflow'
export async function analyzeCompetitors(competitors: string[]) {
'use step'
const analyses = await Promise.all(
competitors.map(async (competitor) => {
'use step'
return await agent.generateText({
prompt: `Analyze ${competitor}'s product strategy.`,
})
})
)
'use step'
const summary = await agent.generateText({
prompt: `Synthesize these competitive analyses: ${analyses.map(a => a.text).join('\n\n')}`,
})
return summary.text
}
```
+248 -9
View File
@@ -1,16 +1,16 @@
---
name: workflow
description: Creates durable, resumable workflows using Vercel's Workflow DevKit. Use when building workflows that need to survive restarts, pause for external events, retry on failure, or coordinate multi-step operations over time. Triggers on mentions of "workflow", "durable functions", "resumable", "workflow devkit", "queue", "event", "push", "subscribe", or step-based orchestration.
description: Creates durable, resumable workflows using Vercel's Workflow SDK. Use when building workflows that need to survive restarts, pause for external events, retry on failure, or coordinate multi-step operations over time. Triggers on mentions of "workflow", "durable functions", "resumable", "workflow sdk", "queue", "event", "push", "subscribe", or step-based orchestration.
metadata:
author: Vercel Inc.
version: '1.4'
version: '1.10'
---
## *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.
The `workflow` documentation outlined below matches the installed version of the Workflow SDK.
Follow these instructions before starting on any `workflow`-related tasks:
Search the bundled documentation in `node_modules/workflow/docs/`:
@@ -24,6 +24,8 @@ Documentation structure in `node_modules/workflow/docs/`:
- `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.)
- `api-reference/workflow-runtime/` - Runtime API (get-world.mdx) and `world/` World SDK (storage.mdx, streams.mdx, queue.mdx)
- `api-reference/workflow-observability/` - Hydration and name parsing utilities (hydrate-resource-io.mdx, parse-workflow-name.mdx, etc.)
- `ai/` - AI SDK integration docs
- `errors/` - Error code documentation
@@ -33,11 +35,11 @@ Related packages also include bundled docs:
- `@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.**
**When in doubt, update to the latest version of the Workflow SDK.**
### Official Resources
- **Website**: https://useworkflow.dev
- **Website**: https://workflow-sdk.dev
- **GitHub**: https://github.com/vercel/workflow
### Quick Reference
@@ -60,6 +62,12 @@ import { getWorkflowMetadata, getStepMetadata } from "workflow";
// API operations
import { start, getRun, resumeHook, resumeWebhook } from "workflow/api";
// Analytics API
import { getWorld } from "workflow/runtime";
// Observability & data hydration
import { hydrateResourceIO, observabilityRevivers, parseStepName, parseWorkflowName } from "workflow/observability";
// Framework integrations
import { withWorkflow } from "workflow/next";
import { workflow } from "workflow/vite";
@@ -289,9 +297,66 @@ if (res.status === 429) {
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.
**Supported built-in 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.
**Not supported:** Functions, Symbols, WeakMap/WeakSet. Pass data, not callbacks.
### Custom Class Serialization
Class instances **can** be serialized across workflow/step boundaries by implementing the `@workflow/serde` protocol. This is essential when a class has instance methods with `"use step"` or when you want to pass class instances between steps.
**Install:** `@workflow/serde` must be a dependency of the package containing the class.
**Pattern:** Add two static methods inside the class body using computed property syntax:
```typescript
import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde";
export class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
// Serialize: return plain data (must be devalue-compatible types only)
static [WORKFLOW_SERIALIZE](instance: Point) {
return { x: instance.x, y: instance.y };
}
// Deserialize: reconstruct from plain data
static [WORKFLOW_DESERIALIZE](data: { x: number; y: number }) {
return new Point(data.x, data.y);
}
async computeDistance(other: Point) {
"use step";
return Math.sqrt((this.x - other.x) ** 2 + (this.y - other.y) ** 2);
}
}
```
**Critical rules:**
1. **Define serde methods INSIDE the class body** as static methods with computed property syntax (`static [WORKFLOW_SERIALIZE](...)`). The SWC plugin detects them by scanning the class. Do NOT assign them externally (e.g., `(MyClass as any)[WORKFLOW_SERIALIZE] = ...`) -- the compiler will not detect this.
2. **Serde methods must return only devalue-compatible types** (plain objects, arrays, primitives, Date, Map, Set, Uint8Array, etc.). No functions, no class instances, no Node.js-specific objects.
3. **Add `"use step"` to Node.js-dependent instance methods.** The SWC plugin strips `"use step"` method bodies from the workflow bundle. This is how you keep Node.js imports (fs, crypto, child_process, etc.) out of the workflow sandbox. The class shell with its serde methods remains in the workflow bundle; only the step method bodies are removed.
4. **Do NOT manually register classes.** The SWC plugin automatically generates registration code (an IIFE that sets `classId` and adds the class to the global registry). Manual calls to `registerSerializationClass()` are unnecessary and error-prone.
5. **Do NOT use dynamic imports to work around sandbox restrictions.** If a class method needs Node.js APIs, the correct solution is `"use step"`, not `/* @vite-ignore */ import(...)`.
**When serde works well:** Pure data classes, domain models, configuration objects, and classes where Node.js-dependent methods can be marked with `"use step"`.
**When to avoid serde:** If a class is fundamentally inseparable from Node.js APIs (every method needs `fs`, `net`, etc.) and cannot meaningfully exist as a shell in the workflow sandbox, keep it entirely in step functions and pass plain data objects across boundaries instead.
### Validating Serde Compliance
Use these tools to verify classes are correctly set up:
- **`workflow transform <file> --check-serde`** -- Shows the SWC transform output for a file and checks if serde classes are compliant (no Node.js imports remaining in the workflow bundle).
- **`workflow validate`** -- Scans all workflow files and reports serde compliance issues. Use `--json` for machine-readable output.
- **SWC Playground** -- The web playground at `workbench/swc-playground` shows a Serde Analysis panel when serde patterns are detected.
- **Build-time warnings** -- The builder automatically warns when serde classes have Node.js built-in imports remaining in the workflow bundle.
## Streaming
@@ -462,15 +527,45 @@ npx workflow cancel <run_id> --backend vercel --project <project-name> --team <t
# --env defaults to "production"; use --env preview for preview deployments
```
### Deep-linking to a run (share a URL, no browser)
Use `--url` to **print** the dashboard deep link and exit — no browser opens and
no local server starts. This is the right tool when you need to hand a user a
clickable link (PR comment, Slack message, debugging summary) rather than open a
UI. (`--web` opens the dashboard; `--url` only prints the link.)
```bash
# Vercel run — prints the Vercel dashboard URL for the run
npx workflow inspect run <run_id> --backend vercel --project <project> --team <team> --url
npx workflow web <run_id> --backend vercel --project <project> --team <team> --env preview --url
# Local run — prints the local web UI deep link
npx workflow inspect run <run_id> --url
# Machine-readable: --url --json prints { "url": "..." } to stdout
npx workflow inspect run <run_id> --backend vercel --url --json
```
URL formats produced:
- **Vercel:** `https://vercel.com/<team-slug>/<project-slug>/workflows/runs/<run_id>?environment=<production|preview>`
(`--env` selects the environment; defaults to `production`. Resolving the team
slug requires being logged in via `vercel login` with the project linked.)
- **Local:** `http://localhost:<port>?resource=run&id=<run_id>` (port defaults
to `3456`; the link works while the `npx workflow web` server is running).
stdout contains **only** the URL (or the JSON object) — all other output goes to
stderr — so you can capture it directly, e.g. `URL=$(npx workflow web <run_id> --backend vercel --url)`.
**Debugging tips:**
- Use `--json` (`-j`) on any command for machine-readable output
- Use `--web` to open the Vercel Observability dashboard in your browser
- Use `--web` to open the Vercel Observability dashboard in your browser, or `--url` to just print the deep link
- 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.
Workflow SDK 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:
@@ -553,3 +648,147 @@ await resumeWebhook(hook.token, new Request("https://example.com/webhook", {
- 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
## Observability & World SDK
Use `await getWorld()` to build observability dashboards, admin panels, and inspect workflow state. `getWorld()` is asynchronous and returns `Promise<World>` (dynamic import / env-based setup).
**Key imports:**
```typescript
import { getWorld } from "workflow/runtime";
import { hydrateResourceIO, observabilityRevivers, parseStepName, parseWorkflowName } from "workflow/observability";
```
**Key docs** (grep `node_modules/workflow/docs/` for full details):
- `api-reference/workflow-runtime/world/storage.mdx` — events, runs, steps, hooks (events are source of truth; others are materialized views)
- `api-reference/workflow-observability/` — hydration and name parsing
### World SDK Method Signatures
⚠️ Pagination is nested: `{ pagination: { cursor } }` — NOT `{ cursor }` directly.
```typescript
const world = await getWorld();
// Runs
const { data, cursor } = await world.runs.list({ pagination: { cursor }, resolveData: 'all' | 'none' });
const run = await world.runs.get(runId, { resolveData: 'all' | 'none' });
// Cancel via event creation (no cancel() method on runs)
await world.events.create(runId, { eventType: 'run_cancelled' });
// Steps — runId is top-level, NOT inside pagination
const { data, cursor } = await world.steps.list({ runId, pagination: { cursor }, resolveData: 'all' | 'none' });
const step = await world.steps.get(runId, stepId, { resolveData: 'all' | 'none' });
// Events
const { data, cursor } = await world.events.list({ runId, pagination: { cursor } });
await world.events.create(runId, { eventType: 'run_cancelled' });
// Hooks
const hook = await world.hooks.get(hookId);
const hook = await world.hooks.getByToken(token);
// Streams (methods on world.streams)
await world.streams.write(runId, name, chunk);
await world.streams.writeMulti?.(runId, name, chunks);
const readable = await world.streams.get(runId, name, startIndex);
await world.streams.close(runId, name);
const streamNames = await world.streams.list(runId);
const chunks = await world.streams.getChunks(runId, name, { limit, cursor });
const info = await world.streams.getInfo(runId, name);
// Queue (methods live directly on world — internal SDK infrastructure)
await world.queue(queueName, payload, opts);
const deploymentId = await world.getDeploymentId();
```
### `resolveData` Parameter
Controls whether input/output data is **included** in the response. Accepts `'all'` (default) or `'none'`.
**IMPORTANT**: Even with `'all'`, data is still devalue-serialized. You MUST call `hydrateResourceIO()` to get usable JS values.
- **Use `'none'`** for status polling, progress dashboards, run listings
- **Use `'all'`** (or omit) when you need to inspect actual step I/O data — then **always hydrate**
```typescript
// Lightweight status check — no I/O loaded
const run = await world.runs.get(runId, { resolveData: 'none' });
console.log(run.status); // 'running' | 'completed' | 'failed' | 'cancelled'
// Full inspection — resolveData includes data, hydrateResourceIO deserializes it
const step = await world.steps.get(runId, stepId); // defaults to 'all'
const hydrated = hydrateResourceIO(step, observabilityRevivers);
```
> **Common mistake**: Checking `step.input !== undefined` after `resolveData: 'all'` and assuming
> the data is ready to use. The data exists but is serialized — always hydrate first.
### Data Hydration (Devalue Format)
Step I/O is serialized via [devalue](https://github.com/Rich-Harris/devalue) with a 4-byte format prefix (`devl`). Without hydration, `input`/`output` are Uint8Array-like objects with numeric keys:
`{"0":100,"1":101,"2":118,"3":108,...}` — these are NOT usable values.
**Always hydrate before using I/O data:**
```typescript
import { hydrateResourceIO, observabilityRevivers } from "workflow/observability";
const { data: steps } = await world.steps.list({ runId, resolveData: 'all' });
const hydrated = steps.map(s => hydrateResourceIO(s, observabilityRevivers));
// hydrated[0].input → [123, 2] (actual function arguments)
// hydrated[0].output → 125 (actual return value)
```
`hydrateResourceIO` works on both `Step` and `WorkflowRun` objects. For encrypted workflows, use `getEncryptionKeyForRun()` + `hydrateResourceIOWithKey()`.
### Name Parsing
`parseWorkflowName()`, `parseStepName()`, and `parseClassName()` return `{ shortName: string, moduleSpecifier: string } | null`. Always use optional chaining:
```typescript
const parsed = parseWorkflowName("workflow//./src/workflows/order//processOrder");
// parsed?.shortName → "processOrder"
// parsed?.moduleSpecifier → "./src/workflows/order"
// ⚠️ Returns null if format doesn't match
```
### Event Types
Events are the append-only source of truth. Runs/Steps/Hooks are materialized views.
| Category | Types |
|----------|-------|
| Run | `run_created`, `run_started`, `run_completed`, `run_failed`, `run_cancelled` |
| Step | `step_created`, `step_started`, `step_completed`, `step_failed`, `step_retrying` |
| Hook | `hook_created`, `hook_received`, `hook_disposed`, `hook_conflict` |
| Wait | `wait_created`, `wait_completed` |
## Error Handling Patterns
Three error strategies for different failure modes:
| Error Type | Use When | Behavior |
|------------|----------|----------|
| `FatalError` | Permanent failure (bad input, auth denied) | Terminates workflow immediately, no retry |
| `RetryableError` | Transient failure (rate limit, timeout) | Retries with optional `retryAfter` delay |
| `Promise.allSettled` | Parallel steps with mixed criticality | Continues even if some steps fail |
```typescript
import { FatalError, RetryableError } from "workflow";
// Permanent failure — workflow terminates
throw new FatalError("Invalid input: missing required field");
// Transient failure — will retry
throw new RetryableError("API rate limited", { retryAfter: "5m" });
// Mixed criticality parallel execution
const results = await Promise.allSettled([
criticalStep(data), // Must succeed
optionalStep(data), // OK to fail
enrichmentStep(data), // OK to fail
]);
const [critical, optional, enrichment] = results;
if (critical.status === "rejected") throw new FatalError(critical.reason);
```
@@ -1,108 +0,0 @@
# Workflow DevKit — DurableAgent Patterns
## Basic DurableAgent
```ts
import { DurableAgent } from '@workflow/ai/agent'
import { openai } from '@ai-sdk/openai'
import { tool } from 'ai'
import { z } from 'zod'
const agent = new DurableAgent({
model: openai('gpt-5.2'),
system: 'You are a helpful research assistant.',
tools: {
searchWeb: tool({
description: 'Search the web for information',
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }) => {
// Search implementation
return { results: await webSearch(query) }
},
}),
writeReport: tool({
description: 'Write a report to a file',
inputSchema: z.object({
title: z.string(),
content: z.string(),
}),
execute: async ({ title, content }) => {
await writeFile(`reports/${title}.md`, content)
return { written: true }
},
}),
},
})
```
## Workflow Endpoint (Next.js)
```ts
// app/api/workflows/research/route.ts
'use workflow'
export async function POST(req: Request) {
const { topic } = await req.json()
const result = await agent.generateText({
prompt: `Research "${topic}" thoroughly and produce a comprehensive report.`,
})
return Response.json({ report: result.text })
}
```
## Workflow with Human-in-the-Loop
```ts
'use workflow'
export async function processApplication(applicationId: string) {
'use step'
const app = await getApplication(applicationId)
'use step'
const aiReview = await agent.generateText({
prompt: `Review this application: ${JSON.stringify(app)}`,
})
'use step'
await notifyReviewer(aiReview.text)
'use step'
// Pauses here until human approves — could be hours or days
const approval = await waitForEvent(`approval:${applicationId}`)
'use step'
if (approval.approved) {
await acceptApplication(applicationId)
} else {
await rejectApplication(applicationId, approval.reason)
}
}
```
## Workflow with Parallel Fan-Out
```ts
'use workflow'
export async function analyzeCompetitors(competitors: string[]) {
'use step'
const analyses = await Promise.all(
competitors.map(async (competitor) => {
'use step'
return await agent.generateText({
prompt: `Analyze ${competitor}'s product strategy.`,
})
})
)
'use step'
const summary = await agent.generateText({
prompt: `Synthesize these competitive analyses: ${analyses.map(a => a.text).join('\n\n')}`,
})
return summary.text
}
```
+8 -8
View File
@@ -242,7 +242,7 @@ AI SDK (v6, TypeScript) ⤳ skill: ai-sdk 📖 docs: https:/
│ ↔ AI Elements (render streaming responses) │
└── Key Patterns
↔ Next.js (chat apps, AI features in web apps)
↔ Workflow DevKit (durable agents)
↔ Workflow SDK (durable agents)
↔ AI Gateway (model routing, cost tracking)
↔ Generation Persistence (IDs, URLs, cost tracking) ⤳ skill: ai-sdk
↔ v0 (AI-generated UI components)
@@ -283,7 +283,7 @@ AI GATEWAY ⤳ skill: ai-gateway 📖 docs: htt
⊃ Text, Image, Video generation
↔ AI SDK (unified interface)
WORKFLOW DEVKIT (WDK) ⤳ skill: workflow 📖 docs: https://vercel.com/docs/workflow
WORKFLOW SDK ⤳ skill: workflow 📖 docs: https://vercel.com/docs/workflow
├── Core Concepts
│ ⊃ 'use workflow' directive
│ ⊃ 'use step' directive
@@ -385,7 +385,7 @@ CHAT SDK (TypeScript) ⤳ skill: chat-sdk 📖 docs: http
├── Key Patterns
│ ↔ AI SDK (streaming AI responses via thread.post(textStream))
│ ↔ Workflow DevKit (registerSingleton/reviver for durable serialization)
│ ↔ Workflow SDK (registerSingleton/reviver for durable serialization)
│ ↔ Vercel Functions (webhook handlers, waitUntil)
│ ↔ Next.js (API routes for webhooks)
│ ↔ Upstash Redis (state adapter backend)
@@ -642,7 +642,7 @@ VERCEL MARKETPLACE ⤳ skill: marketplace 📖 docs: h
| Structured data extraction | AI SDK `generateText` + `Output.object()` + AI Gateway | Type-safe, schema-validated |
| Agent loop embedded in an existing application | AI SDK `Agent` class + AI Gateway | Direct loop control and tool calling |
| New durable agent or agent-powered application | eve | Filesystem-first runtime with sessions, tools, skills, channels, sandboxes, subagents, schedules, evals, and frontend clients |
| Add durability to an existing agent or application workflow | Workflow DevKit `DurableAgent` | Crash-safe orchestration without adopting a complete agent framework |
| Add durability to an existing agent or application workflow | Workflow SDK `DurableAgent` | Crash-safe orchestration without adopting a complete agent framework |
| Browser UI for an eve agent | eve `useEveAgent` + AI Elements-compatible messages | Durable session streaming for React, Vue, or Svelte clients |
| Provider-specific features (e.g., computer use) | Direct provider SDK (`@ai-sdk/anthropic`) | Only when gateway doesn't expose the feature |
| Connect to external tools | AI SDK MCP Client | Standard protocol, OAuth |
@@ -770,7 +770,7 @@ Three distinct caching systems serve different purposes. They can be used indepe
```
1. Choose the architecture boundary:
- New filesystem-first agent or agent-powered app → eve
- Existing app/agent that needs durable orchestration → Workflow DevKit `DurableAgent`
- Existing app/agent that needs durable orchestration → Workflow SDK `DurableAgent`
2. eve path: npx eve@latest init <agent-name> → read node_modules/eve/docs/README.md
→ author instructions, tools, skills, connections, channels, and optional frontend client
3. Workflow path: Next.js Route Handler → DurableAgent → AI SDK tools → AI Gateway
@@ -845,7 +845,7 @@ Git Push → CI Pipeline → vercel build → vercel deploy --prebuilt
| DALL-E 2/3 | `model: 'google/gemini-3.1-flash-image-preview'` | Better quality, faster, cheaper |
| `gemini-2.0-flash-exp-image-generation` | `gemini-3.1-flash-image-preview` | Dramatically better quality |
| `gpt-4o` | `gpt-5.4` | Better, cheaper, faster |
| `experimental_createWorkflow` | `createWorkflow()` (stable) | WDK API stabilized |
| `experimental_createWorkflow` | `createWorkflow()` (stable) | Workflow SDK API stabilized |
| `"pipeline"` (turbo.json) | `"tasks"` | Turborepo v2 rename |
| `next/head` | `metadata` / `generateMetadata()` | App Router pattern (Pages Router only) |
| `next export` | `output: "export"` in next.config | CLI command removed |
@@ -919,7 +919,7 @@ Git Push → CI Pipeline → vercel build → vercel deploy --prebuilt
- Default to AI SDK v6 plus AI Gateway model strings (`provider/model`) rather than direct provider SDK wiring.
- Consider eve when the user is creating or architecting a new durable agent or agent-powered application; propose it as a framework candidate before installing or scaffolding it.
- Use eve for the complete filesystem-first agent runtime and clients, AI SDK for embedded model/agent loops, and Workflow DevKit when existing application logic primarily needs durable orchestration.
- Use eve for the complete filesystem-first agent runtime and clients, AI SDK for embedded model/agent loops, and Workflow SDK when existing application logic primarily needs durable orchestration.
- Use `@ai-sdk/react` for React hooks and keep user-facing AI experiences streaming-first.
- Prefer `toUIMessageStreamResponse()`, `DefaultChatTransport`, and modern tool definitions (`inputSchema`, `outputSchema`) over v5-era patterns.
- Keep AI recommendations scoped to the current task; do not force broad AI stack migrations unless the code already points there.
@@ -947,7 +947,7 @@ Git Push → CI Pipeline → vercel build → vercel deploy --prebuilt
### Workflow and Durability
- Use Workflow DevKit and DurableAgent when the task needs retries, resumability, crash recovery, or long-lived orchestration.
- Use Workflow SDK and DurableAgent when the task needs retries, resumability, crash recovery, or long-lived orchestration.
- Prefer eve when those requirements are part of a new agent application that also needs a structured home for instructions, tools, skills, connections, channels, sandboxes, subagents, schedules, evals, or frontend clients.
- Prefer workflow steps over ad-hoc retry loops, timers, and manual state persistence in request handlers.
- Keep workflow recommendations limited to durable execution problems; do not route ordinary request/response code into workflow patterns by default.