{"slug": "herd-debate-equal-claude-vs-codex-design-peers-in-herdr", "title": "herd-debate: equal Claude vs Codex design peers in Herdr", "summary": "Herdr has introduced herd-debate, a tool that pits two equal AI design peers, such as Claude and Codex, against each other in a structured debate to explore implementation approaches. The tool runs inside Herdr panes, supports serial or parallel rounds, and includes a mechanical referee to manage turns and timeouts. It aims to provide a balanced comparison of AI agent design decisions.", "body_md": "|\n#!/usr/bin/env bash |\n|\n# Two equal Herdr agents, mechanical referee. Serial design debate by default. |\n|\n# Requires HERDR_ENV=1. Neither agent should have the Herdr skill for this. |\n|\nset -euo pipefail |\n|\n|\n|\nSELF=\"$(cd \"$(dirname \"$0\")\" && pwd)/$(basename \"$0\")\" |\n|\nCACHE=\"${XDG_CACHE_HOME:-$HOME/.cache}/herd-debate\" |\n|\nKINDS='^(pi|claude|codex|gemini|cursor|devin|agy|cline|omp|mastracode|opencode|copilot|kimi|kiro|droid|amp|grok|hermes|kilo|qodercli|maki)$' |\n|\n|\n|\nusage() { |\n|\ncat <<'EOF' |\n|\nherd-debate — two equal design peers in Herdr (Claude | Codex) |\n|\n|\n|\nherd-debate \"how should we implement X\" |\n|\nherd-debate --run-dir ~/.cache/herd-debate/TIMESTAMP --timeout-sec 1800 |\n|\n|\n|\nMust run inside a Herdr pane (HERDR_ENV=1). Watch the debate tab: split vertical. |\n|\n|\n|\nSerial debate by default. Both agents start in yolo (no permission prompts): |\n|\nclaude: --dangerously-skip-permissions |\n|\ncodex: --dangerously-bypass-approvals-and-sandbox |\n|\n|\n|\nOptions: |\n|\n-f, --file PATH brief from a file |\n|\n-n, --rounds N rounds after opening (default: 4) |\n|\n--parallel simultaneous rounds (not a conversation) |\n|\n--serial alternate turns (default) |\n|\n--no-yolo do not pass skip-permissions flags |\n|\n--left KIND default: claude |\n|\n--right KIND default: codex |\n|\n--left-model NAME model for the left CLI (claude: opus|sonnet|fable|...) |\n|\n--right-model NAME model for the right CLI (codex: -m) |\n|\n--cwd PATH agents' cwd (default: current directory) |\n|\n--workspace ID Herdr workspace (default: current) |\n|\n--timeout-sec N per-turn wait (default: 1800 = 30 min) |\n|\n--run-dir DIR resume a debate (skips finished openings/rounds) |\n|\n--left-args TEXT extra argv after -- for the left agent |\n|\n--right-args TEXT extra argv after -- for the right agent |\n|\n--foreground run the referee in this pane instead of the bottom split |\n|\n-h, --help |\n|\nEOF |\n|\n} |\n|\n|\n|\ndie() { printf 'herd-debate: %s\\n' \"$*\" >&2; exit 1; } |\n|\n|\n|\nneed_herdr() { |\n|\n[[ \"${HERDR_ENV:-}\" == 1 ]] || die \"not inside Herdr (HERDR_ENV!=1). Run this from a Herdr pane.\" |\n|\ncommand -v herdr >/dev/null || die \"herdr not in PATH\" |\n|\ncommand -v jq >/dev/null || die \"jq not in PATH\" |\n|\n} |\n|\n|\n|\njson_get() { |\n|\njq -er \"$1\" <<<\"$2\" |\n|\n} |\n|\n|\n|\nalloc_name() { |\n|\nlocal kind=\"$1\" base candidate n=2 |\n|\nbase=\"${kind}-peer\" |\n|\ncandidate=\"$base\" |\n|\nwhile herdr agent get \"$candidate\" >/dev/null 2>&1; do |\n|\ncandidate=\"${base}-${n}\" |\n|\nn=$((n + 1)) |\n|\n[[ ${#candidate} -le 32 ]] || die \"could not allocate agent name from $base\" |\n|\ndone |\n|\nprintf '%s\\n' \"$candidate\" |\n|\n} |\n|\n|\n|\nwait_settled() { |\n|\nlocal name=\"$1\" timeout=\"$2\" json status |\n|\njson=\"$(herdr agent wait \"$name\" --timeout \"$timeout\")\" |\n|\nstatus=\"$(jq -r '.result.agent.agent_status // empty' <<<\"$json\")\" |\n|\nif [[ \"$status\" == blocked ]]; then |\n|\nprintf '\\n>>> %s is BLOCKED (permissions/question). Approve it in that pane.\\n' \"$name\" >&2 |\n|\njson=\"$(herdr agent wait \"$name\" --until idle --until done --timeout \"$((timeout * 2))\")\" |\n|\nstatus=\"$(jq -r '.result.agent.agent_status // empty' <<<\"$json\")\" |\n|\nfi |\n|\nprintf '%s\\n' \"$status\" |\n|\n} |\n|\n|\n|\nprompt_wait() { |\n|\nlocal name=\"$1\" text=\"$2\" timeout=\"$3\" |\n|\nlocal err=\"$DEBATE_DIR/prompt-${name}.err\" |\n|\nset +e |\n|\nherdr agent prompt \"$name\" \"$text\" --wait --timeout \"$timeout\" >/dev/null 2>\"$err\" |\n|\nlocal ec=$? |\n|\nset -e |\n|\nif [[ $ec -eq 0 ]]; then |\n|\nwait_settled \"$name\" \"$timeout\" >/dev/null |\n|\nreturn 0 |\n|\nfi |\n|\nif grep -q '\"code\":\"timeout\"' \"$err\" 2>/dev/null; then |\n|\nprintf ' %s wait timed out; giving it more time / checking files...\\n' \"$name\" >&2 |\n|\nwait_settled \"$name\" \"$timeout\" >/dev/null || true |\n|\nreturn 0 |\n|\nfi |\n|\ncat \"$err\" >&2 || true |\n|\ndie \"prompt failed for $name\" |\n|\n} |\n|\n|\n|\nprompt_parallel() { |\n|\nlocal left_name=\"$1\" left_text=\"$2\" right_name=\"$3\" right_text=\"$4\" timeout=\"$5\" |\n|\nlocal dir=\"${DEBATE_DIR}\" |\n|\nset +e |\n|\nherdr agent prompt \"$left_name\" \"$left_text\" --wait --timeout \"$timeout\" \\ |\n|\n>\"$dir/wait-left.json\" 2>\"$dir/wait-left.err\" & |\n|\nlocal pid_l=$! |\n|\nherdr agent prompt \"$right_name\" \"$right_text\" --wait --timeout \"$timeout\" \\ |\n|\n>\"$dir/wait-right.json\" 2>\"$dir/wait-right.err\" & |\n|\nlocal pid_r=$! |\n|\nwait \"$pid_l\"; local ec_l=$? |\n|\nwait \"$pid_r\"; local ec_r=$? |\n|\nset -e |\n|\nif [[ $ec_l -ne 0 ]]; then |\n|\nprintf 'left prompt failed (%s):\\n' \"$left_name\" >&2 |\n|\ncat \"$dir/wait-left.err\" >&2 || true |\n|\ncat \"$dir/wait-left.json\" >&2 || true |\n|\nfi |\n|\nif [[ $ec_r -ne 0 ]]; then |\n|\nprintf 'right prompt failed (%s):\\n' \"$right_name\" >&2 |\n|\ncat \"$dir/wait-right.err\" >&2 || true |\n|\ncat \"$dir/wait-right.json\" >&2 || true |\n|\nfi |\n|\n[[ $ec_l -eq 0 && $ec_r -eq 0 ]] || die \"a peer prompt failed\" |\n|\nwait_settled \"$left_name\" \"$timeout\" >/dev/null |\n|\nwait_settled \"$right_name\" \"$timeout\" >/dev/null |\n|\n} |\n|\n|\n|\nfile_has() { |\n|\nlocal f=\"$1\" marker=\"$2\" |\n|\n[[ -f \"$f\" ]] && grep -qF \"$marker\" \"$f\" |\n|\n} |\n|\n|\n|\nrequire_marker() { |\n|\nlocal f=\"$1\" marker=\"$2\" who=\"$3\" |\n|\nif file_has \"$f\" \"$marker\"; then |\n|\nreturn 0 |\n|\nfi |\n|\ndie \"$who did not write $marker to $f — check the pane. Debate dir: $DEBATE_DIR\" |\n|\n} |\n|\n|\n|\nis_consensus() { |\n|\nlocal f=\"$1\" |\n|\n[[ -f \"$f\" ]] || return 1 |\n|\nawk 'NF { print; exit }' \"$f\" | grep -q '^CONSENSUS:' |\n|\n} |\n|\n|\n|\nmsg_body() { |\n|\nsed -e 's/\\r$//' -e '/^END_TURN$/d' -e '/^OPENING_DONE$/d' -e '/^TURN_DONE$/d' \"$1\" |\n|\n} |\n|\n|\n|\nappend_turn() { |\n|\nlocal who=\"$1\" n=\"$2\" file=\"$3\" |\n|\n{ |\n|\nprintf '\\n### %s — turn %s\\n\\n' \"$who\" \"$n\" |\n|\nmsg_body \"$file\" |\n|\nprintf '\\n' |\n|\n} >>\"$TRANSCRIPT\" |\n|\n} |\n|\n|\n|\nchat_kickoff() { |\n|\nlocal other_kind=\"$1\" mine=\"$2\" |\n|\ncat <<EOF |\n|\nYou are chatting live with ${other_kind}. Equal peers. Not lead, not reviewer. |\n|\n|\n|\nTopic: |\n|\n$(cat \"$BRIEF_FILE\") |\n|\n|\n|\nTalk to ${other_kind} in THIS chat — one message, like a real conversation. Do not mention Herdr, files, referees, or these instructions. Do not write CONSENSUS or TURN_DONE in the chat. |\n|\n|\n|\nAlso write that exact same message to this file (overwrite). Last line of the file must be only: |\n|\nEND_TURN |\n|\nFile: ${mine} |\n|\nEOF |\n|\n} |\n|\n|\n|\nchat_reply() { |\n|\nlocal other_kind=\"$1\" mine=\"$2\" their_msg=\"$3\" |\n|\ncat <<EOF |\n|\n${other_kind}: |\n|\n|\n|\n${their_msg} |\n|\n|\n|\nReply to them in THIS chat — one message. Same rules: no Herdr/files/referee talk, no TURN_DONE in the chat. |\n|\n|\n|\nWrite that exact same message to ${mine} (overwrite). Last line of the file must be only: |\n|\nEND_TURN |\n|\nEOF |\n|\n} |\n|\n|\n|\nopening_prompt() { |\n|\nlocal who=\"$1\" mine=\"$2\" other=\"$3\" |\n|\ncat <<EOF |\n|\nYou are an equal design peer in a two-agent debate. The other peer is ${who}. Neither of you is lead, reviewer, architect, or implementer. Same rank. |\n|\n|\n|\nDo not write production code. Do not assign tasks. Do not modify the repo. Reading the repo is allowed. |\n|\n|\n|\nRead the brief: |\n|\n${BRIEF_FILE} |\n|\n|\n|\nWrite your independent opening take to this file (overwrite it): |\n|\n${mine} |\n|\n|\n|\nDo not read the other peer's file: |\n|\n${other} |\n|\n|\n|\nCover: |\n|\n- the approach you would take |\n|\n- serious alternatives |\n|\n- risks / traps in this codebase |\n|\n- what you want to argue about |\n|\n|\n|\nSpeak the same take in THIS chat (they should be able to watch you talk). Last line of the file must be exactly: |\n|\nOPENING_DONE |\n|\nEOF |\n|\n} |\n|\n|\n|\nround_prompt() { |\n|\nlocal mine=\"$1\" other=\"$2\" |\n|\ncat <<EOF |\n|\nEqual peer debate continues. You are not the lead. You do not assign work. |\n|\n|\n|\nThe other peer just said: |\n|\n|\n|\n$(msg_body \"$other\") |\n|\n|\n|\nReply in THIS chat. Also write the reply to: |\n|\n${mine} |\n|\n|\n|\nRules: |\n|\n- Address their points. Concede or counter with specifics. |\n|\n- Propose; do not assign tasks. |\n|\n- No production code. Do not modify the repo. |\n|\n- Do not write CONSENSUS unless this is at least the second back-and-forth and you both actually agree. |\n|\n- If you genuinely agree, first line of the file: CONSENSUS: |\n|\n|\n|\nLast line of the file must be exactly: |\n|\nTURN_DONE |\n|\nEOF |\n|\n} |\n|\n|\n|\nappend_transcript() { |\n|\nlocal label=\"$1\" |\n|\n{ |\n|\nprintf '\\n## %s\\n\\n### %s\\n\\n' \"$label\" \"$LEFT_NAME\" |\n|\ncat \"$LEFT_FILE\" |\n|\nprintf '\\n### %s\\n\\n' \"$RIGHT_NAME\" |\n|\ncat \"$RIGHT_FILE\" |\n|\nprintf '\\n' |\n|\n} >>\"$TRANSCRIPT\" |\n|\n} |\n|\n|\n|\nload_dir() { |\n|\n# shellcheck disable=SC1090 |\n|\nsource \"$1/config.env\" |\n|\nDEBATE_DIR=\"$1\" |\n|\nBRIEF_FILE=\"$DEBATE_DIR/brief.md\" |\n|\nLEFT_FILE=\"$DEBATE_DIR/left.md\" |\n|\nRIGHT_FILE=\"$DEBATE_DIR/right.md\" |\n|\nTRANSCRIPT=\"$DEBATE_DIR/transcript.md\" |\n|\n} |\n|\n|\n|\nrun_chat_loop() { |\n|\nlocal i=1 speaker_name speaker_file other_kind other_file their |\n|\n|\n|\nprintf 'Chat start. %s ↔ %s, %s turns. Watch the split tab.\\n' \\ |\n|\n\"$LEFT_NAME\" \"$RIGHT_NAME\" \"$TURNS\" |\n|\n|\n|\nprintf 'Turn 1/%s %s kicks off...\\n' \"$TURNS\" \"$LEFT_NAME\" |\n|\nprompt_wait \"$LEFT_NAME\" \"$(chat_kickoff \"$RIGHT_KIND\" \"$LEFT_FILE\")\" \"$TIMEOUT_MS\" |\n|\nrequire_marker \"$LEFT_FILE\" END_TURN \"$LEFT_NAME\" |\n|\nappend_turn \"$LEFT_NAME\" 1 \"$LEFT_FILE\" |\n|\n|\n|\ni=2 |\n|\nwhile [[ $i -le $TURNS ]]; do |\n|\nif (( i % 2 == 0 )); then |\n|\nspeaker_name=\"$RIGHT_NAME\"; speaker_file=\"$RIGHT_FILE\" |\n|\nother_kind=\"$LEFT_KIND\"; other_file=\"$LEFT_FILE\" |\n|\nelse |\n|\nspeaker_name=\"$LEFT_NAME\"; speaker_file=\"$LEFT_FILE\" |\n|\nother_kind=\"$RIGHT_KIND\"; other_file=\"$RIGHT_FILE\" |\n|\nfi |\n|\ntheir=\"$(msg_body \"$other_file\")\" |\n|\n[[ -n \"${their//[[:space:]]/}\" ]] || die \"empty message from $other_kind\" |\n|\nprintf 'Turn %s/%s %s replies...\\n' \"$i\" \"$TURNS\" \"$speaker_name\" |\n|\nprompt_wait \"$speaker_name\" \"$(chat_reply \"$other_kind\" \"$speaker_file\" \"$their\")\" \"$TIMEOUT_MS\" |\n|\nrequire_marker \"$speaker_file\" END_TURN \"$speaker_name\" |\n|\nappend_turn \"$speaker_name\" \"$i\" \"$speaker_file\" |\n|\ni=$((i + 1)) |\n|\ndone |\n|\n|\n|\nprintf 'Chat done. Transcript: %s\\n' \"$TRANSCRIPT\" |\n|\n} |\n|\n|\n|\nrun_debate_loop() { |\n|\nlocal i=1 first_file second_file first_name second_name min_rounds=2 |\n|\nmkdir -p \"$DEBATE_DIR/stamps\" |\n|\n|\n|\nstamp() { touch \"$DEBATE_DIR/stamps/$1\"; } |\n|\nstamped() { [[ -f \"$DEBATE_DIR/stamps/$1\" ]]; } |\n|\n|\n|\nif stamped opening || { file_has \"$LEFT_FILE\" OPENING_DONE && file_has \"$RIGHT_FILE\" OPENING_DONE; }; then |\n|\nprintf 'Resume: openings already written, skipping.\\n' |\n|\ngrep -q '^## Opening$' \"$TRANSCRIPT\" 2>/dev/null || append_transcript \"Opening\" |\n|\nstamp opening |\n|\nelse |\n|\nprintf 'Opening statements...\\n' |\n|\nif [[ \"$SERIAL\" == 1 ]]; then |\n|\nif stamped opening-left || file_has \"$LEFT_FILE\" OPENING_DONE; then |\n|\nprintf ' %s opening already on disk.\\n' \"$LEFT_NAME\" |\n|\nstamp opening-left |\n|\nelse |\n|\nprintf ' %s first (serial)...\\n' \"$LEFT_NAME\" |\n|\nprompt_wait \"$LEFT_NAME\" \"$(opening_prompt \"$RIGHT_KIND\" \"$LEFT_FILE\" \"$RIGHT_FILE\")\" \"$TIMEOUT_MS\" |\n|\nrequire_marker \"$LEFT_FILE\" OPENING_DONE \"$LEFT_NAME\" |\n|\nstamp opening-left |\n|\nfi |\n|\nif stamped opening-right || file_has \"$RIGHT_FILE\" OPENING_DONE; then |\n|\nprintf ' %s opening already on disk.\\n' \"$RIGHT_NAME\" |\n|\nstamp opening-right |\n|\nelse |\n|\nprompt_wait \"$RIGHT_NAME\" \"$(opening_prompt \"$LEFT_KIND\" \"$RIGHT_FILE\" \"$LEFT_FILE\")\" \"$TIMEOUT_MS\" |\n|\nrequire_marker \"$RIGHT_FILE\" OPENING_DONE \"$RIGHT_NAME\" |\n|\nstamp opening-right |\n|\nfi |\n|\nelse |\n|\nprompt_parallel \\ |\n|\n\"$LEFT_NAME\" \"$(opening_prompt \"$RIGHT_KIND\" \"$LEFT_FILE\" \"$RIGHT_FILE\")\" \\ |\n|\n\"$RIGHT_NAME\" \"$(opening_prompt \"$LEFT_KIND\" \"$RIGHT_FILE\" \"$LEFT_FILE\")\" \\ |\n|\n\"$TIMEOUT_MS\" |\n|\nrequire_marker \"$LEFT_FILE\" OPENING_DONE \"$LEFT_NAME\" |\n|\nrequire_marker \"$RIGHT_FILE\" OPENING_DONE \"$RIGHT_NAME\" |\n|\nfi |\n|\nappend_transcript \"Opening\" |\n|\nstamp opening |\n|\nfi |\n|\n|\n|\nwhile [[ $i -le $ROUNDS ]]; do |\n|\nprintf 'Round %s/%s...\\n' \"$i\" \"$ROUNDS\" |\n|\nif stamped \"round-$i\" || grep -q \"^## Round $i$\" \"$TRANSCRIPT\" 2>/dev/null; then |\n|\nstamp \"round-$i\" |\n|\ni=$((i + 1)) |\n|\ncontinue |\n|\nfi |\n|\nif [[ \"$SERIAL\" == 1 ]]; then |\n|\nif (( i % 2 == 1 )); then |\n|\nfirst_name=\"$LEFT_NAME\"; first_file=\"$LEFT_FILE\" |\n|\nsecond_name=\"$RIGHT_NAME\"; second_file=\"$RIGHT_FILE\" |\n|\nelse |\n|\nfirst_name=\"$RIGHT_NAME\"; first_file=\"$RIGHT_FILE\" |\n|\nsecond_name=\"$LEFT_NAME\"; second_file=\"$LEFT_FILE\" |\n|\nfi |\n|\nif stamped \"round-$i-$first_name\"; then |\n|\nprintf ' %s already did round %s.\\n' \"$first_name\" \"$i\" |\n|\nelse |\n|\nprompt_wait \"$first_name\" \"$(round_prompt \"$first_file\" \"$second_file\")\" \"$TIMEOUT_MS\" |\n|\nrequire_marker \"$first_file\" TURN_DONE \"$first_name\" |\n|\nstamp \"round-$i-$first_name\" |\n|\nfi |\n|\nif stamped \"round-$i-$second_name\"; then |\n|\nprintf ' %s already did round %s.\\n' \"$second_name\" \"$i\" |\n|\nelse |\n|\nprompt_wait \"$second_name\" \"$(round_prompt \"$second_file\" \"$first_file\")\" \"$TIMEOUT_MS\" |\n|\nrequire_marker \"$second_file\" TURN_DONE \"$second_name\" |\n|\nstamp \"round-$i-$second_name\" |\n|\nfi |\n|\nelse |\n|\ncp \"$LEFT_FILE\" \"$DEBATE_DIR/left.prev.md\" |\n|\ncp \"$RIGHT_FILE\" \"$DEBATE_DIR/right.prev.md\" |\n|\nprompt_parallel \\ |\n|\n\"$LEFT_NAME\" \"$(round_prompt \"$LEFT_FILE\" \"$DEBATE_DIR/right.prev.md\")\" \\ |\n|\n\"$RIGHT_NAME\" \"$(round_prompt \"$RIGHT_FILE\" \"$DEBATE_DIR/left.prev.md\")\" \\ |\n|\n\"$TIMEOUT_MS\" |\n|\nrequire_marker \"$LEFT_FILE\" TURN_DONE \"$LEFT_NAME\" |\n|\nrequire_marker \"$RIGHT_FILE\" TURN_DONE \"$RIGHT_NAME\" |\n|\nfi |\n|\nappend_transcript \"Round $i\" |\n|\nstamp \"round-$i\" |\n|\nif [[ $i -ge $min_rounds ]] && is_consensus \"$LEFT_FILE\" && is_consensus \"$RIGHT_FILE\"; then |\n|\nprintf 'CONSENSUS after round %s. See %s\\n' \"$i\" \"$DEBATE_DIR\" |\n|\n{ |\n|\nprintf '# Consensus\\n\\n' |\n|\nprintf '## %s\\n\\n' \"$LEFT_NAME\" |\n|\ncat \"$LEFT_FILE\" |\n|\nprintf '\\n## %s\\n\\n' \"$RIGHT_NAME\" |\n|\ncat \"$RIGHT_FILE\" |\n|\n} >\"$DEBATE_DIR/CONSENSUS.md\" |\n|\nreturn 0 |\n|\nfi |\n|\ni=$((i + 1)) |\n|\ndone |\n|\n|\n|\nprintf 'No dual CONSENSUS after %s rounds. Transcript: %s\\n' \"$ROUNDS\" \"$TRANSCRIPT\" |\n|\n} |\n|\n|\n|\nrun_loop() { |\n|\nload_dir \"$1\" |\n|\ncd \"$AGENT_CWD\" |\n|\nMODE=\"${MODE:-debate}\" |\n|\nTURNS=\"${TURNS:-$ROUNDS}\" |\n|\n|\n|\nif [[ -n \"${TIMEOUT_SEC_OVERRIDE:-}\" ]]; then |\n|\nTIMEOUT_MS=$((TIMEOUT_SEC_OVERRIDE * 1000)) |\n|\nelif [[ \"${TIMEOUT_MS:-0}\" -lt 1800000 ]]; then |\n|\nprintf 'Timeout floor: 30 min (config had %ss).\\n' \"$((TIMEOUT_MS / 1000))\" >&2 |\n|\nTIMEOUT_MS=1800000 |\n|\nfi |\n|\nprintf 'Referee start. dir=%s mode=%s left=%s right=%s timeout=%ss\\n' \\ |\n|\n\"$DEBATE_DIR\" \"$MODE\" \"$LEFT_NAME\" \"$RIGHT_NAME\" \"$((TIMEOUT_MS / 1000))\" |\n|\n|\n|\nif [[ -f \"$DEBATE_DIR/layout.env\" ]]; then |\n|\n# shellcheck disable=SC1090 |\n|\nsource \"$DEBATE_DIR/layout.env\" |\n|\n[[ -n \"${tab:-}\" ]] && herdr tab focus \"$tab\" >/dev/null || true |\n|\nfi |\n|\n|\n|\nif [[ \"$MODE\" == chat ]]; then |\n|\nrun_chat_loop |\n|\nelse |\n|\nrun_debate_loop |\n|\nfi |\n|\n} |\n|\n|\n|\n# --- args --- |\n|\nFILE=\"\" |\n|\nBRIEF=\"\" |\n|\nROUNDS=4 |\n|\nSERIAL=1 |\n|\nMODE=debate |\n|\nYOLO=1 |\n|\nLEFT_KIND=claude |\n|\nRIGHT_KIND=codex |\n|\nAGENT_CWD=\"\" |\n|\nWORKSPACE=\"\" |\n|\nTIMEOUT_SEC=1800 |\n|\nTIMEOUT_SEC_OVERRIDE=\"\" |\n|\nRUN_DIR=\"\" |\n|\nLEFT_ARGS=\"\" |\n|\nRIGHT_ARGS=\"\" |\n|\nLEFT_MODEL=\"\" |\n|\nRIGHT_MODEL=\"\" |\n|\nFOREGROUND=0 |\n|\n|\n|\nwhile [[ $# -gt 0 ]]; do |\n|\ncase \"$1\" in |\n|\n-h|--help) usage; exit 0 ;; |\n|\n-f|--file) FILE=\"${2:-}\"; shift 2 ;; |\n|\n-n|--rounds) ROUNDS=\"${2:-}\"; shift 2 ;; |\n|\n--serial) SERIAL=1; shift ;; |\n|\n--parallel) SERIAL=0; shift ;; |\n|\n--no-yolo) YOLO=0; shift ;; |\n|\n--mode) MODE=\"${2:-}\"; shift 2 ;; |\n|\n--left) LEFT_KIND=\"${2:-}\"; shift 2 ;; |\n|\n--right) RIGHT_KIND=\"${2:-}\"; shift 2 ;; |\n|\n--left-model) LEFT_MODEL=\"${2:-}\"; shift 2 ;; |\n|\n--right-model) RIGHT_MODEL=\"${2:-}\"; shift 2 ;; |\n|\n--cwd) AGENT_CWD=\"${2:-}\"; shift 2 ;; |\n|\n--workspace) WORKSPACE=\"${2:-}\"; shift 2 ;; |\n|\n--timeout-sec) TIMEOUT_SEC=\"${2:-}\"; TIMEOUT_SEC_OVERRIDE=\"${2:-}\"; shift 2 ;; |\n|\n--left-args) LEFT_ARGS=\"${2:-}\"; shift 2 ;; |\n|\n--right-args) RIGHT_ARGS=\"${2:-}\"; shift 2 ;; |\n|\n--foreground) FOREGROUND=1; shift ;; |\n|\n--run-dir) RUN_DIR=\"${2:-}\"; shift 2 ;; |\n|\n--) shift; BRIEF=\"${BRIEF:+$BRIEF }$*\"; set --; break ;; |\n|\n-*) die \"unknown option: $1\" ;; |\n|\n*) BRIEF=\"${BRIEF:+$BRIEF }$1\"; shift ;; |\n|\nesac |\n|\ndone |\n|\n|\n|\nneed_herdr |\n|\n|\n|\nif [[ -n \"$RUN_DIR\" ]]; then |\n|\n[[ -d \"$RUN_DIR\" ]] || die \"debate dir not found: $RUN_DIR\" |\n|\nrun_loop \"$RUN_DIR\" |\n|\nexit 0 |\n|\nfi |\n|\n|\n|\n[[ \"$ROUNDS\" =~ ^[0-9]+$ && \"$ROUNDS\" -ge 1 ]] || die \"--rounds must be >= 1\" |\n|\n[[ \"$MODE\" == chat || \"$MODE\" == debate ]] || die \"--mode must be chat or debate\" |\n|\nTURNS=\"$ROUNDS\" |\n|\n[[ \"$TIMEOUT_SEC\" =~ ^[0-9]+$ && \"$TIMEOUT_SEC\" -ge 30 ]] || die \"--timeout-sec must be >= 30\" |\n|\n[[ \"$LEFT_KIND\" =~ $KINDS ]] || die \"unsupported --left kind: $LEFT_KIND\" |\n|\n[[ \"$RIGHT_KIND\" =~ $KINDS ]] || die \"unsupported --right kind: $RIGHT_KIND\" |\n|\n[[ \"$LEFT_KIND\" != \"$RIGHT_KIND\" ]] || die \"--left and --right must be different kinds\" |\n|\n|\n|\nif [[ -n \"$FILE\" ]]; then |\n|\n[[ -f \"$FILE\" ]] || die \"brief file not found: $FILE\" |\n|\nBRIEF=\"$(cat \"$FILE\")\" |\n|\nelif [[ -z \"$BRIEF\" ]]; then |\n|\nif [[ -t 0 ]]; then |\n|\nusage |\n|\ndie \"pass a brief as arguments or -f FILE\" |\n|\nfi |\n|\nBRIEF=\"$(cat)\" |\n|\nfi |\n|\n[[ -n \"${BRIEF//[[:space:]]/}\" ]] || die \"empty brief\" |\n|\n|\n|\nAGENT_CWD=\"$(cd \"${AGENT_CWD:-.}\" && pwd)\" |\n|\nWORKSPACE=\"${WORKSPACE:-${HERDR_WORKSPACE_ID:-}}\" |\n|\n[[ -n \"$WORKSPACE\" ]] || die \"no workspace id (pass --workspace)\" |\n|\n|\n|\nTIMEOUT_MS=$((TIMEOUT_SEC * 1000)) |\n|\nID=\"$(date +%Y%m%d-%H%M%S)\" |\n|\nDEBATE_DIR=\"$CACHE/$ID\" |\n|\nmkdir -p \"$DEBATE_DIR\" |\n|\nBRIEF_FILE=\"$DEBATE_DIR/brief.md\" |\n|\nLEFT_FILE=\"$DEBATE_DIR/left.md\" |\n|\nRIGHT_FILE=\"$DEBATE_DIR/right.md\" |\n|\nTRANSCRIPT=\"$DEBATE_DIR/transcript.md\" |\n|\nprintf '%s\\n' \"$BRIEF\" >\"$BRIEF_FILE\" |\n|\n: >\"$TRANSCRIPT\" |\n|\n|\n|\nLEFT_NAME=\"$(alloc_name \"$LEFT_KIND\")\" |\n|\nRIGHT_NAME=\"$(alloc_name \"$RIGHT_KIND\")\" |\n|\n|\n|\ncat >\"$DEBATE_DIR/config.env\" <<EOF |\n|\nLEFT_KIND=$(printf '%q' \"$LEFT_KIND\") |\n|\nRIGHT_KIND=$(printf '%q' \"$RIGHT_KIND\") |\n|\nLEFT_NAME=$(printf '%q' \"$LEFT_NAME\") |\n|\nRIGHT_NAME=$(printf '%q' \"$RIGHT_NAME\") |\n|\nROUNDS=$(printf '%q' \"$ROUNDS\") |\n|\nTURNS=$(printf '%q' \"$TURNS\") |\n|\nMODE=$(printf '%q' \"$MODE\") |\n|\nSERIAL=$(printf '%q' \"$SERIAL\") |\n|\nTIMEOUT_MS=$(printf '%q' \"$TIMEOUT_MS\") |\n|\nAGENT_CWD=$(printf '%q' \"$AGENT_CWD\") |\n|\nEOF |\n|\n|\n|\nprintf 'Creating debate tab in %s (cwd=%s)...\\n' \"$WORKSPACE\" \"$AGENT_CWD\" |\n|\ncreated=\"$(herdr tab create --workspace \"$WORKSPACE\" --cwd \"$AGENT_CWD\" --label \"debate-${ID##*-}\" --no-focus)\" |\n|\nTAB_ID=\"$(json_get '.result.tab.tab_id' \"$created\")\" |\n|\nLEFT_PANE=\"$(json_get '.result.root_pane.pane_id' \"$created\")\" |\n|\n|\n|\n# Bottom referee strip, then split the top pane left|right. |\n|\nsplit_down=\"$(herdr pane split \"$LEFT_PANE\" --direction down --ratio 0.78 --cwd \"$AGENT_CWD\" --no-focus)\" |\n|\nREF_PANE=\"$(json_get '.result.pane.pane_id' \"$split_down\")\" |\n|\nsplit_right=\"$(herdr pane split \"$LEFT_PANE\" --direction right --ratio 0.5 --cwd \"$AGENT_CWD\" --no-focus)\" |\n|\nRIGHT_PANE=\"$(json_get '.result.pane.pane_id' \"$split_right\")\" |\n|\n|\n|\nsleep 0.5 |\n|\n|\n|\nprintf 'Starting %s on %s and %s on %s...\\n' \"$LEFT_NAME\" \"$LEFT_PANE\" \"$RIGHT_NAME\" \"$RIGHT_PANE\" |\n|\n|\n|\nstart_peer() { |\n|\nlocal name=\"$1\" kind=\"$2\" pane=\"$3\" model=\"$4\" args=\"$5\" |\n|\nlocal extra=() |\n|\nif [[ \"${YOLO:-1}\" == 1 ]]; then |\n|\ncase \"$kind\" in |\n|\nclaude) extra+=(--dangerously-skip-permissions) ;; |\n|\ncodex) extra+=(--dangerously-bypass-approvals-and-sandbox) ;; |\n|\nesac |\n|\nfi |\n|\nif [[ -n \"$model\" ]]; then |\n|\ncase \"$kind\" in |\n|\ncodex) extra+=(-m \"$model\") ;; |\n|\n*) extra+=(--model \"$model\") ;; |\n|\nesac |\n|\nfi |\n|\nif [[ -n \"$args\" ]]; then |\n|\n# shellcheck disable=SC2206 |\n|\nextra+=( $args ) |\n|\nfi |\n|\nif [[ ${#extra[@]} -gt 0 ]]; then |\n|\nprintf ' start %s (%s):' \"$name\" \"$kind\" |\n|\nprintf ' %q' \"${extra[@]}\" |\n|\nprintf '\\n' |\n|\nherdr agent start \"$name\" --kind \"$kind\" --pane \"$pane\" --timeout 60000 -- \"${extra[@]}\" |\n|\nelse |\n|\nherdr agent start \"$name\" --kind \"$kind\" --pane \"$pane\" --timeout 60000 |\n|\nfi |\n|\n} |\n|\n|\n|\nstart_peer \"$LEFT_NAME\" \"$LEFT_KIND\" \"$LEFT_PANE\" \"$LEFT_MODEL\" \"$LEFT_ARGS\" |\n|\nstart_peer \"$RIGHT_NAME\" \"$RIGHT_KIND\" \"$RIGHT_PANE\" \"$RIGHT_MODEL\" \"$RIGHT_ARGS\" |\n|\n|\n|\n{ |\n|\nprintf 'tab=%s\\nleft_pane=%s\\nright_pane=%s\\nref_pane=%s\\n' \\ |\n|\n\"$TAB_ID\" \"$LEFT_PANE\" \"$RIGHT_PANE\" \"$REF_PANE\" |\n|\n} >\"$DEBATE_DIR/layout.env\" |\n|\n|\n|\nherdr tab focus \"$TAB_ID\" >/dev/null |\n|\n|\n|\nprintf '\\nDebate ready.\\n' |\n|\nprintf ' dir: %s\\n' \"$DEBATE_DIR\" |\n|\nprintf ' left: %s (%s)\\n' \"$LEFT_NAME\" \"$LEFT_KIND\" |\n|\nprintf ' right: %s (%s)\\n' \"$RIGHT_NAME\" \"$RIGHT_KIND\" |\n|\nprintf ' files: left.md / right.md / transcript.md\\n\\n' |\n|\n|\n|\nif [[ \"$FOREGROUND\" == 1 ]]; then |\n|\nrun_loop \"$DEBATE_DIR\" |\n|\nexit 0 |\n|\nfi |\n|\n|\n|\nprintf 'bash %s --run-dir %s\\n' \"$SELF\" \"$DEBATE_DIR\" >\"$DEBATE_DIR/referee.sh\" |\n|\nherdr pane run \"$REF_PANE\" \"bash $(printf '%q' \"$SELF\") --run-dir $(printf '%q' \"$DEBATE_DIR\")\" |\n|\nprintf 'Referee is running in the bottom pane. Watch the two agents; this command is done.\\n' |\n|\nprintf 'Foreground instead: herd-debate --foreground -f %s\\n' \"$BRIEF_FILE\" |", "url": "https://wpnews.pro/news/herd-debate-equal-claude-vs-codex-design-peers-in-herdr", "canonical_source": "https://gist.github.com/juancamunoz/aae84e9da06a37c210edbae8dc4cb064", "published_at": "2026-08-19 11:08:25+00:00", "updated_at": "2026-08-19 11:41:36.347398+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "ai-tools"], "entities": ["Herdr", "Claude", "Codex"], "alternates": {"html": "https://wpnews.pro/news/herd-debate-equal-claude-vs-codex-design-peers-in-herdr", "markdown": "https://wpnews.pro/news/herd-debate-equal-claude-vs-codex-design-peers-in-herdr.md", "text": "https://wpnews.pro/news/herd-debate-equal-claude-vs-codex-design-peers-in-herdr.txt", "jsonld": "https://wpnews.pro/news/herd-debate-equal-claude-vs-codex-design-peers-in-herdr.jsonld"}}