개념 관점
플래너 에이전트가 9-phase SDLC의 병목인 이유를 설명하고, Worker만으로는 흡수하지 못하는 설계 결정 종류를 분류한다.
개념 관점
플래너 에이전트가 9-phase SDLC의 병목인 이유를 설명하고, Worker만으로는 흡수하지 못하는 설계 결정 종류를 분류한다.
설계 관점
spec.md 스키마(목표·범위·제약·acceptance·escalation)를 직접 설계하고 검증 게이트를 정의한다.
구현 관점
codebase analysis 단계와 spec generation 단계를 분리한 플래너를 코드로 구현하고, spec validation을 자동화한다.
운영 관점
spec drift(요구사항 변경 vs 구현 불일치)를 감지하고 수정 흐름을 운영하는 체크리스트를 운영한다.
Week 7에서 설계한 9-phase 에이전틱 SDLC를 떠올려보자. Phase 1-3 (요구사항 수집 → 아키텍처 설계 → 태스크 분해)은 전적으로 플래너의 영역이다. 이 세 단계의 산출물 품질이 Phase 4-9 전체의 성공률을 결정한다.
직관적으로 생각해보면: 코더 에이전트가 아무리 뛰어나도, 입력으로 받은 spec.md에 “사용자 인증 기능 추가” 한 줄만 있다면 올바른 코드를 생성할 수 없다. 반대로 acceptance criteria가 테스트 가능한 수준으로 명확하면, 심지어 소형 모델도 충분한 코드를 생성할 수 있다.
MetaGPT (ICLR 2024) 논문은 이를 실증한다. PM → Architect → Engineer 시퀀스에서 각 역할이 생성하는 SOP(Standard Operating Procedure) 문서의 품질과 최종 코드 품질 사이에 0.72의 상관관계가 있음을 보고했다. 특히 PM 역할이 작성한 PRD(Product Requirements Document)의 명확성이 가장 강한 예측 변수였다.
sdlc-toolkit의 /spec 스킬은 이 통찰을 구현한다. 새 요구사항을 처리하기 전, 기존 프로젝트의 LESSON 파일들을 grep하여 과거 실패 패턴을 컨텍스트에 주입한다. 같은 실수를 반복하지 않는 것 — 이것이 결정론적 파이프라인과 단순 LLM 호출의 차이다.
플래너 에이전트는 모호한 인간 요구사항을 코더 에이전트가 처리 가능한 구체적 태스크로 변환한다. 이 변환은 단일 단계가 아니라 2단계 분리 패턴으로 구현된다.
2단계 분리 패턴: /spec → /architect
sdlc-toolkit은 요구사항 명세와 아키텍처 설계를 의도적으로 분리한다. 이 분리는 소프트웨어 엔지니어링의 관심사 분리(Separation of Concerns) 원칙을 에이전트 설계에 적용한 것이다.
/spec — “무엇을”: 요구사항, acceptance criteria, 제약 조건, out-of-scope 항목 정의. 구현 방법은 언급하지 않는다./architect — “어떻게”: 아키텍처 결정, 모듈 분해, TASK 파일 생성, 의존성 DAG 구성.이 분리의 실질적 이점: spec 단계에서 LLM이 구현 세부사항에 매몰되지 않고 요구사항의 완전성(completeness)에 집중할 수 있다. architect 단계에서는 이미 확정된 요구사항을 전제로 최적 구조를 탐색한다.
흐름: Requirement Spec → Architecture Document + TASK 파일들 (의존성 DAG 포함)
import osimport anthropicimport jsonfrom pathlib import Path
SYSTEM_PROMPT = """당신은 소프트웨어 아키텍트 역할을 하는 플래너 에이전트입니다.주어진 요구사항과 코드베이스를 분석하여 구체적이고 실행 가능한 태스크 목록을 생성합니다.
출력 형식: JSON{ "tasks": [ { "id": "task-XXX", "description": "구체적인 구현 내용", "target_files": ["파일 경로"], "dependencies": ["task-XXX"], "acceptance_criteria": ["검증 조건들"] } ]}"""
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=os.getenv("ANTHROPIC_MODEL", "claude-sonnet-4-5"), 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") 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")기본 구현 이후, 두 가지 핵심 기능을 추가해야 한다: 명세서 품질 자동 검증과 태스크 의존성 DAG 구성.
def validate_spec(self, plan: dict) -> tuple[bool, list[str]]: """생성된 명세서의 품질을 자동 검증""" issues = []
for task in plan['tasks']: # acceptance criteria 완전성 검증 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개 필요")
# assumptions 존재 여부 if 'assumptions' not in task: issues.append(f"{task['id']}: assumptions 필드 누락")
# out-of-scope 명시 여부 (태스크 레벨이 아닌 spec 레벨) if 'out_of_scope' not in plan: issues.append("spec 레벨 out_of_scope 미정의") break # 한 번만 보고
return len(issues) == 0, issues
def create_task_dag(self, plan: dict, output_dir: Path): """TASK 파일 생성 및 의존성 DAG 구성 (Week 7 tier 개념 구현)""" task_map = {t['id']: t for t in plan['tasks']}
# tier 계산: 의존성 없는 태스크 = tier 1, 의존성 있는 태스크 = max(dep_tier) + 1 tiers = {} def get_tier(task_id: str) -> int: if task_id in tiers: return tiers[task_id] task = task_map[task_id] deps = task.get('dependencies', []) if not deps: tiers[task_id] = 1 else: tiers[task_id] = max(get_tier(d) for d in deps) + 1 return tiers[task_id]
for task in plan['tasks']: get_tier(task['id'])
# TASK 파일 생성 output_dir.mkdir(parents=True, exist_ok=True) for task in plan['tasks']: task_file = output_dir / f"{task['id']}.md" with open(task_file, 'w') as f: f.write(f"---\nid: {task['id']}\ntier: {tiers[task['id']]}\n") f.write(f"dependencies: {task.get('dependencies', [])}\n---\n\n") f.write(f"## {task['description']}\n\n") f.write("### Acceptance Criteria\n") for ac in task['acceptance_criteria']: f.write(f"- [ ] {ac}\n")기존 analyze_codebase()는 파일 목록만 반환한다. 코더 에이전트에게 필요한 컨텍스트는 훨씬 더 풍부해야 한다.
sdlc-toolkit의 /architect 스킬이 사용하는 3단계 분석 패턴을 보자.
def analyze_structure(self, project_root: Path) -> dict: """파일/디렉토리/모듈 트리 구성""" structure = {"files": [], "directories": set(), "modules": []}
for f in project_root.rglob("*.py"): rel = f.relative_to(project_root) structure["files"].append(str(rel)) structure["directories"].add(str(rel.parent))
# __init__.py가 있는 디렉토리 = Python 패키지 if (f.parent / "__init__.py").exists(): module = str(rel.parent).replace("/", ".") if module not in structure["modules"]: structure["modules"].append(module)
structure["directories"] = sorted(structure["directories"]) return structureimport ast
def analyze_semantics(self, project_root: Path) -> dict: """함수/클래스 시그니처, import 그래프 추출""" symbols = {}
for f in project_root.rglob("*.py"): rel = str(f.relative_to(project_root)) try: tree = ast.parse(f.read_text()) except SyntaxError: continue
file_symbols = {"classes": [], "functions": [], "imports": []}
for node in ast.walk(tree): if isinstance(node, ast.ClassDef): methods = [n.name for n in ast.walk(node) if isinstance(n, ast.FunctionDef)] file_symbols["classes"].append( {"name": node.name, "methods": methods} ) elif isinstance(node, ast.FunctionDef) and node.col_offset == 0: args = [a.arg for a in node.args.args] file_symbols["functions"].append( {"name": node.name, "args": args, "line": node.lineno} ) elif isinstance(node, (ast.Import, ast.ImportFrom)): file_symbols["imports"].append(ast.dump(node))
symbols[rel] = file_symbols
return symbolsdef analyze_context(self, project_root: Path) -> dict: """기존 컨벤션, 아키텍처 패턴 추출""" context = { "naming_convention": None, "test_framework": None, "patterns": [], "entry_points": [] }
# 네이밍 컨벤션 감지 (snake_case vs camelCase) py_files = list(project_root.rglob("*.py")) snake_count = sum(1 for f in py_files if "_" in f.stem) context["naming_convention"] = ( "snake_case" if snake_count > len(py_files) / 2 else "camelCase" )
# 테스트 프레임워크 감지 if (project_root / "pytest.ini").exists() or \ any(project_root.rglob("conftest.py")): context["test_framework"] = "pytest" elif any(project_root.rglob("test_*.py")): context["test_framework"] = "unittest"
# 엔트리포인트 감지 for candidate in ["main.py", "app.py", "run.py", "__main__.py"]: if (project_root / candidate).exists(): context["entry_points"].append(candidate)
return context세 단계를 통합한 analyze_codebase_full() 메서드는 각 단계 결과를 요약하여 LLM에 주입할 compact 표현을 생성한다. 전체 소스코드가 아니라 구조적 요약이 컨텍스트 효율을 10배 이상 높인다.
생성된 spec.md가 코더 에이전트가 실행하기에 충분한지 자동 검증하는 /validate 패턴이다. Week 7에서 설계한 “3회 재시도 → 인간 에스컬레이션” 흐름의 구현이다.
VALIDATION_CHECKLIST = [ ("frontmatter_valid", lambda s: bool(s.get("title") and s.get("description"))), ("has_what_and_why", lambda s: "what" in s.get("description","").lower() or "why" in s.get("description","").lower()), ("ac_specific", lambda s: all( len(ac) > 20 and not ac.endswith("...") for ac in s.get("acceptance_criteria", []) )), ("no_impl_details", lambda s: not any( kw in str(s.get("acceptance_criteria","")) for kw in ["import", "def ", "class ", "```"] )),]
class SpecValidator: def __init__(self, client: anthropic.Anthropic, max_retries: int = 3): self.client = client self.max_retries = max_retries
def validate(self, spec: dict) -> tuple[bool, list[str]]: failures = [] for name, check_fn in VALIDATION_CHECKLIST: try: if not check_fn(spec): failures.append(name) except Exception as e: failures.append(f"{name}: error({e})") return len(failures) == 0, failures
def validate_with_retry(self, spec: dict, requirement: str) -> dict: """자동 재생성 + 검증. 3회 실패 시 인간 에스컬레이션.""" for attempt in range(self.max_retries): passed, failures = self.validate(spec) if passed: return spec
print(f"[attempt {attempt+1}/{self.max_retries}] 검증 실패: {failures}")
# 실패 항목을 피드백으로 재생성 요청 spec = self._regenerate_spec(spec, requirement, failures)
# 3회 모두 실패 → 인간 에스컬레이션 raise ValueError( f"명세서 자동 검증 실패 (3회). 수동 검토 필요.\n" f"마지막 실패 항목: {failures}" )
def _regenerate_spec(self, spec: dict, requirement: str, failures: list[str]) -> dict: feedback = "\n".join(f"- {f}" for f in failures) response = self.client.messages.create( model=os.getenv("ANTHROPIC_MODEL", "claude-sonnet-4-5"), max_tokens=2048, messages=[{ "role": "user", "content": ( f"다음 명세서의 검증 항목이 실패했습니다:\n{feedback}\n\n" f"원본 요구사항: {requirement}\n\n" f"검증을 통과하도록 명세서를 수정하세요. JSON 출력." ) }] ) return json.loads(response.content[0].text)Q1. 모호성 감지
플래너가 생성한 spec.md의 acceptance criteria가 “모호하다”와 “구체적이다”의 경계는 어디인가? 자동으로 모호성을 감지할 수 있는가? 모호성의 기계적 정의를 제안해보라.
Q2. 분리의 이점
sdlc-toolkit은 요구사항 명세(/spec)와 아키텍처 설계(/architect)를 의도적으로 분리한다. 이 분리의 이점은 무엇인가? 두 단계를 하나로 합치면 어떤 문제가 생기는가?
Q3. Context Rot 트레이드오프
코드베이스 전체를 컨텍스트에 넣는 것과 요약 후 넣는 것의 트레이드오프를 Week 5의 Context Rot 관점에서 분석하라. 어느 시점에서 요약 전략으로 전환해야 하는가?
Q4. MetaGPT vs sdlc-toolkit
MetaGPT의 PM 역할과 sdlc-toolkit의 /spec 스킬을 비교하라. 어느 쪽이 더 결정론적인가? 결정론성을 높이는 설계 원칙은 무엇인가?
플래너 에이전트 구현 — 위 코드를 기반으로 확장
코드베이스 분석 고도화 — 단순 파일 목록에서 함수/클래스 목록까지 분석
spec.md 생성 파이프라인 — 실제 프로젝트(Lab 04의 calculator)에 적용
생성된 spec.md 검증 — 코더 에이전트가 실제로 실행 가능한지 확인
/spec(무엇을) → /architect(어떻게). 관심사 분리 원칙의 에이전트 설계 적용.dependencies 배열과 tier 계산이 Week 7의 병렬화 설계를 구현한다. Tier 1 태스크는 동시 실행 가능.MetaGPT (ICLR 2024)
SOP 기반 멀티에이전트 프레임워크. PM → Architect → Engineer 시퀀스와 문서 품질-코드 품질 상관관계 실증. 플래너 설계의 핵심 레퍼런스.
SWE-agent (Princeton, 2024)
코드베이스 탐색과 계획 수립 자동화. Agent-Computer Interface(ACI) 설계를 통해 LLM이 파일 시스템을 효과적으로 탐색하는 방법을 제시.
Anthropic Claude Code Agent Tool
서브에이전트 스폰, 모델 라우팅, worktree 격리. 플래너-코더 분리를 프로덕션에서 구현하는 실제 패턴.
sdlc-toolkit /spec + /architect
요구사항-아키텍처 분리 패턴의 프로덕션 구현. LESSON 파일 기반 과거 실패 학습, validate 게이트, TASK DAG 생성까지 포함한 포괄적 레퍼런스.
제출 마감: 2026-04-28 23:59
요구사항:
PlannerAgent 구현 (planner_agent.py)spec.md