diff --git a/src/content/docs/workflows/build/sleeping-and-retrying.mdx b/src/content/docs/workflows/build/sleeping-and-retrying.mdx index aa84ed645d2ac4a..fcf44c79aa0665a 100644 --- a/src/content/docs/workflows/build/sleeping-and-retrying.mdx +++ b/src/content/docs/workflows/build/sleeping-and-retrying.mdx @@ -92,17 +92,6 @@ let someState = await step.do( ); ``` -## Access step context - -Workflow step callbacks receive a context object containing the current attempt number (1-indexed). This allows you to access which retry attempt is currently executing. - -```ts -await step.do("my-step", async (ctx) => { - // ctx.attempt is 1 on first try, 2 on first retry, etc. - console.log(`Attempt ${ctx.attempt}`); -}); -``` - ## Force a Workflow instance to fail You can also force a Workflow instance to fail and _not_ retry by throwing a `NonRetryableError` from within the step. diff --git a/src/content/docs/workflows/build/step-context.mdx b/src/content/docs/workflows/build/step-context.mdx new file mode 100644 index 000000000000000..62c3e2cc18fc26c --- /dev/null +++ b/src/content/docs/workflows/build/step-context.mdx @@ -0,0 +1,107 @@ +--- +title: Step context +pcx_content_type: concept +sidebar: + order: 5 +--- + +Every `step.do` callback receives a **context object** (`WorkflowStepContext`) as its first argument. The context gives your step code runtime information about the step itself, the current retry attempt, and the resolved configuration for that step. + +## WorkflowStepContext + +```ts +type WorkflowStepContext = { + step: { + name: string; + count: number; + }; + attempt: number; + config: WorkflowStepConfig; +}; +``` + +### Properties + +| Property | Type | Description | +| ------------ | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| `step.name` | `string` | The name you passed to `step.do`. | +| `step.count` | `number` | How many times `step.do` has been called with this name so far in the current Workflow run. Starts at `1` for the first call with a given name. | +| `attempt` | `number` | The current attempt number (1-indexed). `1` on the first try, `2` on the first retry, and so on. | +| `config` | [`WorkflowStepConfig`](/workflows/build/workers-api/#workflowstepconfig) | The resolved retry and timeout configuration for this step, including any defaults applied by the runtime. | + +## Access the context + +Pass a parameter to your `step.do` callback to receive the context object: + +```ts +await step.do("my-step", async (ctx) => { + console.log(ctx.step.name); // "my-step" + console.log(ctx.step.count); // 1 + console.log(ctx.attempt); // 1 on first try, 2 on first retry, etc. + console.log(ctx.config); // { retries: { limit: 5, ... }, timeout: "10 minutes" } +}); +``` + +The context is also available when you pass a custom `WorkflowStepConfig`: + +```ts +await step.do( + "call an API", + { + retries: { + limit: 10, + delay: "10 seconds", + backoff: "exponential", + }, + timeout: "30 minutes", + }, + async (ctx) => { + console.log(ctx.config.retries.limit); // 10 + console.log(ctx.config.timeout); // "30 minutes" + }, +); +``` + +## Examples + +### Adjust behavior based on retry attempt + +Use `ctx.attempt` to change how your step behaves on retries. For example, you might use a fallback endpoint after a certain number of retries: + +```ts +await step.do( + "fetch data", + { retries: { limit: 5, delay: "5 seconds", backoff: "linear" } }, + async (ctx) => { + const url = + ctx.attempt <= 3 + ? "https://api.example.com/primary" + : "https://api.example.com/fallback"; + + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Request failed with status ${response.status}`); + } + return await response.json(); + }, +); +``` + +### Log step metadata for observability + +Use `ctx.step` to add structured metadata to your logs: + +```ts +await step.do("process-order", async (ctx) => { + console.log( + JSON.stringify({ + step: ctx.step.name, + stepCount: ctx.step.count, + attempt: ctx.attempt, + retryLimit: ctx.config.retries?.limit, + }), + ); + + // Your step logic here +}); +``` diff --git a/src/content/docs/workflows/build/workers-api.mdx b/src/content/docs/workflows/build/workers-api.mdx index 47397e7d8ecb9f6..c731cb5ed8a94ce 100644 --- a/src/content/docs/workflows/build/workers-api.mdx +++ b/src/content/docs/workflows/build/workers-api.mdx @@ -76,12 +76,12 @@ Refer to the [events and parameters](/workflows/build/events-and-parameters/) do ### step {/* prettier-ignore */} -- step.do(name: string, callback: (): RpcSerializable): Promise<T> -- step.do(name: string, config?: WorkflowStepConfig, callback: (): +- step.do(name: string, callback: (ctx: WorkflowStepContext): RpcSerializable): Promise<T> +- step.do(name: string, config?: WorkflowStepConfig, callback: (ctx: WorkflowStepContext): RpcSerializable): Promise<T> - `name` - the name of the step, up to 256 characters. - `config` (optional) - an optional `WorkflowStepConfig` for configuring [step specific retry behaviour](/workflows/build/sleeping-and-retrying/). - - `callback` - an asynchronous function that optionally returns serializable state for the Workflow to persist. In JavaScript Workflows, this includes a fresh, unlocked `ReadableStream` for large binary output. + - `callback` - an asynchronous function that receives a [`WorkflowStepContext`](/workflows/build/step-context/) and optionally returns serializable state for the Workflow to persist. In JavaScript Workflows, this includes a fresh, unlocked `ReadableStream` for large binary output. :::note[Returning state] @@ -193,6 +193,27 @@ export type WorkflowStepConfig = { Refer to the [documentation on sleeping and retrying](/workflows/build/sleeping-and-retrying/) to learn more about how Workflows are retried. +## WorkflowStepContext + +```ts +export type WorkflowStepContext = { + step: { + name: string; + count: number; + }; + attempt: number; + config: WorkflowStepConfig; +}; +``` + +- The `WorkflowStepContext` is passed as the first argument to the `step.do` callback function. It provides runtime information about the current step. + - `step.name` - the name of the step as passed to `step.do`. + - `step.count` - how many times `step.do` has been called with this name in the current Workflow run (1-indexed). + - `attempt` - the current attempt number (1-indexed). `1` on the first try, `2` on the first retry, and so on. + - `config` - the resolved `WorkflowStepConfig` for this step, including any defaults applied by the runtime. + +Refer to the [step context documentation](/workflows/build/step-context/) for usage examples. + ## Workflow step limits Each workflow on Workers Paid supports 10,000 steps by default. You can increase this up to 25,000 steps by configuring `steps` within the `limits` property of your Workflow definition in your Wrangler configuration: diff --git a/src/content/docs/workflows/python/python-workers-api.mdx b/src/content/docs/workflows/python/python-workers-api.mdx index aded391f4657b58..d9e0ca23a435662 100644 --- a/src/content/docs/workflows/python/python-workers-api.mdx +++ b/src/content/docs/workflows/python/python-workers-api.mdx @@ -155,6 +155,14 @@ class DemoWorkflowClass(WorkflowEntrypoint): ### Access step context (`ctx`) +If you define a `ctx` parameter, the [step context](/workflows/build/step-context/) is injected into that argument. The context is a dictionary with the following keys: + +| Key | Type | Description | +| --------- | ------ | ------------------------------------------------------------------------------------------------------ | +| `step` | `dict` | Contains `name` (the step name) and `count` (how many times `step.do` has been called with this name). | +| `attempt` | `int` | The current attempt number (1-indexed). | +| `config` | `dict` | The resolved retry and timeout configuration for this step. | + ```python from workers import WorkflowEntrypoint @@ -162,6 +170,10 @@ class CtxWorkflow(WorkflowEntrypoint): async def run(self, event, step): @step.do() async def read_context(ctx): + print(ctx["step"]["name"]) # step name + print(ctx["step"]["count"]) # step count + print(ctx["attempt"]) # attempt number + print(ctx["config"]) # resolved step config return ctx["attempt"] return await read_context()