From c46de6815cbce7da4f5e5221f4b3a684997b0818 Mon Sep 17 00:00:00 2001 From: Sheldon Finlay Date: Sat, 28 Mar 2026 12:17:30 -0400 Subject: [PATCH] refactor: remove headless mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Headless mode was half-built and untested. Agent-loop is a plugin that runs interactively via tmux — there's no CI use case yet. Removes --headless flag, timeout compatibility shim, output capture logic, and LOOP_AGENT_TMPFILE handling. Cuts 82 lines from loop.sh. --- README.md | 14 ----- install.sh | 2 +- loop.sh | 146 ++++++++++++----------------------------------------- 3 files changed, 33 insertions(+), 129 deletions(-) diff --git a/README.md b/README.md index f4745be..b391e00 100644 --- a/README.md +++ b/README.md @@ -72,20 +72,6 @@ Or ask Claude Code "status" — it reads `.loop/prd.json` and `.loop/progress.md Each generator and evaluator run is a full Claude Code session saved to history. Use `claude -r` to resume any session and inspect what happened, debug a rejection, or continue from where it left off. -## Headless Mode - -For CI or background execution without the interactive UI: - -```bash -.loop/loop.sh --headless [options] - ---headless Run without interactive UI ---mode Operating mode ---max Maximum iterations (default: 20) ---skip-eval Skip evaluator pass ---dry-run Print assembled prompts without running -``` - ## Architecture ### Generator diff --git a/install.sh b/install.sh index 4d211be..e5b6dc5 100755 --- a/install.sh +++ b/install.sh @@ -102,5 +102,5 @@ echo " Next steps (inside Claude Code, in any project):" echo "" echo " /agent-loop:run # Single command — setup, plan, and run" echo "" -echo " Or run headless: .loop/loop.sh" +echo " Or run directly: .loop/loop.sh" echo "" diff --git a/loop.sh b/loop.sh index f6482be..940efbe 100755 --- a/loop.sh +++ b/loop.sh @@ -13,7 +13,6 @@ # --no-hooks Don't install stop hooks # --dry-run Print assembled prompts without running agents # --resume Skip already-passed stories (explicit mode) -# --replan (reserved — not yet implemented) # # Each iteration: # 1. Generator: picks highest-priority incomplete story, does the work @@ -81,23 +80,6 @@ if ! command -v jq &>/dev/null && ! command -v python3 &>/dev/null; then exit 1 fi -# --- macOS timeout compatibility --- -# macOS doesn't have GNU timeout. Use gtimeout (from coreutils) or a perl fallback. -if ! command -v timeout &>/dev/null; then - if command -v gtimeout &>/dev/null; then - timeout() { gtimeout "$@"; } - else - # Perl-based fallback: runs command with alarm signal - timeout() { - local duration="$1"; shift - perl -e ' - alarm shift @ARGV; - exec @ARGV; - ' "$duration" "$@" - } - fi -fi - # --- Load config defaults --- CONFIG_FILE="$LOOP_DIR/config.json" config_default() { get_config_value "$1" "$2"; } @@ -122,9 +104,7 @@ while [[ $# -gt 0 ]]; do --tool=*) TOOL="${1#*=}"; shift ;; --no-hooks) AUTO_HOOKS=false; shift ;; --dry-run) DRY_RUN=true; shift ;; - --headless) export LOOP_HEADLESS=true; shift ;; --resume) RESUME=true; shift ;; - --replan) log "ERROR: --replan is not yet implemented. Use /agent-loop:stories interactively."; exit 1 ;; [0-9]*) MAX_ITERATIONS="$1"; shift ;; *) log "Unknown option: $1"; exit 1 ;; esac @@ -147,7 +127,6 @@ fi cd "$PROJECT_ROOT" cleanup() { - [ -n "${LOOP_AGENT_TMPFILE:-}" ] && rm -f "$LOOP_AGENT_TMPFILE" # Remove hooks in case we exit mid-agent (Ctrl+C during a claude session) [ "$AUTO_HOOKS" = true ] && remove_hooks 2>/dev/null release_lock @@ -178,8 +157,6 @@ finish() { read -r -t 30 2>/dev/null || true exit "$exit_code" } -LOOP_AGENT_TMPFILE="" - # NOTE: Stop hook is installed/removed per-agent in run_agent(), not globally. # This prevents the hook from killing the orchestrating CC session. trap cleanup EXIT INT TERM @@ -215,14 +192,10 @@ fi # --- Agent runner --- # Runs a prompt through the selected AI tool. # -# Interactive (default): Pipes prompt to claude WITHOUT --print. -# This gives the full interactive CC UI — tool calls, file edits, etc. -# A Stop hook (installed at startup) sends SIGINT to the loop when claude -# finishes, which returns control to the while loop for the next iteration. -# State is tracked via files (prd.json, .verdict), not stdout. -# -# Headless (LOOP_HEADLESS=true): Uses claude --print for CI/background. -# Output captured to file for verdict parsing. +# Pipes prompt to claude WITHOUT --print. This gives the full interactive +# CC UI — tool calls, file edits, etc. A Stop hook sends SIGINT to the loop +# when claude finishes, returning control to the while loop for the next +# iteration. State is tracked via files (prd.json, .verdict), not stdout. run_agent() { local prompt="$1" local role="${2:-}" @@ -230,65 +203,31 @@ run_agent() { rm -f "$LOOP_DIR/.verdict" local agent_exit=0 - if [ "${LOOP_HEADLESS:-false}" != "true" ]; then - # --- Interactive mode (Ralph pattern) --- - # Install Stop hook just before claude starts, remove after it exits. - # This scopes the hook to only affect the loop's claude sessions. - [ "$AUTO_HOOKS" = true ] && install_hooks + # Install Stop hook just before claude starts, remove after it exits. + # This scopes the hook to only affect the loop's claude sessions. + [ "$AUTO_HOOKS" = true ] && install_hooks - ( - case "$TOOL" in - claude) - printf '%s\n' "$prompt" | claude --dangerously-skip-permissions - ;; - amp) - printf '%s\n' "$prompt" | amp --dangerously-allow-all - ;; - *) - log "ERROR: Unknown tool '$TOOL'" - exit 1 - ;; - esac - ) || agent_exit=$? + ( + case "$TOOL" in + claude) + printf '%s\n' "$prompt" | claude --dangerously-skip-permissions + ;; + amp) + printf '%s\n' "$prompt" | amp --dangerously-allow-all + ;; + *) + log "ERROR: Unknown tool '$TOOL'" + exit 1 + ;; + esac + ) || agent_exit=$? - [ "$AUTO_HOOKS" = true ] && remove_hooks - sleep 2 # Brief pause between sessions + [ "$AUTO_HOOKS" = true ] && remove_hooks + sleep 2 # Brief pause between sessions - # Read verdict from file if evaluator wrote one - if [ "$role" = "evaluator" ] && [ -f "$LOOP_DIR/.verdict" ]; then - cat "$LOOP_DIR/.verdict" - fi - else - # --- Headless mode --- - local output_file - output_file=$(mktemp) - LOOP_AGENT_TMPFILE="$output_file" - - ( - case "$TOOL" in - claude) - printf '%s\n' "$prompt" | timeout "${LOOP_AGENT_TIMEOUT:-600}" \ - claude --dangerously-skip-permissions --output-format text \ - --print > "$output_file" 2>&1 - ;; - amp) - printf '%s\n' "$prompt" | timeout "${LOOP_AGENT_TIMEOUT:-600}" \ - amp --dangerously-allow-all > "$output_file" 2>&1 - ;; - *) - log "ERROR: Unknown tool '$TOOL'" - exit 1 - ;; - esac - ) || agent_exit=$? - - if [ "$agent_exit" -ne 0 ] && [ ! -s "$output_file" ]; then - log "WARNING: Agent exited with code $agent_exit and produced no output." - fi - - cat "$output_file" - rm -f "$output_file" - LOOP_AGENT_TMPFILE="" + # Read verdict from file if evaluator wrote one + if [ "$role" = "evaluator" ] && [ -f "$LOOP_DIR/.verdict" ]; then + cat "$LOOP_DIR/.verdict" fi } @@ -371,18 +310,7 @@ while [ "$ITERATION" -lt "$MAX_ITERATIONS" ]; do exit 0 fi - if [ "${LOOP_HEADLESS:-false}" != "true" ]; then - # Interactive: run directly, no capture. User sees full CC UI. - run_agent "$GENERATOR_PROMPT" "generator" - GENERATOR_OUTPUT="" - else - # Headless: capture output for parsing. - GENERATOR_OUTPUT=$(run_agent "$GENERATOR_PROMPT" "generator") - if [ -z "$GENERATOR_OUTPUT" ]; then - log "WARNING: Generator produced empty output (timeout or crash). Skipping to next iteration." - continue - fi - fi + run_agent "$GENERATOR_PROMPT" "generator" # --- Scope budget check --- # Verify the generator stayed within configured limits (files modified, lines written). @@ -419,22 +347,12 @@ while [ "$ITERATION" -lt "$MAX_ITERATIONS" ]; do EVAL_PROMPT=$(build_prompt "evaluator" "$MODE") - if [ "${LOOP_HEADLESS:-false}" != "true" ]; then - # Interactive: run directly, read verdict from file. - run_agent "$EVAL_PROMPT" "evaluator" - if [ -f "$LOOP_DIR/.verdict" ]; then - EVAL_OUTPUT=$(cat "$LOOP_DIR/.verdict") - else - log "WARNING: No verdict file found. Treating as REJECT." - EVAL_OUTPUT="REJECTEvaluator produced no verdict file" - fi + run_agent "$EVAL_PROMPT" "evaluator" + if [ -f "$LOOP_DIR/.verdict" ]; then + EVAL_OUTPUT=$(cat "$LOOP_DIR/.verdict") else - # Headless: capture output for parsing. - EVAL_OUTPUT=$(run_agent "$EVAL_PROMPT" "evaluator") - if [ -z "$EVAL_OUTPUT" ]; then - log "WARNING: Evaluator produced empty output. Treating as REJECT." - EVAL_OUTPUT="REJECTEvaluator produced no output" - fi + log "WARNING: No verdict file found. Treating as REJECT." + EVAL_OUTPUT="REJECTEvaluator produced no verdict file" fi VERDICT=$(parse_verdict "$EVAL_OUTPUT")