Skip to content

adversarial-review

adversarial-review takes an artifact you hand it (a claim, a design doc, a root-cause analysis, a plan, a spec, a diff) and drives it to a consensus verdict. Three adversarial reviewers attack it in parallel from distinct lenses (logic, evidence, risk), an independent verifier re-checks their load-bearing claims against source, and a synthesizer issues one verdict: CONFIRMED, CONFIRMED-WITH-CHANGES, or REFUTED, with the surviving claims, the dissent, and what stays unverified.

It is a reviewer, not a builder: distinct from pr-review (which posts to GitHub). Its shape encodes a lesson from running the pattern by hand: parallel independent reviewers can’t take turns clearing their throats, and a separate verifier is needed because a debate’s own citations are often wrong.

Terminal window
# A file path, with no argument-channel limit:
keelson workflow run adversarial-review --arguments docs/design/some-proposal.md
# …or inline text, for something short:
keelson workflow run adversarial-review --arguments "Claim: dropping detached on Windows fixes the console flashing."
# …or a directory of evidence files, bundled verbatim (the claim, a diff, CI logs):
keelson workflow run adversarial-review --arguments ./review-bundle/
# Reviewing a change? Pin the tree the claims are about with `subject`:
keelson workflow run adversarial-review --arguments ./review-bundle/ --inputs subject=refs/pull/50/head
# Persist the five review artifacts:
keelson workflow run adversarial-review --arguments ./proposal.md --inputs out=./review-out
# Keep the artifact's author out of the panel:
keelson workflow run adversarial-review --arguments ./proposal.md --inputs author=claude-opus-5
# Combine author-safe seating with persisted artifacts:
keelson workflow run adversarial-review --arguments ./proposal.md --inputs author=claude-opus-5 --inputs out=./review-out

The argument is a path to a file, a directory, or the artifact text itself. A directory is an evidence bundle: every file in it (one level) is concatenated under labeled separators, so primary artifacts travel verbatim instead of being transcribed into prose. Transcription is where fidelity dies. Inline text travels the argument channel, which caps at 16 KiB; paths are read up to a ~1 MB limit (intake rejects larger input rather than review only a prefix, and a large artifact is also bounded by the reviewers’ model context).

Run it from the repository the artifact is about, so verify can read the source it cites. When the artifact concerns a specific change, also pass the optional subject input (any git ref: a branch, a SHA, refs/pull/N/head). intake snapshots that ref read-only into the run’s artifacts, and verify checks code claims against the snapshot instead of whatever branch the working directory happens to have checked out. It needs a Copilot subscription; the verdict lands in the run record as structured output (verdict, headline, must_fix, open_questions) with the full prose in report.

Set author to the model id that wrote the artifact. A matching default seat moves to a same-vendor alternate, every effective seat excludes the author, and the run reports the moved seat. When author is omitted or blank, the workflow normalizes it to seated and keeps the default panel.

Set out to a directory to save review-logic.md, review-evidence.md, review-risk.md, verification.md, and verdict.md. The collector runs after all five lanes settle. A successful run writes all five files. If a lane fails or is skipped, its file is absent and any same-name file from an earlier run is removed, so a reused directory never presents stale output as current.

Ten nodes: intake captures the artifact, subject-status records snapshot availability, and author-seat normalizes the optional author. Three reviewers fan out in parallel, verify checks their claims against source, verify-gate enforces a substantive result, and synthesize issues the verdict. The final save collector persists the five prompt outputs when requested. The reviewers never see each other’s work; the only thing that crosses between stages is each node’s output.

NodeKindWhat it does
intakebashCaptures the artifact (file path, evidence directory, or inline text), rejects empty or truncated input, snapshots the optional subject ref read-only via git archive, and emits the artifact as its output for the rest to inline.
subject-statusbashPublishes whether the optional subject snapshot succeeded, failed, or was not requested.
author-seatbashTrims author and emits seated when it is omitted or blank, giving every model selector a defined value.
reviewer-logicpromptmodel: claude-opus-5, effort: xhigh: attacks internal soundness (does the conclusion follow, what is assumed) with a per-claim verdict. No tools.
reviewer-evidencepromptmodel: gpt-6-sol, effort: xhigh: separates what the artifact shows from what it asserts, and lists the checkable claims. No tools.
reviewer-riskpromptmodel: grok-4.6, effort: high: assumes the artifact is adopted and finds what breaks, tracing bad inputs, retries, and partial completion through the proposal, weighted by cost of failure; where the artifact chooses an approach, it also names the strongest alternative ignored. Each issue carries a repro, or none plus the reason. No tools.
verifypromptmodel: gpt-6-luna, effort: xhigh: re-checks the reviewers’ load-bearing claims against real source with Read/Glob/Grep, trusting no one, and starts each check from the reviewer’s repro; one that does not reproduce is REFUTED. Checks against the subject snapshot when one was taken, else the working directory (where absence of the change is UNVERIFIABLE-HERE, never refutation).
verify-gatebashRequires literal Claim: and Result: records before synthesis. Leading list and bold markers are accepted.
synthesizepromptmodel: claude-opus-4.8, effort: xhigh: weighs the lenses against the verification and issues the verdict as structured output (verdict / headline / must_fix / open_questions + full prose report), a terse ship/no-ship call with dissent; a comparative artifact must get a choice, not a fence-sit. No tools.
savebashRuns after all five prompt lanes settle and writes their current outputs to out, preferring the full output spill over the capped environment value and removing stale files for lanes with no current output.

Every prompt node is pinned to the highest effort tier its model serves (xhigh, or high for grok-4.6) with a generous idle_timeout: a no-tool reviewer at high effort is one long think followed by text, exactly the chunk-sparse shape a default idle timeout kills. The pins put each lens on a different vendor, because lenses sharing one model share its blind spots and can wave the same flaw through unanimously; the verifier re-derives their claims on gpt-6-luna and the judge presides on claude-opus-4.8, models no lens uses, so no node checks or judges its own reasoning. All five ids are account-dependent: swap or drop any to auto (dropping its effort: tier with it) if your Copilot list lacks it.

When author matches one of those five defaults, that seat moves to a same-vendor alternate from the workflow’s closed model_by map. Other seats keep their defaults, and the collector names the effective move in the run record.

Parallel, not turn-taking. The three reviewers run with context: fresh, so none sees another’s take. Independence is the point: it removes the throat-clearing a turn-based debate produces and gets three genuinely separate attacks instead of a conversation that converges too early.

The verifier trusts no one. verify re-derives the checkable claims from source rather than relaying the reviewers’ citations, or the artifact’s. It is the load-bearing node: a debate’s participants are confidently wrong often enough that an independent source check is what separates a verdict from a vibe. Ambiguous or partial evidence never rounds up to CONFIRMED; claims it cannot check with its tools it marks UNVERIFIABLE-HERE rather than guessing.

Minimal privilege for untrusted input. The artifact can carry prompt-injection, so the reviewers and the synthesizer run with no tools at all. It reaches them inlined as $intake.output, so no prompt node reads it from disk. No-tools removes the side-effect surface (injected text can’t write files or run shell), but it does not stop the artifact from trying to steer a verdict, so every prompt also frames the artifact as untrusted data to review, not instructions to follow. The deterministic intake bash node reads a user-supplied path; only verify holds filesystem tools, and only to check source claims, the one node where reading the repo is the point.

The verdict is weighted to evidence. Synthesis is told to weight the verifier over the reviewers wherever they conflict, and to surface dissent rather than smooth it over: a split panel reads as a split verdict, not a false consensus.

  • Fan-out then converge: three independent lanes over one input, a converge node that references each lane’s $nodeId.output. The same skeleton as pr-review.
  • Bound an agent’s reach: the reviewers and synthesizer get no tools; only verify can touch the filesystem.
  • Add a lens. A fourth reviewer is one more node depends_on: [intake] with allowed_tools: []; add it to verify’s and synthesize’s references.
  • Re-seat the panel. The pins are choices, not requirements: any model in your Copilot list can hold a lens. Keep the seating rule when you swap: no two lenses on one model, and the verifier and judge on models no lens uses.
  • Keep author-aware seating aligned. When a default or alternate changes, update its model_by case and the collector’s moved-seat message together. Every effective seat must exclude the model supplied through author.
  • Cheaper runs. The maximum-effort posture buys depth; for a quick pressure-test, drop the lenses to a lighter effort: tier (or auto) and keep verify and synthesize strong; the verdict degrades last.
  • pr-review: the fan-out → synthesize sibling that posts to GitHub.
  • Authoring workflows: the read-only rail and safe-output recipes.
  • Workflow nodes: depends_on, trigger_rule, allowed_tools, and $nodeId.output.