Configuration

An audit is configured by its flags — every seat is named on the command line, because corral has no default models — and by a handful of environment variables for keys, the sandbox, signing and where files live. There is no config file to hand-edit. Everything below is optional except the seats.

Seats and provider keys

Each seat — --mutant-model, --writer-model, --critic-model, and on certify --repo --derive-model — resolves its own backend from the name you give it. A run with an unnamed seat is refused before any sandbox, store or spend, and the refusal says which provider credentials it can see. The one rule: the critic must differ from the writer (--critic-model off drops the critic, which is advisory and never gates).

Variable What it is
ANTHROPIC_API_KEY Claude models
GEMINI_API_KEY (or GOOGLE_API_KEY) Gemini models
OPENAI_API_KEY OpenAI models
OPENROUTER_API_KEY OpenRouter, one key for several vendors
MODEL_BACKEND / OPENAI_BASE_URL Pin every seat to one endpoint (OpenRouter, a local daemon) instead of routing by model name. An explicit gateway is never re-routed
OLLAMA_URL The local Ollama daemon (default http://127.0.0.1:11434); --local-endpoint <role>=<url> places one seat on another daemon

A key alone does not move a seat to a provider. A Gemini model name with only an Anthropic key configured is an error from Anthropic, not a Gemini call: set the model names and the matching keys.

Keys can live in corral’s keystore instead of a plaintext environment:

corral secret set ANTHROPIC_API_KEY    # value read from stdin — never a CLI arg
corral secret list                     # names only, never values
corral secret rm  GEMINI_API_KEY

Each secret resolves through env var → OS keyring → an age-encrypted file (first hit wins, so an env var always overrides a stored value). On a headless server the store is age-encrypted under your config dir (CORRAL_CREDS_DIR to override), with its identity from CORRAL_AGE_IDENTITY, a systemd credential (age-identity) or a 0600 key file, and it fails closed if that source is present but malformed. Model keys are removed from the environment of your tests and of everything else corral runs to grade them; an agent seat’s CLI is the exception (below).

Agent seats (subscriptions)

Any seat can be a coding-agent CLI instead of an API model: claude-code and codex (and antigravity) come defined, as <agent> or <agent>:<model>. The agent uses its own login, so on a subscription plan the seat has no per-token API bill. Define your own:

Variable What it is
CORRALAI_AGENT_<NAME> The command line for a seat called <name>, with {dir}, {out}, {model}, {model:FLAG} and {tools:FLAG} substituted
CORRALAI_AGENT_<NAME>_WIRE How to read its reply: claude-json, codex-jsonl or text (the default for your own definitions). A text seat is not metered, and is refused under --max-tokens

Never put a credential in a CORRALAI_AGENT_* definition — it is published verbatim in the signed record. The agent CLI inherits corral’s environment, so an exported vendor API key may bill instead of the subscription. Details, and what was measured: agent seats.

The model registry

Seats can be named by alias from a registry, read from exactly one of these (most explicit first; they do not merge):

Source What it is
CORRALAI_MODELS The whole registry as inline JSON — how a CI job declares a herd without committing a file
CORRALAI_MODELS_FILE A registry file elsewhere on disk (a named file that does not exist is an error)
.corral/models.json The repository’s own registry

A registry marked "strict": true requires every seat to name a declared alias.

The sandbox

Variable What it is
CORRALAI_EXEC_IMAGE The image --jail container runs in; it must carry your toolchain (e.g. python:3.12-bookworm). Required for that backend
CORRALAI_EXEC_RUNTIME Which container runtime to use (default: podman, then docker, whichever is on PATH)

bwrap is the Linux default and needs nothing configured, except on Ubuntu 24.04 and later, where AppArmor must allow it — see Getting started. certify --repo --substrate workspace runs on your checkout instead of in a sandbox; see Trust & security.

Signing, the ledger and the cache

Variable What it is Default
CORRALAI_CERTIFY_KEY Hex-encoded Ed25519 seed that signs verdicts and ledger entries; takes priority over the file —
CORRALAI_CERTIFY_KEY_FILE Where the signing key seed lives; created 0600 on first use by certify --local $STATE/corralai_certify_key
CORRAL_LEDGER The ledger directory certify --repo writes its entry to and reads its prior from (--ledger overrides) <repo>/.corral/ledger
CORRALAI_MACHINE_LEDGER This machine’s own ledger: certify --local (and so corral demo, which has no opt-out) writes its entry here, and certify --repo and review keep a copy of theirs here (--no-ledger skips it; never written inside the audited checkout) $STATE/ledger
CORRALAI_LEDGER_PUBKEY The public key corral ledger verify checks signatures against when --pub is not given — (signatures not checked)
CORRALAI_CACHE_DB The local cache of derived goals and coverage selections (a cache, not a record) $CACHE/corralai_cache.duckdb
motherduck_token Read by --push md:<db> to reach MotherDuck —

corral certify pubkey prints the public half of your signing key. A certify --repo ledger entry is signed when a key is configured or already exists at the default path, and written unsigned — and said to be — when none is. The verdict cache is the ledger directory itself, not a file of its own.

Where state lives

Every local store defaults under corral’s state directory (~/.local/state/corral on Linux, ~/Library/Application Support/corral on macOS) and the cache under ~/.cache/corral. CORRALAI_STATE_DIR and CORRALAI_CACHE_DIR move them; a per-store variable moves one. corral state paths prints every file, which variable moves it, and where it resolves. The ones an audit writes are the signing key, and on certify --local the scorecard stores (CORRALAI_BUGCATCH_DB, CORRALAI_CRITICSCORE_DB, CORRALAI_MUTANT_ATTEMPTS_DB); the rest belong to the daemon.

Moving from an older install

Before 1.0, corral kept these files in ~/.claude. It no longer reads that directory, and it refuses to start a store whose only copy is still there rather than silently create an empty one (or a new signing key). Run corral state migrate --dry-run, then corral state migrate, with the daemon stopped. It prints the certify key’s public key before and after; it must match the LEDGER_PUBKEY of any ledger you sign.

Migrate refuses, touching nothing, on any conflict, on a symlinked source, on a source owned by another user and on a running daemon. If a migrate stops partway, fix the cause and run corral state migrate --resume: database side files move first, and a side file already at the destination is a conflict unless --resume says it came from that interrupted run (each one adopted is listed). Do not start the daemon until a migrate completes. To keep using the old directory in place instead, set CORRALAI_STATE_DIR to it.

The optional daemon

The daemon is configured entirely through environment variables; with none set, it boots in dev mode (auth off) against defaults under the state directory.

Variable What it is Default
CORRALAI_ADDR Listen address (loopback by default; front it with a tunnel/proxy) 127.0.0.1:9019
CORRALAI_DB Coordination SQLite (missions, tasks, claims) $STATE/corralai_coord.sqlite3
CORRALAI_MEMORY_DB Memory DuckDB (the searchable knowledge store) $STATE/corralai_memory.duckdb
CORRALAI_MEMORY_DIR Where memory entries are written and indexed (markdown files) $STATE/memory
CORRALAI_RECORDINGS_DB Recordings DuckDB for scrubbed replay exports $STATE/corralai_recordings.duckdb

Reliability & stall detection

Variable What it is Default
CORRALAI_TASK_STALL_SECONDS How long a claimed-but-untouched task may sit before the brain treats it as stalled and lets the herd reclaim it 300

This is the knob behind graceful degradation over deadlock: a worker that claims a task and then dies (crash, quota blip) must never strand the mission — after the stall window its lease is reclaimable by the rest of the herd.

Auth & access

With CORRALAI_OIDC_ISSUER unset, auth is disabled (dev mode — any caller is trusted). Set it to any OIDC provider to turn auth on.

Variable What it is
CORRALAI_OIDC_ISSUER / CORRALAI_OIDC_AUDIENCE The primary trusted OIDC issuer + expected audience (empty issuer ⇒ auth off)
CORRALAI_OIDC_CLIENTS Extra trusted clients, "issuer|aud,issuer|aud"
CORRALAI_ALLOWED_PRINCIPALS Day-0 seed of member emails (the DB is canonical afterward)
CORRALAI_ADMIN_PRINCIPALS Day-0 seed of superuser emails
CORRALAI_ALLOWED_HOSTS Accepted Host headers (add the public hostname a tunnel exposes)
CORRALAI_MEMORY_OWNERS Emails allowed to read/write memory

Repo work (jail → PR)

Variable What it is
CORRALAI_GIT_TOKEN Forge PAT for clone + PR; empty ⇒ repo engine off unless CORRALAI_REPO_ENABLE=1
CORRALAI_FORGES Multi-forge config (GitHub / GitLab / Gitea) — each token is only ever used against its own host
CORRALAI_REPO_WORKSPACE Root dir for per-mission working copies

The token lives only in the brain — it is scrubbed from the environment, injected into a push URL for one call, and never written to .git/config. See SECURITY.md’s section on the optional daemon.

Models & the fleet

Variable What it is
MODEL_BACKEND / OPENAI_BASE_URL Bring your own model — point at any OpenAI-compatible endpoint (local Ollama to frontier)
CORRALAI_ADVPOOL_MODELS The hosted pool’s herd: mutant-generator=<m>,test-writer=<m>,test-critic=<m> (test-critic=off or omitted leaves the critic unstaffed). Required — see below
CORRALAI_EMBED_URL Embeddings endpoint for reference RAG + semantic code search
CORRALAI_MOTHERDUCK Fleet-sync target (md:<db> or a .duckdb path); empty ⇒ sync off
CORRALAI_BRAIN_ID Tag for this brain’s rows in the fleet (default: hostname)

The daemon has no default models either. Its hosted adversarial pool used to cold-start on a hardcoded pair of small local models, which meant a brain nobody had configured still ran something. It no longer does: with no CORRALAI_ADVPOOL_MODELS the pool logs DISABLED — no models assigned and is never registered, and a run started without an assignment is refused. A malformed CORRALAI_ADVPOOL_MODELS is refused too, rather than quietly falling back to something you did not choose.

The models are yours to name, from any provider you hold a key for. The only rule is that the test-critic must differ from the test-writer — that decorrelation is what the verdict rests on, and it is a property, not a vendor, so any two distinct models satisfy it. (The critic may also be left unfilled: test-critic=off. It is advisory and never gates a verdict.)

The herd is the allowlist. The brain’s leaderboard can re-seat a role — but only to a model you named somewhere in the herd, only once that model has at least 5 completed tasks in that role, and only when it outranks your own pick on the same evidence. A model you never named cannot be routed to, whatever a worker reported about it, and the roster each run actually used is logged at run start.

The keystore above serves the daemon too: corral-agent reads OPENAI_API_KEY / ANTHROPIC_API_KEY / CORRALAI_BRAIN_KEY through it, so they need never sit in the process environment.

See Running it for dev mode vs. auth-on, and Multi-model herds for the daemon’s herd.