3편. [Architecture] "무작정 코딩 금지": 계획 우선 실행(Plan-Then-Execute) 전략
바이브 코딩의 종말과 설계 우선주의의 부상
인공지능 모델, 특히 대규모 언어 모델(LLM)은 근본적으로 확률론적 특성을 지니며, 이는 소프트웨어 엔지니어링의 핵심 가치인 '결정론적 정확성'과 상충하는 지점을 형성한다. 많은 사용자가 AI 코딩을 일시적인 유행이나 기믹으로 치부하는 이유 중 하나는, 모델에 모호한 프롬프트를 제공했을 때 발생하는 '가정의 오류' 때문이다. 프롬프트가 모호할수록 인공지능은 사용자 의도와 일치하지 않는 수많은 가정을 스스로 내리게 되며, 이는 결과적으로 프로젝트의 역사적 맥락이나 기술적 제약 조건을 무시한 '코드 슬롭(Code Slop)'의 생성으로 이어진다.
이러한 문제를 해결하기 위해 등장한 '계획 우선 실행' 전략은 실행 전 단계에서 정교한 설계 문서를 구축하는 것을 골자로 한다. 이는 소프트웨어 개발 수명 주기(SDLC)에서 요구사항 정의와 설계를 상향(Upstream) 배치하여, 인공지능 에이전트가 코드를 한 줄이라도 작성하기 전에 전체 시스템의 360도 청사진을 이해하도록 강제한다. 이러한 접근 방식은 이른바 '15분 만의 폭포수 모델(Waterfall in 15 minutes)'로 비유될 수 있는데, 이는 신속한 반복을 허용하면서도 구조적 엄밀함을 유지하는 현대적 에이전틱 워크플로우의 정수라 할 수 있다.
개발 방법론별 비교 분석
| 특성 | 바이브 코딩 (Vibe Coding) | 계획 우선 실행 (Plan-Then-Execute) |
|---|---|---|
| 주요 산출물 | 즉흥적 프롬프트 및 대화 기록 | persistent SPEC.md, plan.md |
| 의사결정 시점 | 코드 생성 중 실시간 결정 | 구현 전 설계 단계에서 확정 |
| 맥락 유지 방식 | 단기 기억(Context Window) 의존 | 영구적 참조 문서(North Star) 기반 |
| 오류 수정 방식 | 생성된 코드의 사후 패치 | 설계 문서 수정 후 재실행 |
| 복잡도 관리 | 대규모 코드베이스에서 표류 가능성 높음 | 원자적 분해를 통한 체계적 관리 |
| 비용 효율성 | 반복적 수정으로 인한 토큰 낭비 발생 | 정확한 초기 설계로 리워크 최소화 |
SPEC.md: 에이전트의 북극성과 영구적 맥락의 구축
에이전틱 코딩에서 SPEC.md 또는 AGENTS.md는 단순한 문서 이상의 의미를 지닌다. 이는 인공지능이 컨텍스트 한계로 인해 이전의 결정을 잊어버리거나 아키텍처적 일관성을 잃지 않도록 붙잡아주는 '북극성' 역할을 한다. 에이전트가 새로운 채팅 세션을 시작하거나 메모리를 정리해야 할 때, 이 문서는 프로젝트의 목표, 제약 조건, 기술적 결정 사항을 복원하는 '장기 기억 장치'로 기능한다.
효과적인 SPEC.md를 구축하기 위해서는 'CARE' 프레임워크를 적용하는 것이 권장된다. 이는 맥락(Context), 행동(Action), 결과(Result), 평가(Evaluation)를 의미하며, 인공지능이 의사결정을 내리는 데 필요한 모든 비즈니스 배경과 기술적 가이드라인을 포함한다. 구체적으로는 기술 스택의 버전 정보, 프로젝트의 디렉토리 구조, 코드 스타일 컨벤션, 그리고 무엇보다 중요한 '에이전트의 권한 경계'가 명시되어야 한다.
SPEC.md의 핵심 구성 요소 및 설계 원칙
- 기술 기반(Technical Foundation): 단순한 'React 프로젝트'가 아닌 'React 18.2, TypeScript 5.1, Vite, Zustand'와 같은 구체적인 버전과 라이브러리를 명시하여 에이전트가 임의의 기술적 선택을 내리지 않도록 제한한다.
- 프로젝트 헌법(Constitution): 에이전트가 반드시 준수해야 할 아키텍처적 원칙을 정의한다. 예를 들어 "모든 비즈니스 로직은
/services계층에 위치하며, 컴포넌트 내부에서 직접 API를 호출하지 않는다"와 같은 규칙은 에이전트의 임의적 추상화를 방지한다. - 경계 설정(Boundaries): 에이전트가 승인 없이 수행할 수 있는 작업(Always Do), 반드시 확인을 거쳐야 하는 작업(Ask First), 그리고 절대 금지된 작업(Never Do)을 삼단계로 구분하여 자율성의 범위를 정의한다.
- 수행 가이드라인: 테스트 주도 개발(TDD)의 강제 여부, 린팅 규칙, 커밋 메시지 규격 등을 포함하여 인간 개발자와의 협업 효율성을 극대화한다.
이러한 문서화는 인간이 읽기 위한 용도를 넘어, 인공지능이 해당 문서의 구조적 단서를 통해 아키텍처를 이해할 수 있도록 설계된 '에이전트 경험(AX, Agent Experience)'의 일환이다.
Cursor 플랜 모드: 연구와 설계의 결정론적 분리
현대적인 AI IDE인 Cursor의 '플랜 모드(Plan Mode)'는 계획 우선 실행 전략을 도구적 수준에서 구현한 사례이다. 개발자가 Shift+Tab을 눌러 플랜 모드로 전환하면, 에이전트는 즉시 코드를 작성하는 대신 연구와 질문에 집중하는 '읽기 전용' 상태가 된다. 이 과정에서 에이전트는 코드베이스를 탐색하여 관련 파일을 식별하고, 요구사항의 모호함을 해소하기 위해 사용자에게 질문을 던지며, 최종적으로 마크다운 형태의 상세 구현 계획을 생성한다.
플랜 모드의 진정한 가치는 '수정 가능한 설계도'를 제공한다는 점에 있다. 에이전트가 생성한 계획은 .cursor/plans/ 디렉토리에 저장되어 팀원들과 공유될 수 있으며, 사용자는 구현을 승인하기 전에 계획의 타당성을 검토하고 단계를 조정할 수 있다. 이는 인공지능이 잘못된 방향으로 코드를 대량 생성하는 것을 미연에 방지하여 리소스를 절약하고 프로젝트의 정밀도를 높인다.
Cursor 플랜 모드 워크플로우 분석
| 단계 | 활동 내용 | 도구 및 결과물 |
|---|---|---|
| 연구(Research) | 코드베이스 탐색 및 관련 의존성 파악 | /ask, 코드 인덱싱 활용 |
| 명확화(Clarification) | 요구사항의 모호성 식별 및 사용자 질의 | 대화형 인터페이스 |
| 계획 수립(Planning) | 단계별 작업 목록 및 파일 참조 생성 | plan.md, tasks.md |
| 검토 및 수정(Review) | 인간 개발자의 아키텍처 검증 및 피드백 | 인라인 에디터 |
| 실행(Execution) | 승인된 계획에 따른 자율적 코드 생성 | 에이전트 모드(Agent Mode) |
특히 복잡한 기능을 구현할 때는 'Think More' 기능을 활성화하여 에이전트가 추론 과정을 더 깊게 가져가도록 강제할 수 있다. 이는 모델이 계획을 수립할 때 더 많은 시뮬레이션을 수행하게 하여, 이후 구현 단계에서의 예외 상황을 최소화하는 효과를 거둔다.
에이전트 분해(Decomposition): 복잡성 관리를 위한 아키텍처 패턴
아무리 정교한 계획이라도 하나의 거대한 덩어리(Monolithic)로 에이전트에게 전달되면, 컨텍스트 용량 초과와 집중력 저하로 인해 품질이 급격히 떨어진다. 이를 방지하기 위한 핵심 기술이 바로 '원자적 분해(Atomic Decomposition)'이다. 이는 거대한 비즈니스 목표를 독립적으로 검증 가능하고 실행 가능한 작은 작업 단위로 쪼개는 과정을 의미한다.
작업 분해는 단순히 목록을 만드는 것을 넘어, 작업 간의 의존 관계를 정의하고 최적의 실행 경로를 설정하는 고도의 설계 행위이다. 이를 위해 라우팅(Routing)과 병렬화(Parallelization)라는 두 가지 주요 아키텍처 패턴이 활용된다.
1. 라우팅 패턴: 지능적 작업 배분
라우팅은 사용자의 의도를 분석하여 가장 적합한 전문 에이전트나 도구에게 작업을 할당하는 메커니즘이다. 이는 크게 세 가지 수준에서 작동한다.
- 내부 에이전트 라우터(Intra-Agent Routers): 개별 에이전트 내부에서 ReAct(Reasoning and Acting) 루프를 통해 다음 행동(도구 호출 또는 답변)을 결정하는 미시적 수준의 라우팅이다.
- 에이전트 간 라우터(Inter-Agent Routers): 의미론적 유사성(Semantic Similarity)을 기반으로 특정 요청을 전문 에이전트(예: 데이터베이스 에이전트, UI 에이전트)에게 전달하는 수준이다.
- 오케스트레이터 라우터(Orchestrator Routers): 가장 상위 수준에서 전체 요청을 여러 하위 작업으로 분해하고, 각 작업의 완료 상태를 추적하며 전체 흐름을 관리하는 '관리자 에이전트' 패턴이다.
2. 병렬화 패턴: 성능과 효율의 극대화
병렬화 패턴(Fan-out/Gather)은 서로 의존성이 없는 작업들을 동시에 실행하여 전체 처리 시간을 단축시키는 기법이다. 예를 들어, 새로운 기능을 구현할 때 '단위 테스트 작성', '문서화', '코드 품질 검사' 등은 서로 독립적으로 수행될 수 있다. 연구에 따르면, 병렬 패턴을 적절히 활용할 경우 단일 에이전트 방식에 비해 성능이 최대 81%까지 향상될 수 있다.
하지만 모든 작업이 병렬화 가능한 것은 아니다. 데이터베이스 스키마 설계와 그 스키마를 사용하는 API 구현은 명확한 선후 관계를 가지므로 순차적(Sequential) 패턴을 따라야 한다. 무리한 병렬화는 컨텍스트 충돌과 일관성 결여를 초래할 수 있으므로, 분해 단계에서 '의존성 맵(Dependency Mapping)'을 작성하는 것이 필수적이다.
에이전트 협업 패턴 요약
| 패턴 | 작동 방식 | 적용 사례 |
|---|---|---|
| 순차 패턴 (Sequential) | 앞 작업의 출력이 뒷 작업의 입력이 되는 체인 구조 | 데이터 추출 → 정제 → 로딩 파이프라인 |
| 병렬 패턴 (Parallel) | 독립적 작업을 동시 실행 후 결과를 합성 | 코드 리뷰(보안, 스타일, 성능 동시 분석) |
| 조정자 패턴 (Coordinator) | 중앙 에이전트가 의사결정 및 작업 분배 | 복잡한 고객 서비스 봇(문의 유형별 분기) |
| 반복 패턴 (Iterative) | 피드백 루프를 통해 출력을 지속적으로 정제 | 코드 품질 향상을 위한 생성-비평 루프 |
| 계층적 패턴 (Hierarchical) | 트리 구조의 관리 계층을 통한 작업 위임 | 대규모 엔터프라이즈 애플리케이션 리팩토링 |
AI 표류(Drift) 방지: 컨텍스트 관리와 서킷 브레이커
인공지능 에이전트가 자율적으로 작업을 수행하다 보면, 원래의 목표에서 벗어나거나 코드베이스를 무한히 탐색하는 '표류' 현상이 발생하기 쉽다. 이는 에이전트의 상태(State)가 시간이 지남에 따라 노이즈로 오염되기 때문인데, 이를 방지하기 위한 아키텍처적 장치가 필요하다.
가장 효과적인 방법 중 하나는 **'상태 비저장적 반복 설계(Stateless but Iterative Design)'**이다. 이는 매 반복마다 에이전트의 메모리를 초기화하고, SPEC.md와 현재 작업 상태(Progress Log)만을 신선한 컨텍스트로 주입하는 방식이다. 이렇게 하면 이전 단계에서 발생한 혼란이 다음 단계로 전이되는 것을 막을 수 있다.
또한, '서킷 브레이커(Circuit Breakers)' 전략을 도입하여 에이전트의 자원을 보호해야 한다. 경고 임계값에 도달하면 에이전트에게 탐색을 중단하고 현재까지의 결과를 정리하도록 유도하며, 하드 임계값에 도달하면 실행을 강제 종료하여 토큰 낭비를 방지한다. 이는 특히 루프 패턴에서 발생할 수 있는 무한 루프 위험을 제어하는 핵심적인 안전장치이다.
기술적 구현: 오케스트레이션 코드 예시
계획 우선 실행 전략과 분해 패턴을 실제 코드로 구현하기 위해서는 Python의 LangChain이나 Pydantic AI, 혹은 TypeScript의 Restate와 같은 프레임워크가 활용된다. 이러한 도구들은 에이전트의 도구 호출(Tool Calling)과 상태 전이를 결정론적으로 관리할 수 있게 해준다.
Python을 활용한 작업 분해 및 라우팅 예시 (Pydantic AI 기반)
아래 코드는 사용자의 복잡한 요청을 분석하고 적절한 전문 에이전트에게 할당하는 라우팅 메커니즘의 개념적 예시이다.
from pydantic import BaseModel, Field
from pydantic_ai import Agent, RunContext
from typing import Literal
# 작업 분류를 위한 데이터 모델
class TaskRoute(BaseModel):
category: Literal["frontend", "backend", "database", "devops"] = Field(
description="작업의 기술적 도메인을 분류합니다."
)
priority: int = Field(ge=1, le=10, description="작업의 우선순위입니다.")
# 오케스트레이터 에이전트 설정
orchestrator = Agent(
'anthropic:claude-sonnet-4-5',
result_type=TaskRoute,
instructions="""사용자의 요청을 분석하여 적절한 개발 도메인으로 라우팅하십시오.
SPEC.md를 참조하여 프로젝트의 아키텍처 원칙을 준수해야 합니다.""",
)
@orchestrator.tool
async def check_architecture_standard(ctx: RunContext[str], task_desc: str) -> str:
# SPEC.md 또는 프로젝트 헌법에서 관련 규칙을 검색하는 로직
return "현재 프로젝트는 Zustand를 상태 관리 라이브러리로 사용합니다."
# 실행 예시
async def handle_request(user_input: str):
result = await orchestrator.run(user_input)
print(f"라우팅 결과: {result.data.category}, 우선순위: {result.data.priority}")
TypeScript를 활용한 병렬 에이전트 처리 예시 (Restate 기반)
병렬화 패턴을 구현할 때는 각 작업의 독립성을 보장하고 결과를 안전하게 합성하는 것이 중요하다.
import * as restate from "@restatedev/restate-sdk";
// 병렬 에이전트 서비스 정의
const ParallelDevService = restate.service({
name: "ParallelDev",
handlers: {
processFeature: async (ctx: restate.Context, featureSpec: string) => {
// 1. 작업을 독립적인 하위 태스크로 분해하여 병렬 실행
const = await Promise.all();
// 2. 결과 합성 및 최종 검증
return synthesizeResults();
}
}
});
결론: 자율 주행 코드베이스를 향한 여정
소프트웨어 개발에서 "무작정 코딩 금지"라는 격언은 인공지능의 시대를 맞아 더욱 강력한 의미를 갖게 되었다. 계획 우선 실행 전략은 인공지능의 창의성과 속도를 활용하면서도, 인간 엔지니어의 아키텍처적 통제권을 유지할 수 있게 해주는 유일한 현실적 대안이다. SPEC.md를 통한 북극성 설정은 에이전트에게 변하지 않는 진리의 원천을 제공하며, Cursor의 플랜 모드와 정교한 작업 분해 패턴은 복잡한 문제를 다룰 수 있는 도구적 근간을 마련한다.
앞으로의 AI 코딩은 개발자가 직접 코드를 타이핑하는 시간보다, 에이전트가 따라야 할 명세(Spec)를 정교화하고 생성된 계획의 논리적 허점을 찾아내는 '감독 및 설계자'로서의 역할이 더욱 강조될 것이다. 마크다운이 단순한 문서 형식을 넘어 하나의 고수준 프로그래밍 언어처럼 작동하는 '명세 기반 개발(SDD)'의 시대에서, 성공적인 엔지니어는 인공지능이라는 강력한 엔진을 제어할 수 있는 견고한 아키텍처 프레임워크를 구축하는 능력을 갖추어야 한다. 결국 '표류하지 않는 AI'는 모델의 성능이 아니라, 인간이 설정한 계획의 엄밀함과 분해의 논리성에서 비롯되기 때문이다.