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

# Workflows

> Fan work out across a crew from a small script — saved in your repo, run by name, and watched live as it goes.

A **workflow** is a small JavaScript program that dispatches [sub-agents](/helix/features/sub-agents)
and combines what they send back. `agent(prompt)` starts one and resolves to its final text, so
running ten reviewers at once is `await parallel([...])` — ordinary code, with the agents doing the
thinking.

There is no flag to set and no keyword to say. Helix reaches for a workflow on its own when the work
is a **shape** — the same operation over a list, independent pieces that could run at once — and you
can save that shape in your repo so it becomes something you run by name.

## A workflow you can run

Save this as `.mutagent/workflows/review-changed.js`:

```js .mutagent/workflows/review-changed.js theme={null}
export const meta = {
  name: "review-changed",
  description: "One reviewer per file, then a single ranked list",
}

phase("Review");

const notes = await parallel(
  args.files.map((file) => () =>
    agent(`Review ${file} for correctness bugs only. One line per issue, or "clean".`, {
      label: `review:${file}`,
    })
  )
);

phase("Summarize");

return await agent(
  "Merge these per-file review notes into one ranked list, worst first.\n\n" +
    args.files.map((f, i) => `## ${f}\n${notes[i]}`).join("\n\n"),
  { label: "merge" }
);
```

Then ask for it:

```
run the review-changed workflow on src/cart.ts, src/checkout.ts and src/pricing.ts
```

Three reviewers start at the same time; when all three are back, a fourth agent merges their notes
into one list. You get the merged list, and a row in the transcript that showed you both phases
while they were happening.

## When a workflow is the right tool

<CardGroup cols={2}>
  <Card title="Reach for one" icon="check">
    The same operation repeats over a list · independent readers can run at once · each item flows
    through the same few stages · you want to run it again next week without retyping it.
  </Card>

  <Card title="Don't" icon="xmark">
    The work is one question — ask it directly · each step depends on what the last one found · you'd
    be writing the analysis into the script instead of letting the agents do it.
  </Card>
</CardGroup>

A single prompt is usually better. Every `agent()` call is a full model run with its own context, so
a workflow that fans out five ways costs roughly five times what one agent would, plus the merge.
That is a good trade when five agents genuinely read five different things, and a bad one when you
split a question that only had one answer.

The script is **control flow, not analysis**. Keep it short; let the agents think.

## Where workflows live

```
your-project/
  .mutagent/
    workflows/
      review-changed.js
```

Files in `.mutagent/workflows/` are ordinary source files: commit them, review them in a pull
request, share them with your team. Both `.js` and `.ts` files are picked up.

<Warning>
  The body is **plain JavaScript** whatever you name the file — it runs in an isolated realm, not
  through your bundler. No `import` statements and no TypeScript type annotations: either is a syntax
  error that fails the run before its first agent starts.
</Warning>

### The `meta` block

A saved workflow declares a `meta` object so it can be listed and described without being run.

```js theme={null}
export const meta = {
  name: "review-changed",
  description: "One reviewer per file, then a single ranked list",
}
```

| Field         | Required | What it does                                              |
| ------------- | -------- | --------------------------------------------------------- |
| `name`        | **Yes**  | The name you run it by, and the name shown while it runs. |
| `description` | No       | One line, shown beside the name in the listing.           |

Two shapes matter, because `meta` is read by pattern rather than executed:

* Write it at the top level as `export const meta = { … }`, with the closing `}` at the start of its
  own line.
* Give `name` and `description` plain string literals — not a variable, not a template with a
  substitution in it.

A file with no readable `meta` is skipped by the listing rather than run, and `/workflows` says how
many were skipped. Nothing in `.mutagent/workflows/` is ever executed just to find out what it is.

## Running one

| Command             | Does                                           |
| ------------------- | ---------------------------------------------- |
| `/workflows`        | List what is saved, with each description.     |
| `/workflows <name>` | Show one — its name and the file it came from. |

```
/workflows
```

```
1 workflow:
  review-changed — One reviewer per file, then a single ranked list
```

Ask for a saved workflow by name and Helix passes the values you named through as `args`:

```
run the review-changed workflow on src/cart.ts and src/checkout.ts
```

A workflow does not have to be a file. Describe a shape and Helix writes and runs the script inline
for a one-off:

```
for each route file under src/api, check whether it validates its request body,
then give me one list of the ones that don't
```

### `args`

`args` arrives in the script verbatim — whatever Helix passed in. It is the whole of the difference
between a script and a *reusable* script, so read it at the top and give it a shape you can rely on:

```js theme={null}
const files = args.files ?? [];
const depth = args.depth ?? "quick";
```

## The script API

Six things are in scope. There is nothing else — no `require`, no timers, no file system, no network.

|                                |                                                                                          |
| ------------------------------ | ---------------------------------------------------------------------------------------- |
| `agent(prompt, opts)`          | Dispatch one agent. Resolves to its **final text**.                                      |
| `parallel([() => …, () => …])` | Run every thunk at once, resolve when all are done. Returns an array in the same order.  |
| `pipeline(items, ...stages)`   | Push each item through every stage, all items at once. Returns an array of final values. |
| `phase(title)`                 | Open a named section. Agents started after it are drawn under it.                        |
| `log(message)`                 | Add a line to the run's output.                                                          |
| `args`                         | Whatever was passed in.                                                                  |

Return a value from the top level and it becomes the workflow's result. `console` is available for
debugging.

### `agent(prompt, opts)`

| Option  | What it does                                                                                                                                                         |
| ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `label` | The name this agent shows under. Short and distinct — `review:src/cart.ts`, not `reviewer`.                                                                          |
| `phase` | Put this agent under a named phase instead of whichever phase is currently open. Useful in a `pipeline`, where item 2 is still on stage 1 while item 1 has moved on. |
| `type`  | Which agent to dispatch. Defaults to the general-purpose one.                                                                                                        |

Workflow agents are dispatched onto the **same crew** as any other agent in the session: they appear
in the [fleet list](/helix/features/sub-agents) alongside anything you started by hand.

### Failure is a value, not an exception

Nothing in a workflow throws at you from a distance:

* An `agent()` whose run fails resolves to a string beginning `[agent failed: …]`. Your script keeps
  going, and can branch on it.
* A thunk that throws inside `parallel()` becomes `null` in that slot; the others are unaffected.
* A stage that throws inside `pipeline()` drops **that item** to `null`; the other items keep flowing.
* A script that throws, or has a syntax error, fails the **run** — never the session — and every
  agent it had already dispatched is still reported.

So check for `null` and for `[agent failed:` before feeding a result into the next prompt.

## Two rules worth knowing before you write one

### `Math.random()` and `Date.now()` throw

Both are removed inside a workflow, and calling either fails the run with a message saying so.

A workflow is meant to be re-runnable: the same script with the same `args` should describe the same
run. A script that rolls a die or reads the clock takes a different branch every time, and the record
of what it did stops matching what it would do again. If you need a seed or a timestamp, decide it
**outside** and pass it through `args`:

```js theme={null}
const seed = args.seed;      // ✅ decided by the caller
const stamp = args.runAt;    // ✅ decided by the caller
```

<Note>
  `new Date()` is not blocked, but it is the same trap wearing a different hat. Take the time from
  `args` too.
</Note>

### Label every agent

`opts.label` is what the live view is keyed on. Give one to every `agent()` call, and make it say
which piece of work this is:

```js theme={null}
agent(`Review ${file} …`, { label: `review:${file}` })   // ✅ review:src/cart.ts
agent(`Review ${file} …`)                                 // ⚠️ #3 Review src/cart.ts for corr…
```

Without a label the row falls back to the agent's dispatch number and the clipped first line of its
prompt. It is readable, but three near-identical prompts give you three near-identical rows to tell
apart at a glance — exactly when you most want to know which one is stuck.

## What a run looks like

A workflow renders as a live tool row that fills in as it goes: phases open and close, each agent
gets a line with its state and how long it has been running, and the last line is the run's shape.

```
● Workflow(review-changed)
  ⎿  ◇ Review ─────────────────────────────────────────────── ●3 · 41.6s
         ● review:src/cart.ts  · · · · · · · · · · · · · · · done  38.9s
         ● review:src/checkout.ts  · · · · · · · · · · · · · done  41.5s
         ● review:src/pricing.ts · · · · · · · · · · · · · · done  28.1s
     ◈ Summarize ──────────────────────────────────────────── ◐1 · 12.5s
         ◐ merge · · · · · · · · · · · · · · · · · · · ·  running  12.4s
     ◐ running · 2 phases · 4 agents · ⟂ 3 wide · 54.3s
```

|             |                                                                                                                                                                                                      |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `◈` · `◇`   | A phase, open · closed.                                                                                                                                                                              |
| `◐` `●` `✗` | An agent running · done · failed. A failed agent shows its reason beside its name.                                                                                                                   |
| `⟂ 3 wide`  | The **peak fan-out** — the most agents in flight at the same moment. This is the number that says whether you actually fanned out: five agents at `⟂ 1 wide` is a loop wearing a workflow's clothes. |

Past eight agents in a phase the extra rows are counted (`+ 4 more`), never dropped, and on a narrow
terminal the block collapses to fewer, denser lines rather than losing information. The row stays in
your transcript after the run ends, which is usually when you want to read it.

## Limits, honestly

<Warning>
  **There is no budget cap and no runaway backstop.** Nothing counts what a workflow spends, and
  nothing stops a script that dispatches far more agents than you meant. `parallel(items.map(…))`
  over a thousand-item list will try to start a thousand agents. Bound the list in the script.
</Warning>

* **One agent gets ten minutes.** After that its call gives up and resolves to a failure string; the
  rest of the run continues.
* **The run itself is not time-limited.** It ends when the script returns.
* **Nothing is persisted.** The result and its digest live in the transcript; there is no saved run
  history and no resume — running a workflow again runs it from the top.
* **A workflow cannot reach the disk or the network itself.** Only the agents can, through their own
  tools.
* **If the crew is unavailable**, the workflow refuses up front and says why, rather than hanging on
  its first `agent()` call.

<Card title="Sub-agents" icon="arrow-right" href="/helix/features/sub-agents">
  The fleet list and the viewer — where the agents a workflow dispatches show up.
</Card>
