On this page
Spar independent review guide
한국어 · English
Read this before your first standard or strict goal, or when you change reviewers or read review records. Spar is the independent review tool that has reviewers outside the working agent's conversation look for problems that agent missed. Reviewers cannot change the requirements; what they find is sorted into defect, requirement-question, hardening and note, and how each was handled is recorded. A finished review is kept as a gate file that ha uses to judge completion. When you need it is below and in Review tiers in Understanding Jaekit.
In detail, Spar sends one review packet, pinned to two commits, to several reviewers at once and keeps what they found and what was done about it. The spar binary runs the rounds; the spar plugin tells your agent when to run them and how findings may be closed.
From v0.1.7, Seal starts new goals under run-rules/5, and a standard or strict goal started under run-rules/5 completes only with a recorded Spar gate. light goals, goals without an Assurance: line and goals started under run-rules/4 or earlier do not need Spar. To finish without review, you decide a goal-document change that lowers the tier to light (Review gate).
spar ships next to ha in the release files and the Homebrew formula from v0.1.7; release files and formulas of v0.1.6 and earlier do not contain it. See the install guide.
What leaves your computer
Each reviewer receives the whole packet: the criteria documents, the rule documents, the documents under review, the user's decisions you pass (in the user's own words) and your questions, and the diff between the two commits. A reviewer CLI can also read the tracked files of both commits in its workspace copy and send what it opens to its provider.
| Reviewer | Provider that receives the packet |
|---|---|
claude |
Anthropic, through the Claude Code CLI |
codex |
OpenAI, through the Codex CLI |
agy |
Google, through the Antigravity CLI |
command |
Whatever service the command you configure uses |
native |
The provider of the agent you are already using |
Commit messages, Git history and ignored files are not in the packet. If the criteria or the code must not go to any provider other than the one your agent already uses, configure only the native reviewer. If they must not leave your computer at all, do not run Spar: every reviewer, native included, sends the packet to a provider.
The shared packet's ## Standpoints for this round section contains all standpoints and their reviewer assignments. Each reviewer also receives an instruction identifying its own standpoint and asking it to attack that risk while still checking every angle; an unassigned reviewer is told to review the whole change.
claudeandcodexreceive the instruction and packet on standard input.agyreceives the command note, instruction and packet in-p; for a large packet,PACKET.mdholds the packet and-pholds the command note, instruction and file reference.commandreceives the reviewer'sinput.mdthrough{prompt};nativereceives the instruction after its fixed instructions inprompt.md.
For a round with standpoints, both a real run and --dry-run leave in <round>/<reviewer>/input.md exactly the input that reviewer receives. For agy with a large packet, input.md holds its -p text and the packet is in PACKET.md.
Configure reviewers
Reviewers come only from your user configuration. Spar reads the first file it finds among $SPAR_CONFIG, $XDG_CONFIG_HOME/jaekit/spar.toml and ~/.config/jaekit/spar.toml. Without one, it reads the reviewers list of an existing clean-review config.json, and without that it uses claude, codex and agy. An empty list (reviewers = []) means the native reviewer only.
Without any of these user configurations, claude, codex and agy are all required reviewers. When a required reviewer's CLI is missing or the reviewer does not finish a round, the gate is degraded unless you waived that reviewer for that round (spar waive), so a standard or strict goal does not complete. Before the first round, write a user configuration in ~/.config/jaekit/spar.toml that lists only the CLIs you have. For example, with only the claude CLI, list that one reviewer:
[[reviewers]]
preset = "claude"
With one reviewer listed, it is the only required reviewer, and a round it finishes can be complete. With no reviewer CLI at all, use the empty list for the native reviewer only: a clean-context subagent of the agent you are using reviews.
reviewers = []
With all three CLIs, you can list them like this:
timeout_seconds = 1800
[[reviewers]]
preset = "claude"
[[reviewers]]
preset = "codex"
[[reviewers]]
name = "agy"
preset = "agy"
model = "gemini-4-argon-high"
fallback_model = "gemini-3.8-flash-high"
claude,codexandagystart their CLI with read-only, offline protections that spar checks; they are recorded asverifiedand count as required reviewers.agymay use its file read, list and search tools and shell commands that join read-only programs (ls,grep,cat,diff,trueand so on) andsed -nwith|,&&,||or;. If it runs code, tests, builds, git or any other program, spar stops that reviewer, records it astool_violationand drops its result. spar puts this rule in a short note at the start of agy's prompt.fallback_modelruns once, in the same round and on the same packet, only when the designated model is refused for quota or rate limits.fallback_model = ""turns the preset's default fallback off.nativeis a clean-context subagent of your agent. Spar writes its prompt and workspace into the round folder and records the reply the agent brings back. It is recorded ashostand counts as required.
Models and effort
A reviewer's model (claude, codex, agy) and effort (claude, codex) come first from your user configuration: writing model, as the example above does, fixes it. For a reviewer whose model or effort is not written, the agent chooses: the agent running the round passes --model NAME=MODEL and --effort NAME=EFFORT to spar run, and without them the preset's values run (models opus, gpt-6-astra, gemini-4-argon-high, effort high). Before any reviewer starts, spar refuses a model or effort for a reviewer whose configuration writes it, even one equal to the written value, a model or effort for a native or command reviewer, an effort for agy, a name that is not a reviewer of the run, a second model or effort for the same reviewer, a value outside the allowed list, and a value that is empty, is not valid UTF-8, starts with - or holds a space or control character.
To limit what the agent may choose, write allowed_models and allowed_efforts:
[[reviewers]]
preset = "codex"
allowed_models = ["gpt-6-astra", "example-model-mini"]
allowed_efforts = ["medium", "high"]
- With a list, the agent can choose only its entries. When the agent chooses nothing, the preset's values run, whatever the list holds.
- A model the agent chose falls back only to the
fallback_modelyou wrote; without one it runs with no fallback. spar config --model codex=gpt-6-astrashows the model a run with that choice applies and where it comes from. The round record and summary keep each reviewer's model and effort with their source (user,agent,preset).
A command reviewer
Any other reviewer can run as a command. It runs without a shell, receives the packet file, its workspace copy and the result schema through placeholders, and prints one JSON object that follows the schema. This example is synthetic: example-reviewer stands for a program you provide.
[[reviewers]]
name = "example-reviewer"
preset = "command"
argv = ["/opt/example/bin/example-reviewer", "--prompt", "{prompt}", "--workspace", "{workspace}", "--schema", "{schema}"]
timeout_seconds = 600
{prompt}is the path ofpacket.mdfor a round without standpoints, or that reviewer'sinput.mdfor a round with standpoints;{workspace}the reviewer's copy of the two commits (head/andbase/),{schema}the JSON schema of the result.- Spar cannot check what a command does, so it is recorded as
unverifiedand never counts as a required reviewer. Its findings are classified and closed by the same rules.
Before running a round, check with spar config which reviewers it will start. spar config prints the reviewers a round will start, where the list came from and its digest. spar run --reviewers codex,agy changes the list for one run, and the round record says so.
Project settings
A repository's .jaekit/spar.toml can set only round caps, attack angles, rule documents and check paths:
rules = ["AGENTS.md"]
check_paths = ["tools/checks/**"]
[caps]
code = 2
It cannot choose reviewers, commands, executables, models, efforts or allowed lists; spar names the key and stops. The full format is in the configuration contract.
Set up a project
Spar writes review records only to a folder Git ignores, because a packet can contain private documents. In a project that has never run Spar, Git does not ignore that folder yet, so the first round stops with exit 65 before any reviewer starts and writes nothing. Before a project's first standard or strict goal, add one line to the project's .gitignore:
docs/reviews/
docs/reviews/<goal>/ is where the Spar skill puts a goal's rounds and gate. light goals and goals that do not use Spar do not need this step.
- Another folder. Records can go to any folder Git ignores. Add that folder to
.gitignoreinstead, and tell your agent to put the rounds there. - Only on your computer. To leave
.gitignoreunchanged, add the same line to.git/info/exclude. Git does not commit that file, so another clone or computer needs the line again. - Spar also refuses a folder under which Git tracks files. Ignoring a folder does not untrack files already committed there.
Run a review
Ask your agent, for example $spar:spar in Codex or /spar:spar in Claude Code. The skill runs these stages:
| Stage | When |
|---|---|
plan |
once, after the plan and its checks are written |
plan-delta |
at most once, when the plan round changed the plan |
candidate |
on the commit that should meet every criterion |
fixes |
after fixing findings; its base is the previous code round's head |
One series allows one plan round, one plan-delta round and three code rounds. A round past its cap is refused before any reviewer starts; only your words, recorded with spar extend, allow more. Rerunning reviewers that did not finish the same packet, for example after a quota refusal, does not count.
When the code rounds run out, the spar run refusal points to that series' cap report. spar cap-report reads only the series' records and Git, shows the cap use, the rounds, the open findings and four signals (churn, recurring links, unrelated defects, check growth), and writes nothing. Churn is counted by file and recurring links by link, and unrelated defects use each reviewer's judgment as given.
spar cap-report docs/reviews/example
The report never picks a cause. The agent picks one cause (--cause) from the signals among churn, recurring, unrelated, hardening, other, asks you with its recommendation, and records spar extend <series> --stage code --by 1 --cause <cause> --user-quote "…" only when your words allow more rounds. --by 2 or more, and --cause other, need --reason. Give the series to ha cost with --series to see the recorded cause and reason in its list of cap extensions.
Pass what the user decided to every later round as a decision. A user's answer to a requirement-question is the typical one, and the same holds when you use Spar without Seal. Give each decision as --decision (what it settles) followed right away by --decision-quote (the user's words).
spar run --stage fixes --base 2222222 --head 3333333 \
--criteria docs/specs/example/SPEC.md --rules AGENTS.md \
--decision "Empty input in AC-2" --decision-quote "Keep empty input an error" \
--out docs/reviews/example/03-fixes
Decisions go into the packet's ## Decisions the user made section as given and stay in decisions in round.json. The section tells reviewers not to report what a decision settles, to report as usual a change that goes beyond it, and, when following a decision breaks a criterion, rule or contract the decision does not address, to report a requirement-question that links it. spar does not check that the words are the user's. An empty decision, an unpaired option or text that is not valid UTF-8 is refused before any reviewer starts. The same input with the same decisions is the same packet; different decisions make another packet.
Each round writes a new, Git-ignored folder with round.json, packet.md, findings.json, ledger.jsonl and summary.md. The finding table in summary.md shows each claim whole, on one line in the Claim column, so that file alone is enough to classify and close the findings. Spar refuses a folder that Git would track, because the packet can contain private documents (Set up a project).
Round and input formats, and findings without a readable location
The format is in the round contract. The format of every input spar reads, such as finding locations and links, configuration values and command arguments, and what happens to input outside it, is in one table in the input contract.
A finding whose location names no file is kept, marked as an unreadable location and never deferred automatically.
Reviewer standpoints
In plan and candidate rounds, including the cumulative round, the agent picks the change's main risks and gives each reviewer a different standpoint, one each. When there are fewer risks than reviewers, the rest review without one. In fixes and plan-delta rounds the earlier standpoints may be given again or omitted. A standpoint names the risk, where to look first and the position from which to see it; it must not claim the fix is right or explain the solution. This synthetic example assumes codex is configured.
spar run --stage candidate --base 1111111 --head 2222222 \
--criteria docs/specs/example/SPEC.md --rules AGENTS.md \
--standpoint "codex=From the stored-format reader position, start with the earlier-record readers and attack the risk that changed fields reject earlier records." \
--out docs/reviews/example/02-candidate
Pass --standpoint NAME=TEXT to spar run. The first = separates the reviewer name from the text; the option may be repeated. Refused (65): a name outside this run's configured reviewers and --reviewers, more than one standpoint for a reviewer, text empty after trimming whitespace, or invalid UTF-8. Names are compared exactly and case-sensitively, as in G20. A missing = or an empty name is a usage error (64). Both refusals happen before any reviewer starts or any round folder is created; the message names the offending name or value. Other text is kept unchanged.
The accepted names are the union of the configured reviewers and the names selected by --reviewers. A configured reviewer omitted from this run may still have a standpoint in the packet; when rerunning a subset, pass the full earlier list of standpoints in the same order. Standpoints are run arguments only, with no user or project configuration key; spar config is unchanged.
Standpoint text and assignments are in the packet, so packet_sha256 identifies them as part of the same input; changing either makes a new input. A rerun of only unfinished reviewers is not counted when stage, base, head and packet_sha256 are unchanged, including the same standpoints.
standpoint_report is an object with two fields. checked is a boolean and detail is nonblank text: what was checked when true, or why it could not be checked when false. For an assigned reviewer, a missing, null or malformed report is invalid_output, with standpoint_report in the reason; an unassigned reviewer need not report one. An unassigned reviewer's standpoint_report is not checked and is recorded as null. Every preset uses the same check in spar run and spar record --result or --transcript.
round.json keeps standpoints as reviewer/text pairs, standpoints_uncovered as reviewer names without a completed reviewer, and each reviewer's standpoint and standpoint_report as text/report or null. summary.md shows each reviewer's standpoint and report, and standpoints without a completed reviewer, including waived reviewers. The schema remains review-round/v1; records without standpoint fields mean no standpoints. When reporting the gate, also name standpoints left without a completed reviewer, including waived reviewers.
Standpoints do not change angles, classification, dispositions, caps or the gate's state and reasons.
Findings and the gate
The gate is a review record, not evidence of completion. Seal's completion still comes only from ha done. For a standard or strict goal started under run-rules/5, Seal records the gate file with ha note <goal> review --gate <file>, and ha done records completion only when the recorded gate covers the current HEAD and goal documents (Review gate). So the rounds get SPEC.md and every goal document it links as --criteria.
How findings are classified
Spar classifies every finding as defect, requirement-question, hardening or note. A requirement question closes only with your words; an edge case in check code that claims no false pass, inverted result or named rule violation is deferred as hardening. A reviewer that did not finish is shown with its cause and is never counted as finding nothing.
The smallest fix
Reviewers give each finding a smallest_fix: the smallest change that would make its failure scenario come out right, stated as a direction rather than a patch. A reviewer may leave it empty.
The direction stays in findings.json and appears under ## Smallest fixes in summary.md. It is advice and is not used for classes, dispositions or the gate. The agent starts a fix within that direction and records why with spar triage … --note when the fix goes further.
Gate states
spar gate reads the rounds and the head you are about to complete and writes a review-gate/v1 file: complete (exit 0), degraded (3) when a required reviewer did not finish and you made no decision for that round, or blocked (4) for open findings, caps exceeded or an unreviewed head. It also records how many lines review fixes added to check code and elsewhere. See the gate contract.
Going on without a reviewer
When a required reviewer did not finish a round, for example because of a usage limit, and you say to go on without it, the agent records your words for that round with spar waive <round> <reviewer> --user-quote "…". It never records one without your words. A waived reviewer is not a degraded reason for that round, and the gate file shows it under that round's waived_reviewers with its status and your words. A waiver covers only its round, and a round where no required reviewer completed stays degraded even with waivers.
guides/SPAR.en.mdBuilt from bcaddb7603e8