Skip to main content

config.yaml

.ratchet/config.yaml (also accepted as .ratchet/config.yml) is the project-level configuration file for Ratchet. It is read on every command and validated field-by-field; an invalid field emits a warning and is ignored rather than aborting the command. The file is YAML; all keys are optional unless noted.

Effective batch settings resolve across four scopes in increasing precedence: built-in default ← user/global config ← .ratchet/config.yaml ← per-change manifest.


Top-level keys

KeyTypeRequiredDefaultDescription
schemastringyesWorkflow schema to use. Must be a non-empty string matching a built-in schema name (e.g. ratchet) or a project-local schema name.
contextstringnoFree-text project context injected verbatim into every artifact instruction. Maximum 50 KB (UTF-8 bytes); larger values are ignored with a warning.
rulesmapnoPer-artifact additive rules. Keys are artifact IDs from the active schema; values are arrays of rule strings. Unknown artifact IDs produce a warning.
batchobjectnoProject-level defaults for batch orchestration. All sub-keys are optional; absent keys inherit the built-in default. See batch: settings.
evalobjectnoProject-level defaults for eval orchestration. See eval: settings.

rules structure

rules:
features:
- "Keep scenarios atomic: one behavior per scenario."
plan:
- "Tasks must reference the feature file that requires them."

Keys must match artifact IDs defined in the active schema. For the built-in ratchet schema the valid IDs are features and plan.


batch: settings

Project-level defaults for the batch engine. Per-manifest overrides take precedence over these values; both are overridden by flags passed to individual commands.

Scalar settings (all keys except permissions) are nearest-wins across scopes.

Invalid batch key load behavior

The batch: section is parsed per-key (resilient): each present key is validated independently, so a malformed key (e.g. agent: "claude:") is dropped with a warning naming the key path (batch.agent, or batch.agent.apply for a malformed per-stage map entry) and the offending value, while valid sibling settings (e.g. gate, locus, permissions) are preserved instead of being silently reverted to defaults. The secret authToken is never echoed — its warning names the key only. A fully valid section loads warning-free and value-identical. Unknown keys are ignored (the schema is .partial(), not .strict()).

Gate and orchestration

KeyTypeDefaultAccepted valuesDescription
gatestringvoluntaryvoluntary after-propose every-phase autonomousControls when the batch engine pauses for human approval between phases. voluntary never interrupts; after-propose gates after the propose phase; every-phase gates after every phase; autonomous runs all phases without interruption.
strategystringvertical-slicevertical-slice featureSlice strategy used when the batch manifest is generated.
proofOfWorkstringhard-gatehard-gate warnWhat happens when an agent does not produce required proof-of-work. hard-gate blocks the phase; warn logs a warning and continues.
prGroupingstringoffoff whole-batch per-phase per-changeWhether a completed batch opens a pull request, and how work is grouped into PRs. off opens no PR (behavior unchanged). whole-batch groups the whole batch's work into a single PR opened by a dedicated pr-stage agent at completion. per-phase opens one stacked PR per completed phase and per-change one stacked PR per change — batch apply drives one stacked PR per group boundary, one group per apply in boundary order, idempotently per group (group 0 targets the batch base branch, group N targets group N-1's branch).

Execution locus

KeyTypeDefaultAccepted valuesDescription
locusstringlocallocal docker remoteWhere the agent runs. local drives the in-process ReX sidecar. docker runs the step inside a container via ReX DockerDeployment with the project root bind-mounted. remote drives a swerex-remote server over its REST API.
agentstring | mapan agent[:model] spec, or a {propose, apply, verify, pr, decompose} stage-map of spec valuesCoding agent(s) to spawn. A scalar spec applies to every lifecycle stage; a partial per-stage map assigns a spec to each stage (see Per-stage agent map). Each spec is an agent name optionally followed by :model (e.g. claude, claude:fable, opencode:zai/glm-5.2) — see Agent [:model] spec. When unset, the engine uses the agent configured at init time.
imagestringfree-formContainer image reference for locus: docker. Must be non-empty when set. When unset and locus is docker, the runtime falls back to python:3.12.

Per-stage agent map

agent accepts either a single agent name or a map that names a coding agent per lifecycle stage — propose, apply, verify, pr (the whole-batch PR-opening stage, spawned only when prGrouping is active), and decompose (the phase-decomposition stage, spawned when a batch has a reachable empty phase to author into batch.yaml):

batch:
agent:
propose: claude
apply: opencode
verify: opencode
decompose: claude

Each lifecycle transition spawns the agent its stage maps to — so the example above has claude propose and decompose while opencode applies and verifies. The map is partial: any subset of the five stages may be given. Stage resolution falls back in order mapped stage → scalar → default agent: a stage the map names uses that agent; a stage it omits uses a scalar agent if one is in effect, otherwise the init-time default. A scalar name is therefore equivalent to naming the same agent for every stage, and an unset agent keeps the init-time default for every stage — both behave exactly as before. An unmapped decompose (or pr) falls to the default agent with no model flag, so a config that does not name decompose behaves byte-for-byte as before.

Cross-scope merge (nearest-wins per stage). When agent is set at more than one scope (project config batch: ← manifest settings:), the values merge one stage at a time rather than replacing wholesale:

  • a partial stage-map merges over a lower scope per stage — each stage it names wins for that stage, while stages it omits keep the lower scope's agent (a lower scalar covers all of them);
  • a nearer scalar covers every stage, so it replaces a farther map entirely.

For example, a project-config scalar claude with a manifest map { apply: opencode } routes apply to opencode and both propose and verify to claude; a project-config map { propose: claude, apply: claude } under a manifest map { apply: opencode } routes propose to claude, apply to opencode, and the unmapped verify to the default.

Resolution records, per stage, which scope (project config vs manifest) supplied that stage's resolved agent[:model] spec — so a model-rejection failure can be attributed to the scope that supplied the model. A stage attributed to project carried the project config's spec; a stage attributed to manifest carried the manifest's; a stage no scope supplied (the default agent) carries no attribution. The resolved value and merge results are unchanged by this attribution. This per-stage scope is what the engine's model-failure hint names: when a transition whose spec explicitly named a model fails fast (non-zero exit, no journal progress), the surfaced failure names the stage, agent, exact model string, and the supplying scope above the stderr tail — so the operator can correct the agent setting at that scope rather than reverse-engineer the merge.

The same shape is validated at both the project-config batch: scope and a batch manifest's settings: scope. Validation rejects an unknown stage key (any key other than propose, apply, verify, pr, or decompose), a non-string agent value, and a malformed agent[:model] spec (empty agent part or empty model part, e.g. claude:, :fable, :) — naming the offending value — at config load, before any agent is spawned; an unknown agent name (scalar or mapped) is rejected — naming the available adapters — before that stage's agent is spawned.

Agent [:model] spec

Every string value of the agent setting — the scalar form and each stage-map value, at both the project-config batch: scope and the manifest settings: scope — is an agent[:model] spec: an agent name, optionally followed by a model after the first :.

batch:
agent:
propose: claude:fable
apply: opencode:zai/glm-5.2
verify: opencode:qwen/qwen-3.7

The spec splits on the first : only, so a model id that itself contains a colon or slash passes through intact (e.g. opencode:zai/glm-5.2 → agent opencode, model zai/glm-5.2; codex:vendor:tagged-1 → agent codex, model vendor:tagged-1). A bare agent name (no :) carries no model — the spawned agent uses its harness-configured default model, and existing bare-name configs validate byte-for-byte unchanged.

The model part is free-form pass-through: the parser keeps no model registry and never consults the adapter registry. Whether the agent part names a known agent stays a resolution/spawn-time concern (UnknownAgentError in resolveAdapter, before any spawn). A spec with an empty agent part (:fable, :) or empty model part (claude:, :) is rejected at config load with a message naming the offending value, e.g. Invalid agent spec ":fable": empty agent part (expected "agent[:model]").

A spec whose agent part or model part has leading or trailing whitespace is likewise rejected at config load, naming the offending value and which part is at fault — e.g. Invalid agent spec "claude: opus": model part " opus" has leading or trailing whitespace (expected "agent[:model]") (and " claude", "claude ", "claude :m", "claude: " the same way). The parser rejects rather than auto-trims: silently trimming "claude: opus" would repair the value but hide the config defect. A model part starting with - is also rejected — e.g. Invalid agent spec "claude:-flag": model part "-flag" must not start with "-" (expected "agent[:model]") — so a flag-shaped token like claude:--dangerously-skip-permissions never rides into a spawned agent's argv as an option. Interior dashes stay valid (codex:gpt-5.2-codex), as do models containing / or :.

The schema validates but does not transform: stored values remain whole spec strings, so the cross-scope nearest-wins per-stage merge moves agent and model atomically — a nearer apply: opencode:zai/glm-5.2 replaces a farther apply agent and model together, never one without the other.

When the engine spawns an agent for a transition, it parses the resolved spec once and threads the model part to that agent's adapter via the request context. Each adapter declares its own model flag — claude, opencode, and cursor use --model; codex and gemini use -m — and the adapter appends [flag, model] to the spawn argv only when a model is named. A bare agent name (no : model part) emits no model flag, so the agent runs its harness-configured default model and the spawn argv is byte-for-byte identical to a config with no model named. The model pair sits between the agent's base argv and any permission flags, so the base argv shape and the trailing permission block are preserved.

Agent timeout

Applies to every locus (local, docker, remote) and every coding agent.

KeyTypeDefaultAccepted valuesDescription
agentTimeoutMsnumber600000positive integer (ms)Per-agent ReX timeout in milliseconds — the guard against a hung agent. When unset, the runtime applies its built-in default of 600000 (10 minutes). Raise it when a slow-but-passing transition (e.g. a full-suite coverage proof-of-work) is being killed at the default.

The RATCHET_AGENT_TIMEOUT_MS environment variable overrides this key. It must parse to a positive integer; a zero, negative, non-numeric, or empty value is ignored (a typo never shortens or removes the guard) and resolution falls through to the config key, then to the built-in default. The effective timeout resolves with precedence env > manifest > project config > built-in default.

# Raise the per-agent timeout to 30 minutes for one run, overriding any config key.
RATCHET_AGENT_TIMEOUT_MS=1800000 ratchet batch run

Remote-locus settings

These keys are required when locus: remote and ignored for local and docker.

KeyTypeDefaultDescription
hoststringHostname (or scheme://host) of the swerex-remote server. A bare host resolves to http for loopback addresses and https for all others. An explicit http:// to a non-local host is rejected unless insecure: true is set.
portnumberPort of the swerex-remote server. Must be a positive integer.
authTokenstringSecret sent as the X-API-Key header to the swerex-remote server. Redacted in all ratchet batch config output.
insecurebooleanfalseOpt-in to send authToken over plaintext http:// to a non-local host. Has no effect on loopback hosts, which always allow plaintext.

batch.permissions object

Agent-agnostic permission policy. Merged across user/global, project, and per-change manifest scopes: posture is nearest-wins; deny is the union of all scopes; allow is replaced by the nearest scope that defines it; each agent's raw entry is nearest-wins per agent.

KeyTypeDefaultAccepted valuesDescription
posturestringrepo-sandboxed-permissiverepo-sandboxed-permissive curated-allowlist full-autonomyAgent-agnostic permission posture. repo-sandboxed-permissive: edits and ordinary build/test commands run unprompted, scoped to the repo, with a denylist blocking destructive operations. curated-allowlist: nothing runs unprompted outside an explicit allow list. full-autonomy: all permission checks bypassed.
allowstring[][]tool-pattern stringsAllowlist of tool-name patterns. Injected as native permission flags for each agent. Replaced (not merged) by the nearest scope that defines the key.
denystring[][]tool-pattern stringsDenylist of tool-name patterns. Unioned across all scopes; a narrower scope cannot remove a denial set by a wider scope.
rawobject{}per-agent mapPer-agent raw argv fragment override (escape hatch). Recognized agents: claude, codex, gemini, cursor. Each entry is a string array of flags appended verbatim to the agent invocation. Nearest-wins per agent.

Example batch.permissions block

batch:
permissions:
posture: repo-sandboxed-permissive
deny:
- "Bash(rm -rf*)"
raw:
claude:
- "--allowedTools"
- "Edit,Read,Bash"

eval: settings

Project-level defaults for ratchet eval orchestration.

KeyTypeDefaultAccepted valuesDescription
gateobjectevery contributor enabledkeys deterministic llm-judge invariants regression → booleanEnables or disables each verdict contributor for ratchet eval run. An omitted contributor stays enabled; an unset gate ⇒ every contributor enabled. Overridable per run by --gate/--only/--no-llm-judge/--no-invariants.
judgestringauto deterministic llm-judgeDeprecated default judge mode, mapped onto the gate (deterministic disables llm-judge, llm-judge disables deterministic, auto enables both). Prefer gate.
juryobject{ votes: 1, quorum: majority }votes (integer ≥ 1), quorum (majority | unanimous), panel (reserved, see below)Project-level default jury for the llm-judge contributor. A binding's own jury: block (see LLM-judge binding) overrides this default field-by-field.
skipstring[]— (no patterns)non-empty glob stringsGlob patterns matched against the full case id (* wildcard, anchored to the whole string). A matching case is excluded from judging and recorded skipped, alongside any case carrying an in-file @skip Gherkin tag. Overridable per run by --include-skipped (disables both sources together). See Skip filters.

The gate map selects which contributors execute and gate a run, generalizing the legacy judge mode. A contributor disabled here records its cases unjudged (leaving the run incomplete, so it cannot be promoted to baseline) and takes no part in the overall AND verdict. Setting invariants: false disables the run-level invariant gate — the .ratchet/evals/invariants.yaml manifest is not loaded and no invariant command runs for the run (equivalent to --no-invariants). See Eval verdict aggregation.

An unknown contributor id under gate (or a non-boolean value) is rejected: the whole eval section is dropped with a warning, and eval run falls back to every contributor enabled.

The jury block sets the default number of repeat votes (votes) the llm-judge contributor casts per case and the agreement required to land a definitive verdict (quorum): majority decides on a strict majority either way (a tie does not reach quorum), unanimous requires every vote to agree (any split does not reach quorum). A jury that does not reach its quorum records the case unjudged rather than guessing. panel is a reserved, validated-but-inert slot ({ families: string[] }, min one family) for a future cross-family panel; it is parsed and retained but not yet read by vote resolution. An invalid jury value (e.g. an unrecognized quorum) is rejected the same way an invalid gate value is: the whole eval section is dropped with a warning.

skip excludes matching cases from judging by default, the project-config half of skip filters — the other half is an in-file @skip Gherkin tag, checked first and independent of this config. A skipped case is recorded skipped (never silently dropped) and is counted in the eval run scorecard. --include-skipped on eval run overrides both the skip patterns and the @skip tag together for that run.


Complete example

schema: ratchet

context: |
This is a TypeScript monorepo managed with pnpm workspaces.
All source lives under src/; tests are co-located with a .test.ts suffix.

rules:
features:
- "Each scenario must have at least one Then step asserting an observable output."
plan:
- "Tasks must be ordered by dependency."

batch:
gate: after-propose
strategy: vertical-slice
proofOfWork: hard-gate
locus: local
agent: claude
permissions:
posture: repo-sandboxed-permissive
deny:
- "Bash(curl*)"

eval:
gate:
deterministic: true
llm-judge: true
invariants: true
regression: true
jury:
votes: 3
quorum: unanimous
skip:
- "features/legacy/*"