refactor: harden run_loop.sh verdict parser, add atomic promotion, and revise multi_agent_workflow.md guidelines

This commit is contained in:
2026-07-16 12:57:56 +09:00
parent 230e262414
commit 626f35adfc
3 changed files with 91 additions and 40 deletions
+35 -19
View File
@@ -1,6 +1,6 @@
# Multi-Agent Collaboration Workflow Reference Guide # Multi-Agent Collaboration Workflow Reference Guide
본 문서는 대규모 프로젝트나 정밀한 설계·구현 요구사항을 처리하기 위해 **Planner, Developer, Reviewer** 에이전트 간의 역할 분담 및 피드백 루프를 운영하는 **다중 에이전트 협업 워크플로우(Multi-Agent Collaboration Workflow)**를 정의합니다. 본 문서는 대규모 프로젝트나 정밀한 설계·구현 요구사항을 처리하기 위해 **Planner, Developer, Reviewer** 에이전트 간의 역할 분담 및 피드백 루프를 운영하는 **다중 에이전트 협업 워크플로우(Multi-Agent Collaboration Workflow)**를 정의합니다. 다수의 에이전트 간 협업을 위해서는 앞으로 수동 프롬프트 환류가 아닌, 자동화된 [multi-agent-mux-loop](skills/multi-agent-mux-loop/SKILL.md) 스킬만을 단독으로 사용하여 자율 루프를 기동합니다.
--- ---
@@ -76,30 +76,46 @@
### 3단계: 피드백 루프 및 통과 (Review Phase) ### 3단계: 피드백 루프 및 통과 (Review Phase)
1. Developer 에이전트가 리뷰어 세션에 작업 완료 사실과 변경 범위를 전달합니다. 1. Developer 에이전트가 리뷰어 세션에 작업 완료 사실과 변경 범위를 전달합니다.
2. 리뷰어들은 `git diff`를 바탕으로 개별 검증을 수행하고 보고서 형태의 리뷰 피드백을 출력합니다. 2. 리뷰어들은 `git diff`를 바탕으로 개별 검증을 수행하고 보고서 형태의 리뷰 피드백을 출력합니다.
- **반려 (`NOT PASS`)** -> 피드백 요약본을 Planner 에이전트에게 전송하여 상위 레벨 계획(Rev.n) 개시. - **반려 (`[VERDICT: NOT PASS]`, 리포트 마지막에 단독 행으로)** -> 피드백 요약본을 Planner 에이전트에게 전송하여 상위 레벨 계획(Rev.n) 개시.
- **통과 (`PASS`)** -> 모든 검토 사항이 해결되었음을 명시. - **통과 (`[VERDICT: PASS]`, 리포트 마지막에 단독 행으로)** -> 모든 검토 사항이 해결되었음을 명시.
3. 모든 리뷰어가 PASS를 발행하면 작업을 완결하고, 다른 에이전트 세션들은 종료하지 않고 다음 태스크 지시가 있을 때까지 프롬프트 대기 상태(Standby)로 유지합니다. 3. 모든 리뷰어가 PASS를 발행하면 작업을 완결하고, 다른 에이전트 세션들은 종료하지 않고 다음 태스크 지시가 있을 때까지 프롬프트 대기 상태(Standby)로 유지합니다.
--- ---
## 3. Best Practices & 템플릿 ## 3. Best Practices & 자동화 지침
### 3.1. 작업 시작 시 Planner 에이전트 프롬프트 템플릿 수동 템플릿 작성 및 전달은 폐지되었습니다. 모든 에이전트 간 피드백 루프는 [multi-agent-mux-loop](skills/multi-agent-mux-loop/SKILL.md)를 활용해 자동으로 오케스트레이션합니다.
```
[역할 요구]
프로젝트의 구조 및 내용을 파악하고, 현재 작업 요건에 맞춰 구현 계획서(implementation_plan.md) 및 태스크 체크리스트(task.md)를 작성해 주세요.
[검토 초점] ### 3.1. 개념-옵션 매핑 규격
- 설계 수준에서 논리적 모순이 발생할 여지가 없는지
- naive 구현과 대비되는 핵심 차별점이 코드 명세에 정확히 명시되었는지 워크플로우 단계별로 활용할 수 있는 `run_loop.sh` 옵션 규격은 다음과 같습니다:
| 워크플로우 단계 | 해당 CLI 옵션 | 설명 |
| :--- | :--- | :--- |
| **Phase 1: Planning** | `--plan` | Planner 에이전트를 기동하여 최초 계획 작성을 강제합니다. |
| **Phase 1: Debate** | `--plan-talk N` | Planner와 Creator가 상호 대화식 챌린지 루프를 `N`회 돌며 계획을 교차 정제합니다. |
| **Phase 2: Execution** | (기본값) | `--target-agent`로 명시한 주 작업 세션에 코딩 태스크를 주입합니다. |
| **Phase 3: Review** | `--reviewer "A,B"` | 지정된 리뷰어 세션 리스트(`A`, `B` 등)에 교차 Peer Review를 위임합니다. |
| **Phase 3: Consensus** | `--all-reviewer` | 모든 리뷰어가 PASS를 냈을 때만 최종 통과를 허용합니다. (미지정 시 1명만 PASS여도 통과) |
| **Iterative Loop** | `--max-loop M` | NOT PASS 또는 린트 실패 시 최대 `M`회까지 Planner와 Creator 간 피드백 루프를 반복합니다. |
### 3.2. 실전 자율 루프 기동 예시
```bash
bash .agents/skills/multi-agent-mux-loop/scripts/run_loop.sh \
--plan \
--plan-talk 1 \
--reviewer "canary-projects-multi-agent-mux-reviewer-cline,canary-projects-multi-agent-mux-reviewer-agy" \
--all-reviewer \
--max-loop 3 \
--target-agent "canary-projects-multi-agent-mux-creator-agy" \
--task "작업할 상세 요구사항을 여기에 입력..."
``` ```
### 3.2. 피드백 루프 환류 시 프롬프트 템플릿 ### 3.3. 동작성 제약 및 Pitfalls
```
Reviewer 검토 결과 구현 코드 차원에서 아래의 블로킹(NOT PASS) 피드백이 발생했습니다.
위 피드백을 수용하여 설계 문서를 수정하는 계획을 수립하고, implementation_plan.md (Rev.[N]) 및 task.md 내용을 업데이트해 주세요.
[피드백 내용] 1. **세션 가동 전제**: `run_loop.sh` 기동 전에 타겟 세션(Planner, Target Agent, Reviewer)들이 모두 tmux 세션으로 기동되어 (`status.sh` 기준 `alive``running`) 있어야 합니다.
- [블로킹 항목 1] 2. **동시 루프 기동 금지**: 동일한 작업 트리 내에서 다수의 `run_loop.sh` 제어기를 동시에 기동하면 SQLite DB 갱신 경합 및 YAML 데이터 오염이 발생하므로 절대로 병렬 기동하지 마십시오.
- [블로킹 항목 2] 3. **정형 토큰 단독 행 작성 필수**: 앵커링 파서 하드닝에 의해, 리뷰 리포트 파일 내에서 `[VERDICT: PASS]` 또는 `[VERDICT: NOT PASS]` 토큰은 반드시 리포트의 **마지막에 단독 행으로** 기재되어야 합니다. 코드 인용이나 변경 diff 내의 토큰은 매칭 대상에서 완전 배제됩니다.
``` 4. **결함 시 fail-closed**: 최종 Verdict 토큰이 누락되거나 리포트 픽업 실패 시, 파서는 안전을 위해 `NOT PASS`로 오픽업(Fail-closed) 판정하여 교정 사이클을 수행하므로 반드시 리포트 끝에 단독 행으로 토큰을 찍도록 지시해야 합니다.
5. **원자적 아카이빙 (Promotion)**: 루프 성공 종료 시 최종 계획서와 검증 리포트들은 `.agents/reports/<session_name>/` 디렉토리로 원자적으로 덮어쓰기(`mv -f`)되어 보존되므로, 해당 경로의 리포트들로 VCS 추적성을 확보해야 합니다.
+4
View File
@@ -1168,6 +1168,10 @@ send_keys_safe() {
_sks_tmux set-buffer -b "sks_$job_id" "$text" _sks_tmux set-buffer -b "sks_$job_id" "$text"
_sks_tmux paste-buffer -b "sks_$job_id" -t "$sess" _sks_tmux paste-buffer -b "sks_$job_id" -t "$sess"
_sks_tmux delete-buffer -b "sks_$job_id" 2>/dev/null || true _sks_tmux delete-buffer -b "sks_$job_id" 2>/dev/null || true
if [[ "$sess" =~ "agy" ]]; then
_sks_tmux send-keys -t "$sess" C-m
return 0
fi
sleep 0.5 sleep 0.5
local pane_content was_popup=0 local pane_content was_popup=0
pane_content=$(_pane_capture "$sess") pane_content=$(_pane_capture "$sess")
@@ -8,6 +8,7 @@ set -euo pipefail
# 1. Load Common Framework Library # 1. Load Common Framework Library
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
# shellcheck disable=SC1091
source "$REPO_ROOT/.agents/skills/lib.sh" source "$REPO_ROOT/.agents/skills/lib.sh"
# Default configuration parameters # Default configuration parameters
@@ -83,6 +84,12 @@ log_error() {
echo -e "\033[1;31m[✗]\033[0m $1" echo -e "\033[1;31m[✗]\033[0m $1"
} }
# Verdict must occupy its own line — quoted/diff-embedded tokens ('+', '>' prefixed) never match.
has_verdict() {
local file="$1" verdict="$2"
grep -qE "^\[VERDICT: ${verdict}\][[:space:]]*\r?$" "$file"
}
# Helper: Blocking wait for a delegate job's completion or error state (with safety timeout) # Helper: Blocking wait for a delegate job's completion or error state (with safety timeout)
wait_for_job() { wait_for_job() {
local job_id="$1" local job_id="$1"
@@ -161,29 +168,29 @@ import sqlite3, os, yaml, json
yaml_path = '.mam/agent-sessions.yaml' yaml_path = '.mam/agent-sessions.yaml'
db_path = '.mam/agent-sessions.db' db_path = '.mam/agent-sessions.db'
name = os.environ.get('NAME') name = os.environ.get('NAME')
agent = 'claude' agent = None
if os.path.exists(db_path): if os.path.exists(db_path):
try: try:
conn = sqlite3.connect(db_path) conn = sqlite3.connect(db_path)
row = conn.execute('SELECT data FROM sessions WHERE name=?', (name,)).fetchone() row = conn.execute('SELECT data FROM sessions WHERE name=?', (name,)).fetchone()
if row: if row:
agent = json.loads(row[0]).get('agent', 'claude') agent = json.loads(row[0]).get('agent')
conn.close() conn.close()
print(agent)
raise SystemExit(0)
except Exception: except Exception:
pass pass
if os.path.exists(yaml_path): if not agent and os.path.exists(yaml_path):
try: try:
with open(yaml_path) as f: with open(yaml_path) as f:
d = yaml.safe_load(f) or {} d = yaml.safe_load(f) or {}
for s in d.get('tmux_sessions', []): for s in d.get('tmux_sessions', []):
if s.get('name') == name: if s.get('name') == name:
print(s.get('agent', 'claude')) agent = s.get('agent') or s.get('pane', {}).get('cmd')
raise SystemExit(0) break
except Exception: except Exception:
pass pass
print('claude') if not agent:
agent = 'agy' if 'agy' in name else 'cline' if 'cline' in name else 'hermes' if 'hermes' in name else 'claude'
print(agent)
" "
} }
@@ -248,7 +255,7 @@ if [ "$PLAN_MODE" = true ]; then
log_info "Requesting initial implementation 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 \ PLAN_JOB_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \
--agent-session "tmux:$PLANNER_SESSION" \ --agent-session "tmux:$PLANNER_SESSION" \
--agent "claude" \ --agent "$(resolve_agent_type "$PLANNER_SESSION")" \
--type "direct" \ --type "direct" \
--prompt "태스크 목표를 바탕으로 구체적인 구현 계획서를 작성해주세요. 목표: $TASK") --prompt "태스크 목표를 바탕으로 구체적인 구현 계획서를 작성해주세요. 목표: $TASK")
@@ -266,7 +273,7 @@ if [ "$PLAN_MODE" = true ]; then
fi fi
# Retrieve plan text safely # Retrieve plan text safely
PLAN_FILE=$(find ".mam/jobs/$PLAN_JOB_ID" -name "*.md" 2>/dev/null | head -n 1 || true) PLAN_FILE=$(find ".mam/jobs/$PLAN_JOB_ID" -maxdepth 2 -name "report-final.md" 2>/dev/null | head -n 1 || true)
if [ -z "$PLAN_FILE" ] || [ ! -f "$PLAN_FILE" ]; then if [ -z "$PLAN_FILE" ] || [ ! -f "$PLAN_FILE" ]; then
log_error "Final plan file not found." log_error "Final plan file not found."
exit 1 exit 1
@@ -298,7 +305,7 @@ if [ "$PLAN_MODE" = true ]; then
exit 1 exit 1
fi fi
CRITIQUE_FILE=$(find ".mam/jobs/$DEBATE_JOB_ID" -name "*.md" 2>/dev/null | head -n 1 || true) CRITIQUE_FILE=$(find ".mam/jobs/$DEBATE_JOB_ID" -maxdepth 2 -name "report-final.md" 2>/dev/null | head -n 1 || true)
if [ -z "$CRITIQUE_FILE" ] || [ ! -f "$CRITIQUE_FILE" ]; then if [ -z "$CRITIQUE_FILE" ] || [ ! -f "$CRITIQUE_FILE" ]; then
log_error "Critique file not found." log_error "Critique file not found."
exit 1 exit 1
@@ -308,7 +315,7 @@ if [ "$PLAN_MODE" = true ]; then
log_info "Planner refining plan with Creator's feedback..." 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 \ REFINE_JOB_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \
--agent-session "tmux:$PLANNER_SESSION" \ --agent-session "tmux:$PLANNER_SESSION" \
--agent "claude" \ --agent "$(resolve_agent_type "$PLANNER_SESSION")" \
--type "direct" \ --type "direct" \
--prompt "작업자(Creator)로부터 다음 이의제기 피드백을 받았습니다. 피드백을 반영하여 계획서를 정교하게 업데이트(Refine)하여 다시 출력해주세요. 피드백:\n$CRITIQUE_TEXT\n기존 계획서:\n$CURRENT_PLAN") --prompt "작업자(Creator)로부터 다음 이의제기 피드백을 받았습니다. 피드백을 반영하여 계획서를 정교하게 업데이트(Refine)하여 다시 출력해주세요. 피드백:\n$CRITIQUE_TEXT\n기존 계획서:\n$CURRENT_PLAN")
@@ -325,7 +332,7 @@ if [ "$PLAN_MODE" = true ]; then
exit 1 exit 1
fi fi
REFINE_FILE=$(find ".mam/jobs/$REFINE_JOB_ID" -name "*.md" 2>/dev/null | head -n 1 || true) REFINE_FILE=$(find ".mam/jobs/$REFINE_JOB_ID" -maxdepth 2 -name "report-final.md" 2>/dev/null | head -n 1 || true)
if [ -z "$REFINE_FILE" ] || [ ! -f "$REFINE_FILE" ]; then if [ -z "$REFINE_FILE" ] || [ ! -f "$REFINE_FILE" ]; then
log_error "Refinement plan file not found." log_error "Refinement plan file not found."
exit 1 exit 1
@@ -395,7 +402,7 @@ while [ "$loop_count" -le "$MAX_LOOP" ]; do
--agent-session "tmux:$TARGET_AGENT" \ --agent-session "tmux:$TARGET_AGENT" \
--agent "$(resolve_agent_type "$TARGET_AGENT")" \ --agent "$(resolve_agent_type "$TARGET_AGENT")" \
--type "direct" \ --type "direct" \
--prompt "작업 완료 상태에 대해 스스로 검증(Self-Review)하여 결함이 없음을 확인하고 종결해주세요. 리포트에 반드시 '[VERDICT: PASS]' 혹은 '[VERDICT: NOT PASS]' 태그를 명시해주세요.") --prompt "작업 완료 상태에 대해 스스로 검증(Self-Review)하여 결함이 없음을 확인하고 종결해주세요. 리뷰 리포트 마지막에 단독 행으로 반드시 '[VERDICT: PASS]' 혹은 '[VERDICT: NOT PASS]' 태그를 명시해주세요.")
SELF_REV_ID=$(extract_job_id "$SELF_REV_OUTPUT") SELF_REV_ID=$(extract_job_id "$SELF_REV_OUTPUT")
if [ -z "$SELF_REV_ID" ]; then if [ -z "$SELF_REV_ID" ]; then
@@ -405,8 +412,8 @@ while [ "$loop_count" -le "$MAX_LOOP" ]; do
CREATED_JOBS+=("$SELF_REV_ID") CREATED_JOBS+=("$SELF_REV_ID")
wait_for_job "$SELF_REV_ID" wait_for_job "$SELF_REV_ID"
REPORT_FILE=$(find ".mam/jobs/$SELF_REV_ID" -name "*.md" 2>/dev/null | head -n 1 || true) REPORT_FILE=$(find ".mam/jobs/$SELF_REV_ID" -maxdepth 2 -name "report-final.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 if [ -n "$REPORT_FILE" ] && [ -f "$REPORT_FILE" ] && has_verdict "$REPORT_FILE" "PASS" && ! has_verdict "$REPORT_FILE" "NOT PASS"; then
log_success "Self-Review PASS." log_success "Self-Review PASS."
break break
else else
@@ -432,7 +439,7 @@ while [ "$loop_count" -le "$MAX_LOOP" ]; do
--agent-session "tmux:$rev" \ --agent-session "tmux:$rev" \
--agent "$(resolve_agent_type "$rev")" \ --agent "$(resolve_agent_type "$rev")" \
--type "direct" \ --type "direct" \
--prompt "다음 구현 사항(작업 목표: $TASK) 및 누적 변경분(git diff)에 대해 린트, 동작성, 유실 등의 관점에서 교차 코드 리뷰를 수행해주세요. 확인 후 최종 Verdict로 '[VERDICT: PASS]' 혹은 '[VERDICT: NOT PASS]' 태그를 리뷰 리포트 명시적으로 작성해주세요. 변경분:\n$CHANGES_DIFF") --prompt "다음 구현 사항(작업 목표: $TASK) 및 누적 변경분(git diff)에 대해 린트, 동작성, 유실 등의 관점에서 교차 코드 리뷰를 수행해주세요. 확인 후 최종 Verdict로 '[VERDICT: PASS]' 혹은 '[VERDICT: NOT PASS]' 태그를 리뷰 리포트 마지막에 단독 행으로 명시적으로 작성해주세요. 변경분:\n$CHANGES_DIFF")
REV_JOB_ID=$(extract_job_id "$REV_OUTPUT") REV_JOB_ID=$(extract_job_id "$REV_OUTPUT")
if [ -z "$REV_JOB_ID" ]; then if [ -z "$REV_JOB_ID" ]; then
@@ -459,7 +466,7 @@ while [ "$loop_count" -le "$MAX_LOOP" ]; do
fi fi
# Parse verdict from report file (fails-safe, M-2 anchored checks) # 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) REPORT_FILE=$(find ".mam/jobs/$job_id" -maxdepth 2 -name "report-final.md" 2>/dev/null | head -n 1 || true)
if [ -z "$REPORT_FILE" ] || [ ! -f "$REPORT_FILE" ]; then if [ -z "$REPORT_FILE" ] || [ ! -f "$REPORT_FILE" ]; then
log_warn "Reviewer '$rev' report not found. Counting as NOT PASS." log_warn "Reviewer '$rev' report not found. Counting as NOT PASS."
all_passed=false all_passed=false
@@ -469,7 +476,7 @@ while [ "$loop_count" -le "$MAX_LOOP" ]; do
REPORT_CONTENT=$(cat "$REPORT_FILE" 2>/dev/null || echo "") 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) # 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 if has_verdict "$REPORT_FILE" "NOT PASS" || ! has_verdict "$REPORT_FILE" "PASS"; then
log_warn "Reviewer '$rev': NOT PASS" log_warn "Reviewer '$rev': NOT PASS"
all_passed=false all_passed=false
FEEDBACK_AGGREGATE="$FEEDBACK_AGGREGATE\n--- Reviewer ($rev) Feedback ---\n$REPORT_CONTENT" FEEDBACK_AGGREGATE="$FEEDBACK_AGGREGATE\n--- Reviewer ($rev) Feedback ---\n$REPORT_CONTENT"
@@ -498,7 +505,7 @@ while [ "$loop_count" -le "$MAX_LOOP" ]; do
REFINE_PLAN_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \ REFINE_PLAN_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \
--agent-session "tmux:$PLANNER_SESSION" \ --agent-session "tmux:$PLANNER_SESSION" \
--agent "claude" \ --agent "$(resolve_agent_type "$PLANNER_SESSION")" \
--type "direct" \ --type "direct" \
--prompt "리뷰어들로부터 다음과 같이 정교한 코드 수정 피드백이 도착했습니다. 해당 피드백을 수렴하여 구현 계획서(Plan)를 갱신(Refine)하여 다시 작성해주세요. 피드백:\n$FEEDBACK_AGGREGATE\n기존 계획서:\n$CURRENT_PLAN") --prompt "리뷰어들로부터 다음과 같이 정교한 코드 수정 피드백이 도착했습니다. 해당 피드백을 수렴하여 구현 계획서(Plan)를 갱신(Refine)하여 다시 작성해주세요. 피드백:\n$FEEDBACK_AGGREGATE\n기존 계획서:\n$CURRENT_PLAN")
@@ -510,7 +517,7 @@ while [ "$loop_count" -le "$MAX_LOOP" ]; do
CREATED_JOBS+=("$REFINE_PLAN_ID") CREATED_JOBS+=("$REFINE_PLAN_ID")
wait_for_job "$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) REFINE_PLAN_FILE=$(find ".mam/jobs/$REFINE_PLAN_ID" -maxdepth 2 -name "report-final.md" 2>/dev/null | head -n 1 || true)
if [ -z "$REFINE_PLAN_FILE" ] || [ ! -f "$REFINE_PLAN_FILE" ]; then if [ -z "$REFINE_PLAN_FILE" ] || [ ! -f "$REFINE_PLAN_FILE" ]; then
log_error "Refined plan file not found." log_error "Refined plan file not found."
exit 1 exit 1
@@ -543,6 +550,30 @@ while [ "$loop_count" -le "$MAX_LOOP" ]; do
loop_count=$((loop_count + 1)) loop_count=$((loop_count + 1))
done done
# Promote finalized plan and passed reviewer reports to durable location
if [ "$PLAN_MODE" = true ] && [ -n "${CURRENT_PLAN:-}" ] && [ -n "${PLAN_JOB_ID:-}" ]; then
plan_dest_dir=".agents/reports/$PLANNER_SESSION"
log_info "Promoting final plan to durable location: $plan_dest_dir"
mkdir -p "$plan_dest_dir"
echo "$CURRENT_PLAN" > "$plan_dest_dir/plan-${PLAN_JOB_ID}.md.tmp"
mv -f "$plan_dest_dir/plan-${PLAN_JOB_ID}.md.tmp" "$plan_dest_dir/plan-${PLAN_JOB_ID}.md"
fi
if [ "${#REVIEWERS[@]}" -gt 0 ]; then
log_info "Promoting final review reports to durable location..."
for idx in "${!JOB_IDS[@]}"; do
job_id="${JOB_IDS[$idx]}"
rev="${JOB_REVS[$idx]}"
report_file=$(find ".mam/jobs/$job_id" -maxdepth 2 -name "report-final.md" 2>/dev/null | head -n 1 || true)
if [ -n "$report_file" ] && [ -f "$report_file" ]; then
dest_dir=".agents/reports/$rev"
mkdir -p "$dest_dir"
cp "$report_file" "$dest_dir/report-${job_id}.md.tmp"
mv -f "$dest_dir/report-${job_id}.md.tmp" "$dest_dir/report-${job_id}.md"
fi
done
fi
# =========================================================================== # ===========================================================================
# PHASE 4: CLEANUP # PHASE 4: CLEANUP
# =========================================================================== # ===========================================================================