← Ascendy EN

meta

두 번째 AI를 어떻게 부르고 언제 멈추나 — headless adversarial 코드리뷰 루프

· Ascendy Engineering


TL;DR

소스 노트. 이 글은 백엔드 팀의 인테이크(docs/intake/from-backend/2026-05-31-headless-adversarial-review-loop.md)를 정제한 것이다. 사내 하니스·에이전트 제품명·CLI 이름·모듈 경로 등 내부 식별자는 일반화했다. 멀티 AI를 붙였더니 느려졌다가 진화 서사라면, 이 글은 그 “그래서 실제로 돌려보니” 데이터편이다.

진짜 질문은 “리뷰를 시키느냐”가 아니었다

구현 에이전트(코드를 쓰는 쪽)와 리뷰 에이전트(다른 모델)를 페어로 굴리는 하니스가 있다. AI 코드리뷰를 자동화한다고 하면 보통 “두 번째 모델한테 리뷰를 시킨다”를 떠올린다. 그런데 실제로 발목을 잡은 건 시키느냐가 아니라 어떻게 부르느냐, 그리고 언제 멈추느냐였다.

기존 구조는 리뷰어를 별도 화면 세션으로 띄우는 것이었다. 사람이 프롬프트를 복붙하고, 리뷰어가 화면에 답을 뱉으면 그걸 다시 읽어 sentinel 파일로 결과를 받았다. 이 수동 중계가 가장 큰 마찰이었고, 화면을 읽는 방식은 본질적으로 취약했다 — ghost 텍스트, 타이밍, 포커스 인시던트.

화면 제어를 버리고 subprocess로

리뷰어 CLI가 headless(non-interactive) 모드를 지원하는 걸 확인하고, 리뷰를 화면 제어가 아니라 subprocess 호출로 바꿨다.

이 한 번의 전환으로 화면 파싱, 별도 세션, sentinel 중계가 통째로 사라졌다. 그리고 부수적으로 경계 하나가 분명해졌다 — 협업 상대(다른 레포에 살아있는, 맥락을 쌓아가는 에이전트)와 리뷰 함수(같은 작업을 검토하는 stateless 호출)는 다른 것이다. 전자는 살아있는 세션이어야 하지만, 후자는 그냥 함수처럼 불렀다 버리면 된다. headless로 보내야 하는 건 후자뿐이다.

adversarial 리뷰는 수렴한다 — 단, 멈춤선이 있어야

이 루프로 이번 변경들을 실제로 리뷰했다. 그중 설계 문서 하나를 4라운드 돌린 결과가 인상적이었다.

라운드CHANGES_REQUESTED 항목
19
27
33
40 — APPROVED

항목 수도, 토큰도 단조 감소했다. 핵심은 3라운드였다. 리뷰어가 “이건 설계 문서이고, 필드 단위 명세는 구현 PR에서 정한다”는 사람이 그은 경계를 명시적으로 받아들이고, 더 깊은 명세를 재요구하지 않았다.

이게 수렴의 열쇠다. adversarial 리뷰는 방치하면 끝없이 더 깊은 디테일을 요구한다 — 점근적으로 수렴하긴 하지만 영영 끝나지 않는다. “ideation은 방향, 구현 PR은 명세”라는 멈춤선을 사람이 긋고, 리뷰어가 그 선의 정당성을 판정하게 하자 4라운드에서 깨끗이 끝났다. 리뷰어에게 “멈춰”라고 명령하는 게 아니라, “여기까지가 이 문서의 책임 범위인데 동의하나?”를 묻는 것이다.

그리고 진짜 결함을 잡았다

한 PR(신규 API 엔드포인트)을 “단순하니까”라며 리뷰 게이트 없이 올렸다. headless 리뷰가 단계적으로 잡아낸 것:

다른 PR(리팩터)에선 더 흥미로운 게 나왔다. 내가 추가해둔 “하드코딩 금지” 가드 테스트가 큰따옴표만 검사하는 허점이 있었는데(작은따옴표면 통과), 리뷰어가 그걸 직접 재현해서 코드로 보여줬다. 그래서 가드를 AST 기반으로 고쳤다. 지적을 말로만 하는 게 아니라 증명하는 행동의 가치다.

이 결함들의 공통점은 “단순해 보이는 변경”일수록 리뷰 게이트를 건너뛴 대가가 크다는 것이다 — 그리고 계층 위반·시크릿 로그 노출·quote-only 정적검사 허점은 모두 사람 리뷰가 놓치기 쉬운 종류였다.

가져갈 것


저작·인용: 이 글은 Ascendy Engineering이 작성했으며 출처 표기 시 재인용 가능합니다. 잘못된 정보를 발견하면 GitHub 이슈로 알려주세요.


Tags: ai-agents, code-review, automation, dogfooding, developer-tooling, convergence