Workflows
A workflow is a named, multi-phase fan-out of subagents that runs in the background, streams live progress, persists what it did, and can be resumed.
One mental model runs through the whole feature:
workflow › phase › agent…and a workflow is also a background job, so it has an id and an observable status from the moment it starts.
task |
one focused delegation, one report |
coordinate |
an ad-hoc decomposition, the standard fan-out → synthesize → verify shape |
workflow (script) |
orchestration the model writes, when the shape isn't a plain fan-out |
workflow (name) |
a named template you wrote, re-runnable and resumable |
The eleven things you can do
- Let the model write the orchestration — loops, per-item pipelines, budget-aware fan-out
- Run a workflow by name —
/workflows run review target=… - Multi-phase execution — phases run in order, each rolling up its own status
- Parallel fan-out and per-item pipelines
- Background it — returns a run id + job id immediately
- Disposable expert subagents — built-in personas or your own Markdown ones
- Watch it live — phase rail, per-agent status, tokens, cost, timing
- Drill into one agent — its prompt, activity, result, error
- Watch several at once — multiple concurrent runs is the normal case
- Control it — stop, pause/resume, cancel, skip, retry, save
- Inspect and resume history — long after the session ended
Model-authored scripts
A named definition is data: phases, agents, prompts. That covers most orchestration — but not loop until two consecutive rounds find nothing new, or scale the fan-out to whatever budget is left. For those the model writes the orchestration itself, in a small Python subset:
meta = {"name": "review-diff",
"phases": [{"title": "Review"}, {"title": "Verify"}]}
phase("Review")
reviews = await parallel([
(lambda d=d: agent(f"Review {args['target']} for {d} problems", label=d))
for d in ["correctness", "security", "performance"]
])
phase("Verify")
checked = await pipeline(
reviews,
lambda r: agent(f"Try to REFUTE these findings:\n{r}", agent_type="verify"),
)
return {"reviews": reviews, "verdicts": checked}The hooks are the engine's own API:
await agent(prompt, label=, phase=, agent_type=, model=, schema=) |
one child agent → its final text |
await parallel([thunks]) |
barrier — waits for all |
await pipeline(items, *stages) |
per-item chains, no barrier between stages |
phase(title) · log(msg) |
the rail and the run log |
args · budget.total/spent()/remaining() · json |
inputs and spend |
Thunks are zero-arg lambdas that call agent, and must bind the loop
variable by default argument (lambda d=d: …) — the usual Python late-binding
trap, and the one mistake that makes a fan-out run the same prompt N times.
Prefer pipeline over back-to-back parallel. A pipeline lets item B
enter stage 2 while item A is still in stage 1; a barrier makes every fast item
wait for the slowest. Only put a barrier between stages when a stage genuinely
needs all prior results at once — deduping across the whole set, an early
exit on a total count, or a prompt that compares findings to each other.
What a script may do
Loops, conditionals, comprehensions, f-strings, try/except, def,
lambda. Not imports, file or network access, or attributes starting with
_. Anything outside orchestration is an agent's job, where the permission
system applies — and the agents a script spawns inherit the parent's tools,
permissions and budget unchanged, so writing the orchestration never widens
what runs inside it.
This is a guardrail, not a security boundary: the script's author is the session's own model, which in most configurations can already run shell commands. It keeps a confused or injected script inside its job.
A script is validated before anything spawns, so a bad one costs nothing and comes back with the line number from your own source.
Running one
/workflows list # what's available, and from where
/workflows run review target=mantis_agent/agent.py
/workflows # the live viewer
/workflows history # past runs (persisted)
/workflows resume w2h94j # replay the unchanged prefix
/workflows export w2h94j # dump the run's JSON into the CWDArguments are key=value; anything left over becomes the objective input, so
/workflows run understand the auth flow does what it looks like.
The model can start one too, with the workflow tool — but only when you asked
for orchestration. Workflows spawn many agents and cost far more than a single
task call, so the tool description tells the model that scale must be
requested, not inferred. To turn the feature off entirely:
export MANTIS_AGENT_DISABLE_WORKFLOWS=1Built-in templates
| name | shape |
|---|---|
understand |
three parallel readers over a subsystem → one brief |
design |
three independent designs → judge → synthesized recommendation |
review |
dimensions in parallel → adversarial verify per dimension → report |
research |
multi-modal sweep → deep read per lead → sourced answer |
implement |
plan → implement → adversarial verify (VERDICT: PASS/FAIL/PARTIAL) |
The definition format
Definitions are Markdown with frontmatter — the same shape as agents/*.md and
skills/*/SKILL.md — plus a fenced json block holding the phase graph.
---
name: review
description: Review a change across dimensions, then verify each finding
when_to_use: on a diff that is about to ship
---
Prose here is the shared **briefing**: it is prepended to every agent's prompt
in this workflow. Children start with no memory of anything, so this is how
house rules travel with them.
```json
{
"inputs": [
{"name": "target", "required": true, "description": "what to review"}
],
"phases": [
{"title": "Review", "mode": "parallel", "detail": "one per dimension",
"agents": [
{"label": "correctness", "agent_type": "explore",
"prompt": "Review {target} for correctness bugs."},
{"label": "edge-cases", "agent_type": "explore",
"prompt": "Review {target} for edge cases."}
]},
{"title": "Verify", "mode": "pipeline", "over": "phase:Review",
"stages": [
{"label": "refute", "agent_type": "verify",
"prompt": "Try to refute these findings:\n{item}"}
]},
{"title": "Report", "mode": "sequential",
"agents": [
{"label": "report", "prompt": "Write the review:\n{phase:Verify}"}
]}
]
}
```Where they live, and who wins
| source | path | precedence |
|---|---|---|
| project | ./.mantis/workflows/*.md |
highest |
| user | $MANTIS_AGENT_HOME/workflows/*.md |
middle |
| built-in | shipped with the SDK | lowest |
Later sources win by name, so a project can override a built-in review
with its own. A definition that fails to parse is skipped and named in
/workflows list — a broken file never takes down the session.
Phase modes
| mode | behavior |
|---|---|
parallel |
every agent at once, behind a barrier — the phase ends when all are done |
sequential |
in order; each agent sees the previous one's output as {prev} |
pipeline |
one independent chain per item from over; no barrier between items |
over is either phase:<Title> (an earlier phase's results, one item per
result) or input:<name> (a newline- or comma-separated input).
Template placeholders
| placeholder | resolves to |
|---|---|
{name} |
a declared or supplied input |
{phase:Title} |
the joined results of an earlier phase |
{item} |
the current item (pipeline only) |
{prev} |
the previous stage's / agent's output |
{index} |
1-based position |
Unknown placeholders are left exactly as written, so a prompt containing JSON or code braces survives untouched.
Agent types
agent_type picks the persona: the built-in explore, plan,
general-purpose and verify, or any custom one you defined in
.mantis/agents/<name>.md. An unknown type falls back to the workflow default
and is logged rather than failing the run.
Each child carves its tool kit from the parent's belt through its persona's
policy, and inherits the parent's permission gate and budget — a write-capable
child prompts you exactly as the parent would. Orchestration tools themselves
(task, coordinate, workflow) are stripped from children, so a fan-out
cannot recursively fan out.
Safety rails
A definition may declare at most 64 agents, and a pipeline whose over
resolves to more items than that is refused at run time — before anything is
spent.
Backgrounding
Starting a workflow returns immediately:
▶ workflow review · run w2h94j · job #3 · 3 phases
→ /workflows to watch · /jobs for lifecycle · the report lands as a job notificationThe run id and the job id are two views of one thing: /jobs shows lifecycle
(running, cancel, elapsed), /workflows shows structure (phases, agents,
tokens). Each names the other. When it finishes, the report is injected as
context so the model learns the outcome on its next turn — and
job_output(<id>) reads it early.
The viewer
/workflows with no arguments opens the live overlay:
Workflows 1/2 · review (w2h94j) · job #3
◇ Review 2/3 ❯● ◇ correctness: reading agent.py… ▶ 12s · ↓4.1k tok
✓ Verify 3/3 ● ✓ edge-cases: three findings… 28s · ↑9.2k tok
◇ Report 0/1 ● ◇ refute·1: checking claim 2… ▶ 4s · ↓1.8k tok
↑↓ select · enter/→ inspect · ←/esc back · x stop · p pause/resume · c cancel · k skip · r retry · s saveLeft is the phase rail (glyph · title · done/total) for the selected run; right
is the agent list. ↑↓ moves across all runs, so several concurrent
workflows are one continuous list rather than a special case.
Drilling in
Enter (or →) opens one agent:
Workflow agent · correctness
workflow: review (w2h94j) · job #3
phase: Review · explore · qwen2.5:7b
status: running · 12s
progress: 2 turns · 5 tools · 4.1k tok · $0.0042
prompt
Review mantis_agent/agent.py for correctness bugs.
recent activity
- grep
- read_file
result
…Esc (or ←) goes back: detail → list → closed. Closing the overlay never
stops the run.
Only observable facts appear: prompt, tool names, turn counts, visible output. Hidden model reasoning is never surfaced — the engine does not record it.
Controls
| key | action | when it applies |
|---|---|---|
x |
stop the run | while it is running |
p |
pause / resume | pausing gates new agents at their phase boundary; in-flight turns finish |
c |
cancel the selected agent | while that agent is running |
k |
skip the selected agent | queued (marked cancelled) or running (aborted) |
r |
retry the selected agent | once it has finished, errored, or been cancelled |
s |
snapshot the run into the durable store | always, live or from history |
An action that does not apply says why — "cannot cancel: agent a0 is done", "review is not live (loaded from history) — only 'save' works here" — instead of failing silently.
History and resume
Every run is written to $MANTIS_AGENT_HOME/workflows/runs/<run-id>.json when
it ends, including runs that were stopped or that failed. Plain JSON: the run
snapshot plus the definition name and its inputs.
/workflows history
/workflows resume w2h94jResume replays every agent whose phase, label and prompt digest are
unchanged — those come back instantly, free, marked (replayed) — and re-runs
everything else. Edit a prompt in the definition and that agent, and everything
downstream that depends on it, runs live again.
Input values that look like credentials (api_key, token, secret, …) are
written as [redacted], and a redacted value is never resurrected on resume.
From Python
from mantis_agent import load_workflow_definition, make_workflow_tool
from mantis_agent.workflow_tool import prepare_workflow_launch
defn = load_workflow_definition("review")
launch = prepare_workflow_launch(
defn, {"target": "mantis_agent/agent.py"},
agent_runner=my_runner, # or omit and let make_workflow_tool build one
model="qwen2.5:7b",
)
print(launch.run_id) # exists before anything runs
result = await launch.execute() # persists the artifact on the way outOr hand the whole thing to a model as a tool:
registry.add(make_workflow_tool(
model=model, provider=provider, tools=parent_kit,
permissions=permissions, jobs=job_manager,
on_run=viewer.register, # live registration
on_progress=progress_sink, # task-tool event shape
))See also
- Sub-agents — the personas a workflow's phases run
- Budget & limits — capping what a fan-out can spend
- Permissions — the gate children inherit