콘텐츠로 이동

9주차: 플래너와 QA 에이전트 구현

Phase 39주차고급강의일: 2026-04-28

멀티에이전트 SDLC 파이프라인의 두 축은 파이프라인 시작점에 서는 플래너 에이전트와 검증 게이트에 서는 QA 에이전트다. Week 7에서 설계한 9-phase 파이프라인 기준, Phase 1–3은 플래너가, Phase 5는 QA가 담당한다. 이번 주에는 두 에이전트를 차례로 구현한다 — spec.md를 생성하는 플래너부터, 코드와 테스트만 보고 독립 검증하는 QA까지.

왜 플래너 에이전트가 파이프라인의 병목인가

섹션 제목: “왜 플래너 에이전트가 파이프라인의 병목인가”

Week 7에서 설계한 9-phase 에이전틱 SDLC의 Phase 1-3 (요구사항 → 아키텍처 → 태스크 분해)은 전적으로 플래너의 영역이다. 이 세 단계의 산출물 품질이 Phase 4-9 전체의 성공률을 결정한다.

직관적으로: 코더 에이전트가 아무리 뛰어나도, 입력으로 받은 spec.md에 “사용자 인증 기능 추가” 한 줄만 있다면 올바른 코드를 생성할 수 없다. 반대로 acceptance criteria가 테스트 가능한 수준으로 명확하면 소형 모델도 충분한 코드를 생성한다.

MetaGPT (ICLR 2024) 논문은 이를 실증한다. PM → Architect → Engineer 시퀀스에서 각 역할이 생성하는 SOP 문서의 품질과 최종 코드 품질 사이에 0.72의 상관관계가 있음을 보고했다. 특히 PM 역할이 작성한 PRD의 명확성이 가장 강한 예측 변수였다.

2단계 분리 패턴: /spec/architect

섹션 제목: “2단계 분리 패턴: /spec → /architect”

sdlc-toolkit은 요구사항 명세와 아키텍처 설계를 의도적으로 분리한다. 소프트웨어 엔지니어링의 관심사 분리(Separation of Concerns) 원칙을 에이전트 설계에 적용한 것이다.

  • /spec“무엇을”: 요구사항, acceptance criteria, 제약 조건, out-of-scope 항목. 구현 방법은 언급하지 않는다.
  • /architect“어떻게”: 아키텍처 결정, 모듈 분해, TASK 파일 생성, 의존성 DAG 구성.

이 분리의 실질적 이점: spec 단계에서 LLM이 구현 세부사항에 매몰되지 않고 요구사항의 완전성에 집중할 수 있다. architect 단계는 확정된 요구사항을 전제로 최적 구조를 탐색한다.

planner_agent.py
import anthropic
import json
from pathlib import Path
SYSTEM_PROMPT = """당신은 소프트웨어 아키텍트 역할의 플래너 에이전트입니다.
주어진 요구사항과 코드베이스를 분석하여 구체적이고 실행 가능한 태스크 목록을 생성합니다.
출력 형식: JSON
{
"tasks": [
{
"id": "task-XXX",
"description": "구체적인 구현 내용",
"target_files": ["파일 경로"],
"dependencies": ["task-XXX"],
"acceptance_criteria": ["검증 조건들"],
"assumptions": ["전제 사항들"]
}
],
"out_of_scope": ["범위 외 항목"]
}"""
class PlannerAgent:
def __init__(self):
self.client = anthropic.Anthropic()
def analyze_codebase(self, project_root: Path) -> str:
"""코드베이스 구조를 텍스트로 요약"""
structure = []
for f in project_root.rglob("*.py"):
structure.append(f"- {f.relative_to(project_root)}")
return "\n".join(structure)
def plan(self, requirement: str, project_root: Path) -> dict:
codebase = self.analyze_codebase(project_root)
response = self.client.messages.create(
model="claude-opus-4-6",
max_tokens=4096,
system=SYSTEM_PROMPT,
messages=[{
"role": "user",
"content": f"요구사항: {requirement}\n\n코드베이스:\n{codebase}"
}]
)
return json.loads(response.content[0].text)
def generate_spec_md(self, plan: dict, output_path: Path):
"""JSON 계획을 Markdown 명세서로 변환"""
with open(output_path, 'w') as f:
f.write("# 프로젝트 명세서\n\n")
if plan.get('out_of_scope'):
f.write("## Out of Scope\n")
for item in plan['out_of_scope']:
f.write(f"- {item}\n")
f.write("\n")
for task in plan['tasks']:
f.write(f"## {task['id']}: {task['description']}\n")
f.write(f"- 대상 파일: {', '.join(task['target_files'])}\n")
f.write("- 완료 조건:\n")
for criterion in task['acceptance_criteria']:
f.write(f" - [ ] {criterion}\n")
f.write("\n")

생성된 spec을 코더에게 전달하기 전에 자동 검증이 필요하다. 또한 TASK 파일의 dependencies 배열로 병렬 실행 가능한 tier를 계산한다.

def validate_spec(self, plan: dict) -> tuple[bool, list[str]]:
"""명세서 품질 자동 검증 — 코더에게 전달 전 게이트"""
issues = []
for task in plan['tasks']:
if not task.get('acceptance_criteria'):
issues.append(f"{task['id']}: acceptance_criteria 누락")
elif len(task['acceptance_criteria']) < 2:
issues.append(f"{task['id']}: acceptance_criteria 최소 2개 필요")
if 'assumptions' not in task:
issues.append(f"{task['id']}: assumptions 필드 누락")
if 'out_of_scope' not in plan:
issues.append("spec 레벨 out_of_scope 미정의")
return len(issues) == 0, issues
def compute_tiers(self, plan: dict) -> dict[str, int]:
"""Week 7의 tier 개념 구현 — 병렬 실행 가능 그룹 탐지"""
task_map = {t['id']: t for t in plan['tasks']}
tiers = {}
def get_tier(task_id: str) -> int:
if task_id in tiers:
return tiers[task_id]
deps = task_map[task_id].get('dependencies', [])
tiers[task_id] = 1 if not deps else max(get_tier(d) for d in deps) + 1
return tiers[task_id]
for task in plan['tasks']:
get_tier(task['id'])
return tiers

왜 QA 에이전트가 별도여야 하는가

섹션 제목: “왜 QA 에이전트가 별도여야 하는가”

7주차에서 설계한 멀티에이전트 SDLC 파이프라인의 Phase 5: Verify를 기억하는가. 당시 핵심 설계 원칙은 “검증 에이전트는 생성 에이전트와 독립적이어야 한다”였다. 이 원칙은 직관이 아니라 실증 데이터에 근거한다.

DeepMind와 MIT의 에이전트 시스템 스케일링 연구에 따르면, 검증 에이전트가 생성 에이전트와 컨텍스트를 공유할 경우 동일한 편향을 상속한다. 코더가 특정 가정 하에 코드를 작성했다면, 같은 컨텍스트를 가진 QA 에이전트는 그 가정을 당연하게 받아들이고 검증을 건너뛴다. “나도 그렇게 생각했으니까”라는 편향이다.

PwC의 2025 AI Agent Report는 더 구체적인 수치를 제시한다. 단일 에이전트(코더만) 구조에서 정확도는 10% 수준이었으나, 독립적 judge agent를 추가하자 70%로 향상됐다. 7배 개선이다. QA 에이전트 독립성은 선택이 아니라 시스템 신뢰성의 전제 조건이다.

sdlc-toolkit에서는 이를 2단계로 구현한다:

  • /reflect — 자기 리뷰(self-review): 코더 에이전트 자신이 결과물을 먼저 검토
  • /review — 독립 리뷰(independent review): 별도 QA 에이전트가 코드+테스트만 보고 판단

자기 리뷰가 있음에도 독립 리뷰를 별도로 두는 이유는 단순하다. /reflect는 명백한 오류와 미완성 항목을 빠르게 잡아 /review의 부담을 줄이는 역할이고, /review는 편향 없는 최종 게이트 역할을 한다. 두 단계가 서로 다른 목적을 가진다.


QA 에이전트는 코더 에이전트와 절대 공유 컨텍스트를 사용하지 않는다. 동일한 컨텍스트를 가진 두 에이전트는 동일한 편향을 공유하므로, 독립적인 검증이 불가능하다.

독립성을 실제로 보장하는 메커니즘은 세 가지다:

1. 컨텍스트 격리 (Context Isolation)

QA 에이전트는 코더의 reasoning trace, 중간 결정 과정, system prompt를 볼 수 없다. 입력은 오직 코드 파일과 테스트 파일뿐이다. 코더가 “왜 이렇게 구현했는가”를 아는 순간 QA는 그 합리화를 수용하기 시작한다. 모르는 것이 더 정확한 판단을 낳는다.

2. 도구 제한 (Tool Restriction)

sdlc-toolkit의 /review 단계에서 QA 에이전트에게는 Edit 권한이 없다. ReadBash(테스트 실행)만 허용된다. QA가 발견한 버그를 직접 고칠 수 있으면 “어차피 내가 고칠 테니까” 식의 느슨한 리뷰가 발생한다. 도구를 제한하면 QA는 발견에 집중하고, 수정은 코더에게 넘긴다. 역할 분리가 품질을 높인다.

3. 별도 모델 티어 (Model Tier Separation)

Claude Code의 model routing 기능을 활용하면 QA 에이전트에 더 강력한 모델을 배정할 수 있다. 코더가 claude-sonnet을 사용할 때 QA는 claude-opus를 사용하는 구성이 가능하다. 생성보다 검증에 더 많은 추론 능력을 투자하는 것이 비용 대비 효율적이다.


qa_agent.py
import subprocess
import anthropic
from pathlib import Path
class QAAgent:
def __init__(self):
self.client = anthropic.Anthropic()
def run_tests(self, test_dir: str) -> dict:
"""pytest 실행 및 결과 파싱"""
result = subprocess.run(
["python", "-m", "pytest", test_dir, "-v", "--tb=short", "--json-report"],
capture_output=True, text=True
)
return {
"passed": result.returncode == 0,
"output": result.stdout,
"errors": result.stderr
}
def code_review(self, diff: str) -> str:
"""Claude를 통한 코드 리뷰"""
response = self.client.messages.create(
model="claude-opus-4-6",
max_tokens=2048,
system="""당신은 시니어 소프트웨어 엔지니어입니다.
코드 diff를 검토하고 다음을 확인하세요:
1. 논리적 오류
2. 엣지 케이스 미처리
3. 보안 취약점
4. 성능 문제
5. 테스트 누락
출력: JSON {"approved": bool, "issues": [...], "suggestions": [...]}""",
messages=[{"role": "user", "content": f"코드 리뷰 요청:\n{diff}"}]
)
return response.content[0].text
def review_pr(self, pr_diff: str, test_dir: str) -> dict:
"""PR 전체 검증"""
test_result = self.run_tests(test_dir)
review_result = self.code_review(pr_diff)
return {
"tests_passed": test_result["passed"],
"test_output": test_result["output"],
"code_review": review_result,
"approved": test_result["passed"] and "approved: true" in review_result.lower()
}

7주차에서 설계한 3-parallel reviewer 패턴을 실제로 구현한다. 정확성(Correctness), 품질(Quality), 아키텍처(Architecture) 세 관점이 동시에 검토되고, 심각도 기반 PASS/FAIL 게이트가 최종 판단을 내린다.

parallel_reviewer.py
import concurrent.futures
import anthropic
from dataclasses import dataclass
from typing import Literal
@dataclass
class ReviewResult:
dimension: str
passed: bool
severity: Literal["critical", "major", "minor", "info"]
issues: list[str]
score: int # 0-10
class ParallelReviewer:
def __init__(self):
self.client = anthropic.Anthropic()
def _call_claude(self, system: str, user: str) -> str:
response = self.client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
system=system,
messages=[{"role": "user", "content": user}]
)
return response.content[0].text
def review_correctness(self, code: str, tests: str) -> ReviewResult:
"""정확성 리뷰: 논리 오류, 엣지 케이스, 테스트 충분성"""
result = self._call_claude(
system="""코드의 정확성만 검토하라. 스타일은 무시한다.
확인 항목: 논리 오류, 엣지 케이스 미처리, 테스트 커버리지 공백.
출력: JSON {"score": 0-10, "severity": "critical|major|minor|info", "issues": [...]}""",
user=f"코드:\n{code}\n\n테스트:\n{tests}"
)
import json
data = json.loads(result)
return ReviewResult(
dimension="correctness",
passed=data["score"] >= 4 and data["severity"] != "critical",
severity=data["severity"],
issues=data["issues"],
score=data["score"]
)
def review_quality(self, code: str) -> ReviewResult:
"""품질 리뷰: 코딩 컨벤션, 가독성, 유지보수성"""
result = self._call_claude(
system="""코드 품질만 검토하라. 기능 정확성은 무시한다.
확인 항목: 네이밍, 함수 길이, 중복, 주석 충분성.
출력: JSON {"score": 0-10, "severity": "critical|major|minor|info", "issues": [...]}""",
user=f"코드:\n{code}"
)
import json
data = json.loads(result)
return ReviewResult(
dimension="quality",
passed=data["score"] >= 4 and data["severity"] != "critical",
severity=data["severity"],
issues=data["issues"],
score=data["score"]
)
def review_architecture(self, code: str, context: str) -> ReviewResult:
"""아키텍처 리뷰: 설계 결정, 의존성, 확장성"""
result = self._call_claude(
system="""아키텍처 관점에서만 검토하라.
확인 항목: 단일 책임 원칙, 의존성 방향, 인터페이스 설계, 확장 가능성.
출력: JSON {"score": 0-10, "severity": "critical|major|minor|info", "issues": [...]}""",
user=f"컨텍스트:\n{context}\n\n코드:\n{code}"
)
import json
data = json.loads(result)
return ReviewResult(
dimension="architecture",
passed=data["score"] >= 4 and data["severity"] != "critical",
severity=data["severity"],
issues=data["issues"],
score=data["score"]
)
def parallel_review(self, code: str, tests: str, context: str) -> dict:
"""3-병렬 리뷰 실행 및 결과 통합"""
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
f_correctness = executor.submit(self.review_correctness, code, tests)
f_quality = executor.submit(self.review_quality, code)
f_architecture = executor.submit(self.review_architecture, code, context)
results = [
f_correctness.result(),
f_quality.result(),
f_architecture.result()
]
# 심각도 기반 PASS/FAIL 게이트
has_critical = any(r.severity == "critical" for r in results)
all_pass = all(r.passed for r in results)
avg_score = sum(r.score for r in results) / len(results)
return {
"overall_passed": all_pass and not has_critical,
"average_score": avg_score,
"results": results,
"blocking_issues": [
issue
for r in results if r.severity == "critical"
for issue in r.issues
]
}

독립 리뷰 전에 코더 에이전트 자신이 먼저 검토한다. 명백한 오류를 선제적으로 잡고, 모호한 요구사항을 질문으로 표면화하여 QA 에이전트의 부담을 줄인다.

self_reflect_agent.py
import anthropic
class SelfReflectAgent:
"""코더 에이전트의 자기 리뷰 — /reflect 패턴 구현"""
def __init__(self):
self.client = anthropic.Anthropic()
def reflect(self, code: str, original_requirement: str) -> dict:
"""구현 결과를 요구사항과 대조하여 자기 검토"""
response = self.client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system="""당신은 방금 코드를 작성한 개발자다. 냉정하게 자기 검토하라.
확인 항목:
1. 요구사항의 모든 항목이 구현됐는가?
2. 명백한 버그나 오타가 있는가?
3. 테스트가 실제 요구사항을 검증하는가?
4. 모호하거나 가정에 의존한 부분이 있는가?
출력: JSON {
"obvious_issues": [...],
"ambiguous_requirements": [...],
"questions_for_qa": [...],
"self_confidence": 0-10
}""",
messages=[{
"role": "user",
"content": f"요구사항:\n{original_requirement}\n\n내가 작성한 코드:\n{code}"
}]
)
import json
return json.loads(response.content[0].text)
def should_proceed_to_review(self, reflect_result: dict) -> bool:
"""자기 리뷰 결과로 독립 리뷰 진행 여부 결정"""
# 명백한 이슈가 있으면 먼저 수정 후 재시도
if reflect_result["obvious_issues"]:
return False
# 자신감이 너무 낮으면 재작업
if reflect_result["self_confidence"] < 4:
return False
return True

QA FEEDBACK LOOP
QA Agent 실패 감지
실패 정보 패키징
  • 실패한 테스트 케이스
  • 스택 트레이스
  • 코드 diff
Coder Agent에 재할당task_queue.json에 새 태스크 추가 (priority: HIGH)
Coder Agent 재실행

피드백 루프는 자동 복구를 가능하게 하지만, 7주차에서 경고한 “무한 정제 루프” 위험도 함께 안고 있다. 이를 해결하는 수렴 보장(convergence guarantee) 설계가 필요하다.

반복 상한 (Iteration Cap): 3회

루프는 최대 3회로 제한한다. 3회 내에 해결되지 않는 문제는 코더의 접근 방식 자체가 잘못됐을 가능성이 높다. 자동 수정을 계속하는 것이 오히려 더 복잡한 버그를 낳을 수 있다.

에스컬레이션 경로 (Escalation Path)

1회 실패 → 자동 수정 시도 (Coder 재실행)
2회 실패 → 상세 피드백 + 요구사항 재검토 요청
3회 실패 → Human intervention 플래그 설정 (pipeline-state.json에 기록)

sdlc-toolkit의 /proceed Phase 5는 이 패턴을 reflect → review → escalate 3단계로 통합한다. pipeline-state.jsonqa_iteration_count 필드가 반복 횟수를 추적하여 에스컬레이션 트리거를 결정한다.


sdlc-toolkit의 llm-review-prompt.md에서 정의하는 4차원 스코어링 체계다. 주관적 “좋다/나쁘다” 판단을 0-10 정량 점수로 변환하여 파이프라인 자동화를 가능하게 한다. Week 12의 텔레메트리 시스템에서 이 점수를 집계해 시스템 전반의 품질 트렌드를 추적한다.

PASS 기준: 4개 차원 모두 ≥ 4점 AND Critical 이슈 0개

{
"review_id": "string",
"timestamp": "ISO-8601",
"target": {
"file": "string",
"commit": "string"
},
"scores": {
"correctness": {
"score": 0,
"max": 10,
"rationale": "string",
"issues": []
},
"conventions": {
"score": 0,
"max": 10,
"rationale": "string",
"issues": []
},
"test_coverage": {
"score": 0,
"max": 10,
"rationale": "string",
"issues": []
},
"security": {
"score": 0,
"max": 10,
"rationale": "string",
"issues": []
}
},
"critical_issues": [],
"verdict": "PASS | FAIL",
"feedback_for_coder": "string"
}

이 스코어링 체계는 QA 에이전트 자체가 LLM-as-Judge 패턴을 구현한 것이다. Week 12에서는 이 점수 데이터를 집계하여 에이전트 성능 텔레메트리를 구축하는 방법을 다룬다.


Week 8(PlannerAgent) → Coder → Week 9(QAAgent)로 이어지는 end-to-end 체인이다. 7주차에서 설계한 pipeline-state.json이 각 Phase 완료를 추적하는 중앙 상태 저장소 역할을 한다.

아티팩트 체인 (Artifact Chain)

requirement.md ← Planner 입력
architecture.md ← Planner 출력 (Week 8)
TASK-001.md, TASK-002.md ← task_queue.json 기반 Coder 할당
PR (코드 + 테스트) ← Coder 출력
review-results.json ← QA 출력 (Week 9)
LESSON-001.md ← 실패 패턴 학습 기록

pipeline-state.json은 각 아티팩트가 생성된 시점, 담당 에이전트, 현재 Phase를 기록한다. 파이프라인이 중단되더라도 어느 단계까지 완료됐는지 파악하고 재시작할 수 있다.


토론은 정답을 찾는 것이 아니라 트레이드오프를 명확히 하는 것이 목적이다.

Q1. QA 에이전트에게 Edit 권한을 주면 어떤 문제가 발생하는가?

“발견한 버그를 직접 고치면 더 효율적이지 않은가?” — 이 주장의 논리적 결함을 찾아라. Edit 권한이 있는 QA는 역할이 어떻게 바뀌는가? 단기 효율과 장기 신뢰성 사이의 트레이드오프를 논하라.

Q2. 3-병렬 리뷰어(정확성, 품질, 아키텍처) 중 하나만 선택해야 한다면?

팀의 현재 상황(스타트업 MVP vs 금융 시스템 vs 오픈소스 라이브러리)에 따라 답이 달라질 수 있다. 각 상황에서 어느 차원이 가장 중요하고, 그 근거는 무엇인가? 선택하지 않은 두 차원은 어떻게 보완할 수 있는가?

Q3. 피드백 루프 반복 상한을 3회로 설정한 이유는?

7주차의 “무한 정제 루프” 위험과 연결하여 설명하라. 상한을 1회로 줄이면 어떤 문제가 생기는가? 10회로 늘리면? 3이라는 숫자에 수학적 근거가 있는가, 아니면 경험적 휴리스틱인가?

Q4. Week 12의 LLM-as-Judge와 이번 주의 QA 에이전트는 어떤 관계인가?

“QA 에이전트 자체가 이미 LLM-as-Judge 아닌가?”라는 질문은 정당하다. 둘의 공통점과 차이점을 찾아라. Week 12에서 추가되는 것은 무엇인가? 텔레메트리와 집계가 단순한 1회성 판단과 어떻게 다른가?


  1. QA 에이전트 구현 — 위 코드 기반으로 QAAgent 클래스 완성

  2. 자동 코드 리뷰 파이프라인 — git diff 추출 → Claude 리뷰 → 결과 구조화

  3. 피드백 루프 연동 — QA 실패 시 자동으로 Coder에게 재할당

  4. 전체 파이프라인 통합 — Planner → Coder → QA end-to-end 실행

제출 마감: 2026-05-05 23:59

요구사항:

  1. 완전한 QAAgent 구현
  2. 자동 코드 리뷰 기능 (Claude API 활용)
  3. 피드백 루프 구현 (QA 실패 → Coder 재실행)
  4. Planner → Coder → QA 3단계 파이프라인 end-to-end 시연 영상 또는 로그

  1. QA 독립성 = 컨텍스트 격리 + 도구 제한 + 별도 모델. 편향 상속 차단이 핵심이며, PwC 연구에서 정확도 10%→70% 향상이 실증됐다.

  2. 2단계 리뷰: /reflect(자기 리뷰) → /review(독립 리뷰). 자기 리뷰가 명백한 오류를 선제적으로 제거하여 독립 리뷰의 부담을 줄인다.

  3. 3-병렬 리뷰어: 정확성, 품질, 아키텍처 각각 특화. 심각도 기반 PASS/FAIL 게이트가 최종 판단을 내리며, 병렬 실행으로 지연 없이 세 관점을 동시에 확보한다.

  4. 피드백 루프 + 반복 상한: 자동 복구 + 무한 루프 방지의 균형. 3회 상한과 에스컬레이션 경로(자동 수정 → 상세 피드백 → human intervention)가 수렴을 보장한다.

  5. LLM-as-Judge 스코어링: 4차원(정확성, 컨벤션, 테스트 커버리지, 보안) × 0-10 점수. 전 차원 ≥ 4 AND Critical 0개가 PASS 조건이며, Week 12 텔레메트리의 데이터 소스가 된다.


  1. DeepMind + MIT “Towards a Science of Scaling Agent Systems” — 검증 에이전트가 생성 에이전트와 컨텍스트를 공유할 때 편향이 상속된다는 실증적 근거. 에이전트 독립성 설계의 이론적 토대.

  2. PwC AI Agent Report (2025) — 단일 에이전트 대비 독립적 judge agent 추가 시 정확도가 10%에서 70%로 향상된다는 산업 데이터. 7배 개선의 구체적 메커니즘 분석 포함.

  3. sdlc-toolkit /review + /reflect 공식 문서 — 프로덕션 수준의 2단계 리뷰 패턴 레퍼런스 구현. 도구 제한 설정, 모델 라우팅, pipeline-state.json 스키마 포함.

  4. “Judging the Judges: A Systematic Investigation of Position Bias in Pairwise Comparative Assessments by ChatGPT” (arXiv, 2024) — LLM 기반 자동 평가의 편향과 한계 분석. QA 에이전트 설계 시 스코어링 신뢰성을 높이는 실용적 지침 제공.