herd-debate: equal Claude vs Codex design peers in Herdr 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. | /usr/bin/env bash | | Two equal Herdr agents, mechanical referee. Serial design debate by default. | | Requires HERDR ENV=1. Neither agent should have the Herdr skill for this. | | set -euo pipefail | | | | SELF="$ cd "$ dirname "$0" " && pwd /$ basename "$0" " | | CACHE="${XDG CACHE HOME:-$HOME/.cache}/herd-debate" | | KINDS='^ pi|claude|codex|gemini|cursor|devin|agy|cline|omp|mastracode|opencode|copilot|kimi|kiro|droid|amp|grok|hermes|kilo|qodercli|maki $' | | | | usage { | | cat <<'EOF' | | herd-debate — two equal design peers in Herdr Claude | Codex | | | | herd-debate "how should we implement X" | | herd-debate --run-dir ~/.cache/herd-debate/TIMESTAMP --timeout-sec 1800 | | | | Must run inside a Herdr pane HERDR ENV=1 . Watch the debate tab: split vertical. | | | | Serial debate by default. Both agents start in yolo no permission prompts : | | claude: --dangerously-skip-permissions | | codex: --dangerously-bypass-approvals-and-sandbox | | | | Options: | | -f, --file PATH brief from a file | | -n, --rounds N rounds after opening default: 4 | | --parallel simultaneous rounds not a conversation | | --serial alternate turns default | | --no-yolo do not pass skip-permissions flags | | --left KIND default: claude | | --right KIND default: codex | | --left-model NAME model for the left CLI claude: opus|sonnet|fable|... | | --right-model NAME model for the right CLI codex: -m | | --cwd PATH agents' cwd default: current directory | | --workspace ID Herdr workspace default: current | | --timeout-sec N per-turn wait default: 1800 = 30 min | | --run-dir DIR resume a debate skips finished openings/rounds | | --left-args TEXT extra argv after -- for the left agent | | --right-args TEXT extra argv after -- for the right agent | | --foreground run the referee in this pane instead of the bottom split | | -h, --help | | EOF | | } | | | | die { printf 'herd-debate: %s\n' "$ " &2; exit 1; } | | | | need herdr { | | "${HERDR ENV:-}" == 1 || die "not inside Herdr HERDR ENV =1 . Run this from a Herdr pane." | | command -v herdr /dev/null || die "herdr not in PATH" | | command -v jq /dev/null || die "jq not in PATH" | | } | | | | json get { | | jq -er "$1" <<<"$2" | | } | | | | alloc name { | | local kind="$1" base candidate n=2 | | base="${kind}-peer" | | candidate="$base" | | while herdr agent get "$candidate" /dev/null 2 &1; do | | candidate="${base}-${n}" | | n=$ n + 1 | | ${ candidate} -le 32 || die "could not allocate agent name from $base" | | done | | printf '%s\n' "$candidate" | | } | | | | wait settled { | | local name="$1" timeout="$2" json status | | json="$ herdr agent wait "$name" --timeout "$timeout" " | | status="$ jq -r '.result.agent.agent status // empty' <<<"$json" " | | if "$status" == blocked ; then | | printf '\n %s is BLOCKED permissions/question . Approve it in that pane.\n' "$name" &2 | | json="$ herdr agent wait "$name" --until idle --until done --timeout "$ timeout 2 " " | | status="$ jq -r '.result.agent.agent status // empty' <<<"$json" " | | fi | | printf '%s\n' "$status" | | } | | | | prompt wait { | | local name="$1" text="$2" timeout="$3" | | local err="$DEBATE DIR/prompt-${name}.err" | | set +e | | herdr agent prompt "$name" "$text" --wait --timeout "$timeout" /dev/null 2 "$err" | | local ec=$? | | set -e | | if $ec -eq 0 ; then | | wait settled "$name" "$timeout" /dev/null | | return 0 | | fi | | if grep -q '"code":"timeout"' "$err" 2 /dev/null; then | | printf ' %s wait timed out; giving it more time / checking files...\n' "$name" &2 | | wait settled "$name" "$timeout" /dev/null || true | | return 0 | | fi | | cat "$err" &2 || true | | die "prompt failed for $name" | | } | | | | prompt parallel { | | local left name="$1" left text="$2" right name="$3" right text="$4" timeout="$5" | | local dir="${DEBATE DIR}" | | set +e | | herdr agent prompt "$left name" "$left text" --wait --timeout "$timeout" \ | | "$dir/wait-left.json" 2 "$dir/wait-left.err" & | | local pid l=$ | | herdr agent prompt "$right name" "$right text" --wait --timeout "$timeout" \ | | "$dir/wait-right.json" 2 "$dir/wait-right.err" & | | local pid r=$ | | wait "$pid l"; local ec l=$? | | wait "$pid r"; local ec r=$? | | set -e | | if $ec l -ne 0 ; then | | printf 'left prompt failed %s :\n' "$left name" &2 | | cat "$dir/wait-left.err" &2 || true | | cat "$dir/wait-left.json" &2 || true | | fi | | if $ec r -ne 0 ; then | | printf 'right prompt failed %s :\n' "$right name" &2 | | cat "$dir/wait-right.err" &2 || true | | cat "$dir/wait-right.json" &2 || true | | fi | | $ec l -eq 0 && $ec r -eq 0 || die "a peer prompt failed" | | wait settled "$left name" "$timeout" /dev/null | | wait settled "$right name" "$timeout" /dev/null | | } | | | | file has { | | local f="$1" marker="$2" | | -f "$f" && grep -qF "$marker" "$f" | | } | | | | require marker { | | local f="$1" marker="$2" who="$3" | | if file has "$f" "$marker"; then | | return 0 | | fi | | die "$who did not write $marker to $f — check the pane. Debate dir: $DEBATE DIR" | | } | | | | is consensus { | | local f="$1" | | -f "$f" || return 1 | | awk 'NF { print; exit }' "$f" | grep -q '^CONSENSUS:' | | } | | | | msg body { | | sed -e 's/\r$//' -e '/^END TURN$/d' -e '/^OPENING DONE$/d' -e '/^TURN DONE$/d' "$1" | | } | | | | append turn { | | local who="$1" n="$2" file="$3" | | { | | printf '\n %s — turn %s\n\n' "$who" "$n" | | msg body "$file" | | printf '\n' | | } "$TRANSCRIPT" | | } | | | | chat kickoff { | | local other kind="$1" mine="$2" | | cat <