| name | pair-consult |
| description | Bounded multi-round consultation (default 5 rounds, set with --number) between Claude Code and Codex on a single user question or small coding task โ A proposes, B reviews, A responds, B re-reviews, A synthesizes and asks the user. Use when a single coding question warrants a tight structured back-and-forth ending in user confirmation, rather than a full specโplanโcode workflow. |
| argument-hint | <question> [--number n] [--model high|xhigh] |
pair-consult
TL;DR for a cold-woken peer
You were invoked via codex exec or claude -p with "Resume the pair-consult skill. Read .consult/STATE.md...". You are a one-shot peer: you do exactly ONE round (an even round โ R2, R4, โฆ) and exit. Do this:
cat .consult/STATE.md .consult/QUESTION.md .consult/USER_NOTES.md โ orient.
- Confirm
STATUS: WAITING: <you> and read ROUND: + ROUNDS:. You are B, so this round is a review โ a fresh-critique review (R2-style) of A's latest proposal/response by default. It's a re-review (R4-style, accept/double-down) only when ROUNDS >= 5 and ROUND == ROUNDS-1 (the last B round, which follows an A-response). At n=3 there's no A-response round, so the single B round is always a fresh review. See Round protocol.
- Write
R<ROUND>.md, then update STATE.md: set STATUS: WAITING: <A's CLI name> (the value of STATE.md's A: line, e.g. WAITING: claude โ never the literal letter "A") and bump ROUND.
- EXIT. Do NOT call
consult_handoff or consult_wait. The orchestrator (the interactive session that woke you) is already blocked in consult_wait and resumes the instant you flip STATE.md. If you hand off, you spawn a duplicate orchestrator โ two instances then write the same round and collide on STATE.md. This is the bug. Don't be it.
You never do the final round. The final round (ROUND == ROUNDS) is always the orchestrator's synthesis. Only the orchestrator runs consult_handoff + consult_wait; see Two roles and Handoff.
No session memory across turns. State lives in .consult/. Templates for every file are in reference/file-formats.md.
Overview
The default session is 5 rounds. --number n sets the round cap (ROUNDS: in STATE.md) โ a max, since early termination can finish sooner; see Flags. The 5-round shape:
| Round | Actor | Action | Output | After |
|---|
| R1 | A (orchestrator) | Propose | R1.md (+ code in repo if coding) | flip WAITING: B โ consult_handoff B + consult_wait |
| R2 | B (peer, one-shot) | Review proposal | R2.md (agreements + numbered critiques) | flip WAITING: <A's CLI name> โ exit (no handoff, no wait) |
| R3 | A (orchestrator) | Respond per critique (agree/partial/object) | R3.md | flip WAITING: B โ consult_handoff B + consult_wait |
| R4 | B (peer, one-shot) | Re-review (accept/double-down) | R4.md | flip WAITING: <A's CLI name> โ exit (no handoff, no wait) |
| R5 | A (orchestrator) | Synthesize, ask user | R5.md โ user reads this | stop โ STATUS: AWAITING_USER |
General rule for any odd n (ROUNDS): round 1 = A proposes; the final round n = A synthesizes (AWAITING_USER); interior rounds alternate โ even rounds = B reviews, odd rounds = A responds. n is always odd (forced at init) so A is both the first proposer and the last synthesizer. At n=3 it's propose ยท review ยท synthesize (one B round, a fresh review โ no re-review); at n=5 it's exactly the table above; at n=7 it's propose ยท review ยท respond ยท review ยท respond ยท re-review ยท synthesize.
After the final round, STATUS: AWAITING_USER and the loop stops. The user's reply (confirm / redirect / cancel) closes the session.
Two roles
The loop has an asymmetry that prevents duplicate instances โ internalize it before anything else.
- Orchestrator = A = the interactive session that ran
/pair-consult. It is alive for the whole session. It does the odd rounds (R1, R3, โฆ and the final round). After each non-final round it spawns the peer (consult_handoff B) and blocks in consult_wait until the peer flips STATE.md back. After the final round it stops.
- Peer = B = a fresh, headless, one-shot instance, cold-woken by the orchestrator's
consult_handoff. It does exactly one round (an even round โ R2, R4, โฆ), flips STATUS: WAITING: <A's CLI name>, and exits. It never calls consult_handoff and never calls consult_wait.
Why: consult_handoff always spawns a new instance of the named peer. If the peer (B) hands back with consult_handoff A, it spawns a second A โ while the original orchestrator A is still alive in consult_wait. Both then see WAITING: <A's CLI name>, both act, both write the round, and they collide on STATE.md (Error editing file). The only safe shape is: only the orchestrator hands off and waits; the peer flips-and-exits. A peer that is alive does not need to be re-spawned โ it's already waiting.
So consult_handoff + consult_wait are orchestrator-only verbs. If you were cold-woken by a resume prompt, you are the peer: flip and exit.
When to use
/pair-consult <question> (fresh) or /pair-consult (resume).
- Work fits in one round of proposal โ not a 5-step plan. If it doesn't fit, break it into smaller questions or use a full planning workflow.
- User wants a structured second opinion ending in their own go/no-go.
- You were invoked as the peer by the active agent.
Don't use for: multi-step implementation (use a full planning workflow), open-ended exploration (use brainstorming), one-shot code review (use code-review), or solo work.
Round protocol โ one allowed action per round
Each round is narrow on purpose. Stay in your lane. Templates for each round file are in reference/file-formats.md; the semantics are below โ each bullet's header names the rounds it covers (1:1 with R1โR5 at n=5; for larger odd n the review/respond actions repeat per the Overview rule).
- Propose (round 1, A). Read
QUESTION.md. For coding tasks, write the actual code in the repo, run the test, then write R1.md as the design rationale (not a code dump).
- Review (even rounds, B). Read the latest
R*.md and (for coding) git diff + run the tests yourself. Open numbered critiques in R<round>.md โ do not edit A's artifacts. Your critiques drive A's next round. Then flip STATUS: WAITING: <A's CLI name>, bump ROUND, and exit โ you are one-shot; do not hand off.
- Respond (odd interior rounds, A). For each numbered critique, write a verdict (
agree / partial / object), the action you took, and reasoning when not pure agreement. Address every critique โ skipping one is a bug. If you applied code changes, re-run the test and note the result in the relevant C-block.
- Re-review (round
n-1, the last B round โ only when n >= 5). Exists only when a preceding A-response round feeds it, so never at n=3 (there the one B round is a fresh review). For each item where A objected or partial-applied, decide accept or double down. No fresh critiques โ bugs A introduced in the prior round are double-downs with a sub-finding, not new Cs. This is B's last word. Then flip STATUS: WAITING: <A's CLI name>, set ROUND: <n>, and exit โ do not hand off (A is already waiting and writes the final round).
- Synthesize (round
n, A). Write the user-facing close: what we landed on, where we agreed, unresolved tensions plainly stated, what we need from the user. Then STATUS: AWAITING_USER and stop.
Early termination โ skip ahead when there's consensus
ROUNDS (default 5) is the max, not the requirement. Skip when the next round would be a rubber-stamp; when in doubt, do the round. The triggers are role-relative โ they hold at any n:
| Trigger | What to do |
|---|
| A B-review has zero critiques (full agreement) | Skip the rest of the interior rounds. A jumps to the final synthesis round directly. |
An A-response is all agree AND adds no new code/claims/reasoning | Skip the next B-review. A jumps to synthesis directly. |
An A-response is all agree but introduces new substance | Run the next B-review โ it catches regressions A introduced. (The skill's trial caught a real null-semantics bug this way.) |
An A-response has any object or partial | Run the next B-review. |
| The last B re-review is done | Always run the final synthesis round โ it's where the user is asked. |
When skipping ahead, jump straight to the final round (ROUND: <n>, A synthesizes, AWAITING_USER) โ early exit never lands the terminal step on B. Mark skipped round-log rows skipped, and call out the skip in the synthesis so the user sees the early exit.
Surfacing rounds in chat โ keep the user in the loop
After every round (yours OR the peer's), print a 5-15 line digest in chat before doing your next action. This is what makes pair-consult feel like a conversation instead of a black-box file machine. Use consult_digest <N> from reference/handoff.sh โ it extracts agreements + critique titles + per-C verdicts + overall verdict, already truncated.
**B's R2:**
- โ <agreement, one line>
- ! C1: <critique title>
- ! C2: <critique title>
**My R3:**
- โ Agreed on C1, C2, C3, C5
- โณ C4: partial โ kept the WITH ORDINALITY shape but added type placeholders
- One new claim: SQL-side dedupe preferable to app-side (going into R4)
The user can interrupt at any moment. Treat any user message as a steer โ address it before continuing.
Shared state in .consult/
Create .consult/ at the repo root on init and append .consult/ to .gitignore. Files:
| File | Purpose | Written by |
|---|
QUESTION.md | User's question + pinned context | Active agent on init |
STATE.md | ROUND, STATUS, A, B, round log | Every round |
R1.md โฆ R<ROUNDS>.md | Round content | Actor of that round |
USER_NOTES.md | User-injected steers (created lazily) | consult_inject |
session.log + round-<N>-<peer>.log | Peer stdout | consult_handoff |
A and B are fixed for the session โ whoever proposed in R1 is A. Full templates in reference/file-formats.md.
Entry modes
Both modes accept the optional flags in Flags (--number n, --model high|xhigh). Parse them off the invocation first, then write the resolved ROUNDS: and EFFORT: into STATE.md at init.
Mode 1 โ fresh question: /pair-consult "<question>" [--number n] [--model high|xhigh]
Standard init. You are the orchestrator (A). Write QUESTION.md and STATE.md (ROUND: 1, ROUNDS: <n>, STATUS: ACTIVE: <you>, A: <you>, B: <peer>, EFFORT: <effort>), then do R1 as described in the Round protocol. After R1, ROUND: 2, STATUS: WAITING: <peer>, then consult_handoff <peer> + consult_wait and stay alive for the rest of A's rounds (every odd round through the final).
Mode 2 โ continue from session: /pair-consult --from-session [--number n] [--model high|xhigh]
Use when you're mid-conversation with the user and you've just proposed something โ code, an approach, a decision โ and the user wants the peer to grill it. You are implicitly A; your most-recent proposal becomes R1 content.
- Write
QUESTION.md from the user's recent turn โ include enough context that B can act cold (B does NOT see chat history; only .consult/).
- Write
R1.md by summarizing your most-recent proposal โ don't restate the conversation, extract the proposal into the R1 template.
STATE.md: ROUND: 2, ROUNDS: <n>, STATUS: WAITING: <peer>, A: <you>, B: <peer>, EFFORT: <effort>. Round-log starts with the R1 row noting from-session.
consult_handoff <peer> + consult_wait โ B does R2 (one-shot) and flips back; your consult_wait resumes you for R3.
Flags
| Flag | Meaning | Default |
|---|
--number n (alias --rounds n) | Requested round cap / depth, written to ROUNDS: โ it's a max (early termination can end sooner). Normalized to odd and >= 3 so A both proposes and synthesizes: even n snaps up to n+1, n < 3 rises to 3. Always print a one-line reason before the loop, e.g. --number 4 can't end on A; using a 5-round cap so A synthesizes last. Larger n = longer wall-clock (each round is a fresh peer cold-start), so flag very large values. | 5 |
--model high|xhigh | Peer reasoning effort, written to EFFORT:. consult_handoff injects codex exec -c model_reasoning_effort=<v> / claude --effort <v>. Omitted โ empty EFFORT:, each CLI uses its own default. | unset |
Note: only high and xhigh are accepted. Your ~/.codex/config.toml may already default codex to xhigh, so --model xhigh is often a no-op for the codex peer; --model high is what visibly steps it down.
Auto-detect Mode 2: when /pair-consult is invoked with no question argument AND no existing .consult/ AND the recent conversation contains a proposal you authored, default to Mode 2 without requiring --from-session explicitly. Otherwise prompt the user for a question.
If .consult/ already exists, don't overwrite โ treat as resume; show consult_status and ask whether to continue or rm -rf .consult/ for a fresh start.
Handoff
consult_handoff + consult_wait are orchestrator-only. You run them only after your own non-final A round (R1, R3, โฆ any odd round before the last) โ never as a cold-woken peer (see Two roles). A peer flips STATE.md and exits; the orchestrator's existing consult_wait picks it up.
After writing your round (as the orchestrator) and updating STATE.md:
source ~/.claude/skills/pair-consult/reference/handoff.sh
consult_handoff codex
consult_handoff does the headless invocation correctly (nohup + detach, -- terminator for claude, stdbuf for live logging, and injecting the EFFORT: flag per CLI). Do not call this at the final round (ROUND == ROUNDS) โ the function will refuse and tell you to set STATUS: AWAITING_USER.
Then wait โ the handoff isn't tracked by the harness
consult_handoff uses nohup ... & to detach the peer, which takes it outside the Claude Code harness's process tracking. The harness will NOT notify you when the peer's round lands. If you simply end your turn after consult_handoff, you will sit idle until the user pings (which surfaces a file-change reminder) or until a ScheduleWakeup fires โ both unreliable and high-latency.
The fix: immediately after consult_handoff, call consult_wait so the harness has something to track:
# Claude Code (this is the important case):
Bash(
command="source ~/.claude/skills/pair-consult/reference/handoff.sh && consult_wait",
run_in_background=true,
)
# Codex CLI / inline: call consult_wait in the foreground โ it will block until the peer flips STATE.md.
consult_wait polls .consult/STATE.md every 5s and exits the moment the STATUS line flips off WAITING: <peer>. When invoked via Bash(run_in_background=true), the harness tracks the poll-process and notifies you on exit โ you learn about R<N+1> at file-write time, not at next-user-ping time.
Behavior on exit:
- Exit 0 โ your turn. Read the new round file, surface a digest, do your round.
- Exit 2 โ timeout (default 540s, just under Bash's 600s ceiling). Re-invoke
consult_wait. A peer round legitimately taking >9 min is rare but possible (large code reviews, slow networks).
- Exit 3 โ the peer likely crashed (no process holds the round log open and the file is stale). Run
consult_status and consult_peer_status <peer> to investigate before retrying.
Hazards
All guarded by consult_handoff โ listed here so you don't reinvent them.
| Hazard | Rule |
|---|
claude -p "<prompt>" <flags> โ flags after prompt hang the CLI | Flags first; use consult_handoff. |
claude --add-dir <dir> "<prompt>" โ variadic flag eats prompt | -- terminator before the prompt. |
claude -p --bare โ fails auth unless ANTHROPIC_API_KEY is set | Don't use --bare. |
killall claude / pkill codex to recover | Kills the user's main session. Use consult_peer_status <peer> to list specific PIDs. |
Steering as a human
While the loop runs:
| Command | What it does |
|---|
consult_watch | tail -F .consult/session.log across all rounds. |
consult_status | One-screen summary: STATE.md + open user notes + last 20 log lines. |
consult_inject "<note>" | Append a USER_NOTE to .consult/USER_NOTES.md. Next agent must address it before their round. |
consult_takeover | Kill peer by PID (via lsof), set STATUS: BLOCKED: human-takeover. |
consult_resume <peer> | After takeover + manual edits, hand back. Does NOT bump ROUND. |
consult_digest <N> | Print the terse digest of R<N>.md โ used by the active agent to surface in chat. |
Common mistakes
| Mistake | Fix |
|---|
| Doing more than one round's work in a turn | R1 proposes, doesn't preempt R2. R2 critiques, doesn't rewrite A's code. |
| Skipping critiques in R3 | Address every numbered critique. Even object is an answer. |
| Adding new critiques in R4 | R4 is accept / double down only. New bugs from R3 are double-downs with sub-findings. |
Peer (B) calling consult_handoff/consult_wait after a B round (R2, R4, โฆ) | This spawns a duplicate orchestrator that races the live one โ two instances write the same round and collide on STATE.md (Error editing file). The peer is one-shot: flip STATUS: WAITING: <A's CLI name> and exit. Only A hands off and waits. See Two roles. |
Writing STATUS: WAITING: A (the role letter) | STATUS names the CLI: WAITING: claude / WAITING: codex โ consult_handoff matches on the name. Read it off STATE.md's A: line. |
| Invoking peer at the final round | The final round (ROUND == ROUNDS) ends the loop. Surface to user. |
| R5 hides unresolved tensions | Surface them plainly. Let the user decide. |
Idling after consult_handoff ("harness will notify me") | It won't โ the peer is nohup'd, outside harness tracking. Always follow consult_handoff with consult_wait (via Bash(run_in_background=true) in Claude Code) so the harness has a tracked process to notify on. |
| Telling the user "I'll wait for the peer" with no scheduled wakeup or background wait | Same root cause as above. The wait must be a real tracked process, not an intention. |
Resuming
/pair-consult with no args + existing .consult/ โ resume:
WAITING: <you> โ do your round.
WAITING: <peer> โ loop is mid-flight; consult_watch or report state.
AWAITING_USER โ show the final round file (R<ROUNDS>.md) and ask what they want.
BLOCKED โ tell the user what's blocked; offer consult_resume <peer>.