Coder<->reviewer handoff loop (claude/codex/cursor/pi, roles swappable) with self-instrumented per-round cost tracking. Loops locally via HANDOFF.md until approved, then opens a PR with the full transcript.
Installs into .claude/skills of the current project.
Are you the author of Pair Loop?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/rajitsaha-pair-loop-100xprism)
---
name: pair-loop
description: Coder<->reviewer handoff loop (claude/codex/cursor/pi, roles swappable) with self-instrumented per-round cost tracking. Loops locally via HANDOFF.md until approved, then opens a PR with the full transcript.
category: lifecycle
tier: on-demand
slash_command: /pair-loop
---
# Pair-Loop — Coder <-> Reviewer Handoff
Runs a formal review loop between a coder and a reviewer (default: you as coder,
Codex as reviewer — swap via `~/.100xprism/config.json`'s `pair_loop` section).
Four vendors are supported — `claude`, `codex`, `cursor`, `pi` — as either role.
Each round is recorded in `HANDOFF.md` and self-instrumented into a cost
manifest the token dashboard reads. Do NOT ask for permission to start or to
run rounds — only stop for the outcomes listed in "When to stop" below.
When both roles are `pi`, set **different providers** (`coder_provider` ≠
`reviewer_provider`). Same-provider different-model is allowed only as an
explicit fallback (`fallback_used: true`). Same-model `APPROVED` is still
refused (#93). If `budget.per_run_usd` is unset and coder is `pi`, the cap
defaults to **$5**.
Pi reviewer argv (read-only, isolated session):
```bash
pi -p --provider <other-than-coder> --model <mid-or-cheap> \
--tools read,grep,find,ls --no-skills --no-extensions --no-session
```
## Step 1 — Start
```bash
PROJECT_ROOT=$(git rev-parse --show-toplevel)
cd "$PROJECT_ROOT"
python3 ~/100xprism/scripts/pair-loop.py start --task "<one-line task description>"
```
If this fails with a "dirty" error, commit or stash first — do not force-start
on uncommitted work. Save the printed `run_id`, `handoff_path`, `max_rounds`,
`coder`, and `reviewer` — you'll pass `run_id` to every subsequent command.
## Step 2 — Budget check (before every round)
```bash
python3 ~/100xprism/scripts/pair-loop.py budget-check --run "$RUN_ID"
```
Exit code `2` means the per-run budget cap is hit — STOP and ask the user
whether to continue or stop; do not proceed to another round silently. Exit
code `0` with `"level": "warn"` in the output means print a one-line warning
and continue.
## Step 3 — Coder round
Implement the task (or address the reviewer's findings from the prior round).
Run the project's quick checks (tests + lint) before recording the round. **Do not commit — leave all changes in the working tree so review diffs stay accurate across rounds; commit only in Step 5 (PR phase).** Then:
```bash
python3 ~/100xprism/scripts/pair-loop.py coder-done --run "$RUN_ID" \
--summary "<what you implemented/fixed, files touched, how you verified it>" \
--findings-addressed <N>
```
## Step 4 — Reviewer round
```bash
python3 ~/100xprism/scripts/pair-loop.py review --run "$RUN_ID"
```
This shells out to the configured reviewer and returns
`{"verdict":, "findings": [...], "fallback_used":, "reviewer_tool":, "reviewer_model":, "same_model_conflict":}`.
`reviewer_tool` is the vendor that actually ran — with three vendors, do not
assume it equals `pair_loop.reviewer`; check it, especially when
`fallback_used` is true.
If the configured reviewer's CLI is missing, it falls back to the best
available alternative — preferring a vendor other than the coder's, and
preferring a multi-model CLI (Cursor, which can itself run Claude/GPT/Grok/Kimi
models) over a same-vendor fallback. Only as a last resort does it fall back to
the coder's own vendor. Whichever vendor runs is pinned to
`pair_loop.fallback_models` (default `sonnet` for Claude, `gpt-5.6-luna` for
Codex, `composer-2.5` for Cursor) to reduce the chance of a same-model
self-review. The output says `"fallback_used": true`, with the actual vendor in
`"reviewer_tool"` and its pinned model (if any) in `"reviewer_model"` —
mention this to the user once, don't repeat it every round.
If no supported CLI is on PATH the command fails naming every supported
vendor; that is a stop condition.
Independence is verified, not just configured, whenever it can be: when a
round's verdict comes back `APPROVED`, `review` resolves both the coder's and
the reviewer's session transcripts to the model each actually ran, and if they
match, overrides the verdict to `CHANGES_REQUESTED` and adds a `process`
finding explaining why (#93) — `"same_model_conflict"` in the output carries
the matched model name (`null` otherwise). Treat this exactly like any other
`CHANGES_REQUESTED`: go back to Step 2, and mention the conflict to the user
so they can point `fallback_models` (or the reviewer config) at a model that
differs from the coder's. This is still best-effort, not a guarantee — a
session_id that couldn't be resolved (no adapter for Cursor yet, or a missed
guess) means the check silently can't run, same as before.
- `"verdict": "APPROVED"` → go to Step 5 (PR phase).
- `"verdict": "CHANGES_REQUESTED"` and you have rounds remaining (round count
reported by `start` as `max_rounds`) → go back to Step 2 for the next round.
- `"verdict": "CHANGES_REQUESTED"` at `max_rounds` → STOP. Present the open
findings to the user and ask whether to ship as-is, do another round anyway,
or abandon. Do not silently exceed the configured round cap.
## Step 5 — PR phase
```bash
python3 ~/100xprism/scripts/pair-loop.py finish --run "$RUN_ID" --verdict APPROVED
```
(Omit `--pr` on this first call — you don't have a PR number yet.) This prints
`pr_body_path`. Run the **branch** and **pr** skills to push and open the PR,
passing that file as the PR body (`gh pr create --body-file <pr_body_path>`).
Once you have the real PR number, re-run `finish` with `--pr <number>` so the
manifest records it for the dashboard's `$/merged-PR` metric:
```bash
python3 ~/100xprism/scripts/pair-loop.py finish --run "$RUN_ID" --verdict APPROVED --pr <NUMBER>
```
This second call also posts a review summary to the PR — rounds, reviewer tool
(plus its model when one was pinned; the normal path uses each CLI's own
default, which isn't recorded), findings raised/resolved, verdict, and an
explicit warning when the fallback was used. Pass `--no-comment` to skip it. A failed post warns and
returns `"summary_posted": false`; it never fails the run.
If `pair_loop.pr_final_round` is `true` in the config, run one more `review`
round against the pushed PR diff before this step, and post any findings as a
PR comment via `gh pr comment` — then do exactly one more local coder round to
address them before finishing.
## When to stop and ask the user
- Budget cap hit (Step 2, exit code 2).
- Round cap hit without approval (Step 4).
- Never auto-merge — a human merges the PR, always.
**Note:** If the configured reviewer's CLI was missing and a fallback ran, mention it to the user once (`"fallback_used": true`, with the vendor in `"reviewer_tool"` and any pinned model in `"reviewer_model"`), but do not block — this is not a stop condition. A fallback onto the coder's own vendor is weaker than a genuinely different one; `review` checks the reviewer's resolved model against the coder's when it can (see Step 4, #93) and refuses a same-model approval, but that check can't always run (unresolved session_id), so treat any surviving `APPROVED` from a same-vendor fallback as weaker signal, not a guarantee — the PR summary states this rather than claiming otherwise.