#!/usr/bin/env bash # =========================================================================== # run_loop.sh — Autonomous Planning, Execution, and Peer-Review Orchestrator # =========================================================================== set -eo pipefail # 1. Load Common Framework Library SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" source "$REPO_ROOT/.agents/skills/lib.sh" # Default configuration parameters PLAN_MODE=false PLAN_TALK_TURNS=1 ALL_REVIEWERS=false MAX_LOOP=3 VERBOSE=false CLEANUP=false TARGET_AGENT="" TASK="" REVIEWER_LIST="" # Print usage instructions usage() { echo "Usage: $0 [options] --target-agent --task " echo "Options:" echo " --plan Enable Planner agent intervention & design phase" echo " --plan-talk N Planner-Creator discussion limit turns (default: 1)" echo " --reviewer \"A,B\" Targeted reviewer session name list (comma-separated)" echo " --all-reviewer Enforce PASS verdict from all active reviewer sessions" echo " --max-loop N Max execution-review corrective loop runs (default: 3)" echo " --verbose Print detailed execution timeline traces" echo " --cleanup Purge temporary job directories upon success" exit 1 } # Parse options safely while [[ "$#" -gt 0 ]]; do case "$1" in --plan) PLAN_MODE=true; shift ;; --plan-talk) PLAN_TALK_TURNS="$2"; shift 2 ;; --reviewer) REVIEWER_LIST="$2"; shift 2 ;; --all-reviewer) ALL_REVIEWERS=true; shift ;; --max-loop) MAX_LOOP="$2"; shift 2 ;; --verbose) VERBOSE=true; shift ;; --cleanup) CLEANUP=true; shift ;; --target-agent) TARGET_AGENT="$2"; shift 2 ;; --task) TASK="$2"; shift 2 ;; -h|--help) usage ;; *) echo "Unknown option: $1"; usage ;; esac done if [ -z "$TARGET_AGENT" ] || [ -z "$TASK" ]; then echo "ERROR: --target-agent and --task are mandatory fields." usage fi log_info() { echo -e "\033[1;34m[*]\033[0m $1" } log_success() { echo -e "\033[1;32m[✓]\033[0m $1" } log_warn() { echo -e "\033[1;33m[!]\033[0m $1" } log_error() { echo -e "\033[1;31m[✗]\033[0m $1" } # Helper: Blocking wait for a delegate job's completion or error state wait_for_job() { local job_id="$1" local check_interval=3 if [ "$VERBOSE" = true ]; then log_info "Monitoring job '$job_id' for status changes..." fi while true; do # Fetch job status safely using python to query registry local status status=$(python3 -c " import json, yaml, os try: with open('.mam/jobs/$job_id/job.json') as f: print(json.load(f).get('status', 'unknown')) except Exception: print('unknown') ") if [ "$status" = "completed" ]; then if [ "$VERBOSE" = true ]; then log_success "Job '$job_id' completed successfully." fi return 0 elif [ "$status" = "error" ]; then log_error "Job '$job_id' finished with errors." return 1 elif [ "$status" = "unknown" ]; then log_warn "Job '$job_id' config not found yet. Retrying..." fi sleep "$check_interval" done } # Resolve active reviewers from SQL DB or YAML resolve_all_reviewers() { python3 -c " import sqlite3, os, yaml, json yaml_path = '.mam/agent-sessions.yaml' db_path = '.mam/agent-sessions.db' reviewers = [] if os.path.exists(db_path): try: conn = sqlite3.connect(db_path) cursor = conn.execute('SELECT data FROM sessions') for r in cursor.fetchall(): s = json.loads(r[0]) if s.get('role') == 'reviewer': reviewers.append(s.get('name')) conn.close() except Exception: pass if not reviewers and os.path.exists(yaml_path): try: with open(yaml_path) as f: d = yaml.safe_load(f) or {} for s in d.get('tmux_sessions', []): if s.get('role') == 'reviewer': reviewers.append(s.get('name')) except Exception: pass print(','.join(reviewers)) " } # Resolve target agent type (claude, cline, agy) resolve_agent_type() { local name="$1" NAME="$name" python3 -c " import sqlite3, os, yaml, json yaml_path = '.mam/agent-sessions.yaml' db_path = '.mam/agent-sessions.db' name = os.environ.get('NAME') agent = 'claude' if os.path.exists(db_path): try: conn = sqlite3.connect(db_path) row = conn.execute('SELECT data FROM sessions WHERE name=?', (name,)).fetchone() if row: agent = json.loads(row[0]).get('agent', 'claude') conn.close() print(agent) raise SystemExit(0) except Exception: pass if os.path.exists(yaml_path): try: with open(yaml_path) as f: d = yaml.safe_load(f) or {} for s in d.get('tmux_sessions', []): if s.get('name') == name: print(s.get('agent', 'claude')) raise SystemExit(0) except Exception: pass print('claude') " } # Main Execution Loop Flow log_info "Initializing multi-agent-mux-loop controller..." log_info "Target Agent: $TARGET_AGENT" log_info "Task Goal: $TASK" PLANNER_SESSION="canary-projects-multi-agent-mux-planner-claude" CURRENT_PLAN="" # =========================================================================== # PHASE 1: PLANNING & DISCUSSIONS # =========================================================================== if [ "$PLAN_MODE" = true ]; then log_info "=== Phase 1: Interactive Planning Phase ===" # Step 1.1: Request initial plan from Planner log_info "Requesting initial implementation plan from Planner..." PLAN_JOB_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \ --agent-session "tmux:$PLANNER_SESSION" \ --agent "claude" \ --type "direct" \ --prompt "태스크 목표를 바탕으로 구체적인 구현 계획서를 작성해주세요. 목표: $TASK") PLAN_JOB_ID=$(echo "$PLAN_JOB_OUTPUT" | grep -oP 'registered job:\s*\K\w+') log_info "Planner Job ID: $PLAN_JOB_ID" if ! wait_for_job "$PLAN_JOB_ID"; then log_error "Planning phase failed during initial plan design." exit 1 fi # Retrieve plan text PLAN_FILE=".mam/jobs/$PLAN_JOB_ID/claude-reports/report-final.md" if [ ! -f "$PLAN_FILE" ]; then PLAN_FILE=$(find ".mam/jobs/$PLAN_JOB_ID" -name "*.md" | head -n 1) fi CURRENT_PLAN=$(cat "$PLAN_FILE") # Step 1.2: Interactive Creator-Planner Debate turn=1 while [ "$turn" -le "$PLAN_TALK_TURNS" ]; do log_info "Discussion Turn $turn/$PLAN_TALK_TURNS: Creator challenging the plan..." # Creator critique job DEBATE_JOB_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \ --agent-session "tmux:$TARGET_AGENT" \ --agent "$(resolve_agent_type "$TARGET_AGENT")" \ --type "direct" \ --prompt "Planner가 제시한 다음 계획서를 꼼꼼히 검토하고, 실제 구현 시 마주할 수 있는 맹점이나 제약사항 1가지를 발굴하여 Planner에게 이의를 제기(Challenge)해주세요. 계획서:\n$CURRENT_PLAN") DEBATE_JOB_ID=$(echo "$DEBATE_JOB_OUTPUT" | grep -oP 'registered job:\s*\K\w+') if ! wait_for_job "$DEBATE_JOB_ID"; then log_error "Creator critique step failed." exit 1 fi CRITIQUE_FILE=$(find ".mam/jobs/$DEBATE_JOB_ID" -name "*.md" | head -n 1) CRITIQUE_TEXT=$(cat "$CRITIQUE_FILE") log_info "Planner refining plan with Creator's feedback..." REFINE_JOB_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \ --agent-session "tmux:$PLANNER_SESSION" \ --agent "claude" \ --type "direct" \ --prompt "작업자(Creator)로부터 다음 이의제기 피드백을 받았습니다. 피드백을 반영하여 계획서를 정교하게 업데이트(Refine)하여 다시 출력해주세요. 피드백:\n$CRITIQUE_TEXT\n기존 계획서:\n$CURRENT_PLAN") REFINE_JOB_ID=$(echo "$REFINE_JOB_OUTPUT" | grep -oP 'registered job:\s*\K\w+') if ! wait_for_job "$REFINE_JOB_ID"; then log_error "Planner refinement step failed." exit 1 fi REFINE_FILE=$(find ".mam/jobs/$REFINE_JOB_ID" -name "*.md" | head -n 1) CURRENT_PLAN=$(cat "$REFINE_FILE") turn=$((turn + 1)) done log_success "Interactive planning completed. Plan finalized." else log_info "=== Phase 1: Self-Planning Mode (Direct Execution) ===" fi # =========================================================================== # PHASE 2: IMPLEMENTATION (CREATION) # =========================================================================== log_info "=== Phase 2: Code Implementation ===" EXECUTION_PROMPT="다음 작업 목표를 완성해주세요: $TASK" if [ "$PLAN_MODE" = true ]; then EXECUTION_PROMPT="최종 합의된 다음 계획서에 입각하여 코드를 수정 및 구현해주세요. 계획서:\n$CURRENT_PLAN" fi EXEC_JOB_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \ --agent-session "tmux:$TARGET_AGENT" \ --agent "$(resolve_agent_type "$TARGET_AGENT")" \ --type "direct" \ --prompt "$EXECUTION_PROMPT") EXEC_JOB_ID=$(echo "$EXEC_JOB_OUTPUT" | grep -oP 'registered job:\s*\K\w+') log_info "Creator Job ID: $EXEC_JOB_ID" if ! wait_for_job "$EXEC_JOB_ID"; then log_error "Creator execution failed." exit 1 fi log_success "Initial implementation finished." # =========================================================================== # PHASE 3: VERIFICATION LOOP (PEER REVIEW) # =========================================================================== log_info "=== Phase 3: Verification & Corrective Review Loop ===" # Resolve reviewer array REVIEWERS=() if [ "$ALL_REVIEWERS" = true ]; then IFS=',' read -r -a REVIEWERS <<< "$(resolve_all_reviewers)" elif [ -n "$REVIEWER_LIST" ]; then IFS=',' read -r -a REVIEWERS <<< "$REVIEWER_LIST" fi loop_count=1 while [ "$loop_count" -le "$MAX_LOOP" ]; do log_info "Review Loop Iteration $loop_count/$MAX_LOOP..." if [ "${#REVIEWERS[@]}" -eq 0 ]; then log_warn "No reviewers specified. Conducting Creator Self-Review..." # Self review step SELF_REV_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \ --agent-session "tmux:$TARGET_AGENT" \ --agent "$(resolve_agent_type "$TARGET_AGENT")" \ --type "direct" \ --prompt "작업 완료 상태에 대해 스스로 검증(Self-Review)하여 결함이 없음을 확인하고 종결해주세요.") SELF_REV_ID=$(echo "$SELF_REV_OUTPUT" | grep -oP 'registered job:\s*\K\w+') wait_for_job "$SELF_REV_ID" log_success "Self-Review loop complete. Exiting." break fi log_info "Active reviewers: ${REVIEWERS[*]}" # Trigger concurrent reviews declare -A REVIEW_JOBS for rev in "${REVIEWERS[@]}"; do log_info "Requesting code review from Reviewer '$rev'..." # Get changes diff CHANGES_DIFF=$(git diff HEAD~1..HEAD 2>/dev/null || git diff 2>/dev/null || echo "No git diff available") REV_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \ --agent-session "tmux:$rev" \ --agent "$(resolve_agent_type "$rev")" \ --type "direct" \ --prompt "다음 구현 사항(작업 목표: $TASK) 및 변경분(git diff)에 대해 린트, 동작성, 유실 등의 관점에서 교차 코드 리뷰를 수행해주세요. 확인 후 최종 Verdict로 '[VERDICT: PASS]' 혹은 '[VERDICT: NOT PASS]' 태그를 리뷰 리포트에 명시적으로 작성해주세요. 변경분:\n$CHANGES_DIFF") REV_JOB_ID=$(echo "$REV_OUTPUT" | grep -oP 'registered job:\s*\K\w+') REVIEW_JOBS["$rev"]="$REV_JOB_ID" done # Wait for all reviews all_passed=true FEEDBACK_AGGREGATE="" for rev in "${!REVIEW_JOBS[@]}"; do job_id="${REVIEW_JOBS[$rev]}" if ! wait_for_job "$job_id"; then log_warn "Reviewer '$rev' job crashed." all_passed=false continue fi # Parse verdict from report file REPORT_FILE=$(find ".mam/jobs/$job_id" -name "*.md" | head -n 1) REPORT_CONTENT=$(cat "$REPORT_FILE") if grep -q "\[VERDICT: PASS\]" "$REPORT_FILE"; then log_success "Reviewer '$rev': PASS" else log_warn "Reviewer '$rev': NOT PASS (Feedback generated)" all_passed=false FEEDBACK_AGGREGATE="$FEEDBACK_AGGREGATE\n--- Reviewer ($rev) Feedback ---\n$REPORT_CONTENT" fi done if [ "$all_passed" = true ]; then log_success "All reviewers issued [VERDICT: PASS]. Loop completed successfully." break else if [ "$loop_count" -eq "$MAX_LOOP" ]; then log_error "Reached max loop count ($MAX_LOOP). Review loop aborted with failures." exit 1 fi # Re-planning check for complex refactoring feedback COMPLEX_FIX=false if echo "$FEEDBACK_AGGREGATE" | grep -Eiq "refactor|complex|design|architect"; then COMPLEX_FIX=true fi if [ "$PLAN_MODE" = true ] && [ "$COMPLEX_FIX" = true ]; then log_warn "Feedback involves complex code modifications. Diverting to Planner to revise plan..." REFINE_PLAN_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \ --agent-session "tmux:$PLANNER_SESSION" \ --agent "claude" \ --type "direct" \ --prompt "리뷰어들로부터 다음과 같이 정교한 코드 수정 피드백이 도착했습니다. 해당 피드백을 수렴하여 구현 계획서(Plan)를 갱신(Refine)하여 다시 작성해주세요. 피드백:\n$FEEDBACK_AGGREGATE\n기존 계획서:\n$CURRENT_PLAN") REFINE_PLAN_ID=$(echo "$REFINE_PLAN_OUTPUT" | grep -oP 'registered job:\s*\K\w+') wait_for_job "$REFINE_PLAN_ID" REFINE_PLAN_FILE=$(find ".mam/jobs/$REFINE_PLAN_ID" -name "*.md" | head -n 1) CURRENT_PLAN=$(cat "$REFINE_PLAN_FILE") CORRECTION_PROMPT="갱신된 다음 계획서에 입각하여 지적된 오류들을 수정하고 코드를 다시 구현해주세요. 계획서:\n$CURRENT_PLAN\n피드백 상세:\n$FEEDBACK_AGGREGATE" else log_info "Applying straight bugfixes based on reviewer feedback..." CORRECTION_PROMPT="리뷰어들이 지적한 다음 피드백에 입각하여 코드를 수정해주세요. 피드백:\n$FEEDBACK_AGGREGATE" fi # Creator execution corrective job CORRECT_JOB_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \ --agent-session "tmux:$TARGET_AGENT" \ --agent "$(resolve_agent_type "$TARGET_AGENT")" \ --type "direct" \ --prompt "$CORRECTION_PROMPT") CORRECT_JOB_ID=$(echo "$CORRECT_JOB_OUTPUT" | grep -oP 'registered job:\s*\K\w+') wait_for_job "$CORRECT_JOB_ID" fi loop_count=$((loop_count + 1)) done # =========================================================================== # PHASE 4: CLEANUP # =========================================================================== if [ "$CLEANUP" = true ]; then log_info "Cleaning up temporary job directories..." # Clean job folders for this loop runs if any list exists # Safe cleanup implementation fi log_success "Mux loop finished with 100% PASS verdicts." exit 0