세 개의 AI에게 각자의 역할을 맡겨 프로그래밍하기: Codex는 테스트, Grok은 구현, Claude는 검수
요약
Codex, Grok, Claude를 각각 테스트, 구현, 검수 역할로 나누어 협업하는 멀티 에이전트 코딩 파이프라인 실험 결과를 다룹니다. TDD 기반의 엄격한 테스트를 인터페이스로 활용하여 오류 발견 지점을 앞당기는 워크플로우의 가능성을 제시합니다.
핵심 포인트
- Codex로 테스트와 스텁을 생성하여 RED 상태를 구축
- Grok이 테스트를 통과하도록 구현에만 집중하여 GREEN 상태 달성
- Claude가 독립적으로 테스트 실행 및 검수를 수행하여 신뢰성 확보
- 엄격한 테스트가 에이전트 간의 계약(contract) 역할을 수행함
이번 주에 저는 단일 코딩 에이전트(coding agent)에게 처음부터 끝까지 맡기는 대신, 하나의 기능을 세 가지 역할로 나누어 세 회사의 CLI가 각 구간을 담당하게 했습니다. 그 사이에는 TDD(Test-Driven Development)의 레드-그린 신호등을 인터페이스로 사용했습니다. 두 개의 슬라이스(slice)를 실행해 본 결과, 저는 이 파이프라인(pipeline)이 엄격한 테스트가 계약(contract) 역할을 한다는 전제하에 사용 가능하다는 판단을 내렸습니다. 하지만 이것이 절약해 주는 것은 인력이 아니라, '오류 발견 지점'을 앞쪽으로, 그리고 독립적인 곳으로 옮기는 것입니다. 이는 워크플로우(workflow)의 사용 가능성 판정일 뿐이며, 상용화 가능성 판정은 아닙니다. 후자는 토큰(token)/시트(seat) 비용, 개인정보 보호, 속도 제한(rate limit) 및 감사(audit)를 별도로 계산해야 하므로 본문에서는 다루지 않습니다.
실험 조건 (직접 검증 가능)
- 도구:
codex-cli 0.142.4,grok 0.2.77 (44e77bec3a), Claude Code (CLI, 버전 미기록, 알려진 측정 공백에 해당). - 프로젝트: 하나의 Zig 0.16.0 코드베이스(codebase) (개인 리포지토리(private repo), 커밋 해시(commit hash)는 본인 로컬 대조용), 여기에 '턴 후 성찰(reflection after turn)' 기능 추가.
- 샘플: n=2 슬라이스 (config surface, reflection module), 총 15개 테스트 (4 + 11).
- 문제 출제 명령:
codex exec --sandbox read-only(단회성, 파일 쓰기 없음). - 구현 명령: Grok headless, 파일 쓰기 가능 모드 (write→test→fix).
- 400 에러 유발 명령:
grok-composer-2.5-fast에--effort전달 (reasoningEffort파라미터와 동일). - 검수 명령:
zig test <libs> --dep build_options --dep compat -Mroot=src/root.zig --test-filter "<slice 접두사>"; leak-detecting allocator 결과 0 leak.
샘플이 적으므로 수치를 일반화할 수는 없습니다. 커밋 해시는 개인 리포지토리용이며 본인 로컬 대조용입니다. 아래의 각 논거는 다른 엔지니어가 몇 분 내에 검증하거나 반박할 수 있도록 설계되었습니다.
이 파이프라인의 실제 모습
하나의 슬라이스(한 번에 리뷰 가능한 커밋)는 다섯 단계를 거칩니다:
- Codex가 테스트 + 최소 스텁(stub) 생성. 스텁은 테스트가 '컴파일은 되지만 단언(assertion)에서 실패'하도록 만듭니다. 이것이 진정한 RED 단계이며, 심볼(symbol)이 없어서 컴파일되지 않는 것과는 다릅니다.
- Claude가 원본 코드와 대조하여 테스트 확인. Codex는 API(vtable 필드, 함수 시그니처, 생성 패턴)를 추측하므로, 심볼의 존재를 하나씩 검증해야 확정됩니다.
- Claude가 격리된 git worktree에 배치하여 확실한 RED 상태가 될 때까지 실행합니다: pass/fail이 혼재하며, 각 실패에는 저마다의 원인이 있습니다.
- Grok이 GREEN 상태가 되도록 구현 작성. 임무는 단 하나입니다: 실패하는 테스트들을 통과시키는 것이며, 구현(implementation)만 수정하고 테스트는 절대 건드리지 못하게 합니다.
- Claude가 독립적으로 검수 (Grok의 자기 보고를 신뢰하지 않음): 테스트를 실행하고, 0 leak을 확인하며, diff의 정확성과 변경 범위를 대조한 후에야 커밋합니다.
핵심은 '세 개가 하나보다 강하다'가 아니라, 테스트를 만드는 사람과 구현을 하는 사람이 동일한 에이전트(agent)가 아니라는 점입니다. 이로 인해 테스트는 독립적인 사양(specification)이 되고, 구현은 독립적인 검사 대상이 됩니다.
세 가지 실제 대안과의 비교
- 단일 에이전트가 TDD 수행 (스스로 테스트 생성, 스스로 구현): 가장 빠르지만 자기 검증 위험이 가장 높습니다. 동일한 모델이 정답을 정의하고, 동시에 자신이 맞는지 판단하기 때문입니다.
- 사람이 테스트 작성 + 에이전트가 구현: 가장 신뢰할 수 있습니다. 테스트를 사람이 관리합니다. 대가는 인건비가 가장 높다는 점입니다.
- 본문의 세 에이전트 분업: 오케스트레이션(orchestration) 왕복(아래 참조)이 추가되지만, '가짜 그린(false green)' 위험이 낮아집니다. 어느 한 단계의 자기기만은 다음 단계에서 발각됩니다.
어떤 것을 선택할지는 오류 비용과 왕복 비용 사이의 관계에 달려 있습니다.
각 CLI의 구현 차이 (벤더의 능력 차이가 아닌 사용법의 차이)
세 곳 모두 각각의 한 가지 모드만 사용했습니다. 차이점은 모델의 지능이 아니라, 사용자가 어떻게 연결하느냐에 달려 있습니다:
- Codex:저는
codex exec --sandbox read-only의 단발성 (single-shot) 모드를 사용하여, 파일을 수정하지 않고 오직 테스트 코드만 "출력"하도록 설정했습니다. 사실 이 모델은--sandbox workspace-write와exec resume(다회차 가능, 파일 쓰기 가능)를 지원하지만, 저는 의도적으로 이를 "출제자"로 제한하여 출제와 구현의 출처를 분리했습니다. 단발성 read-only 모드를 구현자로 착각하여 사용하면, 겉보기에는 맞지만 실제로는 컴파일되지 않는 파일을 얻게 됩니다. - Grok:저는 headless 모드와 파일 쓰기 가능 모드를 사용하여 write→test→fix 과정을 실행했습니다. 이 과정에서 파라미터 호환성 (parameter compatibility) 문제라는 함정에 빠졌습니다:
grok 0.2.77에서grok-composer-2.5-fast모델을 사용하며--effort를 전달했을 때, 해당 CLI의 API 경로를 통해400 Bad Request: invalid-argument: Model grok-composer-2.5-fast does not support parameter reasoningEffort라는 응답이 돌아왔고, 이로 인해 전체 루프가 공회전하며 파일이 전혀 작성되지 않았습니다. 이는 Grok CLI의 제한이 아니라 해당 모델이 해당 파라미터를 지원하지 않는 것이었습니다.grok 0.2.77자체는--effort를 지원하지만, 지원하는 모델로 교체하면 문제가 해결됩니다. - Claude:통합 및 검수를 담당합니다. 동일한 세션 내에서 컨텍스트 (context)를 지속적으로 유지하고, 도구를 실행하며, diff를 비교할 수 있기 때문입니다.
통합 (integration) 비용은 모델의 능력보다 더 빨리 병목 현상이 됩니다. 저를 정말 힘들게 했던 것은 지능의 문제가 아니라 Zig의 세부 사항이었습니다. 단순히 pub const x = @import("x.zig")를 통한 re-export를 사용할 경우, 만약 어떤 테스트 경로에서도 이를 참조하지 않는다면 Zig의 지연 탐색 (lazy discovery)은 해당 파일의 테스트를 발견하지 못합니다. 이를 강제로 발견하게 하려면 root의 test {} 블록에 _ = x;를 추가해야 합니다. 이러한 통합의 세부 사항이야말로 파이pipeline의 진정한 지연 시간 (latency) 원인입니다.
비용 및 실패 시나리오 (긍정적인 면 외)
- 비용은 각 역할 간의 왕복 횟수이지, 단 한 번의 LLM 호출이 아닙니다. n=2 슬라이스 (slices)를 처리하는 데 "출제→대조→RED(실패)→구현→검수" 과정에서 십여 차례의 에이전트 (agent) 호출이 발생했습니다. 그중 1회는 실패한 인계(앞서 언급한 Grok의 공회전)였으며, 수동 검수(diff 읽기, 테스트 실행)는 슬라이스당 약 몇 분이 소요되었습니다. 작은 기능의 경우, 단일 에이전트가 직접 작성하는 것보다 느립니다. 이 방식이 경제적인 시나리오는 기능에 명확한 테스트 오라클 (test oracle)이 존재하며, 속도보다 정확성을 중시할 때입니다. (실제 경과 시간은 단계별로 측정하지 않았으므로 제외합니다. 이는 알려진 측정상의 공백입니다.)
- 구현자의 자가 보고를 신뢰해서는 안 됩니다. 한 번은 Grok이 "성공"했다고 보고했지만, 실제로는 잘못된 디렉토리에서 실행 중이었고 테스트는 원래부터 통과(green) 상태였으며, 정작 코드는 한 줄도 작성하지 않았습니다. 독립적인 검수(테스트 실행 + diff 대조)는 형식적인 절차가 아니라 필수적인 방어선입니다.
- 레드 라이트 (Red light)는 "진단 가능"해야 합니다. 만약 stub이 전부
error.NotImplemented를 반환한다면, 모든 테스트는 동일한 방식으로 실패할 것이며, 이는 정보가 없는 RED 상태가 됩니다. 구현자가 어디를 수정해야 할지 알 수 있도록, 각 테스트는 반드시 각자의 이유로 실패해야 합니다.
적용 및 부적합 사례
적용 가능: 정적 타입 (static type) + 엄격한 테스트 프레임워크가 있는 프로젝트 (이번의 경우 Zig + leak-detecting allocator), 기능을 작은 슬라이스로 나눌 수 있는 경우, 각 슬라이스에 명확한 단언 (assertion)이 있는 경우. 테스트가 계약 (contract) 역할을 할 때 에이전트 간의 인계가 의미를 갖습니다.
부적합: 탐색적 작업, 사양이 미정인 경우, 또는 "테스트 자체가 설계해야 할 대상"인 작업. 이 파이프라인은 테스트를 미리 작성할 수 있는 사양으로 가정합니다. 사양이 계속 변하는 상황에서 강제로 역할을 나누는 것은 왕복 비용만 증폭시킬 뿐입니다.
판단
이것은 새로운 패러다임이 아니라, 단일 에이전트의 내부 단계(스스로 테스트를 만들고 스스로 구현하는 것)를 에이전트 간의 외부 계약으로 분리한 것입니다. 이를 통해 얻는 이점은 더 빠르고 독립적인 오류 발견 지점입니다. 테스트는 A가 쓰고, 구현은 B가 하며, 검수는 C가 수행하므로, 어느 한 단계에서의 자기기만도 다음 단계에서 적발됩니다. 대가는 오케스트레이션 (orchestration)의 왕복 비용과, 보고를 믿는 대신 실제로 검수해야 한다는 점입니다.
다중 에이전트를 실제 워크플로우 (workflow)에 도입할지 고민하는 분들에게: 먼저 귀하의 프로젝트가 "테스트가 계약 역할을 할 수 있는" 체질인지 확인하십시오. 이 전제가 없다면, 다중 에이전트는 단일 에이전트의 불확실성을 세 배로 만드는 것에 불과합니다.
본문의 실패 시나리오(Grok grok-composer-2.5-fast의 reasoningEffort 400, 허위 성공 보고, Zig lazy discovery를 위한 _ = x; 필요성)는 모두 본인의 실측 결과이며, 도구 버전과 명령어는 "실험 조건"을 참조하십시오.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기