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

# Run and write workflows

A workflow is a script that runs several agents in stages: for example, three reviewers in
parallel, then one agent that checks their findings. It runs on the workspace server, so it keeps
going if you close the terminal.

Use a workflow for broad work with several stages. For a focused change, just ask the agent.

Workflows run with [Bun](https://bun.sh): from the command line and the terminal UI it must be on
your `PATH`. Desktop brings its own copy.

## Run a workflow

```sh
haarness workflow list
haarness workflow run audit --args '{"scope":"server"}'
```

`list` shows each workflow's name, description and file, and `haarness workflow source audit`
prints its code. `run` waits until the workflow finishes and prints the run's outcome and result;
add `--json` for the whole run record.

Every workflow is also a slash command in the terminal UI. `/audit check the server` runs the
`audit` workflow, and the text after the name becomes its `input` argument.

## Run one from a script

```sh
haarness run --workflow audit --workflow-arg scope=server --workflow-arg reviewers=3
```

Values that look like JSON (numbers, `true`, lists) are passed as JSON; anything else is a string.
The prompt text, if you give one, becomes the `input` argument. This form exits with an error when
the run does not complete, which is what a CI job usually wants.

## Start a run and check on it later

```sh
haarness workflow run audit --detach     # print the run and return
haarness workflow runs                   # recent runs in this folder
haarness workflow inspect <run-id>       # status, phase and result
```

`/workflow` in the terminal UI opens a dashboard of workflows and recent runs. In Desktop,
`/workflow` and `/workflow run <name>` answer in a tray above the message box, which follows the
run until it ends and shows the run ID that `/workflow cancel`, `pause` and `resume` take.

## Pause, resume or cancel a run

```sh
haarness workflow pause <run-id>
haarness workflow resume <run-id>
haarness workflow cancel <run-id>
haarness workflow delete <run-id>
```

Pausing stops the run. Resuming starts it again **from the beginning**, with the same source and
arguments. A run that was going when Haarness stopped is marked `interrupted`, and you resume it
the same way. `delete` removes the run from the list.

## Write a workflow

Save a file in `.haarness/workflows/` in your project, for example `.haarness/workflows/audit.ts`.
The workflow's `name` becomes its command.

```ts
import { workflow } from "@haarness-ai/plugin"

export default workflow({
  name: "audit",
  description: "Review a folder for security and reliability problems",
  phases: [{ title: "scan" }, { title: "verify" }],
  arguments: {
    scope: { type: "string", default: "." },
  },
  async run(args, ctx) {
    ctx.setPhase("scan")
    const review = (topic: string) =>
      ctx.agent({ prompt: `Review ${args.scope} for ${topic} problems`, label: topic })
    const reports = await ctx.parallel([
      () => review("security"),
      () => review("reliability"),
    ])

    ctx.setPhase("verify")
    const findings = reports.map((r) => r?.text).join("\n")
    const check = await ctx.agent({ prompt: `Keep only confirmed findings:\n${findings}` })
    return check?.text
  },
})
```

You do not install `@haarness-ai/plugin`; Haarness provides it. Run `haarness workflow list` to
check that the file was found. Workflows that plugins ship are named `plugin-name:workflow-name`.

To use a workflow in every project, save it in the `workflows` folder of your global Haarness
folder instead: `~/.config/haarness/workflows/` on every system (see
[Where Haarness keeps its files](https://haarness.ai/docs/cli/troubleshooting.md#where-haarness-keeps-its-files)). A
project's workflow wins over a global one with the same name.

You can also ask the agent to write one: "create a workflow that reviews each crate in parallel".
It saves the file in `.haarness/workflows/`.

### What a workflow can call

| Call | Does |
| --- | --- |
| `ctx.agent({ prompt, … })` | Runs a child agent; returns a reply with `.text` |
| `reply.followup(prompt)`, `reply.close()` | Continues or ends that child |
| `ctx.parallel(tasks, { concurrencyLimit })` | Runs tasks at once; a failed task gives `null` |
| `ctx.pipeline(items, ...stages)` | Passes each item through the stages |
| `ctx.tool(name, args)` | Calls a Haarness tool |
| `ctx.shell(command)` | Runs a shell command; returns output and exit code |
| `ctx.workflow(name, args)` | Runs another workflow inside this one |
| `ctx.setPhase(name)`, `ctx.log(value)` | Reports progress |

`ctx.agent` also takes `agent`, `model`, `variant`, `label`, `skills`, `files` and `schema`. With
`schema` (a JSON Schema), the child must answer in that shape and the value arrives as
`reply.data`.

### A restricted form for generated workflows

When a workflow was generated rather than written by you, prefer the restricted form. It has no
access to the file system, network or processes except through Haarness, so every action goes
through your normal permissions.

```js
export const meta = { name: "portable-audit", phases: ["scan", "verify"] }

phase("scan")
const reports = await parallel([
  () => agent({ prompt: "Inspect authentication", label: "auth" }),
  () => agent({ prompt: "Inspect file-system boundaries", label: "filesystem" }),
])

phase("verify")
const manifest = await tool("read", { path: "Cargo.toml" })
return { reports, manifest }
```

The same calls are available without `ctx.`: `args`, `phase`, `log`, `agent`, `tool`, `shell`,
`workflow`, `parallel` and `pipeline`.

## Limits worth knowing

- **By default a run stops after 30 minutes.** `--timeout-ms` only limits how long the command
  waits; the run itself keeps going.
- **Workflow code cannot ask you questions.** It fails instead of waiting.
- **The agents, tools and shell commands it runs follow your permissions.** Their requests ask you
  like a chat's do. `haarness workflow run --yolo` approves them, once each, while the command
  waits; it cannot be combined with `--detach`.
- **A workflow written as ordinary TypeScript is not sandboxed.** It runs with your user's access,
  like any script. Only add workflows you trust.
- **With `--disable-project-config`, project workflows are not loaded.**

## Reference

| Flag (`workflow run`) | Does |
| --- | --- |
| `--args '<json>'` | Arguments, as one JSON value |
| `--agent`, `--model`, `--variant` | Defaults for the child agents |
| `--detach` | Return as soon as the run starts |
| `--timeout-ms` | Stop waiting after this long |
| `--json` | Print the whole run record as JSON |

## Related

- [Run a prompt from a script or CI](https://haarness.ai/docs/cli/headless-runs.md)
- [Reasoning and ultracode](https://haarness.ai/docs/tui/reasoning.md)
- [Trust boundaries](https://haarness.ai/docs/security/trust-boundaries.md)
