64개 병렬은 결과지 원인이 아니다. 일을 성립시킨 건 셋이다 — 컨텍스트 비대칭, 기계적으로 생성되는 큐, 언어 독립 오라클.
언제: 실패를 기계적으로 열거할 수 있고, 그걸 반복해서 고쳐야 할 때.
오라클이 없으면 여기서 멈춘다. 그 포팅에는 4,174개 파일 6만여 개 expect() 짜리 TypeScript 테스트 스위트가 있었고 스킵·삭제 0개였다. 언어를 바꿔도 “테스트 통과”가 진짜 신호였다는 뜻이다. 그게 없으면 리뷰어를 둘 붙여도 검증되지 않은 코드를 빠르게 생산할 뿐이다. 순서를 뒤집는다 — 실패를 열거 가능한 상태(타입 에러 목록·스냅샷·시각 회귀)를 먼저 만들고 그다음에 에이전트를 붙인다.
절차 — 한꺼번에 세우지 않는다. 리뷰어 하나부터.
-
adversarial-reviewer하나만 만들어memory: project로 몇 주 돌린다.--- name: adversarial-reviewer description: diff 하나를 받아 틀린 이유만 찾는다. 수정하지 않는다. tools: Read, Grep, Glob memory: project --- 이 diff는 틀렸다. 어디가 왜 틀렸는지 찾는 것이 유일한 임무다. "괜찮아 보인다"는 결론은 허용되지 않는다. 최소 3개의 실패 시나리오를 제시하라. 구현자의 근거를 요청하지 마라. 주어진 diff와 원본 파일만 본다. 거절 규칙: - 한 문단짜리 주석으로 우회를 정당화해야 한다면 코드가 틀린 것이다. - 스텁·TODO·조건부 비활성화로 컴파일을 통과시킨 것은 미완성이다. - 테스트를 수정해서 통과시킨 변경은 무조건 거절이다. 반복해서 나오는 패턴은 메모리에 기록하라. --- -
쌓인 지적 패턴이 거절 규칙 목록이 된다. 그 목록이 생긴 뒤에야 나머지를 붙이는 게 의미가 있다.
-
porter(구현,isolation: worktree)와fixer(지적만 반영, 새 기능 금지)를 붙인다. 큐를 도는 건 세션 밖이다 — 실패 목록을 파일로 만들고claude -p를 반복한다.
확인: 리뷰어가 tsc·eslint가 통과시키는 지점을 잡고 있는가. 그게 이 역할의 존재 가치 전부다 — effect 의존성의 의미 변화, 이벤트 핸들러의 eager/lazy, ??와 ||의 단축평가 차이, 리렌더 타이밍, 정리 함수 누락. 린터가 잡을 걸 잡고 있으면 프롬프트가 헐거운 것이다.
함정
- 컨텍스트 비대칭은 저절로 유지되지 않는다. 위임 프롬프트를 부모가 쓰기 때문에, 그냥 두면 구현 근거를 요약해서 리뷰어에게 넘겨버린다.
.work/<id>.diff를 경유시키고 “리뷰어에게는 diff 경로와 원본 경로만 전달한다”를 CLAUDE.md에 규약으로 박는다. - 역할 분리를 프롬프트로 하면 무너진다. 리뷰어
tools에Edit/Write가 없는 것이 그 강제다 — 권한으로 막는다. - 같은 모델 둘은 실패 상관관계가 높다. 리뷰어를 둘 둘 거면 하나는
model을 다르게 준다. -p(비대화형)에서는 fork mode가 꺼져 subagent가 포그라운드로 도는 경우가 있다. 병렬은 셸 레벨에서 잡는 편이 예측 가능하다.- 병목은 모델이 아니라 디스크 IOPS였다 — “One slow
grepcommand was all it took to freeze disk reads & writes for minutes.”
왜 되는가 — 같은 모델인데 입력이 다르면 목적함수가 갈린다. 구현자는 원본과 자기 추론을 갖고 머지되기를 원하고, 리뷰어는 diff만 받고 “이 코드는 틀렸다고 가정하라”는 지시를 받아 버그를 찾기를 원한다. 그래서 입력 자체를 잘라야 한다.
실패가 나오면 코드가 아니라 프롬프트를 고친다. 스텁으로 때우기·변명 주석 붙이기는 “한 문단짜리 주석이 필요하면 코드가 틀린 것” 이라는 거절 규칙 하나로 사라졌다.