Lowy ∥ Hickey lens review
SkillAI & modelsReview the current diff through two structural lenses — lowy (volatility-based decomposition) and hickey (structural simplicity) — run as two independent parallel subagents, then merged by ONE reconcile-and-apply pass that commits the uncontested fixes. Debate is not the protocol: it is a bounded escalation valve for findings where the two lenses directly contradict each other, and judgment-shaped calls go to the human instead. Use when the user types `/lens-debate`, or asks to "have lowy and hickey review this", "run the lens review", or "review this diff structurally".
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Lowy ∥ Hickey lens review skill
What this skill tells your AI
The instructions your AI receives, as published by juspay/kolu in .agents/skills/lens-debate/SKILL.md and read by ahel’s review.
Two structural reviewers read your change independently and in parallel — lowy (volatility-based decomposition: do boundaries encapsulate axes of change?) and hickey (structural simplicity: are independent concerns complected, or one thing fragmented?). One reconcile-and-apply pass merges their two finding lists and commits the fixes.
Debate is not the protocol. It is one of two escalation valves, pulled only where the lenses directly contradict each other about the same code — and even then, a discriminating experiment beats another round of argument.
Why this shape, and the three rules kept from the older debate engine:
RATIONALE.md — read it when editing this skill, not when
running it.
Arguments
Parse [<pr-number>] [--base <branch>] [--max-rounds <n>] [--no-comment]:
<pr-number>(optional):gh pr checkout <n>first and default the base to that PR's base branch. Omitted → review the current branch's diff.--base <branch>: ref to diff against — always a remote-tracking ref, never a stale local branch. Default: the repo default branch viagit symbolic-ref --short refs/remotes/origin/HEAD, used as-is.--max-rounds <n>: hard cap on valve-1 debate rounds. Default 2. Reaching it is not a reason to keep going — the finding goes to the human.--no-comment: don't post the summary to the PR (a caller that pushes later posts it itself — see/be-review). Default is to post.
Steps
1. Resolve scope
Determine repoPath — the root of the repo under review, which a caller may
hand you and which need not be the cwd. Every git command and every file path
below is rooted there (git -C "$repoPath", absolute paths).
git fetch origin, resolve base per the rules above, then diff against
MB=$(git merge-base <base> HEAD) so the base branch's drift since the fork
isn't reviewed as ours. If merge-base fails (missing/typoed base, stale ref,
unrelated history), stop and say so — never fall back to the raw base tip;
the review scope would be silently wrong. Confirm a non-empty
git diff --stat $MB; if it's empty, say there's nothing to review and stop.
2. Fan out the two lenses — parallel, independent
Spawn both lens subagents in a single message (two Agent calls, so they
run concurrently), each with model: opus. Each prompt says:
- Read
.claude/skills/<lens>/SKILL.mdfor your framework first. - Inspect the FULL change in the repo at
<repoPath>— your cwd may be a different worktree, so usegit -C <repoPath>and absolute paths: rungit -C <repoPath> diff $MBandgit -C <repoPath> status --short(untracked files never appear in the diff), then Read every changed file plus enough surrounding code to judge it in context. - You are reviewing independently — you are not seeing any other reviewer's findings, and that is the point.
- Return all your findings, no cap, each with:
title,location(file:line),problemin your lens's terms,suggestion(concrete enough to apply verbatim if the other lens simply agrees),disposition(fix= worth changing in THIS PR /drop= observation only), andseverity(minor= local polish /major= structural or correctness-adjacent). An empty list is fine only for a genuinely clean diff; don't fabricate, don't hold back. - Pass the author's rationale when you have one, so deliberate decisions aren't flagged as defects.
- Write your findings to
<repoPath>/.lens-debate/<lens>-findings.mdas your last act, then return only a one-paragraph summary. The file is the artifact every later stage reads; your returned text is not. Never let the invoking agent transcribe your findings — it authored the diff you just reviewed, so a paraphrase by it is exactly the curation this skill exists to prevent.
The lowy prompt additionally carries the electricity probe: name the receptacle (the stable interface consumers plug into), the volatile implementations it encapsulates, whether this is "electricity" (a domain-agnostic utility) or an application concern, and where a consumer is forced to "expose the wires". If the diff has no such boundary, say so — don't invent one.
3. Reconcile and apply — one pass
Point a single reconcile-and-apply subagent (model: opus, repoPath and MB
threaded through) at both findings files — it reads them off disk, so the
lists reach it exactly as each lens wrote them. It merges and implements; it does
not re-review, invent findings, or drop one silently. Every input finding
leaves this pass with exactly one outcome:
fixfindings both lenses raised about the same issue — two independent Opus lenses reaching the same conclusion is consensus. Apply once, under one canonical plan (copy the more concrete suggestion; merge only when each has a detail the other lacks). Mark the pair≡.- Uncontested solo
fixfindings — apply as raised. The other lens read the same full diff and said nothing about that code; manufacturing an objection round to confirm silence is the cost this skill exists to stop paying. dropfindings — recorded as observations, not applied.- Direct contradictions — the two lenses raised the same code and their claims are incompatible (one says extract, the other says inline). Not applied: routed to valve 1.
- Judgment-shaped calls — routed to valve 2.
Commits are grouped by area, not by finding, staging only that area's
files (never git add -A):
- One commit per coherent area for
majorfixes — the port-forward library's mechanism seam, the client's forward rows, the server's subsystem, the build validator. Every major fix belonging to that area rides that one commit, with a bullet per finding in the body. - One commit for ALL
minorfixes, across every area:fix(lens): minor polish from the lens review, a bullet per finding.
fix(lens): <the area>
<one-line summary of what changed in this area>
- <lowy|hickey> <id> — <the finding, one line>
- <lowy|hickey> <id> — <the finding, one line>
Raised by the lowy ∥ hickey lens review. Not pushed or merged.
Why not one commit per finding (which this skill used to require): the two
lenses read a whole diff and mark nearly everything fix, because in this repo
the answer to "is it worth doing in THIS PR" is essentially always yes. That is
the right disposition and the wrong commit granularity — it turned a 40-finding
review into a 30-commit log where a real defect and a corrected doc comment
looked identical. Grouping keeps a major defect individually reviewable and
revertable, which is the property that actually mattered, without spending a
commit on every typo.
Never push, never merge. The pass writes its result to
<repoPath>/.lens-debate/outcome.md — per finding: id, origin, title, location,
outcome, and the commit SHA for applied fixes — and returns a short summary.
4. The two valves
Valve 1 — bounded debate, contradictions only. A finding reaches it only when both lenses spoke about the same code and disagree.
- Prefer a discriminating experiment. If the disagreement is empirically testable, design the smallest test whose result differs under each lens's claim, run it, and let the output settle it. Record the command and its output — that is the argument, and it costs one tool call instead of a debate. This is how the contested calls in #1975 actually got settled.
- Only when nothing decides it empirically, run a bounded exchange: at most
--max-rounds(default 2) lowy ⇄ hickey round trips, each turn scoped to the contested hunks (both lenses have already read the whole diff once). If they converge, apply the agreed plan as in step 3. If they don't, stop — the cap is a hard cap, and the finding moves to valve 2.
Valve 2 — hand it to the human. Product scope ("is this worth doing in this PR?"), naming, and boundary taste are not resolved by more agent rounds; they're resolved by whoever owns the decision. Surface each with both lenses' positions verbatim. Do not pick a winner and do not argue it to a synthetic consensus — a tie a human breaks in thirty seconds beats a paragraph of manufactured agreement.
5. Report, then comment
Report in chat, and return a status for callers:
clean— both lenses found nothing worth raising.applied— every finding settled; the fixes are committed locally.needs-human— at least one finding sits in valve 2 (including anything that hit the debate cap). Name them; do not report this as consensus.merge-base-error— the scope couldn't be trusted (step 1).
Show git log --oneline $MB..HEAD and git diff --stat $MB, plus a per-finding
table: origin (lowy/hickey), title, location, disposition, how it settled
(≡ agreed / applied / observation / experiment / debated / human),
and the commit SHA for fixes.
Render the comment body from the three files in .lens-debate/ — always,
whether or not you post it. The two findings files and outcome.md hold
everything the comment says, so it is rebuildable by anyone (a later stage, a
re-run, a human) without relying on an agent's memory of the run. Write it to
<repoPath>/.lens-debate/comment.md. Under --no-comment that file is the
hand-off: the caller posts it after its own push (see /be-review).
Unless --no-comment and when a PR exists, post it under the header
## [⚖️ Lowy ∥ Hickey lens review](https://kolu.dev/blog/hickey-lowy/), with the
status, the per-lens finding counts, and any valve-2 items spelled out with both
positions:
gh pr comment <pr> -F "$repoPath/.lens-debate/comment.md"
Safety & notes
- The lenses are read-only reviewers. The only writes to the tree come from the reconcile-and-apply pass (and, for a converged valve-1 finding, the same pass re-run on it).
- Commits, but never pushes or merges. "Applied" means the lenses agreed the change improves this PR — not "ship it". The human reviews and pushes.
- No unbounded loop. There is no run-to-consensus path: uncontested findings
never debate, contradictions get evidence or at most
--max-rounds, and everything else goes to the human. .lens-debate/is the run's record, not just scratch:lowy-findings.md,hickey-findings.md,outcome.md,comment.md. Each is written by the stage that produced it, so no stage retypes another's work. It's gitignored and per-worktree, so parallel runs don't collide and the files never show up in the diff the lenses review.
The lenses read .claude/skills/{lowy,hickey}/SKILL.md at runtime for their
frameworks.
This is generated from agents/.apm/skills/lens-debate/; edit the source there and run
just ai::apm to regenerate.
ARGUMENTS: $ARGUMENTS
Signals
- GitHub stars
- 78
- Forks
- 12
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
lens-debate- Source
- github.com/juspay/kolu