> ## Documentation Index
> Fetch the complete documentation index at: https://botpress-ak-docs-20-document-updating-variables-from-outsid.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflows

Workflows are long-running processes that handle complex, multi-step operations or scheduled tasks. Unlike [conversations](/adk/concepts/conversations), workflows can run independently on a schedule or be triggered by events.

<Note>
  While workflows in the ADK are similar in concept to [Workflows](/studio/concepts/workflows) in Botpress Studio, they behave differently and shouldn't be treated as equivalent.
</Note>

## Creating a workflow

Create a workflow in `src/workflows/`:

```typescript theme={null}
import { Workflow } from "@botpress/runtime";

export default new Workflow({
  name: "my-workflow",
  description: "A workflow that processes data",
  handler: async ({}) => {
    // Workflow logic
  },
});
```

## Scheduled workflows

Workflows can run on a schedule using cron syntax:

```typescript highlight={6} theme={null}
import { WebsiteKB } from '../knowledge/docs'

export default new Workflow({
  name: "periodic-indexing",
  description: "Indexes knowledge base every 6 hours",
  schedule: "0 */6 * * *", // Every 6 hours
  handler: async ({}) => {
    await WebsiteKB.refresh();
  },
});
```

## Steps

By default, workflows time out after 2 minutes—if you need to run a workflow for longer, you should use the `step` function. This lets you break the workflow up into a series of steps that each take only a few seconds to run individually:

```typescript highlight={4-17} theme={null}
export default new Workflow({
  name: "data-processing",
  handler: async ({ step }) => {
    // Step 1: Fetch data
    const data = await step("fetch-data", async () => {
      return await fetchDataFromAPI();
    });

    // Step 2: Process data
    const processed = await step("process-data", async () => {
      return processData(data);
    });

    // Step 3: Store results
    await step("store-results", async () => {
      await saveResults(processed);
    });
  },
});
```

Steps are *persisted*—if a workflow is interrupted, it can resume from the last completed step. This provides better handling when errors occur in complex, long-running workflows.

<Note>
  You can nest steps inside other steps—each step will complete when each of its sub-steps complete.
</Note>

### Reference

The `step` object also provides many additional methods for workflow control:

<Card title="Step reference" icon="list-check" href="/adk/concepts/workflows/steps">
  Learn about all available step methods
</Card>

### Handling failing steps

If a step (or a [step method](/adk/concepts/workflows/steps#methods)) fails, it'll throw a rejected promise. This will fail not only the current step, but the *entire workflow*.

To avoid this, make sure you catch errors gracefully:

```ts theme={null}
try {
  await step.request("orderId", "Please provide the order ID.");
} catch (err) {
  console.log(err);
}
```

If the method you're using has an `options.maxAttempts` field, it'll throw an error after the maximum number of retries has been exceeded. You can track the retries using the `attempt` parameter and catch any errors:

```ts highlight={5, 8, 10-12} theme={null}
try {
  await step(
    "data-processing",
    async ({ attempt }) => {
      console.log(`Trying step: attempt #${attempt}`);
      // Your code here
    },
    { maxAttempts: 10 }
  );
} catch (err) {
  console.log(err);
}
```

## Output

Workflows can return an output that matches the output schema:

```typescript highlight={10} theme={null}
export default new Workflow({
  name: "calculate-totals",
  input: z.object({ orderId: z.string() }),
  output: z.object({ total: z.number() }),
  handler: async ({ input, step }) => {
    const order = await step("fetch-order", async () => {
      return await fetchOrder(input.orderId);
    });

    return { total: order.total };
  },
});
```

## Reference

### Workflow props

<Expandable title="Workflow props">
  <ResponseField name="name" type="string" required>
    Unique name for the workflow. Used to identify and reference the workflow.
  </ResponseField>

  <ResponseField name="description" type="string">
    Optional description of what the workflow does.
  </ResponseField>

  <ResponseField name="input" type="z.ZodTypeAny">
    Optional Zod schema defining the input parameters for the workflow. Defaults to an empty object if not provided.
  </ResponseField>

  <ResponseField name="output" type="z.ZodTypeAny">
    Optional Zod schema defining the output/return type of the workflow. Defaults to an empty object if not provided.
  </ResponseField>

  <ResponseField name="state" type="z.ZodTypeAny">
    Optional Zod schema defining the workflow state structure. Defaults to an empty object if not provided.
  </ResponseField>

  <ResponseField name="requests" type="object">
    Optional object mapping request names to their Zod schemas. Each key is a request name and value is a Zod schema for the request data.
  </ResponseField>

  <ResponseField name="handler" type="(props: object) => Promise<any> | any" required>
    Handler function that implements the workflow logic. See handler parameters below for details.
  </ResponseField>

  <ResponseField name="schedule" type="string">
    Optional cron expression for scheduled workflows (e.g., "0 \*/6 \* \* \*" for every 6 hours).
  </ResponseField>

  <ResponseField name="timeout" type="'{number}s' | '{number}m' | '{number}h'">
    Optional timeout for workflow execution (e.g., "5m", "1h"). Defaults to "5m".
  </ResponseField>
</Expandable>

### Handler parameters

The handler function receives workflow context and provides step management:

<Expandable title="Handler parameters">
  <ResponseField name="input" type="any" required>
    The validated input object matching the workflow's input schema. Typed based on your input schema.
  </ResponseField>

  <ResponseField name="state" type="any" required>
    Workflow state object that persists across workflow executions and steps. Typed based on your state schema.
  </ResponseField>

  <ResponseField name="step" type="object" required>
    Step function for creating checkpoints. Also provides methods like `listen`, `fail`, `progress`, `abort`, `sleep`, `sleepUntil`, `waitForWorkflow`, `executeWorkflow`, `map`, `forEach`, `batch`, and `request`.
  </ResponseField>

  <ResponseField name="client" type="object" required>
    Botpress client for making API calls (tables, events, etc.).
  </ResponseField>

  <ResponseField name="signal" type="AbortSignal" required>
    An abort signal that indicates when the workflow should stop execution.
  </ResponseField>

  <ResponseField name="execute" type="(props: object) => Promise<any>" required>
    Function to execute autonomous AI logic. Used to run the agent with instructions, tools, knowledge bases, etc.

    <Expandable title="Execute props">
      <ResponseField name="instructions" type="string | (() => string) | (() => Promise<string>)" required>
        Instructions for the AI agent. Can be a string or a function that returns a string.
      </ResponseField>

      <ResponseField name="tools" type="object[]">
        Optional array of tools the agent can use.
      </ResponseField>

      <ResponseField name="objects" type="object[]">
        Optional array of objects the agent can interact with.
      </ResponseField>

      <ResponseField name="exits" type="Record<string, object>">
        Optional record of exit handlers.
      </ResponseField>

      <ResponseField name="signal" type="AbortSignal">
        Optional abort signal to cancel execution.
      </ResponseField>

      <ResponseField name="hooks" type="object">
        Optional hooks for customizing behavior (`onBeforeTool`, `onAfterTool`, `onTrace`, etc.).
      </ResponseField>

      <ResponseField name="temperature" type="number | (() => number) | (() => Promise<number>)">
        Optional temperature for the AI model. Defaults to 0.7.
      </ResponseField>

      <ResponseField name="model" type="string | string[] | (() => string | string[]) | (() => Promise<string | string[]>)">
        Optional model name(s) to use. Can be a string, array of strings, or a function that returns either.
      </ResponseField>

      <ResponseField name="knowledge" type="object[]">
        Optional array of knowledge bases to use.
      </ResponseField>

      <ResponseField name="iterations" type="number">
        Optional maximum number of iterations (loops). Defaults to 10.
      </ResponseField>
    </Expandable>
  </ResponseField>
</Expandable>

## Methods

Workflows can be started and managed programmatically:

### `workflow.start()`

Start a new workflow instance:

```typescript theme={null}
import ProcessingWorkflow from "../workflows/processing";

const instance = await ProcessingWorkflow.start({ orderId: "12345" });
console.log("Started workflow:", instance.id);
```

### `workflow.getOrCreate()`

Get an existing workflow or create a new one with deduplication:

```typescript theme={null}
const instance = await ProcessingWorkflow.getOrCreate({
  key: "order-12345", // Unique key for deduplication
  input: { orderId: "12345" },
  statuses: ["pending", "in_progress"], // Only match workflows with these statuses
});
```

### `workflow.asTool()`

Convert a workflow into a tool for use with `execute()`:

```typescript highlight={2, 9} theme={null}
import { Conversation } from "@botpress/runtime";
import ProcessingWorkflow from "../workflows/processing";

export default new Conversation({
  channel: "*",
  handler: async ({ execute }) => {
    await execute({
      instructions: "You are a helpful assistant.",
      tools: [ProcessingWorkflow.asTool()],
    });
  },
});
```

### `workflow.provide()`

Provide data in response to a workflow data request (used with `step.request()`):

```typescript highlight={1, 6-9} theme={null}
import { isWorkflowDataRequest } from "@botpress/runtime";
import OrderWorkflow from "../workflows/order";

export default new Conversation({
  channel: "*",
  handler: async ({ type, request }) => {
    if (type === "workflow_request") {
      await OrderWorkflow.provide(request, { orderId: "12345" });
    }
  },
});
```

## Helper functions

### `isWorkflowDataRequest()`

Check if an event is a workflow data request:

```typescript theme={null}
import { isWorkflowDataRequest } from "@botpress/runtime";

if (isWorkflowDataRequest(event)) {
  // Handle the workflow data request
}
```

### `isWorkflowCallback()`

Check if an event is a workflow callback:

```typescript theme={null}
import { isWorkflowCallback } from "@botpress/runtime";

if (isWorkflowCallback(event)) {
  // Handle the workflow callback
}
```
