> ## 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

> Spread work across sub-agents with a small script. Save it in your repo, run it by name, and watch it live.

> [!NOTE]
>
> **Early Access.** Commands and behavior can change between releases.

A **workflow** is a small JavaScript program that dispatches [sub-agents](https://docs.mutagent.io/architech/features/sub-agents.md)
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.

You don't need a flag or a keyword. Architech uses a workflow on its own when the work has a
**shape**: the same operation over a list, or independent pieces that could run at once. Save that
shape in your repo and you can run it by name.

## A workflow you can run

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

```js .architech/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, plus a row in the transcript that showed both phases live.

## Background runs

Ask Architech to run a workflow in the background when you have other work to do. It starts the
workflow with `run_in_background: true`, gets a `job_id`, and receives the result when it finishes.
It can read the status or full result again with `Workflow({ job_id: "workflow-1" })` without
starting the agents again.

In an interactive session, **Ctrl+Shift+B** sends running foreground workflows to the background.
Their agents keep running; nothing restarts. Your terminal must send this key combination as its
own key.

The last 50 finished background results stay available in the current session. Restarting or
switching sessions drops the handles, not the agents already in the fleet list. Use the fleet list
to inspect or stop single agents; `Jobs` doesn't list background workflow results.

## When a workflow is the right tool

> **Reach for one**
>
> 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.

> **Don't**
>
> 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.

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 about five times one agent, plus the merge. That's worth it
when five agents read five different things, and a waste when you split a question with one
answer.

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

## Where workflows live

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

Files in `.architech/workflows/` are ordinary source files: commit them, review them in a pull
request, share them with your team. Use `.js` for new workflows. Architech also picks up existing
`.ts` files, but their bodies must still be plain JavaScript.

> [!WARNING]
>
> The body is **plain JavaScript**, whatever you name the file. It runs in an isolated JavaScript context,
> 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.

### 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. |

Architech reads `meta` by pattern; it doesn't run it. So two things matter:

* 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 without a readable `meta` is left out of the list, not run, and `/workflows` says how many
it skipped. Architech never runs a file in `.architech/workflows/` 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 Architech 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 Architech 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 as Architech passed it (an object or list sent as a JSON string is
decoded first). It's what makes a script *reusable*, 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

A script can use these six things and 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. Keep it short and unique: `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. |
| `model` | Run this agent on a different model, as `provider/model`. A model that cannot be found fails that one call. |
| `timeoutMs` | This agent's time limit in milliseconds, counted from when it starts. `0` turns it off. |
| `startTimeoutMs` | How long to wait for the agent to start; defaults to the larger of 60 minutes and six times the run limit. `0` turns it off. |

Workflow agents are sub-agents like any other: they appear in the
[fleet list](https://docs.mutagent.io/architech/features/sub-agents.md) alongside anything you started by hand.

### Failure is a value, not an exception

* An `agent()` whose run fails resolves to a string beginning `[agent failed: …]`. Your script keeps
  going, and can branch on it.
* An `agent()` that runs past its time limit resolves to `[agent timed out: …]`. The agent itself
  keeps running in the fleet list. A late result replaces the timeout marker only when the thunk
  or stage returns that marker unchanged and its `parallel()` or `pipeline()` is still waiting.
  A direct `await agent()`, a transformed marker or an already-returned combinator is not
  retroactively updated just because the rest of the workflow is still running.
* 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.

An agent that never starts can return `[agent never started: …]` when its queue-wait limit is
reached. So check for `null`, `[agent failed:`, `[agent timed out:` and `[agent never started:`
before feeding a result into the next prompt.

## Two rules

### `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 should 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 path each time, and its record
no longer matches what it would do again. If you need a seed or a timestamp, decide it **outside**
and pass it in 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 has the same problem. Take the time from `args` too.

### Label every agent

The live view names each row by `opts.label`. Give every `agent()` call a label that says which
piece of work it 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 shows the agent's number and the cut-off first line of its prompt. Three
similar prompts then give you three similar rows, just when you need to see which one is stuck.

## What a run looks like

A workflow shows as a live row that fills in as it runs. Phases open and close, each agent gets a
line with its state and run time, and the last line sums up the run.

```
● 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. |
| `◔` | An agent past its time limit that is still running. It settles as `done late` or `failed late`. |
| `⟂ 3 wide` | The **peak fan-out**: the most agents running at the same moment. It tells you whether the run really went parallel. Five agents at `⟂ 1 wide` ran one after another. |

Past eight agents in a phase, extra rows are counted (`+ 4 more`), never dropped. On a narrow
terminal the block folds into fewer, denser lines without losing information. The row stays in
your transcript after the run ends.

## Limits

> [!WARNING]
>
> **There is no budget cap and no runaway limit.** Nothing counts what a workflow spends, and
> nothing stops a script that starts far more agents than you meant. `parallel(items.map(…))`
> over a thousand-item list queues a thousand agents: sub-agents run up to 15 at a time by
> default and the rest wait in a queue, but every one of them runs. Bound the list in the script.

* **One agent gets ten minutes by default**, counted from when it starts, not while it waits in the
  queue. After that its call stops waiting and resolves to a timed-out string; the agent is not
  stopped, and the rest of the run continues. Change it per call with `timeoutMs`.
* **The run itself is not time-limited.** It ends when the script returns. Agents still running at
  that point are named in the result and keep running in the fleet list.
* **No resumable workflow history is persisted.** The result and its digest live in the transcript;
  background handles live only in this session. 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 sub-agents aren't available**, the workflow refuses up front and says why, instead of
  hanging on its first `agent()` call. `architech --prime` has no `Workflow` tool; workflows run in
  normal orchestrator and agent sessions.

> **[Sub-agents](https://docs.mutagent.io/architech/features/sub-agents.md)**
>
> The fleet list and the viewer, where a workflow's agents show up.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.