Conversation
Prevent confusion when Next.js apps live below the repository root and workflow code imports sibling workspace packages. This documents the output tracing root requirement at the point where users configure withWorkflow, so monorepo setups follow the same working patterns as the shipped Next.js integration instead of failing due to unresolved workspace imports. Ploop-Iter: 1
Automated checkpoint commit. Ploop-Iter: 2
Automated checkpoint commit. Ploop-Iter: 3
Capture recent workflow documentation updates so the public docs and package guidance stay aligned with the implementation and current docs-typecheck behavior. Ploop-Iter: 1
Document the current docs verification contract so contributors do not assume JavaScript examples are type-checked when only TypeScript snippets are enforced today. Add regression coverage around the README language and framework integration guidance to keep those docs aligned with the implemented Next.js and docs-typecheck behavior as future changes land. Ploop-Iter: 2
Document the most common causes of the invalid workflow function error so users can resolve start() failures from the API docs and Next.js setup flow without having to infer build-time requirements from runtime behavior. Keep the new troubleshooting page aligned with the shipped runtime message and add regression coverage so future wording or cross-link changes do not silently break that guidance. Ploop-Iter: 3
Document both supported NestJS module formats and add a regression check so the getting-started guide stays aligned with the package README as the integration evolves. Ploop-Iter: 1
Keep the NestJS getting-started guide consistent across the ESM and CommonJS paths so readers do not mix module settings or import styles mid-setup. Strengthen the docs regression coverage around the later guide sections so future edits are more likely to preserve the supported CommonJS path documented in the package README. Ploop-Iter: 2
Document the recently added troubleshooting and observability patterns so the public docs stay aligned with the behavior users now encounter in practice. This keeps the NestJS guide, workflow API reference, and docs regression coverage in sync with the runtime-facing guidance from recent changes. Ploop-Iter: 3
Why: keep the docs aligned with recent API and runtime behavior changes so examples and reference pages don’t drift from the supported surface. Ploop-Iter: 1
Add regression coverage for doc surfaces that are easy to drift from implementation so docs audits catch mismatches early and keep published guidance aligned with the supported API surface. Ploop-Iter: 2
Keep new observability and server-testing guidance anchored to machine-readable interfaces so follow-up implementation changes do not silently drift away from the documented agent and automation patterns. Ploop-Iter: 3
Keep the docs consistent so users get the same guidance when debugging hook token collisions and correlating workflow events with platform logs. This prevents the event-sourcing reference from drifting away from the observability and error docs, and adds guard tests to catch regressions. Ploop-Iter: 1
|
🧪 E2E Test Results❌ Some tests failed Summary
❌ Failed Tests▲ Vercel Production (2 failed)express (1 failed):
nuxt (1 failed):
🌍 Community Worlds (56 failed)mongodb (3 failed):
redis (2 failed):
turso (51 failed):
Details by Category❌ ▲ Vercel Production
✅ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
❌ 🌍 Community Worlds
✅ 📋 Other
❌ Some E2E test jobs failed:
Check the workflow run for details. |
📊 Benchmark Results
workflow with no steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) workflow with 1 step💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Express | Nitro workflow with 10 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Next.js (Turbopack) | Nitro workflow with 25 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Next.js (Turbopack) | Nitro workflow with 50 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) Promise.all with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) Promise.all with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) | Express Promise.all with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) Promise.race with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) | Express Promise.race with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Next.js (Turbopack) | Nitro Promise.race with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) workflow with 10 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Express | Nitro workflow with 25 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) workflow with 50 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) workflow with 10 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Nitro | Express workflow with 25 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Nitro | Express workflow with 50 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Nitro | Express Stream Benchmarks (includes TTFB metrics)workflow with stream💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) stream pipeline with 5 transform steps (1MB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) 10 parallel streams (1MB each)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) fan-out fan-in 10 streams (1MB each)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) SummaryFastest Framework by WorldWinner determined by most benchmark wins
Fastest World by FrameworkWinner determined by most benchmark wins
Column Definitions
Worlds:
|
…itecture-d-2026-02-27T14-07-52-1.png from the repo root, workbench/fastify/public/index.html (a Nitro example mistakenly placed in the fastify workbench by a ploop checkpoint), all .claude/worktrees/* submodule references, and all 15 string-presence audit guard tests in packages/docs-typecheck/src/__tests__/ (they only assert keyword presence, not semantic correctness). None of these belong in the docs audit PR.
d183a8f to
3dd651e
Compare
VaguelySerious
left a comment
There was a problem hiding this comment.
LGTM! Very helpful. I left a bunch of small feedback to address and then it's good to go
|
|
||
| ## Machine-Readable Surfaces | ||
|
|
||
| For automations, custom dashboards, and agentic tooling, prefer stable fields and lifecycle events over parsing human-readable UI labels. |
There was a problem hiding this comment.
Was someone trying to parse UI labels? I don't feel like I know what this is about as someone just reading the observability docs
| npx workflow inspect runs | ||
| ``` | ||
|
|
||
| ## Machine-Readable Surfaces |
There was a problem hiding this comment.
This section seems misplaced, move it to bottom of this file?
There was a problem hiding this comment.
yeah this whole section seems unnecessary imo
There was a problem hiding this comment.
it belongs in the world docs I think and it's already documented there
| ``` | ||
|
|
||
| <Callout type="info"> | ||
| This is the same mechanism that [`withWorkflow()`](/docs/api-reference/workflow-next/with-workflow#monorepos-and-workspace-imports) uses in Next.js — it reads `outputFileTracingRoot` from the Next.js config and passes it as `projectRoot` to the builder. Without this, workflow transforms may fail to resolve workspace module specifiers. |
There was a problem hiding this comment.
This might not be relevant for someone reading this section?
| - `correlationId` links together events for the same entity lifecycle | ||
| - `requestId` tells you which inbound platform request created or updated the event | ||
|
|
||
| On Vercel, `requestId` comes from the platform request ID when available. |
There was a problem hiding this comment.
| On Vercel, `requestId` comes from the platform request ID when available. | |
| On Vercel, `requestId` is the platform request ID when available. |
There was a problem hiding this comment.
we should also mention this is optional for metrics since only vercel implements this. Other worlds are not expected to have a requestId
| Unlike other entities, hooks don't have a `status` field—the states above are conceptual. An "active" hook is one that exists in storage, while "disposed" means the hook has been deleted. When a `hook_disposed` event is created, the hook record is removed rather than updated. | ||
|
|
||
| While a hook is active, its token is reserved and cannot be used by other workflows. If a workflow attempts to create a hook with a token that is already in use by another active hook, a `hook_conflict` event is recorded instead of `hook_created`. This causes the hook's promise to reject with a `WorkflowRuntimeError`, failing the workflow gracefully. See the [hook-conflict error](/docs/errors/hook-conflict) documentation for more details. | ||
| While a hook is active, its token is reserved and cannot be used by other workflows. If a workflow attempts to create a hook with a token that is already in use by another active hook, a `hook_conflict` event is recorded instead of `hook_created`. This causes the hook's promise to reject with a `HookConflictError`, which you can detect with `HookConflictError.is(error)`. See the [hook-conflict error](/docs/errors/hook-conflict) documentation for more details. |
There was a problem hiding this comment.
The part we're cutting out here is that this would bubble up as a WorkflowRuntimeError.
| While a hook is active, its token is reserved and cannot be used by other workflows. If a workflow attempts to create a hook with a token that is already in use by another active hook, a `hook_conflict` event is recorded instead of `hook_created`. This causes the hook's promise to reject with a `HookConflictError`, which you can detect with `HookConflictError.is(error)`. See the [hook-conflict error](/docs/errors/hook-conflict) documentation for more details. | |
| While a hook is active, its token is reserved and cannot be used by other workflows. If a workflow attempts to create a hook with a token that is already in use by another active hook, a `hook_conflict` event is recorded instead of `hook_created`. This causes the hook's promise to reject with a `HookConflictError`, which you can detect with `HookConflictError.is(error)`. When unhandled, this fails the workflow with a `WorkflowRuntimeError`. See the [hook-conflict error](/docs/errors/hook-conflict) documentation for more details. |
There was a problem hiding this comment.
@VaguelySerious I don't think this becomes a WorkflowRuntimeError. It should just fail the workflow run if HookConflictError is unhandled in the workflow context.
WorkflowRuntimeError only happens when something is broken in the world/workflow/queue internals like a corrupted event log, etc.
|
|
||
| Tool calls can be implemented as workflow steps for automatic retries, or as regular workflow-level logic utilizing core library features such as [`sleep()`](/docs/api-reference/workflow/sleep) and [Hooks](/docs/foundations/hooks). | ||
|
|
||
| {/* @skip-typecheck - uses DurableAgentOptions properties not yet in published dist types */} |
There was a problem hiding this comment.
I feel like CI was previously passing. Why are we adding all these typecheck skips? If they're not needed, we should do without
There was a problem hiding this comment.
+1 - it was already passing on latest main
| - The agent processes tool calls iteratively until completion or `maxSteps` is reached | ||
| - **Default `maxSteps` is unlimited** - set a value to limit the number of LLM calls | ||
| - The `stream()` method returns `{ messages, steps, experimental_output, uiMessages }` containing the full conversation history, step details, optional structured output, and optionally accumulated UI messages | ||
| - The `stream()` method returns `{ messages, steps, toolCalls, toolResults, experimental_output, uiMessages }` containing the full conversation history, per-step details, machine-readable tool activity, optional structured output, and optionally accumulated UI messages |
There was a problem hiding this comment.
machine-readable tool activity
What is this? Did you mean tool call information?
| - The `stream()` method returns `{ messages, steps, toolCalls, toolResults, experimental_output, uiMessages }` containing the full conversation history, per-step details, machine-readable tool activity, optional structured output, and optionally accumulated UI messages | |
| - The `stream()` method returns `{ messages, steps, toolCalls, toolResults, experimental_output, uiMessages }` containing the full conversation history, step details, tool call details, optional structured output, and optionally accumulated UI messages |
|
|
||
| ### List Workflow Runs (Display Names) | ||
|
|
||
| List workflow runs and derive user-friendly names from the machine-readable `workflowName` field: |
There was a problem hiding this comment.
| List workflow runs and derive user-friendly names from the machine-readable `workflowName` field: | |
| List workflow runs and derive human-readable names from the `workflowName` field: |
| import { sendReminder } from "./workflows/send-reminder"; | ||
|
|
||
| export async function POST() { | ||
| await start(async () => sendReminder("hello@example.com")); |
There was a problem hiding this comment.
| await start(async () => sendReminder("hello@example.com")); | |
| // Does NOT work | |
| await start(async () => sendReminder("hello@example.com")); |
|
|
||
| ### Aborting Long-Running Streams | ||
|
|
||
| Use `timeout` to abort a stream automatically after a fixed duration. If both `timeout` and `abortSignal` are provided, whichever triggers first will abort the operation: |
There was a problem hiding this comment.
AbortSignal won't actually work till we implement #1301
I haven't tried timeout. might need to have dig in and validate that it actually works too (and should add an e2e test maybe also if possible)?
| import { withWorkflow } from "workflow/next"; | ||
|
|
||
| const nextConfig: NextConfig = { | ||
| outputFileTracingRoot: resolve(process.cwd(), "../.."), |
There was a problem hiding this comment.
by default Next.js should detect the right value we should only mention this as a workaround if things are detecting correctly
|
|
||
| | Option | Type | Default | Description | | ||
| | --- | --- | --- | --- | | ||
| | `workflows.lazyDiscovery` | `boolean` | `false` | When `true`, defers workflow discovery until files are requested instead of scanning eagerly at startup. Useful for large projects where startup time matters. | |
There was a problem hiding this comment.
lazyDiscovery is actually something that @ijjk wants to ship as the default for GA
|
|
||
| ## Options | ||
|
|
||
| `withWorkflow` accepts an optional second argument to control local development behavior. |
There was a problem hiding this comment.
| `withWorkflow` accepts an optional second argument to control local development behavior. | |
| `withWorkflow` accepts an optional second argument to configure the Next.js integration. |
it's not development behavior specific
| ### Configure NestJS for ESM | ||
| ### Choose Your Module Format | ||
|
|
||
| NestJS with SWC uses ES modules. Add `"type": "module"` to your `package.json`: |
There was a problem hiding this comment.
@VaguelySerious can you validate the NestJS changes here?
There was a problem hiding this comment.
Yes this looks right. A community PR added these attributes at some point
| <Callout type="info"> | ||
| If your Next.js app lives inside a monorepo and your workflows import code from sibling workspace packages, set `outputFileTracingRoot` to the workspace root. See [`withWorkflow()`](/docs/api-reference/workflow-next/with-workflow#monorepos-and-workspace-imports) for the full example. | ||
| </Callout> |
| <Callout type="info"> | ||
| If your Next.js app lives inside a monorepo and your workflows import code from sibling workspace packages, set `outputFileTracingRoot` to the workspace root. See [`withWorkflow()`](/docs/api-reference/workflow-next/with-workflow#monorepos-and-workspace-imports) for the full example. | ||
| </Callout> | ||
|
|
||
| <Callout type="info"> | ||
| You can pass a second argument to `withWorkflow()` to control local development behavior, such as enabling lazy workflow discovery or overriding the local port. See [`withWorkflow()` Options](/docs/api-reference/workflow-next/with-workflow#options) for details. | ||
| </Callout> |
There was a problem hiding this comment.
let's drop this callout honestly. let's not make agents use this unless they actually are trying to debug something so let's not load it into context so early
| ### `start()` says it received an invalid workflow function | ||
|
|
||
| If you see this error: | ||
|
|
||
| ``` | ||
| 'start' received an invalid workflow function. Ensure the Workflow Development Kit is configured correctly and the function includes a 'use workflow' directive. | ||
| ``` | ||
|
|
||
| Check both of these first: | ||
|
|
||
| 1. The workflow function includes `"use workflow"`. | ||
| 2. Your `next.config.ts` is wrapped with [`withWorkflow()`](/docs/api-reference/workflow-next/with-workflow). | ||
|
|
||
| See [start-invalid-workflow-function](/docs/errors/start-invalid-workflow-function) for full examples and fixes. | ||
|
|
||
| ## Next Steps |
There was a problem hiding this comment.
should a version of this go into every getting started guide then? not just next.js only
| let server: ChildProcess | null = null; | ||
| const PORT = "4000"; | ||
|
|
||
| function emitSetupLog(event: string, fields: Record<string, unknown> = {}) { |
There was a problem hiding this comment.
hmm why are we changing this? not sure I follow
@VaguelySerious your call on this if this seems right
There was a problem hiding this comment.
It looks like this is just a log wrapper that helps agents debug the setup, which I don't oppose. It is a lot of stuff to write into the testing docs, but we don't recommend server-based test anyway since we have the vitest setup, so fine IMO
| {/* @skip-typecheck - uses getRun().wakeUp() not yet in published dist types */} | ||
| ```typescript title="workflows/calculate.server.test.ts" lineNumbers |
There was a problem hiding this comment.
let's drop the skip type checks please. we need confidence that the docs are actually type checked.
if .wakeUp is not published, that's actually a real bug
I think something's wrong with your local cache @johnlindquist and you just need to build the monorepo so that the docs typecheck works
| To add import inference for new symbols, edit `src/import-inference.ts`: | ||
| The docs test suite includes: | ||
| - `docs/content/docs/**/*.mdx` | ||
| - `packages/*/README.md` |
There was a problem hiding this comment.
we should also do this for the SKILL.md actually @johnlindquist
do you wanna extend the docs type checking to validate the skills so that the skills are accurate too? 🙏🏼
pranaygp
left a comment
There was a problem hiding this comment.
thank you so much! this is great. lett manual comments of things we need to fix. excited to ship this after
Summary
Test plan