workflow
Version:
Workflow SDK - Build durable, resilient, and observable workflows
112 lines (88 loc) • 8.28 kB
text/mdx
---
title: Workflow SDK vs AWS Step Functions
description: How the Workflow SDK compares to AWS Step Functions, including plain TypeScript control flow versus declarative Amazon States Language JSON and a concept-mapping migration guide.
type: conceptual
summary: AWS Step Functions is a managed state-machine orchestrator authored in declarative ASL JSON. The Workflow SDK expresses the same orchestration as plain TypeScript.
prerequisites:
- /docs/foundations/workflows-and-steps
related:
- /docs/foundations/errors-and-retries
- /docs/foundations/hooks
- /worlds/vercel
---
[AWS Step Functions](https://docs.aws.amazon.com/step-functions/latest/dg/welcome.html) is a mature, managed orchestrator that runs **state machines defined in Amazon States Language (ASL)**, a declarative JSON DSL. The authoring model is the main contrast with the Workflow SDK: you assemble a state machine (JSON or a visual editor) instead of writing plain control-flow code.
<Callout type="info">
**Choose the Workflow SDK** when you want orchestration as ordinary TypeScript (`await`, `if`, `Promise.all`, `try/catch`) that lives in your app, portable off a single cloud, with built-in streaming. **Choose Step Functions** when you're deep in the AWS ecosystem and want a managed engine with native optimized integrations to 200+ AWS services and the broadest compliance footprint.
</Callout>
## At a glance
| | Workflow SDK | AWS Step Functions |
| --- | --- | --- |
| **Authoring** | Plain TypeScript: `"use workflow"` orchestrators calling `"use step"` functions | **Declarative ASL JSON** (or Workflow Studio visual editor / CDK), not plain code |
| **Durability model** | Event log + deterministic replay | Managed state machine. **Standard** = exactly-once, up to 1 year; **Express** = at-least-once, up to 5 minutes |
| **Control flow** | `await`, `if`/`switch`, `Promise.all`, `try/catch` | `Task` / `Choice` / `Wait` / `Parallel` / `Map` states wired with `Next` |
| **Where it runs** | Your platform (Vercel managed or self-host) | AWS-managed; tasks run in Lambda or 200+ integrated AWS services |
| **Languages** | TypeScript / JS (Python beta) | ASL JSON for the machine; tasks can be any language (via Lambda) |
| **Human-in-the-loop** | `createHook()` / `createWebhook()` | `.waitForTaskToken` callback (Standard only) |
| **Streaming** | Native durable, resumable streaming to clients | No native client streaming |
| **Versioning** | Runs pinned to immutable deployment | Published versions are immutable; aliases route (≤2 versions) for canary/rollback; **in-flight executions keep their start-time definition** |
| **Portability** | Apache-2.0; runs anywhere Node runs | Proprietary and AWS-only; ASL is AWS-specific, resulting in high lock-in |
| **Pricing** | SDK free; pay your platform | **Standard:** $0.025 / 1K state transitions. **Express:** $1 / M requests + GB-second duration |
| **Limits** | 50 MB payload; 2 GB/run; no duration cap ([Vercel World limits](https://vercel.com/docs/workflows/pricing)) | 256 KB payload between states; Standard 25K history events / 1 year; Express 5 minutes |
| **AI** | `WorkflowAgent` in the AI SDK; durable streaming | Bedrock integration + a preview AgentCore "InvokeHarness" task; no native client streaming |
**What the limits mean in practice:** The 256 KB cap on payloads between states is the binding constraint for AI workloads. Virtually any model context or tool transcript has to round-trip through S3 with claim-check plumbing, and Standard executions cap history at 25K events. The [Vercel World limits](https://vercel.com/docs/workflows/pricing) are 50 MB per payload and 2 GB of state per run.
## Code vs. JSON
In Step Functions, even "call one Lambda" requires a state-machine shell, and ASL states (`Choice`, `Wait`, `Parallel`, `Map`) express the orchestration logic. In the Workflow SDK, ordinary TypeScript handles orchestration: transitions are `await`, branches are `if`, parallelism is `Promise.all`, and error handling is `try/catch`. This keeps orchestration in the same language, repo, and tests as the rest of your app, and removes the orchestrator/compute split (per-task Lambdas, IAM roles, callback queues).
The tradeoff is that Step Functions' optimized service integrations call AWS services (DynamoDB, SQS, EventBridge, Bedrock, `ecs:runTask.sync`, and others) declaratively. In the Workflow SDK, these become ordinary SDK calls inside `"use step"` functions, and you own the credentials, retries, and any polling.
## Migrating from Step Functions
This guide assumes **Standard** workflows. Express workflows have different semantics (at-least-once, 5-minute max, no execution history) and may be better kept on Step Functions or moved to a queue consumer.
| AWS Step Functions | Workflow SDK | Note |
| --- | --- | --- |
| State machine (ASL JSON) | `"use workflow"` function | The workflow function *is* the state machine. |
| Task state / Lambda | `"use step"` function | Side effects go in steps; no separate Lambda. |
| Choice state | `if` / `else` / `switch` | Native control flow. |
| Wait state | `sleep()` | `sleep('1m')` or `sleep(date)`. |
| Parallel state | `Promise.all()` | Standard concurrency. |
| Map state | `for` loop / bounded `Promise.all` (e.g. `p-limit`) / [`start()`](/docs/foundations/starting-workflows) per item for large fan-out | Match the original concurrency mode. |
| Retry / Catch (`MaxAttempts`, `BackoffRate`, `IntervalSeconds`) | `maxRetries`, `RetryableError`, `FatalError`; `try/catch` for compensation | Retry logic moves to step boundaries; backoff curves use `retryAfter`. See [Errors & Retrying](/docs/foundations/errors-and-retries#advanced-example). |
| `.waitForTaskToken` | `createHook()` / `createWebhook()` | Hooks for typed signals; webhooks for HTTP. |
| Child state machine (`StartExecution`) | Call [`start()`](/docs/foundations/starting-workflows) directly from the workflow | Child runs are tagged with `$parentRunId` / `$rootRunId` automatically. |
A single `Task` state and its Lambda collapse into two directive-tagged functions:
```typescript title="workflows/order.ts"
export async function processOrder(orderId: string) {
'use workflow'; // [!code highlight]
const order = await loadOrder(orderId);
if (order.total > 1000) await reviewManually(order); // Choice → if
await chargePayment(order);
}
async function loadOrder(id: string) {
'use step'; // [!code highlight]
return fetch(`https://example.com/api/orders/${id}`).then((r) => r.json());
}
```
A `.waitForTaskToken` callback becomes a hook without an SQS queue, task token, or callback Lambda:
```typescript title="workflows/refund.ts"
import { createHook } from 'workflow';
export async function refundWorkflow(refundId: string) {
'use workflow';
using approval = createHook<{ approved: boolean }>({
token: `refund:${refundId}:approval`, // [!code highlight]
});
return await approval; // resumed via resumeHook(), not SendTaskSuccess
}
```
<Callout type="info">
Install the migration skill to translate a state machine automatically:
```bash
npx skills add https://github.com/vercel/workflow --skill migrating-to-workflow-sdk
```
</Callout>
### Step Functions features without a direct Workflow SDK equivalent
Each row is a Step Functions capability the Workflow SDK does not replicate one-to-one, paired with how to cover it on the Workflow SDK side:
| Step Functions feature | How to cover it with the Workflow SDK |
| --- | --- |
| Express workflows | At-least-once and 5-minute duration fit the durable-replay model poorly; keep them on Step Functions or move to a queue consumer |
| Distributed Map (up to 10,000 concurrent children, S3 item sources) | Fan out with `start()` per item directly from the workflow, then bound concurrency with `p-limit` |
| Optimized AWS service integrations | Become ordinary SDK calls inside steps; `.sync` waits become explicit polling or hooks |
| Per-state IAM roles | Steps share the deployment's credentials; scope secrets at deploy time |
---
*Compiled from public documentation. Step Functions pricing/limits are region-specific (figures are us-east-1); verify against [the AWS pricing page](https://aws.amazon.com/step-functions/pricing/). Not based on head-to-head benchmarks.*