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

# Workflows

> Durable, multi-step runs that combine code, agent turns, specialist reviews and human approvals

## What is a Workflow?

A **workflow** is a durable, multi-step run. You declare the steps and how they connect once, in TypeScript; the platform then executes every run of it, records every step and event, survives restarts, waits for people when a step needs a decision, and lets you watch, cancel, retry and replay the run from the CLI or the desktop.

<Card title="Think of it as:" icon="diagram-project">
  A checklist the platform runs for you - each item is a piece of code, a turn of an agent, a review by a specialist role, or a question for a person - with a full record of what happened at every step.
</Card>

A run can be started by a person (CLI, desktop, chat), by a skill or tool your agent runs, by a schedule, by a trigger or webhook, by a template, or by another workflow.

## Anatomy of a workflow

<CardGroup cols={2}>
  <Card title="Definition" icon="file-code">
    A `createWorkflow({...})` chain in your project. Pushed and versioned like a skill; deployed with `lua workflows deploy`.
  </Card>

  <Card title="Run" icon="play">
    One execution of a definition with a given input. Has an id (`wfr_...`), a status, a budget, an input and an output.
  </Card>

  <Card title="Steps" icon="list-check">
    The units of work inside a run. Each step has its own status, attempts, input, output and error.
  </Card>

  <Card title="Events" icon="timeline">
    The append-only ledger of everything that happened to a run, streamed live over SSE and kept after the run ends.
  </Card>
</CardGroup>

### Step kinds

| Kind            | Builder call                      | What it does                                                                                                             |
| --------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Code step       | `.then(createStep({...}))`        | Runs your `execute` function with typed input and output.                                                                |
| Agent step      | `.agentStep(id, {...})`           | One turn of an agent (yours, or `$self`), with a prompt template and an optional output schema.                          |
| Specialist step | `.specialistStep(id, {...})`      | A turn of your own agent under an additive role (name, instructions, tool allowlist) - for example a sceptical reviewer. |
| Tool step       | `.toolStep(id, tool, {...})`      | Calls one of your `LuaTool`s directly, without a model turn.                                                             |
| Approval        | `.approval(id, {...})`            | Pauses the run until a person approves or denies (optionally editing the payload).                                       |
| Signal wait     | `.waitForSignal(id, {...})`       | Pauses the run until an external system delivers a named signal.                                                         |
| Sleep           | `.sleep(ms)` / `.sleepUntil(iso)` | Waits without holding compute.                                                                                           |
| Map             | `.map({...})`                     | Reshapes data between steps.                                                                                             |
| Nested workflow | `.workflow(id, ref, input)`       | Runs another workflow as a child run.                                                                                    |

Steps are arranged with containers: `.parallel([...])`, `.switch([...], otherwise)` / `.branch([...])`, `.foreach(step, {...})` and `.dowhile(...)` / `.dountil(...)`.

### Graph form

A workflow's control flow is a static graph, not a program that runs at push time. Predicates are built from typed helpers (`eq`, `gt`, `exists`, `and`, ...) and data flow from mapping helpers (`fromInit`, `fromStep`, `template`, ...). A closure where a predicate or a binding is expected is a build error. This is what lets the platform draw the graph, replay a run locally, and resume a run on a fresh machine.

### Two execution tiers

| Tier             | Where a step runs                                 | Step timeout                                       | Use for                                                           |
| ---------------- | ------------------------------------------------- | -------------------------------------------------- | ----------------------------------------------------------------- |
| Worker (default) | The agent's sandbox                               | 1-600 s (default 300 s)                            | API calls, data shaping, short model turns                        |
| Job              | Its own container, with an optional git workspace | Up to 4 h per code step, up to 24 h per agent step | Cloning a repository, running a test suite, a full coding session |

See [Job tier](/workflows/job-tier).

## Example

```typescript theme={null}
import { z } from 'zod';
import { createStep, createWorkflow, fromStep, template } from 'lua-cli';

const fetchSources = createStep({
  id: 'fetchSources',
  inputSchema: z.object({ topic: z.string() }),
  outputSchema: z.object({ urls: z.array(z.string().url()) }),
  timeoutSeconds: 60,
  async execute({ inputData, log }) {
    const res = await fetch(`https://api.example.com/search?q=${encodeURIComponent(inputData.topic)}`);
    const json = (await res.json()) as { url: string }[];
    log(`found ${json.length} sources`);
    return { urls: json.slice(0, 10).map((r) => r.url) };
  },
});

export const researchBrief = createWorkflow({
  name: 'research-brief',
  description: 'Fetch sources, summarise, ask for sign-off.',
  inputSchema: z.object({ topic: z.string() }),
  outputSchema: z.object({ brief: z.string() }),
  budget: { maxCredits: 40 },
})
  .then(fetchSources)
  .agentStep('draft', {
    agentId: 'analyst',
    prompt: template('Write a one-page brief on ${initData.topic} using ${stepResults.fetchSources.urls}'),
    outputSchema: z.object({ brief: z.string() }),
  })
  .approval('signOff', { title: 'Publish this brief?', approver: 'org-admins', timeoutHours: 48 })
  .map({ brief: fromStep('draft', 'brief') }, { id: 'result' })
  .commit();
```

## Workflows next to skills, jobs and templates

|                   | Skill                                       | Job                          | Workflow                                                                                    | Agent template                                                         |
| ----------------- | ------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| What it is        | Tools the agent calls during a conversation | Code that runs on a schedule | A durable graph of steps with its own runs                                                  | A packaged agent (skills, jobs, workflows, persona) others can install |
| Runs for          | One tool call                               | One execution                | Minutes to weeks                                                                            | n/a                                                                    |
| Waits for people  | No                                          | No                           | Yes (approvals, input, signals)                                                             | n/a                                                                    |
| Survives restarts | n/a                                         | No                           | Yes - every step and event is recorded                                                      | n/a                                                                    |
| Starts from       | The model, in chat                          | The scheduler                | CLI, desktop, chat, a skill, a schedule, a trigger, a webhook, a template, another workflow | The marketplace                                                        |

A workflow can carry its own `schedule`, so a scheduled workflow replaces the "job that calls an agent" pattern when you want step-level visibility, retries and approvals.

## When to use a workflow

<CardGroup cols={2}>
  <Card title="Use a workflow when" icon="check">
    * The work has more than one step and you want each step recorded and retryable
    * A person must approve, edit or answer something part-way through
    * Steps run in parallel, over a list, or in a review loop
    * A step needs hours, a repository checkout, or a coding session
    * An external system reports back later (a PR review, a payment webhook)
  </Card>

  <Card title="Use a skill or job when" icon="xmark">
    * One tool call answers the user in the same turn (skill)
    * A short piece of code runs on a timer and nobody needs to intervene (job)
    * You do not need per-step history, replay or a human in the loop
  </Card>
</CardGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Quick Start" icon="rocket" href="/workflows/quick-start">
    Define, push, deploy, start and approve your first workflow
  </Card>

  <Card title="Authoring" icon="code" href="/workflows/authoring">
    The SDK: steps, containers, bindings, approvals and `Workflows.start`
  </Card>

  <Card title="Job tier" icon="server" href="/workflows/job-tier">
    Long steps, git workspaces and coding turns
  </Card>

  <Card title="Runs and events" icon="timeline" href="/workflows/runs-and-events">
    Run lifecycle, the events ledger, SSE, approvals and the desktop Runs pages
  </Card>

  <Card title="CLI reference" icon="terminal" href="/cli/workflows-command">
    Every `lua workflows` subcommand
  </Card>
</CardGroup>
