workflow 5.0.0-beta.35 → 5.0.0-beta.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/nest-builder.d.ts +2 -0
  2. package/dist/nest-builder.d.ts.map +1 -0
  3. package/dist/nest-builder.js +2 -0
  4. package/dist/nest-vercel-builder.d.ts +2 -0
  5. package/dist/nest-vercel-builder.d.ts.map +1 -0
  6. package/dist/nest-vercel-builder.js +2 -0
  7. package/docs/api-reference/workflow/create-hook.mdx +43 -2
  8. package/docs/api-reference/workflow/define-hook.mdx +26 -24
  9. package/docs/api-reference/workflow/fatal-error.mdx +29 -7
  10. package/docs/api-reference/workflow/fetch.mdx +3 -4
  11. package/docs/api-reference/workflow/sleep.mdx +1 -1
  12. package/docs/api-reference/workflow-api/get-hook-by-token.mdx +2 -0
  13. package/docs/api-reference/workflow-api/resume-hook.mdx +2 -0
  14. package/docs/api-reference/workflow-api/resume-webhook.mdx +6 -4
  15. package/docs/api-reference/workflow-api/start.mdx +1 -1
  16. package/docs/api-reference/workflow-nest/nest-local-builder.mdx +1 -1
  17. package/docs/comparisons/index.mdx +66 -0
  18. package/docs/comparisons/meta.json +11 -0
  19. package/docs/comparisons/workflow-sdk-vs-aws-agentcore.mdx +55 -0
  20. package/docs/comparisons/workflow-sdk-vs-aws-step-functions.mdx +111 -0
  21. package/docs/comparisons/workflow-sdk-vs-cloudflare-workflows.mdx +71 -0
  22. package/docs/comparisons/workflow-sdk-vs-inngest.mdx +102 -0
  23. package/docs/comparisons/workflow-sdk-vs-temporal.mdx +123 -0
  24. package/docs/comparisons/workflow-sdk-vs-trigger-dev.mdx +103 -0
  25. package/docs/configuration/runtime-tuning.mdx +22 -3
  26. package/docs/configuration/worlds.mdx +4 -4
  27. package/docs/cookbook/common-patterns/idempotency.mdx +1 -1
  28. package/docs/{deploying/index.mdx → deploying.mdx} +6 -8
  29. package/docs/foundations/hooks.mdx +1 -1
  30. package/docs/foundations/idempotency.mdx +16 -9
  31. package/docs/getting-started/astro.mdx +1 -1
  32. package/docs/getting-started/express.mdx +1 -1
  33. package/docs/getting-started/fastify.mdx +1 -1
  34. package/docs/getting-started/hono.mdx +1 -1
  35. package/docs/getting-started/index.mdx +3 -3
  36. package/docs/getting-started/meta.json +2 -1
  37. package/docs/getting-started/nestjs.mdx +63 -2
  38. package/docs/getting-started/next.mdx +2 -2
  39. package/docs/getting-started/nitro.mdx +1 -1
  40. package/docs/getting-started/nuxt.mdx +1 -1
  41. package/docs/getting-started/sveltekit.mdx +1 -1
  42. package/docs/getting-started/vite.mdx +1 -1
  43. package/docs/how-it-works/encryption.mdx +3 -3
  44. package/docs/how-it-works/event-sourcing.mdx +6 -6
  45. package/docs/how-it-works/framework-integrations.mdx +2 -2
  46. package/docs/meta.json +1 -1
  47. package/package.json +13 -11
  48. package/docs/deploying/building-a-world.mdx +0 -251
  49. package/docs/deploying/meta.json +0 -4
  50. package/docs/deploying/world/local-world.mdx +0 -105
  51. package/docs/deploying/world/meta.json +0 -4
  52. package/docs/deploying/world/postgres-world.mdx +0 -288
  53. package/docs/deploying/world/vercel-world.mdx +0 -270
  54. package/docs/migration-guides/index.mdx +0 -34
  55. package/docs/migration-guides/meta.json +0 -9
  56. package/docs/migration-guides/migrating-from-aws-step-functions.mdx +0 -362
  57. package/docs/migration-guides/migrating-from-inngest.mdx +0 -308
  58. package/docs/migration-guides/migrating-from-temporal.mdx +0 -317
  59. package/docs/migration-guides/migrating-from-trigger-dev.mdx +0 -332
@@ -0,0 +1,71 @@
1
+ ---
2
+ title: Workflow SDK vs Cloudflare Workflows
3
+ description: How the Workflow SDK compares to Cloudflare Workflows — both are durable replay engines, but they handle versioning, encryption, portability, and global distribution very differently.
4
+ type: conceptual
5
+ summary: Cloudflare Workflows is a durable engine on Workers and Durable Objects. It and the Workflow SDK both replay, but differ on versioning safety, encryption, and lock-in.
6
+ prerequisites:
7
+ - /docs/foundations/workflows-and-steps
8
+ related:
9
+ - /docs/how-it-works/event-sourcing
10
+ - /docs/foundations/streaming
11
+ - /worlds/building-a-world
12
+ ---
13
+
14
+ [Cloudflare Workflows](https://developers.cloudflare.com/workflows/) is a durable-execution engine built on Cloudflare Workers and SQLite-backed Durable Objects. It's the closest architectural peer to the Workflow SDK — both persist progress and replay to survive failures — which makes the differences in **versioning safety, encryption, and portability** the deciding factors.
15
+
16
+ <Callout type="info">
17
+ **Choose the Workflow SDK** when you want an open-source, portable engine that runs in your existing app (and off a single vendor), deployment-pinned versioning, and application-level E2E encryption. **Choose Cloudflare Workflows** when you're already all-in on Cloudflare.
18
+ </Callout>
19
+
20
+ ## At a glance
21
+
22
+ | | Workflow SDK | Cloudflare Workflows |
23
+ | --- | --- | --- |
24
+ | **Category** | Open-source durable-functions SDK; portable backends | Durable execution engine, hosted on Cloudflare |
25
+ | **Durability model** | Event log + deterministic replay | Step-result **memoization** in SQLite-backed Durable Objects + deterministic re-calculation ("game-loop") |
26
+ | **Authoring** | `"use workflow"` / `"use step"` in plain async TS, in your app | Class extends `WorkflowEntrypoint`; explicit `step.do(name, cb)` wrapping; Cloudflare Workers only |
27
+ | **Where it runs** | Your platform (Vercel managed, or self-host) | Cloudflare only — both orchestration and execution run on-network (engine ↔ step over internal RPC) |
28
+ | **Versioning** | Runs pinned to their immutable deployment — safe by default | **No version pinning** — running instances resume on the *latest* deployed code; changing step names/order can desync the cached replay. No patching API |
29
+ | **Encryption** | Per-run AES-256-GCM **end-to-end** encryption | **At-rest only** (AES-256, Cloudflare-managed keys) + TLS; no E2E, no customer-managed keys |
30
+ | **AI & streaming** | `WorkflowAgent` in the AI SDK; native resumable streaming | Durable agents via Workflows + the Agents SDK; resumable streaming buffered in SQLite (mid-call eviction needs opt-in `chatRecovery`) |
31
+ | **Performance** | No-penalty resume; scale-to-zero; up to 100K concurrency (Vercel) | **~0 ms isolate cold starts**; 50K concurrent instances; global Anycast (330+ cities) — strongest cold-start and edge story |
32
+ | **Portability** | Apache-2.0; World abstraction; self-hostable | Engine proprietary; tied to Durable Objects; **highest lock-in** of these tools |
33
+ | **Pricing** | SDK free; pay your platform | Workers Standard: requests + CPU-time + storage + per-step ($0.80 / 100K steps); idle/sleep not billed |
34
+ | **Limits** | 50 MB payloads; 2 GB/run; 10K steps ([Vercel World limits](https://vercel.com/docs/workflows/pricing)) | **1 MiB** step result & event payload; 1 GB state/instance; 10K steps (up to 25K) |
35
+
36
+ **What the limits mean in practice:** Cloudflare's 1 MiB cap on step results and event payloads is the tightest in this section — a single large model response or document can exceed it, pushing anything sizable into R2/KV indirection — and instance state is capped at 1 GB. The [Vercel World limits](https://vercel.com/docs/workflows/pricing) are 50 MB per payload and 2 GB of state per run.
37
+
38
+ ## Versioning: pinned vs. live code
39
+
40
+ Both engines replay, so changing code mid-run is the key hazard — and they take opposite approaches:
41
+
42
+ - **Cloudflare** does not pin a running instance to a code version. When an instance resumes (after a sleep, a wait, or a deploy), it runs against whatever code is currently deployed. Because step names act as the replay cache key, reordering, renaming, or inserting steps before already-completed ones can desync the replay of an in-flight instance. There is no patching API — just the documented "[Rules of Workflows](https://developers.cloudflare.com/workflows/build/rules-of-workflows/)" you must follow by hand.
43
+ - **Workflow SDK** pins each run to the immutable deployment that started it, so a deploy never disturbs in-flight runs. Evolving code is safe by default and upgrades are explicit.
44
+
45
+ ## Encryption and portability
46
+
47
+ Cloudflare encrypts Durable Object data at rest with Cloudflare-managed keys, but there is no application-level / end-to-end encryption and no customer-managed-key option — payloads are visible to the platform. The Workflow SDK encrypts each run's inputs, outputs, step I/O, and streams with a per-run AES-256-GCM key.
48
+
49
+ On portability, Cloudflare Workflows is the most locked-in of the tools in this section: the API and Durable-Object-bound state are Cloudflare-specific, so moving means a rewrite. The Workflow SDK is Apache-2.0 and its [World abstraction](/worlds/building-a-world) lets you run the same code on Vercel, on Postgres, or on a backend you build.
50
+
51
+ ## Where Cloudflare leads
52
+
53
+ Credit where due: Cloudflare's V8-isolate model gives **near-zero cold starts**, and code runs across its global Anycast network with no region selection. For latency-sensitive, globally-distributed workloads on Cloudflare's platform, that's a genuine strength. The Workflow SDK's performance depends on the World it runs on; on Vercel it benefits from Fluid Compute and a 100K concurrency ceiling, with multi-region rolling out.
54
+
55
+ ## Moving from Cloudflare Workflows
56
+
57
+ There's no automated migration skill for Cloudflare specifically, but the mapping is direct:
58
+
59
+ | Cloudflare Workflows | Workflow SDK |
60
+ | --- | --- |
61
+ | `class extends WorkflowEntrypoint` + `run(event, step)` | `"use workflow"` function started with `start()` |
62
+ | `step.do(name, cb)` | `"use step"` function called with `await` |
63
+ | `step.sleep` / `step.sleepUntil` | `sleep('1h')` / `sleep(date)` |
64
+ | `step.waitForEvent` | `createHook()` / `createWebhook()` |
65
+ | Per-step `retries` config | `maxRetries`, `RetryableError`, `FatalError` |
66
+ | `env.MY_WORKFLOW.create(...)` binding | `start(workflow, [args])` from your app |
67
+
68
+ Side effects that lived in `step.do` callbacks move into named `"use step"` functions; the orchestration becomes plain `await` / `if` / `Promise.all` instead of the `WorkflowEntrypoint` class.
69
+
70
+ ---
71
+ *Compiled from public documentation. Verify current Cloudflare Workflows limits and pricing against [developers.cloudflare.com/workflows](https://developers.cloudflare.com/workflows/). Not based on head-to-head benchmarks.*
@@ -0,0 +1,102 @@
1
+ ---
2
+ title: Workflow SDK vs Inngest
3
+ description: How the Workflow SDK compares to Inngest — event-driven durable functions that run on your own infrastructure over HTTP, with a concept-mapping migration guide.
4
+ type: conceptual
5
+ summary: Inngest is an event-driven durable-functions platform that invokes your code over HTTP and memoizes step results. The Workflow SDK co-locates orchestration and execution and replays from an event log.
6
+ prerequisites:
7
+ - /docs/foundations/workflows-and-steps
8
+ related:
9
+ - /docs/foundations/hooks
10
+ - /docs/foundations/streaming
11
+ - /docs/ai
12
+ ---
13
+
14
+ [Inngest](https://www.inngest.com) is a durable-functions platform with an **event-driven** core: functions trigger on events or cron, and Inngest invokes your code — running on your own infrastructure — one step at a time over HTTP, memoizing each step's result. It overlaps heavily with the Workflow SDK, with a different execution topology and a strong AI/agent story on both sides.
15
+
16
+ <Callout type="info">
17
+ **Choose the Workflow SDK** when you want orchestration and execution co-located (no per-step HTTP round-trips), deployment-pinned versioning, and a fully-supported open-source self-host path. **Choose Inngest** when an event-bus model fits your architecture and you want its mature flow-control suite (concurrency, throttling, debounce, batching, priority) out of the box.
18
+ </Callout>
19
+
20
+ ## At a glance
21
+
22
+ | | Workflow SDK | Inngest |
23
+ | --- | --- | --- |
24
+ | **Category** | Open-source durable-functions SDK | Durable functions on an event-driven platform |
25
+ | **Durability model** | Event log + deterministic replay | **Step-result memoization** (each `step.run` runs once; completed steps are skipped) — not whole-function replay |
26
+ | **Trigger model** | Direct `start(workflow, [args])` (import the function) | Event bus (`inngest.send`) + cron; loosely coupled publishers/consumers |
27
+ | **Where execution runs** | Orchestration + execution co-located on your platform | **Your code runs on your infra**; Inngest invokes it per step over HTTP (Serve) or a persistent worker connection (Connect, in public beta) |
28
+ | **Languages** | TypeScript / JS (Python beta) | TypeScript; Python & Go (production, pre-1.0) |
29
+ | **Versioning** | Runs pinned to immutable deployment | Tracked by step-ID hashes — hot-edit functions, but editing a step's logic under the **same ID** reuses the old memoized result for in-flight runs; rename the ID or route a new function by timestamp for rewrites |
30
+ | **AI & streaming** | `WorkflowAgent` in the AI SDK; native resumable streaming | Native AI SDK via `step.ai.wrap`; **AgentKit** multi-agent framework and durable **Realtime** (`step.realtime.publish`) + `useAgent` hook — both Developer Preview |
31
+ | **Security** | Zero-config per-run E2E encryption by default; platform security is per-World (the Vercel World inherits Vercel's security posture) | Runs on your infra; signed requests; first-party E2E encryption middleware (TS + Python); SOC 2 Type II, HIPAA add-on |
32
+ | **Portability** | Apache-2.0; World abstraction; **self-host supported** | Engine open source (SSPL); self-host via single Go binary + Postgres, but **self-hosting is community/best-effort** — no support SLA, and the `inngest start` binary is Beta (SaaS is the default) |
33
+ | **Pricing** | SDK free; pay your platform | Per-execution: billed for the run **plus each step plus retries**; Pro from $99/mo, then ~$50 per 1M executions |
34
+ | **Limits** | 50 MB payload; 2 GB/run; 10K steps ([Vercel World limits](https://vercel.com/docs/workflows/pricing)) | 1,000 steps/function; 4 MiB step payload; 32 MiB run state; runs up to 366 days |
35
+
36
+ **What the limits mean in practice:** Inngest's caps are restrictive for real long-running AI workloads. An agent loop spends steps on every model and tool call, so 1,000 steps per function goes quickly; a single large LLM response can approach the 4 MiB step-payload cap; and an accumulated conversation or context easily outgrows 32 MiB of run state. The [Vercel World limits](https://vercel.com/docs/workflows/pricing) are 50 MB per payload and 2 GB of state per run.
37
+
38
+ ## Topology: co-located vs. invoked-over-HTTP
39
+
40
+ Inngest's engine lives outside your code and calls your functions step-by-step over HTTP (or a Connect worker). That keeps your code on your own infra (a portability and data-locality plus), but adds a network round-trip per step — relevant for workflows with many small sequential steps. The Workflow SDK co-locates orchestration and execution on one platform, so steps don't pay a per-step HTTP hop.
41
+
42
+ Triggering differs too: Inngest is event-driven (publishers `send` events; functions subscribe), which is great for loosely-coupled fan-out. The Workflow SDK's `start()` imports the workflow function directly — tighter coupling, stronger type safety. For event-bus-style fan-out, wrap `start()` in a shared publisher.
43
+
44
+ ## Versioning
45
+
46
+ Inngest doesn't use version numbers; it keys state by **step-ID hash**, so you can edit functions while runs are in flight. The catch: if you change the logic *inside* a step but keep the same ID, in-flight runs that already completed that step silently reuse the **old** memoized result — only new runs see the change. To force re-execution you rename the step ID, and for incompatible rewrites the recommended pattern is a new function with timestamp-based event routing.
47
+
48
+ The Workflow SDK pins each run to its immutable deployment, so in-flight runs always finish on the exact code they started with, and upgrades are explicit — no per-step-ID reasoning required.
49
+
50
+ ## AI agents
51
+
52
+ Both are strong here. Inngest offers `step.ai.wrap()` (wrap Vercel AI SDK calls as durable steps), **AgentKit** (a multi-agent framework with MCP tools), and durable **Realtime** streaming with a `useAgent` React hook — though AgentKit and Realtime are both Developer Preview. The Workflow SDK offers `WorkflowAgent` directly inside the AI SDK and native [resumable streaming](/docs/ai/resumable-streams). If you're already event-driven and want a batteries-included agent framework, AgentKit is compelling; if you want the agent loop to *be* a durable workflow in your app with streaming built into the runtime, the Workflow SDK fits.
53
+
54
+ ## Migrating from Inngest
55
+
56
+ | Inngest | Workflow SDK | Note |
57
+ | --- | --- | --- |
58
+ | `inngest.createFunction()` | `"use workflow"` function started with `start()` | No factory or event binding. |
59
+ | `step.run()` | `"use step"` function | Named async function with Node.js access. |
60
+ | `step.sleep()` / `step.sleepUntil()` | `sleep('5m')` / `sleep(date)` | Import from `workflow`. |
61
+ | `step.waitForEvent()` | `createHook()` / `createWebhook()` | Token encodes the routing; no event schema. |
62
+ | `step.invoke()` | `"use step"` wrappers around `start()` / `getRun()` | Spawn a child run. |
63
+ | `inngest.send()` / event triggers | `start()` from your app boundary | Start workflows directly. |
64
+ | Retry config (`retries`) | `maxRetries`, `RetryableError`, `FatalError` | Retries live at the step boundary. |
65
+ | `step.realtime.publish()` / Realtime | `getWritable()` / named streams | Clients read from the stream. |
66
+
67
+ The `createFunction` factory collapses into a plain exported function:
68
+
69
+ ```typescript title="workflows/order.ts"
70
+ export async function processOrder(orderId: string) {
71
+ 'use workflow'; // [!code highlight]
72
+ const order = await loadOrder(orderId);
73
+ return { orderId: order.id, status: 'completed' };
74
+ }
75
+
76
+ async function loadOrder(orderId: string) {
77
+ 'use step'; // [!code highlight]
78
+ const res = await fetch(`https://example.com/api/orders/${orderId}`);
79
+ return res.json() as Promise<{ id: string }>;
80
+ }
81
+ ```
82
+
83
+ <Callout type="info">
84
+ Install the migration skill to translate an Inngest app automatically:
85
+
86
+ ```bash
87
+ npx skills add https://github.com/vercel/workflow --skill migrating-to-workflow-sdk
88
+ ```
89
+ </Callout>
90
+
91
+ ### Inngest features without a direct Workflow SDK equivalent
92
+
93
+ Each row is an Inngest capability the Workflow SDK does not replicate one-to-one, paired with how to cover it on the Workflow SDK side:
94
+
95
+ | Inngest feature | How to cover it with the Workflow SDK |
96
+ | --- | --- |
97
+ | Flow control (concurrency, throttling, debounce, rate limiting, batching, priority) | No direct function-level analog; enforce limits inside steps or debounce at the publisher before `start()` |
98
+ | Cron / scheduled functions | Trigger from Vercel Cron or a system cron calling `start()` |
99
+ | Event-bus fan-out by name match | Replace with explicit `start()` calls per target workflow |
100
+
101
+ ---
102
+ *Compiled from public documentation. Inngest tier names and pricing vary across sources; verify against [inngest.com/pricing](https://www.inngest.com/pricing). Not based on head-to-head benchmarks.*
@@ -0,0 +1,123 @@
1
+ ---
2
+ title: Workflow SDK vs Temporal
3
+ description: How the Workflow SDK compares to Temporal — execution model, where workers run, versioning, AI agents, pricing, and a concept-mapping migration guide.
4
+ type: conceptual
5
+ summary: Temporal is a mature, language-agnostic durable-execution platform where you run the workers. The Workflow SDK runs in your existing app and pins runs to immutable deployments.
6
+ prerequisites:
7
+ - /docs/foundations/workflows-and-steps
8
+ related:
9
+ - /docs/foundations/hooks
10
+ - /docs/foundations/streaming
11
+ - /docs/ai
12
+ - /worlds/vercel
13
+ ---
14
+
15
+ [Temporal](https://temporal.io) is the most mature durable-execution platform — battle-tested at large scale, with seven language SDKs. It and the Workflow SDK share the same core idea (durable orchestration via event-sourced replay), so the real differences are operational: **where your code runs, how you version it, and how it streams to clients.**
16
+
17
+ <Callout type="info">
18
+ **Choose the Workflow SDK** when you want durable execution inside your existing TypeScript app with nothing extra to operate, deployment-pinned versioning, and native streaming for AI apps. **Choose Temporal** when you need polyglot SDKs (Go/Java/etc.), want a self-hostable control plane you fully own, or are standardizing a large org on one orchestration backend across many languages.
19
+ </Callout>
20
+
21
+ ## At a glance
22
+
23
+ | | Workflow SDK | Temporal |
24
+ | --- | --- | --- |
25
+ | **Category** | Open-source durable-functions SDK; managed on Vercel or self-hosted | Durable-execution platform; Temporal Cloud or self-hosted cluster |
26
+ | **Durability model** | Event log + deterministic replay (`"use workflow"` orchestrators, `"use step"` functions) | Event-sourced replay (Workflows + Activities). Same model — `"use workflow"` ≈ Workflow, `"use step"` ≈ Activity |
27
+ | **Languages** | TypeScript / JS (Python beta) | Go, Java, TypeScript, Python, .NET, PHP, Ruby (7 SDKs) |
28
+ | **Where execution runs** | Orchestration + execution + observability co-located on your platform; private networking and E2E encryption out of the box on Vercel | **You run and scale your own Workers.** Temporal Cloud hosts orchestration only; workers connect outbound over the public internet (PrivateLink optional) |
29
+ | **Versioning** | Runs pinned to their immutable deployment — safe by default; opt-in `deploymentId: 'latest'` to upgrade | Editing workflow code can break in-flight runs (non-determinism errors); evolve safely via patch APIs or Worker Versioning (keep old worker fleets draining) |
30
+ | **AI SDK & agents** | `WorkflowAgent` ships in the AI SDK; durable agent loop; **native resumable streaming** (`getWritable`/`getReadable`, `WorkflowChatTransport`) | First-party `@temporalio/ai-sdk` and "Workflow Streams" — both **Public Preview**; streaming rides on Signals/Updates (batched, history-bound) |
31
+ | **Security** | Zero-config per-run AES-256-GCM E2E encryption by default; platform security is per-World (the Vercel World inherits Vercel's security posture) | Workers run your code on your infra (never enters Temporal's plane); client-side E2E via a Codec Server you operate. Cloud: SOC 2 II, HIPAA, GDPR |
32
+ | **Performance** | No-penalty resume; serverless scale-to-zero (true suspension); up to 100K concurrency on Vercel | Self-managed workers are long-running; Cloud namespace default 500 actions/sec (auto-scales) |
33
+ | **Portability** | Apache-2.0 SDK; World abstraction swaps storage/queue/streams independently | MIT server; pluggable persistence (Cassandra/Postgres/MySQL), but an opinionated monolithic backend you run or pay for |
34
+ | **Pricing** | SDK free; pay your platform (Vercel: events + data) or just your infra if self-hosted | Self-host = free software; Temporal Cloud bills per Action (from $50/M) + storage |
35
+ | **Limits** | No run/sleep cap; 10K steps, 50 MB payload, 2 GB/run ([Vercel World limits](https://vercel.com/docs/workflows/pricing)) | No run cap (Continue-As-New for long histories); event history capped at 51,200 events / 50 MB; 2 MB payloads |
36
+
37
+ **What the limits mean in practice:** Temporal caps payloads at 2 MB and event history at 51,200 events, which binds quickly for AI workloads — a large model context, tool transcript, or embedding batch routinely exceeds 2 MB, forcing external blob storage and claim-check plumbing, and long agent loops must be split with Continue-As-New before the history fills. The [Vercel World limits](https://vercel.com/docs/workflows/pricing) (50 MB payloads, 2 GB of state per run, no run or sleep caps) leave room to keep full contexts in the run itself.
38
+
39
+ ## The biggest difference: what you operate
40
+
41
+ Temporal Cloud manages the durable engine, but **you still build, deploy, and scale a fleet of Workers** that poll task queues and run your Workflow and Activity code. Those workers connect *out* to Temporal Cloud, typically over the public internet (AWS PrivateLink / GCP Private Service Connect are available as same-region options). Readable observability requires you to stand up a **Codec Server** so the Web UI can decrypt payloads your data converter encrypted.
42
+
43
+ With the Workflow SDK on Vercel, orchestration, execution, and observability are co-located on one platform with internal networking, and per-run E2E encryption is on by default with no codec server to run. There are no workers, task queues, or a control plane to operate. (Self-hosting via the [Postgres World](/worlds/postgres) is the closest analog to running your own Temporal cluster.)
44
+
45
+ ## Versioning
46
+
47
+ This is where the two differ most in day-to-day risk. Because both replay code against history, *changing* workflow code mid-flight is the hazard.
48
+
49
+ - **Temporal:** simply editing a workflow can produce a non-determinism error that breaks or stalls open executions. You evolve safely with **patch APIs** (`patched()` / `GetVersion()`, which accumulate branch cruft) or **Worker Versioning** (Build IDs / Worker Deployments pin workflows to a build and keep the old worker fleet running until it drains). It works, but the burden is on you for every long-running workflow.
50
+ - **Workflow SDK:** runs are **pinned to the immutable deployment that started them**. Shipping new code never touches in-flight runs — they keep replaying against the exact code they began on. Upgrading a run is explicit and opt-in (start it with `deploymentId: 'latest'`, or self-restart at a checkpoint). Safe by default, no patch branches, no draining worker fleets.
51
+
52
+ ## AI agents and streaming
53
+
54
+ Both target AI agents, but the integration depth differs. The Workflow SDK's `WorkflowAgent` is a first-class construct **inside the AI SDK** (`@ai-sdk/workflow`): the agent loop becomes a durable workflow, each tool `execute` marked `"use step"` is an auto-retried durable step, and partial output streams through [durable, resumable streams](/docs/ai/resumable-streams) that survive reconnects and cold starts.
55
+
56
+ Temporal ships a first-party `@temporalio/ai-sdk` plugin and a "Workflow Streams" library, but both are **Public Preview**, and streaming is built on Signals/Updates — every chunk is written to history, so it's batched rather than per-token.
57
+
58
+ ## Migrating from Temporal
59
+
60
+ The model maps closely. Keep your orchestration logic; drop the workers, task queues, and activity modules.
61
+
62
+ | Temporal | Workflow SDK | Note |
63
+ | --- | --- | --- |
64
+ | Workflow Definition / Execution | `"use workflow"` function started with `start()` | Orchestration stays in the workflow function. |
65
+ | Activity | `"use step"` function | Side effects and Node.js access live in steps. |
66
+ | Worker + Task Queue | Managed execution | No worker fleet or polling loop to operate. |
67
+ | Signal | `createHook()` / `createWebhook()` | Hooks for typed resume signals; webhooks for HTTP callbacks. |
68
+ | Query | `getWritable({ namespace: 'status' })` stream | Stream status durably; clients read the stream instead of polling. |
69
+ | Child Workflow | `"use step"` wrapper around `start()` / `getRun()` | Return the `Run` object so observability can deep-link. |
70
+ | Activity retry policy | `maxRetries`, `RetryableError`, `FatalError` | Retries live at the step boundary. |
71
+ | Event History | Workflow event log / run timeline | Same durable replay; built-in observability UI. |
72
+
73
+ A minimal translation — the orchestrator loses `proxyActivities` and becomes plain TypeScript:
74
+
75
+ ```typescript title="workflows/order.ts"
76
+ export async function processOrder(orderId: string) {
77
+ 'use workflow'; // [!code highlight]
78
+ await chargePayment(orderId);
79
+ return { orderId, status: 'completed' };
80
+ }
81
+
82
+ async function chargePayment(orderId: string) {
83
+ 'use step'; // [!code highlight]
84
+ await fetch(`https://example.com/api/orders/${orderId}/charge`, { method: 'POST' });
85
+ }
86
+ ```
87
+
88
+ Signals become hooks — one `createHook()` + `await` replaces a signal definition, handler, and `condition()` guard:
89
+
90
+ ```typescript title="workflows/refund.ts"
91
+ import { createHook } from 'workflow';
92
+
93
+ export async function refundWorkflow(refundId: string) {
94
+ 'use workflow';
95
+ using approval = createHook<{ approved: boolean }>({
96
+ token: `refund:${refundId}:approval`, // [!code highlight]
97
+ });
98
+ const { approved } = await approval; // suspends durably until resumed
99
+ return { refundId, approved };
100
+ }
101
+ ```
102
+
103
+ <Callout type="info">
104
+ Install the migration skill to translate a Temporal app automatically:
105
+
106
+ ```bash
107
+ npx skills add https://github.com/vercel/workflow --skill migrating-to-workflow-sdk
108
+ ```
109
+ </Callout>
110
+
111
+ ### Temporal features without a direct Workflow SDK equivalent
112
+
113
+ Each row is a Temporal capability the Workflow SDK does not replicate one-to-one, paired with how to cover it on the Workflow SDK side:
114
+
115
+ | Temporal feature | How to cover it with the Workflow SDK |
116
+ | --- | --- |
117
+ | Search attributes / visibility queries | Filter runs by status and timestamps via `getRun()` and the observability UI |
118
+ | Per-activity timeouts (`startToCloseTimeout`, etc.) | Enforce deadlines inside a step with `AbortSignal.timeout(ms)`, or wrap a call in `Promise.race(step(), sleep('5m'))` |
119
+ | Rich retry policy (`backoffCoefficient`, `nonRetryableErrorTypes`) | Only `maxRetries` is configurable; classify with `RetryableError` / `FatalError` and set delay via `new RetryableError(msg, { retryAfter: '5s' })` |
120
+ | Polyglot workers | The Workflow SDK is TypeScript-first (Python in beta); for Go/Java/etc. in the same orchestrator, Temporal remains the better fit |
121
+
122
+ ---
123
+ *Compiled from public documentation. Verify current Temporal pricing, limits, and preview-feature status against [temporal.io/docs](https://docs.temporal.io). Not based on head-to-head benchmarks.*
@@ -0,0 +1,103 @@
1
+ ---
2
+ title: Workflow SDK vs trigger.dev
3
+ description: How the Workflow SDK compares to trigger.dev — deterministic event-log replay versus CRIU process checkpoint/restore, plus a concept-mapping migration guide.
4
+ type: conceptual
5
+ summary: trigger.dev achieves durability by snapshotting the process (CRIU), so code has no determinism constraints. The Workflow SDK uses event-log replay and runs in your existing app.
6
+ prerequisites:
7
+ - /docs/foundations/workflows-and-steps
8
+ related:
9
+ - /docs/foundations/errors-and-retries
10
+ - /docs/foundations/streaming
11
+ - /docs/ai
12
+ ---
13
+
14
+ [trigger.dev](https://trigger.dev) is an open-source, TypeScript-first durable task platform. Its defining trait is *how* it achieves durability: instead of replaying code, it **snapshots the whole process** (CRIU checkpoint/restore) at each wait point and restores it later. That single design choice drives most of the differences with the Workflow SDK.
15
+
16
+ <Callout type="info">
17
+ **Choose the Workflow SDK** when you want durable orchestration that runs in your existing app, a portable open-source backend you can fully self-host, TypeScript *and* Python, and broad framework support. **Choose trigger.dev** when you want a managed task platform with no determinism constraints (code runs as-is), and you're TypeScript-only.
18
+ </Callout>
19
+
20
+ ## At a glance
21
+
22
+ | | Workflow SDK | trigger.dev |
23
+ | --- | --- | --- |
24
+ | **Category** | Open-source durable-functions SDK that runs in your app | Durable task platform with its own runtime (Cloud or self-hosted) |
25
+ | **Durability model** | Event log + **deterministic replay** (workflow body must be deterministic) | **Process checkpoint/restore (CRIU)** — snapshots memory/CPU/FDs; **no determinism constraints**, code runs as-is |
26
+ | **Authoring** | `"use workflow"` / `"use step"` in your existing app | `task()` / `schemaTask()` deployed to trigger.dev as a separate target (Docker image) |
27
+ | **Languages** | TypeScript / JS (Python beta) | **TypeScript / JS only** |
28
+ | **Where it runs** | Co-located with your app (Vercel managed or self-host) | trigger.dev's run engine (isolated containers) |
29
+ | **Versioning** | Runs pinned to immutable deployment — safe by default | **Atomic versioning** — runs lock to their deploy version; new deploys never touch in-flight runs (same safety property) |
30
+ | **AI & streaming** | `WorkflowAgent` in the AI SDK; native resumable streaming | First-class: AI SDK tools, native `useChat` transport, resumable Realtime, durable multi-turn Sessions, HITL via `wait.forToken` |
31
+ | **Concurrency control** | Enforce in steps / at the publisher | First-class queues + concurrency keys |
32
+ | **Portability** | Apache-2.0; World abstraction; runs anywhere Node runs | Apache-2.0; self-host on Docker/K8s — but CRIU needs a compatible host (heavier than plain Docker); TS-only |
33
+ | **Pricing** | SDK free; pay your platform | Cloud: compute-seconds + per-run ($0.0000338/s Small + $0.000025/run); no charge while checkpointed |
34
+ | **Limits** | 50 MB payload; 2 GB/run; no duration cap ([Vercel World limits](https://vercel.com/docs/workflows/pricing)) | 3 MB payload / 10 MB output; 14-day max run TTL; CPU-time-based max duration |
35
+
36
+ **What the limits mean in practice:** trigger.dev caps task payloads at 3 MB and outputs at 10 MB — large model contexts and transcripts need external storage — and the 14-day run TTL means human-in-the-loop flows that wait longer than two weeks can't complete in one run. The [Vercel World limits](https://vercel.com/docs/workflows/pricing) are 50 MB payloads, 2 GB of state per run, and no run-duration cap.
37
+
38
+ ## The core difference: checkpoint/restore vs. replay
39
+
40
+ trigger.dev freezes the entire OS process with CRIU when a task hits a wait point, then restores it later — so there's **no replay and no determinism rule**: you can call `Date.now()` or `Math.random()` anywhere, and prior steps don't re-execute. The cost is an execution model that requires CRIU-capable infrastructure (which makes self-hosting heavier than a plain container) and runs on trigger.dev's runtime as a separate deploy target.
41
+
42
+ The Workflow SDK reconstructs state by **replaying the workflow function** against its event log. That requires the workflow body to be deterministic (side effects go in `"use step"` functions), but it runs inside your existing app and deployment with no special host requirements, and the [World abstraction](/worlds/building-a-world) lets you swap the storage/queue/stream layers.
43
+
44
+ Notably, **both pin runs to a version** so deploys never corrupt in-flight work — trigger.dev via atomic version-locking, the Workflow SDK via immutable-deployment pinning.
45
+
46
+ ## AI agents
47
+
48
+ Both invest heavily in AI. trigger.dev offers AI SDK tool wrapping, a native `useChat` transport over its Realtime layer, resumable streaming, and durable multi-turn Sessions. The Workflow SDK offers `WorkflowAgent` directly inside the AI SDK plus native [resumable streaming](/docs/ai/resumable-streams). Both support human-in-the-loop (trigger.dev's `wait.forToken`, the Workflow SDK's hooks). The deciding factors are usually language (trigger.dev is TS-only; the Workflow SDK adds Python) and whether you want the agent to run in your app vs. on a dedicated platform.
49
+
50
+ ## Migrating from trigger.dev
51
+
52
+ | trigger.dev | Workflow SDK | Note |
53
+ | --- | --- | --- |
54
+ | `task({ id, run })` | `"use workflow"` function started with `start()` | No factory or id registry. |
55
+ | `schemaTask({ schema, run })` | Typed function + `"use workflow"` | Validate inputs at the call site. |
56
+ | Inline `run` body | `"use step"` functions | Side effects move into named steps. |
57
+ | `wait.for` / `wait.until` | `sleep('5m')` / `sleep(date)` | Import from `workflow`. |
58
+ | `wait.forToken({ timeout })` | `createHook()` + `Promise.race` with `sleep()` | Hooks carry a typed token. |
59
+ | `triggerAndWait()` | `"use step"` wrappers around `start()` / `getRun()` | Spawn + collect. |
60
+ | `batch.triggerAndWait()` | `Promise.all` over collected `Run` handles | Standard concurrency. |
61
+ | `metadata.stream()` / Realtime | `getWritable()` / named streams | Clients read from the stream. |
62
+ | `AbortTaskRunError` | `FatalError` | Stops retries immediately. |
63
+
64
+ The `task()` factory collapses into a plain function — and because the workflow body is replayed, move side effects into steps:
65
+
66
+ ```typescript title="workflows/order.ts"
67
+ export async function processOrder(orderId: string) {
68
+ 'use workflow'; // [!code highlight]
69
+ const order = await loadOrder(orderId);
70
+ return { orderId: order.id, status: 'completed' };
71
+ }
72
+
73
+ async function loadOrder(orderId: string) {
74
+ 'use step'; // [!code highlight]
75
+ const res = await fetch(`https://example.com/api/orders/${orderId}`);
76
+ return res.json() as Promise<{ id: string }>;
77
+ }
78
+ ```
79
+
80
+ <Callout type="warn">
81
+ trigger.dev's `run` body has full Node.js access. The Workflow SDK's `"use workflow"` body runs in a sandboxed VM — side effects (`fetch`, `Date.now()`, `Math.random()`, DB access) must live inside `"use step"` functions. Orchestration stays in the workflow body.
82
+ </Callout>
83
+
84
+ <Callout type="info">
85
+ Install the migration skill to translate a trigger.dev app automatically:
86
+
87
+ ```bash
88
+ npx skills add https://github.com/vercel/workflow --skill migrating-to-workflow-sdk
89
+ ```
90
+ </Callout>
91
+
92
+ ### trigger.dev features without a direct Workflow SDK equivalent
93
+
94
+ Each row is a trigger.dev capability the Workflow SDK does not replicate one-to-one, paired with how to cover it on the Workflow SDK side:
95
+
96
+ | trigger.dev feature | How to cover it with the Workflow SDK |
97
+ | --- | --- |
98
+ | Concurrency keys / queue concurrency limits | Enforce limits inside steps or debounce at the publisher |
99
+ | `schedules.task()` / cron | Trigger from Vercel Cron or a system cron calling `start()` |
100
+ | `machine` presets / custom images | Function resources are per-deployment (configured via your hosting platform) |
101
+
102
+ ---
103
+ *Compiled from public documentation. Verify current trigger.dev limits and pricing against [trigger.dev/docs](https://trigger.dev/docs). Not based on head-to-head benchmarks.*
@@ -40,10 +40,13 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
40
40
 
41
41
  ### `WORKFLOW_PRECONDITION_GUARD`
42
42
 
43
- - Default: disabled
44
- - Set `1` to enable an optimistic-concurrency guard for event creation: replay-context event creations send a `stateUpdatedAt` snapshot timestamp, and a backend that supports the guard rejects a creation with 412 ([`PreconditionFailedError`](/docs/api-reference/workflow-errors/precondition-failed-error)) when a newer out-of-band event (a received hook or a completed step) was recorded after that snapshot.
43
+ - Default: enabled
44
+ - An optimistic-concurrency guard for event creation: replay-context event creations send a `stateUpdatedAt` snapshot timestamp, and a backend that supports the guard rejects a creation with 412 ([`PreconditionFailedError`](/docs/api-reference/workflow-errors/precondition-failed-error)) when a newer out-of-band event (a received hook or a completed step) was recorded after that snapshot.
45
45
  - On rejection the runtime reloads the event log and retries, falling back to a queue re-invocation with a fresh replay if it cannot catch up.
46
- - Backends that do not support the guard ignore the snapshot.
46
+ - When enabled — and the World declares that it enforces the guard (`capabilities.preconditionGuard`; the Vercel World does) — the runtime also keeps the per-step event-log delta optimization (consuming the delta returned by a step's terminal write instead of issuing an extra `events.list` per step) active while the run has an open hook. Without an enforced guard, an open hook disables it.
47
+ - While a hook is open on a guard-enforcing deployment, inline steps take the await-then-run path even when optimistic inline start is enabled: the step's `step_started` claim carries the snapshot and is awaited before the body runs, so a claim the backend rejects as stale never executes user code.
48
+ - Backends that do not support the guard ignore the snapshot; they must not declare the capability, so guard-dependent optimizations stay off against them even when the flag is set.
49
+ - Set `0` to disable.
47
50
 
48
51
  ## Inline execution
49
52
 
@@ -63,6 +66,7 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
63
66
 
64
67
  - Default: enabled
65
68
  - Fast path for a run's first delivery.
69
+ - Stops forcing optimistic inline step start once the run creates a hook or wait.
66
70
  - Set `0` or `false` to disable.
67
71
 
68
72
  ### `WORKFLOW_OPTIMISTIC_INLINE_START`
@@ -130,6 +134,21 @@ These variables are primarily for tests, debugging, or unusual deployments.
130
134
  - Stream write buffering interval.
131
135
  - Also available as `streamFlushIntervalMs` on Worlds that expose it.
132
136
 
137
+ ### `WORKFLOW_STREAM_MAX_INFLIGHT_CHUNKS`
138
+
139
+ - Default: `1000`
140
+ - Flow-control bound: how many stream chunks may be read-but-not-yet-durably-written while writes are coalesced. Once this many are outstanding, the producer pauses reading until a batch is written. Bounds in-memory buffering; distinct from the per-request batch caps below.
141
+
142
+ ### `WORKFLOW_STREAM_MAX_CHUNKS_PER_BATCH`
143
+
144
+ - Default: `1000`
145
+ - Wire limit: maximum number of chunks sent in a single coalesced multi-write. Larger bursts are split across requests so one request can't exceed the server's per-multi-write chunk cap.
146
+
147
+ ### `WORKFLOW_STREAM_MAX_BYTES_PER_BATCH`
148
+
149
+ - Default: `1048576` (1 MiB)
150
+ - Wire limit: maximum cumulative bytes in a single coalesced multi-write, so large chunks don't produce a request body that platform limits reject. A single chunk larger than this is still sent on its own.
151
+
133
152
  ### `WORKFLOW_FRAMED_STREAM_MAX_RECONNECTS`
134
153
 
135
154
  - Default: `50`
@@ -4,9 +4,9 @@ description: Configure the Workflow backend that stores runs and delivers queue
4
4
  type: reference
5
5
  summary: Select and configure Local, Postgres, Vercel, or custom Worlds.
6
6
  related:
7
- - /docs/deploying/world/local-world
8
- - /docs/deploying/world/postgres-world
9
- - /docs/deploying/world/vercel-world
7
+ - /worlds/local
8
+ - /worlds/postgres
9
+ - /worlds/vercel
10
10
  ---
11
11
 
12
12
  A [World](/docs/deploying) stores workflow state and delivers queue messages.
@@ -201,7 +201,7 @@ Platform-provided values such as `VERCEL_DEPLOYMENT_ID`, `VERCEL_PROJECT_ID`, an
201
201
  - Default: disabled
202
202
  - Set `1` to serialize orchestrator (flow) invocations per run: each run's replays get their own queue topic and the flow trigger is generated with `maxConcurrency: 1`. Inline step executions get per-step topics and keep full parallelism.
203
203
  - Read at **both build time and runtime** — set it as a project-level environment variable so the generated trigger and the runtime queue routing agree.
204
- - Routing each run through a dedicated `maxConcurrency: 1` topic might lead to higher queue performance overhead. See [Vercel World](/docs/deploying/world/vercel-world#workflow_sequential_replays) for details.
204
+ - Routing each run through a dedicated `maxConcurrency: 1` topic might lead to higher queue performance overhead. See [Vercel World](/worlds/vercel#workflow_sequential_replays) for details.
205
205
 
206
206
  ### `VERCEL_WORKFLOW_SERVER_URL`
207
207
 
@@ -90,6 +90,6 @@ The workflow should create the deterministic hook and check `await hook.getConfl
90
90
  - [`"use step"`](/docs/foundations/workflows-and-steps#step-functions) -- declares step functions with full Node.js access
91
91
  - [`getStepMetadata()`](/docs/api-reference/workflow/get-step-metadata) -- provides the deterministic `stepId` for idempotency keys
92
92
  - [`createHook()`](/docs/api-reference/workflow/create-hook) -- creates a hook with an optional deterministic token
93
- - [`getHookByToken()`](/docs/api-reference/workflow-api/get-hook-by-token) -- finds the active hook for a token
93
+ - [`getHookByToken()`](/docs/api-reference/workflow-api/get-hook-by-token) -- finds the Hook that owns a token
94
94
  - [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) -- resumes the active hook when the duplicate request carries data
95
95
  - [`start()`](/docs/api-reference/workflow-api/start) -- starts a new workflow run
@@ -1,15 +1,13 @@
1
1
  ---
2
2
  title: Deploying
3
- icon: Rocket
4
3
  description: Deploy workflows locally, on Vercel, or anywhere using pluggable World adapters.
5
4
  type: overview
6
5
  summary: Learn how to deploy workflows to different environments using World adapters.
7
- manualCards: true
8
6
  related:
9
- - /docs/deploying/world/local-world
10
- - /docs/deploying/world/postgres-world
11
- - /docs/deploying/world/vercel-world
12
- - /docs/deploying/building-a-world
7
+ - /worlds/local
8
+ - /worlds/postgres
9
+ - /worlds/vercel
10
+ - /worlds/building-a-world
13
11
  ---
14
12
 
15
13
  Workflows are designed to be highly portable. The same workflow code can run locally during development, on Vercel with zero configuration, or on any infrastructure using **Worlds** — pluggable adapters that handle storage, queuing, and communication.
@@ -54,7 +52,7 @@ vercel deploy
54
52
  <FluidComputeCallout />
55
53
 
56
54
  <Callout>
57
- Learn more about the [Vercel World](/docs/deploying/world/vercel-world) and its capabilities, including [multi-region](/docs/deploying/world/vercel-world#multi-region).
55
+ Learn more about the [Vercel World](/worlds/vercel) and its capabilities, including [multi-region](/worlds/vercel#multi-region).
58
56
  </Callout>
59
57
 
60
58
  ## Self-Hosting & Other Providers
@@ -65,7 +63,7 @@ For self-hosting or deploying to other cloud providers, you can use community-ma
65
63
  <Card title="Explore Worlds" href="/worlds">
66
64
  Browse official and community World implementations with compatibility status and performance benchmarks.
67
65
  </Card>
68
- <Card title="Build Your Own" href="/docs/deploying/building-a-world">
66
+ <Card title="Build Your Own" href="/worlds/building-a-world">
69
67
  Learn how to implement a custom World for your infrastructure.
70
68
  </Card>
71
69
  </Cards>
@@ -112,7 +112,7 @@ export async function orderWorkflow(orderId: string) {
112
112
  }
113
113
  ```
114
114
 
115
- Calling `createHook()` on its own does not register the hook — registration is only committed when the workflow suspends. Awaiting `hook.getConflict()` suspends the workflow to commit the hook registration, then resolves with `null` once the hook is registered and ready to receive payloads, or with a `Run` handle for the run that owns the token if another active hook already claimed it (see [`HookConflictError`](/docs/errors/hook-conflict)). For `hook_conflict` events persisted by older worlds that did not record the owning run's ID, `getConflict()` rejects with `HookConflictError` instead of resolving with an incomplete handle. The conflicting run's accessors are durable steps, so the workflow can inspect `await conflict.status`, wait on `await conflict.returnValue`, or cancel the owner with `await conflict.cancel()` — see [Run idempotency](/docs/foundations/idempotency#run-idempotency) for these strategies.
115
+ Calling `createHook()` on its own does not register the hook — registration is only committed when the workflow suspends. Awaiting `hook.getConflict()` suspends the workflow to commit the hook registration, then resolves with `null` once the hook is registered and ready to receive payloads, or with a `Run` handle for the run that owns the token (see [`HookConflictError`](/docs/errors/hook-conflict)). For `hook_conflict` events persisted by older worlds that did not record the owning run's ID, `getConflict()` rejects with `HookConflictError` instead of resolving with an incomplete handle. The conflicting run's accessors are durable steps, so the workflow can inspect `await conflict.status`, wait on `await conflict.returnValue`, or cancel the owner with `await conflict.cancel()` — see [Run idempotency](/docs/foundations/idempotency#run-idempotency) for these strategies.
116
116
 
117
117
  ### Custom Tokens for Deterministic Hooks
118
118