refactor: harden run_loop.sh verdict parser, add atomic promotion, and revise multi_agent_workflow.md guidelines
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 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)
|
||||
1. Developer 에이전트가 리뷰어 세션에 작업 완료 사실과 변경 범위를 전달합니다.
|
||||
2. 리뷰어들은 `git diff`를 바탕으로 개별 검증을 수행하고 보고서 형태의 리뷰 피드백을 출력합니다.
|
||||
- **반려 (`NOT PASS`)** -> 피드백 요약본을 Planner 에이전트에게 전송하여 상위 레벨 계획(Rev.n) 개시.
|
||||
- **통과 (`PASS`)** -> 모든 검토 사항이 해결되었음을 명시.
|
||||
- **반려 (`[VERDICT: NOT PASS]`, 리포트 마지막에 단독 행으로)** -> 피드백 요약본을 Planner 에이전트에게 전송하여 상위 레벨 계획(Rev.n) 개시.
|
||||
- **통과 (`[VERDICT: PASS]`, 리포트 마지막에 단독 행으로)** -> 모든 검토 사항이 해결되었음을 명시.
|
||||
3. 모든 리뷰어가 PASS를 발행하면 작업을 완결하고, 다른 에이전트 세션들은 종료하지 않고 다음 태스크 지시가 있을 때까지 프롬프트 대기 상태(Standby)로 유지합니다.
|
||||
|
||||
---
|
||||
|
||||
## 3. Best Practices & 템플릿
|
||||
## 3. Best Practices & 자동화 지침
|
||||
|
||||
### 3.1. 작업 시작 시 Planner 에이전트 프롬프트 템플릿
|
||||
```
|
||||
[역할 요구]
|
||||
프로젝트의 구조 및 내용을 파악하고, 현재 작업 요건에 맞춰 구현 계획서(implementation_plan.md) 및 태스크 체크리스트(task.md)를 작성해 주세요.
|
||||
수동 템플릿 작성 및 전달은 폐지되었습니다. 모든 에이전트 간 피드백 루프는 [multi-agent-mux-loop](skills/multi-agent-mux-loop/SKILL.md)를 활용해 자동으로 오케스트레이션합니다.
|
||||
|
||||
[검토 초점]
|
||||
- 설계 수준에서 논리적 모순이 발생할 여지가 없는지
|
||||
- naive 구현과 대비되는 핵심 차별점이 코드 명세에 정확히 명시되었는지
|
||||
### 3.1. 개념-옵션 매핑 규격
|
||||
|
||||
워크플로우 단계별로 활용할 수 있는 `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. 피드백 루프 환류 시 프롬프트 템플릿
|
||||
```
|
||||
Reviewer 검토 결과 구현 코드 차원에서 아래의 블로킹(NOT PASS) 피드백이 발생했습니다.
|
||||
위 피드백을 수용하여 설계 문서를 수정하는 계획을 수립하고, implementation_plan.md (Rev.[N]) 및 task.md 내용을 업데이트해 주세요.
|
||||
### 3.3. 동작성 제약 및 Pitfalls
|
||||
|
||||
[피드백 내용]
|
||||
- [블로킹 항목 1]
|
||||
- [블로킹 항목 2]
|
||||
```
|
||||
1. **세션 가동 전제**: `run_loop.sh` 기동 전에 타겟 세션(Planner, Target Agent, Reviewer)들이 모두 tmux 세션으로 기동되어 (`status.sh` 기준 `alive` 및 `running`) 있어야 합니다.
|
||||
2. **동시 루프 기동 금지**: 동일한 작업 트리 내에서 다수의 `run_loop.sh` 제어기를 동시에 기동하면 SQLite DB 갱신 경합 및 YAML 데이터 오염이 발생하므로 절대로 병렬 기동하지 마십시오.
|
||||
3. **정형 토큰 단독 행 작성 필수**: 앵커링 파서 하드닝에 의해, 리뷰 리포트 파일 내에서 `[VERDICT: PASS]` 또는 `[VERDICT: NOT PASS]` 토큰은 반드시 리포트의 **마지막에 단독 행으로** 기재되어야 합니다. 코드 인용이나 변경 diff 내의 토큰은 매칭 대상에서 완전 배제됩니다.
|
||||
4. **결함 시 fail-closed**: 최종 Verdict 토큰이 누락되거나 리포트 픽업 실패 시, 파서는 안전을 위해 `NOT PASS`로 오픽업(Fail-closed) 판정하여 교정 사이클을 수행하므로 반드시 리포트 끝에 단독 행으로 토큰을 찍도록 지시해야 합니다.
|
||||
5. **원자적 아카이빙 (Promotion)**: 루프 성공 종료 시 최종 계획서와 검증 리포트들은 `.agents/reports/<session_name>/` 디렉토리로 원자적으로 덮어쓰기(`mv -f`)되어 보존되므로, 해당 경로의 리포트들로 VCS 추적성을 확보해야 합니다.
|
||||
|
||||
@@ -1168,6 +1168,10 @@ send_keys_safe() {
|
||||
_sks_tmux set-buffer -b "sks_$job_id" "$text"
|
||||
_sks_tmux paste-buffer -b "sks_$job_id" -t "$sess"
|
||||
_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
|
||||
local pane_content was_popup=0
|
||||
pane_content=$(_pane_capture "$sess")
|
||||
|
||||
@@ -8,6 +8,7 @@ set -euo pipefail
|
||||
# 1. Load Common Framework Library
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||
# shellcheck disable=SC1091
|
||||
source "$REPO_ROOT/.agents/skills/lib.sh"
|
||||
|
||||
# Default configuration parameters
|
||||
@@ -83,6 +84,12 @@ log_error() {
|
||||
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)
|
||||
wait_for_job() {
|
||||
local job_id="$1"
|
||||
@@ -161,29 +168,29 @@ import sqlite3, os, yaml, json
|
||||
yaml_path = '.mam/agent-sessions.yaml'
|
||||
db_path = '.mam/agent-sessions.db'
|
||||
name = os.environ.get('NAME')
|
||||
agent = 'claude'
|
||||
agent = None
|
||||
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')
|
||||
agent = json.loads(row[0]).get('agent')
|
||||
conn.close()
|
||||
print(agent)
|
||||
raise SystemExit(0)
|
||||
except Exception:
|
||||
pass
|
||||
if os.path.exists(yaml_path):
|
||||
if not agent 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('name') == name:
|
||||
print(s.get('agent', 'claude'))
|
||||
raise SystemExit(0)
|
||||
agent = s.get('agent') or s.get('pane', {}).get('cmd')
|
||||
break
|
||||
except Exception:
|
||||
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..."
|
||||
PLAN_JOB_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \
|
||||
--agent-session "tmux:$PLANNER_SESSION" \
|
||||
--agent "claude" \
|
||||
--agent "$(resolve_agent_type "$PLANNER_SESSION")" \
|
||||
--type "direct" \
|
||||
--prompt "태스크 목표를 바탕으로 구체적인 구현 계획서를 작성해주세요. 목표: $TASK")
|
||||
|
||||
@@ -266,7 +273,7 @@ if [ "$PLAN_MODE" = true ]; then
|
||||
fi
|
||||
|
||||
# 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
|
||||
log_error "Final plan file not found."
|
||||
exit 1
|
||||
@@ -298,7 +305,7 @@ if [ "$PLAN_MODE" = true ]; then
|
||||
exit 1
|
||||
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
|
||||
log_error "Critique file not found."
|
||||
exit 1
|
||||
@@ -308,7 +315,7 @@ if [ "$PLAN_MODE" = true ]; then
|
||||
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" \
|
||||
--agent "$(resolve_agent_type "$PLANNER_SESSION")" \
|
||||
--type "direct" \
|
||||
--prompt "작업자(Creator)로부터 다음 이의제기 피드백을 받았습니다. 피드백을 반영하여 계획서를 정교하게 업데이트(Refine)하여 다시 출력해주세요. 피드백:\n$CRITIQUE_TEXT\n기존 계획서:\n$CURRENT_PLAN")
|
||||
|
||||
@@ -325,7 +332,7 @@ if [ "$PLAN_MODE" = true ]; then
|
||||
exit 1
|
||||
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
|
||||
log_error "Refinement plan file not found."
|
||||
exit 1
|
||||
@@ -395,7 +402,7 @@ while [ "$loop_count" -le "$MAX_LOOP" ]; do
|
||||
--agent-session "tmux:$TARGET_AGENT" \
|
||||
--agent "$(resolve_agent_type "$TARGET_AGENT")" \
|
||||
--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")
|
||||
if [ -z "$SELF_REV_ID" ]; then
|
||||
@@ -405,8 +412,8 @@ while [ "$loop_count" -le "$MAX_LOOP" ]; do
|
||||
CREATED_JOBS+=("$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)
|
||||
if [ -n "$REPORT_FILE" ] && [ -f "$REPORT_FILE" ] && grep -q "\[VERDICT: PASS\]" "$REPORT_FILE" && ! grep -q "\[VERDICT: NOT PASS\]" "$REPORT_FILE"; then
|
||||
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" ] && has_verdict "$REPORT_FILE" "PASS" && ! has_verdict "$REPORT_FILE" "NOT PASS"; then
|
||||
log_success "Self-Review PASS."
|
||||
break
|
||||
else
|
||||
@@ -432,7 +439,7 @@ while [ "$loop_count" -le "$MAX_LOOP" ]; do
|
||||
--agent-session "tmux:$rev" \
|
||||
--agent "$(resolve_agent_type "$rev")" \
|
||||
--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")
|
||||
if [ -z "$REV_JOB_ID" ]; then
|
||||
@@ -459,7 +466,7 @@ while [ "$loop_count" -le "$MAX_LOOP" ]; do
|
||||
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)
|
||||
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
|
||||
log_warn "Reviewer '$rev' report not found. Counting as NOT PASS."
|
||||
all_passed=false
|
||||
@@ -469,7 +476,7 @@ while [ "$loop_count" -le "$MAX_LOOP" ]; do
|
||||
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
|
||||
if has_verdict "$REPORT_FILE" "NOT PASS" || ! has_verdict "$REPORT_FILE" "PASS"; then
|
||||
log_warn "Reviewer '$rev': NOT PASS"
|
||||
all_passed=false
|
||||
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 \
|
||||
--agent-session "tmux:$PLANNER_SESSION" \
|
||||
--agent "claude" \
|
||||
--agent "$(resolve_agent_type "$PLANNER_SESSION")" \
|
||||
--type "direct" \
|
||||
--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")
|
||||
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
|
||||
log_error "Refined plan file not found."
|
||||
exit 1
|
||||
@@ -543,6 +550,30 @@ while [ "$loop_count" -le "$MAX_LOOP" ]; do
|
||||
loop_count=$((loop_count + 1))
|
||||
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
|
||||
# ===========================================================================
|
||||
|
||||
Reference in New Issue
Block a user