Skip to content

Getting started

Terminal window
go install github.com/pdbethke/corralai/cmd/corral@latest

Or clone and build from source:

Terminal window
git clone https://github.com/pdbethke/corralai
cd corralai
go build ./...

One command, no setup beyond a provider key:

Terminal window
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.

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:

Terminal window
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:

Terminal window
corral certify verify <record>

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:

Terminal window
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 $HOMEpip 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:

Terminal window
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 -q

pipenv’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.

--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):

Terminal window
go run ./cmd/corral

Open 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.

  • CORRALAI_OIDC_ISSUER / CORRALAI_OIDC_AUDIENCE — cross-machine auth
  • CORRALAI_GIT_TOKEN + CORRALAI_FORGES — repo-work / multi-forge missions
  • CORRALAI_EMBED_URL — reference RAG + vector search
  • CORRALAI_MOTHERDUCK — fleet analytics + oracle
  • MODEL_BACKEND / OPENAI_BASE_URL — bring your own model

See Running it for the full rundown, and the CLI reference for every corral-admin verb.