Getting started
Install
Section titled “Install”go install github.com/pdbethke/corralai/cmd/corral@latestOr clone and build from source:
git clone https://github.com/pdbethke/corralaicd corralaigo build ./...See it work first: corral demo
Section titled “See it work first: corral demo”One command, no setup beyond a provider key:
corral demo --writer-model <model> --mutant-model <model> --critic-model <model>It writes a small Go package with a five-clause password rule and a test that checks
only two of them, then audits it with the real certify --local. You need a Go
toolchain — you installed corral with one — and one provider key. No venv, no
database, no fixtures. The project is left on disk so you can read the test and see
what it never asserts.
Then point it at your own code, with corral doctor first.
Your first audit: corral certify --local
Section titled “Your first audit: corral certify --local”The fastest way to see corral do the thing it’s for — certify a change by execution, not opinion — is one command, off your own key, no daemon:
export ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_/GEMINI_/OPENROUTER_API_KEY
corral certify --local \ --code path/to/your/file.go \ --goal "what this code must guarantee" \ --writer-model claude-sonnet-5 \ --mutant-model claude-sonnet-5 \ --critic-model claude-haiku-4-5 \ -- go test ./...Those model names are an example, not a default — corral has none. Every seat is
yours to name, from whichever provider you hold a key for; the models above are what
we run. The only rule is that the test-critic must differ from the test-writer,
because that decorrelation is what the verdict rests on — a property, not a vendor,
so any two distinct models satisfy it. --critic-model off drops the critic
entirely (advisory, never gates the verdict). A run with an unnamed seat is refused,
and the refusal reports which provider credentials it can see.
That runs the full adversarial testing pool in-process: mutant-generators
seed goal-violating bugs into your code, your own test is scored against them
by executing it in a jail (never a self-report), a test-writer proves any gap
is real by writing and killing the test you were missing, and a decorrelated
test-critic reads your suite cold. You get a signed verdict — certified or
needs-review — and can re-check it offline any time:
corral certify verify <record>Before you spend a run: corral doctor
Section titled “Before you spend a run: corral doctor”An audit costs real money and real minutes, and it is almost always the
environment that stops one — the sandbox won’t start, the toolchain is invisible
inside it, the key for the model you assigned is missing, the file has no paired
test. Discovered one at a time, each of those costs another run, and most cost money
to learn. doctor checks them all up front for free — no model is ever called —
in the order the audit itself would hit them, so the first FAIL is the first thing
to fix:
corral doctor --code path/to/your/file.go \ --writer-model claude-sonnet-5 --mutant-model claude-sonnet-5 \ -- go test ./... [ok ] sandbox starts [ok ] toolchain reachable inside the sandbox [FAIL] credential for mutant-generator (claude-sonnet-5) agentbackend: ForModel: model "claude-sonnet-5" needs an Anthropic key — set ANTHROPIC_API_KEY
1 check(s) failed — fix these before spending a run.Every argument is optional and each unlocks more checks: --code/--test add the
test-pairing check, a test command after -- adds the in-sandbox toolchain check,
and --mutant-model/--writer-model/--critic-model check the credential for
exactly the models you plan to route to. It exits non-zero if anything failed.
Two things it deliberately does not check, because both need a real seeded
workspace: whether your suite passes on unmutated code inside the sandbox — the
most common way an audit dies — and whether a multi-file project needs --repo-dir.
certify --local reports the first as COULD-NOT-GRADE, with the runner’s own
output.
On a file with several functions the generation fans out — one seat per
group of functions, so every function gets probed, not just whichever one a
single generator happened to pick (--max-shards N bounds the width; the
default auto-sizes to your machine). Name a --shadow-model and a
challenger model attacks the same regions in parallel, purely to measure one
model against another on identical ground — it’s recorded for comparison and
never part of the verdict (off unless named, like every seat). And a mutant
that makes your suite hang is killed fast and counted as caught, so a runaway
loop can’t stall the audit (--test-timeout overrides the auto-derived cap).
Corral has no default models — every seat is named by you, and a run with an
unnamed seat is refused. One key can still satisfy the distinctness rule on its
own: two different models from the same provider (Sonnet writing and mutating,
Haiku critiquing, say) off that single ANTHROPIC_API_KEY. Naming
--critic-model gemini-3.6-flash plus a Google key (GEMINI_API_KEY or
GOOGLE_API_KEY) routes the critic to Gemini via the OpenAI-compatible Google
endpoint — a real cross-vendor critic, writer and mutant-generator unchanged. A
missing key fails the run closed instead of silently falling back. It supports
Go, Python (pytest), Ruby (minitest/RSpec), JavaScript (node:test),
TypeScript (tsc + node:test), and PHP (PHPUnit) — the language is inferred
from --code’s extension.
It always runs sandboxed. bwrap is the Linux default; --jail container
falls back to docker/podman; macOS uses sandbox-exec. There’s no unsandboxed
option. On Ubuntu 24.04+, apparmor disables unprivileged user namespaces by
default and bwrap won’t start — the error message spells out the exact
one-line fix (or pass --jail container). And the language toolchain has to be
jail-visible: installed system-wide under /usr (your distro’s
golang/python3 package), not a --user/snap/pyenv install — a snap go or
a pip install --user pytest is invisible inside the sandboxed mount
namespace and the run will fail closed looking for it.
Python: you do not have to install pytest system-wide. Plenty of developers
will not contaminate their system interpreter for an audit tool, and they
shouldn’t have to. The jail binds /usr but not your home directory, so what
actually breaks is a runner living under $HOME — pip install --user, pyenv,
or a virtualenv in the default ~/.local/share/virtualenvs. Put the virtualenv
inside the project instead, at ./.venv or ./venv, and --repo-dir
auto-detects it and bind-mounts it read-only into the jail (see
Dependency dirs).
Then run your suite through it:
python3 -m venv .venv && .venv/bin/pip install pytest # or:PIPENV_VENV_IN_PROJECT=1 pipenv install --dev pytest
corral certify --local --repo-dir . --code pkg/thing.py --test tests/test_thing.py \ --goal "…" -- .venv/bin/python -m pytest -qpipenv’s default venv location is outside the project, which is exactly the
invisible case — PIPENV_VENV_IN_PROJECT=1 is what moves it somewhere the jail
can see.
Walk through a real verdict end to end in Your first audit, in detail.
Running the brain
Section titled “Running the brain”--local runs the audit standalone. The brain is the coordination substrate
the repo/control gates run inside of — worth running if you want the gate
poller, shared memory, or the swarm UI, not required for --local.
Against a brain you’re running yourself (dev mode — no CORRALAI_OIDC_ISSUER
set, so auth is off):
go run ./cmd/corralOpen http://127.0.0.1:9019/ for the live corral view and the Progress
tab. From another terminal, drive it with corral-admin — see the
CLI reference for every verb.
Common knobs
Section titled “Common knobs”CORRALAI_OIDC_ISSUER/CORRALAI_OIDC_AUDIENCE— cross-machine authCORRALAI_GIT_TOKEN+CORRALAI_FORGES— repo-work / multi-forge missionsCORRALAI_EMBED_URL— reference RAG + vector searchCORRALAI_MOTHERDUCK— fleet analytics + oracleMODEL_BACKEND/OPENAI_BASE_URL— bring your own model
See Running it for the full rundown, and
the CLI reference for every corral-admin verb.