CommonAutoRearsh/spawn.sh
2026-03-06 21:58:52 +00:00

249 lines
7.5 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# spawn.sh — launch and manage autonomous research groups
#
# Usage:
# bash spawn.sh launch <tag> <agent:gpu> [agent:gpu ...]
# bash spawn.sh stop <tag>
# bash spawn.sh status
#
# Examples:
# bash spawn.sh launch mar5 claude:0 claude:1 claude:2 claude:3
# bash spawn.sh launch mar5 opus:0 sonnet:1 codex:2 codex:3
# bash spawn.sh stop mar5
# bash spawn.sh status
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")" && pwd)"
WORKTREE_DIR="${REPO_ROOT}/worktrees"
INITIAL_PROMPT="Read program.md and follow the instructions."
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
usage() {
echo "Usage:"
echo " $0 launch <tag> <agent:gpu> [agent:gpu ...]"
echo " $0 stop <tag>"
echo " $0 status"
echo ""
echo "Examples:"
echo " $0 launch mar5 claude:0 claude:1 claude:2 claude:3"
echo " $0 launch mar5 opus:0 sonnet:1 codex:2 codex:3"
echo " $0 stop mar5"
exit 1
}
setup_worker() {
local tag="$1" gpu="$2" agent="$3"
local branch="autoresearch/${tag}-gpu${gpu}"
local worktree="${WORKTREE_DIR}/gpu${gpu}"
local claude_model="${CLAUDE_MODEL:-sonnet}"
# Create branch (must be fresh)
cd "$REPO_ROOT"
if git show-ref --verify --quiet "refs/heads/${branch}"; then
echo "ERROR: Branch '${branch}' already exists. Use a different tag or clean up first." >&2
exit 1
fi
git checkout -q master
git checkout -q -b "$branch"
git checkout -q master
# Set up worktree
if [ -d "$worktree" ]; then
git worktree remove "$worktree" --force 2>/dev/null || rm -rf "$worktree"
fi
mkdir -p "$(dirname "$worktree")"
git worktree add "$worktree" "$branch"
# Symlink .venv so uv/python work
ln -sf "${REPO_ROOT}/.venv" "${worktree}/.venv"
# Build agent command
case "$agent" in
claude|sonnet|opus|haiku)
case "$agent" in
sonnet|opus|haiku) claude_model="$agent" ;;
esac
echo "cd ${worktree} && CUDA_VISIBLE_DEVICES=${gpu} claude --dangerously-skip-permissions --model ${claude_model} \"${INITIAL_PROMPT}\""
;;
codex)
echo "cd ${worktree} && CUDA_VISIBLE_DEVICES=${gpu} codex --dangerously-bypass-approvals-and-sandbox --model gpt-5.3-codex-spark \"${INITIAL_PROMPT}\""
;;
*)
echo "ERROR: Unknown agent '${agent}'. Supported: claude, sonnet, opus, haiku, codex" >&2
git worktree remove "$worktree" --force 2>/dev/null || true
git branch -D "$branch" 2>/dev/null || true
exit 1
;;
esac
}
# ---------------------------------------------------------------------------
# Commands
# ---------------------------------------------------------------------------
cmd_launch() {
if [ $# -lt 2 ]; then
usage
fi
local tag="$1"
shift
local specs=("$@")
local tmux_session="autoresearch-${tag}"
local num_workers=${#specs[@]}
# Kill existing session
tmux kill-session -t "$tmux_session" 2>/dev/null || true
echo ""
echo "============================================"
echo " RESEARCH GROUP: ${tag}"
echo "============================================"
echo " Workers: ${num_workers}"
local pane_cmds=()
local labels=()
local agents=()
for spec in "${specs[@]}"; do
local agent="${spec%%:*}"
local gpu="${spec#*:}"
echo " - GPU ${gpu}: ${agent}"
local cmd
cmd=$(setup_worker "$tag" "$gpu" "$agent")
pane_cmds+=("$cmd")
labels+=("$spec")
case "$agent" in
sonnet|opus|haiku|claude) agents+=("claude") ;;
*) agents+=("$agent") ;;
esac
done
echo " tmux: ${tmux_session}"
echo "============================================"
echo ""
# Create tmux session with tiled grid
tmux new-session -d -s "$tmux_session" -n workers \
"${pane_cmds[0]}; echo ''; echo 'Session ended. Press any key to exit.'; read"
for ((i=1; i<num_workers; i++)); do
tmux split-window -t "$tmux_session:workers" \
"${pane_cmds[$i]}; echo ''; echo 'Session ended. Press any key to exit.'; read"
tmux select-layout -t "$tmux_session:workers" tiled
done
tmux select-layout -t "$tmux_session:workers" tiled
echo " Attach: tmux attach -t ${tmux_session}"
echo " Detach: Ctrl-b d"
echo " Zoom: Ctrl-b z (toggle pane fullscreen)"
echo " Navigate: Ctrl-b arrow keys"
echo ""
# Background watcher — nudge idle agents
local nudge_interval=120
echo "Watcher running (nudge interval: ${nudge_interval}s). Ctrl-C to stop watcher only."
echo ""
declare -A prev_hash
while tmux has-session -t "$tmux_session" 2>/dev/null; do
sleep "$nudge_interval"
for ((i=0; i<num_workers; i++)); do
tmux has-session -t "$tmux_session" 2>/dev/null || break
local pane="${tmux_session}:workers.${i}"
local content curr_hash
content=$(tmux capture-pane -t "$pane" -p 2>/dev/null) || continue
curr_hash=$(echo "$content" | md5sum | cut -d' ' -f1)
if [ "${prev_hash[$i]:-}" != "$curr_hash" ]; then
prev_hash[$i]="$curr_hash"
continue
fi
local is_idle=false
if echo "$content" | grep -qP '^\s*$'; then
is_idle=true
elif echo "$content" | grep -qP '^\s*$'; then
is_idle=true
fi
if [ "$is_idle" = true ]; then
local nudge_msg="Keep going. Do not stop — continue your research loop."
if [ "${agents[$i]}" = "codex" ]; then
tmux send-keys -t "$pane" "$nudge_msg" Enter
else
tmux send-keys -t "$pane" "$nudge_msg" C-m
fi
echo "[$(date +%H:%M:%S)] Nudged pane ${i} (${labels[$i]})"
prev_hash[$i]=""
fi
done
done
echo "tmux session ended. Watcher exiting."
}
cmd_stop() {
if [ $# -lt 1 ]; then
usage
fi
local tag="$1"
local tmux_session="autoresearch-${tag}"
# Kill tmux session
if tmux kill-session -t "$tmux_session" 2>/dev/null; then
echo "Killed tmux session '${tmux_session}'"
else
echo "No tmux session '${tmux_session}' found"
fi
# Remove worktrees
local removed=0
for wt in "${WORKTREE_DIR}"/gpu*; do
[ -d "$wt" ] || continue
git -C "$REPO_ROOT" worktree remove "$wt" --force 2>/dev/null && removed=$((removed + 1))
done
echo "Removed ${removed} worktree(s)"
echo "Branches kept for review (git branch -l 'autoresearch/${tag}-*')"
}
cmd_status() {
echo "Active tmux sessions:"
tmux list-sessions 2>/dev/null | grep "^autoresearch-" || echo " (none)"
echo ""
echo "Active worktrees:"
git -C "$REPO_ROOT" worktree list 2>/dev/null | grep "worktrees/" || echo " (none)"
echo ""
echo "Research branches:"
git -C "$REPO_ROOT" branch -l 'autoresearch/*' 2>/dev/null || echo " (none)"
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
if [ $# -lt 1 ]; then
usage
fi
COMMAND="$1"
shift
case "$COMMAND" in
launch) cmd_launch "$@" ;;
stop) cmd_stop "$@" ;;
status) cmd_status "$@" ;;
*) usage ;;
esac