workflow 5.0.0-beta.0 → 5.0.0-beta.2

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 (86) hide show
  1. package/README.md +4 -4
  2. package/dist/api-workflow.d.ts +1 -3
  3. package/dist/api-workflow.d.ts.map +1 -1
  4. package/dist/api-workflow.js +2 -6
  5. package/dist/api.js +1 -1
  6. package/dist/astro.js +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/internal/builtins.js +1 -1
  9. package/dist/internal/class-serialization.js +1 -1
  10. package/dist/internal/errors.js +1 -1
  11. package/dist/nest.js +1 -1
  12. package/dist/next.cjs +4 -2
  13. package/dist/next.d.cts +1 -1
  14. package/dist/next.d.cts.map +1 -1
  15. package/dist/nitro.js +1 -1
  16. package/dist/nuxt.js +1 -1
  17. package/dist/observability.d.ts +1 -1
  18. package/dist/observability.js +2 -2
  19. package/dist/runtime.js +1 -1
  20. package/dist/stdlib.js +1 -1
  21. package/dist/sveltekit.js +1 -1
  22. package/dist/typescript-plugin.cjs +1 -1
  23. package/dist/vite.js +1 -1
  24. package/dist/workflow.js +1 -1
  25. package/docs/ai/resumable-streams.mdx +1 -1
  26. package/docs/api-reference/workflow/create-webhook.mdx +37 -18
  27. package/docs/api-reference/workflow/get-workflow-metadata.mdx +61 -0
  28. package/docs/api-reference/workflow-ai/durable-agent.mdx +0 -4
  29. package/docs/api-reference/workflow-ai/index.mdx +0 -5
  30. package/docs/api-reference/workflow-ai/workflow-chat-transport.mdx +0 -4
  31. package/docs/api-reference/workflow-api/get-world.mdx +6 -6
  32. package/docs/api-reference/workflow-api/index.mdx +1 -1
  33. package/docs/api-reference/workflow-api/world/index.mdx +2 -2
  34. package/docs/api-reference/workflow-api/world/observability.mdx +1 -1
  35. package/docs/api-reference/workflow-api/world/queue.mdx +1 -1
  36. package/docs/api-reference/workflow-api/world/storage.mdx +8 -8
  37. package/docs/api-reference/workflow-api/world/streams.mdx +38 -36
  38. package/docs/cookbook/advanced/custom-serialization.mdx +168 -0
  39. package/docs/cookbook/advanced/durable-objects.mdx +148 -0
  40. package/docs/cookbook/advanced/isomorphic-packages.mdx +145 -0
  41. package/docs/cookbook/advanced/meta.json +10 -0
  42. package/docs/cookbook/advanced/publishing-libraries.mdx +279 -0
  43. package/docs/cookbook/advanced/serializable-steps.mdx +135 -0
  44. package/docs/cookbook/agent-patterns/durable-agent.mdx +191 -0
  45. package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +278 -0
  46. package/docs/cookbook/agent-patterns/meta.json +10 -0
  47. package/docs/cookbook/agent-patterns/stop-workflow.mdx +216 -0
  48. package/docs/cookbook/agent-patterns/tool-orchestration.mdx +255 -0
  49. package/docs/cookbook/agent-patterns/tool-streaming.mdx +181 -0
  50. package/docs/cookbook/common-patterns/batching.mdx +179 -0
  51. package/docs/cookbook/common-patterns/child-workflows.mdx +372 -0
  52. package/docs/cookbook/common-patterns/content-router.mdx +207 -0
  53. package/docs/cookbook/common-patterns/fan-out.mdx +208 -0
  54. package/docs/cookbook/common-patterns/idempotency.mdx +107 -0
  55. package/docs/cookbook/common-patterns/meta.json +15 -0
  56. package/docs/cookbook/common-patterns/rate-limiting.mdx +228 -0
  57. package/docs/cookbook/common-patterns/saga.mdx +152 -0
  58. package/docs/cookbook/common-patterns/scheduling.mdx +249 -0
  59. package/docs/cookbook/common-patterns/webhooks.mdx +185 -0
  60. package/docs/cookbook/index.mdx +41 -0
  61. package/docs/cookbook/integrations/ai-sdk.mdx +204 -0
  62. package/docs/cookbook/integrations/chat-sdk.mdx +203 -0
  63. package/docs/cookbook/integrations/meta.json +4 -0
  64. package/docs/cookbook/integrations/sandbox.mdx +128 -0
  65. package/docs/cookbook/meta.json +5 -0
  66. package/docs/deploying/building-a-world.mdx +45 -43
  67. package/docs/deploying/world/local-world.mdx +1 -1
  68. package/docs/deploying/world/postgres-world.mdx +10 -5
  69. package/docs/deploying/world/vercel-world.mdx +1 -1
  70. package/docs/errors/start-invalid-workflow-function.mdx +1 -1
  71. package/docs/getting-started/index.mdx +8 -1
  72. package/docs/getting-started/meta.json +2 -1
  73. package/docs/getting-started/next.mdx +24 -0
  74. package/docs/getting-started/python.mdx +165 -0
  75. package/docs/how-it-works/code-transform.mdx +6 -5
  76. package/docs/meta.json +1 -0
  77. package/docs/migration-guides/index.mdx +34 -0
  78. package/docs/migration-guides/meta.json +9 -0
  79. package/docs/migration-guides/migrating-from-aws-step-functions.mdx +311 -0
  80. package/docs/migration-guides/migrating-from-inngest.mdx +282 -0
  81. package/docs/migration-guides/migrating-from-temporal.mdx +284 -0
  82. package/docs/migration-guides/migrating-from-trigger-dev.mdx +296 -0
  83. package/package.json +13 -14
  84. package/dist/internal/private.d.ts +0 -6
  85. package/dist/internal/private.d.ts.map +0 -1
  86. package/dist/internal/private.js +0 -6
@@ -0,0 +1,204 @@
1
+ ---
2
+ title: AI SDK
3
+ description: Use AI SDK model providers, tool calling, and streaming inside durable workflows.
4
+ type: guide
5
+ summary: Turn any AI SDK model call into a retryable, observable workflow step with built-in streaming.
6
+ related:
7
+ - /docs/ai
8
+ - /docs/ai/defining-tools
9
+ - /docs/ai/resumable-streams
10
+ - /docs/api-reference/workflow-ai/durable-agent
11
+ ---
12
+
13
+ Workflow SDK integrates with [AI SDK](https://ai-sdk.dev) through the `@workflow/ai` package. This turns your LLM calls and tool executions into durable, retryable steps with built-in streaming and observability.
14
+
15
+ ## What It Enables
16
+
17
+ - **Durable LLM calls** -- Model invocations become steps that survive crashes and cold starts
18
+ - **Any model provider** -- Use OpenAI, Anthropic, Google, Bedrock, or any AI SDK-compatible provider through [Vercel Gateway](https://vercel.com/docs/gateway) or direct provider configuration
19
+ - **Tool durability** -- Tool executions become steps with automatic retries and event logging
20
+ - **Resumable streaming** -- Clients reconnect mid-stream without losing data
21
+
22
+ ## When to Use
23
+
24
+ Use this integration when your application calls an LLM and needs:
25
+
26
+ - Reliability for long-running agent loops (multi-step tool calling)
27
+ - Automatic retry on transient model API failures
28
+ - Stream resumption after disconnects
29
+ - Observability into each model call and tool execution
30
+
31
+ ## DurableAgent with Model Providers
32
+
33
+ The `DurableAgent` wraps AI SDK's streaming interface. Pass any model string supported by [Vercel Gateway](https://vercel.com/docs/gateway) or a provider-specific model ID.
34
+
35
+ ```typescript title="workflows/research.ts" lineNumbers
36
+ import { DurableAgent } from "@workflow/ai/agent";
37
+ import { convertToModelMessages, type UIMessage, type UIMessageChunk } from "ai";
38
+ import { getWritable } from "workflow";
39
+ import z from "zod/v4";
40
+
41
+ async function searchWeb(input: { query: string }): Promise<{ results: string[] }> {
42
+ "use step";
43
+ const response = await fetch(
44
+ `https://api.example.com/search?q=${encodeURIComponent(input.query)}`
45
+ );
46
+ const data = await response.json();
47
+ return { results: data.items.map((item: { title: string }) => item.title) };
48
+ }
49
+
50
+ async function summarize(input: { text: string }): Promise<{ summary: string }> {
51
+ "use step";
52
+ // Each step is individually retried on failure
53
+ const response = await fetch("https://api.example.com/summarize", {
54
+ method: "POST",
55
+ body: JSON.stringify({ text: input.text }),
56
+ });
57
+ const data = await response.json();
58
+ return { summary: data.summary };
59
+ }
60
+
61
+ export async function researchAgent(messages: UIMessage[]) {
62
+ "use workflow";
63
+
64
+ const agent = new DurableAgent({ // [!code highlight]
65
+ model: "anthropic/claude-sonnet-4-20250514",
66
+ instructions: "You are a research assistant. Search the web and summarize findings.",
67
+ tools: {
68
+ searchWeb: {
69
+ description: "Search the web for information",
70
+ inputSchema: z.object({
71
+ query: z.string().describe("The search query"),
72
+ }),
73
+ execute: searchWeb,
74
+ },
75
+ summarize: {
76
+ description: "Summarize a block of text",
77
+ inputSchema: z.object({
78
+ text: z.string().describe("The text to summarize"),
79
+ }),
80
+ execute: summarize,
81
+ },
82
+ },
83
+ });
84
+
85
+ const result = await agent.stream({ // [!code highlight]
86
+ messages: await convertToModelMessages(messages),
87
+ writable: getWritable<UIMessageChunk>(),
88
+ });
89
+
90
+ return { messages: result.messages };
91
+ }
92
+ ```
93
+
94
+ ### Using Different Providers
95
+
96
+ #### Vercel Gateway (string model IDs)
97
+
98
+ All string model IDs route through [Vercel Gateway](https://vercel.com/docs/gateway). Switch providers by changing the model string -- no other code changes required.
99
+
100
+ {/* @skip-typecheck - illustrative snippets with intentional redeclarations */}
101
+ ```typescript
102
+ // All string model IDs route through Vercel Gateway
103
+ const agent = new DurableAgent({ model: "anthropic/claude-sonnet-4-20250514" });
104
+ const agent = new DurableAgent({ model: "openai/gpt-4o" });
105
+ const agent = new DurableAgent({ model: "google/gemini-2.5-pro" });
106
+ const agent = new DurableAgent({ model: "bedrock/claude-haiku-4-5-20251001-v1" });
107
+ ```
108
+
109
+ #### Direct Provider Access
110
+
111
+ Import from a provider package to bypass Gateway and connect to the provider directly.
112
+
113
+ ```typescript
114
+ import { DurableAgent } from "@workflow/ai/agent";
115
+ import { openai } from "@workflow/ai/openai";
116
+
117
+ const agent = new DurableAgent({ model: openai("gpt-4o") });
118
+ ```
119
+
120
+ ### Provider-Specific Options
121
+
122
+ Pass provider options for features like reasoning or extended thinking.
123
+
124
+ ```typescript
125
+ const agent = new DurableAgent({
126
+ model: "anthropic/claude-sonnet-4-20250514",
127
+ providerOptions: {
128
+ anthropic: { thinking: { type: "enabled", budgetTokens: 10000 } },
129
+ },
130
+ // ...tools and instructions
131
+ });
132
+ ```
133
+
134
+ ## Tool Functions with Steps
135
+
136
+ Tool `execute` functions can optionally include steps by using the `"use step"` directive. When a tool is **not** a step, it runs inside the workflow context and can modify workflow state directly. When a tool **is** marked with `"use step"`, it becomes a durable step with:
137
+
138
+ - **Automatic retries** -- If a tool fails (network error, API timeout), the framework retries it
139
+ - **Event logging** -- Inputs and outputs are recorded for observability and replay
140
+ - **Idempotency** -- On replay after a crash, completed steps return their cached result
141
+
142
+ ```typescript
143
+ async function bookFlight(input: {
144
+ origin: string;
145
+ destination: string;
146
+ date: string;
147
+ }): Promise<{ confirmationId: string }> {
148
+ "use step";
149
+ // This call is retried on transient failures and its result is persisted
150
+ const response = await fetch("https://api.airline.com/book", {
151
+ method: "POST",
152
+ headers: { "Content-Type": "application/json" },
153
+ body: JSON.stringify(input),
154
+ });
155
+ if (!response.ok) throw new Error(`Booking failed: ${response.status}`);
156
+ return response.json();
157
+ }
158
+ ```
159
+
160
+ ## Resumable Streaming
161
+
162
+ Use `WorkflowChatTransport` on the client to automatically reconnect to a workflow's stream if the connection drops.
163
+
164
+ ```typescript title="app/api/chat/route.ts" lineNumbers
165
+ import { createUIMessageStreamResponse } from "ai";
166
+ import { start } from "workflow/api";
167
+ import { researchAgent } from "@/workflows/research";
168
+
169
+ export async function POST(request: Request) {
170
+ const { messages } = await request.json();
171
+ const run = await start(researchAgent, [messages]); // [!code highlight]
172
+
173
+ return createUIMessageStreamResponse({
174
+ stream: run.readable, // [!code highlight]
175
+ headers: { "x-workflow-run-id": run.runId },
176
+ });
177
+ }
178
+ ```
179
+
180
+ ```typescript title="components/chat.tsx" lineNumbers
181
+ "use client";
182
+
183
+ import { useChat } from "@ai-sdk/react";
184
+ import { WorkflowChatTransport } from "@workflow/ai";
185
+
186
+ export function Chat() {
187
+ const chat = useChat({
188
+ transport: new WorkflowChatTransport({ // [!code highlight]
189
+ api: "/api/chat",
190
+ }),
191
+ });
192
+
193
+ // Standard useChat usage -- reconnection is handled automatically
194
+ return (
195
+ <div>
196
+ {chat.messages.map((m) => (
197
+ <div key={m.id}>{m.content}</div>
198
+ ))}
199
+ </div>
200
+ );
201
+ }
202
+ ```
203
+
204
+ See [Resumable Streams](/docs/ai/resumable-streams) for advanced options like `startIndex` and `prepareReconnectToStreamRequest`.
@@ -0,0 +1,203 @@
1
+ ---
2
+ title: Chat SDK
3
+ description: Build durable chat sessions by combining workflow persistence with AI SDK's chat primitives.
4
+ type: guide
5
+ summary: Use workflow hooks and streaming to create chat sessions that survive disconnects and server restarts.
6
+ related:
7
+ - /docs/ai/chat-session-modeling
8
+ - /docs/ai/resumable-streams
9
+ - /docs/ai/message-queueing
10
+ - /docs/api-reference/workflow-ai/durable-agent
11
+ - /docs/api-reference/workflow/define-hook
12
+ ---
13
+
14
+ AI SDK provides chat primitives (`useChat`, message types, streaming utilities) for building chat interfaces. Workflow SDK makes those chat sessions durable -- surviving disconnects, cold starts, and server restarts -- by persisting every message and LLM response as workflow events.
15
+
16
+ ## What It Enables
17
+
18
+ - **Durable chat history** -- Messages and responses are persisted in the workflow event log, not just client state
19
+ - **Resumable sessions** -- Users reconnect and pick up where they left off, even after server restarts
20
+ - **Multi-turn conversations** -- A single workflow manages an entire chat session with hook-based message injection
21
+ - **Server-side message queueing** -- Inject follow-up messages while the agent is still processing
22
+
23
+ ## When to Use
24
+
25
+ Use this pattern when your chat application needs:
26
+
27
+ - Persistence beyond the browser session
28
+ - Recovery from server failures mid-conversation
29
+ - Long-running agent sessions (minutes to hours)
30
+ - Server-driven message injection (system messages, external events)
31
+
32
+ ## Single-Turn: Stateless Sessions
33
+
34
+ Each user message starts a new workflow run. The client owns the message history and sends the full array with each request. This is the simplest pattern.
35
+
36
+ ```typescript title="workflows/chat.ts" lineNumbers
37
+ import { DurableAgent } from "@workflow/ai/agent";
38
+ import { convertToModelMessages, type UIMessage, type UIMessageChunk } from "ai";
39
+ import { getWritable } from "workflow";
40
+
41
+ export async function chat(messages: UIMessage[]) {
42
+ "use workflow";
43
+
44
+ const agent = new DurableAgent({
45
+ model: "anthropic/claude-sonnet-4-20250514",
46
+ instructions: "You are a helpful assistant.",
47
+ tools: { /* your tools here */ },
48
+ });
49
+
50
+ const result = await agent.stream({ // [!code highlight]
51
+ messages: await convertToModelMessages(messages),
52
+ writable: getWritable<UIMessageChunk>(),
53
+ });
54
+
55
+ return { messages: result.messages };
56
+ }
57
+ ```
58
+
59
+ ```typescript title="app/api/chat/route.ts" lineNumbers
60
+ import { createUIMessageStreamResponse } from "ai";
61
+ import { start } from "workflow/api";
62
+ import { chat } from "@/workflows/chat";
63
+
64
+ export async function POST(request: Request) {
65
+ const { messages } = await request.json();
66
+ const run = await start(chat, [messages]); // [!code highlight]
67
+
68
+ return createUIMessageStreamResponse({
69
+ stream: run.readable,
70
+ headers: { "x-workflow-run-id": run.runId },
71
+ });
72
+ }
73
+ ```
74
+
75
+ The client uses `WorkflowChatTransport` for automatic stream resumption.
76
+
77
+ ```typescript title="components/chat.tsx" lineNumbers
78
+ "use client";
79
+
80
+ import { useChat } from "@ai-sdk/react";
81
+ import { WorkflowChatTransport } from "@workflow/ai";
82
+
83
+ export function Chat() {
84
+ const chat = useChat({
85
+ transport: new WorkflowChatTransport({ api: "/api/chat" }), // [!code highlight]
86
+ });
87
+
88
+ return (
89
+ <div>
90
+ {chat.messages.map((m) => (
91
+ <div key={m.id}>{m.content}</div>
92
+ ))}
93
+ <form onSubmit={chat.handleSubmit}>
94
+ <input value={chat.input} onChange={chat.handleInputChange} />
95
+ </form>
96
+ </div>
97
+ );
98
+ }
99
+ ```
100
+
101
+ ## Multi-Turn: Durable Sessions
102
+
103
+ A single workflow manages the entire conversation. The workflow loops, waiting for new messages via a hook. This gives you server-side ownership of the full chat history.
104
+
105
+ ```typescript title="workflows/durable-chat.ts" lineNumbers
106
+ import { DurableAgent } from "@workflow/ai/agent";
107
+ import {
108
+ convertToModelMessages,
109
+ type UIMessage,
110
+ type UIMessageChunk,
111
+ } from "ai";
112
+ import { defineHook, getWritable, getWorkflowMetadata } from "workflow";
113
+ import { z } from "zod";
114
+
115
+ const chatMessageHook = defineHook({ // [!code highlight]
116
+ schema: z.object({
117
+ messages: z.array(z.any()),
118
+ }),
119
+ });
120
+
121
+ export async function durableChat(initialMessages: UIMessage[]) {
122
+ "use workflow";
123
+
124
+ const { workflowRunId } = getWorkflowMetadata();
125
+ let allMessages = await convertToModelMessages(initialMessages);
126
+
127
+ const agent = new DurableAgent({
128
+ model: "anthropic/claude-sonnet-4-20250514",
129
+ instructions: "You are a helpful assistant.",
130
+ tools: { /* your tools here */ },
131
+ });
132
+
133
+ // First turn
134
+ const firstResult = await agent.stream({
135
+ messages: allMessages,
136
+ writable: getWritable<UIMessageChunk>(),
137
+ preventClose: true,
138
+ });
139
+ allMessages = firstResult.messages;
140
+
141
+ // Subsequent turns -- wait for new messages via hook
142
+ while (true) {
143
+ const hook = chatMessageHook.create({ token: workflowRunId });
144
+ const { messages: newMessages } = await hook; // [!code highlight]
145
+
146
+ allMessages = [
147
+ ...allMessages,
148
+ ...await convertToModelMessages(newMessages),
149
+ ];
150
+
151
+ const result = await agent.stream({
152
+ messages: allMessages,
153
+ writable: getWritable<UIMessageChunk>(),
154
+ preventClose: true,
155
+ });
156
+ allMessages = result.messages;
157
+ }
158
+ }
159
+ ```
160
+
161
+ ### Multi-Turn API Routes
162
+
163
+ You need two routes: one to start the session, another to send follow-up messages.
164
+
165
+ ```typescript title="app/api/chat/route.ts" lineNumbers
166
+ import { createUIMessageStreamResponse } from "ai";
167
+ import { start } from "workflow/api";
168
+ import { durableChat } from "@/workflows/durable-chat";
169
+
170
+ export async function POST(request: Request) {
171
+ const { messages } = await request.json();
172
+ const run = await start(durableChat, [messages]); // [!code highlight]
173
+
174
+ return createUIMessageStreamResponse({
175
+ stream: run.readable,
176
+ headers: { "x-workflow-run-id": run.runId },
177
+ });
178
+ }
179
+ ```
180
+
181
+ ```typescript title="app/api/chat/follow-up/route.ts" lineNumbers
182
+ import { resumeHook } from "workflow/api";
183
+
184
+ export async function POST(request: Request) {
185
+ const { runId, messages } = await request.json();
186
+ await resumeHook(runId, { messages }); // [!code highlight]
187
+ return new Response("OK");
188
+ }
189
+ ```
190
+
191
+ ## Choosing a Pattern
192
+
193
+ | | Single-Turn | Multi-Turn |
194
+ |---|---|---|
195
+ | **State ownership** | Client | Server (workflow event log) |
196
+ | **Message injection** | Not needed | Via hooks |
197
+ | **Complexity** | Low | Medium |
198
+ | **Session duration** | Per-request | Minutes to hours |
199
+ | **Crash recovery** | Client resends full history | Workflow replays from event log |
200
+
201
+ Start with single-turn. Move to multi-turn when you need server-owned state, message injection from external sources, or sessions that outlive the browser tab.
202
+
203
+ See [Chat Session Modeling](/docs/ai/chat-session-modeling) for the full guide including multiplayer patterns and message queueing.
@@ -0,0 +1,4 @@
1
+ {
2
+ "title": "Integrations",
3
+ "pages": ["ai-sdk", "sandbox", "chat-sdk"]
4
+ }
@@ -0,0 +1,128 @@
1
+ ---
2
+ title: Sandbox
3
+ description: Orchestrate Vercel Sandbox lifecycle -- creation, code execution, snapshotting -- inside durable workflows.
4
+ type: guide
5
+ summary: Use workflow steps to provision sandboxes, run code, and manage sandbox lifecycle with automatic cleanup on failure.
6
+ related:
7
+ - /docs/ai/defining-tools
8
+ - /docs/foundations/errors-and-retries
9
+ - /docs/api-reference/workflow-ai/durable-agent
10
+ ---
11
+
12
+ [Vercel Sandbox](https://vercel.com/docs/sandbox) provides isolated code execution environments. The `@vercel/sandbox` package implements first-class support for the Workflow SDK -- the `Sandbox` class is serializable, and its methods (`create`, `runCommand`, `destroy`, etc.) implicitly run as steps. This means you can interact with sandboxes directly inside workflow functions without wrapping each operation in a separate `"use step"` function.
13
+
14
+ ## What It Enables
15
+
16
+ - **Durable sandbox sessions** -- Sandbox provisioning and teardown survive cold starts
17
+ - **Automatic cleanup** -- Saga-style compensation ensures sandboxes are destroyed on failure
18
+ - **Multi-step code execution** -- Run a sequence of commands in the same sandbox with each step logged
19
+ - **Agent-driven sandboxes** -- Give your DurableAgent a tool that spins up sandboxes on demand
20
+
21
+ ## When to Use
22
+
23
+ Use this integration when your workflow needs to:
24
+
25
+ - Execute user-provided or AI-generated code safely
26
+ - Run multi-step build/test pipelines in isolated environments
27
+ - Provision temporary environments for interactive sessions
28
+ - Snapshot sandbox state between steps for reproducibility
29
+
30
+ ## Sandbox Lifecycle in a Workflow
31
+
32
+ Because `@vercel/sandbox` methods are implicit steps, each call is automatically persisted to the event log. If a failure occurs partway through, the workflow replays from where it left off.
33
+
34
+ ```typescript title="workflows/sandbox-pipeline.ts" lineNumbers
35
+ import { Sandbox } from "@vercel/sandbox";
36
+
37
+ export async function sandboxPipeline(input: {
38
+ template: string;
39
+ commands: string[];
40
+ }) {
41
+ "use workflow";
42
+
43
+ const sandbox = await Sandbox.create({ template: input.template }); // [!code highlight]
44
+
45
+ try {
46
+ const results = [];
47
+ for (const command of input.commands) {
48
+ const result = await sandbox.runCommand(command); // [!code highlight]
49
+ results.push(result);
50
+ }
51
+ return { status: "completed", results };
52
+ } catch (error) {
53
+ await sandbox.destroy(); // [!code highlight]
54
+ throw error;
55
+ }
56
+ }
57
+ ```
58
+
59
+ ## Sandbox as an Agent Tool
60
+
61
+ Give a DurableAgent the ability to create and use sandboxes. The agent decides when to spin up a sandbox, what code to run, and when to tear it down. Since sandbox methods are implicit steps, the tool execute functions can call them directly.
62
+
63
+ ```typescript title="workflows/code-agent.ts" lineNumbers
64
+ import { Sandbox } from "@vercel/sandbox";
65
+ import { DurableAgent } from "@workflow/ai/agent";
66
+ import { convertToModelMessages, type UIMessage, type UIMessageChunk } from "ai";
67
+ import { getWritable } from "workflow";
68
+ import z from "zod/v4";
69
+
70
+ export async function codeAgent(messages: UIMessage[]) {
71
+ "use workflow";
72
+
73
+ let activeSandbox: Sandbox | null = null;
74
+
75
+ const agent = new DurableAgent({
76
+ model: "anthropic/claude-sonnet-4-20250514",
77
+ instructions:
78
+ "You are a coding assistant. You can create sandboxes to run code. " +
79
+ "Always create a sandbox first, then execute code in it. " +
80
+ "Clean up the sandbox when you are done.",
81
+ tools: {
82
+ createSandbox: {
83
+ description: "Create an isolated sandbox environment for running code",
84
+ inputSchema: z.object({
85
+ template: z.string().describe("The sandbox template (e.g., 'node', 'python')"),
86
+ }),
87
+ execute: async ({ template }) => {
88
+ activeSandbox = await Sandbox.create({ template }); // [!code highlight]
89
+ return { sandboxId: activeSandbox.id };
90
+ },
91
+ },
92
+ executeCode: {
93
+ description: "Execute a command in the active sandbox",
94
+ inputSchema: z.object({
95
+ command: z.string().describe("The command to execute"),
96
+ }),
97
+ execute: async ({ command }) => {
98
+ if (!activeSandbox) throw new Error("No active sandbox");
99
+ return activeSandbox.runCommand(command); // [!code highlight]
100
+ },
101
+ },
102
+ cleanupSandbox: {
103
+ description: "Destroy the active sandbox when finished",
104
+ inputSchema: z.object({}),
105
+ execute: async () => {
106
+ if (!activeSandbox) throw new Error("No active sandbox");
107
+ await activeSandbox.destroy(); // [!code highlight]
108
+ activeSandbox = null;
109
+ return { cleaned: true };
110
+ },
111
+ },
112
+ },
113
+ });
114
+
115
+ const result = await agent.stream({
116
+ messages: await convertToModelMessages(messages),
117
+ writable: getWritable<UIMessageChunk>(),
118
+ });
119
+
120
+ return { messages: result.messages };
121
+ }
122
+ ```
123
+
124
+ ## Saga Pattern for Cleanup
125
+
126
+ Combine sandbox orchestration with the [saga pattern](/docs/cookbook/common-patterns/saga) to ensure sandboxes are always cleaned up, even when a step in the middle of your pipeline fails.
127
+
128
+ The example above uses a try/catch around the command execution loop. For more complex pipelines with multiple resources (sandbox + database + external API), push compensation functions onto a stack as shown in the [saga recipe](/docs/cookbook/common-patterns/saga).
@@ -0,0 +1,5 @@
1
+ {
2
+ "title": "Cookbook",
3
+ "defaultOpen": true,
4
+ "pages": ["common-patterns", "agent-patterns", "integrations", "advanced"]
5
+ }
@@ -166,54 +166,56 @@ The Streamer interface enables real-time data streaming:
166
166
  {/* @skip-typecheck - interface definition, not runnable code */}
167
167
  ```typescript
168
168
  interface Streamer {
169
- writeToStream(
170
- name: string,
171
- runId: string,
172
- chunk: string | Uint8Array
173
- ): Promise<void>;
174
-
175
- writeToStreamMulti?(
176
- name: string,
177
- runId: string,
178
- chunks: (string | Uint8Array)[]
179
- ): Promise<void>;
180
-
181
- closeStream(
182
- name: string,
183
- runId: string
184
- ): Promise<void>;
185
-
186
- readFromStream(
187
- name: string,
188
- startIndex?: number
189
- ): Promise<ReadableStream<Uint8Array>>;
190
-
191
- listStreamsByRunId(runId: string): Promise<string[]>;
192
-
193
- /** Paginated snapshot of stream chunks. */
194
- getStreamChunks(
195
- name: string,
196
- runId: string,
197
- options?: { limit?: number; cursor?: string }
198
- ): Promise<{
199
- data: { index: number; data: Uint8Array }[];
200
- cursor: string | null;
201
- hasMore: boolean;
202
- done: boolean;
203
- }>;
204
-
205
- /** Lightweight metadata: tail index and completion flag. */
206
- getStreamInfo(
207
- name: string,
208
- runId: string
209
- ): Promise<{ tailIndex: number; done: boolean }>;
169
+ streamFlushIntervalMs?: number;
170
+
171
+ streams: {
172
+ write(
173
+ runId: string,
174
+ name: string,
175
+ chunk: string | Uint8Array
176
+ ): Promise<void>;
177
+
178
+ writeMulti?(
179
+ runId: string,
180
+ name: string,
181
+ chunks: (string | Uint8Array)[]
182
+ ): Promise<void>;
183
+
184
+ close(runId: string, name: string): Promise<void>;
185
+
186
+ get(
187
+ runId: string,
188
+ name: string,
189
+ startIndex?: number
190
+ ): Promise<ReadableStream<Uint8Array>>;
191
+
192
+ list(runId: string): Promise<string[]>;
193
+
194
+ /** Paginated snapshot of stream chunks. */
195
+ getChunks(
196
+ runId: string,
197
+ name: string,
198
+ options?: { limit?: number; cursor?: string }
199
+ ): Promise<{
200
+ data: { index: number; data: Uint8Array }[];
201
+ cursor: string | null;
202
+ hasMore: boolean;
203
+ done: boolean;
204
+ }>;
205
+
206
+ /** Lightweight metadata: tail index and completion flag. */
207
+ getInfo(
208
+ runId: string,
209
+ name: string
210
+ ): Promise<{ tailIndex: number; done: boolean }>;
211
+ };
210
212
  }
211
213
  ```
212
214
 
213
215
  Streams are identified by a combination of `runId` and `name`. Each workflow run can have multiple named streams.
214
- `writeToStreamMulti()` is an optional optimization for batching multiple writes.
216
+ `writeMulti()` is an optional optimization for batching multiple writes.
215
217
 
216
- `getStreamChunks` returns a paginated snapshot of currently available chunks (unlike `readFromStream` which returns a live `ReadableStream` that waits for new chunks). `getStreamInfo` returns the tail index (last chunk index, 0-based, or `-1` when empty) and whether the stream is complete — useful for resolving negative `startIndex` values into absolute positions.
218
+ `getChunks` returns a paginated snapshot of currently available chunks (unlike `get` which returns a live `ReadableStream` that waits for new chunks). `getInfo` returns the tail index (last chunk index, 0-based, or `-1` when empty) and whether the stream is complete — useful for resolving negative `startIndex` values into absolute positions.
217
219
 
218
220
  ## Reference Implementations
219
221
 
@@ -32,7 +32,7 @@ npx workflow web
32
32
 
33
33
  Learn more in the [Observability](/docs/observability) documentation.
34
34
 
35
- ## Testing & Performance
35
+ ## Testing & Compatibility
36
36
 
37
37
  <WorldTestingPerformance />
38
38