惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Vulnerabilities – Threatpost
V
Visual Studio Blog
博客园_首页
Last Week in AI
Last Week in AI
J
Java Code Geeks
V
V2EX
博客园 - Franky
Apple Machine Learning Research
Apple Machine Learning Research
云风的 BLOG
云风的 BLOG
博客园 - 司徒正美
N
Netflix TechBlog - Medium
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
爱范儿
爱范儿
B
Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
U
Unit 42
W
WeLiveSecurity
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
A
Arctic Wolf
S
Security Affairs
博客园 - 聂微东
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Microsoft Azure Blog
Microsoft Azure Blog
雷峰网
雷峰网
L
LangChain Blog
大猫的无限游戏
大猫的无限游戏
罗磊的独立博客
I
Intezer
有赞技术团队
有赞技术团队
Forbes - Security
Forbes - Security
Engineering at Meta
Engineering at Meta
D
Docker
C
Check Point Blog
N
News | PayPal Newsroom
H
Hacker News: Front Page
T
The Blog of Author Tim Ferriss
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
C
CXSECURITY Database RSS Feed - CXSecurity.com
SecWiki News
SecWiki News
The Last Watchdog
The Last Watchdog
Recorded Future
Recorded Future
量子位
NISL@THU
NISL@THU
T
Tailwind CSS Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
MongoDB | Blog
MongoDB | Blog
MyScale Blog
MyScale Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
I
InfoQ
Hacker News: Ask HN
Hacker News: Ask HN

Workflow SDK Documentation

Patterns for Defining Tools Human-in-the-Loop Building Durable AI Agents Queueing User Messages Resumable Streams Sleep, Suspense, and Scheduling Streaming Updates from Tools API Reference Workflow Globals Changelog Resilient run start Cookbook Building a World Deploying Astro Express Fastify Hono Getting Started NestJS Next.js Nitro Nuxt Python SvelteKit Vite corrupted-event-log fetch-in-workflow hook-conflict Errors node-js-module-in-workflow serialization-failed start-invalid-workflow-function step-not-registered timeout-in-workflow webhook-invalid-respond-with-value webhook-response-not-sent workflow-not-registered Errors & Retrying Hooks & Webhooks Idempotency Foundations Serialization Starting Workflows Streaming Versioning Workflows and Steps How the Directives Work Encryption Event Sourcing Framework Integrations Understanding Directives Migration Guides Migrating from AWS Step Functions Migrating from Inngest Observability Testing Server-Based Testing createHook createWebhook defineHook FatalError fetch getStepMetadata getWorkflowMetadata getWritable workflow RetryableError sleep @workflow/vitest DurableAgent @workflow/ai WorkflowChatTransport getHookByToken getRun getWorld workflow/api resumeHook resumeWebhook Chat Session Modeling runtime-decryption-failed Upgrading Workflows abort-signal-timeout-in-workflow Cancellation How Cancellation Works Internal Serializable AbortController and AbortSignal Eager Processing of Steps & Incremental Event Replay TanStack Start Agent Cancellation Sequential & Parallel Execution Workflow Composition Local World | Workflow SDK Postgres World | Workflow SDK Vercel World | Workflow SDK Migrating from trigger.dev Secure Credential Handling Local World | Workflow SDK Postgres World | Workflow SDK Vercel World | Workflow SDK
Migrating from Temporal
2026-05-31 · via Workflow SDK Documentation

Move a Temporal TypeScript workflow to the Workflow SDK by replacing Activities, Workers, Signals, and Child Workflows with Workflows, Steps, Hooks, and start()/getRun().

Install the Workflow SDK migration skill:

npx skills add https://github.com/vercel/workflow --skill migrating-to-workflow-sdk
  • Streaming is built in. Durable progress writes go to named streams via getWritable({ namespace }), and clients read them directly. No separate WebSocket, SSE, or progress-polling layer to operate.
  • Infrastructure and orchestration live in a single deployment. There is no separate Worker fleet or Temporal Server to run. The runtime, step logic, and orchestration share the app's observability and log aggregation.
  • TypeScript-first developer experience. Workflows and steps live in the same file with plain await control flow, try/catch, and Promise.all.
  • Agent-first tooling. A first-class CLI (npx workflow), @workflow/ai integration for durable AI agents, and a bundled Claude skill for AI-assisted authoring.
  • Per-step retry controls. RetryableError, FatalError, and maxRetries live at the step boundary instead of an Activity-level retry policy configured elsewhere.

Temporal requires operating a control plane (Temporal Server or Cloud), a Worker fleet, Activity modules wired through proxyActivities, and Task Queues. The workflow code is durable; the surrounding infrastructure is substantial.

The Workflow SDK runs on managed infrastructure. Write "use workflow" functions that orchestrate "use step" functions in the same file, in plain TypeScript. There are no Workers, Task Queues, or separate Activity modules. Durable replay, automatic retries, and event history are handled by the runtime.

Workflow functions must still be deterministic — no Date.now(), Math.random(), direct network I/O, or wall-clock branches inside "use workflow". Move any such logic into a step, as you do today with Activities.

Migration removes infrastructure and collapses indirection. Business logic stays as regular async TypeScript.

TemporalWorkflow SDKMigration note
Workflow Definition / Workflow Execution"use workflow" function / run started with start()Keep orchestration code in the workflow function.
Activity"use step" functionPut side effects and Node.js access in steps.
Worker + Task QueueManaged executionNo worker fleet or polling loop to operate.
SignalcreateHook() or createWebhook()Use hooks for typed resume signals; webhooks for HTTP callbacks.
QuerygetWritable({ namespace: 'status' }) streamDurably stream status updates from the workflow. Clients read from the stream instead of polling a database.
UpdatecreateHook() + resumeHook() (one-way)Temporal Updates return a value to the caller; hooks do not. If the Update returns data, either write the result to a named stream via getWritable() and have the caller read from it, or keep an HTTP read route that fetches the workflow's current state.
Child Workflow"use step" wrappers around start() / getRun()Spawn from a step and return the Run object so observability can deep-link into child runs.
Activity retry policyStep retries, RetryableError, FatalError, maxRetriesRetries live at the step boundary.
Event HistoryWorkflow event log / run timelineSame durable replay; built-in observability UI replaces Temporal Web. Search attributes and visibility APIs have no direct equivalent — filter by run status and timestamps instead.

Minimal translation

Start with the directive change. The Temporal definition proxies activities through a module; the Workflow SDK version puts the directive inline.

import * as wf from '@temporalio/workflow';
import type * as activities from './activities';

const { chargePayment } = wf.proxyActivities<typeof activities>({
  startToCloseTimeout: '5 minutes',
});

export async function processOrder(orderId: string) {
  await chargePayment(orderId);
  return { orderId, status: 'completed' };
}
export async function processOrder(orderId: string) {
  'use workflow'; 
  await chargePayment(orderId);
  return { orderId, status: 'completed' };
}

What changed: proxyActivities and the activity module disappear. The orchestrator is plain async TypeScript marked with "use workflow".

Adding a step

Side effects move into a colocated "use step" function:

async function chargePayment(orderId: string) {
  'use step'; 
  await fetch(`https://example.com/api/orders/${orderId}/charge`, {
    method: 'POST',
  });
}

Additional steps (loadOrder, reserveInventory) follow the same shape and can be called from the workflow in sequence.

Starting the run

Replace Worker + Task Queue wiring with a single start() call from an API route:

import { start } from 'workflow/api';
import { processOrder } from '@/workflows/order';

export async function POST(request: Request) {
  const { orderId } = (await request.json()) as { orderId: string };
  const run = await start(processOrder, [orderId]); 
  return Response.json({ runId: run.runId });
}

Minimal translation

Temporal needs a signal definition, a handler, and a condition() guard. The Workflow SDK collapses all three into a single createHook() + await.

export const approveRefund = wf.defineSignal<[boolean]>('approveRefund');

export async function refundWorkflow(refundId: string) {
  let approved: boolean | undefined;
  wf.setHandler(approveRefund, (v) => { approved = v; });
  await wf.condition(() => approved !== undefined);
  return { refundId, approved };
}
import { createHook } from 'workflow';

export async function refundWorkflow(refundId: string) {
  'use workflow';
  using approval = createHook<{ approved: boolean }>({
    token: `refund:${refundId}:approval`, 
  });
  const { approved } = await approval; 
  return { refundId, approved };
}

The workflow suspends durably at await approval until resumed. No polling, no handler registration.

Requires TypeScript 5.2+ for the using keyword. On older TypeScript, assign the hook to a const and call resumeHook() from the consuming code path.

Resuming from an API route

Any HTTP caller can resume by token:

import { resumeHook } from 'workflow/api';

export async function POST(request: Request, { params }: { params: Promise<{ refundId: string }> }) {
  const { refundId } = await params;
  const body = (await request.json()) as { approved: boolean };
  await resumeHook(`refund:${refundId}:approval`, body); 
  return Response.json({ ok: true });
}

Temporal Queries expose in-memory workflow state on demand. In the Workflow SDK, the equivalent is a durable stream: call getWritable({ namespace: 'status' }) from inside the workflow to write status updates, and have clients read from the end of that stream to get the current state. Hooks are the write channel for resuming a paused workflow with new data.

Minimal translation

start() and getRun() are runtime APIs, so wrap them in "use step" functions. Return the Run object (not a plain runId string) so workflow observability can deep-link into child runs.

import { start } from 'workflow/api';

async function spawnChild(item: string) {
  'use step'; 
  return start(childWorkflow, [item]); 
}

export async function parentWorkflow(item: string) {
  'use workflow';
  const child = await spawnChild(item); 
  return { childRunId: child.runId };
}

Awaiting the child's return value

A second step fetches the run and awaits returnValue:

import { getRun } from 'workflow/api';

async function collectResult(runId: string) {
  'use step';
  const run = getRun(runId);
  return (await run.returnValue) as { item: string; result: string }; 
}

Call both steps from the parent in sequence: const result = await collectResult(child.runId). To fan out, call spawnChild inside a loop, then Promise.all the collectResult calls.

Activity retry policy moves to the step boundary. Use maxRetries, RetryableError, and FatalError on each step instead of a single workflow-wide retry block.

Temporal's per-activity timeouts (startToCloseTimeout, scheduleToCloseTimeout, heartbeatTimeout) have no direct Workflow SDK equivalent. Enforce per-step deadlines inside the step using AbortSignal.timeout(ms) (e.g. on fetch), or wrap the call from the workflow in Promise.race(step(), sleep('5m')) to bail out after a bounded duration.

Temporal's retry policy knobs (initialInterval, backoffCoefficient, maximumInterval, nonRetryableErrorTypes) don't port 1:1 — only maxRetries is configurable at the step boundary. Classify retryability with RetryableError (retryable) and FatalError (terminal) instead of listing error types, and control the delay between attempts with new RetryableError(msg, { retryAfter: '5s' }).

Set maxRetries as a property assignment on the step function:

async function chargePayment(orderId: string) {
  "use step";
  // ...
}
chargePayment.maxRetries = 5;

See /docs/foundations/errors-and-retries for full retry docs.

  • Temporal Server or Cloud. Durable state lives in the managed event log.
  • Worker fleet. The runtime schedules execution; workflows run where the app runs.
  • Task Queues. No queue routing to configure or monitor.
  • Activity modules and proxyActivities. Steps live next to the workflow that calls them.
  • Custom progress transport. getWritable() streams updates from steps.

Suspended workflows (on sleep() or a hook) consume no compute until resumed.

Pick one Temporal workflow and migrate it end-to-end before touching the rest. The steps below describe the smallest viable path.

Step 1: Install the Workflow SDK

Add the runtime. The Next.js integration ships as a subpath (workflow/next) of the same package.

Step 2: Collapse Activities into step functions

Delete the activities/ module and the proxyActivities call. Each former Activity becomes a plain async function with "use step" on the first line, living next to the orchestrator.

async function loadOrder(id: string) {
  "use step"; 
  return fetch(`/api/orders/${id}`).then((r) => r.json());
}

Step 3: Mark the orchestrator with "use workflow"

Keep the existing control flow: await, try/catch, Promise.all. The directive turns the function into a durable replay target.

export async function processOrder(orderId: string) {
  "use workflow"; 
  const order = await loadOrder(orderId);
  // ...
}

Step 4: Replace Signals with hooks

Swap defineSignal + setHandler for createHook(). Callers resumeHook(token, payload) instead of handle.signal(signalDef, payload) on a WorkflowHandle obtained from client.workflow.getHandle(workflowId).

Step 5: Start runs from an API route or server action

Delete the Worker bootstrap. Launch runs from an API route or server action with start():

import { start } from "workflow/api";
import { processOrder } from "@/workflows/order";

export async function POST(req: Request) {
  const { orderId } = await req.json();
  const run = await start(processOrder, [orderId]);
  return Response.json({ runId: run.runId });
}

Step 6: Retire the Temporal infrastructure

Remove the Worker process, @temporalio/* dependencies, and the Temporal Server or Cloud connection. Verify the run in the built-in observability UI (npx workflow web) before shipping.

  • Search attributes / visibility queries. Temporal's search attribute system has no direct analog. Filter runs by status and timestamps via getRun() / observability UI.
  • Event history archival. Temporal archives histories to S3/GCS for long-term retention. Workflow SDK event logs are durable, but retention depends on the integration you are using. For example, see Vercel Workflow Storage Retention for Vercel.
  • Per-activity timeouts (startToCloseTimeout, scheduleToCloseTimeout, heartbeatTimeout). Implement deadlines inside the step with AbortSignal.timeout(ms), or wrap the call in Promise.race(step(), sleep(...)) from the workflow.
  • Rich retry policy (initialInterval, backoffCoefficient, maximumInterval, nonRetryableErrorTypes). Only maxRetries is configurable. Classify retryability with RetryableError/FatalError; control delay between attempts via new RetryableError(msg, { retryAfter: '5s' }).
  • Workers + task queues. Managed deployments replace workers; self-hosted deployments still need a World implementation (see /docs/deploying/building-a-world).
  • Move orchestration into a "use workflow" function.
  • Convert each Activity into a "use step" function.
  • Remove Worker and Task Queue code. Start workflows from the app with start().
  • Replace Signals with createHook() or createWebhook() for HTTP callers.
  • Wrap start() and getRun() in "use step" functions for child workflows. Return the Run object from start() so observability can deep-link into child runs.
  • Set retry policy per step with maxRetries, RetryableError, and FatalError.
  • Use getStepMetadata().stepId as the idempotency key for external side effects.
  • Stream status and progress from steps with getWritable({ namespace: 'status' }), and have clients read from the stream instead of polling.
  • Deploy the app and verify runs end-to-end in the built-in observability UI.

Verified against workflow@5.0.0-beta.1 and @temporalio/workflow@1.16 on 2026-04-16.