AI가 코드를 짰습니다. 8개 기능을 전부 만들었습니다. 테스트도 통과했습니다. 머지도 성공했습니다.

신나서 앱을 열어봤더니 네비게이션 바가 2개였고, 메인 페이지에는 “Coming soon…“만 떡하니 있었습니다.

이번 글은 그 좌절에서 시작해서, “AI에게 코딩을 제대로 시키려면 뭐가 필요한가”를 깨닫게 된 과정을 다루겠습니다!


그 전에: 스크립트 6개를 하나의 플랫폼으로

1편에서 개별 스크립트의 한계가 보이기 시작했다고 했는데요. OpenAI 실패 패턴 수정 작업을 하다가 문득 생각이 들었습니다.

“지금 내가 고치고 있는 이 스크립트들(spec-agent, ai-run, ai-pr, ai-review, ai-gatekeeper)을 하나의 시스템으로 묶으면 어떨까?”

그러면서 비전이 점점 구체화되었습니다.

  1. AI가 매일 뉴스를 수집해서 앱 개발 아이디어를 발굴 (수익화 가능성 검토, 회사 기획팀처럼 보고)
  2. 내가 승인하면 설계 에이전트가 코딩 가능한 형태로 설계
  3. 코딩 에이전트가 PR마다 위험도/품질 점수를 매겨서 자동 머지 또는 재코딩
  4. 배포까지 자동화

이걸 클로드에게 기존 에이전트들의 파일 덤프와 함께 전달하고 실현 가능성을 분석해달라고 요청했습니다.

결론은 “Phase 1~3은 기존 자산 기반으로 2~3주 MVP 가능, Phase 4(배포/운영)는 1~2개월” 이었습니다.

충분히 해볼 만 하다고 판단했고 바로 시작했습니다!!


왜 Turborepo + pnpm 모노레포인가

통합 플랫폼의 기반으로 pnpm + Turborepo 모노레포를 선택한 이유가 있습니다.

ai-factory/
├── apps/
│   ├── web/              # Next.js 대시보드
│   └── orchestrator/     # 파이프라인 엔진
├── packages/
│   ├── agents/           # scout, spec, planning, review
│   ├── db/               # Drizzle ORM + SQLite (Turso)
│   └── shared/           # LLM 클라이언트, 공유 타입
  1. 에이전트 독립성: Scout, Spec, Planning, Review 에이전트를 각각 독립 패키지로 분리하면 한 에이전트를 수정할 때 다른 에이전트에 영향을 주지 않습니다. 1편에서 느꼈던 “스크립트 하나 고치면 다른 것에 영향” 문제를 구조적으로 해결하기 위함입니다.
  2. 공유 모듈: 기존에는 OpenAI/Claude API를 각 스크립트에서 직접 호출했는데, packages/shared에 공유 LLM 클라이언트를 두면 API 키 관리, 에러 핸들링, 비용 추적을 한 곳에서 할 수 있습니다.
  3. Turborepo 빌드 캐시: 에이전트 패키지가 변경되지 않으면 빌드를 스킵합니다. 개발 속도에 도움이 됩니다.
  4. DB 분리: packages/db에 Drizzle ORM + 스키마를 두면 web과 orchestrator가 동일한 DB 접근 방식을 공유합니다. DB로 Turso(원격 SQLite)를 선택한 이유는 무료 티어가 충분하고 SQLite 호환이라 로컬 개발이 쉽기 때문입니다.

Gemini 도입 → 제거: 모델은 2개면 충분하다

처음에는 Review Agent에 Gemini 2.5 Pro를 넣었습니다. “설계는 GPT, 코딩은 Claude, 리뷰는 Gemini”로 3개 모델을 써보려는 실험이었는데요.

결과적으로 빠르게 제거했습니다. 이유는 단순합니다.

  1. API 키 관리 복잡도 증가: 3곳의 API 키를 관리하고 각각 다른 에러 핸들링을 해야 함
  2. 공유 LLM 모듈 복잡도: 프로바이더가 2개일 때와 3개일 때의 코드 복잡도 차이가 큼
  3. 실질적 이점 부족: 리뷰는 GPT나 Claude도 충분히 잘하는 영역이라 Gemini를 쓸 특별한 이유가 없었음

결국 OpenAI + Anthropic 2개 프로바이더로 정리했습니다. 유닛 테스트 22개도 이때 추가해서 LLM 클라이언트의 안정성을 확보했습니다.


첫 번째 성과: Scout Agent 실행 성공!

모노레포 구축 후 가장 먼저 동작을 확인한 것은 Scout Agent입니다.

Scout Agent가 웹 대시보드에서 버튼 하나로 아이디어 5개를 생성하는 데 성공했습니다!!

Scout Agent의 동작 방식은 이렇습니다.

  1. GPT-4.1 mini로 최신 뉴스를 웹 검색해서 수집 (비용이 저렴한 모델로 단순 수집 작업)
  2. Claude Sonnet으로 앱 아이디어 생성 (시장성, 수익화 가능성 분석 포함 — 복잡한 분석이 필요해서 Claude 사용)
  3. S/A/B/C/D 등급으로 점수 매기기

뉴스 수집처럼 단순한 작업에는 저렴한 모델을, 아이디어 분석처럼 복잡한 작업에는 강력한 모델을 배치하는 전략을 여기서도 적용했습니다.


diff 방식을 버리다: 코딩 에이전트 도구 도입

이제 가장 큰 난관인 코딩 에이전트를 해결해야 합니다.

1편에서 “LLM에게 diff를 직접 생성시키는 방식은 근본적으로 취약하다”는 결론을 내렸습니다. 대안은 Claude Code, Aider 같은 코딩 에이전트 도구입니다. 이 도구들은 LLM이 직접 diff를 만드는 대신, 실제 파일 시스템을 읽고 쓰면서 코딩합니다. diff의 정확성 문제가 근본적으로 해결되는 방식입니다.

도구별 벤치마크를 조사했습니다.

도구SWE-bench앱 빌딩비용/패킷특징
Claude Code77%93%~$0.20성공률 최고, 비용 가장 높음
Cline중간중간중간VS Code 에디터 통합 장점
Aider--~$0.10Git 통합 최고, 비용 최저, 복잡한 작업에서 약함

비용과 성공률 사이에서 고민한 결과, Aider를 1차로 시도하고 실패 시 Claude Code로 에스컬레이션하는 하이브리드 전략을 선택했습니다.

(결론부터 말하면 Aider의 실패율이 너무 높아서 결국 Claude Code 단일 엔진으로 갈아타게 됩니다.. 이건 3편에서 다룹니다!)


듀얼 패스 구조: 자동 + 수동 기획

초기에는 Scout Agent가 자동으로 뉴스를 분석해서 아이디어를 만드는 것만 있었는데요. 개발하다 보니 “내가 직접 아이디어를 넣고 AI와 대화하면서 기획하고 싶을 때도 있다”는 필요성을 느꼈습니다.

그래서 듀얼 패스 구조를 도입했습니다.

  • 경로 A (자동): Scout Agent — 뉴스 수집 → 아이디어 자동 생성
  • 경로 B (수동): 기획 AI (Planning Agent) — 채팅으로 직접 아이디어 입력 → AI와 대화하며 PRD 생성

Planning Agent를 구현하면서 재밌는 문제가 하나 있었는데요. AI가 역할을 혼동하는 문제가 발생했습니다!! PRD를 생성해야 하는데 코드를 생성하거나, 기획자가 아니라 컨설턴트처럼 조언만 하는 상황이 벌어졌습니다.

원인은 시스템 프롬프트에서 역할이 명확하지 않았기 때문입니다. 프롬프트를 “당신은 시니어 프로덕트 매니저입니다. 사용자와 4단계(Discovery → Deep Dive → Draft → Review)로 대화하며 반드시 PRD를 산출물로 생성해야 합니다”로 명확하게 바꾸니 해결되었습니다.


“빈 레포에서 코딩을 시작합니다”

코딩 에이전트를 연동하고 처음 파이프라인을 돌렸을 때의 일입니다.

GitHub 레포는 만들어졌는데 로컬에 아무 파일도 없는 상태에서 코딩 에이전트가 시작되었습니다. 당연히 실패합니다..

게다가 Claude Code가 --yes-always 플래그 없이 실행되어서 사용자 승인을 기다리다 타임아웃이 걸렸고, Aider도 빈 레포라 할 일을 못 찾고 7초 만에 종료되었습니다.

“코딩 에이전트에게 빈 캔버스를 주면 안 된다. 기본 뼈대(scaffold)를 먼저 만들어줘야 한다.”

그래서 scaffold 자동 생성 로직을 추가했습니다. Next.js 기본 구조 + Tailwind CSS + 필수 설정 파일을 먼저 만들어두고, 코딩 에이전트는 그 위에 살을 붙이는 방식으로 변경했습니다.


드디어: 8/8 패킷 전부 성공!

scaffold와 headless 모드를 적용하고 다시 파이프라인을 돌렸습니다.

8/8 패킷 전부 코딩 + 머지 성공!!

처음으로 파이프라인이 끝까지 돌아간 순간이었습니다. 정말 기뻤습니다!!

그런데 앱을 열어보니…

  • 네비게이션 바가 2개 중복 표시됨 (layout이 2번 렌더링되는 버그)
  • UI가 거의 스타일링이 없음 (기본 HTML 수준)
  • 로그인하지 않아도 페이지에 접근 가능한데 “Unauthorized” 텍스트만 표시
  • 메인 페이지가 “Coming soon…“

솔직히 좀 좌절했습니다.. “파이프라인은 돌아가는데 결과물이 이 모양이면 의미가 있나?”라는 생각이 들었습니다.


근본 원인 2가지

좌절하고만 있을 수는 없으니 근본 원인을 분석했습니다.

원인 1: scaffold가 너무 빈약하다

기본 Next.js만 던져주니까 AI가 디자인 시스템 없이 기능만 구현합니다. “로그인 페이지를 만들어라”라고 하면, 정말 <input><button>만 있는 페이지를 만들어버립니다.

원인 2: Work Packet에 UI/디자인 요구사항이 없다

“로그인 구현해”만 있고, 어떤 모양이어야 하는지, 어떤 컴포넌트를 써야 하는지, 에러 상태는 어떻게 보여줘야 하는지에 대한 정보가 전혀 없습니다.

해결 방향은 명확합니다. Scaffold에 Tailwind CSS + shadcn/ui 컴포넌트를 미리 포함하고, Spec Agent가 Work Packet에 UI 요구사항을 필수로 넣도록 프롬프트를 강화했습니다. 이 개선만으로 프롬프트 토큰 40% 절감 + 성공률 개선이라는 결과를 얻었습니다!


TDD: 게임 체인저를 발견하다

scaffold와 Spec Agent를 개선하면서도 한 가지 고민이 남아있었습니다.

“코딩 에이전트가 프론트엔드 + 백엔드를 유기적으로 코딩할 수 있을까? 앞 패킷에서 만든 API를 뒤 패킷에서 제대로 쓸 수 있을까?”

이 고민을 하다가 AI 코딩 에이전트와 TDD에 관한 연구 결과를 발견했습니다!

테스트를 먼저 만들어주면 코딩 에이전트의 성공률이 크게 올라간다.

이유를 생각해보면 당연합니다.

  1. 명확한 목표 제공: “로그인을 구현해”는 모호하지만, “이 테스트를 통과시켜”는 정확합니다. 어떤 함수가 어떤 입력에 어떤 출력을 내야 하는지 테스트 코드에 다 적혀있으니까요.
  2. 자동 검증 가능: 코딩이 끝나면 pnpm test만 돌리면 됩니다. 사람이 눈으로 확인하지 않아도 성공/실패를 자동으로 알 수 있습니다.
  3. 패킷 간 계약 역할: 앞 패킷이 만든 API의 인터페이스가 테스트에 정의되어 있으면, 뒤 패킷은 그 인터페이스를 보고 사용할 수 있습니다.
  4. 수정 루프 가능: 테스트가 실패하면 에러 메시지를 코딩 에이전트에게 피드백해서 자동 수정을 시도할 수 있습니다.

사람이 TDD를 하는 이유와 AI가 TDD를 해야 하는 이유가 약간 다른 점이 흥미롭습니다. 사람은 “설계를 먼저 생각하기 위해” TDD를 하지만, AI에게는 “뭘 만들어야 하는지 명확하게 알려주기 위해” TDD가 필요합니다.

구현 방식은 이렇게 잡았습니다.

  1. Spec Agent가 각 패킷마다 vitest 테스트 파일을 자동 생성
  2. 코딩 에이전트는 테스트 파일을 READ-ONLY로 두고, 테스트를 통과시키는 코드만 작성
  3. 최대 3회 재시도, 실패 시 Claude Code가 에러 메시지를 보고 자동 수정 시도

TDD를 도입하고 다시 파이프라인을 돌려보니 9/9 패킷 전부 merged, 34.5분 완주!! TDD가 AI 코딩에서 이렇게까지 효과적일 줄은 솔직히 예상 못했습니다!!


2편을 마치며

이 시기의 교훈을 정리하면 이렇습니다.

  1. diff 직접 생성 → 코딩 에이전트 도구 전환이 필수적이었다 — 파일 시스템을 직접 읽고 쓰는 방식이 근본적으로 안정적
  2. 코딩 에이전트에게 빈 캔버스를 주면 안 된다 — scaffold의 품질이 결과물의 품질을 결정
  3. “돌아간다”와 “쓸 만하다”는 완전히 다른 이야기 — 8/8 패킷 성공해도 앱이 망가질 수 있음
  4. TDD는 AI 코딩의 게임 체인저 — “뭘 만들어야 하는지”의 명세서 역할 + 자동 검증 + 수정 루프

다음 글에서는 비용을 20배 줄인 최적화, 품질 체계 구축, 그리고 현재 모습까지 다루겠습니다!!

감사합니다!!


이 시점의 파이프라인 구조

flowchart TD
    subgraph row1 [" "]
        direction LR
        Scout["Scout\nGPT-4.1 mini"] --> Planning["Planning\nSonnet"] --> Spec["Spec\nGPT-5.2"]
    end
    subgraph row2 [" "]
        direction LR
        Scaffold["Scaffold\nNext.js + Tailwind"] --> Coding["Coding\nClaude Code + Aider"] --> Verify["Verification"] --> Git["Git 머지"]
    end
    row1 --> row2
    style row1 fill:none,stroke:none
    style row2 fill:none,stroke:none

© 2025. All rights reserved.

김민제의 블로그