mirror of
https://github.com/vercel/vercel-plugin.git
synced 2026-09-14 15:39:47 +08:00
[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:
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
↓ ↓
|
||||
|
||||
@@ -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
|
||||
↓ ↓
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -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)[''""]'
|
||||
|
||||
@@ -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)[''""]'
|
||||
|
||||
@@ -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
@@ -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);
|
||||
```
|
||||
|
||||
@@ -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
|
||||
}
|
||||
```
|
||||
@@ -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
|
||||
}
|
||||
```
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user