One checklist stretched across everything. The more you ask, the less it catches.
Agentic code review for a world where agents write the code
A merge gate you govern, not a bot you tolerate.
Most AI reviewers are one opaque model you hope behaves. Bastion is a registry of small, single-concern reviewers you author and govern, and a gate that blocks when it can't be sure.
- Single-concern reviewers
- Fails closed
- Local loop + CI
- Open source, self-hostable
# Each reviewer owns exactly one concern.
reviewers:
- name: single-responsibility
trigger: [src/**/*.rs]
mode: gate
prompt: |
Block if any one file has taken on multiple
unrelated concerns; otherwise approve it.
- name: parse-dont-validate
trigger: [src/**/*.rs]
mode: advisor
prompt: |
Flag boundaries that pass raw strings where a
parsed type would remove a class of bugs. bastion review --base main - single-responsibility pass
- error-handling block
- parse-dont-validate advisor
Other AI reviewers were built for the old world.
Copilot, CodeRabbit and the rest read the whole diff at once and leave comments for a person to act on. Pile more concerns onto one generic reviewer and its recall on any single one collapses. Attention is scarce, for humans and for agents, and smarter models don't change that.
You cover more ground by adding narrow reviewers, never by broadening one. Each stays at high recall.
One concern per reviewer.
A reviewer is a focused fitness function: a prompt, a trigger, a mode. The unit of the system is the reviewer, not the review. A cross-cutting property like tenant isolation or migration safety isn't special, it's just another reviewer whose single concern is that property.
- single-responsibility No one file concentrates multiple unrelated concerns.
src/**/*.rsgate - error-handling No recoverable error can panic in production code.
src/**/*.rsgate - api-compatibility Public API changes stay backward-compatible.
src/api/**gate - release-surface-robustness Installers verify checksums and fail closed on any mismatch.
scripts/**gate - parse-dont-validate Boundary data is parsed once into a precise type.
src/**/*.rsadvisor - test-coverage New or changed behavior is covered by tests.
src/**/*.rsadvisor - docs-in-sync User-visible changes update the matching docs.
docs/**/*.mdadvisor
Gates block. Advisors comment.
A gate holds the merge until it passes. An advisor reports findings without blocking. Same reviewer shape, different authority, your call per concern.
Declarative and static.
Reviewers live in one file, not generated by code. That keeps them reviewable and the trigger set stable, so a change to review policy is a diff you can read.
Composable and parallel.
They run independently and aggregate at the end. Add a reviewer for a new concern without touching the others; a slow one never blocks a fast one.
Fails closed.
Reviewers run in parallel with per-reviewer timeouts; their verdicts aggregate into one decision. A gate that crashes, times out, or can't produce a valid verdict resolves to block, never a silent pass. Advisors are best-effort: when one fails, it's ignored.
- single-responsibility returned pass pass
- error-handling returned block block
- e2e-checkout-flow timed out block
- migration-safety crashed block
- no-verdict malformed output block
- test-coverage advisor errored skipped
You govern the policy.
Bastion doesn't take the human out of the loop, it moves them up a level: from reviewing every diff to authoring, curating, and governing the reviewers that do. Your interface becomes the registry and the escape feed.
# Changing the review policy is itself reviewed.
bastion/reviewers.yaml @your-org/platform Because reviewers are declarative and static, any PR that weakens the gate, loosens a prompt, drops a trigger, or downgrades a gate to an advisor shows up as a diff a human must approve. The policy can't quietly erode.
- Reviewers are the agents you already trust, written down.
- Even earnest agents can game a check; governance makes it visible.
- The registry is the artifact. Review it, version it, own it.
- 01
An escape slips through
A change merges that a reviewer should have caught. Inevitable, especially early. It's also your most valuable signal.
- 02
Triage which reviewer missed
Find the concern that wasn't covered, or the reviewer whose prompt was too loose to catch it.
- 03
Adjust the policy
Tighten a prompt, add a narrow reviewer, or harden a trigger. It's a diff against reviewers.yaml, reviewed like any other.
- 04
The gate gets sharper
The next changeset meets a better policy. Convergence over time beats strict perfection up front.
The same reviewers, locally and in CI.
A reviewer is a fitness function the author can optimize against before anyone opens a PR. The same reviewers run in your local loop (fast, pre-PR) and in CI (authoritative). CI stops being a slow surprise and becomes a confirmation that's almost always green.
The author loop
An authoring agent runs bastion review against the working tree, exactly as CI would, and loops until green.
# fix what blocked, then run again
$ while ! bastion review --base main; do
agent fixes the blocking findings
$ done
all gates pass · ready to open a PR The confirmation
On the PR, the same gate runs as the authoritative check. Because the author already drove it green, CI mostly just agrees.
# .github/workflows/bastion.yml
✓ bastion / gate
single-responsibility pass
error-handling pass
release-surface pass
merge gate: pass Bastion doesn't own your CI. It runs anywhere code can run, and stays portable across the local and GitHub surfaces by keeping them deliberate mirror images.
Inspectable, not just trusted.
The closed reviewers sell you a SOC 2 badge and a vanity counter, and bill you for the privilege. Bastion offers something you can verify yourself: read every reviewer, run it on your own infrastructure through the model provider and billing you already use, and own the gate outright.
Open source
AGPL-3.0. Every reviewer, every verdict, the whole gate. Nothing about how your code is judged is hidden behind a service.
Self-hostable
Runs anywhere code can run, local or any CI. It drives the agent backends you already use (Claude Code, Codex) and reuses your own auth and billing.
No lock-in
Your policy is one declarative file in your repo. Take it with you. There's no proprietary format and no black box to migrate off of.
We'd rather block than pretend.
Bastion is early software and evolving fast. The runner, the gate, and the Claude Code and Codex backends execute reviewers for real, and anything not yet wired fails closed rather than pretending. An unimplemented reviewer never claims to have reviewed anything. That honesty is the whole point of a gate.
From nothing to your first gate in five minutes.
The installer detects your platform, downloads the matching release, verifies its SHA-256 checksum, and puts bastion on your PATH. It fails closed on any checksum problem.
curl -sSfL https://raw.githubusercontent.com/jssblck/bastion/main/scripts/install.sh | bash irm https://raw.githubusercontent.com/jssblck/bastion/main/scripts/install.ps1 | iex Install the agent skill so your coding agents know how to drive Bastion (commit what it writes), then loop a reviewer until the gate is green:
$ bastion skills install
$ bastion review --base main