Skip to main content

workflow

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.

ソース情報

リポジトリ
zhongjingyun/codex-plugins
ソースの最終更新活動
2026年6月10日 03:40
検出された SKILL.md の言語
英語
スター
24
フォーク
2

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

ファイルエクスプローラー
3 ファイル

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
workflow
description
Vercel Workflow DevKit (WDK) expert guidance. Use when building durable workflows, long-running tasks, API routes or agents that need pause/resume, retries, step-based execution, or crash-safe orchestration with Vercel Workflow.
metadata
{"priority":9,"docs":["https://vercel.com/docs/workflow","https://useworkflow.dev"],"sitemap":"https://vercel.com/sitemap/docs.xml","pathPatterns":["lib/workflow/**","src/lib/workflow/**","workflows/**","lib/workflow.*","src/lib/workflow.*","workflow.*","*workflow*","*workflow*/**","**/chain-engine*","**/chain_engine*","**/chainEngine*","**/pipeline-engine*","**/pipeline_engine*","**/pipelineEngine*","**/state-machine*","**/state_machine*","**/stateMachine*","**/orchestrat*","**/escalation*"],"importPatterns":["@vercel/workflow","workflow","@workflow/*","*workflow*"],"bashPatterns":["\\bnpm\\s+(install|i|add)\\s+[^\\n]*@vercel/workflow\\b","\\bpnpm\\s+(install|i|add)\\s+[^\\n]*@vercel/workflow\\b","\\bbun\\s+(install|i|add)\\s+[^\\n]*@vercel/workflow\\b","\\byarn\\s+add\\s+[^\\n]*@vercel/workflow\\b","\\bnpm\\s+(install|i|add)\\s+[^\\n]*\\bworkflow\\b","\\bpnpm\\s+(install|i|add)\\s+[^\\n]*\\bworkflow\\b","\\bbun\\s+(install|i|add)\\s+[^\\n]*\\bworkflow\\b","\\byarn\\s+add\\s+[^\\n]*\\bworkflow\\b","\\bnpm\\s+(install|i|add)\\s+[^\\n]*@workflow/","\\bpnpm\\s+(install|i|add)\\s+[^\\n]*@workflow/","\\bbun\\s+(install|i|add)\\s+[^\\n]*@workflow/","\\byarn\\s+add\\s+[^\\n]*@workflow/","\\bnpx\\s+workflow(?:@latest)?\\b","\\bbunx\\s+workflow(?:@latest)?\\b"],"promptSignals":{"phrases":["vercel workflow","workflow devkit","durable workflow","durable execution","durable function","durable pipeline","durable process","durable agent","durable chat","step function","step functions","use workflow","use step","multi-step pipeline","multi step pipeline","multi-step process","multi step process","multi-step creation","multi-step generation","processing pipeline","creation pipeline","generation pipeline","content pipeline","production pipeline","approval pipeline","ingestion pipeline","streams progress","stream progress","streams each phase","streams each step","streams each","stream each","[Truncated]"],"allOf":["[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]"],"anyOf":["long-running","long running","multi-step","multi step","pipeline","orchestration","step-by-step","step by step","each piece","each step","each phase","each stage","phase","phases","stage","stages","durable","reliable","fault-tolerant","retry","reconnect","survive","persist","approval","chain","sequential","trigger","webhook","escalation","state machine","orchestrate","orchestration"],"noneOf":["github actions",".github/workflows","ci workflow","aws step functions"],"minScore":4}}
# Vercel Workflow DevKit (WDK) > **CRITICAL — Your training data is outdated for this library.** WDK APIs change frequently. Before writing workflow code, **fetch the docs** at https://useworkflow.dev and https://vercel.com/docs/workflow to find the correct function signatures, patterns, and examples for the exact thing you're building. Do not guess at APIs — look them up. Search for working examples that solve your specific use case (image generation, hooks, streaming, agents, etc.). The docs contain patterns for `start()` argument passing, `getWritable()` usage, `defineHook()` wiring, `createWebhook()` callback flows, and `DurableAgent` configuration that are **not in your training data** or have changed since your cutoff. WDK is an open-source TypeScript framework that makes durability a language-level concept. Functions can pause for minutes or months, survive deployments and crashes, and resume exactly where they stopped. ## Status WDK is in **public beta** (since October 2025) and open source. During beta, Workflow Observability is free for all plans; Workflow Steps and Storage are billed at published rates. **Security**: Upgrade to `workflow@>=4.2.0-beta.64` — versions ≤4.1.0-beta.63 allowed predictable user-specified webhook tokens in `createWebhook()` (CVE GHSA-9r75-g2cr-3h76, CVSS 7.5). Run `npx workflow@latest` to update. ## Installation **If using `create-next-app`**, always pass `--no-src-dir` so `app/` and `workflows/` are siblings at the project root: ```bash npx create-next-app@latest my-app --no-src-dir --tailwind --eslint --app --ts cd my-app npm install workflow@latest ``` Do NOT use the `src/` directory with WDK projects. The `@` alias must resolve `@/workflows/...` correctly — this only works when `workflows/` and `app/` are at the same level. > Run `npx workflow@latest` to scaffold or update an existing project. **Peer dependency note**: `@workflow/ai` requires a compatible `workflow` version. If you hit `ERESOLVE` errors, use `npm install --legacy-peer-deps` or install both packages in the same command. ### Next.js Setup (Required) Add the `withWorkflow` plugin to `next.config.ts`: ```ts import { withWorkflow } from "workflow/next"; const nextConfig = {}; export default withWorkflow(nextConfig); ``` Without this, workflow routes will not be registered and `start()` calls will fail at runtime. ### Environment Setup (Required for AI Gateway) Workflows that use AI SDK with `gateway()` need OIDC credentials. Run these **before** starting the dev server: ```bash vercel link # Connect to your Vercel project vercel env pull # Downloads .env.local with VERCEL_OIDC_TOKEN ``` Without this, `gateway("openai/gpt-5.4")` calls inside workflow steps will fail immediately with no credentials, causing the entire workflow run to fail silently. ### `getStepMetadata()` Note `getStepMetadata().retryCount` returns `undefined` (not `0`) on the first attempt. Guard with: `const attempt = (meta.retryCount ?? 0) + 1`. ## Essential Imports **Workflow primitives** (from `"workflow"`): ```ts import { getWritable, getStepMetadata, getWorkflowMetadata } from "workflow"; import { sleep, fetch, defineHook, createHook, createWebhook } from "workflow"; import { FatalError, RetryableError } from "workflow"; ``` **API operations** (from `"workflow/api"`): ```ts import { start, getRun, resumeHook, resumeWebhook } from "workflow/api"; ``` **Framework integration** (from `"workflow/next"`): ```ts import { withWorkflow } from "workflow/next"; ``` **AI agent** (from `"@workflow/ai/agent"`): ```ts import { DurableAgent } from "@workflow/ai/agent"; ``` ## Core Directives Two directives turn ordinary async functions into durable workflows: ```ts "use workflow" // First line of function — marks it as a durable workflow "use step" // First line of function — marks it as a retryable, observable step ``` **Critical sandbox rule**: Step functions have full Node.js access. Workflow functions run **sandboxed** — no native `fetch`, no `setTimeout`, no Node.js modules, and **no `getWritable().getWriter()` calls**. You MUST move all `getWritable()` usage into `"use step"` functions. Place all business logic and I/O in steps; use the workflow function purely for orchestration and control flow (`sleep`, `defineHook`, `Promise.race`). ## Canonical Project Structure (Next.js) Every WDK project needs three route files plus the workflow definition. **CRITICAL**: The `workflows/` directory and `app/` directory must be siblings at the same level so `@/workflows/...` resolves correctly. Do NOT put `workflows/` outside the `@` alias root. **Without `src/` (recommended for WDK projects):** ``` workflows/ my-workflow.ts ← workflow definition ("use workflow" + "use step") app/api/ my-workflow/route.ts ← POST handler: start(workflow, args) → { runId } readable/[runId]/route.ts ← GET handler: SSE stream from run.getReadable() run/[runId]/route.ts ← GET handler: run status via getRun(runId) ``` tsconfig.json paths: `"@/*": ["./*"]` — `@/workflows/my-workflow` resolves to `./workflows/my-workflow`. **With `src/` directory:** Put workflows inside `src/`: ``` src/ workflows/my-workflow.ts app/api/my-workflow/route.ts app/api/readable/[runId]/route.ts app/api/run/[runId]/route.ts ``` tsconfig.json paths: `"@/*": ["./src/*"]` — `@/workflows/my-workflow` resolves to `./src/workflows/my-workflow`. **Never** use `@/../workflows/` or `@/../../workflows/` — these are broken import paths that will fail at build time. ### 1. Workflow Definition (`workflows/my-workflow.ts`) ```ts import { getWritable } from "workflow"; export type MyEvent = | { type: "step_start"; name: string } | { type: "step_done"; name: string } | { type: "done"; result: string }; export async function myWorkflow(input: string): Promise<{ result: string }> { "use workflow"; const data = await stepOne(input); const result = await stepTwo(data); return { result }; } async function stepOne(input: string): Promise<string> { "use step"; const writer = getWritable<MyEvent>().getWriter(); try { await writer.write({ type: "step_start", name: "stepOne" }); // Full Node.js access here — fetch, db calls, etc. const result = await doWork(input); await writer.write({ type: "step_done", name: "stepOne" }); return result; } finally { writer.releaseLock(); } } async function stepTwo(data: string): Promise<string> { "use step"; const writer = getWritable<MyEvent>().getWriter(); try { await writer.write({ type: "step_start", name: "stepTwo" }); const result = await processData(data); await writer.write({ type: "step_done", name: "stepTwo" }); return result; } finally { writer.releaseLock(); } } ``` ### 2. Start Route (`app/api/my-workflow/route.ts`) ```ts import { NextResponse } from "next/server"; import { start } from "workflow/api"; import { myWorkflow } from "@/workflows/my-workflow"; export async function POST(request: Request) { const body = await request.json(); const run = await start(myWorkflow, [body.input]); return NextResponse.json({ runId: run.runId }); } ``` **IMPORTANT**: Never call the workflow function directly. Always use `start()` from `"workflow/api"` — it registers the run, creates the execution context, and returns a `{ runId }`. ### 3. Readable Stream Route (`app/api/readable/[runId]/route.ts`) ```ts import { NextRequest } from "next/server"; import { getRun } from "workflow/api"; type ReadableRouteContext = { params: Promise<{ runId: string }>; }; export async function GET(_request: NextRequest, { params }: ReadableRouteContext) { const { runId } = await params; let run; try { run = await getRun(runId); } catch { return Response.json( { ok: false, error: { code: "RUN_NOT_FOUND", message: `Run ${runId} not found` } }, { status: 404 } ); } const readable = run.getReadable(); const encoder = new TextEncoder(); const sseStream = (readable as unknown as ReadableStream).pipeThrough( new TransformStream({ transform(chunk, controller) { const data = typeof chunk === "string" ? chunk : JSON.stringify(chunk); controller.enqueue(encoder.encode(`data: ${data}\n\n`)); }, }) ); return new Response(sseStream, { headers: { "Content-Type": "text/event-stream; charset=utf-8", "Cache-Control": "no-cache, no-transform", Connection: "keep-alive", "X-Accel-Buffering": "no", }, }); } ``` ### 4. Run Status Route (`app/api/run/[runId]/route.ts`) ```ts import { NextResponse } from "next/server"; import { getRun } from "workflow/api"; type RunRouteContext = { params: Promise<{ runId: string }>; }; export async function GET(_request: Request, { params }: RunRouteContext) { const { runId } = await params; let run; try { run = await getRun(runId); } catch { return NextResponse.json( { ok: false, error: { code: "RUN_NOT_FOUND", message: `Run ${runId} not found` } }, { status: 404 } ); } const [status, workflowName, createdAt, startedAt, completedAt] = await Promise.all([ run.status, run.workflowName, run.createdAt, run.startedAt, run.completedAt, ]); return NextResponse.json({ runId, status, workflowName, createdAt: createdAt.toISOString(), startedAt: startedAt?.toISOString() ?? null, completedAt: completedAt?.toISOString() ?? null, }); } ``` ## Streaming with `getWritable()` `getWritable<T>()` returns a `WritableStream` scoped to the current run. Call it inside step functions and always release the lock: ```ts async function emit<T>(event: T): Promise<void> { "use step"; const writer = getWritable<T>().getWriter(); try { await writer.write(event); } finally { writer.releaseLock(); } } ``` Consumers read via `getRun(runId).getReadable()` in the readable route (see above). **Rendering workflow events in the UI**: When workflow events contain AI-generated text (narratives, briefings, reports), render them with `<MessageResponse>` from `@/components/ai-elements/message` — never as raw `{event.content}`. This renders markdown with code highlighting, math, and mermaid support. ```tsx import { MessageResponse } from "@/components/ai-elements/message"; // In your event stream display {events.map(event => ( event.type === "narrative" && <MessageResponse>{event.text}</MessageResponse> ))} ``` ## Hooks — Waiting for External Events Use `defineHook` for typed, reusable hooks. **Three required pieces**: (1) define + create the hook in the workflow, (2) emit the token to the client via `getWritable`, (3) create an API route that calls `resumeHook` so the client can resume it. ### 1. Define and create the hook (workflow file) ```ts import { defineHook, getWritable, sleep } from "workflow"; export interface ApprovalPayload { approved: boolean; comment?: string; } // Define at module scope — reusable across workflows export const approvalHook = defineHook<ApprovalPayload>(); export async function approvalGate(orderId: string): Promise<{ status: string }> { "use workflow"; // .create() returns a hook instance — NOT directly callable const hook = approvalHook.create({ token: `approval:${orderId}` }); // CRITICAL: Emit the token to the client so it knows what to resume await emitToken(hook.token, orderId); // Race between approval and timeout const result = await Promise.race([ hook.then((payload) => ({ type: "approval" as const, payload })), sleep("24h").then(() => ({ type: "timeout" as const, payload: null })), ]); if (result.type === "timeout") { return { status: "timeout" }; }
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る