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 start fn-1.2 --reclaim # resume your own in_progress task; since 5.5.0 a plain start on it refuses
flowctl done fn-1.2 --summary-file summary.md --evidence-json evidence.json
# evidence.json: {"commits": ["<sha>"], "tests": ["<command>"], "prs": []}
flowctl done fn-1.2 --range BASE..HEAD --test "python3 -m unittest" --summary-file - --json # 6.1.0: commits from a contiguous range, summary on stdin
flowctl validate --all

done checks its evidence (6.1.0). Evidence must carry at least one of commits, tests, or prs; other keys besides base_commit, files, and files_touched print a warning and are not rendered. --range BASE..HEAD derives commits and base_commit from the range and fails naming any unreachable SHA; repeat --test for several commands. Interleaved task histories keep explicit evidence lists. With planSync.enabled not true, done appends the stage: plan-sync - skipped(config: planSync.enabled != true) line to the summary itself.

show <spec> --json is smaller (6.1.0). It omits the review-attempt and tracker ledgers. Read them with flowctl review-rounds attempts <spec> --kind plan|impl --review-type plan|impl|completion and flowctl sync get-state <spec>.

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

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 spec skeleton [--json] # print the scaffold spec create writes: templates/spec.md via SPEC.md -> spec.md -> bundled (frontmatter stripped)
flowctl task create --spec fn-1 --from-json tasks.json --json # bulk tasks: one call, one lock, all-or-nothing
flowctl task create --spec fn-1 --title "implement this spec" --require-empty-spec --json # mint only while the spec has zero tasks
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 chain fn-2 --json # read-only: may this dependent spec start now, and on which parent's branch
flowctl spec close fn-1 --json # close the spec and write every task's final status; the caller commits modified_paths
flowctl spec closed-in-range --base origin/main --json # read-only: which specs does this branch close
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
flowctl spec set-no-plan fn-1 --json # record direct execution consent on a zero-task spec (refused once tasks exist)
flowctl spec clear-no-plan fn-1 --json # clear the field (always allowed)

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 ceremony fast path. A spec with a plan plus its full task set lands in two calls instead of about eight, with the same validation, atomicity, and receipts as the granular verbs. --from-json takes a non-empty array of {title, description?, acceptance?, touches?, satisfies?, deps?, priority?}, where deps entries are task ids or 1-based indexes of earlier entries. Since 6.1.0 description_file and acceptance_file may replace the inline strings (paths resolve relative to the JSON file, or the current directory for stdin), and touches is a single line rendered as the task’s Touches line. It rejects the whole batch on any invalid item with zero writes, names every invalid item and the allowed keys in one error, and returns the created ids in input order. The granular verbs remain the editing path. --require-empty-spec refuses, naming the existing task, when the spec already has one; the check runs under the same per-spec lock that allocates ids, so exactly one of N concurrent creates wins. That is the atomic mint behind the no-plan route.

spec ready / spec unready toggle the human-owned readiness flag. Both are idempotent (no write and no updated_at bump when the flag already matches; --json reports "changed"). The flag is lazy on disk, so an absent value reads false, but every JSON read surface emits an explicit "ready": <bool> and ready specs carry a [ready] badge in listings. It is orthogonal to status. With tracker.readyState configured the tracker is authoritative and overwrites local toggles on sync (Tracker sync).

spec set-no-plan / spec clear-no-plan toggle the sibling human-owned no_plan boolean, the durable “skip decomposition” consent behind the no-plan route. Same lazy contract as ready: idempotent toggles (--json reports "changed"), absent reads false, and every JSON read surface emits an explicit "no_plan": <bool> (noPlan on ready --all rows). Setting is refused once the spec has any tasks, because a planned spec routes through its tasks; clearing is always allowed (stale-field cleanup). The field is flow-local and never tracker-projected. Two consumers read it: flow --auto classifies a marked zero-task spec straight to the work dispatch, and on a ready zero-task spec with no recorded route it applies the plan-versus-no-plan rule and records the result with these two verbs before any mint; work honors the field as the explicit no-plan instruction.

spec chain is the read-only chain-eligibility predicate for a dependent spec and the one owner of the rule every consumer applies: flow --auto selection in ready and backlog mode, attended flow’s next-item pick, /flow-next:work at branch creation, and flowctl’s own task-admission gate (ready --spec, next, ready --all). It returns {spec, eligible, parent, parent_branch, parent_branch_on_remote, reason}. eligible is true when every dependency is landed (parent is null, reason is no open dependency, no remote read), or when exactly one dependency is unlanded with all of its tasks done, every other dependency is landed, that parent’s branch exists on origin, and no other unlanded spec is already chained on the same parent. A spec now closes on its pull request branch before the merge, so done alone does not mean landed. A locally closed dependency counts as landed when the default base records it as closed. Failing that, flowctl reads the dependency’s spec at the merge-base of its branch and HEAD: a close recorded there means this branch is stacked on unmerged work, and the dependency stays chained. With no close in the shared history it counts as landed, which covers a dependency squash-merged into a non-default integration branch, a deleted branch, and a spec with no recorded branch. A true merge onto a non-default base conservatively stays unlanded. These reads are local and never fetch. Otherwise reason is one of dependency <id> in progress, two open parents: <id>, <id>; chains are linear, parent branch <b> not on origin; push it or land the parent first, dependency <id> closed locally but not recorded at <base>; dependency branch <ref> is in this branch's history; fetch the base or land it, parent <id> already chained by <sibling-id>, base query failed: <error>, or remote query failed: <first stderr line> (parent_branch_on_remote: null; a failed query is never reported as an absent branch). At most one git ls-remote --heads origin per invocation, none when the spec has no dependencies, and never gh. Exit 0 on any evaluation; exit 2 when the spec or a named dependency does not exist. The task-admission gate treats the chain parent as satisfied and still empties the frontier on every other not-done dependency; task-level depends_on is unchanged. Consumers: work, make-pr, land.

spec close closes the spec and writes each task’s final status: done into its tracked JSON file, so a fresh clone reads the finished state. It refuses, before any file changes, when a task is incomplete. It writes files and commits nothing: --json returns modified_paths, naming the spec file and every task file it rewrote, and the caller commits exactly those. make-pr does this as the last commit before the pull request opens. Creating or starting a follow-up task reopens a closed spec and reports the spec file the same way.

spec closed-in-range --base <ref> lists the specs a branch closes: done at HEAD, absent or not done at the merge base with <ref>, and with task files the range touches. A close that changes only the spec record is excluded, and so is a sibling whose close belongs to the history of its own still-existing branch. Text output prints one id per line; --json returns {"spec_ids": [...]} in the same order. It reads local committed objects only and never writes or fetches. make-pr uses it to write one body for a branch that closes several specs, hosting on the last id when no spec names the branch.

pr-cognitive-aid validates, stores, selects and renders the one object behind a make-pr body.

Terminal window
flowctl pr-cognitive-aid validate --file aid.json [--json] # read-only
flowctl pr-cognitive-aid write <spec-id> --file aid.json --base-sha <sha> --head-sha <sha> [--json]
flowctl pr-cognitive-aid current <spec-id> --base-sha <sha> --head-sha <sha> [--json]
flowctl pr-cognitive-aid render <spec-id> --base-sha <sha> --head-sha <sha>
flowctl pr-cognitive-aid render --file aid.json
  • render emits one briefing for every diff size. The sections are Why, What changes for a user or operator, Scope, Blast radius, Verification, Tradeoffs and Open items, and an empty section disappears. There is no compact or full form, no size threshold and no body line budget. Each review step shows at most ten described files as linked rows; further rows are counted. Verification ticks only a cell whose outcome is pass; fail and unverified render unticked with their note, and a cell with no outcome is a plain item. Artifact id, base and head ride in one HTML comment.
  • Input may be sparse. validate, write and both --file readers accept an object that lists only the must-read files and omits each file’s change type, line counts and diff link. flowctl fills those from the diff, appends the remaining files as a rest-of-diff set, and stores the complete artifact.
  • Mentions are made inert, numbers stay live. @name in authored prose cannot ping anyone. Issue and pull request numbers, URLs and commit SHAs remain links, so fixes #12 still closes that issue on merge.
  • Several specs. specIds lists every spec the body covers, requirement ids are qualified (fn-250:R4), and coverage prints one line per spec.
  • The additions are additive. Four optional authored strings (userImpact, blastRadius, tradeoffs, openItems) and the optional outcome on a verification cell leave the schema version at 1; stored artifacts stay valid.
  • write never overwrites. It validates and creates one immutable generation under .flow/artifacts/<spec-id>/pr-cognitive-aid/. current answers current, absent, stale, unsupported or invalid, and exposes no artifact unless it is current. A sparse artifact at an unchanged base and head is reused.
  • Errors arrive together. validate and write report every independent violation with its field path in stable order and exit 2. A summary that is only whitespace is rejected by field name.
  • html-input was removed with the HTML render lenses in 7.0. For a visual view of a diff, run /flow-next:visual.

Deterministic “what should run now” for scripted drivers. Attended flow reads it as one hint when it picks the next open spec. flow --auto classifies each hop from the flow skill’s routing reference and the spec’s own status fields instead of this verb. Interactive sessions rarely need it; it is the right probe when scripting a loop or debugging why a driver picked (or skipped) a spec.

Terminal window
flowctl next [--specs-file specs.json] [--require-plan-review] [--require-completion-review] [--json]
{"status": "plan|work|completion_review|none", "spec": "fn-12", "task": "fn-12.3", "reason": "needs_plan_review|needs_tasks|needs_completion_review|resume_in_progress|ready_task|none|blocked_by_spec_deps", "blocked_specs": {"fn-12": ["fn-3"]}}

Selection walks specs in id order and stops at the first actionable unit. A zero-task spec with explicit no_plan: true reports status: work with its spec id and no task id; an explicit --require-plan-review gate still reports needs_plan_review until satisfied. An unmarked zero-task spec reports status: plan with reason: needs_tasks. Direct work creates the single full-spec execution owner; the selector does not mint one. Existing planned tasks retain their normal selection and review gates.

--require-plan-review holds work until the spec’s plan review reaches ship. --require-completion-review gates spec closure. With all tasks done but completion_review_status outside {ship, not_required}, it returns status: completion_review. An unrecognized or absent value satisfies nothing.

These verbs serve /flow-next:flow --auto. They keep their pilot spelling; the /flow-next:pilot command itself was removed in 6.1.0.

Terminal window
flowctl pilot snapshot [--spec <id>] --json # 6.1.0: everything one hop needs, in one read
flowctl pilot strikes record <spec-id> --stage <stage> --reason "<reason>" [--json]
flowctl pilot strikes list [--json] # the two-strike ledger flow --auto writes
flowctl pilot strikes clear <spec-id> [--json] # the human clear after a strike 2/2 verdict
flowctl pilot strikes clear --all [--json]
flowctl pilot-log append --id <id> --action <triaged|advanced|asked|blocked|needs-human> [--stage <stage|->] [--cost-tokens <n>] [--reason "<one line>"] [--json]
flowctl ready --all [--json] # backlog-wide eligibility scan for flow --auto --backlog
flowctl ready --spec fn-1 --admit --in-flight fn-1.1 --cap 3 --json # 6.1.0: rolling admission

pilot snapshot (6.1.0) bundles the run guards, config, candidates with their chain, claim, and strike facts, the selected spec, the review backend, lifecycle and PR observation, branch existence, QA freshness, and the task list before dispatch. One PR listing joins branches to their open, merged, and closed pull requests. A failed probe stays explicit, and the caller ends the hop instead of reconstructing the state.

pilot strikes record (6.1.0) increments one cumulative counter per spec under the ledger lock; at count 2 it unreadies the spec. --json returns count and unreadied, and an unknown spec fails.

ready --admit (6.1.0) adds admitted and held lists to the ready read, with mechanical reasons: dependency closure, missing or overlapping Touches (touches-missing when a task has no Touches line), always-serial surfaces, and the --cap capacity. JSON task rows carry parsed touches and transitive dependency facts. The owner can still hold an admitted task for coupling the Touches lines do not show.

pilot strikes reads and clears the ledger at <git-common-dir>/flow-next/pilot-strikes.json, which flow --auto writes when a hop advances nothing. The ledger lives under the git common dir, so worktrees share it and git add -A can never commit it. list is empty-safe (a missing ledger renders empty at exit 0). clear <spec-id> removes exactly one entry atomically; an unknown id is a not-found exit 3 naming the known entries. Clearing never touches spec readiness in either direction. A strike 2/2 verdict unreadies the spec and names this command in its reason string, because on a repo with tracker.readyState armed the board echo re-grants readiness and cannot clear the strike.

pilot-log appends one decision-log row per dispatched stage under .flow/pilot-runs/ when flow --auto --backlog reaches a terminal (auto-gitignored, and not a receipts/ path). --action is the frozen enum above; a live run logs only terminal actions and triaged is diagnostic-only. --cost-tokens is host-reported; flowctl stores the row and never measures cost. --reason "<one line>" stores the host’s verdict reason verbatim as reason on that row, so a chained dispatch’s row begins chained on <parent-id>; ; rows written without the flag keep the frozen {tick, id, action, stage, costTokens} shape.

ready --all is the deterministic substrate for flow --auto --backlog (or pilot.autonomy=backlog). For every open spec it returns ready, noPlan, readySignal, blockedBy (unsatisfied dependency spec ids; a chain parent per spec chain is not listed), and hasSpec, and never a judgment field. Whether an item is workable, thin, or needs a spec is the skill’s agentic read in the triage stage.

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.

Read-only facts about the committed feature map at .flow/features/, so setup, prime, and flow share one recommendation for /flow-next:features. flowctl never validates the four-section shape (the skill does) and never edits the map.

Terminal window
flowctl features status [--repo <path>]... [--json]
  • recommendation is seed (no map), maintain (the map is due), or none. due is true when a map exists and at least one open drift note or stale feature file exists; reasons[] names each.
  • features[] has one row per feature file (the index README.md excluded): file, state (proven, never-proven, or malformed), last_proven ({date, commit} or null), measured_from (commit, or date when the recorded commit is not in this clone, such as a squashed branch head), commits_since, and stale.
  • commits_since counts default-branch commits (origin/HEAD, then main or master, then HEAD; reported as base) after the proof that change at least one path outside .flow/ that is not documentation. A row is stale when that count reaches features.staleAfterCommits (default 50, reported as threshold), and always when it is never proven or malformed.
  • --repo <path> (repeatable) names a sibling repo that holds product code in a home-base workspace, relative to the repo that holds .flow/. Each proven row then adds that repo’s product commits since the proof date (measured from its own default branch) to commits_since and reports the split as commits_since_by_repo ("." is the .flow/ repo); the output gains repos. A path that is not a git repo root, is the .flow/ repo itself, or has no default branch that resolves exits 2 naming it. Without --repo the output is unchanged.
  • open_drift lists active knowledge entries tagged feature-map-drift as {id, title, path}; it is null when memory is disabled or uninitialised, so only the age condition applies.

Exit 0 (or 2 for a bad --repo). A repository without .flow/ has no map and reads as seed. The command never dispatches /flow-next:features. How the three signals fit together: Keep the feature map current.

flowctl judge classifies one supplied state with a bundled TypeSafe Jev preset and applies that preset’s fixed decision rule, so every host computes the same decision from the same answers. The route preset is the exception: it is code only and never sends a request, with or without a key, so routing never asks Jev. It is the plumbing behind Optional Jev judgments; the skills call it, and you can call it to inspect a decision.

Terminal window
flowctl judge --preset <name> --state-file state.json [--json]
flowctl judge --preset route --spec <spec-id> --json # the live spec's lifecycle, decided in code; sends nothing
flowctl judge --preset tier --task <task-id> --json # 6.1.0: tier inputs from the task; returns tier_line, spawn_model, implementer
flowctl memory search "windows subprocess" --limit 15 --rerank --json # BM25, then one rerank request over the top 15

Presets: route, fork-gate, memory-rerank, tier. The retired qa-gate preset is an unknown-preset error: the QA gate never asks Jev. The command reads TYPESAFE_API_KEY from the environment at call time; judge.enabled=false disables it. Requests go to jev-latest with a 10-second timeout and two retries only for HTTP 429 and 529 (after 1 s and 2 s); a request estimated over 32k tokens is rejected without sending. The command never writes state, answers, or credentials to disk.

An available result carries success, available, preset, the returned model, typed answers, decision (value, rule, met), latency_ms, and usage. Tier decisions add the three leading candidates as [option, probability] pairs; memory decisions carry every judged entry id and score, reordered, none dropped. --preset route --spec <spec-id> always reports available: false, reason: routing_is_code, and its decision carries value, rule, met, pr_ref, and startable_target_fact for the tail and QA gates to reuse. It takes --spec only: intake routing is the host’s, so a route --state-file is a command error.

An unavailable result exits 0 and names the reason so the caller takes its existing fallback. Presets other than route omit the decision:

{"success": true, "available": false, "preset": "fork-gate", "reason": "no_key"}

Reasons: no_key, disabled, http_<status>, transport, timeout, bad_answer, over_budget. A failed PR probe adds pr_probe_failed: true so the caller keeps its failure outcome. Unknown presets, unreadable or non-JSON state files, and missing required state fields exit non-zero; one error names every missing field.

memory search --rerank sends up to 15 BM25 hits in one memory-rerank request and reorders them by Jev score (ties retain BM25 order) without dropping any; --limit applies after the reorder. The judge receives each entry’s id, title, track, category, module, tags and snippet, never its path or BM25 score. JSON adds jev_score and jev_rank to reranked matches and top-level rerank: "jev"; an unavailable judge keeps BM25 order and returns rerank: "bm25", and zero hits send no request.

Since 6.1.0 a skill hands flowctl its judgment and flowctl renders the artifact around it. flowctl derives ids, dates, counts, and commit and branch facts, validates every field, and writes atomically. An invalid payload reports every error together and writes nothing. --skeleton prints the exact authoring shape.

Terminal window
# One-call state reads
flowctl preflight [--spec <id>] --json # planning config plus independent gate probes
flowctl setup-status --json # setup's read-only probes and recorded optional answers
# Artifacts rendered from the host's judgment
flowctl prospect write --skeleton
flowctl prospect write --from-json prospect.json --json
flowctl qa receipt --skeleton
flowctl qa receipt --from-json qa.json [--receipt receipt.json] --json
flowctl memory add --track <track> --category <category> --title "..." --body-file body.md --check-overlap --json
flowctl memory audit-scan --json
flowctl memory apply --plan audit.json --json
# Review plumbing
flowctl review-prompt impl <task-id|branch> --axis correctness --base <ref> --out prompt.md
flowctl review-prompt plan <spec-id> --axis correctness --out prompt.md
flowctl review-prompt completion <spec-id> --axis correctness --base <ref> --out prompt.md
flowctl review-rounds record <spec-id> --kind plan|impl --review-type plan|impl|completion \
--output-file review.md --reservation-id <id> --attach [--task <task-id>] [--json]
flowctl review-rounds resume-terminal <spec-id> --review-type completion --json
# Scoped reads
flowctl glossary list [--json] --match "<text>"
  • preflight returns the config snapshot as value plus independent probes for config, strategy, glossary, decision entries, tracker, review backend, and memory. Each probe carries status and value, with an error on failure; each consumer keeps its own fail-open or default behavior.
  • setup-status treats missing or unreadable setup state as a first run. Setup records the optional questions you declined, so a steady re-run asks nothing again.
  • prospect write takes the title, focus, grounding, ranked survivors, and rejected ideas, and derives the artifact id, date, counts, and rejection rate.
  • qa receipt takes id, qa_outcome, findings, and R-ID coverage, plus optional mode and BLOCKED/NA reasons. It derives commit, branch, timestamps, counts, and prior-finding lineage. BLOCKED or NA never closes a prior finding nobody observed, and an invalid payload leaves the previous receipt in place.
  • memory add --check-overlap returns the overlapping entries without writing, so the host can fold a rediscovery into one with --update <id>.
  • memory audit-scan returns frontmatter, schema errors, recurrence counts, module existence and change evidence, and whether a hardened rule is present. memory apply takes the host’s plan (a list, or {"entries": [...]}); each entry names id and may carry set, stamp, body, move, remove, and replacement. Entries apply atomically one by one, moves and replacements update references, unknown ids are reported while the rest proceed, and a decision record can only be superseded.
  • review-prompt renders the dispatch prompt a host review uses, from the same builders and re-review context as backend review.
  • review-rounds record --output-file derives the verdict, review text, suppressed and classification counts, and unaddressed R-IDs from the review output; structured findings win over prose. A receipt payload that contradicts the output exits 2 before any state changes. --attach publishes the journaled receipt in the same call; the matching reservation is still required. resume-terminal returns {action, status, exit} for completion-review re-entry and rejects unknown persisted states.
  • glossary list --match returns only the entries whose term or avoid-alias occurs in the text (whole word, case-insensitive). The worker’s anchor bundle passes it the task’s title and description.

Review commands also need fewer arguments. Codex plan review runs without --files, an omitted --base resolves to the default branch (origin/HEAD, then main or master), and plan and completion receipts default to <repo>/.flow/tmp/. Fan-out saves its round identity before dispatch, so an interrupted round recovers the draws that finished.

A malformed .flow/config.json is reported, never reset. Readers name the file and give line and column for a JSON syntax error, config set refuses to overwrite it, and validate reports a root error. A missing file still means defaults.

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] [--comment-file F] [--overwrite-diverged]
flowctl tracker sync <spec-id> --op push --status-only --event KEY
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 pull|reconcile --event KEY --prepare # 6.1.0
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.

Since 6.1.0 push renders an omitted body from the spec, and an unchanged spec renders identical bytes; --status-only needs no body. A body-writing push on a linked spec whose tracker body changed since the last sync returns conflict with subtype tracker_diverged and writes nothing. --overwrite-diverged overwrites it and belongs only after a human confirms (Edits made on the tracker). --prepare on pull or reconcile returns the pre-reduction class, the tracker body with dependencies stripped, the base pair, and the genuine comments in one call, excluding marker comments and their hash matches. Its files point to private mode-0600 snapshots that the next facade call consumes; snapshots older than one hour are swept. The agent keeps the conflict and fold decisions.

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/
├── features/ # optional user-POV feature map
├── prospects/ # optional ranked idea lists
├── templates/ # optional spec template (spec.md)
├── criteria.md # optional standing criteria
├── artifacts/ # PR briefing source generations
├── review-receipts/ # review receipt copies
├── pilot-runs/ # gitignored backlog-mode decision log
├── 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 follow the sequence at the top of this page, with --json on the reads. Direct CLI usage is for automation and debugging; the docs teach the slash-command workflow first because that is the product surface.