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.
Common commands
Section titled “Common commands”flowctl initflowctl specsflowctl tasks --spec fn-1flowctl ready --spec fn-1flowctl show fn-1.2flowctl start fn-1.2flowctl start fn-1.2 --reclaim # resume your own in_progress task; since 5.5.0 a plain start on it refusesflowctl 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 stdinflowctl validate --alldone 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.
flowctl chart create --title "Multi-tenant billing" --outcome "..." --initial-map-file map.json --jsonflowctl chart show fn-140 --jsonflowctl chart list --jsonflowctl chart frontier fn-140 --jsonflowctl chart add-decision fn-140 --title "Research provider limits" --type research --jsonflowctl chart claim fn-140.D1 --jsonflowctl chart attach-asset fn-140.D3 --asset-file asset.json --jsonflowctl chart resolve fn-140.D1 --answer-file answer.md --sharpen-file sharpen.json --jsonflowctl chart resolve fn-140.D3 --answer-file answer.md --supersedes D2 --jsonflowctl chart out-of-scope fn-140.D4 --reason "Beyond outcome" --jsonflowctl chart release-claim fn-140.D1 --jsonflowctl chart release-claim fn-140.D1 --break-stale --reason "owner session lost" --jsonflowctl chart park-question fn-140 --body-file q.md --jsonflowctl chart remove-question fn-140 --question <key> --jsonflowctl chart wire-decision fn-140.D5 --blocked-by D1 --depends-on D2 --jsonflowctl chart briefing fn-140 --proposal-file proposal.json --jsonflowctl chart locate "https://linear.app/.../stored-url" --jsonflowctl chart link-spec fn-140 --briefing B1 --spec fn-141 --decisions fn-140.D1,fn-140.D2 --jsonflowctl chart abandon fn-140 --reason "deprioritized" --jsonflowctl chart reopen fn-140 --reason "new constraint" --json| Concern | Contract |
|---|---|
| Envelope | {success, schema_version:1, command, result|error} |
| Error classes | not_found, conflict, invalid_state, invalid_graph, stale_claim, validation, io |
| Size ceiling | chart.maxDecisions (default 12); override only via --force-size --reason after consent |
| Stale claims | chart.claimStaleAfter hours (default 24); --break-stale always audited |
| Locate | Local provenance ledger only - no network, no title matching |
| Tracker projection | When 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.
Spec commands
Section titled “Spec commands”flowctl spec create --title "Add OAuth" --branch fn-1-add-oauth --jsonflowctl 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-nothingflowctl task create --spec fn-1 --title "implement this spec" --require-empty-spec --json # mint only while the spec has zero tasksflowctl spec set-plan fn-1 --file spec.md --jsonflowctl spec set-branch fn-1 --branch fn-1-new-name --json # rename an existing spec's branchflowctl spec chain fn-2 --json # read-only: may this dependent spec start now, and on which parent's branchflowctl spec close fn-1 --json # close the spec and write every task's final status; the caller commits modified_pathsflowctl spec closed-in-range --base origin/main --json # read-only: which specs does this branch closeflowctl spec ready fn-1 --json # mark ready for execution (human-owned gate, 1.12.0+)flowctl spec unready fn-1 --json # back to draftflowctl 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.
flowctl brief # cold-session orientation, <=2k tokensflowctl brief --full # same sections, no truncationflowctl brief --json # machine form, per-section truncated flagsThe 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 briefing artifact
Section titled “PR briefing artifact”pr-cognitive-aid validates, stores, selects and renders the one object behind a make-pr body.
flowctl pr-cognitive-aid validate --file aid.json [--json] # read-onlyflowctl 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.jsonrenderemits 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 whoseoutcomeispass;failandunverifiedrender 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,writeand both--filereaders 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.
@namein authored prose cannot ping anyone. Issue and pull request numbers, URLs and commit SHAs remain links, sofixes #12still closes that issue on merge. - Several specs.
specIdslists 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 optionaloutcomeon a verification cell leave the schema version at 1; stored artifacts stay valid. writenever overwrites. It validates and creates one immutable generation under.flow/artifacts/<spec-id>/pr-cognitive-aid/.currentanswerscurrent,absent,stale,unsupportedorinvalid, and exposes no artifact unless it is current. A sparse artifact at an unchanged base and head is reused.- Errors arrive together.
validateandwritereport 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-inputwas removed with the HTML render lenses in 7.0. For a visual view of a diff, run/flow-next:visual.
Next unit selection
Section titled “Next unit selection”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.
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.
Unattended-run commands
Section titled “Unattended-run commands”These verbs serve /flow-next:flow --auto. They keep their pilot spelling; the /flow-next:pilot command itself was removed in 6.1.0.
flowctl pilot snapshot [--spec <id>] --json # 6.1.0: everything one hop needs, in one readflowctl pilot strikes record <spec-id> --stage <stage> --reason "<reason>" [--json]flowctl pilot strikes list [--json] # the two-strike ledger flow --auto writesflowctl pilot strikes clear <spec-id> [--json] # the human clear after a strike 2/2 verdictflowctl 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 --backlogflowctl ready --spec fn-1 --admit --in-flight fn-1.1 --cap 3 --json # 6.1.0: rolling admissionpilot 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.
Criteria commands
Section titled “Criteria commands”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.
flowctl criteria list [--json] # parse + validateflowctl 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.
Feature map
Section titled “Feature map”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.
flowctl features status [--repo <path>]... [--json]recommendationisseed(no map),maintain(the map is due), ornone.dueis 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 indexREADME.mdexcluded):file,state(proven,never-proven, ormalformed),last_proven({date, commit}ornull),measured_from(commit, ordatewhen the recorded commit is not in this clone, such as a squashed branch head),commits_since, andstale.commits_sincecounts default-branch commits (origin/HEAD, thenmainormaster, thenHEAD; reported asbase) after the proof that change at least one path outside.flow/that is not documentation. A row isstalewhen that count reachesfeatures.staleAfterCommits(default 50, reported asthreshold), 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) tocommits_sinceand reports the split ascommits_since_by_repo("."is the.flow/repo); the output gainsrepos. A path that is not a git repo root, is the.flow/repo itself, or has no default branch that resolves exits2naming it. Without--repothe output is unchanged.open_driftlists active knowledge entries taggedfeature-map-driftas{id, title, path}; it isnullwhen 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.
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 nothingflowctl judge --preset tier --task <task-id> --json # 6.1.0: tier inputs from the task; returns tier_line, spawn_model, implementerflowctl memory search "windows subprocess" --limit 15 --rerank --json # BM25, then one rerank request over the top 15Presets: 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.
Rendered artifacts
Section titled “Rendered artifacts”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.
# One-call state readsflowctl preflight [--spec <id>] --json # planning config plus independent gate probesflowctl setup-status --json # setup's read-only probes and recorded optional answers
# Artifacts rendered from the host's judgmentflowctl prospect write --skeletonflowctl prospect write --from-json prospect.json --jsonflowctl qa receipt --skeletonflowctl qa receipt --from-json qa.json [--receipt receipt.json] --jsonflowctl memory add --track <track> --category <category> --title "..." --body-file body.md --check-overlap --jsonflowctl memory audit-scan --jsonflowctl memory apply --plan audit.json --json
# Review plumbingflowctl review-prompt impl <task-id|branch> --axis correctness --base <ref> --out prompt.mdflowctl review-prompt plan <spec-id> --axis correctness --out prompt.mdflowctl review-prompt completion <spec-id> --axis correctness --base <ref> --out prompt.mdflowctl 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 readsflowctl glossary list [--json] --match "<text>"preflightreturns the config snapshot asvalueplus independentprobesfor config, strategy, glossary, decision entries, tracker, review backend, and memory. Each probe carriesstatusandvalue, with an error on failure; each consumer keeps its own fail-open or default behavior.setup-statustreats 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 writetakes the title, focus, grounding, ranked survivors, and rejected ideas, and derives the artifact id, date, counts, and rejection rate.qa receipttakesid,qa_outcome, findings, and R-ID coverage, plus optional mode andBLOCKED/NAreasons. It derives commit, branch, timestamps, counts, and prior-finding lineage.BLOCKEDorNAnever closes a prior finding nobody observed, and an invalid payload leaves the previous receipt in place.memory add --check-overlapreturns the overlapping entries without writing, so the host can fold a rediscovery into one with--update <id>.memory audit-scanreturns frontmatter, schema errors, recurrence counts, module existence and change evidence, and whether a hardened rule is present.memory applytakes the host’s plan (a list, or{"entries": [...]}); each entry namesidand may carryset,stamp,body,move,remove, andreplacement. 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-promptrenders the dispatch prompt a host review uses, from the same builders and re-review context as backend review.review-rounds record --output-filederives 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.--attachpublishes the journaled receipt in the same call; the matching reservation is still required.resume-terminalreturns{action, status, exit}for completion-review re-entry and rejects unknown persisted states.glossary list --matchreturns 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.
Tracker commands
Section titled “Tracker commands”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.
Resolve
Section titled “Resolve”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
Section titled “Wire verbs”Wire verbs are locator-addressed and return normalized data:
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.
Lifecycle verbs and facade
Section titled “Lifecycle verbs and facade”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 KEYflowctl tracker sync <spec-id> --op pull --event KEY \ --flow-file F --body-file F --comments-file comments.jsonflowctl tracker sync <spec-id> --op reconcile --event KEY \ --flow-file F --body-file F --source-body-file F \ --comments-file comments.jsonflowctl tracker sync <spec-id> --op pull|reconcile --event KEY --prepare # 6.1.0flowctl tracker sync <spec-id> --op comment --event KEY \ --body-file FThe 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.
Result envelope and exit codes
Section titled “Result envelope and exit codes”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.
| Exit | class | Meaning |
|---|---|---|
| 0 | success | Completed; inspect degraded and probe |
| 2 | invalid_input | CLI input or local contract is invalid |
| 3 | inactive | Tracker bridge is inactive |
| 4 | unresolved | Required tracker.resolved facts are absent or incomplete |
| 5 | auth | Credential is absent or rejected |
| 6 | rate_limited | Provider rate limit; retry only when retryable is true |
| 7 | transport | Provider request failed or returned an unusable response |
| 8 | not_found | Addressed provider object does not exist |
| 9 | capability | Requested operation is unsupported by the resolved capability set |
| 10 | conflict | Durable identity, status mapping, or transaction state conflicts |
| 11 | stale_id | Persisted provider identity is stale |
| 12 | external_action_required | Host must perform the described MCP continuation |
State layout
Section titled “State layout”.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.mdAgents should use flowctl for writes instead of hand-editing state JSON.
CLI responsibility
Section titled “CLI responsibility”| Responsibility | Why it lives in flowctl |
|---|---|
| ID allocation | Must be deterministic and merge-safe |
| State transitions | Must be machine-readable |
| Validation | Must run in CI and local shells |
| Receipts | Must survive chat context |
| Migration | Must 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 usage pattern
Section titled “Agent usage pattern”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.