Skip to main content

ratchet batch

Coordinate related changes across phases via a batch manifest. A batch is an ordered list of phases, each gating the next with a proof-of-work. Each phase declares a set of change intents forming a DAG via after edges. The manifest is stored at .ratchet/batches/<name>/batch.yaml and references changes by name. Batch status is derived live from change state on disk — the manifest stores intent only and never accumulates progress.

batch apply advances one DAG step by exactly one transition (proposeapplyverify) through the bundled engine. The autonomous loop lives in the apply-batch skill, not in the CLI.

Selection treats an awaiting-verify change as runnable work: when a change's tasks are all checked but no verify completion is journaled yet, batch apply selects it and runs its verify transition — delegating to the canonical /rct:verify <change> skill — as the gate that must pass before the change becomes done. Selection, status, and next-transition computation all honor the single journal-aware definition of done (see the batch status change-status table below), so an all-tasks-checked-but-unverified change is never treated as done and is never skipped.

Manifest structure

name: <batch-name> # kebab-case
created: YYYY-MM-DD # set by `batch new`
settings: # optional per-manifest overrides
gate: voluntary # voluntary | after-propose | every-phase | autonomous
strategy: vertical-slice # vertical-slice | feature
proofOfWork: hard-gate # hard-gate | warn
locus: local # local | docker | remote
agent: <agent>
image: <image> # docker locus only
host: <host> # remote locus only
port: <port> # remote locus only
authToken: <token> # remote locus only (redacted in output)
insecure: false # allow plaintext http to non-local remote host
permissions: # agent permission policy override
posture: repo-sandboxed-permissive
allow: []
deny: []
phases:
- name: <phase-name>
goal: <phase goal>
success: <success criteria>
proofOfWork:
kind: integration # integration | blackbox (llm-judge not yet supported)
run: <bash command>
pass: <pass condition>
changes:
- name: <change-name>
done: <definition of done for this change> # required
after: [] # names of changes this one depends on (DAG edges)

Every change.done field is required. A change intent whose directory does not yet exist under .ratchet/changes/ is pending — this is not an error. Changes are created lazily by the engine as the batch progresses.

batch new

Scaffold a new batch manifest from the template.

Synopsis

ratchet batch new <name> [--json]
ratchet new batch <name> [--json] # alias

Options

OptionDescription
--jsonOutput the created batch path as JSON.

Behavior

  1. Validates <name> as kebab-case; fails with an actionable error if invalid.
  2. Refuses to overwrite an existing batch at .ratchet/batches/<name>/batch.yaml.
  3. Creates .ratchet/batches/<name>/batch.yaml from the canonical batch template, stamping name and created (today's ISO date).

ratchet new batch <name> is an identical alias registered on the top-level new command.

batch status

Show batch status derived live from change state on disk.

Synopsis

ratchet batch status [name] [--json]

[name] defaults to the current active batch when omitted (see Name resolution).

Options

OptionDescription
--jsonOutput structured status as JSON.

Behavior

Reads the manifest, reads the run-state journal, and derives status for each change without consulting the batch manifest for progress (the manifest stores intent only).

Change statuses (the Symbol column is the glyph used in batch status and batch view text output):

StatusSymbolMeaning
pending·No change directory exists yet.
readyDependencies met; change not yet started.
in-progressChange directory exists; tasks partially complete.
awaiting-verifyAll tasks complete but no verify completion is journaled yet — the verify gate has not run, so the change is NOT done.
doneAll tasks complete and a verify completion is journaled for the change, or the change is archived.
blockedDependency unmet, OR an agent voluntarily parked the step with a blocker.
awaiting-approvalAgent completed propose and parked for approval (after-propose/every-phase gate).

"Done" has a single journal-aware definition shared by status derivation, step selection, and next-transition computation: a change is done only when its plan tasks are all checked AND the run journal carries a completion entry for the verify transition (or the change is archived). An all-tasks-checked change with no journaled verify is reported awaiting-verify — it is the batch's next actionable step, because verify is the transition that must run before it can be done.

Phase gating: phase N is gated (status blocked) until phase N−1 reports done for all its changes.

A multi-phase batch is done only once every reachable phase is decomposed AND all its changes are done. Phases are decomposed lazily: a later phase may start with an empty changes list (no concrete change intents yet). A ready, ungated phase with empty changes is an outstanding decomposition step, not terminal — the batch is NOT reported done while such a phase remains, even when every declared change is done. Status and step selection agree by construction: both treat a reachable empty phase as work (status reports in-progress and next points at the phase as a decomposition step; selection returns that phase rather than all-done). A still-gated empty phase is not surfaced yet — the unfinished prior-phase change is selected first.

Text output lists each phase and its changes with status symbols, task progress counters, after edges, and parked step details (blocker message or approval summary). The next actionable step is printed at the end — a reachable empty phase prints as Next: decompose phase <name>.

JSON output (--json): the root object includes name, status, progress ({ completed, total }), changeCount, doneCount, gate (resolved setting), next ({ phase, change } for a change step, or { phase, decompose: true } for a reachable empty phase, or null), and phases. Each phase includes name, goal, success, status, gated, gatedBy, and changes. Each change includes name, status, done, progress, after, blockedBy, exists, archived, blocked, awaitingApproval, awaitingVerify (true when tasks are all checked but no verify completion is journaled), and parked ({ kind, reason, answer?, feedback? } or null).

batch view

Rich terminal dashboard for a single batch.

Synopsis

ratchet batch view [name] [--json]

[name] defaults to the current active batch when omitted.

Options

OptionDescription
--jsonOutput the raw BatchStatusInfo object as JSON.

Behavior

Computes batch status identically to batch status and renders it as a formatted terminal dashboard with progress bars (filled/empty block characters), phase headings, per-change rows (symbol + name + progress bar + after edges + blocked-by), and parked step details. Honors --no-color (via NO_COLOR).

JSON output is the full BatchStatusInfo object — the same fields as batch status --json without the gate field annotation.

batch list

List all batches with change count and aggregate progress.

Synopsis

ratchet batch list [--json]

Options

OptionDescription
--jsonOutput structured list as JSON.

Behavior

Reads all directories under .ratchet/batches/ that contain a batch.yaml (the reserved archive/ directory is excluded). Computes aggregate status for each batch and renders a summary table.

Text output: batch name, inline progress bar, percent complete, and change count.

JSON output (--json): { batches: [ { name, changeCount, doneCount, progress, status } ] }.

batch config

Resolve, get, or set batch settings.

Synopsis

ratchet batch config [name] [--set <key=value>] [--json]

[name] defaults to the current active batch when omitted.

Options

OptionArgumentDescription
--set<key=value>Write the project-level batch: section key.
--jsonOutput resolved settings as JSON.

Behavior

Without --set: resolves and displays effective settings. Settings are resolved in this order (nearest wins): manifest overrides ← project config (.ratchet/config.yaml batch: section) ← user config ← built-in defaults. Each value is annotated with its source: [manifest], [project], [user], or [default]. authToken is always redacted in output (***).

With --set key=value: writes the project-level config (batch: section) only. Invalid enum values are rejected and the file is left unchanged. Secret values (e.g. authToken) are not echoed back.

The agent key is validated through the same shared AgentSettingSchema the loaders use (so the write path can never persist what the loader rejects): a scalar value is validated as an agent[:model] spec, and a value whose trimmed form starts with { is parsed as a YAML flow map and validated as a per-stage map (each entry checked). A malformed value (e.g. agent=claude:) is rejected with an error naming the offending value, and the file is left byte-for-byte unchanged. A valid inline map (e.g. agent={apply: opencode, verify: 'claude:fable'}) persists as a real YAML map the loader round-trips with no warning; a valid scalar persists as a plain string.

Settable keys:

KeyValuesDefault
gatevoluntary | after-propose | every-phase | autonomousvoluntary
strategyvertical-slice | featurevertical-slice
proofOfWorkhard-gate | warnhard-gate
locuslocal | docker | remotelocal
agentagent[:model] spec, or an inline {propose, apply, verify, pr} stage-map (validated through the shared schema; rejected without writing when malformed)(adapter default)
imagestringpython:3.12 (docker locus)
hoststring(required for remote)
portnumber(required for remote)
authTokenstring(required for remote)

The permissions policy is structured; use the manifest settings.permissions block to set it per-batch.

batch report

Report progress, raise a blocker, or request input on a step.

Synopsis

ratchet batch report [name] --change <name> <kind-flag> <message> [--json]

[name] defaults to the current active batch when omitted. --change is always required. Exactly one kind flag must be provided.

Options

OptionArgumentDescription
--change<name>Required. Change the report is about.
--status<message>Record routine progress (appends a progress journal entry).
--blocker<message>Raise a blocker and park the step as blocked.
--needs-input<message>Request input and park the step as blocked.
--complete<message>Signal that the step produced its output (appends a completion entry).
--awaiting-approvalCombined with --complete: parks the step as awaiting-approval (after-propose gate).
--answer<message>Record an answer to a parked blocker; the step remains parked until the next batch apply.
--reject<message>Reject an awaiting-approval step with feedback; next batch apply re-runs propose.
--jsonOutput the result as JSON ({ kind, change, text }).

Behavior

Exactly one of --status, --blocker, --needs-input, --complete, --answer, or --reject must be present; providing zero or more than one is an error.

Kind flagJournal entry appendedParked state written
--statusprogressnone
--blockerblockerblocked (reason = message)
--needs-inputneeds-inputblocked (reason = message)
--completecompletionnone (or awaiting-approval with --awaiting-approval)
--complete --awaiting-approvalcompletionawaiting-approval (reason = message)
--answeransweranswer stored on the existing blocked park; park stays until resume
--rejectrejectfeedback stored on the existing awaiting-approval park; next apply re-runs propose

A blocked step with a recorded answer (via --answer) resumes on the next batch apply. A rejected awaiting-approval step causes the next batch apply to re-run propose with the feedback in context.

Journal entries are appended to .ratchet/batches/<batch>/run/journal.jsonl; parked state is written to .ratchet/batches/<batch>/run/state.json.

batch apply

Advance the batch by one step via the bundled engine.

Synopsis

ratchet batch apply [name] [--json]

[name] defaults to the current active batch when omitted.

Options

OptionDescription
--jsonOutput the structured StepResult as JSON.

Behavior

batch apply is a single-step command: it selects the next ready step, runs exactly one transition, persists the result, and returns. It does not loop. The autonomous apply loop is the apply-batch skill.

Execution sequence:

  1. Select next step. Iterates phases in declaration order, skipping gated phases. Within each ungated phase, picks the first change with status ready, in-progress, or awaiting-verify (an all-tasks-checked change with no journaled verify is selected so its verify gate runs before done). If no change is runnable but a reachable, ungated phase has an empty changes list, that phase is selected as a decomposition step (see below). If neither exists, prints a "nothing ready" message and exits (without error).

    Proof-of-work boundary step. Before returning a runnable change in a phase Q, batch apply interposes the immediately-preceding phase P's proof-of-work as a boundary step. P is done (else Q would be gated), so when P has no recorded proof verdict yet, batch apply runs P's configured proofOfWork.run in the project root (with the resolved policy and P's success criteria), journals the verdict as a proof-of-work entry, and returns. The verdict is recorded once per boundary. The first phase has no predecessor, so no proof runs there. The recorded verdict then drives the gate: the next batch apply derives Q's gate from P's recorded gatePassed. A passing proof (or warn) advances into Q's change; a failing hard-gate proof keeps Q blocked, batch apply advances no Q change, and its "no ready step" output cites P's failing proof instead of the generic gated message. The block persists across separate stateless batch apply invocations. See engine: phase gates and proof-of-work.

    Terminal-phase proof. The last phase has no following phase Q to trigger its boundary proof. So once every change in the batch is done and nothing is left to decompose, batch apply surfaces and runs the terminal phase's proof-of-work the same way — and the batch is not done until that proof is recorded as satisfied (gatePassed: true). A failing terminal hard-gate proof keeps the batch in-progress with nothing auto-runnable: the no-step output cites the failing terminal proof and the operator must batch rerun-proof (or fix the cause). A single-phase batch therefore gates on its one phase's proof before reporting done. The predecessor's boundary proof also runs before a decomposition step (an undecomposed phase is entered off its predecessor's slice).

    Decomposition step. When the next runnable step is a reachable phase whose changes are still empty, batch apply spawns one agent that delegates to the canonical decompose-phase skill (/rct:decompose-phase <phase>, resolved per agent) to author that phase's concrete change intents into batch.yaml from the prior phases' shipped results — the engine orchestrates the spawn, the skill authors the intents (it creates no change directories). The agent reports under the phase name (ratchet batch report <batch> --change <phase> ...), and the resulting StepResult has transition: 'decompose'. The next batch apply then selects the phase's first ready change as an ordinary propose/apply/verify step, so a multi-phase batch with later empty phases is driven to completion with no manual stop/propose/resume detour.

  2. Park precheck. If the selected step is parked as blocked without a recorded answer, or as awaiting-approval without approval or feedback, the command prints a "did not advance" message with a hint and exits. The park must be resolved before the step can advance.

  3. Derive transition. The authoritative transition is computed from on-disk state via computeNextTransition; the status-derived hint in the context is only a coarse fallback.

  4. Acquire per-batch lock. A single-flight lock prevents concurrent applies for the same batch. An already-locked batch returns immediately.

  5. Spawn agent. The bundled RatchetBatchEngine spawns exactly one coding agent for the derived transition (propose, apply, or verify) — or, for a decomposition step, for the phase decomposition (decompose). The engine is in-process — no separate install or activation is required. The agent receives structured instructions including phase goal, success criteria, proof-of-work description, change definition of done (change steps) or the prior phases' shipped results (decomposition steps), and resume context (if any). Every spawn delegates to the canonical rct skill for that step rather than re-describing it inline.

  6. Map outcome. Session journal entries and the on-disk change-state delta are mapped to a StepResult:

    stateMeaning
    advancedTransition completed; change moves to next step.
    blockedAgent raised a blocker; step is parked.
    awaiting-approvalPropose completed under an after-propose/every-phase gate; step is parked for approval.
    nothing-readyNo actionable step found.
  7. Persist outcome. Parked state is written to state.json; journal entry appended to journal.jsonl. A cleared park (on advanced) removes the prior park entry.

Gate behavior by setting:

gateAfter-propose behavior
voluntaryAgent may voluntarily park as blocked; no automatic approval gate.
after-proposeEvery propose parks as awaiting-approval before apply.
every-phaseSame as after-propose within each phase.
autonomousAgent may park on blockers; no approval gate.

JSON output: the raw StepResult object (state, change, transition, blocker?, approvalRequest?, journalRefs?, message?). transition is propose | apply | verify | decompose; on a decomposition step change carries the decomposed phase's name. When nothing is ready, { state: 'nothing-ready', message: '...' }. When a step is pre-checked as parked, { state: 'parked', change, reason, hint }.

Attributed fast failures: when a transition (or a batch-driven pr / phase- decomposition step) whose resolved spec explicitly named a model fails fast on a real non-zero exit code (no signal — signal === null, not a signal kill), with no journal progress — the argv-rejection signature — the surfaced blocker/message carry the stage/agent/model/scope attribution hint, so the non-JSON blocked line (⚠ blocked — …), the parked-step reason re-shown on resume, and the journal entry message recorded for the transition all name the exact model string and its supplying scope as "if this model id is invalid…" guidance (the detail field still opens with the hint above the captured stderr tail for --json consumers). The pr stage spawn is attributed via its pr stage entry's supplying scope; the phase-decomposition spawn is likewise attributed via its own decompose stage entry's supplying scope. A signal-killed spawn (e.g. a timeout SIGKILL, OOM kill — exitCode: null, signal: 'SIGKILL') under a valid explicit model is NOT a real exit code, so the hint is suppressed there even with zero journal progress; the bare-failure fallback names the signal (via signal SIGKILL) and surfaces the stderr tail on every rendered surface without the hint, so an externally killed agent under a valid model never misdirects the operator. Bare-name specs, stage-map-driven decompose spawns (default agent, no model), scope-less standalone paths, and failures after journal progress render byte-for-byte today's output with no hint.

batch rerun-proof

Invalidate a phase's recorded proof-of-work so the next batch apply re-runs that phase's configured boundary proof.

Synopsis

ratchet batch rerun-proof [name] --phase <phase> [--json]

[name] defaults to the current active batch when omitted. --phase is required.

Options

OptionArgumentDescription
--phase<phase>Required. The phase whose recorded proof-of-work to invalidate. Must be a phase declared in the manifest.
--jsonOutput the result as JSON ({ batch, phase, invalidated }).

Behavior

A phase's boundary proof-of-work runs at most once: batch apply journals a durable proof-of-work verdict, and the gate reads that verdict forever after. When a verdict was recorded FAILED for a fixable reason (a misconfigured pass condition, a flaky run, an environment fix), the next phase stays permanently blocked. batch rerun-proof is the supported operator override that replaces hand-editing the append-only run journal.

It appends a superseding proof-of-work-invalidated marker keyed by the same proof-of-work:<phase> key the recorder uses. The journal stays append-only — the original proof-of-work entry is left in place (the audit trail is preserved). The single record reader (proofRecordsFromEntries, read by both the phase gate and boundary-step selection) treats the later marker as removing the phase from the current record map, so the next batch apply re-runs the phase's own configured proofOfWork.run boundary proof and records a fresh verdict that re-derives the gate. The marker carries only the phase name — no toolchain or command detail.

  • Recorded proof present (failing or passing): appends the marker and reports that the phase's recorded proof was invalidated; the next batch apply re-runs the boundary instead of advancing into the next phase.
  • No recorded proof for the phase: a no-op — nothing is appended, the run journal is left unchanged, and the command reports there is nothing to invalidate (invalidated: false in JSON).
  • Missing --phase: exits with an actionable error naming the missing flag; nothing is appended.
  • Unknown phase (not in the manifest): exits with an error stating the phase is not part of the batch; nothing is appended.

This is an orchestration override only: it journals a marker that changes which boundary step the engine next offers. It spawns no agent and defines no second "done" — the boundary proof still runs via the normal batch apply path. See engine: phase gates and proof-of-work and run-state: proof-of-work records.

batch archive

Archive a completed batch: cascade change-archive over member changes, then move the batch directory to the archive.

Synopsis

ratchet batch archive [name] [-y | --yes] [--json]

[name] defaults to the current active batch when omitted.

Options

OptionDescription
-y, --yesSkip the incomplete-batch confirmation prompt.
--jsonOutput the structured archive result as JSON.

Behavior

  1. Loads and derives the current batch status. If any changes are not done, prints a warning listing the incomplete changes and prompts for confirmation (unless --yes skips the prompt). Declining the prompt aborts with no changes made.

  2. Resolves the destination path .ratchet/batches/archive/<YYYY-MM-DD>-<name>/ and fails before any cascade if an entry already exists there.

  3. Cascades the change-archive flow over every change intent in phase order. Already-archived changes are skipped (idempotent). Never-created (pending) changes are skipped. Each archived change runs through the standard ArchiveCommand (feature-store materialization, standard-link materialization).

  4. Moves the batch directory (manifest + run journal) to the resolved archive path.

A mid-cascade failure leaves earlier changes archived and the batch directory in place. Re-running is safe: already-archived changes are skipped.

JSON output: { batchName, archivedChanges, skippedArchived, skippedPending, archivePath?, aborted? }.

Name resolution

For subcommands where [name] is optional:

  • If <name> is given, it must match an existing batch under .ratchet/batches/; otherwise an error is thrown.
  • If omitted and exactly one batch exists, that batch is selected automatically.
  • If omitted and zero or multiple batches exist, the command errors with actionable guidance (create one, or specify a name).

The archive/ subdirectory is never treated as a batch name.

Notes

First-run agent-permissions setup. Before the first batch subcommand executes when no agent-permissions posture is configured, an interactive setup prompt guides the operator to choose a posture. Headless and CI environments are never prompted — the effective posture falls back to the built-in default. The setup is best-effort: any failure is non-fatal and the underlying command proceeds. Debug output is available under DEBUG=1 or RATCHET_DEBUG=1.

See also