Skip to content

Commit b176ace

Browse files
docs: March docs audit and alignment (#1466)
* docs: clarify Next monorepo setup 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 * ploop: iteration 2 checkpoint Automated checkpoint commit. Ploop-Iter: 2 * ploop: iteration 3 checkpoint Automated checkpoint commit. Ploop-Iter: 3 * docs: audit recent documentation coverage 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 * docs: align docs-typecheck docs 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 * docs: add start() troubleshooting guidance 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 * docs: align NestJS setup docs 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 * docs: tighten NestJS CommonJS guidance 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 * docs: align docs with recent workflow guidance 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 * docs: audit docs coverage 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 * test: add docs audit guards 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 * docs: add docs audit guards 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 * docs: align observability troubleshooting guidance 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 * Remove the unreferenced image file img-a-clean-minimal-technical-architecture-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. * Address all PR #1466 review feedback from VaguelySerious, pranaygp, and ijjk: 1. Remove the "Machine-Readable Surfaces" section from docs/content/docs/observability/index.mdx (reviewers say it's unnecessary and already in world docs) 2. Remove all @skip-typecheck annotations from durable-agent.mdx (8) and server-based.mdx (1) — types exist in built packages/ai/dist after pnpm build 3. In durable-agent.mdx, change "machine-readable tool activity" to "tool call details" in the stream() return description 4. In durable-agent.mdx "Aborting Long-Running Streams" section, add a warning callout that abortSignal is not yet supported (blocked by #1301), recommend timeout instead 5. In event-sourcing.mdx, update requestId description: "On Vercel, requestId is the platform request ID when available. Other worlds are not expected to provide a requestId." 6. In get-world.mdx, change "user-friendly names from the machine-readable workflowName field" to "human-readable names from the workflowName field" 7. In start-invalid-workflow-function.mdx, add "// Does NOT work" comment above the bad example line 8. In with-workflow.mdx: reframe outputFileTracingRoot as a workaround (Next.js auto-detects by default per ijjk); change options description from "control local development behavior" to "configure the Next.js integration"; scope the callout to "workflows.local options only affect local development" 9. Drop the withWorkflow() options callout from docs/content/docs/getting-started/next.mdx 10. Remove the Next.js-specific outputFileTracingRoot callout from framework-integrations.mdx 11. Add a Troubleshooting section with the start() invalid-workflow-function error to all 9 non-Next getting-started guides (astro, express, fastify, hono, nestjs, nitro, nuxt, sveltekit, vite), each with framework-appropriate config check in point 2 * docs: absorb unique accuracy fixes from PR #1200 Cherry-picked 6 still-needed fixes from #1200 that aren't covered by this audit PR or #1516: - Fix npx workflow description (observability) - Remove fetch from restricted modules list (errors) - Fix package name @workflow-worlds/postgres → @workflow/world-postgres (deploying) - Fix stream wording (foundations/starting-workflows) - Fix import path simple → simple-streaming (foundations/streaming) - Add close(), getEncryptionKeyForRun(), writeToStreamMulti() to World interface, update create() and streamer signatures (deploying/building-a-world) * docs: address review feedback on March docs audit - durable-agent.mdx: "structured tool activity" → "tool call information" per VaguelySerious's suggestion - next.mdx: drop monorepo callout from getting-started per pranaygp (too much context too early; info is in withWorkflow API ref) * docs: fix 2 typecheck failures in encryption and nestjs guides - encryption.mdx: add skip-typecheck for interface signature block (getEncryptionKeyForRun overloads are not runnable code) - nestjs.mdx: add skip-typecheck for WorkflowModule.forRoot config snippet (fragment inside callout, full import shown above) Verified: pnpm vitest run passes 300/300 in docs-typecheck.
1 parent 5aab85b commit b176ace

28 files changed

Lines changed: 700 additions & 110 deletions

docs/content/docs/api-reference/workflow-ai/durable-agent.mdx

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ export default OutputSpecification;`}
213213
- Tools can use core library features like `sleep()` and Hooks within their `execute` functions
214214
- The agent processes tool calls iteratively until completion or `maxSteps` is reached
215215
- **Default `maxSteps` is unlimited** - set a value to limit the number of LLM calls
216-
- The `stream()` method returns `{ messages, steps, experimental_output, uiMessages }` containing the full conversation history, step details, optional structured output, and optionally accumulated UI messages
216+
- 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
217217
- Use `collectUIMessages: true` to accumulate `UIMessage[]` during streaming, useful for persisting conversation state without re-reading the stream
218218
- The `prepareStep` callback runs before each step and can modify model, messages, generation settings, tool choice, and context
219219
- Generation settings (temperature, maxOutputTokens, etc.) can be set on the constructor and overridden per-stream call
@@ -842,6 +842,91 @@ async function saveConversation(messages: UIMessage[]) {
842842
The `uiMessages` property is only available when `collectUIMessages` is set to `true`. When disabled, `uiMessages` is `undefined`.
843843
</Callout>
844844

845+
### Machine-Readable Tool Results
846+
847+
`stream()` returns tool call information you can inspect programmatically. Compare `toolCalls` with `toolResults` to find unresolved tool calls that need client-side handling:
848+
849+
```typescript lineNumbers
850+
import { DurableAgent } from "@workflow/ai/agent";
851+
import { getWritable } from "workflow";
852+
import { z } from "zod";
853+
import type { UIMessageChunk } from "ai";
854+
855+
async function checkOrderStatus({ orderId }: { orderId: string }) {
856+
"use step";
857+
return `Order ${orderId}: shipped`;
858+
}
859+
860+
async function agentWithToolInspection(userMessage: string) {
861+
"use workflow";
862+
863+
const agent = new DurableAgent({
864+
model: "anthropic/claude-haiku-4.5",
865+
tools: {
866+
checkOrderStatus: {
867+
description: "Check order status",
868+
inputSchema: z.object({ orderId: z.string() }),
869+
execute: checkOrderStatus,
870+
},
871+
},
872+
});
873+
874+
const result = await agent.stream({
875+
messages: [{ role: "user", content: userMessage }],
876+
writable: getWritable<UIMessageChunk>(),
877+
});
878+
879+
const unresolved = result.toolCalls.filter( // [!code highlight]
880+
(tc) => !result.toolResults.some((tr) => tr.toolCallId === tc.toolCallId) // [!code highlight]
881+
); // [!code highlight]
882+
883+
if (unresolved.length > 0) {
884+
return {
885+
status: "needs-client-tools",
886+
unresolved,
887+
};
888+
}
889+
890+
return {
891+
status: "complete",
892+
messages: result.messages,
893+
toolResults: result.toolResults,
894+
};
895+
}
896+
```
897+
898+
<Callout type="info">
899+
`toolCalls` and `toolResults` reflect the *last step* of the agent loop. Tools without an `execute` function will appear in `toolCalls` but not in `toolResults`, which is how you detect calls that need client-side handling.
900+
</Callout>
901+
902+
### Aborting Long-Running Streams
903+
904+
Use `timeout` to abort a stream automatically after a fixed duration:
905+
906+
<Callout type="warn">
907+
`abortSignal` is not yet supported and will be available in a future release. Use `timeout` for now.
908+
</Callout>
909+
910+
```typescript lineNumbers
911+
import { DurableAgent } from "@workflow/ai/agent";
912+
import { getWritable } from "workflow";
913+
import type { UIMessageChunk } from "ai";
914+
915+
async function agentWithTimeout(userMessage: string) {
916+
"use workflow";
917+
918+
const agent = new DurableAgent({
919+
model: "anthropic/claude-haiku-4.5",
920+
});
921+
922+
await agent.stream({
923+
messages: [{ role: "user", content: userMessage }],
924+
writable: getWritable<UIMessageChunk>(),
925+
timeout: 30_000, // [!code highlight]
926+
});
927+
}
928+
```
929+
845930
## See Also
846931

847932
- [Building Durable AI Agents](/docs/ai) - Complete guide to creating durable agents

docs/content/docs/api-reference/workflow-api/get-world.mdx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,57 @@ const hydrated = hydrateResourceIO(step, observabilityRevivers); // [!code highl
6666

6767
See [Observability Utilities](/docs/api-reference/workflow-api/world/observability) for the full hydration, parsing, and encryption API.
6868

69+
### List Workflow Runs (Display Names)
70+
71+
List workflow runs and derive human-readable names from the `workflowName` field:
72+
73+
```typescript lineNumbers
74+
import { getWorld } from "workflow/runtime";
75+
import { parseWorkflowName } from "@workflow/utils/parse-name"; // [!code highlight]
76+
77+
export async function GET(req: Request) {
78+
const url = new URL(req.url);
79+
const cursor = url.searchParams.get("cursor") ?? undefined;
80+
81+
try {
82+
const world = getWorld(); // [!code highlight]
83+
const runs = await world.runs.list({
84+
pagination: { cursor },
85+
resolveData: "none",
86+
});
87+
88+
return Response.json({
89+
data: runs.data.map((run) => {
90+
const parsed = parseWorkflowName(run.workflowName); // [!code highlight]
91+
92+
return {
93+
runId: run.runId,
94+
// Use shortName for UI display (e.g., "processOrder") // [!code highlight]
95+
displayName: parsed?.shortName ?? run.workflowName, // [!code highlight]
96+
// Module info available for debugging // [!code highlight]
97+
module: parsed?.moduleSpecifier, // [!code highlight]
98+
status: run.status,
99+
startedAt: run.startedAt,
100+
completedAt: run.completedAt,
101+
};
102+
}),
103+
cursor: runs.cursor,
104+
});
105+
} catch (error) {
106+
return Response.json(
107+
{ error: "Failed to list workflow runs" },
108+
{ status: 500 }
109+
);
110+
}
111+
}
112+
```
113+
114+
<Callout type="info">
115+
The `workflowName` field contains a machine-readable identifier like `workflow//./src/workflows/order//processOrder`.
116+
Use `parseWorkflowName()` from `@workflow/utils/parse-name` to extract the `shortName` (e.g., `"processOrder"`)
117+
and `moduleSpecifier` for display in your UI.
118+
</Callout>
119+
69120
## Related Functions
70121

71122
- [`getRun()`](/docs/api-reference/workflow-api/get-run) - Higher-level API for working with individual runs by ID.

docs/content/docs/api-reference/workflow-api/start.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ Learn more about [`WorkflowReadableStreamOptions`](/docs/api-reference/workflow-
5656
* All arguments must be [serializable](/docs/foundations/serialization).
5757
* When `deploymentId` is provided, the argument types and return type become `unknown` since there is no guarantee the workflow function's types will be consistent across different deployments.
5858

59+
<Callout type="info">
60+
If `start()` throws `'start' received an invalid workflow function. Ensure the Workflow Development Kit is configured correctly and the function includes a 'use workflow' directive.`, the passed function was not transformed as a workflow. The two most common causes are a missing `"use workflow"` directive or missing framework integration. See [start-invalid-workflow-function](/docs/errors/start-invalid-workflow-function).
61+
</Callout>
62+
5963
## Examples
6064

6165
### With Arguments

docs/content/docs/api-reference/workflow-next/with-workflow.mdx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,57 @@ const workflowConfig = {}
2727
export default withWorkflow(nextConfig, workflowConfig); // [!code highlight]
2828
```
2929

30+
### Monorepos and Workspace Imports
31+
32+
By default, Next.js detects the correct workspace root automatically. If your Next.js app lives in a subdirectory such as `apps/web` and workspace resolution is not working correctly, you can set `outputFileTracingRoot` as a workaround:
33+
34+
```typescript title="apps/web/next.config.ts" lineNumbers
35+
import { resolve } from "node:path";
36+
import type { NextConfig } from "next";
37+
import { withWorkflow } from "workflow/next";
38+
39+
const nextConfig: NextConfig = {
40+
outputFileTracingRoot: resolve(process.cwd(), "../.."),
41+
};
42+
43+
export default withWorkflow(nextConfig);
44+
```
45+
46+
<Callout type="info">
47+
Use the smallest directory that contains every workspace package imported by your workflows. If your app already lives at the repository root, you do not need to set `outputFileTracingRoot`.
48+
</Callout>
49+
50+
## Options
51+
52+
`withWorkflow` accepts an optional second argument to configure the Next.js integration.
53+
54+
```typescript title="next.config.ts" lineNumbers
55+
import type { NextConfig } from "next";
56+
import { withWorkflow } from "workflow/next";
57+
58+
const nextConfig: NextConfig = {};
59+
60+
export default withWorkflow(nextConfig, {
61+
workflows: {
62+
lazyDiscovery: true,
63+
local: {
64+
port: 4000,
65+
},
66+
},
67+
});
68+
```
69+
70+
| Option | Type | Default | Description |
71+
| --- | --- | --- | --- |
72+
| `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. |
73+
| `workflows.local.port` | `number` || Overrides the `PORT` environment variable for local development. Has no effect when deployed to Vercel. |
74+
75+
<Callout type="info">
76+
The `workflows.local` options only affect local development. When deployed to Vercel, the runtime ignores `local` settings and uses the Vercel world automatically.
77+
</Callout>
78+
79+
## Exporting a Function
80+
3081
If you are exporting a function in your `next.config` you will need to ensure you call the function returned from `withWorkflow`.
3182

3283
```typescript title="next.config.ts" lineNumbers

docs/content/docs/deploying/building-a-world.mdx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,13 @@ A World connects workflows to the infrastructure that powers them. The World int
3434
```typescript
3535
interface World extends Storage, Queue, Streamer {
3636
start?(): Promise<void>;
37+
close?(): Promise<void>;
38+
getEncryptionKeyForRun?(run: WorkflowRun): Promise<Uint8Array | undefined>;
39+
getEncryptionKeyForRun?(runId: string, context?: Record<string, unknown>): Promise<Uint8Array | undefined>;
3740
}
3841
```
3942

40-
The optional `start()` method initializes any background tasks needed by your World (e.g., queue polling).
43+
The optional `start()` method initializes background tasks (for example, queue polling). The optional `close()` method releases resources like connection pools and listeners. The optional `getEncryptionKeyForRun()` method returns the AES-256 key used to encrypt data for a run; if it is not implemented, encryption is disabled.
4144

4245
## The Event Log Model
4346

@@ -63,8 +66,8 @@ interface Storage {
6366
};
6467

6568
events: {
66-
// Create a new workflow run (runId must be null - server generates it)
67-
create(runId: null, data: RunCreatedEventRequest, params?: CreateEventParams): Promise<EventResult>;
69+
// Create a new workflow run (runId may be client-provided or null for server generation)
70+
create(runId: string | null, data: RunCreatedEventRequest, params?: CreateEventParams): Promise<EventResult>;
6871

6972
// Create an event for an existing run
7073
create(runId: string, data: CreateEventRequest, params?: CreateEventParams): Promise<EventResult>;
@@ -88,7 +91,7 @@ interface Storage {
8891
2. Atomically update the affected entity (run, step, or hook)
8992
3. Return both the created event and the updated entity
9093

91-
**Run Creation:** For `run_created` events, the `runId` parameter is `null`. Your World generates and returns a new `runId`.
94+
**Run Creation:** For `run_created` events, the `runId` parameter may be a client-provided string or `null`. When `null`, your World generates and returns a new `runId`.
9295

9396
**Hook Tokens:** Hook tokens must be unique. If a `hook_created` event conflicts with an existing token, return a `hook_conflict` event instead.
9497

@@ -165,13 +168,19 @@ The Streamer interface enables real-time data streaming:
165168
interface Streamer {
166169
writeToStream(
167170
name: string,
168-
runId: string | Promise<string>,
171+
runId: string,
169172
chunk: string | Uint8Array
170173
): Promise<void>;
171174

175+
writeToStreamMulti?(
176+
name: string,
177+
runId: string,
178+
chunks: (string | Uint8Array)[]
179+
): Promise<void>;
180+
172181
closeStream(
173182
name: string,
174-
runId: string | Promise<string>
183+
runId: string
175184
): Promise<void>;
176185

177186
readFromStream(
@@ -202,6 +211,7 @@ interface Streamer {
202211
```
203212

204213
Streams are identified by a combination of `runId` and `name`. Each workflow run can have multiple named streams.
214+
`writeToStreamMulti()` is an optional optimization for batching multiple writes.
205215

206216
`getStreamChunks` returns a paginated snapshot of currently available chunks (unlike `readFromStream` which returns a live `ReadableStream` that waits for new chunks). `getStreamInfo` returns the tail index (last chunk index, 0-based, or `-1` when empty) and whether the stream is complete — useful for resolving negative `startIndex` values into absolute positions.
207217

docs/content/docs/deploying/index.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ For self-hosting or deploying to other cloud providers, you can use community-ma
7373
To use a different World implementation, set the `WORKFLOW_TARGET_WORLD` environment variable:
7474

7575
```bash
76-
export WORKFLOW_TARGET_WORLD=@workflow-worlds/postgres
76+
export WORKFLOW_TARGET_WORLD=@workflow/world-postgres
7777
# Plus any world-specific configuration
7878
export DATABASE_URL=postgres://...
7979
```
@@ -89,7 +89,7 @@ The [Observability tools](/docs/observability) work with any World backend. By d
8989
npx workflow inspect runs
9090

9191
# Inspect remote workflows
92-
npx workflow inspect runs --backend @workflow-worlds/postgres
92+
npx workflow inspect runs --backend @workflow/world-postgres
9393
```
9494

9595
Learn more about [Observability](/docs/observability) tools.

docs/content/docs/errors/hook-conflict.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ This error occurs when you try to create a hook with a token that is already in
1515
## Error Message
1616

1717
```
18-
Hook token conflict: Hook with token <token> already exists for this project
18+
Hook token "<token>" is already in use by another workflow
1919
```
2020

2121
## Why This Happens

docs/content/docs/errors/node-js-module-in-workflow.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ async function read(filePath: string) {
6969
These common Node.js core modules cannot be used in workflow functions:
7070

7171
- File system: `fs`, `path`
72-
- Network: `http`, `https`, `net`, `dns`, `fetch`
72+
- Network: `http`, `https`, `net`, `dns`
7373
- Process: `child_process`, `cluster`
7474
- Crypto: `crypto` (use Web Crypto API instead)
7575
- Operating system: `os`

0 commit comments

Comments
 (0)