diff --git a/.claude/skills/benchmark-agents/SKILL.md b/.claude/skills/benchmark-agents/SKILL.md index 60954efe..16e30f78 100644 --- a/.claude/skills/benchmark-agents/SKILL.md +++ b/.claude/skills/benchmark-agents/SKILL.md @@ -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. diff --git a/README.md b/README.md index a5f4cd4a..3e28a518 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ A text-form relational graph covering: | `vercel-sandbox` | Ephemeral Firecracker microVMs for running untrusted/AI-generated code safely | | `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) @@ -264,7 +264,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) - Storage (Blob, Edge Config, Neon Postgres, Upstash Redis) diff --git a/agents/ai-architect.md b/agents/ai-architect.md index bc625393..e4f7f194 100644 --- a/agents/ai-architect.md +++ b/agents/ai-architect.md @@ -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 ↓ ↓ diff --git a/agents/ai-architect.md.tmpl b/agents/ai-architect.md.tmpl index b4fb4393..1c790c7b 100644 --- a/agents/ai-architect.md.tmpl +++ b/agents/ai-architect.md.tmpl @@ -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 ↓ ↓ diff --git a/agents/deployment-expert.md b/agents/deployment-expert.md index 27333f11..bd66d4d8 100644 --- a/agents/deployment-expert.md +++ b/agents/deployment-expert.md @@ -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 ``` diff --git a/generated/skill-manifest.json b/generated/skill-manifest.json index 170937b0..d03e751e 100644 --- a/generated/skill-manifest.json +++ b/generated/skill-manifest.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-07-31T18:16:05.834Z", + "generatedAt": "2026-08-05T16:45:57.300Z", "version": 2, "skills": { "access-protected-vercel-deployment": { @@ -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" }, { @@ -1236,7 +1236,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[''\"]" }, { @@ -3685,11 +3685,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" }, { @@ -3711,7 +3711,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", @@ -3731,7 +3731,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['\\\"]" }, { @@ -3749,7 +3749,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)[''\"\"]" } ], @@ -4443,7 +4443,7 @@ "summary": "", "docs": [ "https://vercel.com/docs/workflow", - "https://useworkflow.dev" + "https://workflow-sdk.dev" ], "sitemap": "https://vercel.com/sitemap/docs.xml", "pathPatterns": [ @@ -4530,7 +4530,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" }, { @@ -4541,7 +4541,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.", @@ -4611,7 +4611,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" }, { @@ -4630,6 +4630,7 @@ "promptSignals": { "phrases": [ "vercel workflow", + "workflow sdk", "workflow devkit", "durable workflow", "durable execution", @@ -5227,6 +5228,7 @@ "implement step function" ], "entities": [ + "Workflow SDK", "Workflow DevKit", "WDK", "step", diff --git a/skills/ai-sdk/SKILL.md b/skills/ai-sdk/SKILL.md index f233ec4e..1e66836e 100644 --- a/skills/ai-sdk/SKILL.md +++ b/skills/ai-sdk/SKILL.md @@ -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/" diff --git a/skills/ai-sdk/overlay.yaml b/skills/ai-sdk/overlay.yaml index 4d6cf9b9..0c3268fb 100644 --- a/skills/ai-sdk/overlay.yaml +++ b/skills/ai-sdk/overlay.yaml @@ -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/" diff --git a/skills/chat-sdk/SKILL.md b/skills/chat-sdk/SKILL.md index 8eb5dbee..a1725f40 100644 --- a/skills/chat-sdk/SKILL.md +++ b/skills/chat-sdk/SKILL.md @@ -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)[''""]' diff --git a/skills/chat-sdk/overlay.yaml b/skills/chat-sdk/overlay.yaml index 62f50631..de5bf273 100644 --- a/skills/chat-sdk/overlay.yaml +++ b/skills/chat-sdk/overlay.yaml @@ -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)[''""]' diff --git a/skills/vercel-functions/SKILL.md b/skills/vercel-functions/SKILL.md index fa457c2a..48b9c892 100644 --- a/skills/vercel-functions/SKILL.md +++ b/skills/vercel-functions/SKILL.md @@ -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 ``` diff --git a/skills/workflow/SKILL.md b/skills/workflow/SKILL.md index 1af247a3..ecc24adf 100644 --- a/skills/workflow/SKILL.md +++ b/skills/workflow/SKILL.md @@ -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,9 @@ import { getWorkflowMetadata, getStepMetadata } from "workflow"; // API operations import { start, getRun, resumeHook, resumeWebhook } from "workflow/api"; +// 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 +721,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, 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(...)`. -**Not supported:** Functions, class instances, Symbols, WeakMap/WeakSet. Pass data, not callbacks. +**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 --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 +951,45 @@ npx workflow cancel --backend vercel --project --team --backend vercel --project --team --url +npx workflow web --backend vercel --project --team --env preview --url + +# Local run — prints the local web UI deep link +npx workflow inspect run --url + +# Machine-readable: --url --json prints { "url": "..." } to stdout +npx workflow inspect run --backend vercel --url --json +``` + +URL formats produced: + +- **Vercel:** `https://vercel.com///workflows/runs/?environment=` + (`--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:?resource=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 --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 +1072,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` (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); +``` diff --git a/skills/workflow/overlay.yaml b/skills/workflow/overlay.yaml index 5538af54..768637c3 100644 --- a/skills/workflow/overlay.yaml +++ b/skills/workflow/overlay.yaml @@ -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 diff --git a/skills/workflow/references/durable-agent-patterns.md b/skills/workflow/references/durable-agent-patterns.md deleted file mode 100644 index 88322a97..00000000 --- a/skills/workflow/references/durable-agent-patterns.md +++ /dev/null @@ -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 -} -``` diff --git a/skills/workflow/upstream/SKILL.md b/skills/workflow/upstream/SKILL.md index 8c7a482f..d5a0e520 100644 --- a/skills/workflow/upstream/SKILL.md +++ b/skills/workflow/upstream/SKILL.md @@ -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,9 @@ import { getWorkflowMetadata, getStepMetadata } from "workflow"; // API operations import { start, getRun, resumeHook, resumeWebhook } from "workflow/api"; +// 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 +294,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, 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(...)`. -**Not supported:** Functions, class instances, Symbols, WeakMap/WeakSet. Pass data, not callbacks. +**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 --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 +524,45 @@ npx workflow cancel --backend vercel --project --team --backend vercel --project --team --url +npx workflow web --backend vercel --project --team --env preview --url + +# Local run — prints the local web UI deep link +npx workflow inspect run --url + +# Machine-readable: --url --json prints { "url": "..." } to stdout +npx workflow inspect run --backend vercel --url --json +``` + +URL formats produced: + +- **Vercel:** `https://vercel.com///workflows/runs/?environment=` + (`--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:?resource=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 --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 +645,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` (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); +``` diff --git a/skills/workflow/upstream/references/durable-agent-patterns.md b/skills/workflow/upstream/references/durable-agent-patterns.md deleted file mode 100644 index 88322a97..00000000 --- a/skills/workflow/upstream/references/durable-agent-patterns.md +++ /dev/null @@ -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 -} -``` diff --git a/vercel.md b/vercel.md index 59167c43..942108c4 100644 --- a/vercel.md +++ b/vercel.md @@ -234,7 +234,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) @@ -275,7 +275,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 @@ -369,7 +369,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) @@ -626,7 +626,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 | @@ -754,7 +754,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 → 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 @@ -829,7 +829,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 | @@ -903,7 +903,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. @@ -931,7 +931,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.