Workflows
The pipeline a run follows: phases, gates, defaults, and the board automation that starts them.
A workflow says what happens, in what order, and where a person looks at it. It is stored with your project as plain YAML and edited in a visual editor — in the dashboard, the desktop app, the Mac app, or a phone — so the whole team shares one definition and every run is reproducible. Everyone can read a workflow; changing one takes the Manage workflows permission (see Roles & permissions).
What one looks like
version: 1
id: feature # stable identity — renaming never breaks a reference
name: Feature pipeline
phases:
- id: spec
title: Spec
gate: true # stop for review after the spec
steps:
- type: prompt
prompt: Write a short spec for the task. List the files you expect to touch.
- id: coding
title: Coding
gate: true
steps:
- { type: command, name: install, run: pnpm install }
- { type: prompt, prompt: Implement the approved spec. }
- { type: check, name: lint, run: pnpm lint }
- { type: check, name: test, run: pnpm test }
- id: merge
title: Merge
terminal: true
steps:
- { type: git, action: merge, method: squash }
- { type: notify, to: [slack], message: Merged and shipped. }
A project keeps as many workflows as it likes, and each run picks one. With none defined at all, a built-in spec → coding → merge pipeline applies, so a fresh project runs out of the box — and the marketplace has ready-made ones to install.
Every run also carries whatever skills and MCP servers its project has picked, without a workflow having to say so.
Phases
| Field | What it does |
|---|---|
gate: true | When the phase's steps finish, the run stops and waits for a person. See Gates & reviews. |
terminal: true | Marks the closing phase. The editor keeps exactly the last phase terminal, and a terminal phase cannot gate. |
approvers, self | Who may approve this phase, and whether the author may approve their own work. |
runner | Where the phase executes — see Run on your own machine. |
include | Restricted folders or files the
phase works with, like [assets/brand/], which runs
otherwise leave out. The run gets what the person who started it can
open of them. See
Restricted folders and files. |
when | Run this phase only when a condition holds. See below. |
timeout | How long one attempt of the phase
may work, first step to last — 3h. See
Time limits below. |
onStatus,
setStatus | Board automation. See below. |
Steps run in order within a phase. Ten types cover the pipeline — see the step reference.
Skipping a phase
A phase can say when it applies:
- id: notify-release
title: Announce
when: branch == "release/*" && !trigger
steps: […]
The expression reads the run's own facts at the moment the phase would
start: source (manual, trigger, api…), branch,
repo, trigger, and task (whether a
backlog task is behind the run).
The grammar fits in a sentence: clauses joined by &&
or by || — one or the other, never mixed — where a clause is
a bare field (task), a negated one (!trigger),
or a comparison against a quoted string. Comparisons match with the same
wildcards trigger filters use. A phase whose condition is false settles
as skipped and the run carries on.
Identity, order, and the default
A workflow's identity is its id, which never changes.
Renaming changes only the display name; every task and routing rule keeps
pointing at the same workflow.
There is no magic filename either. The project's default workflow is simply the first one in order, which is what an unpinned task or a routing rule's fallback resolves to.
Defaults
defaults:
agent: { provider: claude, model: opus-5, effort: high }
basePrompt: Keep diffs small; follow the repo architecture docs.
cacheWorkspace: true
ownsStatus: true
runner: { labels: [linux] }
timeouts: { command: 20m, agent: 1h, phase: 3h, run: 8h }
secrets: [NPM_TOKEN, SENTRY_AUTH_TOKEN]
metrics: [sales.hourly, org/sales.daily]
reports: [weekly-summary]
| Field | What it does |
|---|---|
agent | The provider, model, and reasoning effort every prompt-driven step inherits. Any step can override it. See Agents & models. |
basePrompt | This workflow's own standing instructions for every agent in its runs, added after the organization's and the project's — never replacing them. |
systemPrompt | The older spelling. It used to
replace the project's instructions; it is read now as the start of
basePrompt, so it adds like every other layer. |
cacheWorkspace | Reuse one workspace across the run's phases, for install-heavy repositories. See Machines & isolation. |
ownsStatus | Whether a run of this workflow moves the task's board status. Defaults to true; set it false on a component workflow meant to be used as a lane. |
runner | The default machine selector for every phase. |
timeouts | This workflow's time limits, any
of command, agent, phase and
run. A step's or a phase's own timeout wins.
See Time limits below. |
secrets | At the top level, beside
phases. Names of organization secrets to put in the run's
environment — only what is listed resolves, and values never appear in
the file. |
metrics, reports | At the top
level too. The values a run writes for the dashboards: a step writes a
metric as $PERACTOR_METRICS/<key>.json (a number,
{"value": 12, "unit": "USD"}, a list of numbers or a table)
and a report as $PERACTOR_REPORTS/<key>.md. Each is
written as the step ends, and the run says so. A key not listed here is
refused. org/<key> is the organization's own, which a
project's run may write only after someone with Manage store creates it
and names the project. If the run fails, the last good value stays and is marked
stale. Known run secrets are removed from these files before transfer and
again before storage. If redaction leaves an invalid metric, the previous
value stays. |
Trigger key, title and prompt templates, and a Git step's tag name,
can read a metric: {{ store.metric.release.version }} uses
the project's exact key release.version;
{{ store.metric.org/release.version }} uses the organization's.
Missing metrics render empty. Use | json for a structured
value. A tag step reads the value when it runs, so an earlier step can
write the version it tags.
Time limits
Every piece of work in a run has a limit, so a test that hangs, a server started in the foreground or an agent going round in circles gives its machine back instead of holding it until somebody notices. There are four, and each holds with nothing configured:
| Limit | What it times | Unless set | Set by |
|---|---|---|---|
| Command | One run of a command or
check line, or a preview's build |
1h | The step's timeout, else
defaults.timeouts.command |
| Agent turn | One turn of the agent in a
prompt, publish, plan,
mail, calendar or document
step, and each fix a check asks for | 2h | The step's
timeout, else defaults.timeouts.agent |
| Phase | One attempt of a phase, first step to last | 4h | The phase's timeout, else
defaults.timeouts.phase |
| Run | The whole run, every phase and lane together | 12h | defaults.timeouts.run |
Write a limit as 30s, 20m,
1h30m, 2h or 2d; a bare number is
minutes. Anything from one second to seven days. A value that isn't a
duration is refused when the workflow is saved.
- Waiting isn't counted. Time at a gate, on a question, at a
preview, in an interactive step's conversation, asleep at a
waitstep or in the queue doesn't count. A run that waits a weekend for review still has all of its limit. - A run's time is the clock on the wall. Two lanes working side by side for an hour use an hour of the run's limit, not two.
- At the limit, the work stops. The step's process and
everything it started are stopped. A command fails its phase. A check
is red, so its agent gets the usual chance to fix it and an enclosing
loopsees it like any failure. An agent's turn ends and its phase fails, with the work kept, so Resume from this step carries on from there. A phase or run that runs out fails at the step it was on. - People are told. The step's card says "timed out after 20m", the run's failure reason names the step, and the failure is announced like any other. A limit of 30 minutes or more says in the transcript when 80% of it is used.
- Your organization can hold them lower. An admin sets the longest each limit may be under Organization → Run defaults, and a project can set its own lower under Settings → General, never higher. A workflow or step that asks for more is held to it.
- The machine stops it, and Peractor makes sure. A machine that hasn't stopped a couple of minutes past the limit is stopped for it: on Peractor's machines the run's container is killed, and on a machine you paired the work is cancelled and the phase marked failed.
A server a later step needs is started in the background — see command in the step reference.
Status automation: the board drives execution
Phases can bind to board statuses, and the binding works both ways:
phases:
- id: spec
title: Spec
gate: true
onStatus: in-spec # dragging a task here starts the workflow at this phase
setStatus: in-spec-review # when the phase finishes, the task moves here
steps: […]
- onStatus — a task dragged into that status starts this workflow at this phase. Starting mid-pipeline is fine. While the phase runs, the task shows that status, so the board always mirrors what is happening. The run is the mover's, so putting a task there takes the Start runs permission.
- setStatus — where the task lands when the phase's work completes.
Statuses are qualified by where they come from. A bare name like
in-spec means your own board; a tracker's real status is
written as <connector>:<Status>. A list holds
one per tracker, so a single workflow can drive tasks from several
origins, each in its own vocabulary:
onStatus:
- peractor:queued
- "linear-a1b2c3:Todo"
setStatus:
- peractor:building
- "linear-a1b2c3:In Progress"
With those two fields, dragging a card is what starts, advances, and files work.
Next: the step reference · Gates & reviews