[docs] v5 stable docs - #3100
Conversation
v5 goes stable, so the docs swap places: content/docs/v5 and content/worlds/v5 now serve unprefixed (/docs, /worlds, /cookbook) and v4 moves under a /v4 prefix. The versioned source config drives both route trees, so the swap is a routePrefix + source re-binding plus the app/[lang]/v5 -> app/[lang]/v4 tree rename. - Switcher labels: "v5 (Latest)" / "v4 (Maintenance)". - The v5 pre-release banner becomes an amber maintenance notice on v4 pages, pointing at the current version. - /v5/* redirects onto its unprefixed equivalent (bare /v5 needs its own rule, since the wildcard expands to an empty destination). v4 content keeps its /v5/... hrefs on purpose: render-time href rewriting would pull an unprefixed /docs link into the /v4 view. - Version-switcher fallbacks are inverted: pages that exist only in v5 now get /v4 fallbacks, and the two v4-only pages fall back to the root tree. /docs/configuration/* is a real page again. - The link lint's two-space model swaps with the URL space: unprefixed is v5, /v4-prefixed is v4, and redirect destinations resolve against the HTTP space (they are matched before rendering). - Content sweep: drop pre-release version gates and beta dist-tag references from v5 content, and de-prefix self-referential /v5 links.
🦋 Changeset detectedLatest commit: 9abe275 The changes in this PR will be included in the next version bump. This PR includes changesets to release 0 packagesWhen changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
- New top-level `/docs/whats-new` page above Getting Started: highlights, verified breaking-change table, and a "Migrating from V4" section. - New `skills/migrating-workflow-v4-to-v5` agent skill covering the async `getWorld()`, removed `runStep`/`stepEntrypoint`/private subpaths, the `world.streams.*` argument reorder, `steps.get(runId, ...)`, the `getConflict()` -> `Run` shape, and the Nest builder subpath. - Landing on `/docs/getting-started` (where bare `/docs` links redirect) no longer drills the sidebar into the framework list; the root menu stays visible. Both docs layouts moved into their `[[...slug]]` segment so the active slug is available server-side.
🧪 E2E Test Results❌ Some tests failed ❌ Failed E2E Tests📦 Local Production (2 failed)hono-stable (1 failed):
nextjs-webpack-stable (1 failed):
E2E Test SummarySummary
Details by Category✅ ▲ Vercel Production
✅ 💻 Local Development
❌ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
✅ 📋 Other
✅ vercel-multi-region
|
📊 Workflow Benchmarkscommit Backend:
📜 Previous results (7)eb4659dMon, 27 Jul 2026 22:29:19 GMT · run logs
48b5b7aMon, 27 Jul 2026 21:34:44 GMT · run logs
44a53f0Mon, 27 Jul 2026 20:52:48 GMT · run logs
f6861c4Mon, 27 Jul 2026 20:27:40 GMT · run logs
ccbc70aMon, 27 Jul 2026 20:03:01 GMT · run logs
b36c1a1Sat, 25 Jul 2026 00:25:52 GMT · run logs
4a6fabdFri, 24 Jul 2026 23:38:35 GMT · run logs
ℹ️ Metric definitions & methodologyBest/P75/P90/P99 deltas compare against the most recent benchmark run on Metrics — TTFS: time to first step body (in-deployment start() → first step body, deployment clocks) · STSO: step-to-step overhead (gap between consecutive step bodies) · WO: workflow overhead (whole-run time outside step bodies, in-deployment anchored) · SL: stream latency (in-deployment write → read propagation, readAt - writtenAt) · SO: stream overhead (end-to-end write+consume time beyond the modelled generation window) Scenarios — step: one trivial no-op step, no stream; no hooks, so the run stays in turbo mode (in-process fast path) · stream: one streaming step; no hooks, so the run stays in turbo mode (in-process fast path) · hook + stream: registers a hook before one step, which exits turbo mode (dispatch path) · 1020 steps: 1020 trivial sequential steps; STSO is measured between consecutive steps in the given step ranges, and WO is the whole-run overhead outside step bodies · stream latency: parallel reader/writer steps on a dedicated stream; SL is the in-deployment write->read propagation (readAt - writtenAt) · stream overhead (text): writer streams 300 variable-length text token deltas paced at 100/s for 3s (a haiku-size LLM's token throughput) while a parallel reader drains the whole stream; SO is the end-to-end write+consume time beyond the 3s generation window (overhead/backpressure) · stream overhead (structured): same workload as stream overhead (text), but each delta is an AI-SDK-style structured object ({ type: 'text-delta', id, text }) instead of a raw string, so the SO gap vs the text scenario is the added serialization cost 🔴 marks a percentile over its target (within target is left unmarked). Targets (p75/p90/p99, ms) — TTFS 200/300/600 · SL 50/60/125 · SO 250/500/1000 · STSO (1-20) 20/30/60 · STSO (101-120) 30/45/90 · STSO (1001-1020) 40/60/120 All metrics are measured from deployment-side timestamps only. Runs are triggered by an in-deployment route that stamps the anchor ( Cold starts are kept in the numbers on purpose — they are part of real bursty-workload latency. The workbench deployment cold-starts the |
Co-authored-by: Peter Wielander <mittgfu@gmail.com> Signed-off-by: Peter Wielander <mittgfu@gmail.com>
pranaygp
left a comment
There was a problem hiding this comment.
Thorough pass over all 50 files: the route/source/lint re-binding is careful and internally consistent — I diffed both content trees and every one-sided page has a matching version-switcher fallback, the redirect model in lint.ts (destinations resolved in the HTTP space) is right, and I verified the what's-new/skill claims against the actual v4 (origin/stable) and v5 interfaces: stream signatures, steps.get, getConflict, setAttributes alias, start() attributes/region options, seed derivation, env vars (WORKFLOW_MAX_EVENTS[_OVERRIDE], WORKFLOW_TURBO, WORKFLOW_TRACE_MODE, WORKFLOW_PRECONDITION_GUARD, WORKFLOW_STREAM_FLUSH_INTERVAL_MS), and the removed exports all check out.
Two real issues, both detailed inline:
- Blocker — the unprefixed worlds routes still pass
version="v4"(app/[lang]/worlds/[id]/page.tsxandapp/[lang]/worlds/building-a-world/page.tsx, untouched by this PR). After the semantics flip they serve v4 content withnoindexat the canonical/worlds/*URLs, and all 12 community world pages become self-redirect loops. - Stale
lazyDiscoveryclaim inwhats-new.mdxand the migration skill — the option was removed in@workflow/next@5.0.0-beta.21(#2545); neither v4 nor v5 GA has it.
Plus three minor/UX notes inline (banner dark mode, banner deep-linking, and a question about sidebar state now that the docs layouts live inside [[...slug]]).
| // Community worlds have no versioned content — their canonical page lives | ||
| // at /worlds/<id> only. | ||
| if (version !== 'v4' && !isOfficial) { | ||
| if (version !== 'v5' && !isOfficial) { |
There was a problem hiding this comment.
Blocker: this semantics flip ('v4' now = maintenance) has two consumers that weren't updated: docs/app/[lang]/worlds/[id]/page.tsx and docs/app/[lang]/worlds/building-a-world/page.tsx (not in this diff — they were last touched in #2934) still pass version="v4" / generateWorldMetadata(id, 'v4') for the unprefixed routes.
Consequences with this PR as-is:
/worlds/local,/worlds/postgres,/worlds/vercelrender v4 content at the canonical URLs, getrobots: noindex, a· v4title suffix, atext/markdownalternate pointing at/v4/worlds/<id>.md, andWorldVersionSelect current="v4"— while/worlds/<id>.md(served from the swappedworldsSourceBundle) returns v5 content. The v5 worlds pages become unreachable in the HTML UI: both/worlds/*and/v4/worlds/*serve v4.- Every community world (
turso,mongodb,redis,platformatic, … — 12 ids inworlds-manifest.json) now takes this redirect branch on the unprefixed route itself, redirecting to its own URL — an infinite redirect loop./v4/worlds/<community-id>redirects into the same loop.
Fix: flip the four call sites in those two page files to 'v5'. Might also be worth adding /worlds/vercel (title or a v5-only content marker) and one community world to check-docs-smoke.mjs, since the existing og-meta assertions pass either way.
There was a problem hiding this comment.
Fixed in a0adc9e — all four call sites now pass 'v5'. Verified against the built server: /worlds/vercel serves the v5 content with a clean title and no noindex, /worlds/turso and /worlds/platformatic return 200, and /v4/worlds/turso is a single 307 to /worlds/turso. Also added smoke checks (assertWorldVersionMarkers for /worlds/vercel and /v4/worlds/vercel, plus assertServesDirectly('/worlds/turso')) so this can't silently regress.
|
|
||
| const versionPrefix = VERSION_PREFIXES[version]; | ||
| const isPreRelease = version === 'v5'; | ||
| const isMaintenance = version === 'v4'; |
There was a problem hiding this comment.
Same issue as world-detail-page.tsx: the unprefixed docs/app/[lang]/worlds/building-a-world/page.tsx still passes 'v4', so /worlds/building-a-world now serves the v4 content with noindex and a · v4 title. Needs the 'v5' flip in that page file.
There was a problem hiding this comment.
Fixed in a0adc9e — the unprefixed building-a-world page now passes 'v5'; verified it serves with the current-version title and no noindex.
| | `workflow/internal/private` and `@workflow/core/private` removed | These were never public API. The compiler no longer emits imports from them, so regenerate build output rather than importing them yourself. | | ||
| | `@workflow/world-local` stream chunks moved | Chunks live at `streams/chunks/<streamName>/`. Files written in the old flat layout are not read back; local development state from 4.x can be deleted. | | ||
|
|
||
| On Next.js 16.2.0-canary.48 and newer, `withWorkflow` now defaults to `lazyDiscovery: true`; older versions fall back to eager discovery automatically. |
There was a problem hiding this comment.
This is stale: the lazyDiscovery option — and the whole deferred-discovery builder path — was removed in @workflow/next@5.0.0-beta.21 (#2545); discovery at GA is always eager and workflows.lazyDiscovery no longer exists (the only remaining references in the repo are CHANGELOG entries). The default-true behavior described here only existed between beta.8 (#1805) and beta.20, and v4 never had the option either, so there's no net v4 → 5.0.0 change to document. Suggest dropping this paragraph.
There was a problem hiding this comment.
Fixed in a0adc9e — dropped the paragraph. (Same commit also folds in Pranay's Vercel toolbar feedback on this page: intro paragraph replaced by a post-install callout, a CopyPrompt for the agent migration, Python dropped from the frameworks bullet, multi-region cross-links, child-workflows-first wording on the event-limit row, and the duplicate-ID row rephrased.)
| - **Errors keep their type.** `WorkflowRunFailedError.cause` now preserves the original class identity and cause chain. Code that pattern-matched on `error.message` because the class was flattened in 4.x can use `instanceof` — but flag it rather than rewriting error handling unprompted. | ||
| - **A per-run event limit is enforced.** The World supplies the ceiling (25,000 events on the Local and Vercel Worlds) and a run that reaches it fails with `MAX_EVENTS_EXCEEDED`. Flag any workflow with an unbounded loop; the fix is a child run per batch, which is a design change, not a migration edit. | ||
| - **Stream writes flush the leading chunk immediately.** The flush window default went from 10ms to 0. An app that relied on the window to coalesce a burst of tiny chunks can set `streamFlushIntervalMs` or `WORKFLOW_STREAM_FLUSH_INTERVAL_MS`. | ||
| - **`lazyDiscovery: true` is the default** for `withWorkflow` on Next.js ≥ 16.2.0-canary.48. Older Next.js versions fall back to eager discovery automatically, so this needs no edit — mention it only if the build's discovery behavior changes visibly. |
There was a problem hiding this comment.
Same staleness as whats-new.mdx:129: lazyDiscovery was removed in @workflow/next@5.0.0-beta.21 (#2545). Neither v4 nor v5 GA has the option, so this bullet has the agent report a behavior change that doesn't exist — and its implied remedy (workflows: { lazyDiscovery: false }) would introduce an unknown option into next.config. Drop the bullet (and the corresponding row is absent from the what's-new table, so the two docs will agree once both are fixed).
There was a problem hiding this comment.
Fixed in a0adc9e — bullet removed, skill version bumped to 0.2.1.
| export const MaintenanceBanner = ({ pathname }: MaintenanceBannerProps) => { | ||
| const latestHref = buildVersionUrl(pathname, LATEST_VERSION); | ||
| return ( | ||
| <div className="border-b bg-amber-100 px-4 py-2 text-center text-sm"> |
There was a problem hiding this comment.
Minor: the pre-release banner this replaces carried dark: variants (dark:bg-blue-950/40, dark:text-blue-900, …); this one has none, so in dark mode it renders as a bright light-amber band across an otherwise dark page (bg-amber-100 doesn't adapt). Consider matching the old pattern, e.g. dark:bg-amber-950/40 on the container plus dark:text-amber-200-style variants on the text/link/decoration classes.
There was a problem hiding this comment.
Fixed in a0adc9e — added dark:bg-amber-950/40 on the container and dark:text-amber-200 / decoration variants on the text and link, mirroring the old banner's pattern.
| const { lang, slug } = await params; | ||
| return ( | ||
| <div className="bg-background-100"> | ||
| <MaintenanceBanner pathname={`/${lang}/v4/docs`} /> |
There was a problem hiding this comment.
Optional, but now cheap: slug is available in this layout, so the banner could deep-link instead of always landing on /docs → getting-started:
<MaintenanceBanner
pathname={`/${lang}/v4/docs${slug?.length ? `/${slug.join('/')}` : ''}`}
/>buildVersionUrl strips the /v4 prefix, and the v4-only pages are already covered by the version-switcher fallback redirects, so "Go to Workflow 5 (Latest)" — the banner's main CTA — would keep the reader on the equivalent page. (The hardcoded pathname was a pre-existing limitation from the pre-release banner; the layout move is what makes fixing it possible.)
There was a problem hiding this comment.
Done in a0adc9e — the banner now receives the full slug path. Verified in prerendered output (e.g. the v4 foundations page links to /en/docs/foundations) and confirmed against the built server that locale-prefixed URLs go through the i18n strip and then the version-switcher fallback redirects for v4-only pages.
|
|
||
| const Layout = async ({ children, params }: LayoutProps<'/[lang]/docs'>) => { | ||
| const { lang } = await params; | ||
| // This layout lives inside `[[...slug]]` rather than next to it so that |
There was a problem hiding this comment.
Question on the tradeoff: with the layout inside [[...slug]], it belongs to the dynamic segment, so client-side navigation between two docs pages re-renders it with new params — App Router only preserves layout state when the segment (including its param value) is unchanged. The practical symptom would be sidebar scroll position / drill-in state resetting on every page click, which the previous static docs/layout.tsx avoided. The PR verification covered prerendered output; did you also check client-side navigation in the browser? If geistdocs re-scrolls the active item into view this may be a fine trade for the hydration-flash-free activeSlug, but it affects every docs page, so worth confirming before GA.
There was a problem hiding this comment.
Answered this empirically against the built server with a browser: the sidebar's scroll container IS remounted on every client-side navigation (a DOM marker set before navigating does not survive), but geistdocs re-scrolls the active item into view after each mount — verified in the long API-reference section (sidebar overflow ~2300px, scrollTop lands at the active link, active item visible after both direct load and client-side nav). So the practical UX is the standard docs behavior of following the current page, and the tradeoff for server-rendered activeSlug with no hydration flash holds. No change needed — resolving.
…s review feedback - Pass 'v5' on the unprefixed worlds routes: after the version-semantics flip they still passed 'v4', serving v4 content with noindex at the canonical /worlds/* URLs and turning every community world page into a self-redirect loop. Add smoke checks for the version markers and for a community world serving directly. - Remove the lazyDiscovery claim from whats-new and the migration skill: the option was removed in @workflow/next@5.0.0-beta.21 (#2545); neither v4 nor 5.0.0 GA has it. - whats-new (toolbar feedback): replace the intro paragraph with a post-install callout, add a CopyPrompt for the agent migration, drop Python from the frameworks bullet, add multi-region cross-links, recommend child workflows over the event-limit tuning knob, and rephrase the duplicate-ID row around build failures after upgrading. - Maintenance banner: restore dark-mode styling and deep-link "Go to latest" to the same page (version-switcher fallbacks catch v4-only pages). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Pushed a0adc9e addressing the review findings and Pranay's Vercel toolbar comments on /docs/whats-new: Review fixes
Toolbar feedback on /docs/whats-new (threads replied + resolved in the Vercel dashboard)
Verified: |
- Custom Worlds section gains an intro (spec changed, upgrade your World, compatibility reporting returning to the Worlds page) and a CopyPrompt that walks an agent through diffing packages/world* between stable and main to port the spec changes into a custom World. - Drop the deterministic-seed breaking-change row (runs are pinned to the deployment that created them, so cross-version replay cannot occur) and the trailing reference to it; trim the matching seed rationale and checklist item from the migration skill (0.2.2). - Event-limit row: drop 'one run per batch'. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pranaygp
left a comment
There was a problem hiding this comment.
I'm taking over this PR. need to update a bunch of other docs too that are out dated and will get everything up into shape before approving and getting ready for v5 stable
…cs-reads env var Breaking changes (verified against origin/main, which moved past this branch's last merge): - createLocalWorld()/createVercelWorld() removed (#3112) — new Worlds-table row and a mechanical rewrite rule in the migration skill, replacing the skill's now-wrong 'not a breaking change' note. - Deterministic sandbox hardening (#3045) — application-table row (WeakRef, FinalizationRegistry, Atomics.waitAsync, async WebAssembly gone; crypto.subtle.digest synchronous) and a step-3 skill item. - Step queue topics retired (pending retire-step-* changesets) — Worlds-table row and third World contract change in the skill; stepEntrypoint row reworded since standalone step routes no longer exist. Missed v5-only features: - Lineage attributes ($parentRunId/$rootRunId) noted in the Run attributes section; run.cancel({ cancelReason }) in the cancellation section. - 'Errors keep their type' bullet expanded into a Richer serialization highlight with an instanceof example, serializable workflow-function references and Run handles, and the new SerializationError/context-violation errors. - Trace-viewer section gains the local-tooling additions: Nitro /_workflow dev route, inspect --since/--until, past-24h name lookups, World.describeRun region output. /_workflow also added to the Observability page. Env vars: WORKFLOW_DISABLE_ANALYTICS_READS documented under the Vercel World. WORKFLOW_SAFE_MODE was not documented on purpose — it no longer exists in source (only WORKFLOW_SEQUENTIAL_REPLAYS survived). Skill bumped to 0.2.3 (new rewrite rule, sandbox item, queue contract change, createLocalWorld/createVercelWorld intake + fail checks). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Handoff for whoever ships next from git fetch origin pgp/docs-5-ga-v5-gaps
git merge --ff-only FETCH_HEAD # or cherry-pick f6861c412 if the branch has moved
git push origin peter/docs-5-gaIt covers the v5-vs-v4 gap analysis (4 files):
Verified on an isolated worktree at |
Full audit of the v5 content tree against the actual v4 (stable) -> v5 (main) delta. Fixes, by theme: Wrong facts: - building-a-world: World interface gains the now-required specVersion, the v5 capabilities (preconditionGuard, maxConcurrency), analytics, createRunId, describeRun, processExitTriggersQueueRedelivery, and runs.experimentalSetAttributes; steps.get runId is required; queue considerations cover delaySeconds (waits are delayed continuations) and region. - precondition-failed-error: the guard is on by default (=0 opts out), not opt-in via =1. - set-world/create-world: Worlds are statically injected at build time; the runtime env-mutation example could never switch Worlds - replaced with an explicit createWorld() injection example. - world/storage: steps.get runId param typed as required. - comparisons: child runs use start() directly from a workflow (six step-wrapped-start rows across four pages), Temporal search attributes now map to setAttributes()/attribute filtering, and multi-region has shipped. - errors-and-retries: errorCode table now lists all eight run error codes including the v5 MAX_EVENTS_EXCEEDED. Removed 'client' SWC mode (merged into step in 5.0): - code-transform: restructured around the real v5 modes (step/workflow/detect) with a migration callout; step mode now shows the workflow-function throw + workflowId behavior it absorbed. - framework-integrations: app-code transform passes mode 'step'. - nestjs getting started: .swcrc described as step-mode. v4 execution-model prose (steps run inline in v5; no step route): - workflows-and-steps, understanding-directives, code-transform, streaming (lock warning), tracing (queue-delivery list), testing/server-based (flow-route requests), python (drop per-step route claim pending confirmation of the Python topology). Additions: compression-before-encryption in the encryption internals, AbortController/AbortSignal in workflow globals, region option on start() (api-reference + foundations), QueueOptions region/specVersion, stream flush-window note in streaming. Adjacent fixes: createWebhook() is synchronous (14 samples awaited the thenable handle, which resolves the received request instead), unused import in ai/defining-tools, ai/meta.json missing message-queueing, undeclared class in the whats-new serialization sample. Verified: pnpm test:docs 968 snippet typechecks pass, docs link lint 0 errors, pnpm build exit 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The v5 World interface listing referenced analytics with a one-line bullet only. Add a full 'Analytics Interface (Optional)' section: the interface shape (runs/attributes/steps/events/hooks/waits, matching packages/world/src/analytics.ts), the implementer contract (metadata-only rows, latest-write-wins attribute filtering, paired time windows, optional pageInfo, eventual consistency), feature-detection semantics (tooling prefers analytics when present, falls back to Storage), and which first-party Worlds implement it (Vercel only). Cross-linked from the World-members list and to the Analytics API reference, which was audited against source (pageInfo shape, attribute-key fields, 24h default window, hooks.get signature) and is already accurate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
karthikscale3
left a comment
There was a problem hiding this comment.
The Analytics section is implementor-facing, so I think it should define portable interface semantics and keep Vercel observability-pipeline behavior in the Vercel World documentation. I left two concrete wording suggestions below.
|
|
||
| ## Analytics Interface (Optional) | ||
|
|
||
| The optional `analytics` namespace serves observability surfaces — dashboards, `workflow inspect`, the local web UI — with **metadata-only** listings that can scan large numbers of runs without touching payload data. Tooling feature-detects it: when `world.analytics` is present it is preferred for listings and attribute search, and when it is absent, tooling falls back to the Storage APIs. Of the first-party Worlds, only the [Vercel World](/worlds/vercel) implements it (backed by the observability data pipeline); the Local and Postgres Worlds leave it `undefined`. |
There was a problem hiding this comment.
Separate the portable purpose and feature-detection behavior from the first-party implementation note, without tying the interface to Vercel's observability pipeline.
| The optional `analytics` namespace serves observability surfaces — dashboards, `workflow inspect`, the local web UI — with **metadata-only** listings that can scan large numbers of runs without touching payload data. Tooling feature-detects it: when `world.analytics` is present it is preferred for listings and attribute search, and when it is absent, tooling falls back to the Storage APIs. Of the first-party Worlds, only the [Vercel World](/worlds/vercel) implements it (backed by the observability data pipeline); the Local and Postgres Worlds leave it `undefined`. | |
| The optional `analytics` namespace provides **metadata-only** access to runs and their related records. It is intended for observability and discovery surfaces such as dashboards, `workflow inspect`, and the local web UI. Implementations can optimize these queries independently of payload storage. | |
| Tooling feature-detects this namespace. When `world.analytics` is available, tooling prefers it for listings and attribute search; otherwise, it uses the Storage APIs. Among the first-party Worlds, only the [Vercel World](/worlds/vercel) currently implements it. The Local and Postgres Worlds leave it `undefined`. |
There was a problem hiding this comment.
Applied in 9abe275 — the intro now leads with the portable purpose (metadata-only access, implementations free to optimize independently of payload storage) and puts feature detection in its own paragraph, with the pipeline-backing note dropped from the interface docs. That Vercel-specific behavior now lives in the Vercel World page's Observability section instead.
| If you implement it, honor the contract that makes the namespace safe to expose broadly: | ||
|
|
||
| - **Metadata only.** Rows must never include run inputs/outputs, step data, or hook tokens — the `Analytics*` row schemas in `@workflow/world` define exactly which fields are allowed. There is no `resolveData` escape hatch here; payload access stays on the Storage APIs. | ||
| - **Attribute filtering is latest-write-wins.** `runs.list({ attributes })` matches a run's most recent value for each key (up to 8 pairs), and reserved `$`-prefixed keys are valid in filters even though user writes to them are rejected. | ||
| - **Time windows are the query contract.** `startTime`/`endTime` must be provided together; a bounded window lets the backend prune its scan. Responses may carry an optional `pageInfo` describing the scannable window (lookback days, window starts, upgrade availability) — return it if your backend has retention limits so tools can size date pickers. | ||
| - **Eventual consistency is acceptable.** Analytics data may trail live state by a few seconds; tools treat it as a listing surface, not a source of truth. |
There was a problem hiding this comment.
These are useful interface guarantees, but they can be phrased without prescribing Vercel/ClickHouse query behavior. This also makes the paired-boundary rule explicit when both values are omitted.
| If you implement it, honor the contract that makes the namespace safe to expose broadly: | |
| - **Metadata only.** Rows must never include run inputs/outputs, step data, or hook tokens — the `Analytics*` row schemas in `@workflow/world` define exactly which fields are allowed. There is no `resolveData` escape hatch here; payload access stays on the Storage APIs. | |
| - **Attribute filtering is latest-write-wins.** `runs.list({ attributes })` matches a run's most recent value for each key (up to 8 pairs), and reserved `$`-prefixed keys are valid in filters even though user writes to them are rejected. | |
| - **Time windows are the query contract.** `startTime`/`endTime` must be provided together; a bounded window lets the backend prune its scan. Responses may carry an optional `pageInfo` describing the scannable window (lookback days, window starts, upgrade availability) — return it if your backend has retention limits so tools can size date pickers. | |
| - **Eventual consistency is acceptable.** Analytics data may trail live state by a few seconds; tools treat it as a listing surface, not a source of truth. | |
| If you implement this namespace, observe the following requirements: | |
| - **Metadata only.** Analytics responses must not include run inputs or outputs, step data, hook tokens, or other payload data. The `Analytics*` schemas exported by `@workflow/world` define the complete set of permitted fields. Payload retrieval remains exclusively available through the Storage APIs. | |
| - **Attribute filters use the latest value.** `runs.list({ attributes })` evaluates each filter against the run's most recently written value for that key. A request may contain up to eight key-value pairs. Reserved `$`-prefixed attributes are valid filters, although users cannot write them directly. | |
| - **Time boundaries must be paired.** `startTime` and `endTime` may either both be omitted or both be supplied. Responses may include `pageInfo` describing retention and the available query window. Implementations with retention limits should return this information so tooling can present valid date ranges. | |
| - **Results may be eventually consistent.** Analytics records may lag live workflow state. Consumers use this namespace for discovery and listing; Storage remains the authoritative interface for current workflow state and payload access. |
There was a problem hiding this comment.
Applied in 9abe275, verbatim — including the explicit both-omitted-or-both-supplied phrasing, which matches the AnalyticsListRunsParams doc comments in @workflow/world. The Vercel/ClickHouse specifics that were dropped here (bounded-window scan speed, plan lookback surfacing as observability-upgrade-required + pageInfo) moved to the Vercel World page's Observability section so they're still documented, just in the right place.
… attributes across comparisons Toolbar feedback on the Temporal comparison: - Search attributes move out of the 'no direct equivalent' table into the migration mapping — setAttributes()/attributes-on-start() is the direct equivalent. trigger.dev gains the matching row (run tags / metadata.set()); the other platforms have no run-tag concept to map. - The retry-policy row no longer reads as a limitation: declarative Temporal policies (backoffCoefficient, nonRetryableErrorTypes, maximumAttempts) map to idiomatic code — maxRetries, FatalError, and backoff derived from getStepMetadata().attempt via RetryableError's retryAfter — with links to the errors-and-retries examples. Same treatment for the Cloudflare, Inngest, and Step Functions retry rows. - code-transform: 'Changed in 5.0' callout on __step_registrations.js explaining the v4 step route + step queue topic merger, linking the v4 version of the page. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* origin/main: Prewarm only appended replay payloads (#3131) feat: publish each run's X25519 public key on the run entity (#3095) feat(core): route sealed envelopes through the serialization layer (#3094) docs: redirect retired migration-guides URLs to comparisons (#3127) feat(core): add `encp` sealed-box encryption primitive (#3093) chore(core): clarify runtime comments (#3111) Remove obsolete world factory aliases (#3112) feat(core): deterministic sandbox hardening (#3045)
- Map @workflow/world-local in the docs snippet type-checker and drop the @skip-typecheck escape on the set-world example; also unskip the three complete input samples on code-transform. Four more snippets are now machine-checked (972 passing, was 968). - World-upgrade prompt diffs against the release tag being upgraded to (workflow@5.x.y) instead of a moving main, falling back to main. - World SDK index points World authors at the top-level interface members (specVersion, capabilities, createRunId, describeRun, ...) documented in Building a World. The preceding merge of origin/main makes the GA-forward docs claims true in-tree (factory aliases actually removed by #3112, sandbox hardening from #3045 including the workflow-globals digest row this branch had stale) and picks up the migration-guides redirects (#3127), whose destinations now pass this branch's redirect-destination lint. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A correlation id names a step, hook or wait within its run, so the lookup is now scoped to one run. Adds it to the World migration table and to the v4→v5 migration skill.
… to Vercel World page Apply karthikscale3's review suggestions on Building a World: the analytics namespace intro and implementation requirements now describe portable interface semantics (paired-or-omitted time bounds, latest-value attribute filters, metadata-only schemas) without prescribing Vercel/ClickHouse query behavior. The Vercel-specific details (observability pipeline backing, bounded-window scan speed, plan lookback via observability-upgrade-required and pageInfo) move to the Vercel World Observability section. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Makes v5 the default documentation version for the 5.0.0 GA. Companion to #3090 (exit changeset pre mode) and #3091 (pin 4.x publishes to
previous).Version flip and routing
content/docs/v5andcontent/worlds/v5now serve unprefixed —/docs,/worlds,/cookbook— and v4 moves under a/v4prefix. The versioned source config drives both route trees (the root tree always rendersversionedSources.current), so this is aroutePrefixmove plus source re-binding, not a restructure. The only file moves areapp/[lang]/v5/**→app/[lang]/v4/**.v5 (Latest)/v4 (Maintenance);current: 'v5'.pre-release-banner.tsx→maintenance-banner.tsx. v4 pages carry an amber notice (with dark-mode styling) whose "Go to Workflow 5 (Latest)" link deep-links to the same page on the current version; v4-only pages land on the nearest section index via the fallback redirects. v4 pages keeprobots: noindex, follow.docs/next.config.ts):/v5/*→ unprefixed equivalent (permanent; bare/v5gets its own rule because:path*expands to an emptyLocationotherwise). The world-docs and api-reference restructure rules are mirrored onto/v4/docs/*, and every page that exists in only one tree has a version-switcher fallback (verified by diffing the two content trees)./v4worlds routes pass an explicit version into the shared page components, whose semantics flipped with the switch — smoke checks (assertWorldVersionMarkers,assertServesDirectly('/worlds/turso')) now guard the pairing so the canonical/worlds/*pages can't silently serve the maintenance version or self-redirect-loop again.docs/scripts/lint.ts): the two-space model swaps with the URL space; redirect destinations resolve against the real HTTP space since redirects are matched before render-time href rewriting.rewriteHrefForVersionpulls unprefixed hrefs on v4 pages into the/v4view, so a/v5/...href is the only way for v4 content to point at a current page. Three such links are kept deliberately; the lint models this.[[...slug]]segment soparams.slugis available server-side, and the getting-started section no longer drills in on/docs/getting-started(where every bare/docslink lands). The per-navigation layout remount this causes is fine in practice — verified in a browser that geistdocs re-scrolls the active sidebar item into view after each navigation.What's new page and migration skill
/docs/whats-new: new top-level page, first in the v5 sidebar. Leads with faster/cheaper runs (single-pass invocations, skipped persistence round trips, payload compression — up to 80% lower compute and 70% lower storage cost on Vercel Workflows), then multi-region, in-flight cancellation, attributes GA, richer serialization, and the redesigned trace viewer. A<CopyPrompt>after the install snippet gives a paste-ready agent prompt for the upgrade, and the Custom Worlds section opens with a World-upgrade prompt that diffspackages/world*betweenstableandmain.stable, then re-swept twice: once against the 5.0.0 draft changelog and once against post-branchmain— picking up the late-landing GA breakers (createLocalWorld/createVercelWorldremoval (Remove obsolete world factory aliases #3112), deterministic-sandbox hardening (feat(core): deterministic sandbox hardening #3045), step-queue-topic retirement).skills/migrating-workflow-v4-to-v5(v0.2.3): mechanical rewrite rules, behavior changes the agent must report rather than silently "fix", World contract changes, a fixed output shape, and a fail-the-migration checklist.WORKFLOW_MAX_EVENTS_OVERRIDE(Runtime Tuning › Limits),WORKFLOW_MAX_EVENTS(Worlds › Local),WORKFLOW_DISABLE_ANALYTICS_READS(Worlds › Vercel).Full-tree staleness sweep
The whole v5 content tree (165 docs pages + worlds + cookbook) was audited against the actual v4→v5 delta, with every finding verified against SDK source. 30 files fixed in
44a53f0be:clientSWC mode:code-transform.mdxrestructured around the real v5 modes (step/workflow/detect) with a migration callout;framework-integrations.mdxand the NestJS guide now pass/describemode: "step".specVersion, the v5 capabilities (preconditionGuard,maxConcurrency— with the fail-closed warning),analytics,createRunId(the multi-region hook),describeRun,experimentalSetAttributes, a requiredsteps.getrunId, and queue guidance fordelaySeconds(a World that ignores it busy-loops everysleep()) andregion.=1);set-world/create-worldno longer describe runtimeWORKFLOW_TARGET_WORLDswitching, which build-time World injection makes impossible.start()directly from a workflow (six step-wrapped-start()rows across four pages), Temporal search attributes map tosetAttributes(), multi-region is shipped.AbortController/AbortSignalin workflow globals,regiononstart()(API reference + foundations), all eight run error codes includingMAX_EVENTS_EXCEEDED, stream flush-window note.createWebhook()(whose thenable handle resolves the received request), an unused import, a missing sidebar entry, and an undeclared class that failed snippet typecheck.One deliberate soft-fix: the Python guide's "each step compiles into an isolated route" was made topology-neutral rather than corrected, pending confirmation of the Python SDK's route model.
Verification
pnpm test:docs: 972 documentation snippet typechecks pass (the snippet type-checker now covers@workflow/world-local, so theset-worldexample and the code-transform input samples are machine-checked instead of skipped).bun ./scripts/lint.ts: 0 errored files, 0 errors — every link/anchor resolves in both URL spaces.pnpm build: exit 0; key fixes confirmed in prerendered HTML (version markers, Detect Mode, error-code table, set-world example).pnpm start; curl matrix over redirects and worlds routes (/worlds/vercel200 v5-titled,/worlds/turso200,/v4/worlds/tursosingle 307,/v5/*→ unprefixed 308s, markdown + sitemap routes 200).origin/main(factory-alias removal Remove obsolete world factory aliases #3112, sandbox hardening feat(core): deterministic sandbox hardening #3045, migration-guides redirects docs: redirect retired migration-guides URLs to comparisons #3127), so the GA-forward claims in the breaking-change tables are true in-tree and docs: redirect retired migration-guides URLs to comparisons #3127's redirect destinations pass this branch's lint.Empty changeset — docs and skills only, no published package changes.
Docs Preview
Base: https://workflow-docs-git-peter-docs-5-ga.vercel.sh (Vercel SSO). One row per changed page.
New page
Foundations & observability
MAX_EVENTS_EXCEEDEDregionoption bullet with multi-region link/v5/_workflowdev routeHow it works
mode: "step"API reference
regionandattributesbullets=0opts out), not opt-inAbortController/AbortSignaladded to the globals listcreateWorld()injectionQueueOptionslistsregionandspecVersionsteps.getrunIdparam typed as requiredComparisons
start(); search attributes now map tosetAttributes()step.invoke()row uses directstart()triggerAndWait()row uses directstart()+ awaitedRunstart()rows (Map, child state machine, Distributed Map) use directstart()Getting started & testing
.swcrcdescribed as step-mode (client mode removed)5.0.0-beta.33→5.0.05.0.0-beta.33→5.0.0Configuration, deploying, errors, AI
WORKFLOW_MAX_EVENTS_OVERRIDE;/v5link de-prefixedWORKFLOW_MAX_EVENTS(Local) andWORKFLOW_DISABLE_ANALYTICS_READS(Vercel) documentedcreateWebhook()samples no longer await the thenable handlecreateWebhook()fixmessage-queueingpage (meta.json)/v5links de-prefixedWorlds
specVersion, capabilities,createRunId,describeRun, …), requiredsteps.getrunId,delaySeconds/regionqueue guidance, new Analytics section5.0.0-beta.33→5.0.0references;· v4title + noindexv4 (maintenance) docs
5.0.0; intentional/v5cross-version linkworkflow@4.0.1Routing & chrome spot-checks
/v5/*permanent redirect/v5redirect🤖 Generated with Claude Code