| name | review-swarm |
| description | Use when a result, manuscript, derivation, or diff needs independent review. Runs clean-room multi-agent review loops across Claude/Gemini/Codex/OpenCode/Kimi with strict review-contract checks, fallback policy, and convergence gates. |
Review Swarm (multi-backend)
This skill provides a reusable clean-room swarm harness for independent reviewers/analysts.
Core capabilities:
- Run N agents with
run_multi_task.py.
- One-command single-reviewer entry with
review_one.py (advisory; packet auto-assembled).
- Mix backends: OpenCode, Claude CLI, Codex CLI, Gemini CLI, Kimi CLI.
- Enforce strict review output contract (optional).
- Opt-in two-phase commit-then-review protocol (
--two-phase).
- Apply fallback policy when a target backend fails/returns invalid output.
- Retry empty-output runs and report unreachable backends (
unavailable_backends) so
a degraded run reads as a backend outage, never as review disagreement.
- Record deterministic artifacts (
trace.jsonl, meta.json, outputs).
- Gate on convergence (optional Jaccard similarity).
Canonical entrypoint
Use scripts/bin/run_multi_task.py for all new multi-reviewer workflows, and
scripts/bin/review_one.py for a one-command single-reviewer pass (it delegates
into run_multi_task.py — one orchestration path).
Primary public skill name: review-swarm.
Use review-swarm consistently in documentation and automation references.
Requirements
Install runner skills for any backends you plan to use:
opencode-cli-runner (for OpenCode backend)
claude-cli-runner (for claude/... models)
codex-cli-runner (for codex/... models)
gemini-cli-runner (for gemini/... models)
kimi-cli-runner (for kimi/... models)
CLIs should be available in PATH according to the chosen backends.
Host-aware execution (your own family runs native; quality first)
run_multi_task.py shells out to CLIs — that is for CROSS-family reviewers: the families you list in
--models should be families OTHER than the one you (the driving agent) run as, so every reviewer is
genuinely independent of you. Your own family reviews natively in-host, not through its own CLI — never list
your own family in --models just to aggregate. Host capabilities VARY; gate on what your host exposes:
- Single-family review → keep it in-host, not via that family's CLI. If you only need YOUR family's
reviewer (no cross-family swarm aggregation), run it in-host: use a native child-agent/sub-agent
primitive if your host has one (your host's sub-agent / Agent-Task mechanism; OpenCode subagents), else run it inline
in your own loop — don't
claude exec a model you are already running as (latency, separate
auth/session, context loss). Plain Claude Desktop / the Gemini CLI may have no sub-agent primitive →
inline.
- Cross-family swarm → all reviewers go through
run_multi_task.py (honest caveat). Its convergence
/ contract aggregation is computed over the runner's OWN output files, so a natively-run same-family
reviewer would not be in the swarm. Getting one unified multi-backend verdict therefore means your own
family also goes through its CLI here — that hop is the price of in-process aggregation. Use the swarm
when you need cross-MODEL review; do single-family reviews natively.
- Reasoning effort scales with review difficulty — quality first, not token thrift. High-stakes,
cross-package, or security-sensitive reviews warrant maximum thinking (extended thinking / high–xhigh
reasoning effort / a stronger model); trivial diffs do not. Never accept a missed defect to save tokens.
- For a long/expensive swarm, prefer a steerable background task chip (e.g. your host's spawn-task / sub-agent launch)
the user can inspect and adjust mid-run, when the host supports one; otherwise run inline and
checkpoint. Capability varies by host — degrade gracefully.
Agents file and honest degradation (below the cross-family minimum)
A machine may carry a third-party agents file (project level .nullius/agents.json, else user level
~/.nullius/agents.json; format and discovery order in the repository page docs/AGENTS_FILE.md)
mapping model families to execution routes and model strings. When one is present,
run_multi_task.py accepts family:<name> and family:<name>:<tier> model specs and resolves them
through the file, and its meta.json gains an independence record: which families actually produced
output, at which level (cross-family / single-family / none), which declared families were absent,
and whether the machine sits below the file's cross-family minimum. A missing file changes nothing —
explicit model specs keep working exactly as before.
The launcher only records; the degradation decision is yours (the agent driving this skill):
- If the file declares fewer usable families than its cross-family minimum, or a
family: request
fails because that family is declared unavailable or its runner executable is absent, do NOT
substitute another family and do NOT silently shrink the review. Follow the file's policy value
(native subagents): run the review as a panel of your host's own subagents — multiple instances
with distinct review perspectives (correctness, numerics, portability, ...).
- Label the outcome honestly in whatever you hand back: state the independence level (single-family)
and list the absent families, so a degraded round is never mistaken for a cross-family one.
Single-model agreement is the floor; cross-family agreement is the ceiling.
- A family served by a native runner (normally your own) always runs in-host, never through a CLI
(see Host-aware execution above).
Quick start (single reviewer, one command — advisory)
For one independent reviewer from another family, review_one.py assembles the
whole packet for you (no hand-written prompt files). A single reviewer is one
model family, so treat its verdict as advisory only — final verdicts require
cross-family review (the packet itself carries that caveat as its first line):
python3 scripts/bin/review_one.py \
--model codex/default \
--artifact /path/to/notes.md \
--role correctness
See "Single-reviewer entry" below for the full flag set.
Quick start (cross-family review: families other than your own)
The swarm exists to add reviewers from model families OTHER than the one you run as. List only the OTHER
families in --models; run your own family natively in-host (see Host-aware execution above). Choose the
list by your host — currently wired backends are claude/..., codex/..., gemini/..., kimi/..., and any OpenCode
provider/model (e.g. zhipuai-coding-plan/glm-5.2 for GLM):
- From a Claude host →
codex/..., gemini/..., zhipuai-coding-plan/glm-5.2 (not claude/...).
- From a Codex host →
claude/..., gemini/..., zhipuai-coding-plan/glm-5.2 (not codex/...).
- From an OpenCode/GLM host →
claude/..., codex/..., gemini/... (not the OpenCode lane).
Example, driven from a Claude host (three non-Claude reviewers):
python3 scripts/bin/run_multi_task.py \
--out-dir /tmp/cross_family_review \
--system /path/to/reviewer_system.md \
--prompt /path/to/packet.md \
--models codex/default,gemini/default,zhipuai-coding-plan/glm-5.2 \
--check-review-contract
Choosing the reviewer set: your own (host) family reviews natively in-host; every
OTHER family you want goes through --models. There is no automatic rotation
worth relying on — pick the families explicitly.
Single-reviewer entry (review_one.py)
scripts/bin/review_one.py is the one-command way to get a single independent
reviewer with zero hand-written files. It builds the system prompt from a role
template, embeds your artifacts (or a git diff) into an auto-assembled packet,
then delegates to run_multi_task.py (same directory), inheriting runner
discovery, per-backend read-only tool modes, the process-group timeout,
trace.jsonl/meta.json artifacts and contract checking. Its delegated run
also opts in to one orchestrator-level empty-output retry
(--retry-empty-output 1; the launcher default is 0).
python3 scripts/bin/review_one.py \
--model gemini/default \
--artifact /path/to/extraction_note.md \
--source /path/to/exact_source_excerpt.tex \
--source-text-origin direct-original-text \
--correction-status checked-corrections-included \
--correction-source /path/to/exact_erratum_excerpt.tex \
--correction-search-evidence /path/to/correction_search_record.md \
--role source-fidelity \
--out-dir /tmp/one_review
For the candidate-withheld pass that must precede comparison of a load-bearing
source item, use the separate role. Its packet structure accepts a neutral
request but rejects candidate artifacts, diffs, and additional context:
python3 scripts/bin/review_one.py \
--model gemini/default \
--extraction-request /path/to/neutral_questions.md \
--source /path/to/visually_transcribed_source.txt \
--source-text-origin visually-verified-transcription \
--source-provenance-evidence /path/to/page_crop_check.md \
--correction-status checked-none-found \
--correction-search-evidence /path/to/correction_search_record.md \
--role source-extraction \
--out-dir /tmp/source_extraction
Flags:
--model SPEC — required, exactly one, no default (codex/default,
gemini/default, kimi/default, claude/<model>, or an OpenCode
provider/model).
--artifact PATH (repeatable), --diff BASE..HEAD, or
--extraction-request PATH — mutually exclusive packet inputs. The first two
are review targets. The last is a neutral locator/question list accepted only
by source-extraction.
- A
--diff packet contains changed hunks, not every unchanged declaration or
invariant they depend on. Add the full relevant files with repeatable --context
flags when a correctness verdict may depend on surrounding definitions. A reviewer must
not infer that an implementation element is absent merely because it is not
visible in the diff.
--role generic|correctness|execution-adversary|source-extraction|source-fidelity — picks the
system prompt from templates/<role>.md (default: generic). Each template
embeds the required review-contract output format.
--source PATH (repeatable) — exact UTF-8 text/LaTeX/Markdown primary-source
input for the two source roles. They fail closed before model
launch when no source is supplied, when source and target are the same file,
or when the source is binary/non-UTF-8. A PDF/image-capable reviewer must
first check PDF/scan passages against the original rendered page. Persist
the visually verified excerpt, locator, original-page hash, and crop hash;
lossy automatic PDF decoding is not formula-level evidence. A later
text-only pass checks note against excerpt, not excerpt against original page.
--source-text-origin direct-original-text|visually-verified-transcription —
required for both source roles. This prevents a manually normalized extract
from being silently treated as publisher/repository bytes. The declaration is
recorded but not machine-proven.
--source-provenance-evidence PATH (repeatable) — exact UTF-8 record of the
original document/page/crop locators and hashes plus the visual comparison.
Required for visually-verified-transcription. It must be separate from the
source and correction-search files. The launcher verifies presence, separation,
UTF-8 validity, and hashes; the reviewer must still inspect it for source-layer
normalization, answer leakage, and lack of an independent visual verifier.
--correction-status not-applicable|checked-none-found|checked-corrections-included
— required for both source roles; it prevents silent reliance on an
unchecked main publication. Use not-applicable only when a correction chain
is genuinely inapplicable, not as a shortcut for an unperformed search.
--correction-source PATH (repeatable) — exact UTF-8 correction text. It is
required with checked-corrections-included, rejected with the other statuses,
and persisted separately from the primary source in the packet and manifest.
--correction-search-evidence PATH (repeatable) — exact UTF-8 record of the
indexes/identifiers searched and their result. It is required for both
checked-none-found and checked-corrections-included, so a negative or
positive correction-chain claim cannot be represented by an unsupported flag.
--extraction-request PATH — exact UTF-8 neutral questions/locators for
source-extraction. That role rejects --artifact, --diff, and --context
to keep the candidate and prior verdict out of the packet by construction.
The launcher cannot semantically prove that the request is neutral, so the
reviewer must block if it contains an expected answer or proposed correction.
--context PATH — optional extra material appended to the packet. Repeatable;
every file is embedded and recorded in command-line order. Duplicate paths are
rejected instead of being silently collapsed, and a context path identical to
an --artifact target is rejected for every reviewer role.
--out-dir DIR — defaults to ./review-one-<UTC timestamp>/; the assembled
inputs are persisted under <out-dir>/inputs/ for audit. Every run also writes
inputs/review_input_manifest.json, which records the SHA-256 of each target,
primary source, correction source, correction-search record, source-provenance
record, context, neutral extraction request, persisted system prompt, and
persisted packet, plus the exact diff hash when applicable.
--host-family FAMILY — pass your own family to make the entry refuse when
--model resolves to it, pointing you to host-native sub-agent review instead
(mirrors "Host-aware execution" above).
--max-prompt-bytes N / --max-prompt-chars N — the launcher's prompt-size
guard applied to the assembled inputs; an oversize packet is refused with the
guard's message (no silent truncation).
--use-project-config — opt back into .nullius/review-swarm.json
auto-discovery; by default the entry sets REVIEW_SWARM_NO_AUTO_CONFIG=1 so
runs are hermetic. With the flag, REVIEW_SWARM_NO_AUTO_CONFIG is removed for
the delegated run even when the caller's environment already sets it (the
prior value is restored afterward), so the opt-in works under a suppressing
environment too.
--timeout-secs, --backend-tool-mode, --<backend>-runner — forwarded to
the launcher unchanged.
The assembled packet's first line is the advisory caveat ("single-family review —
advisory; final verdicts require cross-family review"). When the reviewer returns,
the launcher recomputes every manifest hash, writes post_review_freshness.json,
records the status in meta.json, and prints acceptance_status with the model
verdict, contract_ok, and the output/packet/meta/trace paths. A successful model
verdict with stale or unverifiable inputs exits with the dedicated status 86; the
model verdict remains historical evidence, not a verdict on the changed artifact.
This immediate FRESH status means only that the inputs did not change while that
review ran. Durable closeout still requires rerunning the standalone checker immediately
before the verdict is folded into a conclusion.
Backend overrides
run_multi_task.py supports per-backend overrides:
--backend-prompt backend=/path/to/prompt
--backend-prompt @/path/to/overrides.json (batch mode)
--backend-system backend=/path/to/system or backend=none
--backend-output backend=relative_or_absolute_path
--backend-tool-mode backend=mode
--timeout-secs N
Notes:
- These flags are repeatable.
--backend-prompt @json supports:
- shorthand prompt map:
{"gemini": "/path/to/gemini_prompt.txt"}
- batch object:
{"prompt": {...}, "system": {...}, "output": {...}}
- Relative
--backend-output paths are resolved under --out-dir.
claude=none for --backend-system is rejected (Claude runner requires a system prompt file).
- For a single run,
--backend-output does not allow one path for repeated same-backend agents (to avoid output clobbering).
--timeout-secs is a per-backend hard timeout. Default: 900 seconds. Use 0 to disable.
--backend-tool-mode is explicit and backend-specific:
claude=none|review
gemini=none|review
opencode=none|workspace
Reviewer Tool Modes
Default behavior is explicit:
- Claude, Gemini, and OpenCode now receive an explicit tool mode from
review-swarm.
- The default mode is
none for all three backends.
- Tool access must be opted into per backend with
--backend-tool-mode.
Reviewer-safe modes:
claude=review: maps to a read-only built-in tool profile (Read,Glob,Grep).
gemini=review: maps to Gemini CLI --approval-mode plan plus local CLI execution (--no-proxy-first), sandboxing, and --extensions none, which is Gemini's read-only review path.
- When Gemini is in
review mode and --gemini-cli-home was not explicitly set, review-swarm now synthesizes an isolated GEMINI_CLI_HOME under the run output directory and writes a minimal user settings file there (mcpServers={}, mcp.allowed=[]) to avoid inheriting reviewer-external user MCP state by default.
- Gemini
review is a headless review path, not the same interaction mode as the Gemini TUI /mcp session. If this path emits MCP discovery noise or does not yield a usable source-grounded verdict on a large packet, prefer a same-model rerun with an embedded-source packet and gemini=none rather than assuming TUI MCP health guarantees headless review stability.
OpenCode caveat:
opencode=workspace explicitly grants workspace visibility by passing --workspace-dir.
- For formal workspace reviews, prefer OpenCode's official headless-server flow (
opencode serve + opencode run --attach ...) rather than relying only on repeated direct run --dir ... cold starts.
- Current
opencode run CLI does not expose a built-in read-only tool allowlist comparable to Claude/Gemini, so workspace is explicit workspace access, not a hard no-mutation guarantee.
- For
opencode=workspace, prefer workspace-relative file paths in prompts/packets. Large prompts that enumerate absolute workspace paths or globs can push the model into external_directory permission requests even when the repo itself is mounted as the workspace.
- Treat
OpenCode workspace and OpenCode embedded-source as two different review roles:
workspace: packet-challenge / discovery reviewer. Best when blast radius or hidden front-door / consumer drift is still uncertain.
none + embedded-source packet: verdict-normalization / formal gate reviewer. Best when scope is already narrowed and you need a stable closeout artifact.
- Do not treat an OpenCode workspace pass as "failed" just because the output includes exploratory text or lacks a clean final JSON block. If it still contains source-grounded, current-worktree findings, keep that review signal and only rerun same-model to normalize the gate artifact.
- For formal reviewer use, prefer Claude/Gemini for source-grounded read-only review guarantees; treat OpenCode workspace mode as discovery-strong but gate-fragile, and reserve embedded-source OpenCode passes for final formal-verdict stabilization once packet scope is adequate.
- When packet scope touches public/package/CLI/workflow/default-entry surfaces, also follow the
Front-door Surface Audit requirement in AGENTS.md; runner setup does not replace packet widening.
Execution adversary (mandatory for correctness-critical / method-precondition reviews)
A read-only review is a static read; it cannot confirm a runtime property. When a review must establish
that a method's load-bearing precondition actually holds — an operator identity (commutation with a
projector/symmetrizer, Hermiticity, self-adjointness, idempotency, unitarity, variational/Galerkin-subspace
invariance), a numerical invariant, or a true-operator eigen-residual — at least one reviewer must take an
"execution adversary" role: load the artifact and execute the disconfirming test at the production
scale/configuration, not statically read the code. Give that reviewer real execution access (a host-native
sub-agent with run/Bash, or a sandbox that can execute), and record in meta.json whether each reviewer
executed vs. only read the precondition checks. A swarm in which no reviewer executed the precondition
is a static-only swarm and must be labeled as such — it does not count as a precondition pass. (A
static read can certify code shape; only execution at the production scale can certify that a discretized /
implemented property actually holds — a property can read as correct and still fail numerically above the
minimal size.)
Source-fidelity reviewer (mandatory for transcription / source-extraction artifacts)
A source-extraction / transcription note — a deep-read / knowledge-base note that transcribes
equations, numeric values, source locators, and term-by-term mappings onto a consuming artifact from a
primary source — is a valid gate target, not a gate-exempt "reading task." Its primary observable is
fidelity to the source, so the review is a different shape from a code/design review: at least one
cross-model-family reviewer must do a LITERAL, line-by-line comparison of the note against the primary
source with "do not trust the note." Loose semantic agreement is insufficient — transcription drift (a
flipped sign, a dropped magnitude factor, a transposed digit, a stale locator, or a stale mapping to the
consuming artifact) reads as plausible and is caught only by literal comparison. Reviewer model-family
diversity materially strengthens this gate: a same-family looser read tends to pass exactly the defects it
is meant to catch.
Give that reviewer the persisted primary source (the exact text that was transcribed), not the note
alone, plus the transcription/extraction failure checklist (research-integrity → Extraction /
transcription fidelity). Declare whether those bytes are direct original text or a visual transcription;
the latter also requires separate source-provenance evidence. With review_one.py, first run
source-extraction with repeatable --source, an exact --extraction-request, explicit
--source-text-origin, explicit --correction-status, and the required correction-search
evidence. This role rejects candidate artifacts, diffs, and additional context and writes
inputs/source_extraction_manifest.json. Then run the candidate-visible source-fidelity comparison,
passing the candidate via --artifact/--diff, the same source separately through repeatable --source,
and each applicable correction through repeatable --correction-source. The launcher writes
inputs/source_fidelity_manifest.json with primary/correction/search-evidence and target hashes, diff range
when applicable, ordered additional-context paths and hashes, correction status, and candidate visibility. Context content is
explicitly marked as not machine-classified because the launcher cannot know whether it contains a prior
verdict or proposed answer. Both roles fail closed before model launch if required source, correction,
or correction-search evidence is absent or invalid. Both manifests mark source dependency closure as
not_machine_verified, and the extraction manifest marks request neutrality not_machine_verified (the
fidelity manifest records it as not_applicable, since that pass carries no neutral request); the reviewer,
not the packet assembler, must decide whether the question leaks an answer and whether all required
domain/branch/convention premises are present.
The extraction input manifest certifies only that a candidate-withheld packet was constructed; it does not
claim the extraction was completed. Completion belongs to the reviewer output and meta.json verdict.
The source set must include the applicable correction chain. For a load-bearing paper,
record whether an erratum, corrigendum, expression of concern, retraction, revised
version, or later explicit author correction was searched and found. If a correction
supersedes a formula or statement, give the reviewer both the printed source and the
correction, and require the artifact to distinguish the pre-correction and corrected
forms. A literal match to the original but superseded text is not a fidelity pass.
The reverse case needs equal care: a search that found no erratum does not certify the
printed source as scientifically correct. If an equation or statement appears internally
inconsistent, preserve the exact printed form and open a separate suspected-source-error
record. Keep PRINTED_FORM/SOURCE_FIDELITY_STATUS distinct from
SCIENTIFIC_STATUS/PROPOSED_CORRECTION/EVIDENCE_AND_COUNTERCHECKS/
DOWNSTREAM_POLICY. Source-fidelity checks the first track only. For a re-derivable item,
the second track needs candidate-withheld, method-diverse re-derivation through
derivation-verify, with every premise checked to exclude downstream consequences of the
disputed item. For an empirical or reported numerical item, it needs independent
reproduction from the underlying data, calculation, or measurement; downstream sources
that merely repeat the claim are not independent. For a non-derivable convention, label,
or prose statement, require independent primary records, internal definitional closure,
or author clarification; repetition and citation count are not votes. Agreement with a
conjectured repair does not turn it into source text, a published correction, or an
author-confirmed correction. If the repair is not unique, retain
unresolved_source_error and block silent downstream selection.
The manifest distinguishes each original file's SHA-256 from the SHA-256 of the UTF-8 text embedded in the
packet. Primary-source text is strict UTF-8 and is inserted without trimming, so those two hashes agree;
artifact/context decoding may use replacement characters and therefore records both hashes explicitly.
Diff targets likewise record raw and embedded-text hash fields for schema symmetry; because git diff
is decoded and embedded as the same UTF-8 text, those two values currently agree.
Each entry also records whether one delimiter newline was added after a payload that lacked a terminal
newline; that delimiter is packet framing and is not part of the embedded-text hash.
Keep four layers separate in both the packet and the verdict: literal source, normalized
transcription, symbol dictionary, and derived mapping/inference. A correct derivation from a
wrongly transcribed formula does not certify the transcription. Require the reviewer to localize the first
divergent source token or mapping step rather than only compare final formulas.
Conflicting visual transcriptions do not become source truth by vote. Reinspect the original page or a
lossless crop; if the surviving pixels still do not distinguish the disputed glyph, keep the inventory item
AMBIGUOUS and require the candidate artifact to preserve that uncertainty. Do not collapse it to the
mathematically expected sign, exponent, relation operator, or endpoint convention.
An exhaustive source-fidelity claim needs an exhaustive comparison surface. If the verdict says that all
formulas in a paper or section were checked, persist a locator inventory of every displayed equation and
every adjacent formula-like condition in that scope, with an item-level status. A selected-item pass cannot
be promoted to complete coverage. In either scope, explicitly sweep signs, coefficients or factors,
operators, indices or subscripts, relation operators or interval endpoints, numerator/denominator
placement, exponents, primes, function arguments, and integration limits; algebraic or dimensional
consistency is a separate check.
Model-family independence, method independence, and input/framing independence are different axes. A
cross-family reviewer that sees the same faulty note, prior verdict, or proposed correction is not an
input-independent extraction. For a load-bearing source item, first obtain at least one
candidate-withheld extraction with the source-extraction role; only then show the candidate note for
the literal source-fidelity comparison. The extraction manifest records
candidate_visibility=withheld_by_packet_structure, while also stating that request neutrality is not
machine-verified. Feedback iterations state a discrepancy and source locator as a hypothesis; they must
not feed the desired corrected formula back as an assumption.
Record whether a literal cross-family source comparison was performed; a swarm that only read the note,
stayed within one model family, or shared one candidate-anchored input without a candidate-withheld pass is
not a full fidelity pass and must be labeled as such.
For PDF or scanned sources, a text-only packet cannot establish source fidelity by itself. The gate also
requires a PDF/image-capable reader to compare the original rendered page (or a lossless page crop) with
the persisted excerpt. Record both the original file/page hash and crop hash. OCR or pdftotext output is
a discovery aid only; agreement between the note and OCR cannot certify a formula that OCR misread.
Artifact-integration reviewer (for rendered research artifacts)
When a workflow turns source-read notes into a rendered artifact — for example an interactive literature
graph, slide deck, dashboard, or browsable note bundle — include at least one reviewer whose task is to
inspect the current rendered artifact and its source files, not merely the synthesis prose. This reviewer
checks integration failures that source-fidelity review alone cannot see: broken relative links, missing
images, unrendered math, non-clickable connected references, stale note paths, layout collisions, and a
renderer that displays placeholders or filenames instead of the intended evidence.
Write reusable workflows in terms of reviewer roles and capabilities, not specific model names. A concrete
run may choose particular models, but the skill or project contract should say "independent cross-model
artifact reviewer" or "source-fidelity reviewer" unless a user explicitly pins a model for that run.
After any artifact fix, rerun the reviewers on the fixed artifact before calling convergence.
Reference-reproduction reviewer (mandatory for "matches / reproduces a published value" claims)
A claim that a result reproduces / matches / agrees with a published reference value is a quantitative
claim a static read cannot certify — reading the prose only confirms the prose. When a packet asserts such a
match, at least one reviewer must take a "reference-reproduction" role and cover two distinct dimensions
that a correctness / methodology / honesty review routinely passes over:
- D1 — recompute and compare. Compute the claimed observable on a comparable state / regime /
configuration and compare to the published number numerically — do not accept a qualitative "same order
of magnitude / same sign / right scale" assertion, and do not accept the citation as if citing the source
proved the match. Compare term by term where the claim is term-level (a net total can agree while
individual contributions are suppressed or sign-flipped). An order-of-magnitude same-direction
discrepancy, or a sign reversal, is a BLOCKING finding, not a pass. Give this reviewer real execution
access (a host-native sub-agent with run/Bash, or an executing sandbox) when the comparison requires
computation.
- D2 — the independent cross-check did not silently lapse. Confirm that any cross-validation evaluates
the same model by a different route. A structurally different-model engine, or a check valid only in
a degenerate / limit regime, must be labeled as a different-model / limit-regime comparison, not
presented as validation; and when no apples-to-apples independent check is feasible, the absence is
recorded as an explicit stated limitation rather than an established cross-check being allowed to
silently disappear.
Record in meta.json whether a reviewer computed-and-compared vs. only read the match assertion; a
swarm in which no reviewer recomputed the claimed observable on the comparable state is a static-only
swarm for that claim and must be labeled as such — it does not count as a reference-match pass.
Cross-model-family diversity strengthens this gate. Pair it with numerical-reliability-gate G8 (the
compute-and-compare gate, returning reference_mismatch on an order-of-magnitude or sign gap) and the
research-integrity Reference-reproduction fidelity dimensions.
Independent-recomputation reviewer (mandatory for load-bearing structural claims)
A cross-family review that only re-reads evidence supplied by the claimant is an argument audit, not a
verification: its "confirm" can sit entirely inside the claimant's blind spot, because the supplied
evidence may probe the wrong axis altogether. The reference-reproduction reviewer above is the special
case where a published number anchors the check; the general contract covers any load-bearing
structural claim — a claim about what a computed object is (its shape, degeneracy structure, scaling,
sign structure, convergence behavior) on which downstream conclusions rest. For such a claim, at least
one reviewer must independently recompute the quantity through a different route (a different
discretization, implementation, or representation), receiving only the problem statement and the raw
inputs — never the claimant's answer, evidence selection, or initial judgment. Record in meta.json
which axis the independent recomputation actually probed (what it could have falsified); a
recomputation whose probed axis does not intersect the claim's load-bearing axis leaves the swarm
static-only for that claim, and it must be labeled as such rather than counted as a verification pass.
Model selection
--models a,b,c: explicit model specs — the recommended path (host family native,
every other family listed here explicitly).
--agents N: rotate through available OpenCode config models (legacy convenience;
prefer explicit --models for reviewer runs).
--model default: one OpenCode agent, CLI default model.
- Mixed backends supported:
claude/..., codex/..., gemini/..., kimi/..., OpenCode provider/model.
Default-model policy (hard rule)
When model is omitted or set to default, do not inject historical model names.
Always delegate to each backend CLI's configured default model.
This rule applies to all backends:
- OpenCode
- Claude CLI
- Codex CLI
- Gemini CLI
- Kimi CLI
Fallback policy
Fallback can be enabled for explicitly chosen target backends:
--fallback-mode off (default)
--fallback-mode ask (exit code 4, asks for rerun decision)
--fallback-mode auto (tries --fallback-order, default codex,claude)
--fallback-target-backends a,b — required whenever fallback is enabled.
There is no default target list: enabling ask/auto without naming targets
is an input error, so a substitute reviewer can never be swapped in for a
backend you did not explicitly nominate.
Example:
python3 scripts/bin/run_multi_task.py \
--out-dir /tmp/cross_family_review \
--system /path/to/system.md \
--prompt /path/to/prompt.md \
--models codex/default,gemini/default,zhipuai-coding-plan/glm-5.2 \
--check-review-contract \
--fallback-mode auto \
--fallback-target-backends gemini \
--fallback-order codex,claude
Backend outage handling (retry + availability report)
Backend infrastructure failures are classified and handled distinctly from
review content, so a degraded run is legible as "backend down" rather than
mistaken for reviewer disagreement:
- Every agent result carries a
failure_class: infrastructure (timeout,
crash exit, empty output — the backend never delivered reviewable content) or
content (the backend answered but the answer failed a protocol check, e.g.
an invalid two-phase criteria block). Successes carry null.
--retry-empty-output N (default 0 — disabled, so existing callers of the
launcher keep their behavior; review_one.py passes 1 explicitly for its
delegated run): an agent whose runner exited 0 but wrote an empty output file
is re-run at the orchestrator level up to N times — before fallback is
considered and before the agent is recorded as empty_output. Retried agents
record empty_output_retries in meta.json. In two-phase mode a phase-1
empty output (phase1_empty_output) is classified infrastructure but is NOT
auto-retried by this flag, and --two-phase rejects fallback — recover it
with a manual same-model rerun (see the two-phase section).
meta.json always includes unavailable_backends: the requested model specs
whose runs ALL failed at the infrastructure level this invocation (a
fallback-recovered run counts by its original failure). Read entries there as
backend outages; never fold them into a review verdict.
Prompt-size guardrail (optional)
--max-prompt-bytes N or --max-prompt-chars N
--max-prompt-overflow fail|truncate
When enabled, guardrails apply to global inputs and backend override inputs.
Convergence check
python3 scripts/bin/run_multi_task.py \
--out-dir /tmp/multi_review \
--system /path/to/system.md \
--prompt /path/to/task.md \
--models codex/default,gemini/default,zhipuai-coding-plan/glm-5.2 \
--check-convergence \
--convergence-threshold 0.8
Re-review after every fix (gate-loop discipline)
Convergence is a property of the reviewers' agreement on the current artifact, never a
self-pronouncement after applying a fix. The gate loop is review → fix → re-run the independent
reviewers on the fixed artifact → repeat, and it converges only when the reviewers themselves return
clean. Re-review after every correction round, including ones that look trivial or single-line: a fix
can introduce a new defect — a corrected transcription line that silently drops a magnitude factor, or
a refactor that re-breaks an invariant — that exists only after the fix and is caught only by the next
independent round. Skipping the confirmation round because the change "obviously" closed the finding is the
failure mode this rule exists to stop. The leader integrates and decides, but does not declare
convergence in place of the reviewers.
The verdict is scoped to the exact hashes in inputs/review_input_manifest.json.
Before counting an older run toward a later convergence or closeout, rerun the
freshness check:
python3 <nullius-root>/skills/review-swarm/scripts/bin/verify_review_freshness.py \
--review-dir /path/to/review-out
Require status: FRESH. A target, source, correction record, context, system prompt,
packet, or diff mismatch is STALE, even if the edit is one character and the prior
model output says READY. Re-review the full current artifact or the complete exact delta
from the reviewed hash. Running the checker once does not authorize later edits; any
post-check mutation invalidates the acceptance state.
The standalone checker returns 0 for FRESH, 3 for STALE, and 2 for an invalid
or unreadable manifest. The review_one.py entry instead returns 86 when its model run
succeeded but acceptance is STALE or UNVERIFIABLE, so it remains distinguishable from
the delegated model result. Automation should treat the JSON status as authoritative
rather than assuming both entry points share one numeric convention.
The manifest is a trusted launch record, not a cryptographic signature over itself.
This gate catches accidental or uncoordinated post-review drift. If deliberate co-editing
of both an input and its manifest is in scope, commit the review directory or persist the
reported manifest SHA-256 in an external closeout record before accepting the verdict.
Contract checking (informational)
--check-review-contract validates output format compliance and records results in meta.json.
Contract failures are informational only — they never trigger fallback. Content matters more than format.
If you want models to output a specific format, include format instructions in your system/user prompt.
Standalone checker:
python3 scripts/bin/check_review_output_contract.py /tmp/dual_review/claude_output.md
Contract auto-detects output format:
- Markdown:
VERDICT: READY/NOT_READY first line + required headers (## Blockers, etc.)
- JSON: Valid JSON object with
blocking_issues (array), verdict (PASS/FAIL), summary
JSON outputs wrapped in markdown code fences (```json ... ```) are automatically unwrapped.
Two-phase review protocol (opt-in)
Default reviews are single-phase: each reviewer sees the full packet (diff included) in one
call. --two-phase adds an opt-in commit-then-review protocol for formal reviews where two
documented multi-agent failure modes matter: a reviewer improvising its evaluation standard
only after seeing the diff, and persuasive phase-2 prose substituting for the standard it
would have committed to up front. Single-phase behavior is completely unchanged when the
flag is absent.
When to use: formal review of high-risk or irreversible public-surface changes —
cross-package contract changes, default-entry behavior, anything where the project already
requires independent formal review. Routine incremental diffs do not need it.
How it works:
- Phase 1 — criteria commitment. Each reviewer receives a scope packet only — change
title, intent, and the changed-file list, with the diff deliberately withheld
(
--scope-prompt file, prepared by the caller). The reviewer must declare the review
criteria it commits to: exactly one block wrapped in <review_criteria> /
</review_criteria> sentinel lines, containing a JSON object with a non-empty
categories array (each entry: a name plus a one-sentence blocking_criteria) and a
severity_scale sentence.
- Phase 2 — review per committed criteria. The same reviewer is called again with the
full diff packet (the normal
--prompt / per-backend override) plus its own phase-1
criteria block, verbatim. Every BLOCKING finding must carry a declared category:
a [<category>] bullet prefix under ## Blockers in Markdown output, or a category
field / [<category>] string prefix on blocking_issues entries in JSON output.
- Criteria revision. If the diff reveals a problem class outside the declared
categories, the reviewer may add a category only with an explicit revision declaration:
a
CRITERIA_REVISION: <category>: <one-line reason> line in Markdown output, or a
criteria_revisions array entry (category + reason) in JSON output. The machine
check verifies the declaration exists and is well-formed; judging whether the reason is
any good stays with the synthesis agent.
Conformance is machine-checked after phase 2 (same code path as
check_review_output_contract.py --two-phase PHASE1_FILE PHASE2_FILE): a BLOCKING finding
whose category is neither declared nor covered by a revision declaration is a conformance
failure. Like the single-phase contract check, conformance failures are informational —
recorded per agent in meta.json under two_phase (conformance_ok,
conformance_errors), never a fallback trigger. Phase-1 failures are different: if the
phase-1 call fails or returns no parseable criteria block, phase 2 is skipped and the agent
is marked failed (phase1_command_failed, phase1_empty_output, or
phase1_criteria_invalid); rerun that reviewer same-model. A phase-1 empty output is
classified as an infrastructure failure but is NOT auto-retried by --retry-empty-output,
and two-phase rejects fallback — recovery is that manual same-model rerun.
python3 scripts/bin/run_multi_task.py \
--out-dir /tmp/formal_review \
--system /path/to/reviewer_system.md \
--scope-prompt /path/to/scope_packet.md \
--prompt /path/to/diff_packet.md \
--models codex/default,gemini/default,zhipuai-coding-plan/glm-5.2 \
--two-phase \
--check-review-contract
Notes:
- The scope packet must not contain the diff; keeping it to the change title, intent, and
changed-file list is the caller's responsibility.
--scope-prompt is global (no per-backend override); per-backend --backend-prompt
overrides apply to the phase-2 diff packet as usual.
--two-phase rejects --fallback-mode ask|auto: silently substituting a different
backend mid-protocol would break the commitment chain. Use a same-model rerun instead.
- Two-phase is a per-invocation CLI opt-in only; it is deliberately not settable from the
project config file, so a config can never silently flip a default run into two-phase.
- In Markdown phase-2 output, findings under
## Blockers must be bullets. Untagged
indented lines are continuations of the bullet above; an indented bullet that itself
carries a [<category>] tag counts as a finding (nesting is not an evasion channel);
column-0 prose other than a no-blocker placeholder is flagged as unstructured content.
- Phase transcripts stay auditable: composite prompts live under
{out-dir}/two_phase/,
phase-1 outputs sit next to the final outputs with a .phase1 suffix (both paths are
cleared of stale files from previous runs at the start of a two-phase run).
Outputs
{out-dir}/agent_*_*.txt (or backend output override paths)
{out-dir}/trace.jsonl
{out-dir}/meta.json (per-agent results incl. failure_class, plus the
unavailable_backends outage report)
- With
--two-phase: {out-dir}/two_phase/ (composite phase prompts) and *.phase1.*
phase-1 outputs next to the final outputs.
- With
review_one.py: the assembled inputs persist under {out-dir}/inputs/
(system.md, packet.md) for audit.
Runner parity notes
System prompt delivery
All backends now receive the system prompt by default. However, the delivery mechanism differs:
| Runner | Delivery | True system role? |
|---|
| claude-cli-runner | --system-prompt native arg | Yes |
| codex-cli-runner | Merged into stdin (=== System Instructions === + === Task ===) | No — prepended to user message |
| gemini-cli-runner | Concatenated into stdin (system + \n\n + prompt) | No — prepended to stdin |
| opencode-cli-runner | Concatenated into stdin (same as gemini) | No — prepended to stdin |
| kimi-cli-runner | --system-prompt-file prepended to the prompt file | No — prepended to user message |
Only Claude CLI uses a true system role with elevated priority. The other four runners prepend the system prompt as a user-message prefix. This is a CLI limitation, not a bug.
File access
| Runner | File access | Notes |
|---|
| Codex | --sandbox read-only | Can browse the codebase |
| Gemini | Default headless Gemini CLI mode | Review-safe tool access is opt-in via --backend-tool-mode gemini=review |
| Claude | --tools parameter | Review-safe tool access is opt-in via --backend-tool-mode claude=review |
| OpenCode | Workspace exposure is explicit | --backend-tool-mode opencode=workspace exposes the workspace, but not with a hard read-only allowlist |
Implications for review weight
- Codex reviews may reference specific files/lines thanks to sandbox access — treat as higher-confidence for implementation details.
- Gemini reviews now default to standard headless mode unless review-safe tools are explicitly enabled.
- Claude reviews now default to no built-in tools unless review-safe tools are explicitly enabled.
- OpenCode reviews default to isolated, prompt-driven runs unless workspace access is explicitly enabled.
- System prompt parity ensures all backends share the same review criteria (BLOCKING/HIGH/LOW taxonomy, output format).
Skill name note
Use review-swarm as the canonical external name.
Use review-swarm consistently during migration and in new integrations.