cd /news/developer-tools/herd-debate-equal-claude-vs-codex-de… · home topics developer-tools article
[ARTICLE · art-102816] src=gist.github.com ↗ pub= topic=developer-tools verified=true sentiment=· neutral

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.

read18 min views2 publishedAug 19, 2026

| #!/usr/bin/env bash | | | | 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 <<EOF | | You are chatting live with ${other_kind}. Equal peers. Not lead, not reviewer. | | | | Topic: | | $(cat "$BRIEF_FILE") | | | | Talk 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. | | | | Also write that exact same message to this file (overwrite). Last line of the file must be only: | | END_TURN | | File: ${mine} | | EOF | | } | | | | chat_reply() { | | local other_kind="$1" mine="$2" their_msg="$3" | | cat <<EOF | | ${other_kind}: | | | | ${their_msg} | | | | Reply to them in THIS chat — one message. Same rules: no Herdr/files/referee talk, no TURN_DONE in the chat. | | | | Write that exact same message to ${mine} (overwrite). Last line of the file must be only: | | END_TURN | | EOF | | } | | | | opening_prompt() { | | local who="$1" mine="$2" other="$3" | | cat <<EOF | | You 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. | | | | Do not write production code. Do not assign tasks. Do not modify the repo. Reading the repo is allowed. | | | | Read the brief: | | ${BRIEF_FILE} | | | | Write your independent opening take to this file (overwrite it): | | ${mine} | | | | Do not read the other peer's file: | | ${other} | | | | Cover: | |

  • the approach you would take | |
  • serious alternatives | |
  • risks / traps in this codebase | |
  • what you want to argue about | | | | Speak the same take in THIS chat (they should be able to watch you talk). Last line of the file must be exactly: | | OPENING_DONE | | EOF | | } | | | | round_prompt() { | | local mine="$1" other="$2" | | cat <<EOF | | Equal peer debate continues. You are not the lead. You do not assign work. | | | | The other peer just said: | | | | $(msg_body "$other") | | | | Reply in THIS chat. Also write the reply to: | | ${mine} | | | | Rules: | |
  • Address their points. Concede or counter with specifics. | |
  • Propose; do not assign tasks. | |
  • No production code. Do not modify the repo. | |
  • Do not write CONSENSUS unless this is at least the second back-and-forth and you both actually agree. | |
  • If you genuinely agree, first line of the file: CONSENSUS: | | | | Last line of the file must be exactly: | | TURN_DONE | | EOF | | } | | | | append_transcript() { | | local label="$1" | | { | | printf '\n## %s\n\n### %s\n\n' "$label" "$LEFT_NAME" | | cat "$LEFT_FILE" | | printf '\n### %s\n\n' "$RIGHT_NAME" | | cat "$RIGHT_FILE" | | printf '\n' | | } >>"$TRANSCRIPT" | | } | | | | load_dir() { | | | source "$1/config.env" | | DEBATE_DIR="$1" | | BRIEF_FILE="$DEBATE_DIR/brief.md" | | LEFT_FILE="$DEBATE_DIR/left.md" | | RIGHT_FILE="$DEBATE_DIR/right.md" | | TRANSCRIPT="$DEBATE_DIR/transcript.md" | | } | | | | run_chat_loop() { | | local i=1 speaker_name speaker_file other_kind other_file their | | | | printf 'Chat start. %s ↔ %s, %s turns. Watch the split tab.\n' \ | | "$LEFT_NAME" "$RIGHT_NAME" "$TURNS" | | | | printf 'Turn 1/%s %s kicks off...\n' "$TURNS" "$LEFT_NAME" | | prompt_wait "$LEFT_NAME" "$(chat_kickoff "$RIGHT_KIND" "$LEFT_FILE")" "$TIMEOUT_MS" | | require_marker "$LEFT_FILE" END_TURN "$LEFT_NAME" | | append_turn "$LEFT_NAME" 1 "$LEFT_FILE" | | | | i=2 | | while [[ $i -le $TURNS ]]; do | | if (( i % 2 == 0 )); then | | speaker_name="$RIGHT_NAME"; speaker_file="$RIGHT_FILE" | | other_kind="$LEFT_KIND"; other_file="$LEFT_FILE" | | else | | speaker_name="$LEFT_NAME"; speaker_file="$LEFT_FILE" | | other_kind="$RIGHT_KIND"; other_file="$RIGHT_FILE" | | fi | | their="$(msg_body "$other_file")" | | [[ -n "${their//[[:space:]]/}" ]] || die "empty message from $other_kind" | | printf 'Turn %s/%s %s replies...\n' "$i" "$TURNS" "$speaker_name" | | prompt_wait "$speaker_name" "$(chat_reply "$other_kind" "$speaker_file" "$their")" "$TIMEOUT_MS" | | require_marker "$speaker_file" END_TURN "$speaker_name" | | append_turn "$speaker_name" "$i" "$speaker_file" | | i=$((i + 1)) | | done | | | | printf 'Chat done. Transcript: %s\n' "$TRANSCRIPT" | | } | | | | run_debate_loop() { | | local i=1 first_file second_file first_name second_name min_rounds=2 | | mkdir -p "$DEBATE_DIR/stamps" | | | | stamp() { touch "$DEBATE_DIR/stamps/$1"; } | | stamped() { [[ -f "$DEBATE_DIR/stamps/$1" ]]; } | | | | if stamped opening || { file_has "$LEFT_FILE" OPENING_DONE && file_has "$RIGHT_FILE" OPENING_DONE; }; then | | printf 'Resume: openings already written, skipping.\n' | | grep -q '^## Opening$' "$TRANSCRIPT" 2>/dev/null || append_transcript "Opening" | | stamp opening | | else | | printf 'Opening statements...\n' | | if [[ "$SERIAL" == 1 ]]; then | | if stamped opening-left || file_has "$LEFT_FILE" OPENING_DONE; then | | printf ' %s opening already on disk.\n' "$LEFT_NAME" | | stamp opening-left | | else | | printf ' %s first (serial)...\n' "$LEFT_NAME" | | prompt_wait "$LEFT_NAME" "$(opening_prompt "$RIGHT_KIND" "$LEFT_FILE" "$RIGHT_FILE")" "$TIMEOUT_MS" | | require_marker "$LEFT_FILE" OPENING_DONE "$LEFT_NAME" | | stamp opening-left | | fi | | if stamped opening-right || file_has "$RIGHT_FILE" OPENING_DONE; then | | printf ' %s opening already on disk.\n' "$RIGHT_NAME" | | stamp opening-right | | else | | prompt_wait "$RIGHT_NAME" "$(opening_prompt "$LEFT_KIND" "$RIGHT_FILE" "$LEFT_FILE")" "$TIMEOUT_MS" | | require_marker "$RIGHT_FILE" OPENING_DONE "$RIGHT_NAME" | | stamp opening-right | | fi | | else | | prompt_parallel \ | | "$LEFT_NAME" "$(opening_prompt "$RIGHT_KIND" "$LEFT_FILE" "$RIGHT_FILE")" \ | | "$RIGHT_NAME" "$(opening_prompt "$LEFT_KIND" "$RIGHT_FILE" "$LEFT_FILE")" \ | | "$TIMEOUT_MS" | | require_marker "$LEFT_FILE" OPENING_DONE "$LEFT_NAME" | | require_marker "$RIGHT_FILE" OPENING_DONE "$RIGHT_NAME" | | fi | | append_transcript "Opening" | | stamp opening | | fi | | | | while [[ $i -le $ROUNDS ]]; do | | printf 'Round %s/%s...\n' "$i" "$ROUNDS" | | if stamped "round-$i" || grep -q "^## Round $i$" "$TRANSCRIPT" 2>/dev/null; then | | stamp "round-$i" | | i=$((i + 1)) | | continue | | fi | | if [[ "$SERIAL" == 1 ]]; then | | if (( i % 2 == 1 )); then | | first_name="$LEFT_NAME"; first_file="$LEFT_FILE" | | second_name="$RIGHT_NAME"; second_file="$RIGHT_FILE" | | else | | first_name="$RIGHT_NAME"; first_file="$RIGHT_FILE" | | second_name="$LEFT_NAME"; second_file="$LEFT_FILE" | | fi | | if stamped "round-$i-$first_name"; then | | printf ' %s already did round %s.\n' "$first_name" "$i" | | else | | prompt_wait "$first_name" "$(round_prompt "$first_file" "$second_file")" "$TIMEOUT_MS" | | require_marker "$first_file" TURN_DONE "$first_name" | | stamp "round-$i-$first_name" | | fi | | if stamped "round-$i-$second_name"; then | | printf ' %s already did round %s.\n' "$second_name" "$i" | | else | | prompt_wait "$second_name" "$(round_prompt "$second_file" "$first_file")" "$TIMEOUT_MS" | | require_marker "$second_file" TURN_DONE "$second_name" | | stamp "round-$i-$second_name" | | fi | | else | | cp "$LEFT_FILE" "$DEBATE_DIR/left.prev.md" | | cp "$RIGHT_FILE" "$DEBATE_DIR/right.prev.md" | | prompt_parallel \ | | "$LEFT_NAME" "$(round_prompt "$LEFT_FILE" "$DEBATE_DIR/right.prev.md")" \ | | "$RIGHT_NAME" "$(round_prompt "$RIGHT_FILE" "$DEBATE_DIR/left.prev.md")" \ | | "$TIMEOUT_MS" | | require_marker "$LEFT_FILE" TURN_DONE "$LEFT_NAME" | | require_marker "$RIGHT_FILE" TURN_DONE "$RIGHT_NAME" | | fi | | append_transcript "Round $i" | | stamp "round-$i" | | if [[ $i -ge $min_rounds ]] && is_consensus "$LEFT_FILE" && is_consensus "$RIGHT_FILE"; then | | printf 'CONSENSUS after round %s. See %s\n' "$i" "$DEBATE_DIR" | | { | | printf '# Consensus\n\n' | | printf '## %s\n\n' "$LEFT_NAME" | | cat "$LEFT_FILE" | | printf '\n## %s\n\n' "$RIGHT_NAME" | | cat "$RIGHT_FILE" | | } >"$DEBATE_DIR/CONSENSUS.md" | | return 0 | | fi | | i=$((i + 1)) | | done | | | | printf 'No dual CONSENSUS after %s rounds. Transcript: %s\n' "$ROUNDS" "$TRANSCRIPT" | | } | | | | run_loop() { | | load_dir "$1" | | cd "$AGENT_CWD" | | MODE="${MODE:-debate}" | | TURNS="${TURNS:-$ROUNDS}" | | | | if [[ -n "${TIMEOUT_SEC_OVERRIDE:-}" ]]; then | | TIMEOUT_MS=$((TIMEOUT_SEC_OVERRIDE * 1000)) | | elif [[ "${TIMEOUT_MS:-0}" -lt 1800000 ]]; then | | printf 'Timeout floor: 30 min (config had %ss).\n' "$((TIMEOUT_MS / 1000))" >&2 | | TIMEOUT_MS=1800000 | | fi | | printf 'Referee start. dir=%s mode=%s left=%s right=%s timeout=%ss\n' \ | | "$DEBATE_DIR" "$MODE" "$LEFT_NAME" "$RIGHT_NAME" "$((TIMEOUT_MS / 1000))" | | | | if [[ -f "$DEBATE_DIR/layout.env" ]]; then | | | source "$DEBATE_DIR/layout.env" | | [[ -n "${tab:-}" ]] && herdr tab focus "$tab" >/dev/null || true | | fi | | | | if [[ "$MODE" == chat ]]; then | | run_chat_loop | | else | | run_debate_loop | | fi | | } | | | | | FILE="" | | BRIEF="" | | ROUNDS=4 | | SERIAL=1 | | MODE=debate | | YOLO=1 | | LEFT_KIND=claude | | RIGHT_KIND=codex | | AGENT_CWD="" | | WORKSPACE="" | | TIMEOUT_SEC=1800 | | TIMEOUT_SEC_OVERRIDE="" | | RUN_DIR="" | | LEFT_ARGS="" | | RIGHT_ARGS="" | | LEFT_MODEL="" | | RIGHT_MODEL="" | | FOREGROUND=0 | | | | while [[ $# -gt 0 ]]; do | | case "$1" in | | -h|--help) usage; exit 0 ;; | | -f|--file) FILE="${2:-}"; shift 2 ;; | | -n|--rounds) ROUNDS="${2:-}"; shift 2 ;; | | --serial) SERIAL=1; shift ;; | | --parallel) SERIAL=0; shift ;; | | --no-yolo) YOLO=0; shift ;; | | --mode) MODE="${2:-}"; shift 2 ;; | | --left) LEFT_KIND="${2:-}"; shift 2 ;; | | --right) RIGHT_KIND="${2:-}"; shift 2 ;; | | --left-model) LEFT_MODEL="${2:-}"; shift 2 ;; | | --right-model) RIGHT_MODEL="${2:-}"; shift 2 ;; | | --cwd) AGENT_CWD="${2:-}"; shift 2 ;; | | --workspace) WORKSPACE="${2:-}"; shift 2 ;; | | --timeout-sec) TIMEOUT_SEC="${2:-}"; TIMEOUT_SEC_OVERRIDE="${2:-}"; shift 2 ;; | | --left-args) LEFT_ARGS="${2:-}"; shift 2 ;; | | --right-args) RIGHT_ARGS="${2:-}"; shift 2 ;; | | --foreground) FOREGROUND=1; shift ;; | | --run-dir) RUN_DIR="${2:-}"; shift 2 ;; | | --) shift; BRIEF="${BRIEF:+$BRIEF }$"; set --; break ;; | | -) die "unknown option: $1" ;; | | ) BRIEF="${BRIEF:+$BRIEF }$1"; shift ;; | | esac | | done | | | | need_herdr | | | | if [[ -n "$RUN_DIR" ]]; then | | [[ -d "$RUN_DIR" ]] || die "debate dir not found: $RUN_DIR" | | run_loop "$RUN_DIR" | | exit 0 | | fi | | | | [[ "$ROUNDS" =~ [1]+$ && "$ROUNDS" -ge 1 ]] || die "--rounds must be >= 1" | | [[ "$MODE" == chat || "$MODE" == debate ]] || die "--mode must be chat or debate" | | TURNS="$ROUNDS" | | [[ "$TIMEOUT_SEC" =~ [2]+$ && "$TIMEOUT_SEC" -ge 30 ]] || die "--timeout-sec must be >= 30" | | [[ "$LEFT_KIND" =~ $KINDS ]] || die "unsupported --left kind: $LEFT_KIND" | | [[ "$RIGHT_KIND" =~ $KINDS ]] || die "unsupported --right kind: $RIGHT_KIND" | | [[ "$LEFT_KIND" != "$RIGHT_KIND" ]] || die "--left and --right must be different kinds" | | | | if [[ -n "$FILE" ]]; then | | [[ -f "$FILE" ]] || die "brief file not found: $FILE" | | BRIEF="$(cat "$FILE")" | | elif [[ -z "$BRIEF" ]]; then | | if [[ -t 0 ]]; then | | usage | | die "pass a brief as arguments or -f FILE" | | fi | | BRIEF="$(cat)" | | fi | | [[ -n "${BRIEF//[[:space:]]/}" ]] || die "empty brief" | | | | AGENT_CWD="$(cd "${AGENT_CWD:-.}" && pwd)" | | WORKSPACE="${WORKSPACE:-${HERDR_WORKSPACE_ID:-}}" | | [[ -n "$WORKSPACE" ]] || die "no workspace id (pass --workspace)" | | | | TIMEOUT_MS=$((TIMEOUT_SEC * 1000)) | | ID="$(date +%Y%m%d-%H%M%S)" | | DEBATE_DIR="$CACHE/$ID" | | mkdir -p "$DEBATE_DIR" | | BRIEF_FILE="$DEBATE_DIR/brief.md" | | LEFT_FILE="$DEBATE_DIR/left.md" | | RIGHT_FILE="$DEBATE_DIR/right.md" | | TRANSCRIPT="$DEBATE_DIR/transcript.md" | | printf '%s\n' "$BRIEF" >"$BRIEF_FILE" | | : >"$TRANSCRIPT" | | | | LEFT_NAME="$(alloc_name "$LEFT_KIND")" | | RIGHT_NAME="$(alloc_name "$RIGHT_KIND")" | | | | cat >"$DEBATE_DIR/config.env" <<EOF | | LEFT_KIND=$(printf '%q' "$LEFT_KIND") | | RIGHT_KIND=$(printf '%q' "$RIGHT_KIND") | | LEFT_NAME=$(printf '%q' "$LEFT_NAME") | | RIGHT_NAME=$(printf '%q' "$RIGHT_NAME") | | ROUNDS=$(printf '%q' "$ROUNDS") | | TURNS=$(printf '%q' "$TURNS") | | MODE=$(printf '%q' "$MODE") | | SERIAL=$(printf '%q' "$SERIAL") | | TIMEOUT_MS=$(printf '%q' "$TIMEOUT_MS") | | AGENT_CWD=$(printf '%q' "$AGENT_CWD") | | EOF | | | | printf 'Creating debate tab in %s (cwd=%s)...\n' "$WORKSPACE" "$AGENT_CWD" | | created="$(herdr tab create --workspace "$WORKSPACE" --cwd "$AGENT_CWD" --label "debate-${ID##-}" --no-focus)" | | TAB_ID="$(json_get '.result.tab.tab_id' "$created")" | | LEFT_PANE="$(json_get '.result.root_pane.pane_id' "$created")" | | | | | split_down="$(herdr pane split "$LEFT_PANE" --direction down --ratio 0.78 --cwd "$AGENT_CWD" --no-focus)" | | REF_PANE="$(json_get '.result.pane.pane_id' "$split_down")" | | split_right="$(herdr pane split "$LEFT_PANE" --direction right --ratio 0.5 --cwd "$AGENT_CWD" --no-focus)" | | RIGHT_PANE="$(json_get '.result.pane.pane_id' "$split_right")" | | | | sleep 0.5 | | | | printf 'Starting %s on %s and %s on %s...\n' "$LEFT_NAME" "$LEFT_PANE" "$RIGHT_NAME" "$RIGHT_PANE" | | | | start_peer() { | | local name="$1" kind="$2" pane="$3" model="$4" args="$5" | | local extra=() | | if [[ "${YOLO:-1}" == 1 ]]; then | | case "$kind" in | | claude) extra+=(--dangerously-skip-permissions) ;; | | codex) extra+=(--dangerously-bypass-approvals-and-sandbox) ;; | | esac | | fi | | if [[ -n "$model" ]]; then | | case "$kind" in | | codex) extra+=(-m "$model") ;; | | *) extra+=(--model "$model") ;; | | esac | | fi | | if [[ -n "$args" ]]; then | | | extra+=( $args ) | | fi | | if [[ ${#extra[@]} -gt 0 ]]; then | | printf ' start %s (%s):' "$name" "$kind" | | printf ' %q' "${extra[@]}" | | printf '\n' | | herdr agent start "$name" --kind "$kind" --pane "$pane" --timeout 60000 -- "${extra[@]}" | | else | | herdr agent start "$name" --kind "$kind" --pane "$pane" --timeout 60000 | | fi | | } | | | | start_peer "$LEFT_NAME" "$LEFT_KIND" "$LEFT_PANE" "$LEFT_MODEL" "$LEFT_ARGS" | | start_peer "$RIGHT_NAME" "$RIGHT_KIND" "$RIGHT_PANE" "$RIGHT_MODEL" "$RIGHT_ARGS" | | | | { | | printf 'tab=%s\nleft_pane=%s\nright_pane=%s\nref_pane=%s\n' \ | | "$TAB_ID" "$LEFT_PANE" "$RIGHT_PANE" "$REF_PANE" | | } >"$DEBATE_DIR/layout.env" | | | | herdr tab focus "$TAB_ID" >/dev/null | | | | printf '\nDebate ready.\n' | | printf ' dir: %s\n' "$DEBATE_DIR" | | printf ' left: %s (%s)\n' "$LEFT_NAME" "$LEFT_KIND" | | printf ' right: %s (%s)\n' "$RIGHT_NAME" "$RIGHT_KIND" | | printf ' files: left.md / right.md / transcript.md\n\n' | | | | if [[ "$FOREGROUND" == 1 ]]; then | | run_loop "$DEBATE_DIR" | | exit 0 | | fi | | | | printf 'bash %s --run-dir %s\n' "$SELF" "$DEBATE_DIR" >"$DEBATE_DIR/referee.sh" | | herdr pane run "$REF_PANE" "bash $(printf '%q' "$SELF") --run-dir $(printf '%q' "$DEBATE_DIR")" | | printf 'Referee is running in the bottom pane. Watch the two agents; this command is done.\n' | | printf 'Foreground instead: herd-debate --foreground -f %s\n' "$BRIEF_FILE" |

  1. 0-9 ↩︎

  2. 0-9 ↩︎

── more in #developer-tools 4 stories · sorted by recency
── more on @herdr 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/herd-debate-equal-cl…] indexed:0 read:18min 2026-08-19 ·