workflow 4.2.0-beta.69 → 4.2.0-beta.71

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.
@@ -3,7 +3,7 @@
3
3
  * similar to "stdlib" except that are not meant to be imported by users, but are instead "just available"
4
4
  * alongside user defined steps. They are used internally by the runtime
5
5
  */
6
- export declare function __builtin_response_array_buffer(res: Response): Promise<ArrayBuffer>;
7
- export declare function __builtin_response_json(res: Response): Promise<unknown>;
8
- export declare function __builtin_response_text(res: Response): Promise<string>;
6
+ export declare function __builtin_response_array_buffer(this: Request | Response): Promise<ArrayBuffer>;
7
+ export declare function __builtin_response_json(this: Request | Response): Promise<unknown>;
8
+ export declare function __builtin_response_text(this: Request | Response): Promise<string>;
9
9
  //# sourceMappingURL=builtins.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../../src/internal/builtins.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAsB,+BAA+B,CAAC,GAAG,EAAE,QAAQ,wBAGlE;AAED,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,QAAQ,oBAG1D;AAED,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,QAAQ,mBAG1D"}
1
+ {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../../src/internal/builtins.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAsB,+BAA+B,CACnD,IAAI,EAAE,OAAO,GAAG,QAAQ,wBAIzB;AAED,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,oBAGrE;AAED,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,mBAGrE"}
@@ -3,16 +3,16 @@
3
3
  * similar to "stdlib" except that are not meant to be imported by users, but are instead "just available"
4
4
  * alongside user defined steps. They are used internally by the runtime
5
5
  */
6
- export async function __builtin_response_array_buffer(res) {
6
+ export async function __builtin_response_array_buffer() {
7
7
  'use step';
8
- return res.arrayBuffer();
8
+ return this.arrayBuffer();
9
9
  }
10
- export async function __builtin_response_json(res) {
10
+ export async function __builtin_response_json() {
11
11
  'use step';
12
- return res.json();
12
+ return this.json();
13
13
  }
14
- export async function __builtin_response_text(res) {
14
+ export async function __builtin_response_text() {
15
15
  'use step';
16
- return res.text();
16
+ return this.text();
17
17
  }
18
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVpbHRpbnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW50ZXJuYWwvYnVpbHRpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7R0FJRztBQUVILE1BQU0sQ0FBQyxLQUFLLFVBQVUsK0JBQStCLENBQUMsR0FBYTtJQUNqRSxVQUFVLENBQUM7SUFDWCxPQUFPLEdBQUcsQ0FBQyxXQUFXLEVBQUUsQ0FBQztBQUMzQixDQUFDO0FBRUQsTUFBTSxDQUFDLEtBQUssVUFBVSx1QkFBdUIsQ0FBQyxHQUFhO0lBQ3pELFVBQVUsQ0FBQztJQUNYLE9BQU8sR0FBRyxDQUFDLElBQUksRUFBRSxDQUFDO0FBQ3BCLENBQUM7QUFFRCxNQUFNLENBQUMsS0FBSyxVQUFVLHVCQUF1QixDQUFDLEdBQWE7SUFDekQsVUFBVSxDQUFDO0lBQ1gsT0FBTyxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUM7QUFDcEIsQ0FBQyJ9
18
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVpbHRpbnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW50ZXJuYWwvYnVpbHRpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7R0FJRztBQUVILE1BQU0sQ0FBQyxLQUFLLFVBQVUsK0JBQStCO0lBR25ELFVBQVUsQ0FBQztJQUNYLE9BQU8sSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO0FBQzVCLENBQUM7QUFFRCxNQUFNLENBQUMsS0FBSyxVQUFVLHVCQUF1QjtJQUMzQyxVQUFVLENBQUM7SUFDWCxPQUFPLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztBQUNyQixDQUFDO0FBRUQsTUFBTSxDQUFDLEtBQUssVUFBVSx1QkFBdUI7SUFDM0MsVUFBVSxDQUFDO0lBQ1gsT0FBTyxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7QUFDckIsQ0FBQyJ9
@@ -39,12 +39,12 @@ export async function chat(messages: UIMessage[]) {
39
39
 
40
40
  const agent = new DurableAgent({
41
41
  model: "bedrock/claude-haiku-4-5-20251001-v1",
42
- system: FLIGHT_ASSISTANT_PROMPT,
42
+ instructions: FLIGHT_ASSISTANT_PROMPT,
43
43
  tools: flightBookingTools,
44
44
  });
45
45
 
46
46
  await agent.stream({
47
- messages: convertToModelMessages(messages), // [!code highlight] Full history from client
47
+ messages: await convertToModelMessages(messages), // [!code highlight] Full history from client
48
48
  writable,
49
49
  });
50
50
  }
@@ -171,7 +171,7 @@ export async function chat(initialMessages: UIMessage[]) {
171
171
 
172
172
  const { workflowRunId: runId } = getWorkflowMetadata();
173
173
  const writable = getWritable<UIMessageChunk>();
174
- const messages: ModelMessage[] = convertToModelMessages(initialMessages);
174
+ const messages: ModelMessage[] = await convertToModelMessages(initialMessages);
175
175
 
176
176
  // Write markers for initial user messages (for replay) // [!code highlight]
177
177
  for (const msg of initialMessages) { // [!code highlight]
@@ -183,7 +183,7 @@ export async function chat(initialMessages: UIMessage[]) {
183
183
 
184
184
  const agent = new DurableAgent({
185
185
  model: "bedrock/claude-haiku-4-5-20251001-v1",
186
- system: FLIGHT_ASSISTANT_PROMPT,
186
+ instructions: FLIGHT_ASSISTANT_PROMPT,
187
187
  tools: flightBookingTools,
188
188
  });
189
189
 
@@ -25,7 +25,7 @@ When you tool needs access to the full message history, you can access it via th
25
25
  ```typescript title="tools.ts" lineNumbers
26
26
  async function getWeather(
27
27
  { city }: { city: string },
28
- { messages, toolCallId }: { messages: LanguageModelV2Prompt, toolCallId: string }) { // [!code highlight]
28
+ { messages, toolCallId }: { messages: ModelMessage[], toolCallId: string }) { // [!code highlight]
29
29
  "use step";
30
30
  return `Weather in ${city} is sunny`;
31
31
  }
package/docs/ai/index.mdx CHANGED
@@ -127,11 +127,11 @@ export async function POST(req: Request) {
127
127
  const { messages }: { messages: UIMessage[] } = await req.json();
128
128
  const agent = new Agent({ // [!code highlight]
129
129
  model: gateway("bedrock/claude-4-5-haiku-20251001-v1"),
130
- system: FLIGHT_ASSISTANT_PROMPT,
130
+ instructions: FLIGHT_ASSISTANT_PROMPT,
131
131
  tools: flightBookingTools,
132
132
  });
133
- const modelMessages = convertToModelMessages(messages);
134
- const stream = agent.stream({ messages: modelMessages }); // [!code highlight]
133
+ const modelMessages = await convertToModelMessages(messages);
134
+ const stream = await agent.stream({ messages: modelMessages }); // [!code highlight]
135
135
  return createUIMessageStreamResponse({
136
136
  stream: stream.toUIMessageStream(),
137
137
  });
@@ -273,7 +273,7 @@ export async function chatWorkflow(messages: ModelMessage[]) {
273
273
  // ELSE if using a custom provider, pass the provider call as an argument:
274
274
  model: openai("gpt-5.1"), // [!code highlight]
275
275
 
276
- system: FLIGHT_ASSISTANT_PROMPT,
276
+ instructions: FLIGHT_ASSISTANT_PROMPT,
277
277
  tools: flightBookingTools,
278
278
  });
279
279
 
@@ -304,7 +304,7 @@ import { chatWorkflow } from "@/workflows/chat/workflow";
304
304
 
305
305
  export async function POST(req: Request) {
306
306
  const { messages }: { messages: UIMessage[] } = await req.json();
307
- const modelMessages = convertToModelMessages(messages);
307
+ const modelMessages = await convertToModelMessages(messages);
308
308
 
309
309
  const run = await start(chatWorkflow, [modelMessages]); // [!code highlight]
310
310
 
@@ -33,15 +33,15 @@ The `prepareStep` callback runs before each step in the agent loop. It receives
33
33
 
34
34
  ```typescript lineNumbers
35
35
  interface PrepareStepInfo {
36
- model: string | (() => Promise<LanguageModelV2>); // Current model
36
+ model: string | (() => Promise<LanguageModel>); // Current model
37
37
  stepNumber: number; // 0-indexed step count
38
38
  steps: StepResult[]; // Previous step results
39
- messages: LanguageModelV2Prompt; // Messages to be sent
39
+ messages: ModelMessage[]; // Messages to be sent
40
40
  }
41
41
 
42
42
  interface PrepareStepResult {
43
- model?: string | (() => Promise<LanguageModelV2>); // Override model
44
- messages?: LanguageModelV2Prompt; // Override messages
43
+ model?: string | (() => Promise<LanguageModel>); // Override model
44
+ messages?: ModelMessage[]; // Override messages
45
45
  }
46
46
  ```
47
47
 
@@ -65,7 +65,7 @@ export async function chat(initialMessages: ModelMessage[]) {
65
65
 
66
66
  const agent = new DurableAgent({
67
67
  model: "bedrock/claude-haiku-4-5-20251001-v1",
68
- system: FLIGHT_ASSISTANT_PROMPT,
68
+ instructions: FLIGHT_ASSISTANT_PROMPT,
69
69
  tools: flightBookingTools,
70
70
  });
71
71
 
@@ -101,7 +101,7 @@ export async function chat(initialMessages: ModelMessage[]) {
101
101
  Messages sent via `chatMessageHook.resume()` accumulate in the queue and get injected before the next step, whether that's a tool call or another LLM request.
102
102
 
103
103
  <Callout type="info">
104
- The `prepareStep` callback receives messages in `LanguageModelV2Prompt` format (with content arrays), which is the internal format used by the AI SDK.
104
+ The `prepareStep` callback receives messages in `ModelMessage[]` format (with content arrays), which is the internal format used by the AI SDK.
105
105
  </Callout>
106
106
 
107
107
  ## Combining with Multi-Turn Sessions
@@ -17,6 +17,9 @@ All the functions and primitives that come with Workflow DevKit by package.
17
17
  <Card title="workflow/next" href="/docs/api-reference/workflow-next">
18
18
  Next.js integration for Workflow DevKit that automatically configures bundling and runtime support.
19
19
  </Card>
20
+ <Card title="@workflow/serde" href="/docs/api-reference/workflow-serde">
21
+ Serialization symbols for custom class serialization in workflows.
22
+ </Card>
20
23
  <Card title="@workflow/ai" href="/docs/api-reference/workflow-ai">
21
24
  Helpers for integrating AI SDK for building AI-powered workflows.
22
25
  </Card>
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "title": "API Reference",
3
- "pages": ["...", "workflow-ai", "vitest"]
3
+ "pages": ["...", "workflow-serde", "workflow-ai", "vitest"]
4
4
  }
@@ -34,7 +34,7 @@ async function myAgent() {
34
34
 
35
35
  const agent = new DurableAgent({
36
36
  model: "anthropic/claude-haiku-4.5",
37
- system: "You are a helpful weather assistant.",
37
+ instructions: "You are a helpful weather assistant.",
38
38
  temperature: 0.7,
39
39
  tools: {
40
40
  getWeather: {
@@ -249,7 +249,7 @@ async function weatherAgentWorkflow(userQuery: string) {
249
249
  execute: getWeather,
250
250
  },
251
251
  },
252
- system: "You are a helpful weather assistant. Always provide accurate weather information.",
252
+ instructions: "You are a helpful weather assistant. Always provide accurate weather information.",
253
253
  });
254
254
 
255
255
  await agent.stream({
@@ -446,7 +446,7 @@ async function agentWithPrepareStep(userMessage: string) {
446
446
 
447
447
  const agent = new DurableAgent({
448
448
  model: "openai/gpt-4.1-mini", // Default model
449
- system: "You are a helpful assistant.",
449
+ instructions: "You are a helpful assistant.",
450
450
  });
451
451
 
452
452
  await agent.stream({
@@ -500,7 +500,7 @@ async function agentWithMessageQueue(initialMessage: string) {
500
500
 
501
501
  const agent = new DurableAgent({
502
502
  model: "anthropic/claude-haiku-4.5",
503
- system: "You are a helpful assistant.",
503
+ instructions: "You are a helpful assistant.",
504
504
  });
505
505
 
506
506
  await agent.stream({
@@ -812,7 +812,7 @@ async function agentWithUIMessages(userMessage: string) {
812
812
 
813
813
  const agent = new DurableAgent({
814
814
  model: "anthropic/claude-haiku-4.5",
815
- system: "You are a helpful assistant.",
815
+ instructions: "You are a helpful assistant.",
816
816
  });
817
817
 
818
818
  const result = await agent.stream({
@@ -76,3 +76,27 @@ const run = await start(myWorkflow, ["arg1", "arg2"], { // [!code highlight]
76
76
  deploymentId: "custom-deployment-id" // [!code highlight]
77
77
  }); // [!code highlight]
78
78
  ```
79
+
80
+ ### Using `deploymentId: "latest"`
81
+
82
+ Set `deploymentId` to `"latest"` to automatically resolve the most recent deployment for the current environment. This is useful when you want to ensure a workflow run targets the latest deployed version of your application rather than the deployment that initiated the call.
83
+
84
+ ```typescript
85
+ import { start } from "workflow/api";
86
+ import { myWorkflow } from "./workflows/my-workflow";
87
+
88
+ const run = await start(myWorkflow, ["arg1", "arg2"], { // [!code highlight]
89
+ deploymentId: "latest" // [!code highlight]
90
+ }); // [!code highlight]
91
+ ```
92
+
93
+ <Callout type="info">
94
+ The `deploymentId` option is currently a Vercel-specific feature. The `"latest"` value resolves to the most recent deployment matching your current environment — the same production target for production deployments, or the same git branch for preview deployments.
95
+ </Callout>
96
+
97
+ <Callout type="warn">
98
+ When using `deploymentId: "latest"`, the workflow run will execute on a potentially different deployment than the one calling `start()`. Be mindful of forward and backward compatibility:
99
+
100
+ - **Workflow identity**: The workflow ID is derived from the function name and file path. If the latest deployment has renamed the workflow function or moved it to a different directory, the workflow ID will no longer match and the run will fail to start.
101
+ - **Input and output compatibility**: The arguments passed to `start()` are serialized by the calling deployment but deserialized by the target deployment. Similarly, the workflow's return value is serialized by the target deployment but deserialized by the caller. If the workflow's expected arguments or return type have changed (e.g. added required fields, removed fields, or changed types), the run may fail or behave unexpectedly. Ensure that input and output schemas remain backward-compatible across deployments.
102
+ </Callout>
@@ -0,0 +1,52 @@
1
+ ---
2
+ title: "@workflow/serde"
3
+ ---
4
+
5
+ Serialization symbols for custom class serialization in Workflow DevKit.
6
+
7
+ ## Installation
8
+
9
+ ```package-install
10
+ npm i @workflow/serde
11
+ ```
12
+
13
+ ## Overview
14
+
15
+ By default, Workflow DevKit can serialize standard JavaScript types like primitives, objects, arrays, `Date`, `Map`, `Set`, and more. However, custom class instances are not serializable by default because the serialization system doesn't know how to reconstruct them.
16
+
17
+ The `@workflow/serde` package provides two symbols that allow you to define custom serialization and deserialization logic for your classes, enabling them to be passed between workflow and step functions.
18
+
19
+ ## Symbols
20
+
21
+ <Cards>
22
+ <Card href="/docs/api-reference/workflow-serde/workflow-serialize" title="WORKFLOW_SERIALIZE">
23
+ Symbol for defining how to serialize a class instance to plain data.
24
+ </Card>
25
+ <Card href="/docs/api-reference/workflow-serde/workflow-deserialize" title="WORKFLOW_DESERIALIZE">
26
+ Symbol for defining how to reconstruct a class instance from plain data.
27
+ </Card>
28
+ </Cards>
29
+
30
+ ## Quick Example
31
+
32
+ {/* @expect-error:2351 */}
33
+
34
+ ```typescript lineNumbers
35
+ import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde";
36
+
37
+ class Point {
38
+ constructor(public x: number, public y: number) {}
39
+
40
+ static [WORKFLOW_SERIALIZE](instance: Point) {
41
+ return { x: instance.x, y: instance.y };
42
+ }
43
+
44
+ static [WORKFLOW_DESERIALIZE](data: { x: number; y: number }) {
45
+ return new Point(data.x, data.y);
46
+ }
47
+ }
48
+ ```
49
+
50
+ <Callout>
51
+ For a complete guide on custom class serialization, see the [Serialization documentation](/docs/foundations/serialization#custom-class-serialization).
52
+ </Callout>
@@ -0,0 +1,3 @@
1
+ {
2
+ "pages": ["...", "workflow-serialize", "workflow-deserialize"]
3
+ }
@@ -0,0 +1,70 @@
1
+ ---
2
+ title: WORKFLOW_DESERIALIZE
3
+ ---
4
+
5
+ A symbol used to define custom deserialization for user-defined class instances. The static method should accept serialized data and return a new class instance.
6
+
7
+ ## Usage
8
+
9
+ {/* @expect-error:2351 */}
10
+
11
+ ```typescript lineNumbers
12
+ import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde";
13
+
14
+ class Point {
15
+ constructor(public x: number, public y: number) {}
16
+
17
+ static [WORKFLOW_SERIALIZE](instance: Point) {
18
+ return { x: instance.x, y: instance.y };
19
+ }
20
+
21
+ static [WORKFLOW_DESERIALIZE](data: { x: number; y: number }) {
22
+ return new Point(data.x, data.y);
23
+ }
24
+ }
25
+ ```
26
+
27
+ ## API Signature
28
+
29
+ {/* @skip-typecheck */}
30
+
31
+ ```typescript
32
+ static [WORKFLOW_DESERIALIZE](data: SerializableData): T
33
+ ```
34
+
35
+ ### Parameters
36
+
37
+ <TSDoc
38
+ definition={`
39
+ interface Parameters {
40
+ /**
41
+ * The serialized data to reconstruct into a class instance.
42
+ * This is the same data that was returned by WORKFLOW_SERIALIZE.
43
+ */
44
+ data: SerializableData;
45
+ }
46
+ export default Parameters;`}
47
+ />
48
+
49
+ ### Returns
50
+
51
+ The method should return a new instance of the class, reconstructed from the serialized data.
52
+
53
+ ## Requirements
54
+
55
+ <Callout type="warn">
56
+ The method must be implemented as a **static** method on the class. Instance methods are not supported.
57
+ </Callout>
58
+
59
+ - Both `WORKFLOW_SERIALIZE` and `WORKFLOW_DESERIALIZE` must be implemented together
60
+ - The method receives the exact data that was returned by `WORKFLOW_SERIALIZE`
61
+ - If `WORKFLOW_SERIALIZE` returns complex types (like `Map` or `Date`), they will be properly deserialized before being passed to this method
62
+
63
+ <Callout type="warn">
64
+ This method runs inside the workflow context and is subject to the same constraints as `"use workflow"` functions:
65
+ - No Node.js-specific APIs (like `fs`, `path`, `crypto`, etc.)
66
+ - No non-deterministic operations (like `Math.random()` or `Date.now()`)
67
+ - No external network calls
68
+
69
+ Keep this method simple and focused on reconstructing the instance from the provided data.
70
+ </Callout>
@@ -0,0 +1,75 @@
1
+ ---
2
+ title: WORKFLOW_SERIALIZE
3
+ ---
4
+
5
+ A symbol used to define custom serialization for user-defined class instances. The static method should accept an instance and return serializable data.
6
+
7
+ ## Usage
8
+
9
+ {/* @expect-error:2351 */}
10
+
11
+ ```typescript lineNumbers
12
+ import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde";
13
+
14
+ class Point {
15
+ constructor(public x: number, public y: number) {}
16
+
17
+ static [WORKFLOW_SERIALIZE](instance: Point) {
18
+ return { x: instance.x, y: instance.y };
19
+ }
20
+
21
+ static [WORKFLOW_DESERIALIZE](data: { x: number; y: number }) {
22
+ return new Point(data.x, data.y);
23
+ }
24
+ }
25
+ ```
26
+
27
+ ## API Signature
28
+
29
+ {/* @skip-typecheck */}
30
+
31
+ ```typescript
32
+ static [WORKFLOW_SERIALIZE](instance: T): SerializableData
33
+ ```
34
+
35
+ ### Parameters
36
+
37
+ <TSDoc
38
+ definition={`
39
+ interface Parameters {
40
+ /**
41
+ * The class instance to serialize.
42
+ */
43
+ instance: T;
44
+ }
45
+ export default Parameters;`}
46
+ />
47
+
48
+ ### Returns
49
+
50
+ The method should return serializable data. This can be:
51
+
52
+ - Primitives (`string`, `number`, `boolean`, `null`, `undefined`, `bigint`)
53
+ - Plain objects with serializable values
54
+ - Arrays of serializable values
55
+ - Built-in serializable types (`Date`, `Map`, `Set`, `RegExp`, `URL`, etc.)
56
+ - Other custom classes that implement serialization
57
+
58
+ ## Requirements
59
+
60
+ <Callout type="warn">
61
+ The method must be implemented as a **static** method on the class. Instance methods are not supported.
62
+ </Callout>
63
+
64
+ - Both `WORKFLOW_SERIALIZE` and `WORKFLOW_DESERIALIZE` must be implemented together
65
+ - The returned data must itself be serializable
66
+ - The SWC compiler plugin automatically detects and registers classes that implement these symbols
67
+
68
+ <Callout type="warn">
69
+ This method runs inside the workflow context and is subject to the same constraints as `"use workflow"` functions:
70
+ - No Node.js-specific APIs (like `fs`, `path`, `crypto`, etc.)
71
+ - No non-deterministic operations (like `Math.random()` or `Date.now()`)
72
+ - No external network calls
73
+
74
+ Keep this method simple and focused on extracting data from the instance.
75
+ </Callout>
@@ -6,6 +6,7 @@ summary: Deploy workflows to Vercel with fully-managed storage, queuing, and aut
6
6
  prerequisites:
7
7
  - /docs/deploying
8
8
  related:
9
+ - /docs/how-it-works/encryption
9
10
  - /docs/deploying/world/local-world
10
11
  - /docs/deploying/world/postgres-world
11
12
  ---
@@ -125,7 +126,7 @@ This ensures long-running workflows complete reliably without being affected by
125
126
 
126
127
  The Vercel World uses Vercel's infrastructure for workflow execution:
127
128
 
128
- - **Storage** - Workflow data is stored in Vercel's cloud with automatic replication and encryption
129
+ - **Storage** - Workflow data is stored in Vercel's cloud with automatic replication and [end-to-end encryption](/docs/how-it-works/encryption)
129
130
  - **Queuing** - Steps are distributed across serverless functions with automatic retries
130
131
  - **Authentication** - OIDC tokens provide secure, automatic authentication
131
132
 
@@ -252,6 +252,10 @@ export async function processOrder(orderId: string) {
252
252
 
253
253
  With background execution, the parent workflow continues immediately after starting the child. The child workflow runs independently with its own event log and can be monitored separately using the returned `runId`.
254
254
 
255
+ <Callout type="info">
256
+ If you want the child workflow to run on the latest deployment rather than the current one, you can pass [`deploymentId: "latest"`](/docs/api-reference/workflow-api/start#using-deploymentid-latest) in the `start()` options. This is currently a Vercel-specific feature. Be aware that the child workflow's function name, file path, argument types, and return type must remain compatible across deployments — renaming the function or changing its location will change the workflow ID, and modifying expected inputs or outputs can cause serialization failures.
257
+ </Callout>
258
+
255
259
  **Choose direct await when:**
256
260
  - The parent needs the child's result before continuing
257
261
  - You want a single, unified event log
@@ -56,6 +56,10 @@ These types have special handling and are explained in detail in the sections be
56
56
  - `ReadableStream<Serializable>`
57
57
  - `WritableStream<Serializable>`
58
58
 
59
+ **Custom Classes:**
60
+
61
+ - Class instances that implement [`WORKFLOW_SERIALIZE` and `WORKFLOW_DESERIALIZE`](#custom-class-serialization)
62
+
59
63
  ## Streaming
60
64
 
61
65
  `ReadableStream` and `WritableStream` are supported as serializable types with special handling. These streams can be passed between workflow and step functions while maintaining their streaming capabilities.
@@ -121,6 +125,213 @@ export async function fetch(...args: Parameters<typeof globalThis.fetch>) {
121
125
 
122
126
  This allows you to make HTTP requests directly in workflow functions while maintaining deterministic replay behavior through automatic caching.
123
127
 
128
+ ## Custom Class Serialization
129
+
130
+ By default, custom class instances cannot be serialized because the serialization system doesn't know how to reconstruct them. You can make your classes serializable by implementing two static methods using special symbols from the `@workflow/serde` package.
131
+
132
+ ### Basic Example
133
+
134
+ {/* @expect-error:2351 */}
135
+
136
+ ```typescript title="workflows/custom-class.ts" lineNumbers
137
+ import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde"; // [!code highlight]
138
+
139
+ class Point {
140
+ constructor(
141
+ public x: number,
142
+ public y: number
143
+ ) {}
144
+
145
+ // Define how to serialize an instance to plain data
146
+ static [WORKFLOW_SERIALIZE](instance: Point) { // [!code highlight]
147
+ return { x: instance.x, y: instance.y }; // [!code highlight]
148
+ } // [!code highlight]
149
+
150
+ // Define how to reconstruct an instance from plain data
151
+ static [WORKFLOW_DESERIALIZE](data: { x: number; y: number }) { // [!code highlight]
152
+ return new Point(data.x, data.y); // [!code highlight]
153
+ } // [!code highlight]
154
+ }
155
+ ```
156
+
157
+ Once you've implemented these methods, instances of your class can be passed between workflow and step functions:
158
+
159
+ {/* @expect-error:2351 */}
160
+
161
+ ```typescript title="workflows/geometry.ts" lineNumbers
162
+ import { Point } from "./custom-class";
163
+
164
+ export async function geometryWorkflow() {
165
+ "use workflow";
166
+
167
+ const point = new Point(10, 20);
168
+ // Point is serialized automatically
169
+ const doubled = await doublePoint(point); // [!code highlight]
170
+
171
+ console.log(doubled.x, doubled.y); // 20, 40
172
+ return doubled;
173
+ }
174
+
175
+ async function doublePoint(point: Point) {
176
+ "use step";
177
+ // Returns a new Point instance
178
+ return new Point(point.x * 2, point.y * 2); // [!code highlight]
179
+ }
180
+ ```
181
+
182
+ ### How It Works
183
+
184
+ 1. **`WORKFLOW_SERIALIZE`**: A static method that receives a class instance and returns serializable data (primitives, plain objects, arrays, etc.)
185
+
186
+ 2. **`WORKFLOW_DESERIALIZE`**: A static method that receives the serialized data and returns a new class instance
187
+
188
+ 3. **Automatic Registration**: The SWC compiler plugin automatically detects classes that implement these symbols and registers them for serialization
189
+
190
+ ### Requirements
191
+
192
+ <Callout type="warn">
193
+ Both methods must be implemented as **static** methods on the class. Instance methods are not supported.
194
+ </Callout>
195
+
196
+ - The data returned by `WORKFLOW_SERIALIZE` must itself be serializable (see [Supported Serializable Types](#supported-serializable-types))
197
+ - Both symbols must be implemented together - a class with only one will not be serializable
198
+
199
+ <Callout type="warn">
200
+ The `WORKFLOW_SERIALIZE` and `WORKFLOW_DESERIALIZE` methods run inside the workflow context and are subject to the same constraints as `"use workflow"` functions. This means:
201
+ - No Node.js-specific APIs (like `fs`, `path`, `crypto`, etc.)
202
+ - No non-deterministic operations (like `Math.random()` or `Date.now()`)
203
+ - No external network calls
204
+
205
+ Keep these methods simple and focused on data transformation only.
206
+ </Callout>
207
+
208
+ ### Complex Example
209
+
210
+ A class that uses Node.js APIs or other non-deterministic operations cannot be used directly inside a workflow function. The recommended approach is to make the class workflow-compatible by adding `"use step"` to its instance methods. The SWC compiler will strip the method bodies from the workflow bundle and replace them with proxy functions that invoke the method as a step — with full Node.js runtime access. The `this` context (the class instance) is automatically serialized and deserialized across the workflow/step boundary.
211
+
212
+ This requires the class to implement `WORKFLOW_SERIALIZE` and `WORKFLOW_DESERIALIZE`, so that the instance can be passed to the step execution context.
213
+
214
+ {/* @expect-error:2351 */}
215
+
216
+ ```typescript title="workflows/order.ts" lineNumbers
217
+ import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde"; // [!code highlight]
218
+ import { db } from "../lib/db";
219
+
220
+ class Order {
221
+ constructor(
222
+ public id: string,
223
+ public items: Map<string, number>,
224
+ public createdAt: Date
225
+ ) {}
226
+
227
+ // Custom serialization — data must be serializable types
228
+ static [WORKFLOW_SERIALIZE](instance: Order) { // [!code highlight]
229
+ return { // [!code highlight]
230
+ id: instance.id, // [!code highlight]
231
+ items: instance.items, // Map is serializable // [!code highlight]
232
+ createdAt: instance.createdAt, // Date is serializable // [!code highlight]
233
+ }; // [!code highlight]
234
+ } // [!code highlight]
235
+
236
+ static [WORKFLOW_DESERIALIZE](data: { // [!code highlight]
237
+ id: string; // [!code highlight]
238
+ items: Map<string, number>; // [!code highlight]
239
+ createdAt: Date; // [!code highlight]
240
+ }) { // [!code highlight]
241
+ return new Order(data.id, data.items, data.createdAt); // [!code highlight]
242
+ } // [!code highlight]
243
+
244
+ // Methods without "use step" run in the workflow context
245
+ // and must follow the same constraints as workflow functions
246
+ total(): number {
247
+ let sum = 0;
248
+ for (const quantity of this.items.values()) {
249
+ sum += quantity;
250
+ }
251
+ return sum;
252
+ }
253
+
254
+ // Instance methods with "use step" run as step functions
255
+ // with full Node.js access — `this` is automatically serialized
256
+ async save(): Promise<void> {
257
+ "use step"; // [!code highlight]
258
+ await db.orders.insert({ // [!code highlight]
259
+ id: this.id, // [!code highlight]
260
+ items: Object.fromEntries(this.items), // [!code highlight]
261
+ createdAt: this.createdAt, // [!code highlight]
262
+ }); // [!code highlight]
263
+ }
264
+
265
+ async sendConfirmation(email: string): Promise<string> {
266
+ "use step"; // [!code highlight]
267
+ const res = await fetch("https://api.example.com/email", { // [!code highlight]
268
+ method: "POST", // [!code highlight]
269
+ body: JSON.stringify({ // [!code highlight]
270
+ to: email, // [!code highlight]
271
+ orderId: this.id, // [!code highlight]
272
+ itemCount: this.items.size, // [!code highlight]
273
+ }), // [!code highlight]
274
+ }); // [!code highlight]
275
+ const { messageId } = await res.json();
276
+ return messageId;
277
+ }
278
+ }
279
+ ```
280
+
281
+ The class can then be used naturally inside a workflow function. Instance methods marked with `"use step"` are each executed as a step — with automatic caching, retry semantics, and full Node.js runtime access. Methods _without_ `"use step"` run directly in the workflow context, so they must follow the same constraints as workflow functions:
282
+
283
+ {/* @expect-error:2693 */}
284
+
285
+ ```typescript title="workflows/process-order.ts" lineNumbers
286
+ export async function processOrderWorkflow(
287
+ orderId: string,
288
+ items: Map<string, number>,
289
+ email: string
290
+ ) {
291
+ "use workflow";
292
+
293
+ const order = new Order(orderId, items, new Date()); // [!code highlight]
294
+
295
+ // Runs in the workflow context — no "use step" needed
296
+ const itemCount = order.total(); // [!code highlight]
297
+
298
+ // Each "use step" instance method call runs as a separate step
299
+ await order.save(); // [!code highlight]
300
+ const messageId = await order.sendConfirmation(email); // [!code highlight]
301
+
302
+ return { orderId, itemCount, messageId };
303
+ }
304
+ ```
305
+
306
+ Note that [pass-by-value semantics](#pass-by-value-semantics) also apply to the `this` context of `"use step"` instance methods. Modifying instance properties inside a step method will not affect the original instance in the workflow. If you need to update instance state, return `this` from the step method and re-assign the variable in the workflow:
307
+
308
+ {/* @expect-error:2351 */}
309
+
310
+ ```typescript title="workflows/order.ts" lineNumbers
311
+ export class Order {
312
+ // ...
313
+
314
+ async addItem(name: string, quantity: number): Promise<Order> {
315
+ "use step";
316
+ this.items.set(name, quantity);
317
+ return this; // [!code highlight]
318
+ }
319
+ }
320
+ ```
321
+
322
+ {/* @expect-error:2693,2552,2304 */}
323
+
324
+ ```typescript title="workflows/process-order.ts" lineNumbers
325
+ export async function processOrderWorkflow() {
326
+ "use workflow";
327
+
328
+ let order = new Order(orderId, items, new Date());
329
+
330
+ // Re-assign to capture the updated instance
331
+ order = await order.addItem("Widget", 3); // [!code highlight]
332
+ }
333
+ ```
334
+
124
335
  ## Pass-by-Value Semantics
125
336
 
126
337
  **Parameters are passed by value, not by reference.** Steps receive deserialized copies of data. Mutations inside a step won't affect the original in the workflow.
@@ -0,0 +1,93 @@
1
+ ---
2
+ title: Encryption
3
+ description: Learn how Workflow DevKit encrypts user data end-to-end in the event log.
4
+ type: conceptual
5
+ summary: Understand how workflow and step data is encrypted at rest.
6
+ prerequisites:
7
+ - /docs/how-it-works/event-sourcing
8
+ related:
9
+ - /docs/observability
10
+ - /docs/deploying/world/vercel-world
11
+ ---
12
+
13
+ <Callout>
14
+ This guide explains how Workflow DevKit encrypts user data in the event log. Understanding these details is not required to use workflows — encryption is automatic and requires no code changes. For getting started, see the [getting started](/docs/getting-started) guides for your framework.
15
+ </Callout>
16
+
17
+ Workflow DevKit supports automatic end-to-end encryption of all user data before it is written to the event log. When a `World` implementation provides encryption support, it is safe to pass sensitive data — such as API keys, tokens, or user credentials — as workflow inputs, step arguments, and return values. The storage backend only ever sees ciphertext.
18
+
19
+ Encryption support varies by `World` implementation. See the [Worlds](/worlds) page to check which worlds support this feature. `World` implementations opt into encryption by providing a `getEncryptionKeyForRun()` method — the core runtime will use it automatically when present.
20
+
21
+ ## What Is Encrypted
22
+
23
+ All user data flowing through the event log is encrypted:
24
+
25
+ - **Workflow inputs** — arguments passed when starting a workflow
26
+ - **Workflow return values** — the final output of a workflow
27
+ - **Step inputs** — arguments passed to step functions
28
+ - **Step return values** — the result returned by step functions
29
+ - **Hook metadata** — data attached when creating a hook
30
+ - **Hook payloads** — data received by hooks and webhooks
31
+ - **Stream data** — each frame in a `ReadableStream` or `WritableStream`
32
+
33
+ Metadata such as workflow names, step names, entity IDs, timestamps, and lifecycle states are **not** encrypted. This allows the observability tools to display run structure and timelines without requiring decryption.
34
+
35
+ ## How It Works
36
+
37
+ ### Key Management
38
+
39
+ Each workflow run is encrypted with its own unique key, provided by the `World` implementation via `getEncryptionKeyForRun()`. How the key is generated and stored is up to the `World`.
40
+
41
+ For example, the [Vercel World](/docs/deploying/world/vercel-world) provides unique keys per run and execution environment, ensuring that a given run can only decrypt data from that run itself.
42
+
43
+ ### Encryption Algorithm
44
+
45
+ Data is encrypted using **AES-256-GCM** via the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API):
46
+
47
+ - A random 12-byte nonce is generated for each encryption operation
48
+ - The GCM authentication tag provides integrity verification — any tampering with the ciphertext is detected
49
+ - The same plaintext produces different ciphertext each time due to the random nonce
50
+
51
+ ## Decrypting Data
52
+
53
+ When viewing workflow runs through the observability tools, encrypted fields display as locked placeholders until you explicitly choose to decrypt them.
54
+
55
+ ### Permissions
56
+
57
+ Decryption access is controlled by the `World` implementation. On Vercel, decryption follows the same permissions model as project environment variables — if you don't have permission to view environment variable values for a project, you won't be able to decrypt workflow data either. Each decryption request is recorded in your [Vercel audit log](https://vercel.com/docs/audit-log), giving your team full visibility into when and by whom workflow data was accessed.
58
+
59
+ ### Web Dashboard
60
+
61
+ Click the **Decrypt** button in the run detail panel to decrypt all data fields. Decryption happens entirely in the browser via the Web Crypto API — the observability server retrieves the encryption key but never sees your plaintext data.
62
+
63
+ ### CLI
64
+
65
+ Add the `--decrypt` flag to any `inspect` command:
66
+
67
+ ```bash
68
+ # Inspect a specific run
69
+ npx workflow inspect run <run-id> --decrypt
70
+
71
+ # Inspect a specific step
72
+ npx workflow inspect step <step-id> --run <run-id> --decrypt
73
+
74
+ # List events for a run
75
+ npx workflow inspect events --run <run-id> --decrypt
76
+
77
+ # Inspect a specific stream
78
+ npx workflow inspect stream <stream-id> --run <run-id> --decrypt
79
+ ```
80
+
81
+ Without `--decrypt`, encrypted fields display as `🔒 Encrypted` placeholders.
82
+
83
+ ## Custom World Implementations
84
+
85
+ The core runtime encrypts data automatically when the `World` implementation provides a `getEncryptionKeyForRun()` method. This method receives the run ID and returns the raw encryption key bytes.
86
+
87
+ To add encryption support to a custom `World`:
88
+
89
+ 1. Implement `getEncryptionKeyForRun(runId: string)` on your `World` class
90
+ 2. Return the raw 32-byte key as a `Uint8Array` — the core runtime uses it for AES-256-GCM operations
91
+ 3. Ensure the same key is returned for the same run ID across invocations (for decryption during replay)
92
+
93
+ The [Vercel World](/docs/deploying/world/vercel-world) implementation uses HKDF derivation from a deployment-scoped key, but any consistent key management scheme will work.
@@ -4,7 +4,8 @@
4
4
  "understanding-directives",
5
5
  "code-transform",
6
6
  "framework-integrations",
7
- "event-sourcing"
7
+ "event-sourcing",
8
+ "encryption"
8
9
  ],
9
10
  "defaultOpen": false
10
11
  }
@@ -7,6 +7,7 @@ prerequisites:
7
7
  - /docs/foundations
8
8
  related:
9
9
  - /docs/how-it-works/event-sourcing
10
+ - /docs/how-it-works/encryption
10
11
  ---
11
12
 
12
13
  Workflow DevKit provides powerful tools to inspect, monitor, and debug your workflows through the CLI and Web UI. These tools allow you to inspect workflow runs, steps, webhooks, [events](/docs/how-it-works/event-sourcing), and stream output.
@@ -60,3 +61,5 @@ To inspect workflows running on Vercel, ensure you're logged in to the Vercel CL
60
61
  # Inspect workflows running on Vercel
61
62
  npx workflow inspect runs --backend vercel
62
63
  ```
64
+
65
+ When deployed to Vercel, workflow data is [encrypted end-to-end](/docs/how-it-works/encryption). Encrypted fields display as locked placeholders until you choose to decrypt them using the **Decrypt** button in the web UI or the `--decrypt` flag in the CLI.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workflow",
3
- "version": "4.2.0-beta.69",
3
+ "version": "4.2.0-beta.71",
4
4
  "description": "Workflow DevKit - Build durable, resilient, and observable workflows",
5
5
  "main": "dist/typescript-plugin.cjs",
6
6
  "type": "module",
@@ -52,17 +52,17 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "ms": "2.1.3",
55
- "@workflow/astro": "4.0.0-beta.43",
56
- "@workflow/cli": "4.2.0-beta.69",
57
- "@workflow/core": "4.2.0-beta.69",
55
+ "@workflow/astro": "4.0.0-beta.45",
56
+ "@workflow/cli": "4.2.0-beta.71",
57
+ "@workflow/core": "4.2.0-beta.71",
58
58
  "@workflow/errors": "4.1.0-beta.18",
59
59
  "@workflow/typescript-plugin": "4.0.1-beta.5",
60
- "@workflow/next": "4.0.1-beta.65",
61
- "@workflow/nest": "0.0.0-beta.18",
62
- "@workflow/nitro": "4.0.1-beta.64",
63
- "@workflow/nuxt": "4.0.1-beta.53",
64
- "@workflow/sveltekit": "4.0.0-beta.58",
65
- "@workflow/rollup": "4.0.0-beta.26"
60
+ "@workflow/next": "4.0.1-beta.67",
61
+ "@workflow/nest": "0.0.0-beta.20",
62
+ "@workflow/nitro": "4.0.1-beta.66",
63
+ "@workflow/nuxt": "4.0.1-beta.55",
64
+ "@workflow/sveltekit": "4.0.0-beta.60",
65
+ "@workflow/rollup": "4.0.0-beta.28"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@types/ms": "2.1.0",