3 Commits
Author SHA1 Message Date
GodopuandClaude Sonnet 5 35fc44f269 fix(loop): repair review-diff/verdict-parsing bugs, deduplicate session lookups
Applies the P0-P3 fixes from the multi-agent-mux-loop audit
(.mam/jobs/ab686e47/claude-reports/report-final.md):

- P0-1: capture BASE_COMMIT before Phase 2 and diff against it, so reviewer
  diffs stay non-empty and cumulative even after the Creator commits per the
  documented DoD (bare `git diff` alone showed nothing once committed).
- P0-2: has_verdict now matches only the report's last non-blank line, so a
  stray [VERDICT: ...] token quoted mid-report as a formatting example can no
  longer flip the outcome.
- P1-1: replace the English-only refactor/complex/design/architect keyword
  sniff (dead code against Korean-language reviewer reports) with an explicit
  [ESCALATE: PLANNER] tag the reviewer prompt now asks for.
- P1-2: resolve_all_reviewers/resolve_agent_type/resolve_planner_session now
  read through lib.sh's load_state_json single source of truth instead of
  each hand-rolling its own SQLite+YAML lookup; resolve_agent_type's name
  fallback matches exact hyphen segments instead of a substring `in` check.
- P2-1: warn when --all-reviewer and --reviewer are both given, since the
  latter is silently discarded.
- P2-2: correct the SKILL.md CLI-mapping table row that overstated an
  automated lint gate and an unconditional Planner feedback loop.
- P3: fix lib.sh shellcheck SC2164 (unguarded cd in start_watchdog) and
  annotate the intentional SC2317 dual source/exec guard.

Verified: shellcheck clean on both scripts, bash -n syntax OK, and the
rewritten has_verdict/resolve_* functions were unit-tested against this
repo's live .mam/agent-sessions state.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 13:50:25 +09:00
Godopu 7fde1d2b8a docs(loop): merge multi_agent_workflow.md into SKILL.md, drop duplicate 2026-07-16 13:36:12 +09:00
Godopu 626f35adfc refactor: harden run_loop.sh verdict parser, add atomic promotion, and revise multi_agent_workflow.md guidelines 2026-07-16 12:57:56 +09:00
5 changed files with 215 additions and 212 deletions
-105
View File
@@ -1,105 +0,0 @@
# Multi-Agent Collaboration Workflow Reference Guide
본 문서는 대규모 프로젝트나 정밀한 설계·구현 요구사항을 처리하기 위해 **Planner, Developer, Reviewer** 에이전트 간의 역할 분담 및 피드백 루프를 운영하는 **다중 에이전트 협업 워크플로우(Multi-Agent Collaboration Workflow)**를 정의합니다.
---
## 1. 역할 정의 및 분담 (Roles & Responsibilities)
협업 시스템은 각 에이전트의 책임 영역을 명확히 격리하여 상호 교차 검증을 강제합니다.
```
┌──────────────────────┐
│ User Prompt │
└──────────┬───────────┘
┌──────────────────────┐
│ 1. Planner Agent │ ◄──────────────────┐
│ - Plan & Checklist │ │
└──────────┬───────────┘ │
▼ │
┌──────────────────────┐ │
│ 2. Developer Agent │ │
│ - Code & DoD Verify │ │
└──────────┬───────────┘ │
▼ │ (NOT PASS Feedback)
┌──────────────────────┐ │
│ 3. Reviewer Agents │ │
│ - Dual Peer Review │ ───────────────────┘
└──────────┬───────────┘
▼ (PASS)
┌──────────────────────┐
│ 4. Done & Commit │
└──────────────────────┘
```
### 1.1. Planner Agent (설계 및 통제)
- **목적**: 요구사항을 명세화하고, 구현 단계의 설계 결함이나 모순(Contradiction)을 사전에 차단합니다.
- **역할**:
- 사용자 요구사항에 따른 구현 목표 및 범위 수립.
- `implementation_plan.md``task.md` (체크리스트) 작성 및 버전 관리(Rev.1, Rev.2, ...).
- 리뷰어 피드백 발생 시 설계 변경의 파급 범위를 계산하여 계획 갱신.
- **핵심 원칙**: 직접 코드를 수정하지 않고 오직 설계와 체크리스트 자산만 관리합니다.
### 1.2. Developer Agent (구현 및 자가 검증)
- **목적**: Planner가 제공한 체크리스트를 기반으로 실제 리포지토리 코드를 물리적으로 수정 및 구현합니다.
- **역할**:
- `task.md`를 순차적으로 완료 상태(`[x]`)로 업데이트하며 구현 수행.
- 커밋 전 **Definition of Done (DoD)** 체크리스트를 자체 실행하여 금지된 코드 패턴, 메모리/구조적 사이드 이펙트 유무 자가 검토.
- 수정 사항을 단일 원자적(Atomic) 커밋으로 마감하고 리뷰어에게 전달.
### 1.3. Reviewer Agents (교차 피드백 및 검증)
- **목적**: 구현된 결과물이 최초 설계서 및 학술적 제약 요건에 일치하는지 제3자의 관점에서 엄격하게 검토합니다.
- **역할**:
- **Reviewer A (Claude)**: 학술 서사와 코드 설계 간의 상위 논리적 정합성 및 결함(예: HOLB 해소 주장과 단일 커넥션 다이얼러의 모순 등) 검증.
- **Reviewer B (Cline)**: 전이 조건, 예외 처리, 타입 시그니처, 텔레메트리 매핑 등 하위 레벨 구현의 세부 사항 기계적 검증.
- **판정 규칙**: 두 리뷰어 모두 **PASS** 판정을 내릴 때까지 개발자는 마감할 수 없으며, 반려 시 **1단계(Planner)**로 피드백이 환류됩니다.
---
## 2. 세부 운영 단계 (Step-by-Step Workflow)
### 1단계: 설계 수립 (Planning Phase)
1. 사용자가 요구사항을 제시하면, **Planner 에이전트**가 프로젝트의 전반적인 구조를 파악합니다.
2. `implementation_plan.md``task.md`를 작성하여 개발을 위한 로드맵을 제공합니다.
3. 사용자가 해당 구현 계획을 승인하면 다음 단계로 이행합니다.
### 2단계: 코드 구현 및 자가 검증 (Development Phase)
1. **Developer 에이전트**가 배정된 태스크의 코드를 수정합니다.
2. 작업 진행 중 예상치 못한 설계 변경 필요성이 감지되면 작업을 멈추고 **Planner 에이전트**에게 계획 수정을 먼저 위임합니다.
3. 구현 완료 후 아래의 **DoD 검증**을 수행합니다:
- 핵심 기능의 타입 매핑 확인.
- 공유 자원(`tls.Config` 등) 변경 시 사이드 이펙트 방지(복제 후 변이 적용 등).
- 문서-코드 간 주장의 정합성 체크.
4. 검증 완료 후 단일 커밋을 작성합니다.
### 3단계: 피드백 루프 및 통과 (Review Phase)
1. Developer 에이전트가 리뷰어 세션에 작업 완료 사실과 변경 범위를 전달합니다.
2. 리뷰어들은 `git diff`를 바탕으로 개별 검증을 수행하고 보고서 형태의 리뷰 피드백을 출력합니다.
- **반려 (`NOT PASS`)** -> 피드백 요약본을 Planner 에이전트에게 전송하여 상위 레벨 계획(Rev.n) 개시.
- **통과 (`PASS`)** -> 모든 검토 사항이 해결되었음을 명시.
3. 모든 리뷰어가 PASS를 발행하면 작업을 완결하고, 다른 에이전트 세션들은 종료하지 않고 다음 태스크 지시가 있을 때까지 프롬프트 대기 상태(Standby)로 유지합니다.
---
## 3. Best Practices & 템플릿
### 3.1. 작업 시작 시 Planner 에이전트 프롬프트 템플릿
```
[역할 요구]
프로젝트의 구조 및 내용을 파악하고, 현재 작업 요건에 맞춰 구현 계획서(implementation_plan.md) 및 태스크 체크리스트(task.md)를 작성해 주세요.
[검토 초점]
- 설계 수준에서 논리적 모순이 발생할 여지가 없는지
- naive 구현과 대비되는 핵심 차별점이 코드 명세에 정확히 명시되었는지
```
### 3.2. 피드백 루프 환류 시 프롬프트 템플릿
```
Reviewer 검토 결과 구현 코드 차원에서 아래의 블로킹(NOT PASS) 피드백이 발생했습니다.
위 피드백을 수용하여 설계 문서를 수정하는 계획을 수립하고, implementation_plan.md (Rev.[N]) 및 task.md 내용을 업데이트해 주세요.
[피드백 내용]
- [블로킹 항목 1]
- [블로킹 항목 2]
```
+12 -2
View File
@@ -16,6 +16,9 @@
if [ -z "${BASH_VERSION:-}" ]; then if [ -z "${BASH_VERSION:-}" ]; then
echo "ERROR: lib.sh must be executed/sourced from bash (foreign shell detected)" >&2 echo "ERROR: lib.sh must be executed/sourced from bash (foreign shell detected)" >&2
# Reachable when this file is executed directly rather than sourced;
# `return` only fails (falling through to `exit`) in that path.
# shellcheck disable=SC2317
return 1 2>/dev/null || exit 1 return 1 2>/dev/null || exit 1
fi fi
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -1018,10 +1021,10 @@ start_watchdog() {
# Start the wildcard monitor subscriber daemon with --idle-timeout 0 (never idle out) # Start the wildcard monitor subscriber daemon with --idle-timeout 0 (never idle out)
# and ensure it runs with $workdir as cwd to anchor relative log paths. # and ensure it runs with $workdir as cwd to anchor relative log paths.
local orig_pwd="$PWD" local orig_pwd="$PWD"
cd "$workdir" cd "$workdir" || return 1
nohup bash "$monitor_script" --subscribe --idle-timeout 0 >> "$log_file" 2>&1 & nohup bash "$monitor_script" --subscribe --idle-timeout 0 >> "$log_file" 2>&1 &
pid=$! pid=$!
cd "$orig_pwd" cd "$orig_pwd" || return 1
fi fi
echo "$pid" echo "$pid"
@@ -1168,6 +1171,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")
@@ -1185,6 +1192,9 @@ send_keys_safe() {
local cur_content local cur_content
cur_content=$(_pane_capture "$sess") cur_content=$(_pane_capture "$sess")
# Hardened Submission Checks # Hardened Submission Checks
if printf '%s\n' "$cur_content" | grep -Eq "● |Twisting|Thinking"; then
return 0
fi
if [ "$was_popup" = "0" ] && ! _pane_tail "$sess" 3 | grep -Fq "$marker" && [ "$cur_content" != "$pre_submit" ]; then if [ "$was_popup" = "0" ] && ! _pane_tail "$sess" 3 | grep -Fq "$marker" && [ "$cur_content" != "$pre_submit" ]; then
return 0 return 0
elif [ "$was_popup" = "1" ] && \ elif [ "$was_popup" = "1" ] && \
+89 -3
View File
@@ -4,6 +4,8 @@
> **Safety Guard**: `--max-loop` and `--plan-talk` restrict API cost runaways. > **Safety Guard**: `--max-loop` and `--plan-talk` restrict API cost runaways.
> **Single source of truth**: `./.mam/agent-sessions.yaml`. > **Single source of truth**: `./.mam/agent-sessions.yaml`.
수동 템플릿 작성 및 수동 프롬프트 환류는 폐지되었습니다. Planner, Creator, Reviewer 간의 모든 협업 피드백 루프는 본 스킬(`run_loop.sh`)만을 단독으로 사용하여 자동으로 오케스트레이션합니다.
## What this skill does ## What this skill does
Run an autonomous planning-execution-review loop using multiple agents (Planner, Creator, Reviewers) in the workspace. It supports: Run an autonomous planning-execution-review loop using multiple agents (Planner, Creator, Reviewers) in the workspace. It supports:
@@ -16,6 +18,59 @@ Run an autonomous planning-execution-review loop using multiple agents (Planner,
--- ---
## Roles & Responsibilities
협업 시스템은 각 에이전트의 책임 영역을 명확히 격리하여 상호 교차 검증을 강제합니다.
```
┌──────────────────────┐
│ User Prompt │
└──────────┬───────────┘
┌──────────────────────┐
│ 1. Planner Agent │ ◄──────────────────┐
│ - Plan & Checklist │ │
└──────────┬───────────┘ │
▼ │
┌──────────────────────┐ │
│ 2. Creator Agent │ │
│ - Code & DoD Verify │ │
└──────────┬───────────┘ │
▼ │ (NOT PASS Feedback)
┌──────────────────────┐ │
│ 3. Reviewer Agents │ │
│ - Dual Peer Review │ ───────────────────┘
└──────────┬───────────┘
▼ (PASS)
┌──────────────────────┐
│ 4. Done & Standby │
└──────────────────────┘
```
### Planner (설계 및 통제)
- **목적**: 요구사항을 명세화하고, 구현 단계의 설계 결함이나 모순(Contradiction)을 사전에 차단합니다.
- **역할**:
- 사용자 요구사항에 따른 구현 목표 및 범위 수립.
- `implementation_plan.md``task.md` (체크리스트) 작성 및 버전 관리(Rev.1, Rev.2, ...).
- 리뷰어 피드백 발생 시 설계 변경의 파급 범위를 계산하여 계획 갱신.
- **핵심 원칙**: 직접 코드를 수정하지 않고 오직 설계와 체크리스트 자산만 관리합니다.
### Creator (구현 및 자가 검증)
- **목적**: Planner가 제공한 체크리스트를 기반으로 실제 리포지토리 코드를 물리적으로 수정 및 구현합니다.
- **역할**:
- `task.md`를 순차적으로 완료 상태(`[x]`)로 업데이트하며 구현 수행.
- 커밋 전 **Definition of Done (DoD)** 체크리스트를 자체 실행하여 금지된 코드 패턴, 메모리/구조적 사이드 이펙트 유무 자가 검토.
- 수정 사항을 단일 원자적(Atomic) 커밋으로 마감하고 리뷰어에게 전달.
### Reviewer Agents (교차 피드백 및 검증)
- **목적**: 구현된 결과물이 최초 설계서 및 제약 요건에 일치하는지 제3자의 관점에서 엄격하게 검토합니다.
- **역할**:
- 상위 논리적 정합성(설계 주장과 구현 간 모순 여부) 검증.
- 전이 조건, 예외 처리, 타입 시그니처 등 하위 레벨 구현의 세부 사항 기계적 검증.
- **판정 규칙**: 지정 또는 자동으로 수집된 모든 리뷰어 세션이 만장일치로 **PASS** 판정을 내릴 때까지 Creator는 마감할 수 없으며, 반려 시 **Planner**에게 피드백이 환류됩니다.
---
## Specification & Flow ## Specification & Flow
```mermaid ```mermaid
@@ -65,6 +120,33 @@ sequenceDiagram
--- ---
## Feedback Loop Cadence
1. **Planning Phase**: 사용자가 요구사항을 제시하면 Planner가 프로젝트 구조를 파악하고 `implementation_plan.md`/`task.md`로 로드맵을 제공합니다. 사용자가 승인하면 다음 단계로 이행합니다.
2. **Execution Phase**: Creator가 배정된 태스크의 코드를 수정합니다. 진행 중 예상치 못한 설계 변경 필요성이 감지되면 작업을 멈추고 Planner에게 계획 수정을 먼저 위임합니다. 구현 완료 후 DoD(타입 매핑, 공유 자원 사이드 이펙트 방지, 문서-코드 정합성)를 자체 검증한 뒤 단일 커밋을 작성합니다.
3. **Review Phase**: Creator가 리뷰어 세션에 작업 완료 사실과 변경 범위(`git diff`)를 전달합니다. 리뷰어는 검증 후 리포트 **마지막에 단독 행**으로 판정을 남깁니다:
- **반려 (`[VERDICT: NOT PASS]`)** → 피드백 요약을 Planner에게 전송하여 상위 레벨 계획(Rev.n)을 개시합니다.
- **통과 (`[VERDICT: PASS]`)** → 모든 검토 사항이 해결되었음을 명시합니다.
4. 지정되거나 자동 수집된 리뷰어 전원이 PASS를 발행해야 완결되며, `--max-loop N`회 내에 도달하지 못하면 안전을 위해 루프를 중단합니다.
5. 완결 후에도 에이전트 세션은 종료하지 않고, 다음 태스크 지시가 있을 때까지 프롬프트 대기 상태(Standby)로 유지됩니다.
---
## CLI Option ↔ Workflow Phase Mapping
워크플로우 단계별로 활용할 수 있는 `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` | 레지스트리에 등록된 모든 active 리뷰어 세션을 자동으로 수집하여 리뷰를 돌립니다. (지정/수집된 모든 리뷰어의 PASS 만장일치가 항상 필요합니다.) |
| **Iterative Loop** | `--max-loop M` | NOT PASS 판정 시 최대 `M`회까지 Creator가 자체 수정합니다. `--plan` 모드에서 리뷰어가 리포트에 `[ESCALATE: PLANNER]` 태그를 남기면 설계 변경 수준으로 판단하여 Planner에게 계획 갱신을 위임합니다 (린트는 리뷰어가 검토 관점 중 하나로 확인할 뿐, 별도의 자동 게이트는 아닙니다). |
---
## Workflow ## Workflow
```bash ```bash
@@ -74,10 +156,12 @@ bash .agents/skills/multi-agent-mux-loop/scripts/run_loop.sh \
--task "Fix typo in deploy/README.md" --task "Fix typo in deploy/README.md"
# 2. Collaborative planning + Targeted Reviewers + Safety limits # 2. Collaborative planning + Targeted Reviewers + Safety limits
# (실전 자율 루프 기동의 표준 패턴 — 리뷰어 2인 지정 + 전원 합의 + 최대 3회 반복)
bash .agents/skills/multi-agent-mux-loop/scripts/run_loop.sh \ bash .agents/skills/multi-agent-mux-loop/scripts/run_loop.sh \
--plan \ --plan \
--plan-talk 1 \ --plan-talk 1 \
--reviewer "canary-projects-multi-agent-mux-reviewer-cline,canary-projects-multi-agent-mux-reviewer-claude" \ --reviewer "canary-projects-multi-agent-mux-reviewer-cline,canary-projects-multi-agent-mux-reviewer-claude" \
--all-reviewer \
--max-loop 3 \ --max-loop 3 \
--verbose \ --verbose \
--target-agent "canary-projects-multi-agent-mux-creator-claude" \ --target-agent "canary-projects-multi-agent-mux-creator-claude" \
@@ -94,6 +178,8 @@ bash .agents/skills/multi-agent-mux-loop/scripts/run_loop.sh \
## Pitfalls ## Pitfalls
- **Incorrect Verdict format**: Reviewers MUST output `[VERDICT: PASS]` or `[VERDICT: NOT PASS]` in their final reports for the loop parser to recognize results. If missing, the parser falls back to scanning for "PASS" or "not pass" but warns. - **Incorrect Verdict format (앵커링 파서 하드닝)**: 리뷰 리포트 파일 내에서 `[VERDICT: PASS]` 또는 `[VERDICT: NOT PASS]` 토큰은 반드시 리포트의 **마지막에 단독 행**으로 기재되어야 합니다. 코드 인용이나 변경 diff 내에 등장하는 토큰은 매칭 대상에서 완전 배제됩니다.
- **Session Availability**: Ensure the referenced planner, creator, and reviewer sessions are running or alive before launching `run_loop.sh`. - **Fail-closed on missing verdict**: 최종 Verdict 토큰이 누락되거나 리포트 픽업에 실패하면, 파서는 **경고 후 통과시키는 것이 아니라** 안전을 위해 즉시 `NOT PASS`로 판정(fail-closed)하고 교정 사이클을 수행합니다. 리뷰어에게는 반드시 리포트 끝에 단독 행으로 토큰을 찍도록 지시해야 합니다.
- **NFS Lock Shadowing**: Spawning simultaneous loop processes will serialize job database transactions. Let one loop finish before launching another. - **Session Availability**: `run_loop.sh` 기동 전에 참조되는 Planner, Target Agent, Reviewer 세션들이 모두 tmux 세션으로 기동되어 (`status.sh` 기준 `alive``running`) 있어야 합니다.
- **동시 루프 기동 금지 (NFS Lock Shadowing)**: 동일한 작업 트리 내에서 다수의 `run_loop.sh` 제어기를 동시에 기동하면 SQLite DB 갱신 경합 및 YAML 데이터 오염이 발생합니다. 하나의 루프가 끝날 때까지 다른 루프를 병렬로 기동하지 마십시오.
- **원자적 아카이빙 (Promotion)**: 루프 성공 종료 시 최종 계획서와 검증 리포트들은 `.agents/reports/<session_name>/` 디렉토리로 원자적으로 덮어쓰기(`mv -f`)되어 보존됩니다. 해당 경로의 리포트들로 VCS 추적성을 확보해야 합니다.
@@ -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,22 @@ log_error() {
echo -e "\033[1;31m[✗]\033[0m $1" echo -e "\033[1;31m[✗]\033[0m $1"
} }
# --all-reviewer silently takes precedence over an explicit --reviewer list;
# warn so the discarded list isn't mistaken for having been honored (P2-1).
if [ "$ALL_REVIEWERS" = true ] && [ -n "$REVIEWER_LIST" ]; then
log_warn "--all-reviewer takes precedence; ignoring --reviewer list ('$REVIEWER_LIST')."
fi
# Verdict must occupy the report's last non-blank line — a standalone token
# quoted mid-report (e.g. as a formatting example) never matches (P0-2).
has_verdict() {
local file="$1" verdict="$2"
local last_line pattern
last_line=$(grep -v '^[[:space:]]*$' "$file" 2>/dev/null | tail -n 1)
pattern="^\[VERDICT: ${verdict}\][[:space:]]*\r?\$"
[[ "$last_line" =~ $pattern ]]
}
# 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"
@@ -121,100 +138,58 @@ except Exception:
return 1 return 1
} }
# Resolve active reviewers from SQL DB or YAML (excluding $TARGET_AGENT) # Resolve active reviewers (excluding $TARGET_AGENT) via lib.sh's
# load_state_json — the single source of truth for the merged DB/YAML
# session state, instead of hand-rolling a 4th copy of that lookup (P1-2).
resolve_all_reviewers() { resolve_all_reviewers() {
TARGET_AGENT="$TARGET_AGENT" python3 -c " TARGET_AGENT="$TARGET_AGENT" MAM_STATE_JSON="$(load_state_json)" python3 -c "
import sqlite3, os, yaml, json import os, json
yaml_path = '.mam/agent-sessions.yaml' d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
db_path = '.mam/agent-sessions.db'
target_agent = os.environ.get('TARGET_AGENT') target_agent = os.environ.get('TARGET_AGENT')
reviewers = [] reviewers = [s.get('name') for s in d.get('tmux_sessions', [])
if os.path.exists(db_path): if s.get('role') == 'reviewer' and s.get('name') != target_agent]
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' and s.get('name') != target_agent:
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' and s.get('name') != target_agent:
reviewers.append(s.get('name'))
except Exception:
pass
print(','.join(reviewers)) print(','.join(reviewers))
" "
} }
# Resolve target agent type (claude, cline, agy) # Resolve target agent type (claude, cline, agy) via load_state_json.
resolve_agent_type() { resolve_agent_type() {
local name="$1" local name="$1"
NAME="$name" python3 -c " NAME="$name" MAM_STATE_JSON="$(load_state_json)" python3 -c "
import sqlite3, os, yaml, json import os, json
yaml_path = '.mam/agent-sessions.yaml'
db_path = '.mam/agent-sessions.db'
name = os.environ.get('NAME') name = os.environ.get('NAME')
agent = 'claude' d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
if os.path.exists(db_path): agent = None
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', []): 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: if not agent:
pass # Exact hyphen-segment match, not a naive substring 'in' check, so a
print('claude') # decoy substring inside an unrelated segment can't misclassify.
segments = name.split('-')
if 'agy' in segments:
agent = 'agy'
elif 'cline' in segments:
agent = 'cline'
elif 'hermes' in segments:
agent = 'hermes'
else:
agent = 'claude'
print(agent)
" "
} }
# Resolve planner session dynamically # Resolve planner session dynamically via load_state_json.
resolve_planner_session() { resolve_planner_session() {
python3 -c " MAM_STATE_JSON="$(load_state_json)" python3 -c "
import sqlite3, os, yaml, json import os, json
yaml_path = '.mam/agent-sessions.yaml' d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
db_path = '.mam/agent-sessions.db'
planner = 'canary-projects-multi-agent-mux-planner-claude' 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', []): for s in d.get('tmux_sessions', []):
if s.get('role') == 'planner': if s.get('role') == 'planner':
print(s.get('name')) planner = s.get('name')
raise SystemExit(0) break
except Exception:
pass
print(planner) print(planner)
" "
} }
@@ -248,7 +223,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 +241,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 +273,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 +283,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 +300,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
@@ -343,6 +318,11 @@ fi
# PHASE 2: IMPLEMENTATION (CREATION) # PHASE 2: IMPLEMENTATION (CREATION)
# =========================================================================== # ===========================================================================
log_info "=== Phase 2: Code Implementation ===" log_info "=== Phase 2: Code Implementation ==="
# Fix the pre-implementation commit as the diff baseline so review diffs stay
# cumulative and non-empty even after the Creator commits per DoD (P0-1).
BASE_COMMIT=$(git rev-parse HEAD 2>/dev/null || echo "")
EXECUTION_PROMPT="다음 작업 목표를 완성해주세요: $TASK" EXECUTION_PROMPT="다음 작업 목표를 완성해주세요: $TASK"
if [ "$PLAN_MODE" = true ]; then if [ "$PLAN_MODE" = true ]; then
EXECUTION_PROMPT="최종 합의된 다음 계획서에 입각하여 코드를 수정 및 구현해주세요. 계획서:\n$CURRENT_PLAN" EXECUTION_PROMPT="최종 합의된 다음 계획서에 입각하여 코드를 수정 및 구현해주세요. 계획서:\n$CURRENT_PLAN"
@@ -395,7 +375,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 +385,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
@@ -425,14 +405,20 @@ while [ "$loop_count" -le "$MAX_LOOP" ]; do
for rev in "${REVIEWERS[@]}"; do for rev in "${REVIEWERS[@]}"; do
log_info "Requesting code review from Reviewer '$rev'..." log_info "Requesting code review from Reviewer '$rev'..."
# Cumulative working tree diff (M-6) # Cumulative diff since BASE_COMMIT (M-6): includes committed AND
# uncommitted changes, so it stays non-empty even after the Creator
# commits per the documented DoD (bare `git diff` alone would not).
if [ -n "$BASE_COMMIT" ]; then
CHANGES_DIFF=$(git diff "$BASE_COMMIT" 2>/dev/null || echo "No git diff available")
else
CHANGES_DIFF=$(git diff 2>/dev/null || echo "No git diff available") CHANGES_DIFF=$(git diff 2>/dev/null || echo "No git diff available")
fi
REV_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \ REV_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \
--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]' 태그를 리뷰 리포트 마지막에 단독 행으로 명시적으로 작성해주세요. 만약 단순 버그 수정으로는 부족하고 설계 변경/재작업 수준의 재계획이 필요하다고 판단되면, 리포트 아무 곳에나 단독 행으로 '[ESCALATE: PLANNER]' 태그도 함께 남겨주세요. 변경분:\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 +445,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 +455,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"
@@ -487,9 +473,11 @@ while [ "$loop_count" -le "$MAX_LOOP" ]; do
exit 1 exit 1
fi fi
# Re-planning check for complex refactoring feedback # Re-planning check: rely on the explicit '[ESCALATE: PLANNER]' tag a
# reviewer is instructed to emit, rather than sniffing English keywords
# (reviewers report in Korean, so keyword matching never fired) (P1-1).
COMPLEX_FIX=false COMPLEX_FIX=false
if echo "$FEEDBACK_AGGREGATE" | grep -Eiq "refactor|complex|design|architect"; then if echo "$FEEDBACK_AGGREGATE" | grep -qE '^\[ESCALATE: PLANNER\][[:space:]]*\r?$'; then
COMPLEX_FIX=true COMPLEX_FIX=true
fi fi
@@ -498,7 +486,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 +498,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 +531,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
# =========================================================================== # ===========================================================================
+1 -1
View File
@@ -67,4 +67,4 @@ Strong success criteria let you loop independently. Weak criteria ("make it work
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes. **These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
Read [MULTI_AGENT_RULES.md](.agents/MULTI_AGENT_RULES.md) (or [Korean version](.agents/MULTI_AGENT_RULES.ko.md)) and [multi_agent_workflow.md](.agents/multi_agent_workflow.md) first before working and follow the instructions for orchestration and collaboration. Read [MULTI_AGENT_RULES.md](.agents/MULTI_AGENT_RULES.md) (or [Korean version](.agents/MULTI_AGENT_RULES.ko.md)) and [multi-agent-mux-loop/SKILL.md](.agents/skills/multi-agent-mux-loop/SKILL.md) first before working and follow the instructions for orchestration and collaboration.