Step types
Every step has a type: Agent (“Run a coding agent or LLM with a prompt”), Shell (“Run a command: tests, linters, git, deploys”) or Approval (“Pause until a human approves”). You can switch a step’s type at any time with the type picker at the top of the step’s settings; fields for other types are kept but ignored.
Common fields
Section titled “Common fields”| UI label | JSON | Default | Notes |
|---|---|---|---|
| Name | name |
New agent step / Run command / Approval |
Display name in the graph, logs and notifications. |
| Step id | id |
agent, command, approval (+ a number if taken) |
Stable key used in templates: {{steps.<id>.output}}. Letters, digits, -, _; must start with a letter or _. Unique within the pipeline. The UI strips other characters as you type. |
| — | description |
— | Optional free text. Not shown in the editor. |
Agent steps
Section titled “Agent steps”An agent step renders its prompt and runs it on a harness with a model, in the run’s workspace.
Harness & model
Section titled “Harness & model”| UI label | JSON | Default | Notes |
|---|---|---|---|
| Harness | harnessId |
the first enabled, detected CLI harness when the step is added (falls back to the first enabled harness, then claude-code) |
● marks harnesses detected as available, ○ those that aren’t. API harnesses are labelled “(API)”. Disabled harnesses are hidden unless the step already uses one. Changing the harness clears the model. |
| Model | model |
Default (the harness’s default model) | Chips for the harness’s known models, a Default: … chip, and a text box for any other model id. A refresh chip pulls the latest model list when the harness supports it. |
The editor warns when the chosen harness is disabled (“… is disabled, so this step will fail”) or not detected (“… is not available: …”). At run time:
- An unknown harness fails the step with
Harness "x" is not configured. - A disabled harness fails it with
Harness … is disabled — enable it on the Harnesses page or pick another harness. - A CLI that isn’t installed fails with
Failed to start <command>: … - is "<command>" installed and on PATH? (Settings → Extra PATH entries).
See Harnesses for what each harness does with the prompt, model and system prompt.
Prompt
Section titled “Prompt”| UI label | JSON | Default | Notes |
|---|---|---|---|
| Prompt | prompt |
Resolve {{issue.key}}: {{issue.title}} + blank line + {{issue.description}} |
Required. A template. Not shell-quoted. Generate with AI writes or revises it for you. |
| System prompt (optional) | systemPrompt |
empty | A template. Passed with the harness’s system-prompt flag if it has one (Claude Code and Pi use --append-system-prompt, which appends to the agent’s built-in system prompt). For CLI harnesses without such a flag it’s prepended to the prompt, separated by a --- line. API harnesses send it as the system message. |
Each agent runs in isolation and only knows what its prompt says. Include the issue, the upstream outputs it needs, what not to do, and what to output. See Templates and the prompt guidance in Generating pipelines with AI.
Subagents
Section titled “Subagents”Chips for each subagent defined in Pipeline settings → Subagents; click to attach (subagents: ["id", …]). Only harnesses with Subagent arguments configured receive them (the built-in Claude Code harness does); others ignore them, and the editor warns “… has no subagent arguments configured, so these subagents will be ignored.” See Subagents.
What counts as success
Section titled “What counts as success”An agent step succeeds when the harness process exits with code 0 (and, for structured output formats, doesn’t report an error result), or the API call completes normally. The Claude API harness fails on a refusal or max_tokens stop reason. Then the verdict patterns are applied.
Shell steps
Section titled “Shell steps”A shell step runs a command with a POSIX shell in the run’s workspace: /bin/sh on macOS and Linux, and Git Bash (from Git for Windows) on Windows. The same commands work on every OS.
| UI label | JSON | Default | Notes |
|---|---|---|---|
| Command | command |
npm test |
Required. A template. Generate with AI can write it. |
- Exit code
0is success; anything else fails the step withCommand exited with code N. - Every interpolated
{{value}}is shell-quoted automatically, so issue text can’t inject commands. Add| rawas the last filter to insert a value unquoted, for values that are themselves commands:{{vars.testCommand | raw}}. See Shell quoting. - Issue and run data is also exported as environment variables (
$FACTORY_ISSUE_KEY,$FACTORY_ISSUE_TITLE,$FACTORY_BRANCH,$FACTORY_WORKSPACE, …). Using them inside double quotes is often the most readable option. See Environment variables. - The step’s output is stdout and stderr combined (last 20,000 characters).
- Multi-line commands are fine; the whole text is passed to the shell.
- Child processes inherit a non-interactive environment:
CI=1(unless already set),NO_COLOR=1,FORCE_COLOR=0,GIT_TERMINAL_PROMPT=0. There is no stdin, so anything that prompts will hang until the timeout.
# Open a PR (from the built-in templates)git push -u origin HEAD && gh pr create --base {{run.baseBranch}} --head {{run.branch}} \ --title "$FACTORY_ISSUE_KEY: $FACTORY_ISSUE_TITLE" \ --body "Automated by Jimothy run #$FACTORY_RUN_NUMBER. $FACTORY_ISSUE_URL"Approval steps
Section titled “Approval steps”An approval step pauses its branch of the pipeline until a human decides.
| UI label | JSON | Default | Notes |
|---|---|---|---|
| Message shown to the approver | approvalMessage |
Approve {{issue.key}} to continue. |
A template, rendered as Markdown. If empty at run time: Approve to continue. |
| Let the approver pick from | choices.fromStep |
Nothing (field absent) | An agent or shell step whose numbered options (### Idea 1: … headings or a 1. … list) the approver picks from. See Picking from options. |
| Selection | choices.mode |
multiple |
multiple (one or more) or single (exactly one). Shown once a source step is set. |
When the step starts, it becomes Needs approval, the approval.required notification fires (desktop by default, plus any channel subscribed to it), and the run page shows a banner with the message, an optional comment box, and Reject / Approve. See Approvals.
- Approve: the step succeeds. Its output is the comment. With choices, the approver must pick first, and the output is the picked options’ text followed by the comment as
Approver notes: …. - Reject: the step fails with
Rejected by <name>: <comment>. Dependents are skipped and the run fails, unless the step has Continue on error or a loop-back. - The decision is recorded with the approver’s name (Settings → Your name, default
me) and time. Templates can read{{steps.<id>.comment}}and{{steps.<id>.approvedBy}}, and with choices{{steps.<id>.selected}},.selectedIdsand.selectedTitles. - Other parallel branches keep running while an approval waits.
- Rejecting with a loop-back sends work back upstream: set On failure, loop back to on the approval step, and the rejection comment is available to the re-run steps as
{{steps.<approval id>.output}}. Use.outputhere: the loop resets the approval step and clears itscommentandapprovedBy, but keeps its output.
Approval steps ignore Timeout, Retries and the pass/fail patterns: they wait indefinitely and run once per pass.
Flow options (all step types)
Section titled “Flow options (all step types)”| UI label | JSON | Default | Notes |
|---|---|---|---|
| Runs after | dependsOn |
previous step (field absent) | Steps this one waits for. [] = starts immediately. See DAG & parallelism. |
| Timeout (minutes) | timeoutMinutes |
none | Kills the attempt (whole process tree) after this long. Empty or 0 = no timeout. Not applied to approvals. |
| Retries on failure | retries |
0 |
Extra attempts after a failure (UI allows 0–10). Exponential backoff 2s, 4s, 8s, 16s, 30s cap. Not applied to verdict failures or approvals. |
| Run only if | runIf |
empty | Template; the step is skipped unless it renders truthy. Placeholder: {{vars.deploy}}. |
| Continue the pipeline even if this step fails | continueOnError |
off | Dependents run and the run can succeed even though this step failed. |
Details and examples: Retries, timeouts & conditions.
Quality gate & feedback loop (all step types)
Section titled “Quality gate & feedback loop (all step types)”| UI label | JSON | Default | Notes |
|---|---|---|---|
| Pass if output matches (regex) | passPattern |
empty | If set and the output doesn’t match, the step fails. Placeholder VERDICT:\s*APPROVE. |
| Fail if output matches (regex) | failPattern |
empty | If it matches, the step fails. Checked before the pass pattern. Placeholder CHANGES_REQUESTED. |
| On failure, loop back to | loopBackTo |
— fail the step — | When the step fails, re-run from this step. |
| Max loops | maxLoops |
2 |
How many times this step may loop back per run (UI allows 1–10). Disabled until a loop-back target is chosen. |
Patterns are case-insensitive and multiline. They don’t apply to approval steps. See Loops & verdicts.
Environment (all step types)
Section titled “Environment (all step types)”env adds environment variables for this step only, on top of global (Settings) and project env. For agent steps the harness’s own env is applied last. FACTORY_* variables can’t be overridden. See Environment variables.
JSON reference
Section titled “JSON reference”{ "id": "implement", "name": "Implement", "type": "agent", "harnessId": "claude-code", "model": "sonnet", "prompt": "Implement {{issue.key}}: {{issue.title}}\n\n{{issue.description}}\n\nPlan:\n{{steps.plan.output}}", "systemPrompt": "You are a careful senior engineer. Keep changes minimal.", "subagents": ["test-writer"], "dependsOn": ["plan"], "timeoutMinutes": 60, "retries": 1, "env": { "NODE_ENV": "development" }}{ "id": "test", "name": "Run tests", "type": "shell", "command": "{{vars.testCommand | raw}}", "dependsOn": ["implement"], "timeoutMinutes": 20, "loopBackTo": "implement", "maxLoops": 2}{ "id": "approve", "name": "Human approval", "type": "approval", "approvalMessage": "Approve to push {{run.branch}} and open a pull request for {{issue.key}}.", "dependsOn": ["review"]}All fields:
| Field | Type | Applies to |
|---|---|---|
id |
string | all |
name |
string | all |
type |
"agent" | "shell" | "approval" |
all |
description |
string | all |
harnessId |
string (harness id, e.g. claude-code, codex, anthropic-api, simulator) |
agent |
model |
string; empty = harness default | agent |
prompt |
template | agent |
systemPrompt |
template | agent |
subagents |
string[] (subagent ids) | agent |
command |
template | shell |
approvalMessage |
template | approval |
dependsOn |
string[]; absent = previous step | all |
timeoutMinutes |
number | agent, shell |
retries |
number | agent, shell |
continueOnError |
boolean | all |
passPattern / failPattern |
regex string (JSON-escaped: "VERDICT:\\s*APPROVE") |
agent, shell |
loopBackTo |
step id | all |
maxLoops |
number, default 2 | all |
env |
object of strings | agent, shell |
runIf |
template | all |