Getting started
Install
go install github.com/pdbethke/corralai/cmd/corral@v1.0.0-rc.29
Or clone and build from source:
git clone https://github.com/pdbethke/corralai
cd corralai
go build ./...
Run it on your repo
Two commands. The first is free — no key, no sandbox, no money — and tells you in seconds which files corral can audit and which have no paired test:
cd your-repo
corral certify --repo . --dry-run
In Go, JavaScript, TypeScript, Ruby, PHP or Rust, add --evidence and your test
command to also see which files a green test executes even though no filename
pairs them. It runs your suite in the sandbox, with no model and no key:
corral certify --repo . --dry-run --evidence -- <your test command>
Then audit the three highest-ranked files, with a cap on what the run may spend:
export GEMINI_API_KEY=... # and ANTHROPIC_API_KEY for the critic below
corral certify --repo . --top 3 --max-tokens 3000000 \
--writer-model gemini-3.8-flash --mutant-model gemini-3.8-flash \
--derive-model gemini-3.8-flash --critic-model claude-haiku-4-5 \
-- .venv/bin/python -m pytest -q # your own test command: npm test, go test ./..., cargo test, …
- Those model names are an example, not a default — corral has none. Name any
models you hold keys for. The one rule: the critic must be a different model
from the writer. One provider is enough (for example two different Anthropic
models), and
--critic-model offdrops the critic, which is advisory anyway. - No API key? Use a subscription. Any seat can be a coding-agent CLI instead
of an API model:
--writer-model codex,--critic-model claude-code, orclaude-code:<model>to pin one. The agent uses its own login, so on a Pro/Max/Plus plan a seat has no per-token bill. Two cautions: the CLI inherits corral’s environment, so an exported vendor API key may be billed instead of the subscription; and a seat that reports no token usage is refused under--max-tokens(claude-codeandcodexdo report it). See agent seats. - Your suite has to pass first, inside the sandbox. The sandbox is offline
and sees
/usrplus your checkout, not your home directory. Install the project’s dev dependencies into a virtualenv inside the project (./.venv), which is bound in read-only, and run the suite through it, as above: a barepythoninside the sandbox is the system interpreter, which does not have your dependencies. For Python, addpytest-covso corral can pick the tests that cover each file. - One language per explicit test command. A test command after
--grades every file, so it is refused when the scan spans more than one language. On a mixed repository, omit it (corral then runs each language’s default runner), or narrow the scan with--path <dir>. - What it costs, measured once: flask’s
src/flask/app.pywith these seats took about six minutes and 0.5M input tokens, and proved 9 of 9 surviving bugs catchable. Your files and suite will differ;--max-tokensis the hard cap. - Where the proven tests appear: at the end of the report, each with “add it to
your suite”.
corral scans show <id> --evidenceprints them again later. corral does not edit your repository or open a pull request.
corral doctor (below) checks your sandbox, toolchain and keys for free before
the first paid run. To run it on every pull request instead, see
the GitHub Action.
A Rust project
Rust is audited with cargo test or cargo nextest run. Two things differ from
the other languages:
- The source’s tests must live in a separate file it declares —
#[path = "foo_tests.rs"] mod tests;, ormod tests;with rustc’s default module file. A file whose tests are an inline#[cfg(test)] mod tests { … }block cannot be audited yet: corral would have to compose its test into the source file itself, and that is not built.--dry-runlists those files as excluded, with the reasontest-landing-not-built. - The toolchain. A stock rustup install lives under
$HOME, which the jail cannot see. Either install a system-wide toolchain the jail can use (RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo rustup-init --no-modify-path, with/usr/local/cargo/binonPATH), or, on a disposable checkout such as a CI runner or a scratch clone, pass--substrate workspace, which runs your own toolchain on the checkout itself (and mutates it — see Limitations).
In a Cargo workspace, scope the scan and the command to one crate:
corral certify --repo . --path crates/<crate> --top 3 --max-tokens 3000000 \
--writer-model gemini-3.8-flash --mutant-model gemini-3.8-flash \
--derive-model gemini-3.8-flash --critic-model claude-haiku-4-5 \
-- cargo test -p <crate>
Rust has no fail-fast flag verified yet, no salvage of a partly failing authored test and no coverage pre-flight; language support has the full table.
See it work first: corral demo
One command, no setup beyond a provider key:
corral demo --writer-model claude-sonnet-5 --mutant-model claude-sonnet-5 --critic-model claude-haiku-4-5
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 the sandbox can see — a system-wide install under /usr, not one under
your home directory or from snap (see “It always runs sandboxed” below) — and one
provider key, or agent seats (--writer-model codex --mutant-model codex --critic-model claude-code) and no key at all. 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
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 --repo-dir . \
--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 \
--out verdict.json \
-- 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, because of --out, a record file you can re-check offline
any time:
corral certify verify verdict.json --pubkey "$(corral certify pubkey)" --allow-unanchored
A record’s own embedded key is never a trust anchor, so verify refuses to run
without --pubkey (or --brain); --allow-unanchored accepts a record that
was not logged to a transparency witness.
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 \
--critic-model claude-haiku-4-5 \
-- go test ./...
[ok ] sandbox starts
[ok ] toolchain reachable inside the sandbox
[FAIL] herd accepted by certify's own preflight (mutant-generator=claude-sonnet-5, test-writer=claude-sonnet-5, test-critic=claude-haiku-4-5)
agentbackend: ForModel: model "claude-sonnet-5" needs an Anthropic key — set ANTHROPIC_API_KEY
[ok ] code file pairs to a test
found path/to/your/file_test.go by convention (sibling)
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 run the same seat preflight
certify runs, for exactly the models you plan to route to. An agent seat gets its
own line: the binary is found and its version printed, but its login is not
verified, because that would cost a call. It exits non-zero if anything failed.
One thing it deliberately does not check, because it needs a real seeded
workspace: whether your suite passes on unmutated code inside the sandbox — the
most common way an audit dies. Run your own test command by hand first;
certify --local reports a failing baseline as COULD-NOT-GRADE, with the
runner’s own output. Whether a multi-file project needs --repo-dir is checked
only where the single-file form is refused outright (a Rust file is one); for
anything with imports, use --repo-dir.
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 is 8 — only --swarm sizes itself to your machine). Name a --shadow-model (or a --shadow-pool of them, one drawn per run) 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
seven languages — Go (go test, gotestsum), Python (pytest, unittest), Ruby
(RSpec, minitest), JavaScript and TypeScript (node --test, jest, mocha,
vitest), PHP (PHPUnit) and Rust (cargo test, cargo nextest) — with the
language inferred from --code’s extension and the runner from your test
command. What each runner can and cannot do is on
language support.
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 restricts unprivileged user namespaces by
default and bwrap won’t start. The error message tells you to load the profile
Ubuntu ships for bwrap, bwrap-userns-restrict (on 24.04 it may still be under
/usr/share/apparmor/extra-profiles):
[ -f /etc/apparmor.d/bwrap-userns-restrict ] || sudo cp /usr/share/apparmor/extra-profiles/bwrap-userns-restrict /etc/apparmor.d/
sudo apparmor_parser -r /etc/apparmor.d/bwrap-userns-restrict
Do not write your own /etc/apparmor.d/bwrap marked unconfined: it takes bwrap
away from the shipped profile and leaves it without the capabilities it needs.
corral printed such a profile before 1.0; if one is on your host, remove it with
sudo apparmor_parser -R /etc/apparmor.d/bwrap && sudo rm /etc/apparmor.d/bwrap.
Or pass --jail container, which needs docker or
podman and CORRALAI_EXEC_IMAGE set to an image carrying your toolchain (for
example python:3.12-bookworm). The sandbox is offline: anything your tests
fetch from the network fails inside it. 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 "…" --writer-model … --mutant-model … --critic-model … \
-- .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.
In your coding agent: corral setup
If you work in a coding agent, one command wires corral into it: the read-only
findings server (corral mcp) and the corral skill, which teaches the agent
to read corral’s record, check a finding by running something, and leave the
verdict to you.
corral setup # pick your agents from a list
corral setup --dry-run --all-detected # see the plan, write nothing
corral setup --uninstall --all-detected # remove only what setup added
Detected agents start unticked and agents already wired start ticked, and
nothing is written until you have seen the plan and said yes. Proven on Linux (2026-10-25) in Claude Code, Codex CLI, Gemini CLI,
Cursor, OpenCode, Antigravity and GitHub Copilot CLI; VS Code is wired but
labelled (not yet proven). See
agent setup
for each agent’s commands and the evidence.
The brain (optional)
Everything on this page runs with no server. The brain is corral’s optional coordination daemon — the substrate under a coordinated herd, a live console and the human-gated proposal loop. Nothing in it is required for a verdict, and nothing in it is read by an audit. If you want it, start with Running it, which says what it is and is not, or read the brain, and what it is not.
Common knobs
MODEL_BACKEND/OPENAI_BASE_URL— pin every seat to one gateway endpoint (OpenRouter, a local daemon); without it each seat routes by the model you nameCORRALAI_MODELS_FILE/CORRALAI_MODELS— the model registry, so seats can be named by aliasCORRAL_LEDGER— wherecertify --repowrites its entry and reads its prior (default: the audited repo’s.corral/ledger/)CORRALAI_CACHE_DB— corral’s local cache of derived goals and test selectionsCORRALAI_CERTIFY_KEY_FILE— where the signing key seed lives (--localverdicts,--attest)
The daemon’s own knobs (CORRALAI_OIDC_ISSUER, CORRALAI_GIT_TOKEN,
CORRALAI_MOTHERDUCK, …) are on Running it; every variable
is documented from the binary’s own source comment on
corral’s CLI page.