| name | lens-debate |
| description | Run a structural-review debate between two lenses — lowy (volatility-based decomposition) and hickey (structural simplicity) — on the current diff. Each reviews independently, then they cross-examine every finding until they agree per-finding, and the agreed fixes are applied. Use when the user types `/lens-debate`, or asks to "have lowy and hickey review this", "run the lens debate", "debate this diff structurally", or "argue the structure of this PR until the lenses agree". |
| argument-hint | [<pr-number>] [--base <branch>] [--max-rounds <n>] [--no-commit] [--no-comment] [--with-police] |
Lowy ⇄ Hickey lens debate
Two structural reviewers argue your change to a settled conclusion. lowy
(volatility-based decomposition — do boundaries encapsulate axes of change?) and
hickey (structural simplicity — are independent concerns complected, or one
thing fragmented?) each review the diff independently, then cross-examine
every finding from both reviews until they agree on each one. The agreed
fix findings are applied — each as its own commit — and the outcome is posted
to the PR as a comment. You stay out of the middle: the script couriers
schema-constrained dispositions between the lenses and decides when they agree.
This is the sibling of /codex-debate. Same engine (the Workflow tool), same
"both sides emit structured JSON so agreement is detected in code, not by vibes,"
same "commits but never pushes or merges." The difference is who debates whom.
Why this shape
The structure was found by trial in #1109, and two parts of it are load-bearing:
-
Independent parallel review, then debate. lowy and hickey review the diff
simultaneously and independently — neither sees the other's findings before
forming its own. A first cut fed hickey a pre-curated "lowy finding" to rebut
and it concluded drop — framing bias. Running the reviews independently in
parallel made hickey raise the same issue on its own, flipping the verdict to
fix. Curation biases the outcome; independent-then-debate does not. So the
lenses never trust a handed-down finding list — each reads the source itself.
-
Both lenses run on Opus (overriding their model: sonnet frontmatter), as
/be already requires for structural review.
-
The lowy lens runs Löwy's electricity probe. Beyond the generic "where's
the boundary?", the lowy reviewer must name the receptacle (the stable
interface consumers plug into), the volatile implementations behind it,
whether the thing is "electricity" (a domain-agnostic utility) or an app
concern, and where a consumer is forced to "expose the wires." This is not a
second lens — a separate voice would double-count lowy and reintroduce the
framing bias above. It's the same volatility vote with a sharper probe that
reliably pulls structural review out of abstraction and into "what plugs into
what" (the abstraction-without-grounding failure mode a lens debate is prone
to). It earned its keep on a live run (#1111).
Why deadlock is not possible
Neither this skill nor /codex-debate has a deadlock exit — both run until
consensus, as many rounds as it takes. But the reason convergence is safe to
rely on is even stronger here. In /codex-debate the asymmetry is reviewer vs
author: Claude wrote the code and carries an authorship stake, so in
principle it could dig in and dispute a finding round after round (the loop
trusts good-faith concession to break the tie, and aborts only on reviewer
infrastructure failure).
Here both sides are disinterested third-party lenses applied to someone
else's diff. Neither authored the code; neither has anything to defend. Their
disagreements are not ego conflicts but framework-weighting differences ("is this
worth fixing in this PR?") about a shared question with a knowable answer. Two
good-faith analysts, each told to argue from the code and concede when the other
is right, converge — there is no fixed position to defend. So there is no
deadlock exit: the debate runs until consensus, as many rounds as it takes.
Three mechanics make that real rather than hopeful:
- Independent review (above) removes the up-front framing bias.
- Settled findings lock. The moment both lenses agree on a finding's
disposition, it leaves the active set. The contested set is monotonically
non-increasing — the debate can only shrink, never grow, so it can't oscillate
a settled point back open.
- Sequential reveal. Within a round lowy posts first and hickey answers
lowy's current positions, so the two land together instead of chasing each
other's stale positions.
--max-rounds (default 12) is a pure safety backstop so a pathological
oscillation can't run unbounded — not a deadlock cap. Reaching it is reported as
unresolved (needs a human), never deadlock, and should essentially never
happen between two good-faith lenses.
This skill requires Claude Code's Workflow tool (it is the engine). Under
codex/opencode runtimes the skill is inert.
Arguments
Parse [<pr-number>] [--base <branch>] [--max-rounds <n>] [--no-commit] [--no-comment] [--with-police]:
<pr-number> (optional): a PR to debate. If given, gh pr checkout <n>
first and default the base to that PR's base branch. If omitted, debate the
current branch's diff.
--base <branch>: ref to diff against. Always a remote-tracking ref,
never a stale local branch. Default: origin/<PR base> when a PR number is
given, else the repo default branch via
git symbolic-ref --short refs/remotes/origin/HEAD (e.g. origin/master),
used as-is. Fallback origin/master. Step 1 runs git fetch origin first.
The workflow resolves this to the merge-base of base and HEAD and diffs
against that, so the base branch's drift since the fork isn't reviewed as ours.
--max-rounds <n>: safety backstop on debate rounds. Default 12. Not a
deadlock cap (see above) — raise it freely.
--no-commit: still apply the agreed fixes to the working tree, but leave
them uncommitted for you to commit yourself. Default is to commit each fix
individually (see below).
--no-comment: don't post the debate summary to the PR. By default,
when a PR exists, the summary IS posted as a PR comment (see step 3).
--with-police: fold in /code-police as a third, lower-weight voice.
It runs in the parallel review and seeds findings into the debate, but does
not get a vote in consensus — only lowy ⇄ hickey decide agreement. Off by
default (in #1109 its findings largely duplicated the lens findings).
Steps
1. Resolve context
- Determine
repoPath (the worktree root, normally the cwd).
git fetch origin so the base remote-tracking ref is current.
- Resolve
base per the rules above (a remote-tracking ref like origin/master).
- If a PR number was given,
gh pr checkout <n> and confirm the branch.
- Confirm a non-empty diff:
git diff --stat <base>. If empty, say there's
nothing to review and stop.
2. Run the debate Workflow
Invoke the Workflow tool pointing at this skill's committed script, passing
context through args:
Workflow({
scriptPath: ".claude/skills/lens-debate/debate.workflow.js",
args: {
repoPath: "<worktree root>", // also the per-worktree scratch dir root
base: "<base branch>", // a remote-tracking ref, e.g. origin/master
maxRounds: <n, default 12>,
commit: <false only if --no-commit>,
withPolice: <true only if --with-police>,
rationale: "<optional author note on deliberate design decisions>",
model: "<optional model override; defaults to opus>"
}
})
The workflow runs in the background and notifies you when it completes. It runs
three phases the user can watch via /workflows:
- Review —
review:lowy, review:hickey (and review:code-police with
--with-police) in parallel, each independent.
- Debate — alternating
lowy:roundN / hickey:roundN until every finding is
agreed. Agreed findings drop out of each subsequent round. Agreement on a fix
means both lenses agree on the disposition and the plan — if they both say
fix but propose different changes, the finding stays open until the plans
converge too (so Apply never picks one lens's plan arbitrarily).
- Apply — one
apply:<finding-id> per agreed fix, each followed (unless
--no-commit) by a commit:<finding-id> that commits exactly that fix's
changed files with a message carrying the debate context.
When rationale is set, pull it from the PR/issue description (the deliberate
design decisions the author wants the lenses to respect, e.g. a deliberate
fail-open) so the lenses don't flag intentional choices.
Ephemeral scratch (commit-message files) lives under the gitignored, per-worktree
<repoPath>/.lens-debate/, so parallel debates in different worktrees never
collide and the scratch never shows up in the diff the lenses review. It returns:
{ status: "consensus" | "unresolved" | "clean",
rounds, base, withPolice,
settled, // per-finding: id, origin, title, location, agreed disposition, plan, both reasonings
unresolved, // findings still contested at the backstop (empty on consensus)
applied, // [{ id, title, files, commit }]
reviews, // each lens's independent findings
history } // per-round dispositions
- consensus — every finding settled (the normal outcome).
- clean — every lens found nothing worth raising.
- unresolved — the backstop was hit with findings still contested. Rare;
needs a human. This is NOT a deadlock — the lenses simply didn't converge in
the round budget; raise
--max-rounds or adjudicate the listed findings.
3. Present the result
Report in chat (do not push or merge — the per-fix commits sit on the local
branch for the human to review):
- The outcome (
status) and round count.
git log --oneline <base>..HEAD (the per-fix commits) and `git diff --stat
` so the user sees what the debate changed.
- A per-finding table from
settled: origin (lowy/hickey/police), title,
location, agreed disposition (fix/drop), and the applied commit SHA for fixes.
- On any unresolved finding, surface both lenses' final positions plainly so
the human can adjudicate — do not pick a winner yourself.
- Post the debate summary to the PR (default). When a PR exists and
--no-comment was NOT passed, post a ## [⚖️ Lowy ⇄ Hickey lens debate](https://kolu.dev/blog/hickey-lowy/) comment via
gh pr comment. Include: the outcome badge and round count; the independent
per-lens finding counts; the per-finding table (origin, title, location, agreed
disposition, applied commit); and, for any unresolved finding, both lenses'
positions. Use a single-quoted heredoc so backticks/$ survive. This mirrors
/codex-debate; --no-comment suppresses it.
Safety & notes
- The lenses are read-only reviewers; only the Apply phase writes. lowy and
hickey never edit code — they only emit dispositions. The sole writes to the
tree come from the
apply: agents implementing the agreed fixes.
- Commits, but never pushes or merges. Each agreed fix is committed locally
(unless
--no-commit) so the PR history reads as the debate's conclusions, but
the skill never pushes or merges. Consensus means "both lenses agree on the
disposition," not "ship it" — the human reviews the commits and pushes/merges.
- No deadlock; bounded by a safety backstop. The loop runs to consensus.
--max-rounds only prevents a pathological unbounded run; reaching it is
reported as unresolved, not deadlock.
- Parallel-safe. Ephemeral scratch lives under the gitignored, per-worktree
<repoPath>/.lens-debate/, so debates on many worktrees run at once without
clobbering each other.
- Posts to the PR by default (unless
--no-comment) — the point is to leave
the structural-review trail on the PR.
Files
debate.workflow.js — the Workflow script (parallel review + the
lock-and-converge debate loop + the apply phase).
The lenses read .claude/skills/{lowy,hickey}/SKILL.md (and
.claude/skills/code-police/SKILL.md with --with-police) at runtime for their
frameworks.
This is generated from .apm/skills/lens-debate/; edit the source there and run
just ai::apm to regenerate.
ARGUMENTS: $ARGUMENTS