Agent review
cappa agent turns a failed visual run into something a coding agent can review cheaply. A
heuristic labels every changed screenshot from its diff interpretation;
only the changes it cannot settle are handed to the agent, as small crops of each changed region
instead of full-page screenshots. The agent writes a verdict, Cappa folds it back in, and the agent
approves what you confirm — or, because it has your code, fixes what is broken.
The approach follows BlazeDiff’s agent review, adapted to Cappa’s plugins and baselines.
Install the skill into your project from the directory holding cappa.config.ts:
cappa agent initThe files always land in the project root — the nearest directory above with a lockfile or
pnpm-workspace.yaml — because that is where agents look for skills. So in a monorepo where only
one package depends on Cappa, run it in that package (pnpm -F web exec cappa agent init), and
the skill still goes to the root.
What gets installed depends on the agents the project already uses. You can also pick with
--stack:
| Stack | Detected by | Installs |
|---|---|---|
claude |
.claude/, CLAUDE.md (also the fallback) |
the skill in .claude/skills/cappa/, plus a cappa-judge subagent in .claude/agents/ that judges screenshots in its own context (below) |
agents |
.agents/, AGENTS.md |
the skill in .agents/skills/cappa/ (the vendor-neutral Agent Skills directory, read by Codex and others) |
opencode |
.opencode/, opencode.json(c) |
the .agents skill, plus OpenCode commands and a judge subagent (below) |
--stack all installs everything. The skill is SKILL.md, JUDGING.md and MASKING.md.
The installed files call Cappa the way your project runs it: pnpm exec cappa, yarn cappa,
bunx cappa or npx cappa, picked from the lockfile. When Cappa lives in a workspace package,
the command switches into it — pnpm -C packages/web exec cappa, yarn workspace web cappa,
bun --cwd packages/web x cappa or npm exec -w packages/web -- cappa — so agents run it from
the root. Override it with --runner "<command>".
Commit the files and tailor them to your project. Re-running init skips files you modified,
unless you pass --force.
When cappa.config.ts has no webServer, init points that out:
without one, the agent has to work out how to start your Storybook or app by itself. init never
edits the config.
Then ask your agent for a visual review, or run /cappa in Claude Code.
OpenCode
Section titled “OpenCode”OpenCode reads the skill from .agents/skills/. The opencode stack also adds commands that run
the review flow, and a subagent that keeps the judging out of your main conversation:
| File | What it does |
|---|---|
.opencode/commands/cappa-review.md |
/cappa-review [request] injects the current agent check state. It captures when asked or when nothing is captured yet, hands pending judgments to cappa-judge, reports one line per screenshot and offers next steps. |
.opencode/commands/cappa-judge.md |
/cappa-judge [names] runs the judge as a subtask on the pending screenshots, or on the ones named. |
.opencode/commands/cappa-approve.md |
/cappa-approve <names or label> approves by exact name or verdict label. It holds back regressions and deletions unless you name them explicitly. |
.opencode/agents/cappa-judge.md |
A cappa-judge subagent. It reads the region crops and the git history in its own context, and records verdicts with cappa agent judge. |
The judge works in a sandbox. It cannot edit files, fetch from the web or start other agents.
Its shell is not narrowed to particular commands: OpenCode’s permission patterns match command
text literally, so a list of allowed commands breaks as soon as the runner, the shell or the
quoting differs. Its instructions limit it to cappa agent check, cappa agent judge and
read-only git (diff, log, show, status), so it records verdicts but does not approve,
capture or change code.
The commands and the agent use the runner chosen at install time, because OpenCode runs their shell steps before the model sees them.
The judge’s model
Section titled “The judge’s model”Judging means reading images. A subagent runs on the model that started it, so if your main
agent uses a model that cannot read images, the judge cannot either. Pin the judge to one that
can with --judge-model:
cappa agent init --judge-model anthropic/claude-sonnet-5-5 # OpenCode: provider/modelcappa agent init --judge-model sonnet # Claude Code: alias or model idThe model goes into the model: field of the judge whose format it matches. init refuses a
model that no installed judge can take. Changing the model rewrites the judge without --force.
Re-running init without the flag keeps the pinned model. Without a pin, init warns about the
OpenCode judge. Every Claude model can read images, so the Claude Code judge works without one.
A judge that still finds it cannot see the crops records nothing. It replies with the
--judge-model command to run instead of guessing from the region metadata. Don’t rely on that
check alone: a model that cannot read images does not always notice. Pinning a vision model is
what makes judging work.
The Claude Code judge
Section titled “The Claude Code judge”Claude Code’s agent files cannot limit Bash to particular commands, so the Claude Code
cappa-judge uses a PreToolUse hook instead. Before every shell command the judge runs, the
hook calls cappa agent guard, which allows only cappa agent check, cappa agent judge and
read-only git. It refuses anything else, including chaining, pipes, redirects and command
substitution.
Large reviews
Section titled “Large reviews”Each judgment comes with two images, so a single judge cannot take hundreds of them. The skill gives each judge about 20 screenshots and runs several judges in parallel. Screenshots that share a headline stay with the same judge.
Two copies of the skill
Section titled “Two copies of the skill”If the project also has .claude/skills/cappa, OpenCode uses that copy of the skill instead of
the one in .agents/skills, and init points the OpenCode judge at it too. Tailor the .claude
copy, and keep .agents/skills in sync for other agents.
How a review runs
Section titled “How a review runs”-
Capture: the agent runs
cappa capture --json(see machine-readable output). With awebServer, that also starts and stops the app under test. -
Check —
cappa agent check --jsonlabels every changed screenshot:Label Meaning Next step regression-likelyStructural change (content added, removed or replaced) at medium or high severity Check against the code, then fix intentional-likelyConfident recolour, or content shifted/restyled with structure intact Approve if intended noise-likelyRendering noise, or a sub-perceptual (< 0.01 %) low-severity change Mask or approve ambiguousAnything else, including changed image dimensions Judged by the agent -
Judge — for each
ambiguouschange, Cappa writes a judgment request the agent reads instead of the full screenshots:locator.png— a thumbnail with every changed region outlined and numberedregions.png— oneexpected | actualrow per region, cropped with contextoverview.png— expected and actual side by side, when the dimensions changedrequest.json— regions, heuristic label, interpretation summary and paths
The agent records its verdict with
cappa agent judgeand re-runscappa agent check, which applies it. To get the same evidence for changes the heuristic already labelled, the skill runscappa agent check --judge all. The skill does this when a heuristic label looks doubtful, for example aregression-likelythat a deliberate code change explains. A request stays pending until it gets a verdict, so after--judge allthe agent judges every change. When thecappa-judgesubagent is installed, the agent always hands the judging to it, so a judge pinned with--judge-modelis never bypassed. Without one, an agent whose model cannot read images hands the judgment directories to any subagent that can, rather than working from the raw screenshots. -
Act — the agent reports one line per screenshot, asks you to confirm, and approves with
cappa agent approve. A regression can be fixed in code and re-captured with--filter.
cappa agent check never captures: it reads the screenshots the last cappa capture left on disk,
so it is safe to run as often as needed. It works without diff.interpret — when a capture did not
persist an interpretation, it is computed on demand.
In the review UI
Section titled “In the review UI”cappa review shows the verdicts too. Changed screenshots get a verdict badge in the list and
grid views. A robot icon marks a verdict an agent wrote; a magnifier marks the heuristic’s label.
The detail page shows the headline, the reasons, the confidence and when the check ran. You can
look at what the agent concluded, agree or disagree, and approve from the same screen.
A verdict is shown only while it still describes the images on disk. Once a screenshot is
re-captured or approved after the check, its verdict disappears until the next
cappa agent check. A verdict written with cappa agent judge shows up after the next check
applies it.
Commands
Section titled “Commands”cappa agent check
Section titled “cappa agent check”cappa agent check --jsonPrints { reportPath, total, passed, changed, new, deleted, pendingJudgments, results }, where
results holds one entry per non-passing screenshot:
{ "name": "example/button/primary", "category": "changed", "verdict": { "label": "ambiguous", "headline": "2 regions: 1 content-change, 1 shift @ center (1.20%, medium)", "action": "investigate", "source": "heuristic" }, "pending": true, "judgment": "/abs/screenshots/.cappa-agent/judgments/example/button/primary"}Headlines are built so the same kind of change produces the same text on every screenshot, which
lets an agent spot one shared cause — a footer date, a theme toggle — without opening any image.
The full report, with regions and absolute paths, is written to reportPath.
| Option | Description |
|---|---|
--judge <mode> |
ambiguous (default) requests judgments for ambiguous changes only; all for every changed screenshot; none for none. Existing judgments are always kept and applied. |
--max-tile-regions <n> |
Regions rendered into each regions.png (default 8). |
--json |
Print the summary as JSON and nothing else. |
Exits with 1 while any screenshot is changed, new or deleted, like cappa status.
cappa agent judge
Section titled “cappa agent judge”cappa agent judge example/button/primary --label regression-likely \ --headline "Submit label truncated" --rationale "'Submit' renders as 'Subm'" --confidence 0.9 --jsonRecords a validated verdict for one or more screenshots with a judgment request. --label is
regression-likely, intentional-likely or noise-likely. Passing several names records one
shared verdict.
A verdict is tied to a fingerprint of the expected and actual images it was made about. When a re-capture changes either image, the verdict is dropped and the screenshot is judged again.
cappa agent approve
Section titled “cappa agent approve”cappa agent approve example/button/primary --jsoncappa agent approve --label intentional-likely --jsonApproves screenshots by exact name — unlike cappa approve --filter, which matches substrings —
or every changed screenshot whose current verdict carries a label. Refused when CI is set, so
baselines only change on developer machines.
It also refuses any screenshot whose current verdict is regression-likely. If the change was
intended, judge it intentional-likely first. To approve a regression anyway, pass
--allow-regression.
cappa agent init
Section titled “cappa agent init”Installs the skill files described in Setup. --stack, --runner and --force are
covered there; --judge-model <model> pins the judge subagent to a model that can read images
(see The judge’s model).
Everything lives in <outputDir>/.cappa-agent/, which carries its own .gitignore — judgments are
only valid for the screenshots on the machine that made them.
.cappa-agent/├── report.json└── judgments/<screenshot name>/ ├── request.json ├── locator.png ├── regions.png # or overview.png └── verdict.json # written by `cappa agent judge`