월요일 아침, 다섯 개의 치명적인 버그 리포트를 마주하며 잠에서 깨어납니다. 리뷰 모니터링 도구는 제 역할을 다했습니다. 모든 크래시 리포트, 분노 섞인 별점 1점 리뷰, "저장 버튼을 누르면 앱이 멈춰요" 같은 메시지를 모두 잡아냈습니다. 무엇이 고장 났는지는 정확히 압니다. 하지만 어디를 찾아봐야 할지는 모릅니다.
첫 번째 파이프라인을 구축한 후 제가 마주한 벽이 바로 이것이었습니다. 그 파이프라인은 앱 리뷰와 유입되는 크래시 로그를 문제없이 모니터링하며, 각 피드백을 버그, 크래시, 기능 요청과 같은 깔끔한 카테고리로 분류했습니다. 대시보드는 건강해 보였습니다. 하지만 실제 디버깅 과정은 그렇지 않았습니다.
버그가 존재한다는 것을 아는 것은 1마일의 여정 중 고작 첫 1인치에 불과합니다. 저는 여전히 IDE를 열고, 모듈을 grep으로 뒤지고, 현재 코드베이스와 스택 트레이스를 대조하며, 머릿속으로 실패 경로를 재구성해야 했습니다. 티켓이 쌓여가고 커피가 아직 따뜻할 때, 이러한 수동적인 고고학 작업은 결코 가질 수 없는 시간을 갉아먹습니다. 저는 파이프라인이 단순히 문제를 알리는 것 이상의 일을 해주길 원했습니다. 문제를 조사해주길 원했습니다.
그래서 저는 단 하나의 목표를 중심으로 시스템을 재구축했습니다. 가공되지 않은 버그 리포트를 받아 검증된 진단을 반환하는 것입니다. LLM의 장황한 설명이 아니라, 파일명을 명시하고, 라인을 지목하며, 위험도를 추정하고, 수정 방안을 제안하는 구조화된 결과물 말입니다. 어떻게 구현했는지 소개합니다.
채팅 로그보다 구조화된 데이터가 나은 이유
저는 PydanticAI를 사용하여 조사 에이전트를 구축했습니다. 이유는 간단합니다. 언어 모델에게 코드에 대해 추론하라고 요청하면, 기본 출력은 친절한 텍스트 흐름입니다. 이는 사람에게는 도움이 될 수 있지만, 후속 스크립트에는 무용지물입니다. 저는 기계가 읽을 수 있는 계약(contract)이 필요했습니다.
에이전트는 근본 원인(root cause), 영향받는 파일(affected files), 제안된 변경 사항(proposed changes), 복잡도 및 위험도 평가(assessment of complexity and risk)라는 네 가지 특정 필드를 가진 검증된 데이터 모델을 반환합니다. 모델에 필드가 누락되었거나 파일 경로를 환각(hallucinate)하면 검증이 실패하며, 저는 이를 즉시 잡아낼 수 있습니다. 이러한 엄격함이 파이프라인의 신뢰성을 유지합니다.
실제 탐정 업무를 수행하기 위해 에이전트는 오직 네 가지 읽기 전용(read-only) 도구만을 가집니다. grep을 통해 코드를 검색하고, 파일의 특정 라인 범위를 읽고, 디렉토리 내용을 나열하며, 클래스나 함수 같은 심볼(symbol)의 위치를 찾을 수 있습니다. '읽기 전용'이라는 점이 중요합니다. 새벽 2시에 쓰기 권한을 가진 에이전트가 제 저장소를 돌아다니는 것을 원치 않았습니다. 먼저 이해하고, 그다음에 수정해야 합니다.
레포 맵(Repo Map): 도구를 쓰기 전의 컨텍스트
에이전트의 첫 번째 버전은 정확했지만 비용이 엄청났습니다. 마치 제자리를 맴도는 관광객처럼 토큰을 낭비했습니다. 모델은 list-dir을 호출하고, 그다음 grep을 하고, 파일을 읽고, 다시 list-dir을 호출하며, 비싼 토큰을 하나씩 써가며 프로젝트 구조에 대한 정신적 모델을 천천히 조립했습니다.
해결책은 에이전트가 시작하기 전에 압축된 레포 맵(repo map)을 생성하는 것이었습니다. 이 맵은 저장소의 핵심 파일, 주요 함수나 클래스, 그리고 주요 모듈이 어떻게 연결되는지를 보여주는 정제된 개요입니다. 에이전트에게 시행착오를 통해 길을 찾으라고 하는 대신 GPS를 건네주는 것이라고 생각하면 됩니다.
이 맵이 컨텍스트 윈도우(context window)에 있으면, 에이전트는 src/utils/parser.ts가 존재하는지 확인하기 위해 불필요한 호출을 하지 않습니다. 이미 지형을 알고 있기 때문입니다. 연기가 피어오르는 능선을 향해 곧장 달려갑니다. 이 단 한 번의 변화로 방황하는 단계를 완전히 제거할 수 있었습니다.
도구 깔때기(Tool Funnel): 결론을 강제하기
맵이 있어도 에이전트는 망설일 수 있었습니다. 의심스러운 파일을 찾은 뒤, 스스로를 의심하고, 다시 검색하고, 다른 파일을 읽는 등 "한 번만 더 확인하자"는 끝없는 루프에 빠지곤 했습니다. 저는 추진력을 강제할 방법이 필요했습니다.
저는 에이전트가 진행함에 따라 할 수 있는 일을 제한하는 3단계 도구 깔때기(tool funnel)를 구현했습니다.
1단계는 탐색(exploration)입니다. 에이전트는 네 가지 도구 모두에 대한 완전한 권한을 가집니다. 추론 과정에서 버그를 재현하는 데 필요한 것이라면 무엇이든 검색하고, 둘러보고, 읽을 수 있습니다.
2단계는 심층 분석(deep-dive)입니다. 에이전트가 유력한 결함 지점을 식별하면, 탐색 도구에 대한 권한을 잃습니다. 오직 파일 읽기만 가능합니다. 더 이상의 grep이나 디렉토리 나열은 불가능합니다. 이 단계에서 에이전트는 이미 찾아낸 코드를 연구하고 증거 체인을 구축해야 합니다.
3단계는 출력(output)입니다. 모든 도구가 잠깁니다. 에이전트는 더 이상 코드베이스에 쿼리를 보낼 수 없습니다. 이제 자리에 앉아 보고서를 작성해야 합니다. 이는 "한 가지만 더 확인해 볼게요"라며 끝없이 반복되는 스파이럴을 방지합니다.
이 깔때기 방식을 통해 분석당 평균 도구 호출 횟수를 40회 이상에서 약 10회로 줄였습니다. 에이전트는 더 빨라지고 저렴해졌으며, 역설적으로 결론을 내려야만 했기에 더 확신에 찬 결과를 내놓게 되었습니다.
백엔드를 교체 가능하게 유지하기
저는 시스템을 특정 모델 제공자에 종속되도록 하드코딩하고 싶지 않았습니다. 작업에 따라 서로 다른 엔진을 사용합니다. 때로는 Claude Code를, 때로는 Grok Build를, 때로는 그 순간 가장 저렴한 것을 사용합니다. 핵심 로직을 특정 제공자에 구애받지 않게 유지하기 위해, 저는 작업을 두 단계로 나누었습니다.
1단계는 탐색입니다. 역량 있는 모델이라면 무엇이든 될 수 있는 코딩 에이전트가 레포 맵을 읽고, 도구를 사용하며, 가공되지 않은 마크다운 보고서를 생성합니다. 이 단계는 비용이 많이 드는 사고 과정입니다.
2단계는 구조화입니다. 저렴하고 빠른 LLM이 해당 마크다운을 가져와 엄격한 Pydantic 모델로 재포맷합니다. 이 단계는 추론이 거의 필요하지 않습니다. 단순히 추출과 포맷팅 작업일 뿐이므로 경량 하드웨어에서도 실행 가능합니다.
경계가 명확하기 때문에 검증 로직을 건드리지 않고도 백엔드를 교체할 수 있습니다. 마크다운 보고서는 탐색적 두뇌와 제가 실제로 사용하는 구조화된 출력 사이에서 범용 어댑터 역할을 합니다.
실제로 효과가 있었던 것
이 설정은 들어오는 이슈를 처리하는 방식을 바꾸어 놓았습니다. 분류 레이어는 여전히 버그와 기능 요청을 구분하지만, 이제는 분석 레이어가 그 직후에 바로 작업을 이어받습니다. 에디터를 열 때쯤이면 파일 경로, 라인 범위, 그리고 제안된 변경 사항이 저를 기다리고 있습니다. 저는 여전히 모든 것을 수동으로 검토합니다. 이것은 보조 도구이지, 자율 주행이 아닙니다. 하지만 예전에 하던 컨텍스트 수집은
