workflow 5.0.0-beta.32 → 5.0.0-beta.34

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.
package/docs/ai/index.mdx CHANGED
@@ -64,7 +64,7 @@ In order to connect to an LLM, we'll need to set up an API key. The easiest way
64
64
 
65
65
  <Tab value="Gateway">
66
66
 
67
- Get a Gateway API key from the [Vercel Gateway](https://vercel.com/docs/gateway/api-reference/overview) page.
67
+ Get a Gateway API key from the [Vercel Gateway](https://vercel.com/docs/ai-gateway/authentication) page.
68
68
 
69
69
  Then add it to your `.env.local` file:
70
70
 
@@ -18,7 +18,7 @@ All the functions and primitives that come with Workflow SDK by package.
18
18
  API reference for runtime functions from the `workflow/api` package.
19
19
  </Card>
20
20
  <Card title="workflow/runtime" href="/docs/api-reference/workflow-runtime">
21
- Runtime functions for resolving the World instance and the low-level World SDK.
21
+ Runtime functions for resolving the World instance and the low-level World SDK, including storage and analytics queries.
22
22
  </Card>
23
23
  <Card title="workflow/observability" href="/docs/api-reference/workflow-observability">
24
24
  Utilities to hydrate step I/O, parse display names, and decrypt workflow data.
@@ -16,7 +16,7 @@ The runtime package provides low-level access to the workflow runtime — resolv
16
16
  Async: resolve the World instance for storage, queuing, and streaming backends.
17
17
  </Card>
18
18
  <Card href="/docs/api-reference/workflow-runtime/world" title="World SDK">
19
- Low-level API for inspecting runs, steps, events, hooks, streams, and queues.
19
+ Low-level API for inspecting runs, steps, events, hooks, streams, and queues, plus metadata-only analytics with attribute search.
20
20
  </Card>
21
21
  </Cards>
22
22
 
@@ -0,0 +1,138 @@
1
+ ---
2
+ title: Analytics
3
+ description: Metadata-only read APIs for runs, steps, events, hooks, waits, and attributes, backed by the observability pipeline.
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."
6
+ prerequisites:
7
+ - /docs/api-reference/workflow-runtime/get-world
8
+ related:
9
+ - /docs/api-reference/workflow-runtime/world/storage
10
+ - /docs/observability/attributes
11
+ keywords:
12
+ - world.analytics
13
+ - analytics.runs
14
+ - analytics.attributes
15
+ - attribute filter
16
+ - listValues
17
+ - lookback window
18
+ - observability-upgrade-required
19
+ - pageInfo
20
+ - metadata-only
21
+ ---
22
+
23
+ `world.analytics` is an optional, read-only namespace for observability surfaces — dashboards, CLIs, and admin tools that list large numbers of runs without touching payload data.
24
+
25
+ It differs from [Storage](/docs/api-reference/workflow-runtime/world/storage) in two ways:
26
+
27
+ - **Metadata only.** Results never include run input/output, step data, or hook tokens. There is no `resolveData` option.
28
+ - **Served from the observability pipeline.** On Vercel, queries are served from the Vercel observability data pipeline, so large listings do not compete with workflow execution. Data is ingested asynchronously and may trail the live state by a few seconds.
29
+
30
+ The namespace is optional — worlds that don't implement it (such as the local development world) leave it `undefined`, so feature-detect before use:
31
+
32
+ ```typescript lineNumbers
33
+ import { getWorld } from "workflow/runtime";
34
+
35
+ const world = await getWorld();
36
+ if (world.analytics) { // [!code highlight]
37
+ const page = await world.analytics.runs.list();
38
+ }
39
+ ```
40
+
41
+ ---
42
+
43
+ ## analytics.runs
44
+
45
+ ### runs.list()
46
+
47
+ List runs with metadata, current status, and attributes. Without an explicit time window, the listing defaults to the trailing 24 hours; pass `startTime`/`endTime` to reach older runs within your plan window.
48
+
49
+ ```typescript lineNumbers
50
+ const page = await world.analytics.runs.list({
51
+ workflowName: "orderWorkflow",
52
+ status: "failed",
53
+ attributes: { source: "checkout" }, // [!code highlight]
54
+ pagination: { limit: 50, sortOrder: "desc" },
55
+ });
56
+ ```
57
+
58
+ | Parameter | Type | Description |
59
+ |-----------|------|-------------|
60
+ | `params.workflowName` | `string` | Filter to one workflow |
61
+ | `params.status` | `string` | `pending`, `running`, `completed`, `failed`, or `cancelled` |
62
+ | `params.startTime` / `params.endTime` | `string` | ISO 8601 window; must be provided together |
63
+ | `params.attributes` | `Record<string, string>` | Only return runs whose latest attributes match every pair (up to 8) |
64
+ | `params.pagination` | `PaginationOptions` | Cursor pagination |
65
+
66
+ **Returns:** `PaginatedResponse<AnalyticsRun>` — each run includes `runId`, `status`, `workflowName`, `deploymentId`, `attributes`, and lifecycle timestamps.
67
+
68
+ Attribute matching is latest-write-wins: a run whose attribute moved from `"v1"` to `"v2"` no longer matches `{ key: "v1" }`. Reserved `$`-prefixed keys may be used in filters even though user code cannot write them.
69
+
70
+ ### runs.get()
71
+
72
+ Fetch one run by ID. Point lookups search the full plan window, not just the trailing 24 hours.
73
+
74
+ ```typescript lineNumbers
75
+ const run = await world.analytics.runs.get(runId);
76
+ ```
77
+
78
+ ---
79
+
80
+ ## analytics.attributes
81
+
82
+ Discover which [attributes](/docs/observability/attributes) exist on your runs — for example to build filter dropdowns over arbitrary user-defined keys.
83
+
84
+ ### attributes.list()
85
+
86
+ List the distinct attribute keys observed on runs in the window, ordered alphabetically.
87
+
88
+ ```typescript lineNumbers
89
+ const page = await world.analytics.attributes.list({ // [!code highlight]
90
+ workflowName: "orderWorkflow",
91
+ });
92
+ for (const { key, runCount, lastSeenAt } of page.data) {
93
+ console.log(key, runCount, lastSeenAt);
94
+ }
95
+ ```
96
+
97
+ | Parameter | Type | Description |
98
+ |-----------|------|-------------|
99
+ | `params.workflowName` | `string` | Only count runs of one workflow |
100
+ | `params.startTime` / `params.endTime` | `string` | ISO 8601 window; must be provided together |
101
+ | `params.pagination` | `PaginationOptions` | Cursor pagination |
102
+
103
+ **Returns:** `PaginatedResponse<AnalyticsAttributeKey>` — `{ key, runCount, firstSeenAt, lastSeenAt }`
104
+
105
+ ---
106
+
107
+ ## analytics.steps, analytics.events, analytics.hooks, analytics.waits
108
+
109
+ Run-scoped listings mirroring their [Storage](/docs/api-reference/workflow-runtime/world/storage) counterparts, minus payload data:
110
+
111
+ ```typescript lineNumbers
112
+ const steps = await world.analytics.steps.list({ runId });
113
+ const events = await world.analytics.events.list({ runId, eventType: "step_failed" });
114
+ const related = await world.analytics.events.listByCorrelationId({ correlationId });
115
+ const hooks = await world.analytics.hooks.list({ runId });
116
+ const waits = await world.analytics.waits.list({ runId, status: "waiting" });
117
+ ```
118
+
119
+ 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.
120
+
121
+ ---
122
+
123
+ ## Lookback windows and pageInfo
124
+
125
+ Every paginated response carries `pageInfo` describing the window the query was allowed to scan:
126
+
127
+ {/* @skip-typecheck: shape illustration, not runnable code */}
128
+ ```typescript
129
+ {
130
+ currentLookbackDays: 2, // what your plan allows today
131
+ maxLookbackDays: 30, // ceiling with Observability Plus on Vercel
132
+ currentWindowStart: Date,
133
+ maxWindowStart: Date,
134
+ upgradeAvailable: true, // for Vercel deployed workflows
135
+ }
136
+ ```
137
+
138
+ Requests for a window older than `currentWindowStart` fail with an `observability-upgrade-required` error; windows older than `maxWindowStart` return not-found. Use `pageInfo` to size date pickers and to decide whether to surface an upgrade prompt.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: World SDK
3
- description: Low-level API for inspecting and managing workflow runs, steps, events, hooks, streams, and queues.
3
+ description: Low-level API for inspecting and managing workflow runs, steps, events, hooks, streams, and queues, with an analytics namespace for metadata listings and attribute search.
4
4
  type: overview
5
5
  summary: Access workflow infrastructure via await getWorld() for building observability dashboards, admin tools, and custom integrations.
6
6
  prerequisites:
@@ -14,7 +14,7 @@ keywords:
14
14
  - workflow management
15
15
  ---
16
16
 
17
- The World SDK provides direct access to workflow infrastructure — runs, steps, events, hooks, streams, and queues. Use it to build observability dashboards, admin panels, debugging tools, and custom workflow management logic.
17
+ The World SDK provides direct access to workflow infrastructure — runs, steps, events, hooks, streams, and queues — plus a metadata-only [Analytics](/docs/api-reference/workflow-runtime/world/analytics) namespace with attribute discovery and filtering. Use it to build observability dashboards, admin panels, debugging tools, and custom workflow management logic.
18
18
 
19
19
  ```typescript lineNumbers
20
20
  import { getWorld } from "workflow/runtime";
@@ -28,6 +28,9 @@ const world = await getWorld(); // [!code highlight]
28
28
  <Card href="/docs/api-reference/workflow-runtime/world/storage" title="Storage">
29
29
  Query runs, steps, hooks, and the underlying event log.
30
30
  </Card>
31
+ <Card href="/docs/api-reference/workflow-runtime/world/analytics" title="Analytics">
32
+ Metadata-only listings with attribute discovery and filtering, built for observability dashboards.
33
+ </Card>
31
34
  <Card href="/docs/api-reference/workflow-runtime/world/streams" title="Streams">
32
35
  Read, write, and manage real-time data streams for workflow runs.
33
36
  </Card>
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "title": "World SDK",
3
- "pages": ["storage", "streams", "queue"]
3
+ "pages": ["storage", "analytics", "streams", "queue"]
4
4
  }
@@ -196,6 +196,14 @@ Platform-provided values such as `VERCEL_DEPLOYMENT_ID`, `VERCEL_PROJECT_ID`, an
196
196
  - Default: `https://api.vercel.com/v1/workflow`
197
197
  - Workflow API proxy URL for external tooling.
198
198
 
199
+ ### `WORKFLOW_SEQUENTIAL_REPLAYS`
200
+
201
+ - Default: disabled
202
+ - Set `1` to serialize orchestrator (flow) invocations per run: each run's replays get their own queue topic and the flow trigger is generated with `maxConcurrency: 1`. Inline step executions get per-step topics and keep full parallelism.
203
+ - Read at **both build time and runtime** — set it as a project-level environment variable so the generated trigger and the runtime queue routing agree.
204
+ - Also enabled by `WORKFLOW_SAFE_MODE=1` when not set explicitly.
205
+ - Costs: flow-route push deliveries under `maxConcurrency` are billed at 2x units, queue observability sees one topic per run, and deliveries can queue behind the per-run slot — including hook resumes, aborts, and run-timeout enforcement, which are delayed while a replay is in flight. See [Vercel World](/docs/deploying/world/vercel-world#workflow_sequential_replays) for details.
206
+
199
207
  ### `VERCEL_WORKFLOW_SERVER_URL`
200
208
 
201
209
  - Factory option: none
@@ -43,6 +43,7 @@ The Vercel World provides:
43
43
  - **Managed queuing** - Steps are processed reliably with automatic retries
44
44
  - **Automatic scaling** - Workflows scale with your application
45
45
  - **Built-in observability** - View workflow runs in the Vercel dashboard
46
+ - **Multi-region** - Runs are pinned to the region that creates them, keeping workflow data, queuing, and streaming close to your users (requires `workflow` 5.0.0-beta.33 or later)
46
47
 
47
48
  Simply deploy your application:
48
49
 
@@ -53,7 +54,7 @@ vercel deploy
53
54
  <FluidComputeCallout />
54
55
 
55
56
  <Callout>
56
- Learn more about the [Vercel World](/worlds/vercel) and its capabilities.
57
+ Learn more about the [Vercel World](/docs/deploying/world/vercel-world) and its capabilities, including [multi-region](/docs/deploying/world/vercel-world#multi-region).
57
58
  </Callout>
58
59
 
59
60
  ## Self-Hosting & Other Providers
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "title": "Deploying",
3
- "pages": ["...deploying", "building-a-world"]
3
+ "pages": ["...deploying", "world", "building-a-world"]
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
- "title": "World",
2
+ "title": "Worlds",
3
3
  "pages": ["local-world", "vercel-world", "postgres-world"]
4
4
  }
@@ -36,17 +36,61 @@ That's it. Vercel automatically:
36
36
 
37
37
  For complete details on pricing, usage limits, and included allotments on Vercel, see the official Vercel documentation:
38
38
 
39
- - **[Vercel Workflow](https://vercel.com/docs/workflow)** — Pricing details, concepts, and observability for Workflow on Vercel
39
+ - **[Vercel Workflow](https://vercel.com/docs/workflows)** — Pricing details, concepts, and observability for Workflow on Vercel
40
40
  - **[Vercel limits](https://vercel.com/docs/limits)** — Platform-wide limits including Workflow-specific constraints
41
41
  - **[Vercel Hobby plan](https://vercel.com/docs/plans/hobby)** — Free tier included usage for Workflow and other resources
42
42
 
43
43
  For self-hosted deployments, use the [Postgres World](/worlds/postgres). For local development, use the [Local World](/worlds/local).
44
44
 
45
- ## Limitations
45
+ ## Multi-region
46
+
47
+ The Vercel World runs in every [Vercel Function region](https://vercel.com/docs/regions). Each workflow run is pinned to a single region at creation time: its stored state, queue dispatch, and streams are all served from that region — no cross-region round trips on the hot path. When your application is deployed in the run's region (the automatic case below), step execution is region-local too.
48
+
49
+ <Callout type="info">
50
+ Multi-region requires `workflow` version **5.0.0-beta.33** or later.
51
+ The 4.x release line does not support region pinning — runs created by
52
+ 4.x always live in `iad1`.
53
+ </Callout>
54
+
55
+ ### Automatic region pinning
56
+
57
+ No configuration is needed. A run is pinned to the region of the function that creates it:
58
+
59
+ - Deploy your app to a single region (via [`regions`](https://vercel.com/docs/project-configuration/vercel-json#regions) in `vercel.json` or the project settings), and every run lives there.
60
+ - Deploy to multiple regions for a globally distributed audience, and each run is pinned to the region that served the user who triggered it — workflow data and streaming stay close to that user.
61
+
62
+ ### Explicit region selection
63
+
64
+ To pin a specific run somewhere else, pass the `region` option to [`start()`](/docs/api-reference/workflow-api/start):
65
+
66
+ ```typescript
67
+ import { start } from "workflow/api";
68
+ import { myWorkflow } from "@/workflows/my-workflow";
69
+
70
+ const run = await start(myWorkflow, [input], { region: "sfo1" });
71
+ ```
72
+
73
+ <Callout type="warn">
74
+ The `region` option controls where the run's **data is stored** and where
75
+ its **queue messages are dispatched from** — it does not deploy your code
76
+ there. Your workflow and step functions execute in the regions your
77
+ application is deployed to. For execution to actually happen in the
78
+ specified region, your app must be deployed there — via
79
+ [`regions`](https://vercel.com/docs/project-configuration/vercel-json#regions) in
80
+ `vercel.json` or the Function Regions setting in your project settings.
81
+ If it isn't, the run's data lives in the requested region but its steps
82
+ execute in the nearest region your app is deployed to.
83
+ </Callout>
84
+
85
+ ### Good to know
46
86
 
47
- - **Single-region deployment** - The backend infrastructure is currently deployed only in `iad1`. Applications in other regions will route workflow requests to `iad1`, which may result in higher latency. For best performance, deploy your Vercel apps using Workflow to `iad1`. Global deployment is planned to colocate the backend closer to your applications.
87
+ - Reads, hook resumes, and stream consumers can come from anywhere the platform routes them to the run's region automatically.
88
+ - Runs created by 4.x SDKs (and any runs that existed before you upgraded) live in `iad1` and are unaffected by an upgrade; there is no migration.
89
+ - **Hook tokens are currently stored in `iad1`** for every run, regardless of the run's region: the token-to-run mapping that powers [`getHookByToken()`](/docs/api-reference/workflow-api/get-hook-by-token) and [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) lives there so tokens — which carry no region information — can always be resolved. Hook *payloads* are not affected: a received payload is recorded on the run's event log, which lives in the run's region like all other run data. This token placement may become a project-level setting in the future.
90
+
91
+ ## Limitations
48
92
 
49
- - **Data residency** - The Vercel World is currently deployed in the `iad1` region. This means independently of the deployment location of your application, the data for your workflows will be stored in the `iad1` region.
93
+ - **No run migration** - A run's region is fixed at creation. Existing runs cannot be moved to a different region.
50
94
 
51
95
  ## Observability
52
96
 
@@ -125,6 +169,23 @@ Custom base URL for the Vercel workflow API proxy. Default: `https://api.vercel.
125
169
 
126
170
  Custom workflow-server URL for direct runtime requests, or for the proxy to forward to via `WORKFLOW_VERCEL_BACKEND_URL`. Default: unset; normal deployments should not need this.
127
171
 
172
+ ### `WORKFLOW_SEQUENTIAL_REPLAYS`
173
+
174
+ Set `WORKFLOW_SEQUENTIAL_REPLAYS=1` to guarantee that **at most one orchestrator (flow) invocation runs at a time per workflow run**. This behavior is off by default; without it, the runtime relies on idempotency and the event log to tolerate concurrent flow invocations of the same run. It is also enabled by `WORKFLOW_SAFE_MODE=1` when `WORKFLOW_SEQUENTIAL_REPLAYS` is not set explicitly.
175
+
176
+ When enabled, each run's orchestrator messages are given their own queue topic and the flow trigger is configured with `maxConcurrency: 1`, so [Vercel Queues](https://vercel.com/docs/queues) processes replays for a given run strictly one at a time. Step executions (which ride the flow topic in the combined handler model) get a per-step topic, so steps keep full parallelism.
177
+
178
+ <Callout type="warn">
179
+ This variable is read at **both build time and runtime**, so it must be set as a project-level environment variable that applies to your build and your deployed functions. Setting it for only one will produce an inconsistent configuration. The same applies to framework integrations that write their own queue trigger configuration instead of using `getWorkflowQueueTrigger()` from `@workflow/builders`: they only get the runtime half (per-run topics) unless they also emit `maxConcurrency: 1` on their flow trigger.
180
+
181
+ Enabling sequential replays has a cost. Per [Vercel Queues pricing](https://vercel.com/docs/queues/pricing), push deliveries under `maxConcurrency` are billed at **2x units** for that operation, so every flow-route delivery costs double while this is enabled. It also creates one queue topic per run, which increases the number of distinct queues surfaced in queue observability, and each flow invocation waits for a per-run concurrency slot before delivery, which can add queueing latency. Leave it off unless you specifically need the per-run serialization guarantee.
182
+
183
+
184
+ While a replay holds a run's slot — including time spent executing steps inline — other wake messages for that run (hook resumes, aborts and cancellations, and run-timeout enforcement) wait for the slot. Expect aborts and timeouts to be delayed by up to the duration of the longest single invocation.
185
+
186
+ The guarantee covers messages sent by the Workflow SDK itself. External producers that compute a flow topic name directly (rather than enqueueing through the SDK) still deliver, but bypass the per-run serialization slot.
187
+ </Callout>
188
+
128
189
  ### `VERCEL_QUEUE_MAX_DELAY_SECONDS`
129
190
 
130
191
  Maximum delay, in seconds, that Workflow uses for one Vercel Queues continuation message when implementing `sleep()`. If a workflow sleeps longer than this, the runtime schedules another continuation message when the first one fires, repeating until the sleep's target time is reached. Default: `82800` (23 hours).
@@ -211,4 +272,4 @@ The Vercel World uses Vercel's infrastructure for workflow execution:
211
272
  - **Queuing** - Steps are distributed across serverless functions via [Vercel Queues](https://vercel.com/docs/queues) with automatic retries and [consumer function security](#consumer-function-security)
212
273
  - **Authentication** - OIDC tokens provide secure, automatic authentication
213
274
 
214
- For more details, see the [Vercel Workflow documentation](https://vercel.com/docs/workflow).
275
+ For more details, see the [Vercel Workflow documentation](https://vercel.com/docs/workflows).
@@ -15,7 +15,7 @@ related:
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/workflow/python?language=py).
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).
19
19
  </Callout>
20
20
 
21
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.
@@ -160,7 +160,7 @@ When a hook receives data, the workflow resumes automatically. You don&apos;t ne
160
160
 
161
161
  ## Learn More
162
162
 
163
- For comprehensive documentation, examples, and the latest updates, visit the [official Vercel Workflow Python documentation](https://vercel.com/docs/workflow/python).
163
+ For comprehensive documentation, examples, and the latest updates, visit the [official Vercel Workflow Python documentation](https://vercel.com/docs/workflows/python).
164
164
 
165
165
  ## Next Steps
166
166
 
@@ -405,12 +405,17 @@ Two queue topics are created per deployment:
405
405
  | `step.func` | `__wkf_step_*` | Step execution (long-running, `maxDuration: max`) |
406
406
  | `flow.func` | `__wkf_workflow_*` | Workflow orchestration (`maxDuration: 60`) |
407
407
 
408
- If you're building a framework integration that targets Vercel, you should write these triggers into the `.vc-config.json` for each generated function. The `STEP_QUEUE_TRIGGER` and `WORKFLOW_QUEUE_TRIGGER` constants are exported from `@workflow/builders` for this purpose:
408
+ If you're building a framework integration that targets Vercel, you should write these triggers into the `.vc-config.json` for each generated function. Use `getWorkflowQueueTrigger()` for flow functions so `WORKFLOW_SEQUENTIAL_REPLAYS=1` is reflected in the generated trigger configuration (it also accepts a `namespace` option, matching `createWorkflowQueueTrigger`); `STEP_QUEUE_TRIGGER` is exported for step functions:
409
409
 
410
410
  ```typescript
411
- import { STEP_QUEUE_TRIGGER, WORKFLOW_QUEUE_TRIGGER } from "@workflow/builders";
411
+ import { getWorkflowQueueTrigger, STEP_QUEUE_TRIGGER } from "@workflow/builders";
412
+
413
+ const flowTriggers = [getWorkflowQueueTrigger()];
414
+ const stepTriggers = [STEP_QUEUE_TRIGGER];
412
415
  ```
413
416
 
417
+ If your integration constructs the flow trigger object itself instead of calling `getWorkflowQueueTrigger()`, it must add `maxConcurrency: 1` to that trigger when sequential replays are enabled at build time (`WORKFLOW_SEQUENTIAL_REPLAYS=1`, or `WORKFLOW_SAFE_MODE=1` when the specific variable is unset — the exported `isSequentialReplaysEnabled()` helper implements this check). The runtime half of the feature (per-run queue topics) activates from the environment variable alone — without the trigger half, those per-run topics are not serialized and the setting only adds queue-topic cardinality.
418
+
414
419
 
415
420
  ### Custom implementations
416
421
 
@@ -11,8 +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 are displayed in observability CLI/UI.
15
- In the future, you'll be able to search and filter runs by attributes.
14
+ [`setAttributes`](/docs/api-reference/workflow/set-attributes) attaches plaintext string metadata to the current workflow run. These attributes are displayed in observability CLI/UI, and can be used to search and filter runs through the [Analytics API](/docs/api-reference/workflow-runtime/world/analytics).
16
15
 
17
16
  You can also seed any attributes directly when starting a run:
18
17
 
@@ -72,10 +71,33 @@ Expanding an `attr_set` event — in the run sidebar or the Events tab — shows
72
71
 
73
72
  ![Expanded attr_set events showing changes and the writer](/screenshots/attributes/run-details-attr-set-events.png)
74
73
 
74
+ ## Searching and filtering by attributes
75
+
76
+ 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` — feature-detect it before use; it is absent on local, Postgres, and other custom Worlds:
77
+
78
+ ```typescript lineNumbers
79
+ import { getWorld } from "workflow/runtime";
80
+
81
+ const world = await getWorld();
82
+ if (!world.analytics) {
83
+ throw new Error("This World does not support analytics queries"); // [!code highlight]
84
+ }
85
+
86
+ // Which attribute keys exist, and on how many runs?
87
+ const keys = await world.analytics.attributes.list();
88
+
89
+ // List runs whose latest attributes match every pair
90
+ const stuck = await world.analytics.runs.list({
91
+ attributes: { phase: "received" }, // [!code highlight]
92
+ });
93
+ ```
94
+
95
+ Matching is latest-write-wins: once the run above writes `phase: "complete"`, it stops matching `phase: "received"`.
96
+
75
97
  ## Behavior
76
98
 
77
99
  - Attributes require a World implementing spec version 4 or later.
78
100
  - Writes from workflow and step bodies append native `attr_set` events and immediately materialize `run.attributes`.
79
101
  - Storage errors surface rather than being silently ignored: transient errors on workflow-body writes are retried, and a write the World rejects as invalid (for example, exceeding the per-run attribute cap across multiple calls) fails the run with the validation error.
80
102
  - Step-body storage errors throw from `setAttributes` like any other step-side network write. Catch the error inside the step if the attribute is best-effort.
81
- - Reading and querying attributes is not available yet. A query API is planned.
103
+ - Reading and querying: each run's current attributes are returned on the run objects from the [Storage](/docs/api-reference/workflow-runtime/world/storage) and [Analytics](/docs/api-reference/workflow-runtime/world/analytics) APIs, and the Analytics API supports discovering attribute keys and filtering run listings by key=value pairs (see [Searching and filtering by attributes](#searching-and-filtering-by-attributes)). On Worlds without the optional `analytics` namespace, attributes are readable on run objects but not searchable.
@@ -17,7 +17,7 @@ The SDK only depends on the OpenTelemetry **API**, never on an SDK or exporter.
17
17
 
18
18
  ## Enabling tracing
19
19
 
20
- Register any OpenTelemetry Node SDK in your application. On Vercel with Next.js, the simplest setup is [`@vercel/otel`](https://vercel.com/docs/observability/otel-overview) in `instrumentation.ts`:
20
+ Register any OpenTelemetry Node SDK in your application. On Vercel with Next.js, the simplest setup is [`@vercel/otel`](https://vercel.com/docs/tracing/instrumentation) in `instrumentation.ts`:
21
21
 
22
22
  ```typescript title="instrumentation.ts" lineNumbers
23
23
  import { registerOTel } from "@vercel/otel"
@@ -43,6 +43,8 @@ No workflow-specific configuration is required. As soon as a tracer provider and
43
43
  | `http <method>` | client | the SDK calls the workflow backend (event reads/writes) |
44
44
  | `workflow.stream.write` | client | a stream chunk (or the stream close) is flushed to the backend |
45
45
  | `workflow.stream.flush` | client | a buffered batch of stream writes settles; back-dated to the batch's first `write()`, so its duration is the app-perceived batch latency (buffer dwell + RPC) |
46
+ | `workflow.stream.close` | client | the stream-close RPC; its duration is the close round trip |
47
+ | `workflow.stream.read.complete` | client | a stream read drains; back-dated to the read dispatch, so its duration is the total read (`workflow.stream.read.chunks` / `.bytes` carry throughput counts) |
46
48
  | `workflow.stream.read.connect` | client | a live stream read opens; the span covers dispatch → response headers (network connect) |
47
49
  | `workflow.stream.read` | client | a live stream read receives its first chunk; the span's duration is the end-to-end time-to-first-chunk (see `workflow.stream.read.ttfc_ms`) |
48
50
 
@@ -61,9 +63,10 @@ Stream spans are emitted by the SDK's world backend on the client that writes or
61
63
  | `workflow.queue.overhead_ms` | Time between the message being enqueued and the handler starting — queue dwell plus any cold start. |
62
64
  | `workflow.stream.name` | The stream name, on stream write/read spans. |
63
65
  | `workflow.stream.operation` | The stream operation: `write`, `write_multi`, `close`, `read`, or `flush`. |
64
- | `workflow.stream.write.chunk_rtt` | Time between emissions of a chunk to the wire, and receiving the `ack` message for that chunk. |
66
+ | `workflow.stream.write.chunk_rtt` | Time between emissions of a chunk to the wire, and receiving the `ack` message for that chunk. Also stamped on `workflow.stream.flush` (the batch's write RPC duration, network included). |
65
67
  | `workflow.stream.flush.buffer_dwell_ms` | On `workflow.stream.flush`: time the batch's first chunk waited in the client-side write buffer (flush timer, run-ready barrier) before the request was dispatched. `workflow.stream.flush.chunks` / `.bytes` carry the batch shape. |
66
68
  | `workflow.stream.read.ttfc_ms` | Time between opening a read connection and observing and receiving the first chunk back. |
69
+ | `workflow.stream.read.connect_ms` | On `workflow.stream.read`: the connect portion (read dispatch → stream handle/response headers), network included. |
67
70
 
68
71
  ## Trace shape: one trace per invocation
69
72
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workflow",
3
- "version": "5.0.0-beta.32",
3
+ "version": "5.0.0-beta.34",
4
4
  "description": "Workflow SDK - Build durable, resilient, and observable workflows",
5
5
  "main": "dist/typescript-plugin.cjs",
6
6
  "type": "module",
@@ -57,22 +57,23 @@
57
57
  },
58
58
  "dependencies": {
59
59
  "ms": "2.1.3",
60
- "@workflow/astro": "5.0.0-beta.32",
61
- "@workflow/cli": "5.0.0-beta.32",
62
- "@workflow/core": "5.0.0-beta.32",
60
+ "@workflow/astro": "5.0.0-beta.34",
61
+ "@workflow/cli": "5.0.0-beta.34",
62
+ "@workflow/core": "5.0.0-beta.34",
63
63
  "@workflow/errors": "5.0.0-beta.10",
64
64
  "@workflow/typescript-plugin": "5.0.0-beta.5",
65
65
  "@workflow/utils": "5.0.0-beta.6",
66
- "@workflow/next": "5.0.0-beta.32",
67
- "@workflow/nest": "5.0.0-beta.32",
68
- "@workflow/nitro": "5.0.0-beta.32",
69
- "@workflow/nuxt": "5.0.0-beta.32",
70
- "@workflow/sveltekit": "5.0.0-beta.32",
71
- "@workflow/rollup": "5.0.0-beta.32"
66
+ "@workflow/next": "5.0.0-beta.34",
67
+ "@workflow/nest": "5.0.0-beta.34",
68
+ "@workflow/nitro": "5.0.0-beta.34",
69
+ "@workflow/nuxt": "5.0.0-beta.34",
70
+ "@workflow/sveltekit": "5.0.0-beta.34",
71
+ "@workflow/rollup": "5.0.0-beta.34"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@types/ms": "2.1.0",
75
75
  "@types/node": "22.19.0",
76
+ "typescript": "^6.0.3",
76
77
  "@workflow/tsconfig": "5.0.0-beta.0"
77
78
  },
78
79
  "peerDependencies": {