workflow 5.0.0-beta.47 → 5.0.0-beta.48

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.
@@ -1,2 +1,2 @@
1
- export { EntityConflictError, HookConflictError, HookNotFoundError, PreconditionFailedError, RunExpiredError, RunNotSupportedError, StepNotRegisteredError, ThrottleError, TooEarlyError, WorkflowError, WorkflowNotRegisteredError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, WorkflowWorldError, } from '@workflow/errors';
1
+ export { EntityConflictError, HookConflictError, HookNotFoundError, PreconditionFailedError, RunExpiredError, RunNotSupportedError, StepNotRegisteredError, StreamError, ThrottleError, TooEarlyError, WorkflowError, WorkflowNotRegisteredError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, WorkflowWorldError, } from '@workflow/errors';
2
2
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/internal/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,uBAAuB,EACvB,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,aAAa,EACb,aAAa,EACb,aAAa,EACb,0BAA0B,EAC1B,yBAAyB,EACzB,sBAAsB,EACtB,4BAA4B,EAC5B,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/internal/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,uBAAuB,EACvB,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,WAAW,EACX,aAAa,EACb,aAAa,EACb,aAAa,EACb,0BAA0B,EAC1B,yBAAyB,EACzB,sBAAsB,EACtB,4BAA4B,EAC5B,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC"}
@@ -1,2 +1,2 @@
1
- export { EntityConflictError, HookConflictError, HookNotFoundError, PreconditionFailedError, RunExpiredError, RunNotSupportedError, StepNotRegisteredError, ThrottleError, TooEarlyError, WorkflowError, WorkflowNotRegisteredError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, WorkflowWorldError, } from '@workflow/errors';
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3JzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2ludGVybmFsL2Vycm9ycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsbUJBQW1CLEVBQ25CLGlCQUFpQixFQUNqQixpQkFBaUIsRUFDakIsdUJBQXVCLEVBQ3ZCLGVBQWUsRUFDZixvQkFBb0IsRUFDcEIsc0JBQXNCLEVBQ3RCLGFBQWEsRUFDYixhQUFhLEVBQ2IsYUFBYSxFQUNiLDBCQUEwQixFQUMxQix5QkFBeUIsRUFDekIsc0JBQXNCLEVBQ3RCLDRCQUE0QixFQUM1Qix3QkFBd0IsRUFDeEIsb0JBQW9CLEVBQ3BCLGtCQUFrQixHQUNuQixNQUFNLGtCQUFrQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IHtcbiAgRW50aXR5Q29uZmxpY3RFcnJvcixcbiAgSG9va0NvbmZsaWN0RXJyb3IsXG4gIEhvb2tOb3RGb3VuZEVycm9yLFxuICBQcmVjb25kaXRpb25GYWlsZWRFcnJvcixcbiAgUnVuRXhwaXJlZEVycm9yLFxuICBSdW5Ob3RTdXBwb3J0ZWRFcnJvcixcbiAgU3RlcE5vdFJlZ2lzdGVyZWRFcnJvcixcbiAgVGhyb3R0bGVFcnJvcixcbiAgVG9vRWFybHlFcnJvcixcbiAgV29ya2Zsb3dFcnJvcixcbiAgV29ya2Zsb3dOb3RSZWdpc3RlcmVkRXJyb3IsXG4gIFdvcmtmbG93UnVuQ2FuY2VsbGVkRXJyb3IsXG4gIFdvcmtmbG93UnVuRmFpbGVkRXJyb3IsXG4gIFdvcmtmbG93UnVuTm90Q29tcGxldGVkRXJyb3IsXG4gIFdvcmtmbG93UnVuTm90Rm91bmRFcnJvcixcbiAgV29ya2Zsb3dSdW50aW1lRXJyb3IsXG4gIFdvcmtmbG93V29ybGRFcnJvcixcbn0gZnJvbSAnQHdvcmtmbG93L2Vycm9ycyc7XG4iXX0=
1
+ export { EntityConflictError, HookConflictError, HookNotFoundError, PreconditionFailedError, RunExpiredError, RunNotSupportedError, StepNotRegisteredError, StreamError, ThrottleError, TooEarlyError, WorkflowError, WorkflowNotRegisteredError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, WorkflowWorldError, } from '@workflow/errors';
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3JzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2ludGVybmFsL2Vycm9ycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsbUJBQW1CLEVBQ25CLGlCQUFpQixFQUNqQixpQkFBaUIsRUFDakIsdUJBQXVCLEVBQ3ZCLGVBQWUsRUFDZixvQkFBb0IsRUFDcEIsc0JBQXNCLEVBQ3RCLFdBQVcsRUFDWCxhQUFhLEVBQ2IsYUFBYSxFQUNiLGFBQWEsRUFDYiwwQkFBMEIsRUFDMUIseUJBQXlCLEVBQ3pCLHNCQUFzQixFQUN0Qiw0QkFBNEIsRUFDNUIsd0JBQXdCLEVBQ3hCLG9CQUFvQixFQUNwQixrQkFBa0IsR0FDbkIsTUFBTSxrQkFBa0IsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCB7XG4gIEVudGl0eUNvbmZsaWN0RXJyb3IsXG4gIEhvb2tDb25mbGljdEVycm9yLFxuICBIb29rTm90Rm91bmRFcnJvcixcbiAgUHJlY29uZGl0aW9uRmFpbGVkRXJyb3IsXG4gIFJ1bkV4cGlyZWRFcnJvcixcbiAgUnVuTm90U3VwcG9ydGVkRXJyb3IsXG4gIFN0ZXBOb3RSZWdpc3RlcmVkRXJyb3IsXG4gIFN0cmVhbUVycm9yLFxuICBUaHJvdHRsZUVycm9yLFxuICBUb29FYXJseUVycm9yLFxuICBXb3JrZmxvd0Vycm9yLFxuICBXb3JrZmxvd05vdFJlZ2lzdGVyZWRFcnJvcixcbiAgV29ya2Zsb3dSdW5DYW5jZWxsZWRFcnJvcixcbiAgV29ya2Zsb3dSdW5GYWlsZWRFcnJvcixcbiAgV29ya2Zsb3dSdW5Ob3RDb21wbGV0ZWRFcnJvcixcbiAgV29ya2Zsb3dSdW5Ob3RGb3VuZEVycm9yLFxuICBXb3JrZmxvd1J1bnRpbWVFcnJvcixcbiAgV29ya2Zsb3dXb3JsZEVycm9yLFxufSBmcm9tICdAd29ya2Zsb3cvZXJyb3JzJztcbiJdfQ==
@@ -2,7 +2,7 @@
2
2
  title: Analytics
3
3
  description: Metadata-only read APIs for runs, steps, events, hooks, waits, and attributes, backed by the observability pipeline.
4
4
  type: reference
5
- summary: "Interfaces: world.analytics.runs, .attributes, .steps, .events, .hooks, .waits. Metadata-only listings with plan-based lookback windows; filter runs by attribute key=value."
5
+ summary: "Interfaces: world.analytics.runs, .attributes, .steps, .events, .hooks, .waits. Metadata-only listings with plan-based lookback windows; filter runs by attribute key=value. Page limits are 1000 run-scoped, 100 cross-run."
6
6
  prerequisites:
7
7
  - /docs/api-reference/workflow-runtime/get-world
8
8
  related:
@@ -13,7 +13,8 @@ keywords:
13
13
  - analytics.runs
14
14
  - analytics.attributes
15
15
  - attribute filter
16
- - listValues
16
+ - getMany
17
+ - pagination limit
17
18
  - lookback window
18
19
  - observability-upgrade-required
19
20
  - pageInfo
@@ -22,9 +23,10 @@ keywords:
22
23
 
23
24
  `world.analytics` is an optional, read-only namespace for observability surfaces: dashboards, command-line interface (CLI) tools, and admin tools that list large numbers of runs without touching payload data.
24
25
 
25
- For observability and inspection listings, prefer this namespace over
26
- [`world.runs.list()`](/docs/api-reference/workflow-runtime/world/storage#runslist).
27
- The storage API remains available for operational and payload-bearing reads.
26
+ Prefer this namespace for observability: listing, filtering, and inspecting
27
+ workflow state. Use the [Storage](/docs/api-reference/workflow-runtime/world/storage)
28
+ API for payload-bearing reads, and for anything operational that has to see the
29
+ canonical, up-to-the-moment record.
28
30
 
29
31
  It differs from [Storage](/docs/api-reference/workflow-runtime/world/storage) in two ways:
30
32
 
@@ -108,19 +110,190 @@ for (const { key, runCount, lastSeenAt } of page.data) {
108
110
 
109
111
  ---
110
112
 
111
- ## analytics.steps, analytics.events, analytics.hooks, analytics.waits
113
+ ## analytics.steps
112
114
 
113
- Run-scoped listings mirroring their [Storage](/docs/api-reference/workflow-runtime/world/storage) counterparts, minus payload data:
115
+ Run-scoped step listings mirroring their [Storage](/docs/api-reference/workflow-runtime/world/storage) counterparts, minus payload data.
116
+
117
+ ### steps.list()
118
+
119
+ ```typescript lineNumbers
120
+ const steps = await world.analytics.steps.list({
121
+ runId,
122
+ pagination: { limit: 200, sortOrder: "asc" },
123
+ });
124
+ ```
125
+
126
+ | Parameter | Type | Description |
127
+ |-----------|------|-------------|
128
+ | `params.runId` | `string` | Required. The run to list steps for |
129
+ | `params.pagination` | `PaginationOptions` | Cursor pagination, `limit` up to 1000 |
130
+
131
+ **Returns:** `PaginatedResponse<AnalyticsStep>`. Each step includes `stepId`, `stepName`, `status`, `attempt`, lifecycle timestamps, `errorCode`, and the `computeInstanceId` of the latest attempt.
132
+
133
+ ### steps.get()
134
+
135
+ ```typescript lineNumbers
136
+ const step = await world.analytics.steps.get(runId, stepId);
137
+ ```
138
+
139
+ **Returns:** `AnalyticsStep`. A step id is only unique within its run, so both arguments are required.
140
+
141
+ ---
142
+
143
+ ## analytics.events
144
+
145
+ ### events.list()
146
+
147
+ ```typescript lineNumbers
148
+ const events = await world.analytics.events.list({
149
+ runId,
150
+ eventType: "step_failed", // [!code highlight]
151
+ pagination: { limit: 1000 },
152
+ });
153
+ ```
154
+
155
+ | Parameter | Type | Description |
156
+ |-----------|------|-------------|
157
+ | `params.runId` | `string` | Required. The run to list events for |
158
+ | `params.eventType` | `string` | One event type, for example `run_failed` or `step_retrying` |
159
+ | `params.correlationId` | `string` | Narrow to one entity: a step, hook, wait, or attribute id |
160
+ | `params.pagination` | `PaginationOptions` | Cursor pagination, `limit` up to 1000 |
161
+
162
+ **Returns:** `PaginatedResponse<AnalyticsEvent>`. Each event includes `eventId`, `eventType`, `correlationId`, `stepName`, `createdAt`, and provenance fields (`region`, `requestId`, `computeInstanceId`).
163
+
164
+ Pass a step id as `correlationId` to build that step's timeline: `step_created` through `step_completed`, `step_failed`, or `step_retrying`.
165
+
166
+ ### events.get()
167
+
168
+ ```typescript lineNumbers
169
+ const event = await world.analytics.events.get(runId, eventId);
170
+ ```
171
+
172
+ **Returns:** `AnalyticsEvent`.
173
+
174
+ ### events.getMany()
175
+
176
+ Look up a bounded set of event ids in one run with a single request.
177
+
178
+ ```typescript lineNumbers
179
+ const events = await world.analytics.events.getMany(runId, eventIds); // [!code highlight]
180
+ ```
181
+
182
+ **Returns:** `AnalyticsEvent[]` — not paginated, and no `pageInfo`. Duplicate ids are looked up once, and ids with no analytics row yet are **omitted rather than erroring**, since ingestion can trail canonical storage. Compare the returned length against your input to detect that.
183
+
184
+ ---
185
+
186
+ ## analytics.hooks
187
+
188
+ ### hooks.list()
114
189
 
115
190
  ```typescript lineNumbers
116
- const steps = await world.analytics.steps.list({ runId });
117
- const events = await world.analytics.events.list({ runId, eventType: "step_failed" });
118
- const related = await world.analytics.events.listByCorrelationId({ runId, correlationId });
119
191
  const hooks = await world.analytics.hooks.list({ runId });
120
- const waits = await world.analytics.waits.list({ runId, status: "waiting" });
121
192
  ```
122
193
 
123
- Each namespace also has a `get()` for point lookups (`steps.get(runId, stepId)`, `events.get(runId, eventId)`, `hooks.get(hookId)`, `waits.get(runId, waitId)`). Hook listings never include the hook token. Resolve it separately through the runtime APIs if you need to deliver a payload.
194
+ | Parameter | Type | Description |
195
+ |-----------|------|-------------|
196
+ | `params.runId` | `string` | Required. The run to list hooks for |
197
+ | `params.pagination` | `PaginationOptions` | Cursor pagination, `limit` up to 100 |
198
+
199
+ **Returns:** `PaginatedResponse<AnalyticsHook>`: `hookId`, `status` (`created`, `received`, `disposed`, or `conflict`), `receivedAt`, `disposedAt`, `isWebhook`, `isSystem`.
200
+
201
+ ### hooks.get()
202
+
203
+ ```typescript lineNumbers
204
+ const hook = await world.analytics.hooks.get(hookId);
205
+ ```
206
+
207
+ Unlike steps and waits, a hook id identifies one hook on its own, so no `runId` is needed. Pass `{ runId }` to scope the lookup when you already know it.
208
+
209
+ <Callout>
210
+ Hook listings never include the hook token. Resolve it separately through the
211
+ runtime APIs if you need to deliver a payload.
212
+ </Callout>
213
+
214
+ ---
215
+
216
+ ## analytics.waits
217
+
218
+ ### waits.list()
219
+
220
+ ```typescript lineNumbers
221
+ const waits = await world.analytics.waits.list({
222
+ runId,
223
+ status: "waiting", // [!code highlight]
224
+ });
225
+ ```
226
+
227
+ | Parameter | Type | Description |
228
+ |-----------|------|-------------|
229
+ | `params.runId` | `string` | Required. The run to list waits for |
230
+ | `params.status` | `string` | `waiting` or `completed` |
231
+ | `params.pagination` | `PaginationOptions` | Cursor pagination, `limit` up to 1000 |
232
+
233
+ **Returns:** `PaginatedResponse<AnalyticsWait>`: `waitId`, `status`, `resumeAt`, `completedAt`.
234
+
235
+ ### waits.get()
236
+
237
+ ```typescript lineNumbers
238
+ const wait = await world.analytics.waits.get(runId, waitId);
239
+ ```
240
+
241
+ **Returns:** `AnalyticsWait`. A wait id is only unique within its run, so both arguments are required.
242
+
243
+ ---
244
+
245
+ ## Limits and validation
246
+
247
+ Arguments are validated in your process before a request goes out. An
248
+ out-of-range or malformed argument throws a `RangeError` naming the bound it
249
+ broke, rather than reaching the backend and coming back as a 400 — which
250
+ matters because analytics is optional and callers commonly wrap it in a
251
+ `try`/`catch`, where a rejected request is easy to mistake for "no data".
252
+
253
+ ### Page limits
254
+
255
+ `pagination.limit` defaults to 40 everywhere. The maximum depends on whether
256
+ the listing scans within one run or across runs:
257
+
258
+ | Method | Max `limit` |
259
+ |--------|-------------|
260
+ | `steps.list()`, `events.list()`, `waits.list()` | 1000 |
261
+ | `runs.list()`, `attributes.list()`, `hooks.list()` | 100 |
262
+
263
+ `events.getMany()` is not paginated; it accepts 1 to 100 event ids per call.
264
+
265
+ <Callout type="warn">
266
+ The two page caps differ by a factor of ten, and `hooks.list()` takes the
267
+ lower one despite being run-scoped. Reusing one page size across listings is
268
+ the most common way to trip this.
269
+ </Callout>
270
+
271
+ ### Identifiers
272
+
273
+ Every id is a prefix plus a ULID, and each is checked before the request:
274
+
275
+ | Parameter | Shape |
276
+ |-----------|-------|
277
+ | `runId` | `wrun_` |
278
+ | `stepId` | `step_` |
279
+ | `eventId` | `evnt_` |
280
+ | `hookId` | `hook_` |
281
+ | `waitId` | `wait_` |
282
+ | `correlationId` | `step_`, `hook_`, `wait_`, or `attr_` |
283
+
284
+ Step, event, and wait ids are only unique **within** their run, so the methods that take them require a `runId` too. A hook id stands alone.
285
+
286
+ ### Time windows
287
+
288
+ `startTime` and `endTime` must be supplied **together** and be parseable ISO 8601 timestamps with `startTime` no later than `endTime`. Passing one without the other throws: it used to be dropped silently, which turned a listing you meant to bound into a scan of the whole retention window that looked like a successful answer.
289
+
290
+ ### Attribute filters
291
+
292
+ `runs.list({ attributes })` accepts 1 to 8 pairs. Keys are 1 to 256 characters; values are at most 256 UTF-8 bytes. Reserved `$`-prefixed keys are valid in a filter even though user code cannot write them.
293
+
294
+ ### Pagination
295
+
296
+ `cursor` is an opaque token from the previous response; do not construct or parse one. Branch on `hasMore`, not on `cursor` being non-null, and do not change `sortOrder` mid-walk — the cursor encodes the sort position, so reversing it can skip or repeat rows.
124
297
 
125
298
  ---
126
299
 
@@ -94,14 +94,14 @@ Vercel project and auth settings can often be inferred from `.vercel/project.jso
94
94
  ### `--sort`
95
95
 
96
96
  - Environment variable: none
97
- - Default: `desc`
97
+ - Default: `desc` for time-ordered listings; `workflow inspect attributes` orders keys alphabetically unless you pass this flag
98
98
  - Sort order for list commands. Accepts `asc` or `desc`.
99
99
 
100
100
  ### `--limit`
101
101
 
102
102
  - Environment variable: none
103
103
  - Default: `20`
104
- - Number of items returned per page for list commands.
104
+ - Number of items returned per page for list commands. `workflow cancel` bounds it separately; see [`--limit` (cancel)](#--limit-cancel).
105
105
 
106
106
  ### `--cursor`
107
107
 
@@ -115,6 +115,63 @@ Vercel project and auth settings can often be inferred from `.vercel/project.jso
115
115
  - Default: disabled
116
116
  - Enables keyboard-controlled pagination for supported list commands.
117
117
 
118
+ ## Inspect filtering
119
+
120
+ Flags for `workflow inspect`. Each list view accepts a different subset, noted
121
+ per flag.
122
+
123
+ ### `--runId` / `-r`
124
+
125
+ - Command: `workflow inspect`
126
+ - Default: unset
127
+ - Scopes the listing to one run. Required for `steps`, `events`, and `sleeps`; optional for `hooks`. Must be a run ID: `wrun_` followed by a 26-character ULID.
128
+
129
+ ### `--limit` (inspect)
130
+
131
+ - Command: `workflow inspect`
132
+ - Default: `20`
133
+ - Must be an integer between 1 and 100, the smallest page any inspect listing accepts. Larger pages are reachable by paging: pass `--cursor`, or `--interactive` to walk them.
134
+
135
+ ### `--stepId` / `-s`
136
+
137
+ - Command: `workflow inspect events`
138
+ - Default: unset
139
+ - Filters events to one step.
140
+
141
+ ### `--hookId`
142
+
143
+ - Command: `workflow inspect events`
144
+ - Default: unset
145
+ - Filters events to one hook.
146
+
147
+ ### `--attribute`
148
+
149
+ - Command: `workflow inspect runs`
150
+ - Default: unset
151
+ - Filters runs to those whose [attributes](/docs/observability/attributes) match every `key=value` pair given. Repeatable up to 8 times, and splits on the first `=` so a value may contain one.
152
+ - Requires a backend with the analytics read path; ignored with a warning otherwise.
153
+ - Cannot be combined with `--url` or `--web`, which hand off to the dashboard, or with `--withData`, which reads payloads from storage. Storage carries no attribute index.
154
+ - Use `workflow inspect attributes` to discover which keys exist.
155
+
156
+ ### `--since` / `--until`
157
+
158
+ - Command: `workflow inspect runs`, `workflow inspect attributes`
159
+ - Default: the backend's own window
160
+ - Bounds the listing to a window. `--since` opens the window and accepts a relative duration (`30m`, `12h`, `7d`, `2w`) or a timestamp. `--until` is optional and defaults to now, so `--until` on its own is rejected.
161
+ - Requires a backend with the analytics read path; ignored with a warning otherwise.
162
+
163
+ ### `--withData` / `-d`
164
+
165
+ - Command: `workflow inspect`
166
+ - Default: disabled
167
+ - Includes full input and output payloads in list views. Deprecated for list views — use `workflow inspect <resource> <id>` to read one item's payloads. Setting it also moves the read off the analytics path, which carries metadata only.
168
+
169
+ ### `--decrypt`
170
+
171
+ - Command: `workflow inspect`
172
+ - Default: disabled
173
+ - Decrypts encrypted values. Triggers an audit-logged key retrieval.
174
+
118
175
  ## Bulk cancel
119
176
 
120
177
  `workflow cancel <run-id>` cancels one run. Given a filter instead, it bulk-cancels a batch; bulk mode requires `--status` or `--workflowName`.
@@ -124,18 +181,20 @@ Vercel project and auth settings can often be inferred from `.vercel/project.jso
124
181
  - Command: `workflow cancel`
125
182
  - Default: unset
126
183
  - Restricts the batch to this status. Only `pending` and `running` are accepted; terminal runs cannot be canceled.
184
+ - Also filters `workflow inspect runs`, which accepts any run status. It does not narrow `workflow inspect attributes`, which indexes keys per tenant rather than per run; passing it there warns and lists every key.
127
185
 
128
186
  ### `--workflowName` / `-n`
129
187
 
130
188
  - Command: `workflow cancel`
131
189
  - Default: unset
132
190
  - Restricts the batch to one workflow. Expects the generated workflow ID from `workflow inspect runs`, not the short function name.
191
+ - Also filters `workflow inspect runs` and `workflow inspect attributes`.
133
192
 
134
193
  ### `--limit` (cancel)
135
194
 
136
195
  - Command: `workflow cancel`
137
196
  - Default: `50`
138
- - Maximum runs to cancel in one batch (1–500). Only one batch is canceled per invocation; run the command again to cancel the next batch.
197
+ - Maximum runs to cancel in one batch (1–100), the largest page the run listing serves. Only one batch is canceled per invocation; run the command again to cancel the next batch.
139
198
 
140
199
  ### `--confirm` / `-y`
141
200
 
@@ -185,10 +185,10 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
185
185
  ### `WORKFLOW_RETAINED_VM`
186
186
 
187
187
  - Default: enabled
188
- - Keeps the suspended workflow VM alive across inline steps within one invocation, so each iteration of the inline loop appends only the newly written events instead of replaying the whole event log in a fresh VM.
189
- - A step-driven suspension can keep the VM retained even when hooks are open or created at the same boundary. Hook-only suspensions park the invocation. Suspensions involving waits or attributes, runs with an open wait, and any replay divergence fall back to a full replay.
188
+ - Node.js VM engine only. Keeps the suspended workflow VM alive across inline steps within one invocation, so each iteration of the inline loop appends only the newly written events instead of replaying the whole event log in a fresh VM. QuickJS manages its own retained inline loop independently of this setting.
189
+ - A step- or attribute-driven suspension can keep the VM retained even when hooks or waits are open or created at the same boundary. Hook- or wait-only suspensions park the invocation because nothing in the current delivery can advance them, with one exception: when the hook's own create is what the workflow is waiting on (a `hook.getConflict()` awaiter, or a create whose token is already claimed), the invocation resumes the retained VM over the committed `hook_created` or `hook_conflict` instead of re-invoking through the queue. Any replay divergence falls back to a full replay.
190
190
  - Step inputs made of plain data (objects, arrays, primitives) and standard built-ins (`Map`, `Set`, `Date`, `RegExp`, typed arrays, `ArrayBuffer`, `URL`, `Headers`) keep the VM retained. Patching or polyfilling built-in prototypes doesn't change that because serialization never calls them. A boundary falls back to a full replay only when serializing its arguments runs code the workflow controls, such as a getter, a proxy, or a custom class serializer, or computes an `Error`'s stack trace.
191
- - Set `0` or `false` to replay from scratch in a fresh VM on every iteration.
191
+ - Set `0` or `false` to replay the Node.js workflow from scratch in a fresh VM on every iteration.
192
192
 
193
193
  ### `WORKFLOW_INLINE_OWNERSHIP`
194
194
 
@@ -11,23 +11,23 @@ related:
11
11
  ---
12
12
 
13
13
  <CopyPrompt
14
- text="In this Python project, add `requires-python = &quot;&gt;=3.12&quot;` and `dependencies = [&quot;vercel&quot;]` under `[project]` in `pyproject.toml`. Add `[[tool.vercel.workflows]]` with `entrypoint = &quot;app.workflows:wf&quot;`. Create `app/workflow.py` with `from vercel import workflow` and `wf = workflow.Workflows()`. Create `app/workflows/ai_content_workflow.py` importing `wf`, define `@wf.workflow async def ai_content_workflow(*, topic: str)`, and call step functions such as `generate_draft` and `summarize_draft`. Export `wf` from `app/workflows/__init__.py` and import the workflow module so its definitions are registered. Mark step functions with `@wf.step`, use `await workflow.sleep(&quot;7 days&quot;)` for durable delays where needed, and use a `workflow.BaseHook` Pydantic model plus `.wait(token=...)` and `.resume(token)` for external approval events. Verify the workflow entrypoint uses the `module:object` format and points to the exported `Workflows` registry."
14
+ text="Set up Workflow in this Python project. In `pyproject.toml`, add `requires-python = &quot;&gt;=3.12&quot;` and `dependencies = [&quot;vercel-workflow&quot;]` under `[project]`, then add `[[tool.vercel.workflows]]` with `entrypoint = &quot;app.workflows:wf&quot;`. Create `app/workflow.py` with `from vercel import workflow` and `wf = workflow.Workflows()`. Create `app/steps/generate_draft.py`, import `wf`, and define async step functions such as `generate_draft` and `summarize_draft`, decorating each with `@wf.step`. Then create `app/workflows/ai_content_workflow.py`, import `wf` and those step functions, and define `@wf.workflow async def ai_content_workflow(*, topic: str)` to orchestrate them and return the result. In `app/workflows/__init__.py`, export `wf` and import the workflow module so its definitions are registered. From server-side code, start it with `await workflow.start(ai_content_workflow, topic=...)`; use the returned `Run` to access its ID, check its status, or await its return value. Where the workflow needs a durable delay, use `await workflow.sleep(timedelta(days=7))` after importing `timedelta` from `datetime`. Where it needs an external approval event, define a Pydantic model that also extends `workflow.BaseHook`, wait with `.wait(token=...)`, and resume it from server-side code with `.resume(token)`."
15
15
  />
16
16
 
17
17
  <Callout type="warn">
18
- The Python SDK is currently in **beta**. APIs and behavior may change. For the latest documentation and updates, see the [official Vercel Workflow Python documentation](https://vercel.com/docs/workflows/python).
18
+ The Python SDK is currently in **beta**. APIs and behavior may change.
19
19
  </Callout>
20
20
 
21
- You can build durable workflows in Python using the [`vercel` Python SDK](https://pypi.org/project/vercel/). Your workflow code can pause, resume, and maintain state, just like the JavaScript and TypeScript Workflow SDK.
21
+ You can build durable workflows in Python using the [`vercel-workflow` SDK](https://pypi.org/project/vercel-workflow/). Your workflow code can pause, resume, and maintain state, just like the JavaScript and TypeScript Workflow SDK.
22
22
 
23
23
  ## Getting started
24
24
 
25
- Add the `vercel` package and workflow entrypoint to `pyproject.toml`:
25
+ Add the `vercel-workflow` package and workflow entrypoint to `pyproject.toml`:
26
26
 
27
27
  ```toml filename="pyproject.toml"
28
28
  [project]
29
29
  requires-python = ">=3.12"
30
- dependencies = ["vercel"]
30
+ dependencies = ["vercel-workflow"]
31
31
 
32
32
  [[tool.vercel.workflows]]
33
33
  entrypoint = "app.workflows:wf"
@@ -39,16 +39,17 @@ The workflow `entrypoint` uses the `module:object` format and points to the expo
39
39
 
40
40
  A workflow is a stateful function that coordinates multi-step logic over time. Create a `Workflows` instance and use the `@wf.workflow` decorator to mark a function as durable:
41
41
 
42
- ```python filename="app/workflow.py" {3}
42
+ ```python filename="app/workflow.py"
43
43
  from vercel import workflow
44
44
 
45
- wf = workflow.Workflows()
45
+ wf = workflow.Workflows() # [!code highlight]
46
46
  ```
47
47
 
48
- ```python filename="app/workflows/ai_content_workflow.py" {3}
48
+ ```python filename="app/workflows/ai_content_workflow.py"
49
49
  from app.workflow import wf
50
+ from app.steps.generate_draft import generate_draft, summarize_draft
50
51
 
51
- @wf.workflow
52
+ @wf.workflow # [!code highlight]
52
53
  async def ai_content_workflow(*, topic: str):
53
54
  draft = await generate_draft(topic=topic)
54
55
  summary = await summarize_draft(draft=draft)
@@ -74,15 +75,15 @@ Under the hood, the workflow compiles into a route that orchestrates execution.
74
75
 
75
76
  A step is a stateless function that runs a unit of durable work inside a workflow. Use `@wf.step` to mark a function as a step:
76
77
 
77
- ```python filename="app/steps/generate_draft.py" {4,8}
78
+ ```python filename="app/steps/generate_draft.py"
78
79
  import random
79
80
  from app.workflow import wf
80
81
 
81
- @wf.step
82
+ @wf.step # [!code highlight]
82
83
  async def generate_draft(*, topic: str):
83
84
  return await ai_generate(prompt=f"Write a blog post about {topic}")
84
85
 
85
- @wf.step
86
+ @wf.step # [!code highlight]
86
87
  async def summarize_draft(*, draft: str):
87
88
  summary = await ai_summarize(text=draft)
88
89
 
@@ -95,18 +96,42 @@ async def summarize_draft(*, draft: str):
95
96
 
96
97
  Each step executes separately from the workflow orchestrator. While the step executes, the workflow suspends without consuming resources. When the step completes, the workflow resumes automatically where it left off.
97
98
 
99
+ ## Starting a workflow
100
+
101
+ Call `workflow.start()` from server-side code to start a workflow. It returns a `Run` that you can use to identify the run, check its status, and wait for its result:
102
+
103
+ ```python filename="app/api/generate.py"
104
+ from app.workflows.ai_content_workflow import ai_content_workflow
105
+ from vercel import workflow
106
+
107
+ @app.post("/api/generate")
108
+ async def generate_content(*, topic: str):
109
+ run = await workflow.start(ai_content_workflow, topic=topic) # [!code highlight]
110
+
111
+ print(run.run_id)
112
+ print(await run.status()) # [!code highlight]
113
+
114
+ # Wait until the workflow completes and return its result.
115
+ return await run.return_value() # [!code highlight]
116
+ ```
117
+
118
+ Starting a workflow only waits until the run has been created and queued. Await `return_value()` to wait for the workflow to finish, or save its `run_id` and recreate the handle later with `workflow.Run(run_id)`.
119
+
98
120
  ## Sleep
99
121
 
100
122
  Sleep pauses a workflow for a specified duration without consuming compute resources:
101
123
 
102
- ```python filename="app/workflows/ai_refine.py" {7}
124
+ ```python filename="app/workflows/ai_refine.py"
125
+ from datetime import timedelta
126
+
127
+ from app.workflow import wf
103
128
  from vercel import workflow
104
129
 
105
130
  @wf.workflow
106
131
  async def ai_refine_workflow(*, draft_id: str):
107
132
  draft = await fetch_draft(draft_id)
108
133
 
109
- await workflow.sleep("7 days") # Wait 7 days to gather more signals.
134
+ await workflow.sleep(timedelta(days=7)) # Wait 7 days to gather more signals. # [!code highlight]
110
135
 
111
136
  refined = await refine_draft(draft)
112
137
 
@@ -116,7 +141,31 @@ async def ai_refine_workflow(*, draft_id: str):
116
141
  }
117
142
  ```
118
143
 
119
- The sleep call pauses the workflow and consumes no resources. The workflow resumes automatically when the time expires.
144
+ The parameter accepts four forms:
145
+
146
+ | Form | Description | Example |
147
+ | --- | --- | --- |
148
+ | `str` | Human-readable duration string | `"2 days"`, `"1w"`, `"1h 30m"` |
149
+ | `int` or `float` | Seconds from now | `5` (5 seconds) |
150
+ | `datetime.timedelta` | Duration from now | `timedelta(days=7)` |
151
+ | `datetime.datetime` | Absolute wake-up time (must be timezone-aware) | `datetime(2025, 1, 1, tzinfo=UTC)` |
152
+
153
+ The string form accepts one or more `<value><unit>` pairs. Supported units:
154
+
155
+ | Duration | Unit |
156
+ | --- | --- |
157
+ | Milliseconds | `ms` |
158
+ | Seconds | `s`, `second`, `seconds` |
159
+ | Minutes | `m`, `minute`, `minutes` |
160
+ | Hours | `h`, `hour`, `hours` |
161
+ | Days | `d`, `day`, `days` |
162
+ | Weeks | `w`, `week`, `weeks` |
163
+
164
+ <Callout>
165
+ `sleep()` must be called from the workflow body, not from inside a step. Calling it from a step raises a `RuntimeError`.
166
+ </Callout>
167
+
168
+ The sleep consumes no resources. The workflow resumes automatically when the time expires.
120
169
 
121
170
  ## Hooks
122
171
 
@@ -124,13 +173,17 @@ A hook lets a workflow wait for external events such as user actions, webhooks,
124
173
 
125
174
  Define a hook model with Pydantic and `workflow.BaseHook`:
126
175
 
127
- ```python filename="app/workflows/approval.py" {3,14}
176
+ ```python filename="app/workflows/approval.py"
177
+ import typing
178
+
179
+ import pydantic
180
+ from app.workflow import wf
128
181
  from vercel import workflow
129
182
 
130
- class Approval(BaseModel, workflow.BaseHook):
183
+ class Approval(pydantic.BaseModel, workflow.BaseHook): # [!code highlight]
131
184
  """Human approval for AI-generated drafts"""
132
185
 
133
- decision: Literal["approved", "changes"]
186
+ decision: typing.Literal["approved", "changes"]
134
187
  notes: str | None = None
135
188
 
136
189
  @wf.workflow
@@ -138,7 +191,7 @@ async def ai_approval_workflow(*, topic: str):
138
191
  draft = await generate_draft(topic=topic)
139
192
 
140
193
  # Wait for human approval events
141
- async for event in Approval.wait(token="draft-123"):
194
+ async for event in Approval.wait(token="draft-123"): # [!code highlight]
142
195
  if event.decision == "approved":
143
196
  await publish_draft(draft)
144
197
  break
@@ -149,20 +202,56 @@ async def ai_approval_workflow(*, topic: str):
149
202
 
150
203
  Resume the workflow when data arrives:
151
204
 
152
- ```python filename="app/api/resume.py" {5}
205
+ ```python filename="app/api/resume.py"
206
+ from app.workflows.approval import Approval
207
+
153
208
  @app.post("/api/resume")
154
- async def resume(approval: Approval):
209
+ async def resume(approval: Approval): # [!code highlight]
155
210
  """Resume the workflow when an approval is received"""
156
211
 
157
- await approval.resume("draft-123")
212
+ await approval.resume("draft-123") # [!code highlight]
158
213
  return {"ok": True}
159
214
  ```
160
215
 
161
216
  When a hook receives data, the workflow resumes automatically. You don&apos;t need polling, message queues, or manual state management.
162
217
 
163
- ## Learn more
218
+ ## Streaming
219
+
220
+ Steps can stream progress while a workflow is running. Get the run&apos;s writable stream inside a step, write values to it, and close it when no more values will be sent:
221
+
222
+ ```python filename="app/workflows/streaming.py"
223
+ from app.workflow import wf
224
+ from vercel import workflow
225
+
226
+ @wf.step
227
+ async def write_progress():
228
+ writable = workflow.get_writable() # [!code highlight]
229
+
230
+ for message in ["Drafting", "Reviewing", "Complete"]:
231
+ await writable.write(message) # [!code highlight]
232
+
233
+ await writable.close()
234
+
235
+ @wf.workflow
236
+ async def streaming_workflow():
237
+ await write_progress()
238
+ ```
239
+
240
+ Read the values from the returned `Run` as they arrive:
241
+
242
+ ```python filename="app/api/stream.py"
243
+ from app.workflows.streaming import streaming_workflow
244
+ from vercel import workflow
245
+
246
+ @app.post("/api/stream")
247
+ async def stream_progress():
248
+ run = await workflow.start(streaming_workflow)
249
+
250
+ async for message in run.readable(): # [!code highlight]
251
+ print(message)
252
+ ```
164
253
 
165
- For comprehensive documentation, examples, and the latest updates, visit the [official Vercel Workflow Python documentation](https://vercel.com/docs/workflows/python).
254
+ Streams are not closed automatically. Close the writable in the last step that writes to it so readers know when the stream is complete.
166
255
 
167
256
  ## Next steps
168
257
 
@@ -11,7 +11,7 @@ related:
11
11
  - /docs/api-reference/workflow-errors/workflow-world-error
12
12
  ---
13
13
 
14
- [`setAttributes`](/docs/api-reference/workflow/set-attributes) attaches plaintext string metadata to the current workflow run. These attributes appear in the Workflow CLI and web UI, and you can use them to search and filter runs through the [Analytics API](/docs/api-reference/workflow-runtime/world/analytics).
14
+ [`setAttributes`](/docs/api-reference/workflow/set-attributes) attaches plaintext string metadata to the current workflow run. These attributes appear in the Workflow CLI and web UI, and you can search and filter runs by them from either the [CLI](#from-the-cli) or the [Analytics API](/docs/api-reference/workflow-runtime/world/analytics).
15
15
 
16
16
  You can also seed any attributes directly when starting a run:
17
17
 
@@ -82,6 +82,28 @@ Expanding an `attr_set` event (in the run sidebar or the Events tab) shows the c
82
82
 
83
83
  ## Searching and filtering by attributes
84
84
 
85
+ ### From the CLI
86
+
87
+ `workflow inspect attributes` lists the keys recorded on this project's runs,
88
+ with how many runs carry each and when it was first and last seen:
89
+
90
+ ```bash
91
+ workflow inspect attributes
92
+ ```
93
+
94
+ Pass one or more `--attribute key=value` pairs to `inspect runs` to list the
95
+ runs carrying them. Repeatable up to 8 times:
96
+
97
+ ```bash
98
+ workflow inspect runs --attribute phase=received --status running
99
+ ```
100
+
101
+ Both require a backend with the analytics read path. `--attribute` is ignored
102
+ with a warning on backends without one, and `inspect attributes` reports that
103
+ it is unavailable.
104
+
105
+ ### From the Analytics API
106
+
85
107
  The [Analytics API](/docs/api-reference/workflow-runtime/world/analytics) can discover which attribute keys exist and filter run listings by them. The `analytics` namespace is optional on `World`, so feature-detect it before use; it is absent on local, Postgres, and other custom Worlds:
86
108
 
87
109
  ```typescript lineNumbers
@@ -33,7 +33,7 @@ The largest change in v5 has no API surface: the runtime does far less work per
33
33
 
34
34
  **The runtime avoids waiting on the persistence layer where it can determine that is safe for your workload.** The runtime skips many API calls when they aren't needed, such as requesting the event log on a run's first invocation. Step creation is folded into step execution rather than being its own round trip. The inline loop consumes the event-log delta from the previous step's write instead of re-listing events. Each optimization is gated on specific runtime conditions and can be turned off individually. See [Runtime tuning](/docs/configuration/runtime-tuning).
35
35
 
36
- **The workflow VM is kept alive across inline steps.** Within one invocation, a step-driven suspension keeps the live VM and hydrated state, including when hooks are open or created at the same boundary, so the next iteration appends only the newly written events instead of rebuilding the sandbox and replaying the whole log. Step inputs made of plain data or standard built-ins keep this fast path; see [`WORKFLOW_RETAINED_VM`](/docs/configuration/runtime-tuning#workflow_retained_vm).
36
+ **The workflow VM is kept alive across inline steps.** Within one invocation, a step- or attribute-driven suspension keeps the live VM and hydrated state, including when hooks or waits are open or created at the same boundary, so the next iteration appends only the newly written events instead of rebuilding the sandbox and replaying the whole log. Step inputs made of plain data or standard built-ins keep this fast path; see [`WORKFLOW_RETAINED_VM`](/docs/configuration/runtime-tuning#workflow_retained_vm).
37
37
 
38
38
  **Resuming a hook takes one round trip instead of two.** `resumeHook()` writes the `hook_received` event and dispatches the queue message concurrently, with a `(runId, resumeId)` dedup constraint keeping the two writers converging on exactly one event. See [Resilient hook resumption](/docs/changelog/resilient-resume).
39
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workflow",
3
- "version": "5.0.0-beta.47",
3
+ "version": "5.0.0-beta.48",
4
4
  "description": "Workflow SDK - Build durable, resilient, and observable workflows",
5
5
  "main": "dist/typescript-plugin.cjs",
6
6
  "type": "module",
@@ -58,25 +58,25 @@
58
58
  }
59
59
  },
60
60
  "dependencies": {
61
- "ms": "2.1.3",
62
- "@workflow/astro": "5.0.0-beta.47",
63
- "@workflow/cli": "5.0.0-beta.47",
64
- "@workflow/core": "5.0.0-beta.47",
65
- "@workflow/errors": "5.0.0-beta.19",
61
+ "@workflow/astro": "5.0.0-beta.48",
62
+ "@workflow/cli": "5.0.0-beta.48",
63
+ "@workflow/core": "5.0.0-beta.48",
64
+ "@workflow/errors": "5.0.0-beta.20",
66
65
  "@workflow/typescript-plugin": "5.0.0-beta.5",
67
66
  "@workflow/utils": "5.0.0-beta.10",
68
- "@workflow/next": "5.0.0-beta.47",
69
- "@workflow/nest": "5.0.0-beta.47",
70
- "@workflow/nitro": "5.0.0-beta.47",
71
- "@workflow/nuxt": "5.0.0-beta.47",
72
- "@workflow/sveltekit": "5.0.0-beta.47",
73
- "@workflow/rollup": "5.0.0-beta.47"
67
+ "ms": "2.1.3",
68
+ "@workflow/next": "5.0.0-beta.48",
69
+ "@workflow/nest": "5.0.0-beta.48",
70
+ "@workflow/nitro": "5.0.0-beta.48",
71
+ "@workflow/nuxt": "5.0.0-beta.48",
72
+ "@workflow/sveltekit": "5.0.0-beta.48",
73
+ "@workflow/rollup": "5.0.0-beta.48"
74
74
  },
75
75
  "devDependencies": {
76
76
  "@types/ms": "2.1.0",
77
77
  "@types/node": "22.19.0",
78
- "typescript": "^6.0.3",
79
- "@workflow/tsconfig": "5.0.0-beta.0"
78
+ "@workflow/tsconfig": "5.0.0-beta.0",
79
+ "typescript": "^6.0.3"
80
80
  },
81
81
  "peerDependencies": {
82
82
  "@opentelemetry/api": "1"