> ## Documentation Index
> Fetch the complete documentation index at: https://app.keystroke.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflow steps

> Run actions as steps inside a workflow.

Inside a [workflow](/learn/workflows/overview), every call to an [action](/learn/actions/overview) is a durable step. You import the action and call `.run()` with its input; the workflow records the result so a retry can skip work that already succeeded. This is the most common way to compose actions, since an action can never call another action directly.

## Call an action as a step

Import the action and `await` its `.run()` inside the workflow's `run` function. The input is validated against the action's `input` schema, and the result is validated against its `output`.

```ts src/workflows/signup-pipeline.ts theme={null}
import { defineWorkflow } from "@keystrokehq/keystroke/workflow";
import { z } from "zod";
import { researchSignup } from "../actions/research-signup";
import { postBrief } from "../actions/post-brief";

export default defineWorkflow({
  slug: "signup-pipeline",
  input: z.object({ name: z.string(), email: z.string().email() }),
  output: z.object({ brief: z.string() }),
  async run(input) {
    const { brief } = await researchSignup.run(input);
    await postBrief.run({ text: brief });
    return { brief };
  },
});
```

Each `.run()` is a recorded step. Steps run in the order you `await` them, and you can branch, loop, and pass one step's output into the next like any other `async`/`await` code.

## Integration actions as steps

Integration actions are used directly as steps; never wrap them in a custom action. Import only the ones a workflow needs.

```ts theme={null}
import { slackSendMessage } from "@keystrokehq/slack/actions";

async run(input) {
  await slackSendMessage.run({ channel: input.channel, markdown_text: input.summary });
  return { sent: true };
}
```

## Durability

As each step completes, its result is written to the run's event log. If a later step fails and the run is retried, Keystroke replays the log: completed steps return their recorded result instead of running again, and execution resumes at the first step that hasn't finished.

Two practical consequences:

* **Put side effects inside steps.** Work done directly in the `run` body (not inside an action, agent, or LLM call) runs again on every replay. Keep network calls and writes inside `.run()` so they're recorded and not repeated.
* **Make steps idempotent where you can.** A step can be retried after a transient failure, so design actions to tolerate being called more than once with the same input.

See [durability and retries](/learn/workflows/build-workflows#durability-and-retries) for the full model.

## Scope credentials per step

When a step's action declares `credentials` that could resolve at more than one [scope](/learn/credentials/overview), pin the scope with `.scope()`:

```ts theme={null}
await slackSendMessage.run({ channel, markdown_text }).scope("user");
```

Without an explicit scope, the resolver uses the project default, then the organization default.

## Actions, agents, and LLM steps

A workflow can mix deterministic action steps with [agent steps](/learn/workflows/build-workflows#agent-steps) (`agent.prompt()`) and one-shot [LLM steps](/learn/workflows/build-workflows#llm-steps) (`promptLlm()`). A common shape is to call actions to gather data, prompt an agent to make a decision or draft text, then call more actions to act on the result.

## Next steps

<CardGroup cols={2}>
  <Card title="Build workflows" href="/learn/workflows/build-workflows">
    Steps, durability, durable waits, and the run context.
  </Card>

  <Card title="Actions as agent tools" href="/learn/actions/agent-tools">
    Attach the same actions to an agent as tools.
  </Card>

  <Card title="Test workflows" href="/learn/workflows/test-workflows">
    Run a workflow and assert its output.
  </Card>

  <Card title="Workflow runs" href="/learn/logs/workflow-runs">
    Inspect each step's input, output, and errors.
  </Card>
</CardGroup>
