Skip to the content.

CLI reference

The sf and stageflow binaries expose the same commands. Run sf --help for the full usage string.

Storage locations

Path Purpose
<git-root>/.stageflow/ Run store (SQLite) and per-run workspaces when inside a git repo
~/.stageflow/ Global home — sf_owned auth (agent/auth.json), global settings

Store backend: SF_STORE=sqlite only; SF_STORE=disk is rejected. If SQLite has no runs yet, a disk-era .stageflow/runs tree may be imported; if .stageflow is missing and .software-factory exists, the next store open renames it.

Global

sf --version
sf -V
sf --help

sf --version and sf -V print the npm package version on stdout and exit 0. They do not open a catalog, run store, or git root.

sf init

Scaffold a new Stageflow project at the git root (or current directory when not in git).

sf init

Creates (skipping files that already exist):

File Purpose
stageflow.yaml Manifest with pipelines/ and tasks/ roots
pipelines/hello.pipeline.yaml Inline single-stage pipeline
tasks/hello.task.yaml Sample task

Also ensures ~/.stageflow/ exists for global config.

sf run

Run a pipeline against a task file.

sf run --task <path> --pipeline <path> [--checkout <path>] [--json] [--include stages] [--skip-gates] [--git-sha <sha>] [--ci-pr-url <url>] [--ci-job-url <url>] [--operator-cwd <path>] [--operator-agent-dir <path>]
Flag Description
--task Path to a task YAML file (required)
--pipeline Filesystem path to a pipeline YAML file (required)
--checkout Override task checkout with a working tree path
--json Print one JSON document to stdout
--include stages With --json, append stages[] run projection (requires --json)
--skip-gates Fail the stage instead of waiting on HITL (see HITL)
--git-sha Record git SHA on the run (CI identity)
--ci-pr-url Record PR URL on the run
--ci-job-url Record CI job URL on the run
--operator-cwd Operator checkout root for skill resolution (default: process cwd)
--operator-agent-dir Pi agent directory for user/runner skills (default: Pi getAgentDir())

Exit codes:

Code Meaning
0 Pipeline succeeded
1 Failed (stage error, validation error, busy start, or --skip-gates on HITL)
2 Pipeline waiting on operator input

JSON outcomes (--json):

outcome ok runId exit
succeeded true present 0
failed false present (omit if start never created a run) 1
waiting false present 2
busy false omit 1

Busy codes: busy_capacity (concurrency limit), busy_checkout (same checkout leased).

Validation failure during sf run --json prints validate-shaped JSON (ok, scope, checks, findings…) with no outcome / runId (exit 1). See CI / headless.

Example:

sf run --task tests/fixtures/tasks/sample.task.yaml --pipeline tests/fixtures/pipelines/single.pipeline.yaml --json

With stage projections (CI / post-run extraction):

sf run --task examples/hello-world/my-task.task.yaml \
  --pipeline examples/hello-world/hello.pipeline.yaml \
  --json --include stages > sf-run.json

Each stages[] item is a StageProjection (snake_case): stage_id, status, envelope, artifacts, and optional last_at, pending_prompt. That --include stages schema is unchanged for diamond runs — it does not add pipeline_track or join-input fields. The multi-edge graph (a diamond join has two inbound pipeline_track edges; blocked_by lists unresolved parents) is on sf runs show --json, MCP get_run, and sf export-run.

--include stages without --json exits 1. See CI / headless.

sf runs

Inspect and control existing stored runs (in-progress, parked, or terminal). These verbs are not a 1:1 MCP tool list and they do not list pipelines or tasks. sf run stays the blocking start command.

sf runs list [--status created|running|succeeded|failed] [--since <iso>] [--pipeline <id-or-path>] [--json]
sf runs show --run <runId> [--from <sf-run.json>] [--json]
sf runs verify --run <runId> --stage <stageId> [--json]
sf runs recover --run <runId> --stage <stageId> [--guidance <text>] [--stop] [--json]
sf runs waiting [--run <runId>] [--json]
sf runs wait --run <runId> [--from <sf-run.json>] [--until any|waiting|terminal] [--timeout-ms <n>] [--json]
sf runs answer --run <runId> --stage <stageId> [--answer '<json>'] [--json]
sf runs feedback-decide --run <runId> --stage <sourceStageId> [--loop <loopId>] --decision extend|continue|abandon [--reason <text>] [--json]
sf runs retry --run <runId> --stage <stageId> [--json]
sf runs abandon --run <runId> --stage <stageId> [--json]
sf runs rerun --run <runId> [--json]
Subcommand Role
list Stored runs (same filters as MCP list_runs)
show Live projectRun for any run status (not the export-run completeness gate)
verify Completion-check attempts, verification dispositions, and evidence for one stage
recover Explicitly retry or stop a manual-recovery stage
waiting Waiting gates with pending_prompt
wait Block until waiting, terminal, or timeout
answer Submit an AskOperatorAnswer for a parked stage
feedback-decide Resolve a feedback-loop wait_for_human decision (extend / continue / abandon)
retry Retry a failed stage; process waits until waiting or terminal
abandon Mark a running stage abandoned
rerun Start a new run from a stored run; process waits until waiting or terminal

--from is accepted on show and wait only (reads runId from a prior sf run --json file). --answer is answer only.

Read vs mutate

Kind Verbs Host up
Read list, show, verify, waiting, wait Allowed
Mutate answer, feedback-decide, retry, recover, abandon, rerun Refused

Mutating verbs probe GET http://127.0.0.1:3847/api/health (1500 ms). HTTP 200 with parseable JSON → exit 1 without opening a mutating writer. This is not a single-writer lock: a host on another port (sf ui --port 4000) and a live blocking sf run are undetected second writers. Reads still work while a host is up.

Parked runs

A HITL park keeps store status running. --status waiting is not a valid list filter; use sf runs waiting.

JSON and exits

--json pretty-prints one document. Two families:

Inspect / wait / answer — MCP field names for the overlapping verb:

Verb --json shape Exit
list { "runs": [ … ] } 0 success, 1 error
show projectRun object 0 success, 1 error
verify Stage verification history: attempts, dispositions, checks, and evidence 0 success, 1 error
recover Completion result after an approved retry, or { "ok", "runId", "stageId" } for --stop 0 success, 1 error, 2 waiting after retry
waiting { "waiting": [ … ] } 0 success, 1 error
wait waitRun result: ok, reason, elapsed_ms, until, nested run 0 for waiting / terminal / already / timeout; 130 (or platform abort) with { "error", "code": "aborted" }
answer { "ok": true } 0 on success even if the run parks again; 1 on error
feedback-decide { "ok": true, "effect": "extended"|"continued"|"abandoned", "loopId" } 0 on success; 1 on error

Do not treat answer { "ok": true } as terminal — call sf runs wait / waiting for the next state. Do not reuse sf run exit 2 for a completed wait that woke on waiting.

Retry / rerun / abandon:

Verb --json shape Exit
retry, rerun sf run completion JSON (ok, outcome, runId, …) 0 succeeded, 1 failed/busy, 2 waiting
abandon { "ok", "runId", "stageId" } 0 success, 1 error

sf runs list

Flag Description
--status created | running | succeeded | failed
--since ISO timestamp; keep runs with created_at >= since
--pipeline Match pipeline_id or pipeline_path
--json Pretty-printed { "runs": [ … ] }

sf runs show

Flag Description
--run Run id (optional when --from provides runId)
--from Read runId from a prior sf run --json output file
--json Pretty-printed projectRun

Works for in-progress and parked runs. sf export-run still requires succeeded or failed. --json is the projectRun object, including pipeline_track (diamond joins show both inbound edges). --include stages on sf run --json stays a flat stages[] list and does not carry that graph.

When a feedback loop is active or waiting, --json includes active_feedback_loop and feedback_loops (history with replays / stage passes). See YAML catalog — Feedback loops.

sf runs verify

Flag Description
--run Run id (required)
--stage Stage id (required)
--json Attempt-scoped verification dispositions, checks, and stored evidence

Use this to see whether verification ran, why a completion check failed, and what an automatic repair later changed. It is intentionally stage-scoped, so run listings do not carry command output.

sf runs recover

Flag Description
--run Run id (required)
--stage Stage id (required)
--guidance Optional instruction recorded for the next agent attempt
--stop Record the decision to leave the stage failed; cannot be combined with --guidance
--json Completion JSON after retry, or the recorded stop decision

Only a stage with recovery.mode: manual that failed completion verification is eligible. A recovery retry starts a fresh attempt; a stop is terminal for that stage in this run.

sf runs waiting

Flag Description
--run Limit to one run (omit to scan all)
--json Pretty-printed { "waiting": [ … ] } (MCP list_waiting fields, including pending_prompt)

For waiting_kind: "feedback_loop_decision", entries also carry feedback_loop_id and deferred_target (exhausted wait_for_human loop). Resolve with sf runs feedback-decide.

sf runs wait

Flag Description
--run Run id (optional when --from provides runId)
--from Read runId from a prior sf run --json output file
--until any (default), waiting, or terminal
--timeout-ms Wait budget in ms. Default 60000. Must be in (0, 240000].
--json Pretty-printed waitRun result

reason is waiting | terminal | timeout | already. Timeout is success (ok: true, exit 0); the run is unchanged. Abort cancels only the wait, not the run.

Host-down park-and-answer:

sf runs waiting → sf runs answer → sf runs wait --until any
  reason waiting  → waiting then answer
  reason terminal → done
  reason timeout  → wait again

sf runs answer

Flag Description
--run Run id (required)
--stage Stage id (required)
--answer AskOperatorAnswer JSON
--json { "ok": true } on success

If --answer is omitted, read stdin JSON only when stdin is not a TTY. On a TTY or empty stdin, exit 1 with a missing-answer error.

Answer kinds match HITL (free_text, confirm, artifact_backed, multi_question).

sf runs feedback-decide {#sf-runs-feedback-decide}

Resolve a feedback-loop park when the source policy uses on_max_replays: wait_for_human and the loop is waiting_for_human.

sf runs feedback-decide \
  --run <runId> \
  --stage <sourceStageId> \
  [--loop <loopId>] \
  --decision extend|continue|abandon \
  [--reason <text>] \
  [--json]
Flag Description
--run Run id (required)
--stage Feedback-loop source stage id (required)
--loop Optional loop id when more than one loop could match
--decision extend — bump max_replays by one and accept the deferred send_back. continue — treat the source as succeeded and advance downstream. abandon — fail the source (and typically the run)
--reason Optional text recorded on abandon
--json { "ok": true, "effect": "extended"|"continued"|"abandoned", "loopId" }

Same semantics as MCP decide_feedback_loop and POST /api/runs/:runId/stages/:stageId/feedback-decision. Host-down mutate rules apply (refused while sf ui / sf mcp health responds on the default port).

Inspect loop state with sf runs show --json (active_feedback_loop, feedback_loops) and sf runs waiting (waiting_kind: "feedback_loop_decision").

sf runs retry / abandon / rerun

Human/API parity for the remaining control verbs. Waiting stages are not retryable or abandonable. retry and rerun block in-process until waiting or terminal (same 0 / 1 / 2 as sf run). MCP { "runId" } fire-and-forget is not the CLI contract.

sf envelope get

Read a stage envelope or CI handoff JSON from the run store.

sf envelope get --run <runId> --stage <stageId> [--json] [--from <sf-run.json>] [--detect-stage <id>] [--format envelope|handoff]
Flag Description
--run Run id (optional when --from provides runId)
--stage Stage id to read (required). After clonable fan-out this is the instance id (work~1), not the catalog id; run-once stays the catalog id. See YAML catalog — instance ids.
--from Read runId / runDir from a prior sf run --json output file
--detect-stage For --format handoff: when this stage emitted fork_choice: [], output { skipped: true } and exit 0
--format envelope (default) — raw stage envelope; handoff — downstream deliverables shape
--json Print JSON to stdout

Handoff format (--format handoff):

Shape When
{ "skipped": true } Detect stage emitted fork_choice: [] (when --detect-stage is set)
{ skipped: false, runId, runDir, stageId, diagrams: [{ diagram_type, spec_path, summary }] } Author stage succeeded with spec artifacts

With --from sf-run.json, handoff requires the run document to have outcome: "succeeded" and ok: true.

Exit codes: 0 success, 1 error (missing args, envelope not found, handoff build failure).

Example (Archify-on-PR):

sf envelope get --from sf-run.json --stage author-diagrams \
  --detect-stage detect-changes --format handoff --json > envelope.json

See CI: handoff envelope extraction and Envelopes: CI consumption.

sf export-run

Export a portable run projection for debug or audit.

sf export-run --run <runId> [--from <sf-run.json>] [--out <file>]
Flag Description
--run Run id (optional when --from provides runId)
--from Read runId from a prior sf run --json output file
--out Write JSON to a file under the current working directory (stdout when omitted). Path must stay under cwd (no ..).

Writes the full projectRun projection (includes pipeline_track and waiting fields). The run must be complete (succeeded or failed). In-progress runs exit 1.

Exit codes: 0 success, 1 error.

sf artifact read

Read a run workspace artifact as UTF-8 text (same path rules as MCP read_artifact).

sf artifact read --run <runId> --path <relPath> [--out <file>]
Flag Description
--run Run id (required)
--path Run-relative artifact path (as returned in envelope artifacts[]). Must be relative, with no .., and confined to the run workspace. Denied: any .pi-agent path segment, and files named auth.json.
--out Write contents to a file under cwd (stdout when omitted)

Exit codes: 0 success, 1 error (missing artifact, path escape, denied path).

Example:

sf artifact read --run "$RUN_ID" \
  --path stages/detect-changes/attempts/1/artifacts/changes.json

sf skills

List and install Pi skills under <git-root>/.pi/skills/ for pipeline stages that bind skill:.

sf skills list
sf skills install --from-path <dir> [--skill-name <name>]
sf skills install --from-zip <url-or-path> [--skill-name <name>] [--checksum sha256:<hex>]
Subcommand Description
list Installed project skills under .pi/skills/. Prints TSV name\tversion\tbin/<name>.mjs (one line per skill). An empty or missing skills dir prints no lines.
install --from-path Copy a local skill tree into .pi/skills/<name>/
install --from-zip Download or read a zip, locate skill root, copy, then run skill doctor
Flag Description
--skill-name Destination name (default: inferred from path or zip)
--checksum Optional sha256:<hex> integrity check for zip installs

Install runs the skill's bin/<name>.mjs doctor after copy. Missing or failing doctor exits 1.

Example (CI):

sf skills install --from-zip "https://github.com/tt-a1i/archify/releases/download/v2.15.0/archify.zip" \
  --skill-name archify

See CI: Skills in CI and YAML catalog: skill binding.

sf validate

Validate catalog YAML (pipelines, their stages, and tasks).

sf validate [--pipeline <path>] [--task <path>] [--strict] [--json]

With no flags, validates all pipelines and tasks declared in stageflow.yaml (manifest-all), including each pipeline’s stages (uses: / include:).

Flag Description
--pipeline Validate that pipeline file and its stages (uses: / include: transitively). Does not validate all tasks.
--task Validate that task file only
--strict Promote manifest warnings (catalog.manifest_missing, catalog.empty_catalog) to errors
--json Machine-readable findings

Use at most one of --pipeline or --task. The CLI rejects both.

Exit codes: 0 pass, 1 fail. Validate never exits 2 — no waiting state.

Does not prove provider auth or checkout paths.

Example:

sf validate --strict --json

sf ui

Start the operator console and MCP endpoint (sessions are the MCP product default).

sf ui [--port 3847] [--mcp-stateless]

Prints:

Opens the default browser. Process runs until interrupted. The run store resolves to <git-root>/.stageflow/ even when started from a subdirectory.

--mcp-stateless / STAGEFLOW_MCP_STATELESS=1 is a test/debug escape hatch that disables MCP sessions. See MCP.

Run either sf ui or sf mcp for a project — not both against the same store.

sf mcp

Start an MCP-only HTTP host (no operator console UI, no browser open).

sf mcp [--port 3847] [--mcp-stateless]

Prints the MCP endpoint URL (default http://127.0.0.1:3847/mcp). Also serves minimal GET /api/health. Same git-root / .stageflow/ semantics as sf ui. Sessions are the default; --mcp-stateless / env as above. See MCP.

sf providers

Manage Pi model provider authentication.

sf providers list
sf providers status [--provider <id>]
sf providers detect
sf providers source [get | set <pi_home|sf_owned>]
sf providers login <providerId> [--type api_key|oauth] [--api-key-env <VAR>]
sf providers logout <providerId>
Subcommand Description
list Available providers and auth capabilities
status Configured/disconnected state per provider
detect Pi home detection and credential binding
source get Show credential storage mode
source set pi_home|sf_owned Pin storage mode
login API key (prompt or env var) or OAuth flow
logout Remove stored credentials for a provider

Raw --api-key on the command line is not supported; use a prompt or --api-key-env.

See Providers.

Internal: sf internal run-stage

Used by the runtime to execute a single stage in a worker process. Not intended for direct use.

Environment variables (selected)

Variable Purpose
SF_STORE Must be sqlite (default)
STAGEFLOW_MAX_CONCURRENT_RUNS Soft max parallel runs
STAGEFLOW_MAX_ACTIVE_STAGES_PER_RUN Stage concurrency per run
STAGEFLOW_MAX_ACTIVE_STAGE_PROCESSES Stage worker process cap (also in CI / headless)
STAGEFLOW_STAGE_EXECUTION Stage worker mode: process (default) or inprocess (mainly tests)
STAGEFLOW_MCP_STATELESS Disable MCP sessions (test/debug); same as --mcp-stateless
STAGEFLOW_ACTIVITY_TEXT_LIMIT Transcript text truncation
STAGEFLOW_CURSOR_EXTENSION Path to Cursor Pi extension
STAGEFLOW_OPERATOR_CWD Operator checkout root for skill resolution in CI
STAGEFLOW_OPERATOR_AGENT_DIR Pi agent directory for user/runner skills in CI

Full CI-related flags and env vars: CI / headless.

See also