diff --git a/.agents/skills/multi-agent-mux-loop/SKILL.md b/.agents/skills/multi-agent-mux-loop/SKILL.md index bde650a..86692c8 100644 --- a/.agents/skills/multi-agent-mux-loop/SKILL.md +++ b/.agents/skills/multi-agent-mux-loop/SKILL.md @@ -38,7 +38,7 @@ sequenceDiagram end else Self-Planning Loop->>Dev: notify direct task execution (Self-planned) - fi + end Loop->>Dev: delegate code implementation Dev-->>Loop: code modification complete diff --git a/.agents/skills/multi-agent-mux-loop/scripts/run_loop.sh b/.agents/skills/multi-agent-mux-loop/scripts/run_loop.sh index 754e6bc..6a28d5e 100644 --- a/.agents/skills/multi-agent-mux-loop/scripts/run_loop.sh +++ b/.agents/skills/multi-agent-mux-loop/scripts/run_loop.sh @@ -3,7 +3,7 @@ # run_loop.sh — Autonomous Planning, Execution, and Peer-Review Orchestrator # =========================================================================== -set -eo pipefail +set -euo pipefail # 1. Load Common Framework Library SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -39,10 +39,20 @@ usage() { while [[ "$#" -gt 0 ]]; do case "$1" in --plan) PLAN_MODE=true; shift ;; - --plan-talk) PLAN_TALK_TURNS="$2"; shift 2 ;; + --plan-talk) + if [[ ! "$2" =~ ^[0-9]+$ ]]; then + echo "ERROR: --plan-talk requires a positive integer." + exit 1 + fi + 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 ;; + --max-loop) + if [[ ! "$2" =~ ^[0-9]+$ ]] || [ "$2" -le 0 ]; then + echo "ERROR: --max-loop requires a positive non-zero integer." + exit 1 + fi + MAX_LOOP="$2"; shift 2 ;; --verbose) VERBOSE=true; shift ;; --cleanup) CLEANUP=true; shift ;; --target-agent) TARGET_AGENT="$2"; shift 2 ;; @@ -73,26 +83,28 @@ log_error() { echo -e "\033[1;31m[✗]\033[0m $1" } -# Helper: Blocking wait for a delegate job's completion or error state +# Helper: Blocking wait for a delegate job's completion or error state (with safety timeout) wait_for_job() { local job_id="$1" local check_interval=3 + local max_wait="${2:-3900}" + local deadline + deadline=$((SECONDS + max_wait)) if [ "$VERBOSE" = true ]; then - log_info "Monitoring job '$job_id' for status changes..." + log_info "Monitoring job '$job_id' for status changes (timeout: ${max_wait}s)..." fi - while true; do - # Fetch job status safely using python to query registry + while [ "$SECONDS" -lt "$deadline" ]; do local status status=$(python3 -c " -import json, yaml, os +import json, os try: with open('.mam/jobs/$job_id/job.json') as f: print(json.load(f).get('status', 'unknown')) except Exception: print('unknown') -") +" 2>/dev/null || echo "unknown") if [ "$status" = "completed" ]; then if [ "$VERBOSE" = true ]; then @@ -102,19 +114,20 @@ except Exception: 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 + log_error "Job '$job_id' timed out after ${max_wait}s." + return 1 } -# Resolve active reviewers from SQL DB or YAML +# Resolve active reviewers from SQL DB or YAML (excluding $TARGET_AGENT) resolve_all_reviewers() { - python3 -c " + TARGET_AGENT="$TARGET_AGENT" python3 -c " import sqlite3, os, yaml, json yaml_path = '.mam/agent-sessions.yaml' db_path = '.mam/agent-sessions.db' +target_agent = os.environ.get('TARGET_AGENT') reviewers = [] if os.path.exists(db_path): try: @@ -122,7 +135,7 @@ if os.path.exists(db_path): cursor = conn.execute('SELECT data FROM sessions') for r in cursor.fetchall(): s = json.loads(r[0]) - if s.get('role') == 'reviewer': + if s.get('role') == 'reviewer' and s.get('name') != target_agent: reviewers.append(s.get('name')) conn.close() except Exception: @@ -132,7 +145,7 @@ if not reviewers and os.path.exists(yaml_path): with open(yaml_path) as f: d = yaml.safe_load(f) or {} for s in d.get('tmux_sessions', []): - if s.get('role') == 'reviewer': + if s.get('role') == 'reviewer' and s.get('name') != target_agent: reviewers.append(s.get('name')) except Exception: pass @@ -174,13 +187,56 @@ print('claude') " } +# Resolve planner session dynamically +resolve_planner_session() { + python3 -c " +import sqlite3, os, yaml, json +yaml_path = '.mam/agent-sessions.yaml' +db_path = '.mam/agent-sessions.db' +planner = 'canary-projects-multi-agent-mux-planner-claude' +if os.path.exists(db_path): + try: + conn = sqlite3.connect(db_path) + row = conn.execute('SELECT name FROM sessions WHERE role=\"planner\" LIMIT 1').fetchone() + if row: + planner = row[0] + conn.close() + print(planner) + 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('role') == 'planner': + print(s.get('name')) + raise SystemExit(0) + except Exception: + pass +print(planner) +" +} + +# Portable Job ID extraction helper (fails-safe, avoids SC1091/grep GNU dependency) +extract_job_id() { + local output="$1" + local job_id + # Portable extraction equivalent to PCRE K + job_id=$(echo "$output" | grep -o 'registered job: [A-Za-z0-9]*' | awk '{print $3}' || true) + echo "$job_id" +} + # 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" +PLANNER_SESSION=$(resolve_planner_session) +log_info "Resolved Planner session: $PLANNER_SESSION" CURRENT_PLAN="" +CREATED_JOBS=() # =========================================================================== # PHASE 1: PLANNING & DISCUSSIONS @@ -196,7 +252,12 @@ if [ "$PLAN_MODE" = true ]; then --type "direct" \ --prompt "태스크 목표를 바탕으로 구체적인 구현 계획서를 작성해주세요. 목표: $TASK") - PLAN_JOB_ID=$(echo "$PLAN_JOB_OUTPUT" | grep -oP 'registered job:\s*\K\w+') + PLAN_JOB_ID=$(extract_job_id "$PLAN_JOB_OUTPUT") + if [ -z "$PLAN_JOB_ID" ]; then + log_error "Failed to register planner job. Output:\n$PLAN_JOB_OUTPUT" + exit 1 + fi + CREATED_JOBS+=("$PLAN_JOB_ID") log_info "Planner Job ID: $PLAN_JOB_ID" if ! wait_for_job "$PLAN_JOB_ID"; then @@ -204,10 +265,11 @@ if [ "$PLAN_MODE" = true ]; then 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) + # Retrieve plan text safely + PLAN_FILE=$(find ".mam/jobs/$PLAN_JOB_ID" -name "*.md" 2>/dev/null | head -n 1 || true) + if [ -z "$PLAN_FILE" ] || [ ! -f "$PLAN_FILE" ]; then + log_error "Final plan file not found." + exit 1 fi CURRENT_PLAN=$(cat "$PLAN_FILE") @@ -223,14 +285,24 @@ if [ "$PLAN_MODE" = true ]; then --type "direct" \ --prompt "Planner가 제시한 다음 계획서를 꼼꼼히 검토하고, 실제 구현 시 마주할 수 있는 맹점이나 제약사항 1가지를 발굴하여 Planner에게 이의를 제기(Challenge)해주세요. 계획서:\n$CURRENT_PLAN") - DEBATE_JOB_ID=$(echo "$DEBATE_JOB_OUTPUT" | grep -oP 'registered job:\s*\K\w+') + DEBATE_JOB_ID=$(extract_job_id "$DEBATE_JOB_OUTPUT") + if [ -z "$DEBATE_JOB_ID" ]; then + log_error "Failed to register Creator critique job. Output:\n$DEBATE_JOB_OUTPUT" + exit 1 + fi + CREATED_JOBS+=("$DEBATE_JOB_ID") + log_info "Critique Job ID: $DEBATE_JOB_ID" 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_FILE=$(find ".mam/jobs/$DEBATE_JOB_ID" -name "*.md" 2>/dev/null | head -n 1 || true) + if [ -z "$CRITIQUE_FILE" ] || [ ! -f "$CRITIQUE_FILE" ]; then + log_error "Critique file not found." + exit 1 + fi CRITIQUE_TEXT=$(cat "$CRITIQUE_FILE") log_info "Planner refining plan with Creator's feedback..." @@ -240,14 +312,24 @@ if [ "$PLAN_MODE" = true ]; then --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+') + REFINE_JOB_ID=$(extract_job_id "$REFINE_JOB_OUTPUT") + if [ -z "$REFINE_JOB_ID" ]; then + log_error "Failed to register plan refinement job. Output:\n$REFINE_JOB_OUTPUT" + exit 1 + fi + CREATED_JOBS+=("$REFINE_JOB_ID") + log_info "Refinement Job ID: $REFINE_JOB_ID" 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) + REFINE_FILE=$(find ".mam/jobs/$REFINE_JOB_ID" -name "*.md" 2>/dev/null | head -n 1 || true) + if [ -z "$REFINE_FILE" ] || [ ! -f "$REFINE_FILE" ]; then + log_error "Refinement plan file not found." + exit 1 + fi CURRENT_PLAN=$(cat "$REFINE_FILE") turn=$((turn + 1)) @@ -272,7 +354,12 @@ EXEC_JOB_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-m --type "direct" \ --prompt "$EXECUTION_PROMPT") -EXEC_JOB_ID=$(echo "$EXEC_JOB_OUTPUT" | grep -oP 'registered job:\s*\K\w+') +EXEC_JOB_ID=$(extract_job_id "$EXEC_JOB_OUTPUT") +if [ -z "$EXEC_JOB_ID" ]; then + log_error "Failed to register Creator execution job. Output:\n$EXEC_JOB_OUTPUT" + exit 1 +fi +CREATED_JOBS+=("$EXEC_JOB_ID") log_info "Creator Job ID: $EXEC_JOB_ID" if ! wait_for_job "$EXEC_JOB_ID"; then @@ -289,9 +376,13 @@ 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)" + # Parse list safely using command substitution + fallback + RESOLVED_REVS=$(resolve_all_reviewers) + if [ -n "$RESOLVED_REVS" ]; then + IFS=' ,' read -r -a REVIEWERS <<< "$RESOLVED_REVS" + fi elif [ -n "$REVIEWER_LIST" ]; then - IFS=',' read -r -a REVIEWERS <<< "$REVIEWER_LIST" + IFS=' ,' read -r -a REVIEWERS <<< "$REVIEWER_LIST" fi loop_count=1 @@ -300,106 +391,153 @@ while [ "$loop_count" -le "$MAX_LOOP" ]; do 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") + --prompt "작업 완료 상태에 대해 스스로 검증(Self-Review)하여 결함이 없음을 확인하고 종결해주세요. 리포트에 반드시 '[VERDICT: PASS]' 혹은 '[VERDICT: NOT PASS]' 태그를 명시해주세요.") - 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." + SELF_REV_ID=$(extract_job_id "$SELF_REV_OUTPUT") + if [ -z "$SELF_REV_ID" ]; then + log_error "Failed to register Self-Review job." exit 1 fi + CREATED_JOBS+=("$SELF_REV_ID") + wait_for_job "$SELF_REV_ID" - # 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" + REPORT_FILE=$(find ".mam/jobs/$SELF_REV_ID" -name "*.md" 2>/dev/null | head -n 1 || true) + if [ -n "$REPORT_FILE" ] && [ -f "$REPORT_FILE" ] && grep -q "\[VERDICT: PASS\]" "$REPORT_FILE" && ! grep -q "\[VERDICT: NOT PASS\]" "$REPORT_FILE"; then + log_success "Self-Review PASS." + break else - log_info "Applying straight bugfixes based on reviewer feedback..." - CORRECTION_PROMPT="리뷰어들이 지적한 다음 피드백에 입각하여 코드를 수정해주세요. 피드백:\n$FEEDBACK_AGGREGATE" + log_warn "Self-Review NOT PASS." + if [ "$loop_count" -eq "$MAX_LOOP" ]; then + log_error "Reached max loop count. Self-review loop aborted with failures." + exit 1 + fi fi + else + log_info "Active reviewers: ${REVIEWERS[*]}" - # 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") + # We use space-separated lists or simple loops to bypass bash-4 associative array requirement (M-7 macOS compatibility) + declare -a JOB_IDS=() + declare -a JOB_REVS=() - CORRECT_JOB_ID=$(echo "$CORRECT_JOB_OUTPUT" | grep -oP 'registered job:\s*\K\w+') - wait_for_job "$CORRECT_JOB_ID" + for rev in "${REVIEWERS[@]}"; do + log_info "Requesting code review from Reviewer '$rev'..." + # Cumulative working tree diff (M-6) + CHANGES_DIFF=$(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=$(extract_job_id "$REV_OUTPUT") + if [ -z "$REV_JOB_ID" ]; then + log_error "Failed to register review job for '$rev'." + exit 1 + fi + CREATED_JOBS+=("$REV_JOB_ID") + JOB_IDS+=("$REV_JOB_ID") + JOB_REVS+=("$rev") + done + + # Wait for all reviews + all_passed=true + FEEDBACK_AGGREGATE="" + + for idx in "${!JOB_IDS[@]}"; do + job_id="${JOB_IDS[$idx]}" + rev="${JOB_REVS[$idx]}" + + if ! wait_for_job "$job_id"; then + log_warn "Reviewer '$rev' job crashed." + all_passed=false + continue + fi + + # Parse verdict from report file (fails-safe, M-2 anchored checks) + REPORT_FILE=$(find ".mam/jobs/$job_id" -name "*.md" 2>/dev/null | head -n 1 || true) + if [ -z "$REPORT_FILE" ] || [ ! -f "$REPORT_FILE" ]; then + log_warn "Reviewer '$rev' report not found. Counting as NOT PASS." + all_passed=false + continue + fi + + REPORT_CONTENT=$(cat "$REPORT_FILE" 2>/dev/null || echo "") + + # Precedence rules: NOT PASS wins over PASS. Absence of verdict tags is treated as NOT PASS (fail-closed) + if grep -q "\[VERDICT: NOT PASS\]" "$REPORT_FILE" || ! grep -q "\[VERDICT: PASS\]" "$REPORT_FILE"; then + log_warn "Reviewer '$rev': NOT PASS" + all_passed=false + FEEDBACK_AGGREGATE="$FEEDBACK_AGGREGATE\n--- Reviewer ($rev) Feedback ---\n$REPORT_CONTENT" + else + log_success "Reviewer '$rev': PASS" + 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=$(extract_job_id "$REFINE_PLAN_OUTPUT") + if [ -z "$REFINE_PLAN_ID" ]; then + log_error "Failed to register plan refinement job." + exit 1 + fi + CREATED_JOBS+=("$REFINE_PLAN_ID") + wait_for_job "$REFINE_PLAN_ID" + + REFINE_PLAN_FILE=$(find ".mam/jobs/$REFINE_PLAN_ID" -name "*.md" 2>/dev/null | head -n 1 || true) + if [ -z "$REFINE_PLAN_FILE" ] || [ ! -f "$REFINE_PLAN_FILE" ]; then + log_error "Refined plan file not found." + exit 1 + fi + 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=$(extract_job_id "$CORRECT_JOB_OUTPUT") + if [ -z "$CORRECT_JOB_ID" ]; then + log_error "Failed to register Creator correction job." + exit 1 + fi + CREATED_JOBS+=("$CORRECT_JOB_ID") + wait_for_job "$CORRECT_JOB_ID" + fi fi loop_count=$((loop_count + 1)) @@ -409,9 +547,17 @@ 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 + log_info "Cleaning up temporary job directories created during this loop run..." + for job in "${CREATED_JOBS[@]}"; do + if [ -d ".mam/jobs/$job" ]; then + rm -rf ".mam/jobs/$job" + rm -f ".mam/jobs/$job.subscriber.out" + if [ "$VERBOSE" = true ]; then + log_info "Purged temp assets for job: $job" + fi + fi + done + log_success "Cleanup complete." fi log_success "Mux loop finished with 100% PASS verdicts." diff --git a/PLAN_LOOP.md b/PLAN_LOOP.md new file mode 100644 index 0000000..e25736f --- /dev/null +++ b/PLAN_LOOP.md @@ -0,0 +1,104 @@ +# 📑 자율 반복 정제 루프 스킬 (`multi-agent-mux-loop`) 개발 계획서 + +이 문서는 멀티 에이전트 자율 오케스트레이션 루프(`multi-agent-mux-loop`)의 **최종 안전/가드레일 옵션 규격을 포함하여 완벽하게 정제된 마스터 계획서**입니다. +리뷰어 에이전트들의 교차 2차 피드백(Verdict 파싱, 자가 리뷰 방지, 타임아웃 보강)을 완벽하게 수렴하여 정교하게 갱신되었습니다. + +--- + +## 1. ⚙️ 최종 스킬 명령 및 전체 옵션 세트 명세 (CLI Spec) + +```bash +$ bash .agents/skills/multi-agent-mux-loop/scripts/run_loop.sh \ + [--plan] \ + [--plan-talk N] \ + [--reviewer "reviewer-1,reviewer-2"] \ + [--all-reviewer] \ + [--max-loop N] \ + [--verbose] \ + [--cleanup] \ + --target-agent "canary-projects-multi-agent-mux-creator-claude" \ + --task "수행할 작업 목표" +``` + +### 📥 옵션 상세 리스트 및 가드레일 제약 + +| 옵션명 | 기본값 | 분류 | 역할 및 안전 조치 | +|---|---|---|---| +| `--plan` | 비활성 | 기능 | Planner 에이전트를 기동하여 협력 계획 수립 및 토론 단계 개시. | +| `--plan-talk N` | `1` | 안전 | 플래너-작업자 간 토론 왕복 횟수 상한선. 토큰 낭비 무한 토론 차단. | +| `--reviewer "A,B"` | 비활성 | 기능 | 지정된 peer 리뷰어 에이전트 세션(들)에 피드백 루프 의뢰 (주 작업자 세션은 강제 제외). | +| `--all-reviewer` | 비활성 | 기능 | 레지스트리 상의 모든 `role: reviewer` 세션들을 전수 자동 수집하여 의뢰 (주 작업자 세션은 강제 제외). | +| `--max-loop N` | `3` | **안전 (필수)** | 반려(`NOT PASS`) 시 최대 수정 횟수 제한. **토큰 비용 폭주 방지 가드레일.** | +| `--verbose` | 비활성 | 편의 | 단계별 타임라인 진행 상태 및 잡 매핑 로그의 실시간 상세 출력. | +| `--cleanup` | 비활성 | 편의 | 루프 완료 후 성공한 임시 잡 파일(`.mam/jobs/`)들의 자동 클린업 청소. | +| `--target-agent` | (필수) | 인프라 | 구현을 처리할 주 개발자(Creator) 세션 이름 명시. | +| `--task` | (필수) | 인프라 | 자율 루프에 전달할 최종 구현 지시사항 텍스트. | + +--- + +## 🔄 2. 자율 오케스트레이션 상세 파이프라인 (Sequence Flow) + +```mermaid +sequenceDiagram + autonumber + actor User as 사용자 / run_loop.sh + participant Plan as Planner Claude + participant Dev as Creator Claude + participant Rev as Reviewer Agents + + User->>User: run_loop.sh 기동 (옵션 세트 검증 및 대상 예외 필터링) + + %% Planning & Challenge discussion + alt --plan 지정 시 + User->>Plan: delegate-job (계획 수립 지시) + Plan-->>User: 계획서 도출 완료 + loop 지정된 --plan-talk 횟수 동안 반복 (기본 1회) + User->>Dev: delegate-job (계획서 비판적 검토 및 이의제기 지시) + Dev->>Plan: 계획서의 맹점 1가지 이상 Challenge 메일 교환 + Plan-->>Dev: 수정 반영 및 최종 계획 합의 + end + else --plan 미지정 + User->>Dev: Self-planning 지시 (스스로 계획 세워 즉시 시작) + fi + + %% Execution + User->>Dev: delegate-job (작업 지시) + Dev-->>User: 구현 완료 (git diff 발생) + + %% Peer-Review Loop with Max-Loop constraint + loop 최대 --max-loop 횟수 동안 반복 (기본 3회) + alt 리뷰어 옵션 지정 시 (--reviewer or --all-reviewer) + User->>Rev: delegate-job (정식 peer 코드 리뷰 위임) + Rev-->>User: [VERDICT: PASS] 또는 [VERDICT: NOT PASS] 태그 리포트 제출 + alt 100% PASS 충족 시 + Note over User,Rev: 루프 즉시 탈출 (성공) + else NOT PASS 검출 시 + User->>Dev: 피드백 전달 및 수정 지시 (피드백 난이도에 따라 Planner 우회 계획 갱신 적용) + end + else 리뷰어 미지정 + User->>Dev: Self-Review 지시 (자가 검증 및 자율 종결) + fi + end + + %% Cleanup & Final Report + alt --cleanup 지정 시 + User->>User: 임시 잡 폴더 청소 + end + User-->>User: 최종 결과 요약 출력 및 마감 +``` + +--- + +## 🛠️ 3. 개발 로직 및 안전 파싱 체크포인트 + +### 1) Verdict 판정 파서 안전 가이드라인 (Fail-Closed & Precedence) +* **NOT PASS 우선권**: 리뷰 리포트 본문 내에 `[VERDICT: NOT PASS]` 가 단 한 번이라도 등장하면, `[VERDICT: PASS]` 문구 존재 여부와 상관없이 무조건 **NOT PASS**로 처리하여 오독 필터링을 방지합니다. +* **Fail-Closed 기본 실패주의**: 태그 누락이나 malformed 리포트로 인해 두 토큰이 모두 스캔되지 않을 경우, 통과시키지 않고 **NOT PASS(실패)** 로 취급하여 루프 무한 기동 및 맹점 통과를 원천 차단합니다. +* **템플릿 명시**: 리뷰어 위임 잡 발행 시, 최종 결과 요약 행에 정형화된 태그 `[VERDICT: PASS]` 혹은 `[VERDICT: NOT PASS]`를 리포트 본문 하단에 반드시 기재하도록 프롬프트 템플릿에 명시적으로 추가합니다. + +### 2) 자가 리뷰 방지 가드 (Exclusion Rule) +* `--all-reviewer` 혹은 `--reviewer` 목록을 소집할 때, 해당 작업을 수행한 대상 개발자 세션인 `$TARGET_AGENT` 는 **리뷰어 매핑 목록에서 강제로 배제(Exclude)** 하도록 파싱 쉘 스크립트에서 필터링을 적용합니다. + +### 3) 쉘 예외 처리 및 대기 타임아웃 (Error Guard & Timeout) +* `grep -oP` 나 `find | head` 시 매칭이 없을 때 `set -eo pipefail`에 의해 쉘 스크립트 전체가 비명횡사하지 않도록 `|| true` 가드 및 공백 체크문을 엄밀히 적용합니다. +* `wait_for_job` 함수 실행 시 타임아웃 가드레일(`WAIT_TIMEOUT`, 기본값 3600초)을 명시적으로 설계하여 무한 루프 행(Hang) 현상을 차단합니다.