[agent]
name = "reviewer"
version = "0.1.1"
description = "Code review agent - discover, scan, review the areas in parallel, then deep review and report, with error recovery and multi-provider fallback"
entry_stage = "discover"
# Read-only agent: it inspects a diff/codebase and reports. No write access.
[tool_permissions]
read_file = "allow"
list_dir = "allow"
bash = "ask"
# ─── Stage 1: Discover ────────────────────────────────────────────────────────
# A reviewer that doesn't know how to run this project's tests can only assert;
# one that does can verify. Map the project first, then review. Cheap model, hard
# iteration cap, one non-error edge (auto-followed, no routing call).
[stages.discover]
mode = "autonomous"
model = { models = [{ provider = "anthropic", model = "claude-sonnet-5" }, { provider = "openai", model = "gpt-5.4-mini" }, { provider = "google", model = "gemini-3.5-flash" }, { provider = "openrouter", model = "deepseek/deepseek-v4-flash" }, { provider = "ollama", model = "qwen3.5:9b" }] }
description = "Map the project and work out how to verify a hypothesis in it"
available_tools = ["read_file", "list_dir", "bash", "context_write"]
max_iterations = 6
max_revisits = 2
system_prompt = """
Before reviewing anything, work out what this project is and how a claim about it
can be CHECKED rather than asserted. Do not review the diff here.
Start from what you already have - do not rediscover it:
- `repo_files` holds the tracked file list (empty if this isn't a git repo).
- `project_context` is pre-loaded with architecture/conventions docs.
- If `CLAUDE.md`, `AGENTS.md`, `CONTRIBUTING.md` or
`.github/copilot-instructions.md` exist, read them - they usually state the
test command and the review bar directly.
Fill gaps with list_dir/read_file. Use bash ONLY read-only, to interrogate the
tooling (e.g. `pytest --collect-only -q`, `cargo test --list`, `npm run`). This
agent has no write access; never attempt to modify anything.
Write `discovery` (context_write): language, build system, test runner, the
command to run a SINGLE test or file, directory layout, and the conventions this
project actually holds itself to (which is what "correct" means in the review).
Write `workflow` (context_write) ending with these two literal lines, which the
deep review uses to confirm hypotheses instead of guessing:
VERIFY: <command to run one test or check, or "none - no runnable suite">
DONE WHEN: <what makes this review complete and trustworthy>
"""
[stages.discover.transitions.scan]
hint = "Project mapped - begin the review"
transform = "direct"
[stages.discover.transitions.error_handler]
condition = "error"
transform = "direct"
# ─── Stage 2: Scan ────────────────────────────────────────────────────────────
[stages.scan]
mode = "autonomous"
model = { models = [{ provider = "anthropic", model = "claude-sonnet-5" }, { provider = "openai", model = "gpt-5.4-mini" }, { provider = "google", model = "gemini-3.5-flash" }, { provider = "openrouter", model = "deepseek/deepseek-v4-flash" }, { provider = "ollama", model = "qwen3.5:9b" }] }
description = "Fast first pass for obvious issues"
available_tools = ["read_file", "list_dir", "context_append"]
max_iterations = 12
system_prompt = """
Do a fast first pass over the code in the `diff` region (and read_file for
surrounding context). Honor any focus in `review_criteria`, and use `discovery`
(this project's layout and conventions, mapped by the discover stage) plus
`project_context` to judge what "correct" means here.
Flag the obvious red flags - don't go deep yet:
- Security: injection, unsafe deserialization, secrets in code, missing authz.
- Correctness smells: unhandled errors, nil/None derefs, off-by-one, dead code.
- Missing input validation, TODO/FIXME markers, and anything that looks risky.
Append the flagged areas (file + one line each) to the `findings` region via
context_append - this is the worklist the deep review scrutinizes.
"""
# Passive reads go to `findings`; everything else stays in conversation.
[stages.scan.tool_routing]
default_region = "conversation"
[stages.scan.tool_routing.overrides]
read_file = "findings"
list_dir = "findings"
[stages.scan.transitions.split_review]
hint = "First pass complete - hand the flagged areas out for review"
transform = "direct"
# Un-exhaustible escape (lint: dead-end-possible): when every looping
# target has spent its max_revisits budget, the run can still move forward
# to the deliverable instead of dead-ending.
[stages.scan.transitions.report]
hint = "The review stages are out of revisits - report the scan findings"
condition = "dead_end"
transform = "compact"
[stages.scan.transitions.error_handler]
condition = "error"
transform = "direct"
# ─── Stage 3: Split review ────────────────────────────────────────────────────
# One worker per area of the change, so forty files get read at once instead of
# one after another - and each area is read by a reviewer whose context holds
# nothing but that area. The workers hand back findings; the deep review merges
# them and does the work that needs the whole picture.
[stages.split_review]
mode = "fan_out"
model = { models = [{ provider = "anthropic", model = "claude-sonnet-5" }, { provider = "openai", model = "gpt-5.4-mini" }, { provider = "google", model = "gemini-3.5-flash" }, { provider = "openrouter", model = "deepseek/deepseek-v4-flash" }, { provider = "ollama", model = "qwen3.5:9b" }] }
description = "Split the change into areas, one reviewer each"
available_tools = []
max_iterations = 4
# split_review ↔ error_handler is a cycle, so it needs a ceiling. One retry is
# enough to route around a transient failure; a second means the change cannot
# be split usefully and the run should review what it has rather than keep
# splitting.
max_revisits = 1
worker_stage = "review_worker"
merge_stage = "deep_review"
max_workers = 4
# An area nobody could review is a gap in the report, not a reason to throw the
# other nine away. The failed worker is named in the results, and the deep review
# is told to cover its area itself.
on_worker_failure = "continue"
# Where the workers' findings land, and the budget their shares divide. A region
# of its own rather than the conversation, which is carrying the message history
# alongside them.
results_region = "worker_findings"
# A ceiling on areas, not just on concurrency. Ten areas is enough breadth for
# any one change, and past that each worker's share of the region is too small
# to carry a usable finding list.
max_items = 10
split_prompt = """
Split this review into areas that can be scrutinized independently, one work
item each. An area is a file, a module, a subsystem, or one coherent group of
hunks - whatever divides the change without two workers reviewing the same
lines. Start from the areas `findings` already flags.
Output ONLY a JSON array, starting with '[' and ending with ']'. No prose, no
markdown fences. Each item:
[{"id": "<short-slug>", "context": {"area": "<the files or hunks to review>", "code": "<the diff hunks for this area, verbatim>", "focus": "<what to look hardest at here>", "conventions": "<what this project calls correct, verbatim from discovery>"}}]
Every item MUST carry its code and the conventions: workers do not share this
agent's context, so the work item is all they get. A worker that does not know
what this project holds itself to can only offer generic advice.
A small change is ONE item, not one per hunk. Split when there is genuinely more
ground than a single reviewer can hold at once.
"""
[stages.split_review.transitions.error_handler]
condition = "error"
transform = "direct"
# ─── Worker: review one area ─────────────────────────────────────────────────
# Entered as a sub-agent, one per area. It hands back findings, which the deep
# review merges. `require_output` is what makes that a guarantee rather than a
# hope. Same models as the deep review: this IS the deep review, on one area.
# No bash here - a worker's hypothesis checks would raise four approval prompts
# at once, so verification stays in the merge, where `workflow`'s VERIFY line is.
[stages.review_worker]
mode = "autonomous"
model = { models = [{ provider = "anthropic", model = "claude-opus-5" }, { provider = "openai", model = "gpt-5.5" }, { provider = "google", model = "gemini-3.1-pro-preview" }, { provider = "openrouter", model = "deepseek/deepseek-v4-pro" }, { provider = "ollama", model = "devstral:24b" }, { provider = "ollama", model = "qwen3.5:9b" }] }
description = "Review one area of the change in depth"
available_tools = ["read_file", "list_dir", "submit_output"]
allow_as_worker = true
require_output = true
max_iterations = 20
system_prompt = """
Review ONE area. Your work item holds the code, the focus, and the conventions
every worker is holding to - it is all the context you get, so work from it.
Run the same framework the deep review does, on your area only:
1. Correctness - does it do what it intends? Trace the logic.
2. Security - injection, authz, secret handling, unsafe input.
3. Performance - hot-path allocations, N+1 queries, needless work.
4. Maintainability - clarity, naming, duplication, conventions.
5. Test coverage - are the risky paths actually tested?
Read the surrounding code with read_file before judging a hunk. A diff read on
its own is how a reviewer reports a bug the caller already prevents.
Submit your findings with submit_output. Recommend fixes; never apply one - no
stage in this agent has a tool that writes, and calling one is refused.
A clean area is a result, not a failure: say so in one line rather than padding
the list. Only your submitted findings reach the merge - anything else you write
is lost.
"""
[stages.review_worker.output]
format = "markdown"
instructions = "One finding per line, most severe first, as `severity | file:line | what is wrong | the failure scenario | the fix`, with severity in {critical, warning, info}. A clean area is the single line `none | <area> | no findings`."
[stages.review_worker.transitions]
# ─── Stage 4: Deep review ─────────────────────────────────────────────────────
[stages.deep_review]
mode = "autonomous"
model = { models = [{ provider = "anthropic", model = "claude-opus-5" }, { provider = "openai", model = "gpt-5.5" }, { provider = "google", model = "gemini-3.1-pro-preview" }, { provider = "openrouter", model = "deepseek/deepseek-v4-pro" }, { provider = "ollama", model = "devstral:24b" }, { provider = "ollama", model = "qwen3.5:9b" }] }
description = "In-depth analysis of correctness, security, and architecture"
available_tools = ["read_file", "list_dir", "bash", "context_write", "context_append"]
max_iterations = 25
max_revisits = 2
system_prompt = """
The area reviewers' findings are in the `worker_findings` region, one block per
area. Start there: fold each finding into `findings` (context_append), drop the
duplicates and anything that reads as generic advice, and check the ones that
would block a merge yourself. A worker reported as FAILED covered nothing - its
area is still unreviewed, so review it here.
Then scrutinize what no single area could show: the interactions between them,
and anything the workers left. Work the review through a fixed framework so
nothing is skipped:
1. Correctness - does it do what the task/diff intends? Trace the logic.
2. Security - injection, authz, secret handling, unsafe input.
3. Performance - hot-path allocations, N+1 queries, needless work.
4. Maintainability - clarity, naming, duplication, conventions.
5. Test coverage - are the risky paths actually tested?
For each finding give: location, the concrete failure scenario (inputs/state →
wrong result or crash), a severity (critical / warning / info), and a recommended
fix. Recommend it - do not apply it. This agent reads and judges; no stage here
has a tool that writes or edits a file, and calling one is refused. Append each finding to `findings` (context_append).
Keep a running tally in the `severity_index` region: rewrite it with
context_write each time the counts change, e.g. "critical: 2, warning: 5, info: 3".
This survives even if the finding detail is compacted, so the report stage always
has the totals.
Use bash only to CONFIRM a hypothesis, never to modify anything - `workflow`'s
VERIFY line holds the command this project actually uses to run one test. A
finding you were able to check beats one you reasoned your way to; say which it
is. Hold the review to `workflow`'s DONE WHEN line.
"""
# Passive reads go to `findings`; bash (hypothesis checks) stays in conversation.
[stages.deep_review.tool_routing]
default_region = "conversation"
[stages.deep_review.tool_routing.overrides]
read_file = "findings"
list_dir = "findings"
[stages.deep_review.transitions.report]
hint = "Analysis complete - write the report"
transform = "compact"
[stages.deep_review.transitions.error_handler]
condition = "error"
transform = "direct"
# ─── Stage 5: Report ──────────────────────────────────────────────────────────
[stages.report]
mode = "autonomous"
model = { models = [{ provider = "anthropic", model = "claude-sonnet-5" }, { provider = "openai", model = "gpt-5.4-mini" }, { provider = "google", model = "gemini-3.5-flash" }, { provider = "openrouter", model = "deepseek/deepseek-v4-flash" }, { provider = "ollama", model = "qwen3.5:9b" }] }
description = "Produce an actionable, ranked review report"
available_tools = ["read_file"]
max_iterations = 10
system_prompt = """
Produce the review report from the `findings` region, structured by the
`severity_index` tally so the counts and the report agree.
Order findings most-severe first and group by severity level:
## Critical - <must fix before merge>
## Warning - <should fix>
## Info - <nice to have>
For each: file and line, one-sentence defect summary, the failure scenario, and the
fix. End with a short overall assessment (ship / ship-with-fixes / needs-work) that
matches the severity_index totals.
If `error_report` says an earlier stage hit its iteration cap, the review was cut
off before covering everything - say so in the overall assessment and list what
went unreviewed.
"""
[stages.report.transitions.summary]
hint = "The review is written"
# ─── Stage 6: Error handler ───────────────────────────────────────────────────
[stages.error_handler]
mode = "autonomous"
model = { models = [{ provider = "anthropic", model = "claude-sonnet-5" }, { provider = "openai", model = "gpt-5.4-mini" }, { provider = "google", model = "gemini-3.5-flash" }, { provider = "openrouter", model = "deepseek/deepseek-v4-flash" }, { provider = "ollama", model = "qwen3.5:9b" }] }
description = "Handle errors encountered during scanning or review"
available_tools = ["read_file", "bash"]
max_iterations = 10
max_revisits = 1
system_prompt = """
An error occurred during review. The `error_report` region holds the error text
the runtime captured - read it first, note a workaround (e.g. skip an unreadable
file and record why), then return to deep_review to continue with the rest.
"""
# Un-exhaustible escape (lint: dead-end-possible): when every looping
# target has spent its max_revisits budget, the run can still move forward
# to the deliverable instead of dead-ending.
[stages.error_handler.transitions.report]
hint = "Recovery is not converging - report what was reviewed"
transform = "compact"
[stages.error_handler.transitions.deep_review]
hint = "Recovered - resume the deep review"
transform = "compact"
[compaction]
provider = "anthropic"
model = "claude-sonnet-5"
# ─── Final output ────────────────────────────────────────────────────────────
# Terminal. `mode = "output"` grants `submit_output`, requires the call, and
# lets the run end here. No file tools on purpose: this stage reports, it does
# not work.
[stages.summary]
mode = "output"
model = { models = [{ provider = "anthropic", model = "claude-sonnet-5" }, { provider = "openai", model = "gpt-5.4-mini" }, { provider = "google", model = "gemini-3.5-flash" }, { provider = "openrouter", model = "deepseek/deepseek-v4-flash" }, { provider = "ollama", model = "qwen3.5:9b" }] }
description = "Say what the review found"
max_iterations = 8
system_prompt = """
Say what this review found, for whoever asked for it.
Lead with whether the change is safe to merge and why. Then the findings that
would block it, and the ones worth fixing later. Name the report file you wrote.
If nothing blocking turned up, say so plainly - a clean review is a result.
"""
[stages.summary.transitions]
# ─── Context layout ───────────────────────────────────────────────────────────
# Budgets are a percentage of the model's context window (issue #100) with an
# absolute `max_tokens` guard-rail. Percentages are ceilings, not reservations.
[context.regions]
# The code/diff to review - required. Supply via `--diff <text|@file>` (CLI),
# a ---region:diff--- block (ACP), or the API `regions` field.
diff = { kind = "pinned", budget = "20%", max_tokens = 20000, required = true, seed = "diff", required_message = "Provide the code or diff to review via --diff <text|@file>." }
# Optional caller focus, e.g. --criteria "focus on security / backwards-compat".
review_criteria = { kind = "pinned", budget = "2%", max_tokens = 3000, seed = "criteria" }
# Pre-loaded on startup: architecture / conventions (missing files skipped).
project_context = { kind = "pinned", budget = "4%", max_tokens = 5000, seed = { files = ["ARCHITECTURE.md", "DESIGN.md", "docs/ARCHITECTURE.md", "CONVENTIONS.md", "CONTRIBUTING.md", "README.md"] } }
# Deterministic repo scan, run once at spawn (issue #108) - the discover stage
# starts from facts instead of burning iterations on `ls`. `git ls-files` behaves
# identically on POSIX and Windows shells; outside a git repo it fails and this is
# simply left empty (non-fatal), and oversized output is trimmed to the budget.
# Refuse it with `--no-seed-commands` or `[security] allow_seed_commands = false`.
repo_files = { kind = "pinned", budget = "3%", max_tokens = 4000, seed = { command = "git ls-files" } }
# ── Discovery (issue #108): written by the discover stage, read by every later
# stage. Pinned, so no edge transform can clear or compact them. `required` puts
# the runtime's own gate behind them (`require_context_regions`): the discover
# stage is re-run with a nudge until it actually fills them.
discovery = { kind = "pinned", budget = "4%", max_tokens = 5000, required = true, required_message = "Populate `discovery` (context_write) with this project's build system, test runner, layout and conventions before leaving the discover stage." }
workflow = { kind = "pinned", budget = "2%", max_tokens = 2000, required = true, required_message = "Populate `workflow` (context_write) with the literal VERIFY / DONE WHEN lines before leaving the discover stage." }
# The area workers' submitted findings, on the way into the deep review. Its
# budget is what each worker's share is divided from, so it gets a slice the size
# of the findings region it feeds. Clearable: once the deep review has folded the
# findings in, the raw worker report is safe to drop.
worker_findings = { kind = "clearable", budget = "20%", max_tokens = 25000 }
# Accumulated review findings and the running severity tally.
findings = { kind = "temporary", budget = "20%", max_tokens = 25000 }
severity_index = { kind = "pinned", budget = "2%", max_tokens = 2000 }
report = { kind = "clearable", budget = "7%", max_tokens = 8000 }
# The message stream. Every blueprint needs an explicit sliding_window
# `conversation` region - the layout that replaces it on each stage transition is
# rebuilt from this table, so an auto-added one would be dropped after stage 1.
conversation = { kind = "sliding_window", max_items = 30, budget = "15%", max_tokens = 18000, strategy = "bulk", overflow = 10 }
# Written by the RUNTIME on an abnormal stage ending (issue #154): a failed
# inference call's error text, or a note that a stage hit its iteration cap.
# Pinned so it survives the edge transform into the stage that acts on it.
error_report = { kind = "pinned", budget = "1%", max_tokens = 2000 }