Migrating to the Workflow SDK
Use this skill when converting an existing orchestration system to the Workflow SDK.
Intake
- Identify the source system:
- Temporal
- Inngest
- Trigger.dev
- AWS Step Functions
- Identify the target runtime:
- Managed hosting -> keep examples focused on
start(), getRun(), hooks/webhooks, and route handlers.
- Self-hosted -> also read
references/runtime-targets.md and explicitly say the workflow/step code can stay the same, but deployment still needs a World implementation and startup bootstrap.
- Extract the source constructs:
- entrypoint
- waits / timers
- external callbacks / approvals
- retries / failure handling
- child workflows / fan-out
- progress streaming
- external side effects
Default migration rules
- Put orchestration in
"use workflow" functions.
- Put side effects, SDK calls, DB calls, HTTP calls, and stream I/O in
"use step" functions.
- Use
sleep() only in workflow context.
- For Signals,
step.waitForEvent(), and .waitForTaskToken, choose exactly one resume surface:
resume/internal -> createHook() + resumeHook() when the app resumes from server-side code with a deterministic business token.
resume/url/default -> createWebhook() when the external system needs a generated callback URL and the default 202 Accepted response is fine.
resume/url/manual -> createWebhook({ respondWith: 'manual' }) only when the prompt explicitly requires a custom response body, status, or headers.
- If a callback-URL prompt does not specify response semantics, default to
resume/url/default and make the assumption explicit in ## Open Questions.
- Never pair
createWebhook() with resumeHook(), and never pass token: to createWebhook().
- Wrap
start() and getRun() inside "use step" functions for child runs.
- Use
getStepMetadata().stepId as the idempotency key for external writes.
- Use
getWritable() in workflow context to obtain the stream, but interact with it (write, close) only inside "use step" functions.
- Prefer rollback stacks for multi-step compensation.
- Choose app-boundary syntax in this order:
- If the prompt explicitly asks for framework-agnostic app-boundary code, use plain
Request / Response even when a framework like Hono is named.
- Otherwise, if the target framework is named, shape app-boundary examples to that framework.
- Otherwise, keep examples framework-agnostic with
Request / Response. Do not default to Next.js-only route signatures unless Next.js is explicitly named.
Fast memory aid:
- Callback URL + default ack ->
createWebhook()
- Callback URL + custom ack ->
createWebhook({ respondWith: 'manual' })
- Deterministic server-side resume ->
createHook() + resumeHook()
Fast-path router
Load references/resume-routing.md when the source pauses for Signals, step.waitForEvent(), or .waitForTaskToken.
Fast defaults:
- callback URL only ->
resume/url/default
- callback URL + explicit custom response ->
resume/url/manual
- deterministic server-side resume ->
resume/internal
- self-hosted -> add
runtime/self-hosted
- named framework -> add
boundary/named-framework
- explicit framework-agnostic request -> add
boundary/framework-agnostic
Before drafting ## Migrated Code, write the selected route keys in ## Migration Plan.
Source references
- Temporal ->
references/temporal.md
- Inngest ->
references/inngest.md
- Trigger.dev ->
references/trigger-dev.md
- AWS Step Functions ->
references/aws-step-functions.md
Shared references
references/shared-patterns.md — reusable code templates for hooks, child workflows, idempotency, streaming, and rollback.
references/runtime-targets.md — Managed vs custom World guidance.
references/resume-routing.md — route-key selection, obligations, and exact ## Migration Plan shape.
references/retries.md — canonical retry mechanics: stepFn.maxRetries, RetryableError({ retryAfter }), FatalError.
Required output shape
Return the migration in this structure:
## Migration Plan
## Source -> Target Mapping
## Migrated Code
## App Boundary / Resume Endpoints
## Verification Checklist
## Open Questions
Verification checklist
Fail the draft if any of these are true:
Validation note:
- Reading webhook request data in workflow context is allowed. Only
request.respondWith() is step-only.
Additional fail conditions:
resume/internal output omits resumeHook() in app-boundary code
resume/internal output omits a deterministic business token
resume/internal output emits createWebhook() or webhook.url
resume/url/default output does not pass webhook.url to the external system
resume/url/default output emits resumeHook(), respondWith: 'manual', or RequestWithResponse without a custom-response requirement in the prompt
resume/url/default output invents a user-authored callback route or resumeWebhook() wrapper when webhook.url is the intended resume surface
resume/url/manual output does not pass webhook.url to the external system
resume/url/manual output omits RequestWithResponse or await request.respondWith(...)
resume/url/manual output calls request.respondWith(...) outside a "use step" function
resume/url/manual output invents a user-authored callback route or resumeWebhook() wrapper when webhook.url is the intended resume surface
createWebhook() is paired with resumeHook()
- self-hosted output omits
World extends Queue, Streamer, Storage, startWorkflowWorld(), or the explicit note that the workflow and step code can stay the same while the app still needs a custom World
- named-framework output mixes framework syntax with plain
Request / Response app-boundary code without a framework-agnostic override
For concrete passing code, load:
references/shared-patterns.md -> ## Generated callback URL (default response)
references/shared-patterns.md -> ## Generated callback URL (manual response)
references/runtime-targets.md -> ## Self-hosted output block
references/aws-step-functions.md -> ## Combined recipe: callback URL on self-hosted Hono
Sample prompt
Migrate this Inngest workflow to the Workflow SDK.
It uses step.waitForEvent() with a timeout and step.realtime.publish().
Expected response shape:
## Migration Plan
## Source -> Target Mapping
## Migrated Code
## App Boundary / Resume Endpoints
## Verification Checklist
## Open Questions
Example references
Load a worked example only when the prompt needs concrete code:
references/shared-patterns.md -> ## Named-framework internal resume example (Hono)
references/shared-patterns.md -> ## Generated callback URL (default response)
references/shared-patterns.md -> ## Generated callback URL (manual response)
references/runtime-targets.md -> ## Self-hosted output block
references/aws-step-functions.md -> ## Combined recipe: callback URL on self-hosted Hono
Reject these counterexamples:
resume/url/default or resume/url/manual + user-authored callback route when webhook.url is the intended resume surface
createWebhook() paired with resumeHook()
- named-framework app-boundary output mixed with plain
Request / Response without a framework-agnostic override
Source: vercel/workflow — distributed by TomeVault.
1---2name: migrating-to-workflow-sdk3description: Migrates Temporal, Inngest, Trigger.dev, and AWS Step Functions workflows to the Workflow SDK. Use when porting Activities, Workers, Signals, step.run(), step.waitForEvent(), Trigger.dev tasks / wait.forToken / triggerAndWait, ASL JSON state machines, Task/Choice/Wait/Parallel states, task tokens, or child workflows. Use when this capability is needed.4---56# Migrating to the Workflow SDK78Use this skill when converting an existing orchestration system to the Workflow SDK.910## Intake11121. Identify the source system:13 - Temporal14 - Inngest15 - Trigger.dev16 - AWS Step Functions172. Identify the target runtime:18 - Managed hosting -> keep examples focused on `start()`, `getRun()`, hooks/webhooks, and route handlers.19 - Self-hosted -> also read `references/runtime-targets.md` and explicitly say the workflow/step code can stay the same, but deployment still needs a `World` implementation and startup bootstrap.203. Extract the source constructs:21 - entrypoint22 - waits / timers23 - external callbacks / approvals24 - retries / failure handling25 - child workflows / fan-out26 - progress streaming27 - external side effects2829## Default migration rules3031- Put orchestration in `"use workflow"` functions.32- Put side effects, SDK calls, DB calls, HTTP calls, and stream I/O in `"use step"` functions.33- Use `sleep()` only in workflow context.34- For Signals, `step.waitForEvent()`, and `.waitForTaskToken`, choose exactly one resume surface:35 - `resume/internal` -> `createHook()` + `resumeHook()` when the app resumes from server-side code with a deterministic business token.36 - `resume/url/default` -> `createWebhook()` when the external system needs a generated callback URL and the default `202 Accepted` response is fine.37 - `resume/url/manual` -> `createWebhook({ respondWith: 'manual' })` only when the prompt explicitly requires a custom response body, status, or headers.38 - If a callback-URL prompt does not specify response semantics, default to `resume/url/default` and make the assumption explicit in `## Open Questions`.39- Never pair `createWebhook()` with `resumeHook()`, and never pass `token:` to `createWebhook()`.40- Wrap `start()` and `getRun()` inside `"use step"` functions for child runs.41- Use `getStepMetadata().stepId` as the idempotency key for external writes.42- Use `getWritable()` in workflow context to obtain the stream, but interact with it (write, close) only inside `"use step"` functions.43- Prefer rollback stacks for multi-step compensation.44- Choose app-boundary syntax in this order:45 1. If the prompt explicitly asks for framework-agnostic app-boundary code, use plain `Request` / `Response` even when a framework like Hono is named.46 2. Otherwise, if the target framework is named, shape app-boundary examples to that framework.47 3. Otherwise, keep examples framework-agnostic with `Request` / `Response`. Do not default to Next.js-only route signatures unless Next.js is explicitly named.4849> Fast memory aid:50> - Callback URL + default ack -> `createWebhook()`51> - Callback URL + custom ack -> `createWebhook({ respondWith: 'manual' })`52> - Deterministic server-side resume -> `createHook()` + `resumeHook()`5354## Fast-path router5556Load `references/resume-routing.md` when the source pauses for Signals, `step.waitForEvent()`, or `.waitForTaskToken`.5758Fast defaults:5960- callback URL only -> `resume/url/default`61- callback URL + explicit custom response -> `resume/url/manual`62- deterministic server-side resume -> `resume/internal`63- self-hosted -> add `runtime/self-hosted`64- named framework -> add `boundary/named-framework`65- explicit framework-agnostic request -> add `boundary/framework-agnostic`6667Before drafting `## Migrated Code`, write the selected route keys in `## Migration Plan`.6869## Source references7071- Temporal -> `references/temporal.md`72- Inngest -> `references/inngest.md`73- Trigger.dev -> `references/trigger-dev.md`74- AWS Step Functions -> `references/aws-step-functions.md`7576## Shared references7778- `references/shared-patterns.md` — reusable code templates for hooks, child workflows, idempotency, streaming, and rollback.79- `references/runtime-targets.md` — Managed vs custom `World` guidance.80- `references/resume-routing.md` — route-key selection, obligations, and exact `## Migration Plan` shape.81- `references/retries.md` — canonical retry mechanics: `stepFn.maxRetries`, `RetryableError({ retryAfter })`, `FatalError`.8283## Required output shape8485Return the migration in this structure:8687```md88## Migration Plan89## Source -> Target Mapping90## Migrated Code91## App Boundary / Resume Endpoints92## Verification Checklist93## Open Questions94```9596## Verification checklist9798Fail the draft if any of these are true:99100- [ ] `## Migration Plan` omits `Route keys`101- [ ] `## Migration Plan` omits `Why these route keys`102- [ ] `## Migration Plan` lists route keys that do not match the prompt103- [ ] `## Migration Plan` lists required code obligations that do not match the selected route keys104- [ ] Source-framework primitives remain in the migrated code105- [ ] Side effects remain in workflow context106- [ ] `sleep()` appears inside a step107- [ ] Stream interaction (`getWriter()`, `write()`, `close()`) appears inside a workflow function108- [ ] Child workflows call `start()` / `getRun()` directly from workflow context109- [ ] External writes omit idempotency keys110- [ ] Hooks/webhooks are missing where the source used signals, waitForEvent, or task tokens111- [ ] A callback-URL flow uses `createHook()` + `resumeHook()` instead of `createWebhook()`112- [ ] A `resume/url/default` or `resume/url/manual` migration invents a user-authored callback route or `resumeWebhook()` wrapper when `webhook.url` should be the only resume surface113- [ ] `createWebhook()` is given a custom `token` or paired with `resumeHook()`114115Validation note:116117- Reading webhook request data in workflow context is allowed. Only `request.respondWith()` is step-only.118119Additional fail conditions:120121- `resume/internal` output omits `resumeHook()` in app-boundary code122- `resume/internal` output omits a deterministic business token123- `resume/internal` output emits `createWebhook()` or `webhook.url`124- `resume/url/default` output does not pass `webhook.url` to the external system125- `resume/url/default` output emits `resumeHook()`, `respondWith: 'manual'`, or `RequestWithResponse` without a custom-response requirement in the prompt126- `resume/url/default` output invents a user-authored callback route or `resumeWebhook()` wrapper when `webhook.url` is the intended resume surface127- `resume/url/manual` output does not pass `webhook.url` to the external system128- `resume/url/manual` output omits `RequestWithResponse` or `await request.respondWith(...)`129- `resume/url/manual` output calls `request.respondWith(...)` outside a `"use step"` function130- `resume/url/manual` output invents a user-authored callback route or `resumeWebhook()` wrapper when `webhook.url` is the intended resume surface131- `createWebhook()` is paired with `resumeHook()`132- self-hosted output omits `World extends Queue, Streamer, Storage`, `startWorkflowWorld()`, or the explicit note that the workflow and step code can stay the same while the app still needs a custom `World`133- named-framework output mixes framework syntax with plain `Request` / `Response` app-boundary code without a framework-agnostic override134135For concrete passing code, load:136137- `references/shared-patterns.md` -> `## Generated callback URL (default response)`138- `references/shared-patterns.md` -> `## Generated callback URL (manual response)`139- `references/runtime-targets.md` -> `## Self-hosted output block`140- `references/aws-step-functions.md` -> `## Combined recipe: callback URL on self-hosted Hono`141142## Sample prompt143144```145Migrate this Inngest workflow to the Workflow SDK.146It uses step.waitForEvent() with a timeout and step.realtime.publish().147```148149Expected response shape:150151```md152## Migration Plan153## Source -> Target Mapping154## Migrated Code155## App Boundary / Resume Endpoints156## Verification Checklist157## Open Questions158```159160## Example references161162Load a worked example only when the prompt needs concrete code:163164- `references/shared-patterns.md` -> `## Named-framework internal resume example (Hono)`165- `references/shared-patterns.md` -> `## Generated callback URL (default response)`166- `references/shared-patterns.md` -> `## Generated callback URL (manual response)`167- `references/runtime-targets.md` -> `## Self-hosted output block`168- `references/aws-step-functions.md` -> `## Combined recipe: callback URL on self-hosted Hono`169170Reject these counterexamples:171172- `resume/url/default` or `resume/url/manual` + user-authored callback route when `webhook.url` is the intended resume surface173- `createWebhook()` paired with `resumeHook()`174- named-framework app-boundary output mixed with plain `Request` / `Response` without a framework-agnostic override175176---177> Source: [vercel/workflow](https://github.com/vercel/workflow) — distributed by [TomeVault](https://tomevault.io).178<!-- tomevault:4.0:skill_md:2026-07-02 -->