Skip to content

CLI Reference

flowctl is the deterministic CLI for .flow/ state.

Most users should use /flow-next:* commands. Use flowctl when debugging state, scripting CI, building integrations, or inspecting what the skills wrote.

Terminal window
flowctl init
flowctl specs
flowctl tasks --spec fn-1
flowctl ready --spec fn-1
flowctl show fn-1.2
flowctl start fn-1.2
flowctl done fn-1.2 --summary-file summary.md --evidence-json evidence.json
# evidence.json: {"commits": ["<sha>"], "tests": ["<command>"], "prs": []}
flowctl validate --all

Deterministic store for optional pre-capture decision-map discovery. Skill page: Chart. Slash map: Commands.

Terminal window
flowctl chart create --title "Multi-tenant billing" --outcome "..." --initial-map-file map.json --json
flowctl chart show fn-140 --json
flowctl chart list --json
flowctl chart frontier fn-140 --json
flowctl chart add-decision fn-140 --title "Research provider limits" --type research --json
flowctl chart claim fn-140.D1 --json
flowctl chart attach-asset fn-140.D3 --asset-file asset.json --json
flowctl chart resolve fn-140.D1 --answer-file answer.md --sharpen-file sharpen.json --json
flowctl chart resolve fn-140.D3 --answer-file answer.md --supersedes D2 --json
flowctl chart out-of-scope fn-140.D4 --reason "Beyond outcome" --json
flowctl chart release-claim fn-140.D1 --json
flowctl chart release-claim fn-140.D1 --break-stale --reason "owner session lost" --json
flowctl chart park-question fn-140 --body-file q.md --json
flowctl chart remove-question fn-140 --question <key> --json
flowctl chart wire-decision fn-140.D5 --blocked-by D1 --depends-on D2 --json
flowctl chart briefing fn-140 --proposal-file proposal.json --json
flowctl chart locate "https://linear.app/.../stored-url" --json
flowctl chart link-spec fn-140 --briefing B1 --spec fn-141 --decisions fn-140.D1,fn-140.D2 --json
flowctl chart abandon fn-140 --reason "deprioritized" --json
flowctl chart reopen fn-140 --reason "new constraint" --json
ConcernContract
Envelope{success, schema_version:1, command, result|error}
Error classesnot_found, conflict, invalid_state, invalid_graph, stale_claim, validation, io
Size ceilingchart.maxDecisions (default 12); override only via --force-size --reason after consent
Stale claimschart.claimStaleAfter hours (default 24); --break-stale always audited
LocateLocal provenance ledger only - no network, no title matching
Tracker projectionWhen bridge active and tracker.charts=on; local commit first; remote failure never rolls back

Decision types: research|probe|eval|prototype|interview|task. Attendance derived for the first five; task requires --attendance attended|unattended.

Terminal window
flowctl spec create --title "Add OAuth" --branch fn-1-add-oauth --json
flowctl spec create --title "Add OAuth" --plan-file plan.md --json # one-shot create+set-plan (--plan - reads stdin)
flowctl task create --spec fn-1 --from-json tasks.json --json # bulk tasks: one call, one lock, all-or-nothing
flowctl spec set-plan fn-1 --file spec.md --json
flowctl spec set-branch fn-1 --branch fn-1-new-name --json # rename an existing spec's branch
flowctl spec close fn-1 --json
flowctl spec ready fn-1 --json # mark ready for execution (human-owned gate, 1.12.0+)
flowctl spec unready fn-1 --json # back to draft

A cold session orients with flowctl brief — session-scope sibling of the task-scope anchor bundle. One pure-read call renders open specs (one-line goals), actionable tasks with claim state, the last five completions with evidence flags, the memory index, and go-deeper pointers, deterministically capped at ~2k tokens with explicit truncation markers (--full lifts the cap, --json is the machine form with identical retained items). No git state, no writes: brief is .flow/-only, so identical state always renders identical bytes.

Terminal window
flowctl brief # cold-session orientation, <=2k tokens
flowctl brief --full # same sections, no truncation
flowctl brief --json # machine form, per-section truncated flags

The one-shot and bulk forms are the ceremony fast path: a spec with a plan plus its full task set lands in two calls instead of ~eight, with the same validation, atomicity, and receipts as the granular verbs — --from-json takes a non-empty array of {title, description?, acceptance?, satisfies?, deps?, priority?} where deps entries are task ids or 1-based indexes of earlier entries, rejects the whole batch on any invalid item with zero writes, and returns the created ids in input order. Granular verbs are unchanged and remain the editing path.

spec ready / spec unready toggle the spec’s readiness flag — the human-owned “complete enough to hand to an agent” gate. Both are idempotent (no write, no updated_at bump when the flag already matches; --json reports "changed"). The flag is lazy on disk (written only after a toggle; absent reads false) but every JSON read surface (show, specs, list) emits an explicit "ready": <bool>, and ready specs carry a [ready] badge in listings. Orthogonal to status — a ready spec stays open through planning and work. With tracker.readyState configured the tracker is authoritative and overwrites local toggles on sync — see Tracker Sync.

Thin plumbing for the project’s standing criteria in the user-owned .flow/criteria.md (G-ID grammar: one line-anchored - **G<N>:** <criterion prose> bullet per criterion). Parse and validate only, since judging compliance belongs to spec completion review.

Terminal window
flowctl criteria list [--json] # parse + validate
flowctl criteria prompt-block # print the completion-review injection block
{"success": true, "criteria": [{"id": "G1", "text": "Every route change regenerates the API contract."}], "count": 1, "path": "/abs/path/to/repo/.flow/criteria.md"}

An absent or empty file returns an empty list (and empty prompt-block output) at exit 0 - absence costs nothing anywhere. An existing file that is unreadable or invalid fails closed: nonzero exit with the validation errors on stderr and empty stdout, so a careless append cannot inject error text into a prompt file. Ids must be unique, gaps are allowed, and at most 100 active criteria are accepted. path is absolute, and null when the file is absent.

The RepoPrompt and host completion-review workflows run criteria prompt-block before reserving a review round, which turns a broken criteria file into a fast validation error instead of a spent round.

flowctl tracker is the deterministic provider boundary for Linear, GitHub, GitLab, and Jira. Skills supply semantic input files and recovery judgment. The CLI owns credentials, provider requests, pagination, retries, normalization, capability checks, mutations, and transaction boundaries.

Terminal window
flowctl tracker resolve \
[--scope destination|destination.statusIds|destination.stateIds|capabilities] \
[--select KEY=VALUE] [--json]

resolve populates or refreshes tracker.resolved. --select persists an explicit choice when discovery finds multiple candidates. Consuming verbs fail unresolved rather than guessing when required facts are absent.

Wire verbs are locator-addressed and return normalized data:

Terminal window
flowctl tracker wire read --locator "$LOCATOR" [--json]
flowctl tracker wire update --locator "$LOCATOR" [--title TEXT] [--body-file F] [--json]
flowctl tracker wire comment-add --locator "$LOCATOR" --body-file F [--json]
flowctl tracker wire comment-list --locator "$LOCATOR" [--json]
flowctl tracker wire comment-update --locator "$LOCATOR" <comment-id> --body-file F [--json]
flowctl tracker wire comment-delete --locator "$LOCATOR" <comment-id> [--json]
flowctl tracker wire label --locator "$LOCATOR" [--add LABEL]... [--remove LABEL]... [--json]
flowctl tracker wire assign --locator "$LOCATOR" [--add USER]... [--remove USER]... [--json]
flowctl tracker wire list-open [--json]
flowctl tracker wire list-states [--json]
flowctl tracker wire relation-list --locator "$LOCATOR" [--json]
flowctl tracker wire question --locator "$LOCATOR" \
--subject-id ID --blocked-stage STAGE --reason-code CODE \
--question-slug SLUG --body-file F [--json]
flowctl tracker wire attach --locator "$LOCATOR" --file PATH [--json]
flowctl tracker wire attach-get <attachment-id> --out PATH [--json]

list-states (4.1.0) enumerates the destination’s workflow states read-only - Linear workflow states or Jira project statuses as {"states": [{"id", "name", "type"}], "complete": bool}. The complete flag distinguishes a provably full listing from a truncated one, so a caller can refuse instead of trusting a partial page; GitHub and GitLab have no workflow-state pool and return a typed capability error. It never writes config - detection stays separate from tracker resolve, which repairs the mapping and writes. relation-list returns normalized directed dependency rows for backlog ordering and fails closed when bounded pagination cannot prove the graph complete. question computes its marker id from the four stable identity inputs, reads comments before writing, and skips an existing id; free prose travels only through the body file and does not affect deduplication.

Wire verbs are useful for integrations and diagnostics. Lifecycle callers use the facade below because granular verbs do not provide create-if-unlinked, semantic marker deduplication, or aggregate receipts.

Terminal window
flowctl tracker create <spec-id> --title TEXT --body-file F [--event KEY] [--json]
flowctl tracker create-first --title TEXT --body-file F --retry-key <16-hex> [--json]
flowctl tracker persist-external <spec-id> --identifier KEY-N \
[--id DURABLE-ID] [--url URL] --source mcp [--event KEY] [--json]
flowctl tracker status <spec-id> \
--to backlog|todo|in_progress|in_review|done|cancelled \
[--reason completed|not_planned|duplicate|reopened] [--event KEY] [--json]
flowctl tracker relate <spec-id> --blocked-by <dependency-spec-id> [--event KEY] [--json]
flowctl tracker sync-body <spec-id> --flow-file F \
[--tracker-body-file F] [--direction push|pull] [--event KEY] [--json]
flowctl tracker sync <spec-id> --op push --event KEY \
--flow-file F --body-file F
flowctl tracker sync <spec-id> --op pull --event KEY \
--flow-file F --body-file F --comments-file comments.json
flowctl tracker sync <spec-id> --op reconcile --event KEY \
--flow-file F --body-file F --source-body-file F \
--comments-file comments.json
flowctl tracker sync <spec-id> --op comment --event KEY \
--body-file F

The facade owns create-if-unlinked, lifecycle ordering, marker deduplication, status, readiness and dependency projection, transaction boundaries, and exactly one aggregate receipt. Pull and reconcile receive the already adjudicated final Flow form as an input file; flowctl never authors the semantic merge.

Success:

{"success":true,"data":{},"degraded":null,"probe":null}

Failure:

{
"success": false,
"class": "conflict",
"error": "redacted human-readable summary",
"retryable": false,
"details": {"normalized": "todo", "candidates": []}
}

data is verb-specific. degraded records a confirmed capability transition. probe records a capability probe outcome without claiming a transition. Failure details is typed: rate limits include retry_after_s, capability failures include capability and required_plan, conflicts include normalized and candidates, and MCP continuations include action and payload. Branch on class, never provider error text.

ExitclassMeaning
0successCompleted; inspect degraded and probe
2invalid_inputCLI input or local contract is invalid
3inactiveTracker bridge is inactive
4unresolvedRequired tracker.resolved facts are absent or incomplete
5authCredential is absent or rejected
6rate_limitedProvider rate limit; retry only when retryable is true
7transportProvider request failed or returned an unusable response
8not_foundAddressed provider object does not exist
9capabilityRequested operation is unsupported by the resolved capability set
10conflictDurable identity, status mapping, or transaction state conflicts
11stale_idPersisted provider identity is stale
12external_action_requiredHost must perform the described MCP continuation
.flow/
├── meta.json
├── config.json
├── specs/
├── charts/ # optional chart discovery maps + decision records
├── tasks/
├── memory/
├── bin/
└── usage.md

Agents should use flowctl for writes instead of hand-editing state JSON.

ResponsibilityWhy it lives in flowctl
ID allocationMust be deterministic and merge-safe
State transitionsMust be machine-readable
ValidationMust run in CI and local shells
ReceiptsMust survive chat context
MigrationMust preserve repo-local state

Judgment stays in the host agent. flowctl should not decide whether a spec is good, whether an architecture is right, or whether a finding is product-important.

Agent-facing commands generally follow this sequence:

Terminal window
flowctl ready --spec fn-1 --json
flowctl start fn-1.2 --json
flowctl done fn-1.2 --summary-file summary.md --evidence-json evidence.json
# evidence.json: {"commits": ["<sha>"], "tests": ["<command>"], "prs": []}
flowctl validate --all

Direct CLI usage is useful for automation, but human docs should keep teaching the slash-command workflow first.