AI 스페셜리스트와 자동사냥 — 하네스로 제어하는 AI 파이프라인
Slow Query 수집부터 정합성 검증 · PR 생성 · 리뷰 반영까지
[Tech-Blog] AI 스페셜리스트와 자동사냥 — 하네스로 제어하는 AI 파이프라인
Slow Query 수집부터 정합성 검증 · PR 생성 · 리뷰 반영까지

“또 슬로우 쿼리야?”

안녕하세요, 무신사 물류플랫폼팀에서 WMS를 개발하고 있는 이상호입니다.
금요일 오후, 슬랙 알림이 울립니다. “DB 응답 속도 저하 감지.” 이미 익숙한 시나리오입니다.
CloudWatch 콘솔을 열고, 로그 그룹을 찾아 Logs Insights 쿼리를 작성합니다. 느린 쿼리 몇 개를 복사해서 에디터에 붙여넣고, 코드베이스에서 해당 쿼리가 어디서 호출되는지 추적합니다. Repository를 찾고, Service 레이어를 거슬러 올라가며, Entity 관계를 파악합니다.
원인을 찾았습니다. N+1 문제. fetch join을 추가하고, 브랜치를 만들고, 커밋하고, PR을 올립니다. 리뷰어가 코멘트를 남기면 다시 코드를 수정합니다.
쿼리 하나에 이 정도면 양호합니다. 그런데 슬로우 쿼리가 10개라면? 매주 반복된다면?
백엔드 개발자라면 이 흐름이 익숙할 겁니다. 각 단계가 어렵지는 않지만, 반복되면 상당한 시간을 잡아먹습니다. 어느 날 문득 이런 생각이 들었습니다.
“이 과정을 AI한테 시키면 어떨까?”
범용 AI에게 던져봤습니다
처음에는 Claude Code에 슬로우 쿼리를 붙여넣고 “분석해줘”라고 던졌습니다. 돌아오는 대답은 교과서적이었습니다. “인덱스를 추가하세요”, “서브쿼리를 JOIN으로 변환하세요.” 틀린 말은 아닌데, 우리 코드베이스의 맥락이 전혀 없었습니다. 어떤 Repository에서 호출되는지, Entity 관계가 어떻게 얽혀 있는지, QueryDSL을 쓰는지 Native Query를 쓰는지 — 이런 맥락 없이는 피상적인 조언밖에 나오지 않습니다.
여기서 깨달은 것이 있습니다. AI를 “잘 쓰는 것”이 아니라, AI가 “잘 움직이도록 설계하는 것”이 핵심이었습니다.
말(馬)에게 마구를 씌우듯, AI에게도 하네스(Harness)를 씌워야 합니다. 같은 Claude 모델이라도 어떤 하네스를 씌우느냐에 따라 전혀 다른 결과가 나옵니다. LangChain의 Terminal Bench 2.0 리더보드를 보면, 동일한 모델이 하네스에 따라 상위권과 하위권을 오가는 것을 확인할 수 있습니다.
Agent = Model + Harness — 모델이 아닌 모든 것이 하네스입니다. — The Anatomy of an Agent Harness, LangChain
이 단순한 공식에서 시작된 프로젝트가 query-engineer입니다. Slow Query 수집부터 정합성 검증, PR 생성, 리뷰 피드백 반영까지 자동화하는 파이프라인 — 그리고 그 파이프라인을 올바르게 작동시키는 하네스를 설계한 이야기를 공유합니다.

자동사냥 파티를 꾸리다 — AI에 하네스를 적용해보자
RPG 게임에서 자동사냥을 돌리려면 파티가 필요합니다. 만능 캐릭터 하나로는 고난도 레이드를 클리어할 수 없습니다. 탱커, 딜러, 힐러 — 각자의 역할에 특화된 캐릭터를 모아 파티를 구성해야 합니다.
AI도 마찬가지입니다.
하네스 없는 AI는 방향을 모릅니다
하네스 없이 AI에게 복잡한 태스크를 던지면 어떤 일이 벌어질까요? 우리도 직접 겪었습니다. 프로젝트 초기에 “이 슬로우 쿼리 10개를 한꺼번에 분석해줘”라고 던졌더니, 앞의 3~4개는 잘 분석하다가 뒤쪽 쿼리로 갈수록 분석 품질이 눈에 띄게 떨어졌습니다. 이미 분석한 내용과 코드 조각들이 컨텍스트를 채우면서, 새 쿼리에 집중할 여력이 줄어든 것입니다.
LangChain은 이 현상을 컨텍스트 부패(Context Rot)라고 부릅니다 — 컨텍스트 윈도우가 채워질수록 모델의 추론 능력과 태스크 완료 능력이 저하되는 현상입니다. 하네스 없는 AI는 이 부패를 막을 수 없습니다.
쿼리 최적화 스페셜리스트의 하네스 설계
그래서 우리는 AI에게 하네스를 씌웠습니다. 구체적으로 어떻게 했는지, 하네스의 핵심 축을 따라 설명하겠습니다.
첫째, 시스템 프롬프트로 AI의 행동 공간을 좁혔습니다.
“당신은 10년 경력의 Spring Boot 및 JPA/Hibernate 전문가입니다.”
이 한 줄이 AI의 행동 공간을 극적으로 좁힙니다. 범용 AI가 Python이나 Go 관련 답변을 섞어 내놓을 가능성을 차단하고, JPA/Hibernate 도메인에 집중하게 만듭니다. 여기에 출력 형식도 제한했습니다 — “반드시 unified diff 형식으로 패치를 생성하라”, “직접 파일을 수정하지 마라.” 이 규격이 AI가 만들어내는 결과물의 품질을 보증합니다.
마구(馬具)에 비유하면, 시스템 프롬프트는 고삐에 해당합니다. 말이 어디로 갈지 방향을 잡아줍니다.
둘째, 서브에이전트로 컨텍스트를 격리했습니다.
하나의 AI가 코드 구조 파악, DB 메타데이터 분석, 설정 확인, 트레이드오프 분석을 동시에 하면 컨텍스트가 빠르게 오염됩니다. 그래서 4개의 정찰조를 편성했습니다.

4개 에이전트가 병렬로 정찰하고, 각자의 노이즈를 흡수합니다. 상위 에이전트는 정제된 결과만 수신합니다. 서브에이전트의 진정한 가치는 역할 분담이 아니라 컨텍스트 격리에 있었습니다. 실제로 처음에는 하나의 Claude에게 “코드 구조도 파악하고, DB 메타데이터도 분석하고, 설정도 확인하고, 트레이드오프도 분석해줘”라고 시켰는데, 컨텍스트가 빠르게 오염되어 분석 품질이 떨어졌습니다. 관점별로 에이전트를 분리하자 각자의 컨텍스트가 깨끗하게 유지되면서 품질이 눈에 띄게 올라갔습니다.
셋째, 자기 검증 메커니즘을 내장했습니다.
AI가 만든 패치를 그대로 적용하면 안 된다는 걸 빨리 깨달았습니다. 삭제 줄(-)과 추가 줄(+)의 순서가 뒤바뀌거나, hunk 헤더의 줄 수가 실제 본문과 맞지 않는 경우가 빈번했습니다. 패치 정규화기를 만들어 줄 순서 보정, 중복 제거, 헤더 재계산을 자동으로 수행하고, 적용에는 4단계 폴백을 두었습니다. 여기에 한발 더 나아가, 수정된 SQL이 정말로 더 빠른지를 EXPLAIN과 실측으로 확인하는 정합성 검증 스테이지(Stage 7)까지 파이프라인 안에 내장했습니다.
에이전트가 실패할 때마다, 그 실패가 다시 발생하지 않도록 하네스를 업데이트하는 것 — 이것이 하네스 엔지니어링의 본질입니다. 패치 정규화기, 폴백 전략, 정합성 검증 — 이 모두가 이전 실패에서 태어났습니다.
코드 리뷰 스페셜리스트 파티 — 두 공급자, 네 관점
쿼리 분석 스페셜리스트만으로는 부족했습니다. AI가 만든 코드를 AI가 검증하는 구조가 필요했습니다. 그래서 4명의 코드 리뷰 스페셜리스트를 편성했고, 여기에 한 단계 더 확장해 두 개의 AI 공급자(Claude + OpenAI Codex)가 같은 diff를 각자의 시선으로 교차 검증하도록 만들었습니다.

각 리뷰어에게 독립된 핵심 질문과 체크리스트를 부여한 것이 핵심입니다. 이것이 관점별 전용 하네스입니다. 공급자를 1개 선택하면 4관점이, 2개를 선택하면 4관점 × 2공급자 = 최대 8개 리뷰가 병렬로 실행됩니다. 공급자별로 서로 놓치는 부분을 보완해줍니다. 그 대신 동일한 결함을 중복해서 보고할 수 있습니다. 결과를 취합할 때 파일 + 카테고리 + 라인 윈도우로 중복을 자동 병합하고 심각도는 높은 쪽을 채택합니다.
Codex CLI가 설치되지 않은 환경에서도 Codex 공급자를 선택할 수 있습니다. 이때는 Codex 쪽 관점 전부가 CodexCliNotFoundError로 실패하지만, Claude 공급자는 영향받지 않고 graceful degrade 됩니다. 반면 스킬 파일 자체가 유실되는 SkillFileLoadError는 분리 rethrow되어 "리뷰 0건 = PASS"로 오역되는 게이트 무력화를 차단합니다. 실패가 곧 PASS가 되지 않도록 하네스의 경계를 단단히 잠가두는 것 — 이 규약이 자동화 시스템에서 조용한 품질 저하를 막습니다.
자동사냥 시스템 설계 — 7-Stage 파이프라인 아키텍처
파티를 꾸렸으니, 이제 자동사냥 시스템을 설계할 차례입니다. 전체 파이프라인은 사냥감 탐지부터 사람의 최종 검수까지 7개 스테이지로 구성됩니다.

이 파이프라인 전체를 관통하는 것이 PipelineCallbacks — 사람의 지휘 인터페이스입니다. 각 Stage가 시작/완료/실패할 때마다 콜백을 호출하여 진행 상황을 실시간으로 보고합니다. SSE(Server-Sent Events)로 브라우저에 스트리밍되어, 사람은 자동사냥이 돌아가는 모습을 실시간으로 모니터링할 수 있습니다. 이 추상화 덕분에 웹 UI와 CLI(qe 바이너리)가 동일한 파이프라인 엔진을 공유합니다(뒤에서 자세히 설명합니다).
Stage 1: 사냥감 탐지 — 슬로우 쿼리 수집
수동으로 할 때 가장 귀찮았던 건 CloudWatch 콘솔을 여는 것부터였습니다. AWS SDK v3로 CloudWatch Logs Insights에 직접 쿼리를 날립니다. RDS 인스턴스명을 입력하면 로그 그룹 경로를 자동으로 탐지합니다.
여기서 한 가지 삽질이 있었습니다. 같은 SQL 구조인데 파라미터 값만 다른 쿼리가 수십 개씩 쏟아졌습니다. SELECT * FROM orders WHERE id = 1과 SELECT * FROM orders WHERE id = 2는 사실 같은 패턴이니까요. 그래서 SQL fingerprinting을 구현했습니다. 리터럴 값을 ?로 치환하고, IN (?, ?, ?)을 IN (?)으로 축약하여 구조적으로 동일한 쿼리를 하나로 합칩니다.
Stage 2~3: 정찰조 투입 & 스페셜리스트 출격
Stage 2에서 DB 메타데이터(테이블 구조, 인덱스 정보)를 수집하고, Stage 3에서 본격적인 AI 코드 분석이 시작됩니다.
슬로우 쿼리가 여러 건이면 병렬로 분석합니다. 최대 5명의 스페셜리스트가 동시에 출격하여, 각각 독립된 쿼리를 분석합니다.

각 스페셜리스트는 Claude CLI를 통해 실행됩니다. 처음에는 분석에 필요한 코드 파일을 사람이 직접 찾아서 프롬프트에 넣어줬습니다. 하지만 관련 파일이 Repository 하나가 아니라 Service에서 호출하는 다른 Repository까지 연쇄적으로 엮여 있는 경우가 많았습니다.
전환점은 Claude CLI의 내장 도구(Read, Grep, Glob)를 활용하기로 한 순간이었습니다. 이제 SQL에서 테이블명만 추출하여 “이 파일들부터 살펴봐”라는 힌트만 제공합니다. 나머지는 AI의 자율 탐색에 맡깁니다. 우리도 처음에는 관련 파일 내용을 통째로 프롬프트에 넣어줬지만, 오히려 분석 품질이 떨어지는 경험을 했습니다. 컨텍스트를 과도하게 채우면 정작 핵심을 놓치는 것입니다. 힌트만 주고 탐색은 AI에게 맡기는 것이 더 나은 결과를 만들었습니다.
분석 결과를 어떤 형태로 받을지도 정해야 했습니다. AI에게 작업 공간 전체를 맡기지 않고, unified diff 형식의 패치 파일로 제안만 받기로 했습니다. 패치는 그 자체로 리뷰 가능한 산출물이고, 쿼리당 텍스트 파일 하나라 병렬 분석에 가볍습니다. 무엇보다 "실제로 코드를 바꾸는 행위"를 AI가 아니라 하네스 쪽이 통제할 수 있다는 점이 결정적이었습니다 — 사람이 전리품을 검수한 뒤에야 실제 코드에 반영되는 구조를 유지하기 위해서입니다. 대신 AI가 만든 패치가 항상 깨끗한 것은 아니어서 패치 정규화기와 4단계 폴백 전략이 필요했고, 패치가 베이스 브랜치 기준으로 굳는 만큼 라인 번호가 금세 어긋난다는 대가도 있었습니다. 이 대가는 Stage 5에서 다시 다루겠습니다.
Stage 4: 전리품 검수 — 패치 메타데이터 등록
Stage 3에서 생성된 패치는 Stage 4에서 출력 디렉토리 스캔을 통해 파이프라인 내부 메타데이터로 등록됩니다. git apply 테스트는 Stage 5로 넘기기 전 4단계 폴백 전략으로 수행됩니다.
핵심 설계 원칙은 쿼리별 격리입니다. 10개의 슬로우 쿼리를 분석했는데, 그중 하나의 패치가 적용에 실패하더라도 나머지 9개는 정상적으로 PR이 생성됩니다. 분석 리포트가 PR 본문이 되어, 리뷰어는 AI가 왜 이런 수정을 제안했는지 맥락을 바로 파악할 수 있습니다.
Stage 4.1: 정합성 검증 — “정말 더 빨라졌는가?”를 자동으로 묻다
파이프라인의 게이트 스테이지입니다. AI가 만든 패치가 이론적으로 그럴듯한 것이 아니라, 실제로 더 빠른지를 검증합니다. PR 생성 직전에 패치의 품질을 자동으로 검사합니다.
검증은 세 단계로 이루어집니다.
- SQL 페어 추출:
report.md와fix.patch에서 AS-IS SQL과 TO-BE SQL을 각각 추출합니다. - 정적 검증: 구문이 유효한지, 결과 집합 형태가 호환되는지, 명백한 누락이 없는지 검사합니다. (DB 연결이 없어도 항상 수행)
- 동적 검증: DB 연결이 있으면 AS-IS/TO-BE 둘 다에 대해 EXPLAIN을 수행하고, SELECT는
LIMIT래핑 + READ ONLY 트랜잭션으로 실제 실행까지 하여 결과를 비교합니다. DML은 안전하게 EXPLAIN만 수행합니다.
검증 결과는 네 가지 verdict 중 하나로 귀결됩니다.

여기서 중요한 설계 결정 하나를 공유하고 싶습니다. 기본값은 MISMATCH가 감지되어도 PR을 생성하고 경고만 남기는 것입니다. 자동화가 사람의 판단을 대체하는 것이 아니라, 사람이 더 나은 판단을 내리도록 정보를 모아주는 것이 하네스의 역할이기 때문입니다. 다만 failOnMismatch: true 옵션을 켜면 해당 쿼리의 PR 생성을 skip하도록 보수적으로 전환할 수 있습니다. 기본은 "정보를 제공한다", 옵션은 "자동으로 차단한다" — 이 두 레이어를 분리해둔 것이 실제 운영에서 가장 유연했습니다.
산출물은 쿼리별로 정합성 검증, 쿼리 explain검증 파일이 쿼리별로 생성됩니다. 이 산출물은 Stage 5에서 PR 본문에 자동 첨부되어, 리뷰어가 “이 수정이 정말 더 빠른가?”를 판단할 근거를 미리 받게 됩니다.
Stage 5: 전리품 수거 — AI 리뷰 폐쇄 루프로 게이트를 지키다
Stage 5는 원래 “패치를 적용하고 PR을 만드는” 단순한 스테이지였습니다. 그런데 운영하다 보니 문제가 보였습니다. AI가 생성한 패치가 기본적인 검증(빌드, Stage 7 정합성)은 통과해도, 코드 리뷰 관점에서 보면 아직 거슬리는 부분이 남아 있을 때가 많았습니다. 사람이 매번 그걸 코멘트로 남기고 다시 수정을 요청하는 것도 결국 반복 노동입니다.
그래서 Stage 5 안에 AI 코드 리뷰 폐쇄 루프를 집어넣었습니다. PR을 만들기 직전에 CRITICAL/WARNING이 0건이 될 때까지 리뷰와 수정을 반복합니다.

설계 포인트 두 가지를 공유하고 싶습니다.
첫째, 라인 번호는 믿지 않습니다. 리뷰를 받을 때 finding에 line: 42 같은 정보가 붙지만, 수정 패치가 적용되는 순간 라인은 금방 밀려납니다. Stage 3에서 작업 공간(worktree)을 통째로 맡기지 않고 베이스 브랜치 기준의 패치 텍스트로 결과를 받기로 한 결정의 대가가 여기서 드러납니다. 그래서 수정 프롬프트에 강제 규약을 넣었습니다. "finding.line은 리뷰 시점의 참고값일 뿐이다. 현재 위치는 반드시 Read/Grep으로 재확인하라." 자동화 시스템에서 stale data를 참값으로 취급하지 않는 습관을 하네스 안에 박아둔 것입니다.
둘째, 조기 종료 조건을 명확히 했습니다. 루프가 도는 것만으로 품질이 보장되는 건 아닙니다. 이전 반복 대비 needsFix가 감소하지 않거나, fix 자체가 실패하거나, AI가 NO_PATCH 응답을 내면 즉시 루프를 빠져나옵니다. 무한 수정을 피하고, 수렴하지 않는 상황을 조용히 넘기지 않기 위함입니다. 반복별 산출물은 review-iterations/iter-N-*.{json,md,patch} 형태로 쿼리 디렉토리 안에 스냅샷으로 남아, 어떤 반복에서 무엇이 바뀌었는지 사후 추적이 가능합니다.
maxReviewIterations = 0으로 설정하면 루프를 비활성화해 과거 동작(CRITICAL 잔존 시 즉시 PR 차단)으로 되돌릴 수 있습니다. 하네스는 엄격함의 정도를 설정값으로 다이얼 돌리듯 조정 가능해야 합니다.
Stage 6: PR AI 피드백 → 스페셜리스트 재출격
무신사에서는 GitHub PR이 생성되면 Claude가 자동으로 코드 리뷰를 수행하는 파이프라인이 이미 운영되고 있습니다. Stage 5에서 AI가 만든 PR도 예외가 아닙니다.
Stage 6은 여기서 한 발 더 나아갑니다. Claude가 작성한 코드 리뷰 코멘트를 다시 Claude가 분석하고, 리뷰 피드백을 반영한 수정 패치를 자동으로 생성하여 PR 브랜치에 push합니다. AI가 코드를 작성하고, AI가 리뷰하고, 그 리뷰를 AI가 다시 반영하는 폐쇠 루프입니다. Stage 5의 리뷰 루프가 “PR 생성 전 사전 게이트”라면, Stage 6은 “PR 생성 후 사후 반영”이라는 대칭적인 역할을 맡습니다.

Stage 6에서 실제로 만났던 장애 하나를 공유합니다. 스킬 파일이 유실되면 SkillFileLoadError가 발생하는데, 처음에는 이걸 일반 오류와 똑같이 취급해 폴러가 무한 재시도하며 GitHub API를 계속 두드렸습니다. 지금은 이 에러를 영구 환경 오류로 분리해서, 감지되는 즉시 해당 PR 타겟의 폴링 state를 정리하고 중단합니다. 사람이 스킬을 복구한 뒤 폴링을 다시 시작하면, 이미 처리한 코멘트는 건너뛰고 새 코멘트부터 재개됩니다. 영구 오류를 일시적 실패로 오해하지 않는 것 — 이것도 하네스의 일부입니다.
세 가지 하네스, 세 가지 철학 — 스페셜리스트, 제너럴리스트, 오케스트레이터
한 발 물러나서, 우리의 선택을 더 넓은 맥락에 놓아보겠습니다. AI 에이전트를 설계하는 방식은 크게 세 가지로 나뉘고, 각각 다른 하네스 설계 철학을 따릅니다.
타이트한 하네스: 스페셜리스트
“AI가 할 수 있는 것”을 의도적으로 좁혀서 정확도를 극대화합니다. query-engineer가 이 방식입니다. “10년 경력 JPA 전문가”라는 페르소나, “unified diff 형식만 허용”이라는 출력 규격, “Plan 모드 금지”라는 행동 제약 — 이 모든 제약이 결과물의 품질을 보증합니다. 고위험 작업이나 규제 산업에서 특히 효과적이며, 통제와 감사가 용이합니다.
느슨한 하네스: 제너럴리스트
“AI가 할 수 있는 것”을 넓혀서 범용성을 극대화합니다. Claude Code에 쿼리를 붙여넣고 자유롭게 대화하는 것이 이 방식입니다. 자유도가 높은 대신, 사람이 실시간으로 결과를 판단하고 방향을 교정해야 합니다. 탐색적 작업이나 아직 패턴이 정해지지 않은 새로운 문제에 적합합니다.
적응형 하네스: 오케스트레이터
여러 스페셜리스트를 조합하고, 실행 결과의 피드백으로 하네스를 지속적으로 개선하는 방식입니다. 개별 에이전트의 강점을 살리되, 에이전트 간 조율(Orchestration)이 핵심입니다. AI 기반 추천 시스템처럼 사용자 반응 데이터가 모델을 개선하는 루프나, 여러 전문 에이전트가 하나의 워크플로우를 함께 처리하는 파이프라인이 이 방식에 해당합니다.

우리가 타이트한 하네스를 선택한 이유
슬로우 쿼리 분석은 반복적이고 패턴화 가능한 태스크입니다. N+1 문제, 인덱스 미활용, 불필요한 전체 스캔 — 패턴이 정해져 있습니다. 이런 태스크에는 “한 번 하네스를 잘 설계하면, 이후 수백 번의 실행은 사람 개입 없이 자동”이라는 스페셜리스트 전략이 가장 효율적입니다.
반대로, 아키텍처를 재설계하거나 비즈니스 로직을 새로 구현하는 작업에는 제너럴리스트 방식이 더 적합합니다. 정답이 하나가 아니라 맥락에 따라 달라지는 태스크에서는 느슨한 하네스와 사람의 실시간 판단이 더 효과적이기 때문입니다.
그리고 흥미롭게도, query-engineer의 전체 파이프라인은 사실 오케스트레이터이기도 합니다. 쿼리 분석 스페셜리스트, 정합성 검증 스페셜리스트, 코드 리뷰 스페셜리스트(Claude 4인 + Codex 4인), 리뷰 반영 스페셜리스트 — 이 여러 전문 에이전트를 PipelineCallbacks로 조율하는 구조 자체가 오케스트레이션입니다. 개별 에이전트는 스페셜리스트로 설계하되, 전체 시스템은 오케스트레이터로 운영하는 것 — 이것이 이 프로젝트에서 찾은 실질적인 답이었습니다.
참고: Agentic AI, explained (MIT Sloan), From Coder to Orchestrator (Nicholas C. Zakas)
삽질에서 배운 것들
Bash 스크립트에서 웹 파이프라인으로, 그리고 다시 CLI로
처음부터 거창한 계획이 있었던 건 아닙니다. CloudWatch에서 쿼리를 뽑아 Claude에게 분석을 맡기는 단순한 Bash 스크립트로 시작했습니다. 하나씩 욕심이 생기면서 — 쿼리 10개를 동시에 분석하고 싶고, 진행 상황을 실시간으로 보고 싶고, PR까지 자동으로 만들고 싶었습니다. 셸 스크립트에 기능을 덧붙이다 보니 한계가 금방 왔고, 결국 Next.js 기반의 웹 애플리케이션으로 전면 전환했습니다.
그런데 한 번 웹으로 옮기고 보니, 이번엔 “웹 UI 없이 터미널에서 바로 돌리고 싶다”는 요구가 생겼습니다. 새벽 on-call 중이거나, 사내 자동화 워크플로우에 끼워 넣고 싶을 때 브라우저를 띄우는 건 번거롭습니다. 그래서 qe라는 CLI 바이너리를 추가했습니다.
여기서 중요한 결정을 내렸습니다. 웹과 CLI가 같은 파이프라인 엔진을 공유하도록 만들자.

핵심은 PipelineCallbacks라는 인터페이스입니다. 파이프라인 엔진은 각 Stage의 시작/완료/실패를 콜백으로 통지할 뿐, "어떻게 사용자에게 보여줄지"는 모릅니다. 웹에서는 SSE 브로드캐스트로 브라우저에 스트리밍하고, CLI에서는 터미널에 컬러 출력하고, 언젠가 슬랙봇이 추가된다면 그 역시 PipelineCallbacks 구현체 하나만 새로 작성하면 됩니다. 파이프라인 엔진을 건드리지 않고도 새 프론트엔드를 붙일 수 있는 구조 — 이것이 하네스를 재사용 가능한 라이브러리로 만드는 방법입니다.
하네스를 교체 가능한 모듈로 만들기 — 스킬 시스템
하네스 관점에서 또 하나의 큰 전환은 프롬프트를 코드에서 분리한 것이었습니다. 초기에는 프롬프트를 코드 문자열로 하드코딩했는데, 프롬프트를 살짝 바꿔 실험해보려면 빌드를 새로 해야 했습니다. 지금은 마크다운 파일(skills/*.md)로 분리하고, {{queryNumber}}, {{projectPath}} 같은 템플릿 변수를 런타임에 치환합니다.
스킬 파일은 user prompt와 system prompt가 쌍(pair)으로 구성됩니다 — 예컨대 query-analysis-prompt.md와 query-analysis-system-prompt.md가 한 쌍입니다. 시스템 프롬프트에는 정적인 역할 선언을 두고, 동적인 컨텍스트(쿼리, 파일 힌트, DB 메타)는 user prompt 쪽에 몰아줍니다. 이 분리가 없으면 "역할"과 "맥락"이 섞여 프롬프트 유지보수가 금세 어려워집니다.
공급자를 추가할 때도 이 구조가 강력하게 작동했습니다. Claude에 이어 Codex를 붙일 때, 4관점 체크리스트를 양쪽에 중복해서 쓰지 않기 위해 {{>code-review-common}}이라는 partial include 지시자를 도입했습니다.
code-review-prompt.md (Claude 엔트리)
└── {{>code-review-common}} ←── 4관점 공통 체크리스트
code-review-codex-prompt.md (Codex 엔트리)
└── {{>code-review-common}} ←── 같은 파일을 공유
덕분에 체크리스트 한 줄을 바꾸면 두 공급자에 동시에 반영됩니다. 미래에 Gemini나 다른 공급자를 붙일 때도 {{>code-review-common}}만 include하면 4관점을 그대로 승계합니다.
여기에 안전장치 하나를 더 두었습니다. 신규 스킬 파일은 화이트리스트(ALLOWED_SKILLS)에 등록해야만 로드됩니다. 등록되지 않은 파일을 실수로(또는 악의적으로) 불러오려 하면 SkillFileLoadError가 발생합니다. 또 include 깊이를 2로 제한하고 순환 참조를 탐지해, 프롬프트 로딩이 조용히 실패하는 상황을 원천 차단했습니다. 하네스의 와이어를 교체 가능하게 만들되, 아무 와이어나 끼울 수는 없게 만든 것입니다.
왜 API가 아니라 CLI였나
기술 스택을 고를 때 가장 고민했던 부분은 AI 연동 방식이었습니다. API와 CLI, 두 갈래가 있었습니다. API는 분석에 필요한 코드 파일을 사람이 직접 읽어서 프롬프트에 넣어줘야 합니다. CLI는 Read, Grep, Glob 같은 도구를 내장하고 있어서, AI가 스스로 프로젝트를 탐색합니다. 어떤 파일을 읽어야 할지 사람이 정해줄 필요가 없습니다.

이 비교를 거쳐 CLI 방식을 택했고, 시작은 Claude CLI였습니다. 그리고 이 결정이 두 번째 공급자로 Codex CLI를 붙일 때도 그대로 통했습니다. 두 CLI 모두 “로컬 코드를 스스로 읽고, stream-json으로 결과를 흘려준다”는 규격을 만족했기 때문에, 공급자 어댑터만 추가하고 나머지(타임아웃/abort/프롬프트 렌더링/에러 포장)는 공통 헬퍼를 그대로 재사용할 수 있었습니다. Codex 쪽에서도 CLI 인터페이스에 막혀 멈춘 일은 없었습니다 — “CLI를 택한 것” 자체가 공급자 확장의 길을 열어준 셈입니다.
사람은 하네스를 설계한다 — 책임과 권한의 재배치
이 프로젝트를 만들면서 느낀 가장 큰 변화는 엄밀함의 위치가 이동했다는 것입니다. 코드 한 줄 한 줄을 정확하게 작성하던 엄밀함이, AI가 코드를 올바르게 작성할 수 있는 환경을 설계하는 엄밀함으로 바뀌었습니다.
query-engineer 프로젝트에서 이 변화를 직접 체감했습니다. JPA 쿼리를 직접 작성하는 대신, AI가 올바른 JPA 쿼리 수정을 제안하도록 하네스를 설계하는 데 시간을 투자했습니다. 그리고 그 투자가 수백 번의 자동 실행으로 돌아왔습니다.
사람의 책임
RPG 레이드에서 공대장은 직접 몬스터를 잡지 않습니다. 전략을 수립하고, 파티를 편성하고, 전투 중 의사결정을 내립니다. AI 시대의 엔지니어도 마찬가지입니다.
1. 하네스를 설계합니다.
시스템 프롬프트를 작성하고, 서브에이전트 구조를 설계하고, 파이프라인의 각 Stage를 정의합니다. 어떤 도구를 AI에게 허용할지, 어떤 행동을 금지할지 — 하네스의 모든 제약은 사람이 결정합니다.
2. 파티를 구성합니다.
어떤 스페셜리스트를 몇 명 투입할지, 어떤 공급자를 선택할지, 동시성을 어떻게 제한할지, 관점별 리뷰어를 어떻게 편성할지, 리뷰 루프의 최대 반복 횟수를 몇으로 둘지 — 파티의 구성은 사람의 몫입니다.
3. 실패에서 하네스를 개선합니다.
에이전트가 실패할 때마다, 그 실패가 재발하지 않도록 하네스를 업데이트합니다. 패치 정규화기, 4단계 폴백 전략, SQL fingerprinting, 정합성 검증(Stage 7), 리뷰 폐쇄 루프의 조기 종료 조건, SkillFileLoadError 분리 rethrow — 이 모든 것은 이전 실패에서 배워 하네스에 추가한 안전장치입니다.
4. 최종 판단을 내립니다.
“이 쿼리가 정말 수정되어야 하는가?”, “이 변경이 다른 기능에 영향을 주지 않는가?”, “Stage 7이 MISMATCH를 보고했는데 그럼에도 이 수정을 반영할 맥락이 있는가?”, “지금 이 시점에 이 수정을 반영하는 것이 맞는가?” — 이런 판단은 비즈니스 맥락을 이해하는 사람만이 내릴 수 있습니다. 그래서 파이프라인의 마지막은 항상 개발자가 PR을 검토하고 Approve 버튼을 누르는 것으로 끝납니다. 자동 머지를 의도적으로 배제한 것은 기술적 한계가 아니라 설계 철학입니다.
스페셜리스트(AI)의 권한
하네스 안에서 AI는 상당한 자율성을 가집니다.
- 어떤 파일을 읽을지는 AI가 결정합니다 — Read, Grep, Glob 도구를 자유롭게 사용
- 도메인 deep dive의 깊이도 AI가 결정합니다 — 관련 코드를 얼마나 추적할지
- 패치 생성과 리뷰 반영도 AI가 수행합니다 — 하네스가 출력 형식을 보장
- 리뷰 findings에 대응하는 수정 전략도 AI가 선택합니다 — 단, 현재 라인을 Read/Grep으로 재확인한다는 규약 안에서
하네스는 AI가 “무엇을 할 수 있는가”를 제한하지만, 그 범위 안에서의 의사결정은 AI에게 위임합니다. “이것을 실제로 적용할 것인가”만은 반드시 사람이 결정합니다. 이것이 하네스의 최종 안전장치입니다.
자가진단: 당신의 AI 하네스는 건강한가?
AI를 활용한 자동화를 설계하고 있다면, 아래 체크리스트로 점검해보시기 바랍니다.
하네스 설계 점검:
- [ ] 시스템 프롬프트가 AI의 역할과 행동 범위를 명확히 제한하는가?
- [ ] 서브에이전트로 컨텍스트를 격리하여 Context Rot을 방지하는가?
- [ ] 자기 검증 메커니즘(패치 검증, 빌드 테스트, 정합성 검증 등)이 파이프라인에 내장되어 있는가?
- [ ] AI가 만든 결과를 다른 AI(또는 다른 공급자)가 교차 검증하는 구조인가?
- [ ] 하네스를 코드 변경 없이 교체/수정할 수 있는가? (프롬프트 분리, 스킬 오버레이)
- [ ] 영구 오류와 일시적 실패를 구분해 다루는가? (실패가 조용히 PASS로 오역되지 않는가)
거버넌스 & 사람 역할 점검:
- [ ] AI 출력물에 대한 최종 승인 절차가 존재하는가?
- [ ] AI의 판단 근거(분석 리포트, 정합성 보고, 리뷰 findings)를 사람이 열람할 수 있는가?
- [ ] AI가 실패했을 때 사람이 개입할 수 있는 폴백 경로가 있는가?
- [ ] 하네스 설계자가 파이프라인 전체 흐름을 이해하고 있는가?
수시간이 수분으로
Before / After

그리고 의도적으로 남겨둔 수동 영역
의도적으로 사람의 손에 남겨둔 영역을 따로 적어둡니다. 자동화의 진짜 가치는 무엇을 자동화했느냐가 아니라, 무엇을 자동화하지 않을지 결정했느냐에 있기 때문입니다.

이 영역들은 자동화 대상이 안 된 것이 아니라, 되지 않도록 일부러 설계한 것입니다. 하네스가 “AI가 할 수 있는 것”을 정의하는 것만큼이나 “AI가 하면 안 되는 것”을 정의하는 것도 중요합니다.
향후 과제
- 하네스 표준화: 현재는 특정 플랫폼에 특화된 하네스입니다. 다른 기술 스택(MyBatis, Node.js 등)을 위한 하네스 템플릿을 설계할 계획입니다.
- 팀 내 확산: 개인 도구 수준에서 대응 프로세스 표준화로 확장하고 싶습니다. CLI(
qe) 덕분에 사내 자동화 워크플로우·슬랙봇과 결합하기도 용이해졌습니다. - 하네스 자가 개선: 실행 트레이스(Stage 7 verdict, 리뷰 findings, 반복 횟수)를 분석하여 하네스 스스로가 약점을 진단하고 개선을 제안하는 적응형 하네스를 구상하고 있습니다.
- 공급자 확장: 현재는 Claude + Codex 두 공급자를 지원합니다. 새로운 모델이 등장할 때
{{>code-review-common}}한 줄과 어댑터 하나만 추가하면 되도록 구조를 열어두었습니다.
마치며 — 하네스를 설계하는 엔지니어
한 가지 고백할 것이 있습니다. 이 프로젝트의 코드 99%는 Claude가 작성했습니다.
Bash 스크립트 시절부터 그랬습니다. “슬로우 쿼리를 분석하는 스크립트를 만들어줘”라고 Claude에게 요청하는 것으로 시작했고, Next.js 웹앱으로 전환할 때도, 파이프라인의 각 Stage를 구현할 때도, 패치 정규화기를 만들 때도, 정합성 검증 스테이지를 추가할 때도, Codex 공급자를 붙일 때도, CLI 바이너리를 뽑아낼 때도 — Claude가 코드를 작성했습니다.
개발자인 제가 한 일은 하네스를 설계하는 것이었습니다. 무엇을 만들지 결정하고, 어떤 제약을 걸지 설계하고, AI가 작성한 코드를 검토하고 피드백하는 것. 그리고 실패할 때마다 하네스를 개선하는 것.
돌이켜보면, 이 프로젝트 자체가 AI 시대 엔지니어 역할의 축소판이라는 생각이 듭니다. 엔지니어의 역할이 바뀌고 있습니다. 코드를 정확하게 작성하는 것에서, AI가 코드를 올바르게 작성할 수 있는 환경(하네스)을 설계하는 것으로.
사람은 직접 몬스터를 잡지 않습니다. 파티를 꾸리고, 하네스를 씌우고, 자동사냥이 올바르게 돌아가도록 설계합니다. 그리고 전리품을 검수하는 최종 판단은, 여전히 사람의 몫입니다.
혹시 비슷한 반복 작업에 시달리고 있다면, AI에게 하네스를 씌워보는 건 어떨까요?
TEAM MUSINSA CAREER
무신사는 2001년 온라인 커뮤니티로 시작해 2005년 무신사 매거진, 2009년 무신사 스토어를 오픈하며 빠르게 성장하고 있는 국내 대표 온라인 패션 스토어입니다. ‘입점 브랜드와 동반성장’이라는 경영 철학을 바탕으로 브랜드가 안정적으로 사업을 전개할 수 있도록 무신사가 보유한 노하우와 인프라를 지원합니다. 고객에게는 풍성한 패션 콘텐츠와 패션에 특화된 차별화된 서비스로 최상의 온라인 쇼핑 경험을 제공하고 있습니다. 글로벌 №1 패션 기업으로 성장할 무신사와 함께 새로운 도전과 혁신을 만들 인재를 기다립니다.
29CM는 ‘고객의 더 나은 선택을 돕는다’라는 미션으로 출발했습니다. 우리는 우리만의 방식으로 콘텐츠를 제공하며, 브랜드와 고객 모두에게 대체 불가능한 커머스 플랫폼을 만들어가고 있습니다. 이 미션을 이루기 위해 우리는 흥미로우면서도 복잡한 문제들을 해결하고 있습니다. 만약 우리와 함께 이 문제들을 해결해 보고 싶다면, 주저하지 말고 29CM에 합류하세요!
*🚀 팀 무신사 채용 페이지 (무신사/29CM 전체 포지션 확인이 가능해요)*
메타데이터
- post_id
- 6c578f8bd1fb
- slug
- ai-스페셜리스트와-자동사냥-하네스로-제어하는-ai-파이프라인-6c578f8bd1fb
- url
- https://techblog.musinsa.com/ai-%EC%8A%A4%ED%8E%98%EC%85%9C%EB%A6%AC%EC%8A%A4%ED%8A%B8%EC%99%80-%EC%9E%90%EB%8F%99%EC%82%AC%EB%83%A5-%ED%95%98%EB%84%A4%EC%8A%A4%EB%A1%9C-%EC%A0%9C%EC%96%B4%ED%95%98%EB%8A%94-ai-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-6c578f8bd1fb
- canonical_url
- https://techblog.musinsa.com/ai-%EC%8A%A4%ED%8E%98%EC%85%9C%EB%A6%AC%EC%8A%A4%ED%8A%B8%EC%99%80-%EC%9E%90%EB%8F%99%EC%82%AC%EB%83%A5-%ED%95%98%EB%84%A4%EC%8A%A4%EB%A1%9C-%EC%A0%9C%EC%96%B4%ED%95%98%EB%8A%94-ai-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-6c578f8bd1fb
- author_url
- https://medium.com/@sangho.lee_6120
- status
- ok
- fetched_at
- 2026-06-27 23:56:40