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:
.mutagent/workflows/review-changed.js
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.
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
.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.
The meta block
A saved workflow declares a meta object so it can be listed and described without being run.
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
nameanddescriptionplain string literals — not a variable, not a template with a substitution in it.
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
args:
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:
The script API
Six things are in scope. There is nothing else — norequire, no timers, no file system, no network.
Return a value from the top level and it becomes the workflow’s result.
console is available for
debugging.
agent(prompt, opts)
Workflow agents are dispatched onto the same crew as any other agent in the session: they appear
in the fleet list 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()becomesnullin that slot; the others are unaffected. - A stage that throws inside
pipeline()drops that item tonull; 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.
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:
new Date() is not blocked, but it is the same trap wearing a different hat. Take the time from
args too.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:
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.
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
- 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.
Sub-agents
The fleet list and the viewer — where the agents a workflow dispatches show up.