skills/workflow/SKILL.md
Build applications that use the Vercel Workflow SDK against a self-hosted Platformatic Workflow Service via `@platformatic/world`. Use when users ask to: - "add workflow to my app", "use workflow sdk", "self-host vercel workflow" - "set up @platformatic/world", "platformatic world", "PLT_WORLD_SERVICE_URL" - "trigger a workflow run", "start a workflow", "resume a hook" - "wire workflow in Next.js", "instrumentation.ts for workflow" - "use workflow with Fastify", "@platformatic/workflow-fastify" - "workflow build --target standalone", "callback handlers", ".well-known/workflow" - "queue handler", "world.start()", "register handlers" Covers `@platformatic/world` (the World adapter), the Vercel Workflow SDK `'use workflow'` / `'use step'` authoring model, framework wiring (Next.js via `instrumentation.ts`, Node/Express/etc. via explicit `world.start()`), and the optional `@platformatic/workflow-fastify` plugin for mounting standalone-build handlers on Fastify. Does NOT cover deploying or operating the Workflow Service itself — assumes one is already reachable at `PLT_WORLD_SERVICE_URL`.
npx skillsauth add platformatic/skills workflowInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are an expert in building applications with the Vercel Workflow SDK
(workflow / @workflow/*) against @platformatic/world,
the self-hosted World adapter for Platformatic.
This skill helps users build apps that use workflows. It does not cover
deploying the Workflow Service — assume one is reachable at the URL the user
will supply as PLT_WORLD_SERVICE_URL.
Before any workflow setup, verify:
Node.js Version: @platformatic/world requires Node.js v22.19.0+
node --version
Workflow Service URL: The user needs a reachable Workflow Service.
http://localhost:3042 (started via
npx @platformatic/workflow postgresql://...).@platformatic/workflow quick start
before continuing — but do not try to start the service from this skill.Existing package.json: This skill installs into an existing Node app.
Based on user input ($ARGUMENTS), route to the appropriate workflow:
| Input Pattern | Action |
|---|---|
| init, setup, add, (empty) | Run Detect Framework + World Setup |
| next, nextjs, next.js | Run Next.js Setup |
| node, express, koa, generic | Run Generic Node.js Setup |
| fastify | Run Fastify Setup (with plugin) |
| author, write, use workflow, use step, step, hook, sleep, stream | Read references/authoring.md |
| trigger, start, run | Run Trigger a Workflow guidance |
| build, standalone | Run Standalone Build Workflow |
| env, config | Run Environment Configuration |
| status, check | Run Status Check |
| troubleshoot, debug | Read references/troubleshooting.md |
When the user asks to "add workflow" without specifying a framework:
next.config.{js,ts,mjs} or next in package.json → Next.js Setupfastify in package.json → Fastify Setup (with plugin)
(the plugin is optional — also offer plain Node setup)In all cases, the core install is the same:
npm install workflow @platformatic/world
workflow is the Vercel SDK; @platformatic/world is the World adapter that
points the SDK at the Platformatic Workflow Service.
Then read:
createWorld, world.start(), framework wiring).'use workflow', 'use step',
retries, hooks, sleeps, streams, determinism rules).Continue with the framework-specific section below for the wiring details.
When the user runs 'use workflow' / 'use step' files inside a Next.js app:
npm install workflow @platformatic/world
instrumentation.ts at the project root:
// instrumentation.ts
export async function register () {
if (process.env.PLT_WORLD_SERVICE_URL) {
const { createWorld } = await import('@platformatic/world')
const world = createWorld()
await world.start?.()
}
}
This registers the app's queue handler endpoints (/.well-known/workflow/v1/{flow,step,webhook})
with the Workflow Service on boot. In K8s + ICC, world.start() is a no-op
(ICC registers handlers via the control plane)..env.local:
WORKFLOW_TARGET_WORLD=@platformatic/world
PLT_WORLD_SERVICE_URL=http://localhost:3042
# Optional:
PLT_WORLD_APP_ID=my-app-id # defaults to package.json name
PLT_WORLD_DEPLOYMENT_VERSION=local # auto-detected in K8s
'use workflow' / 'use step' files automatically inside Next.js.import { start } from 'workflow/api'
import { handleSignup } from '@/workflows/signup'
await start(handleSignup, [email])
npm run dev and watch the Next.js logs for the
[workflow] handler registration line.For Node servers without a framework-specific transform (Express, Koa, Hono,
plain http), the SDK still works but you call world.start() explicitly.
npm install workflow @platformatic/world
world.start() once during server boot, before accepting traffic:
import { createWorld } from '@platformatic/world'
const world = createWorld()
await world.start?.()
// ...then app.listen(PORT)
'use workflow' /
'use step' files. If the host framework does not transform them, use
the standalone build (see Standalone Build Workflow) and mount the
resulting handlers on your routes — or use the Fastify plugin which does
this for you.When the user wants to host Vercel Workflow SDK workflows inside a Fastify
app, recommend @platformatic/workflow-fastify. It is optional — they can
also do the work manually following the Generic Node.js Setup.
npm install fastify workflow @platformatic/world @platformatic/workflow-fastify
workflows/signup.ts) with
'use workflow' / 'use step'.npx workflow build --target standalone
This emits flow, step, webhook, and manifest.json under
.well-known/workflow/v1/.import Fastify from 'fastify'
import { start } from 'workflow/api'
import workflowFastify from '@platformatic/workflow-fastify'
const app = Fastify()
await app.register(workflowFastify)
app.post('/api/signup', async (req) => {
const run = await start(
{ workflowId: app.workflows.handleSignup },
[req.body.email]
)
return { runId: run.runId }
})
await app.listen({ port: Number(process.env.PORT) })
npx workflow build --target standalone && node server.js
The plugin:
flow, step, webhook handlers under /.well-known/workflow/v1/.app.workflows (name → workflowId map).world.start() on boot to register the queue handler.When the user asks how to start a workflow run from application code:
@platformatic/world:
import { start } from 'workflow/api'
import { handleSignup } from '@/workflows/signup'
const run = await start(handleSignup, [email])
const run = await start({ workflowId: app.workflows.handleSignup }, [email])
import { resumeHook } from 'workflow/api'
await resumeHook(token, payload)
The Vercel SDK's workflow build --target standalone produces self-contained
bundles for hosts that don't have a built-in SDK transform (e.g. Fastify,
plain Node, scripts).
'use workflow' / 'use step' files from your source tree.<buildDir>/.well-known/workflow/v1/:
flow — workflow orchestration handler (POST, Web Request => Response)step — step execution handlerwebhook — webhook resume handlermanifest.json — workflow/step ids and graph.mjs; v4 emits .js (flow/step
CommonJS, webhook ESM). @platformatic/workflow-fastify handles both
transparently..well-known/workflow/v1/ — add it to .gitignore alongside
dist/.npx workflow build --target standalone
Inside Next.js you do not need this — the Next plugin transforms workflows inline. The standalone build is for non-Next hosts.
The SDK and @platformatic/world read these environment variables:
| Variable | Required | Default | Description |
|---|---|---|---|
| WORKFLOW_TARGET_WORLD | Yes | — | Set to @platformatic/world to select this adapter |
| PLT_WORLD_SERVICE_URL | Yes | — | Workflow Service base URL (e.g. http://localhost:3042) |
| PLT_WORLD_APP_ID | No | package.json name | Application identifier (multi-tenant routing) |
| PLT_WORLD_DEPLOYMENT_VERSION | No | K8s label or 'local' | Version each run pins to |
| PORT | No | — | Used by world.start() to register the callback URL locally |
In K8s, PLT_WORLD_DEPLOYMENT_VERSION is auto-detected from the pod's
plt.dev/version label via the K8s API — usually leave it unset there.
For local development without K8s, the Workflow Service runs in single-tenant
mode and any appId is accepted.
When user runs /workflow status:
Node.js Version
node --version
Check if >= v22.19.0.
Dependencies present
node -e "require('workflow/package.json')"
node -e "require('@platformatic/world/package.json')"
Required env vars set
node -e "console.log({world:process.env.WORKFLOW_TARGET_WORLD, url:process.env.PLT_WORLD_SERVICE_URL})"
WORKFLOW_TARGET_WORLD must equal @platformatic/world.
PLT_WORLD_SERVICE_URL must be a reachable URL.
Service reachable
curl -fsS "$PLT_WORLD_SERVICE_URL/api/v1/apps/default/runs" >/dev/null && echo OK
For Next.js: instrumentation.ts exists at the project root and
calls world.start() inside register().
For Fastify with plugin: .well-known/workflow/v1/manifest.json exists
(i.e. the standalone build has been run).
Report findings in a clear format:
Workflow SDK Configuration Status
=================================
Node.js Version: vX.X.X [OK/UPGRADE NEEDED]
workflow installed: [vX.X.X/Missing]
@platformatic/world: [vX.X.X/Missing]
WORKFLOW_TARGET_WORLD: [@platformatic/world/Missing]
PLT_WORLD_SERVICE_URL: [URL/Missing]
Service reachable: [OK/Unreachable]
Framework wiring: [instrumentation.ts/Fastify plugin/manual]
Standalone build: [Present/Not built/N/A]
@platformatic/world is a runtime drop-in for @workflow/world. The
Vercel SDK calls into it via the standard World interface; nothing
application-level changes.world.start() must run exactly once per process before accepting
traffic. Calling it twice is harmless (idempotent) but wastes a call.
Skipping it means queue messages will never reach this process and runs
will hang in pending.world.start() is a no-op — ICC registers handlers
centrally via the control plane.@platformatic/world declares spec v3 (CBOR queue
transport). It interoperates with v2 servers and v2 clients, so version
skew during rollout is safe.[email protected]
(stable) and [email protected] (next). The world is typed against
@workflow/[email protected] but exposes the v5 streams.* namespace at
runtime alongside the v4 flat methods.For common issues (runs stuck in pending, world.start() failing, missing
callback URL, K8s deployment version mismatch), read
references/troubleshooting.md.
tools
Integrate, configure, and deploy Platformatic Watt for Node.js and PHP applications. Use when users ask to: - "add watt", "setup watt", "integrate watt", "configure watt" - "deploy with watt", "containerize my app", "deploy to kubernetes" - "migrate to watt", "port my app to watt" - "create watt.json", "configure platformatic" - "wattpm", "wattpm create", "wattpm inject", "wattpm logs" - use wattpm CLI commands, manage running applications - work with Node.js application servers - run PHP, WordPress, or Laravel in Node.js Supports Next.js, Express, Fastify, Koa, Remix, Astro, NestJS, PHP, WordPress, and Laravel.
development
Set up Kafka-based event-driven microservices with Platformatic Watt. Use when users ask about: - "kafka", "event-driven", "messaging" - "kafka hooks", "kafka webhooks" - "kafka producer", "kafka consumer" - "dead letter queue", "DLQ" - "request response pattern" with Kafka - "migrate from kafkajs", "kafkajs migration", "replace kafkajs" - "node-rdkafka", "node rdkafka", "rdkafka", "librdkafka" - "migrate from node-rdkafka", "replace node-rdkafka" - "Kafka.Producer", "Kafka.KafkaConsumer", "ProducerStream", "ConsumerStream" Covers @platformatic/kafka, @platformatic/kafka-hooks, consumer lag monitoring, OpenTelemetry instrumentation, and migrations from KafkaJS or node-rdkafka.
tools
Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence.
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------