종속 없는 하네스를 향해 — Critic 도입기

challengemulti-modelcodexcriticharness

ADR 텍스트 네 줄을 갱신했습니다. status를 completed로 바꾸고, 정리 두세 줄을 더한 정도였죠. 그리고 main 브랜치에 바로 commit하고 push했습니다.

CLAUDE.md를 열어보면 굵은 글자로 적혀 있어요. "에이전트 브랜치 규율 — main 직접 push 절대 금지." 이전 위반 한 번 뒤 강화된, 명시적으로 반복 강조된 규칙이었습니다. 다음 스프린트 메모리에 한 줄을 남겼어요. "정책 위반 자가 기록".

처음이 아니었습니다. 며칠 전에는 셸 글로빙으로 어떤 디렉토리가 없다고 결론냈고, 사용자가 정정해줬어요. 실재했습니다. 며칠 더 지나서 똑같은 패턴으로 또 같은 실수를 했어요.

워크플로우를 무시하는 빈도가 분명히 늘고 있었습니다. 같은 모델이 같은 실수를 반복하고 있었어요.

그 무렵, OpenAI Codex가 코딩에서 두각을 나타낸다는 얘기가 들렸습니다.

왜 추가였나, 교체가 아니라

전면 교체를 고민하지는 않았어요.

12명의 에이전트는 이미 자기 역할에 자리 잡고 있었습니다. 각자의 페르소나, SSoT, 통신 채널이 안정화돼 있었어요. 무너뜨릴 이유가 없었죠. 게다가 한 모델군에서 다른 모델군으로 갈아치우는 게 정답이라는 보장도 없었습니다. 그건 종속의 대상만 바꾸는 일이니까요.

대신 추가하기로 했습니다. 기존 12명은 그대로 두고, 한 자리만 새로 만들어서 다른 모델 가족을 앉히는 식으로요.

왜 머지 직전 자리였나

추가할 자리가 검증층이었던 데에는 이유가 있어요.

코드의 정확성·보안·동시성·롤백 가능성을 가장 마지막에 한 번 더 봐 줄 시선이 필요했고, 그 자리가 다른 시선의 가치가 가장 컸습니다. 같은 모델 가족끼리 self-review를 하면 같은 훈련 분포에서 오는 같은 맹점을 공유할 수밖에 없거든요. 합의가 곧 통과였어요.

머지 직전에 다른 가족의 시선이 들어오면, 그 합의를 의심하는 자리가 생깁니다. 합의를 깨는 자리, 그게 새 에이전트가 들어가야 할 곳이라고 봤어요.

이름도 그래서 Critic(비평가)으로 정했습니다. 동의자가 아니라, 의심자.

17 라운드 — "다른 시선"의 의미

본격적으로 Critic이 일하는 광경을 본 건 Sprint 135였어요. 5건의 PR을 차례로 머지하면서 매 PR마다 Critic을 호출했고, 결과는 다음과 같았습니다.

PR #167 Wave A
3 라운드
aiQuotaCheck PoC
PR #168 Wave B
4 라운드
P1 4 / P2 2
PR #169 동기화
3 라운드
errorFilter 정책 동기화
PR #170 Wave C
5 라운드
P1 2 / P2 3
PR #171 Wave D
3 라운드
P1 1 / P2 1

17 라운드, P1 8건 + P2 9건을 검출·해결한 뒤 머지했습니다.

흥미로웠던 건 결함의 종류였어요. 잡힌 P1들은 대부분, 같은 모델 가족이 합의해 통과시켰을 코드들이었습니다. 통합 자체에 집중하느라 부수효과를 놓친 모듈, 휴리스틱에 기대 race 가능성이 남아 있던 분기, 비즈니스 분류와 인프라 분류가 섞여 있던 필터.

각각의 코드는 작동했어요. 다만 압박을 받지 않았을 뿐이었습니다. Codex는 그 압박이 들어오는 자리였어요. 같은 분포를 공유하지 않는 시선이 들어오자, 합의가 의심받기 시작했습니다.

이때 처음으로 "모델 다양성"의 가치를 체감했어요. 단순히 더 똑똑한 모델이 필요했던 게 아니라, 다른 분포의 모델이 필요했던 거였습니다.

그런데 — Critic은 만능이 아니었어요

기쁨은 짧았어요.

Sprint 135를 끝내고 메모리를 정리하면서 깨달았습니다. Sprint 134의 main 직접 push 위반은 Critic이 도입된 한참 뒤에 발생한 사건이었어요. Critic은 Sprint 114에 신설됐고, 그 위반은 약 20 스프린트 뒤였습니다.

Critic은 코드 변경의 정확성을 머지 직전에 검증합니다. 절차 위반은 잡지 않아요. 신규 브랜치를 안 따고 main에서 commit하는 행위는 코드 자체가 잘못된 게 아니라, 워크플로우를 우회한 행위예요. PR이 만들어지지 않으니, Critic이 호출될 자리도 없습니다.

코드 검증의 맹점은 줄었지만, 절차 우회는 별개의 문제로 남아 있었어요.

더 거슬러 올라간 발견 — 하네스도 편향이었다

여기서 한 걸음 더 거슬러 올라가게 됐어요.

지금 세팅된 하네스를 보면, 디렉토리 이름부터 도구 이름까지 모두 한 모델 가족의 환경에 묶여 있었습니다. Codex를 한 자리 더 앉혔다고 해서, 시스템 골격이 진짜로 모델 다양성을 가진 건 아니었어요. 검증층 한 자리에 다른 모델을 끼웠을 뿐, 골격은 여전히 한 모델 가족이 통제하고 있었습니다.

이게 진짜 문제였어요.

이번 일이 비슷했어요. 종속의 대상이 외부 서비스에서 AI 모델로 바뀌었을 뿐, 패턴은 같았습니다. 한 가족에 시스템이 묶여 있으면, 그 가족이 흔들릴 때 시스템도 흔들립니다.

같은 교훈이 레이어를 바꿔서 다시 찾아온 셈이었어요.

그래서 — 모델 종속 없는 하네스로

모델 성능은 시점마다 다릅니다. 지금은 코딩에서 Codex가 앞설 수 있고, 다음 분기엔 Claude가, 또 다음엔 Gemini가 앞설 수 있어요. 어떤 시점이 올지 미리 알 수 없고, 알 필요도 없습니다.

다만, 그 시점이 왔을 때 즉시 갈아끼울 수 있어야 합니다. 그게 종속에서 해방된다는 의미예요.

다음 단계로 그리는 그림은 두 층이에요.

먼저 에이전트의 모델 스왑 스위치. 12명의 페르소나는 그대로 두고, 각 자리에 어떤 모델 가족을 앉힐지를 설정으로 선택할 수 있는 구조. 같은 Critic 자리에 Codex를 앉힐 수도, 다음 분기엔 다른 모델을 앉힐 수도 있게요.

그리고 더 멀리 — 하네스 자체를 모델·환경 종속에서 해방. 도구도 환경도 부품 수준으로 추상화하고, 시스템의 골격은 그 위에 얹는 구조. 새 모델이 나오는 순간 기다리지 않고 바로 끼워 넣을 수 있도록.

시작점은 작았어요. 한 자리에 다른 가족을 앉혔고, 17 라운드를 검증했고, P1 여덟 건을 잡았습니다. 거기서 멈출 생각은 없어요. 백준이 사라졌을 때 배운 교훈을 모델 레이어에서 다시 꺼내, 이번엔 더 깊게 적용해보려 합니다.

종속성에서 해방되는 시스템은, 그래서 더 오래갑니다.