에이전트에게 코딩을 가르친다는 말의 실제 뜻
실행 피드백으로 탐색을 이끌고 되돌린 트리로 패치를 고르는 방법이 SWE-bench에서 52.8%를 풀고 검증기의 오답 허용을 절반 이하로 낮춘 과정을 해설합니다.
원문 논문 보기
Teaching Agents to Code Reliably는 arXiv에 2610.03984번으로 공개된 연구인데, 저장소 수준 이슈를 푸는 코딩 에이전트를 어떻게 믿을 만하게 가르칠지를 다룹니다.
이 연구가 내세우는 주장은 한 문장으로 정리되는데, 추가 연산은 쓸 만한 수정을 만들어내고 후보 가운데 하나를 고를 믿음직한 근거까지 줄 때에만 성능 향상으로 이어진다는 것입니다.
원문 링크는 그래서 제목 아래에 걸어 두었는데, 저는 이 조건이 왜 이렇게 까다로운지 궁금했습니다. 연산만 늘리면 답이 좋아질 것 같지만 현장에서는 번번이 그렇지 않았기 때문입니다.
왜 더 돌릴수록 답이 나아지지 않을까
처음 이 논문을 펼쳤을 때 저는 연산 이야기에 끌렸습니다. 보통은 모델을 더 오래 돌리면 답이 좋아진다고 믿고 싶어지니까요. 그런데 이 연구는 그 믿음에 바로 제동을 겁니다. 연산 자체가 해답이 아니라 연산으로 무엇을 얻느냐가 갈림길이라고 말합니다. 그렇다면 무엇이 부족했던 걸까요. 저자는 답을 두 가지로 나눕니다. 고칠 만한 후보를 만들어내는 일과 그 후보들 사이에서 하나를 고르는 근거를 마련하는 일입니다. 둘 가운데 하나라도 비어 있으면 연산은 공회전합니다.
이런 구분은 현장에서 일해본 사람에게는 낯설지 않습니다. 밤늦게 테스트를 열 번 더 돌렸는데 아침에 보면 같은 실패 로그만 쌓여 있던 경험이 있습니다. 그때 문제는 컴퓨터가 덜 돌아간 것이 아니라 시도마다 새로운 정보가 생기지 않은 데 있었습니다. 이 논문은 그 감각을 측정 가능한 형태로 바꿉니다. 시도 횟수를 늘리기 전에 시도에서 얻는 증거의 질을 먼저 묻는 것입니다. 저는 이 대목에서 고개를 끄덕였습니다. 숫자를 올리는 처방보다 구조를 뜯어보는 처방이 더 오래가기 때문입니다.
여기서 재미있는 질문이 생깁니다. 에이전트가 정말 멍청해서 못 푸는 걸까요. 아니면 푸는 과정의 설계가 허술해서 같은 실수를 반복하는 걸까요. 연구의 시선은 후자에 가 있습니다. 그래서 다음 장에서는 에이전트가 실제로 어떻게 일하는지부터 차근차근 따라가 보려 합니다. 무엇을 읽고 무엇을 실행하고 무엇을 제출하는지 알면 뒤에 나오는 진단이 훨씬 또렷해지니까요.

저장소를 넘나들며 패치를 만드는 에이전트의 하루
코딩 에이전트의 하루는 사람이 저장소를 처음 받을 때와 많이 닮았습니다. 먼저 관련 코드를 읽고 이슈가 가리키는 위치를 좁혀 갑니다. 이어서 명령을 실행해 재현해 보고 파일을 고친 뒤 다시 테스트를 돌립니다. 마지막에는 패치를 제출합니다. 읽고 실행하고 고치고 내는 흐름이 한 사이클인데, 이 사이클이 몇 번이고 반복됩니다.
그런데 이 단순한 흐름 안에 놓치기 쉬운 지점이 있습니다. 읽는 단계에서 시간을 아끼면 엉뚱한 파일을 고치게 되고, 실행 단계에서 로그를 흘려 읽으면 고친 이유가 흐릿해집니다. 제출 단계에서는 더 미묘한 문제가 생깁니다. 여러 후보가 생겼을 때 어느 것을 낼지 정해야 하는데, 그 판단이 매번 일관되지 않기 때문입니다. 실제 시스템 관점에서 보면 다음 질문이 바로 생깁니다. 좋은 후보를 만드는 능력과 좋은 후보를 알아보는 능력 가운데 어느 쪽이 병목일까요.
저는 이 논문에서 두 능력을 분리해서 본 점이 마음에 들었습니다. 생성과 선택을 한 덩어리로 묶으면 실패 원인이 뒤섞이니까요. 생성은 그런대로 되는데 선택이 흔들리는 것인지, 아니면 선택할 만한 후보 자체가 나오지 않는 것인지 갈라야 처방이 달라집니다. 이 구분을 손에 쥐고 나면 뒤에 나오는 세 가지 진단이 자연스럽게 읽힙니다. 위치가 좁고, 결이 한쪽으로 쏠려 있고, 검증이 헐겁다는 이야기입니다.
시도할수록 같은 자리로 돌아오는 이유
여덟 번을 돌렸는데 여덟 번 다 같은 함수 앞에서 서성이면 그 연산은 어디에 쓴 셈일까요. 연구가 짚은 첫 번째 증상은 위치 다양성이 좁다는 점입니다. 시도는 늘어나는데 손이 가는 자리는 늘어나지 않습니다. 같은 파일을 열고 같은 줄을 고치고 같은 로그를 다시 읽습니다. 그래서 추가 샘플이 적용 범위를 넓히지 못합니다.
왜 그럴까요. 이유는 생각보다 평범합니다. 에이전트는 처음에 그럴듯해 보이는 단서를 잡으면 그 주변에서 벗어나기 어렵습니다. 이슈 본문에 나온 함수 이름이 그 단서가 되기도 하고 스택 트레이스의 맨 위가 그 단서가 되기도 합니다. 한번 경로가 굳어지면 다음 시도도 그 경로에서 조금씩만 비틀어집니다. 마치 처음 들어간 골목이 막다른 줄 알면서도 골목 안에서만 방향을 트는 것과 비슷합니다.
그렇다면 위치를 억지로 흩뿌리면 해결될까요. 저는 여기서 조금 의심했습니다. 무작정 넓히면 운에 맡기는 탐색이 되기 때문입니다. 이 논문이 말하는 방향은 흩뿌리기가 아니라 이끌기입니다. 실행에서 얻은 피드백으로 다음에 어디를 볼지 정하라는 것입니다. 실패 로그가 가리키는 곳과 성공 로그가 열어준 곳을 엮으면 같은 자리 맴돌기가 줄어듭니다. 이 대목은 다음 장의 결 이야기와 이어집니다. 어디를 고칠지와 어떻게 고칠지는 서로 다른 질문이니까요.

고치는 결이 다르면 풀리는 문제도 달라진다
같은 위치를 보더라도 고치는 손맛이 다르면 결과가 달라집니다. 어떤 시도는 조건문을 손보고 어떤 시도는 함수 경계를 옮기며 어떤 시도는 테스트가 기대하는 값을 먼저 맞춥니다. 연구가 말하는 두 번째 증상은 이 편집 다양성이 놀고 있다는 점입니다. 서로 다른 방식으로 접근하면 서로 다른 이슈가 풀리는데, 한 번의 실행 방식에만 기대면 그 보완 관계가 사라집니다.
생각해보면 사람도 비슷하게 배웁니다. 한 동료는 로그부터 파고들고 다른 동료는 호출 그래프부터 그립니다. 각자 잘 푸는 문제의 모양이 조금씩 다르죠. 혼자 일할 때는 그 차이를 한 사람이 메워야 하는데, 에이전트는 여러 번 돌릴 기회를 공짜로 가집니다. 문제는 그 기회를 같은 결로만 쓰는 데 있습니다. 여덟 번을 돌려도 여덟 번 다 같은 스타일이면 여덟 명이 아니라 한 명이 여덟 번 일한 것과 같습니다.
그래서 저는 이 숫자보다 그 뒤의 구조가 더 중요하다고 봅니다. 다양한 방법론이 서로 보완한다는 관찰은 횟수 경쟁을 그만두라는 신호이기 때문입니다. 횟수를 늘리기 전에 결을 다르게 만들라는 것이죠. 다만 결을 다르게 만드는 것만으로는 부족합니다. 서로 다른 후보가 나오면 그 가운데 하나를 골라야 하니까요. 고르는 눈이 흐리면 다양한 시도는 오히려 혼란만 키웁니다. 그렇다면 검증은 지금 어떤 상태일까요.
직접 짠 테스트에 속아 넘어가는 검증
가장 쓰라린 대목은 검증 이야기인데, 에이전트가 자기 패치에 맞춰 테스트를 직접 쓰면 그 테스트는 틀린 패치도 쉽게 통과시키기 때문입니다. 자신이 낸 답에 맞춰 문제를 다시 내는 셈이니까요. 그래서 연구는 이 현상을 검증이 오도한다고 표현하는데, 저는 이 표현이 꽤 정확하다고 느꼈습니다. 결국 고르는 과정이 믿음직하지 않으면 앞에서 벌어들인 다양성이 뒤에서 새어나가기 때문입니다.
구체적인 장면을 떠올려 보겠습니다. 오후 세 시, 에이전트가 어떤 경계 조건을 수정했는데, 이어서 그 수정을 확인하는 테스트를 스스로 작성합니다. 그런데 그 테스트는 수정된 동작만 좁게 확인하고 원래 스펙이 요구하던 넓은 조건은 건드리지 않죠. 그래서 불이 녹색으로 바뀌자 에이전트는 안심하고 제출합니다. 하지만 숨은 평가는 그 패치를 틀렸다고 판정하는데, 결국 그 녹색 불은 거짓이었던 것입니다. 저는 이 장면에서 스스로 채점하면 왜 관대해지는지 다시 생각하게 됐습니다.
여기서는 조금 조심해서 읽을 필요가 있습니다. 테스트를 자동으로 만들면 안 된다는 말이 아니기 때문입니다. 문제는 테스트를 누구의 기준으로 채점하느냐에 있습니다. 자기 패치의 논리로 자기 패치를 재면 관대해질 수밖에 없습니다. 그래서 연구는 패치마다 독립적인 잣대를 세우자고 제안합니다. 그 잣대가 바로 뒤에 나오는 되돌린 트리입니다. 패치를 뺀 상태와 넣은 상태를 견주면 자기 확신의 거품이 꺼지니까요.

실행 결과를 나침반으로 쓰는 탐색
앞에서 미뤄둔 질문으로 돌아올 차례입니다. 실행 피드백으로 탐색을 이끌면 무엇이 달라질까요. 이 연구가 제안하는 첫 번째 처치는 탐색 자체를 바꾸는 일입니다. 무작정 후보를 쌓지 말고 실행에서 돌아온 신호로 다음 행동을 정하라는 것이죠. 실패한 명령의 메시지, 통과한 테스트의 범위, 재현 스크립트의 출력이 모두 나침반이 됩니다.
이 방식이 먹히는 이유는 정보가 누적되기 때문입니다. 처음 시도가 틀렸다는 사실만 남기는 것이 아니라 왜 틀렸는지에 대한 단서가 남습니다. 어느 파일에서 막혔는지, 어느 가정 enrichment이 빗나갔는지, 어느 조건에서만 깨지는지가 기록되죠. 다음 시도는 그 기록 위에서 출발합니다. 그래서 같은 자리를 맴도는 대신 점차 유망한 가지로 이동합니다.
저희가 새로운 AI 구조를 볼 때도 숫자만 보지는 않습니다. 실제로 무엇이 바뀌었고 그 변화가 시스템에서 어떤 비용 구조를 만드는지를 함께 봅니다. 이 방법도 마찬가지 잣대로 볼 수 있습니다. 탐색을 이끄는 데 드는 추가 연산과 그로 인해 아끼는 헛걸음을 견주어야 하니까요. 연구의 답은 꽤 분명합니다. 실행으로 이끈 탐색은 여덟 개 샘플을 무작정 뽑는 기준선보다 훨씬 적은 걸음으로 더 많은 이슈를 풉니다. 그 차이가 바로 뒤에 나오는 52.8%와 48.1%라는 숫자 쌍으로 나타납니다.

되돌린 트리 위에서 패치를 가려내는 감각
이제 고르는 눈 이야기로 넘어가겠습니다. 연구가 제안하는 두 번째 처치는 각 패치를 자기 자신의 되돌린 트리에 비추어 점수를 매기는 일입니다. 말을 풀면 이렇습니다. 패치를 적용하기 전 상태로 코드를 되돌린 가지 위에서 그 패치가 무엇을 바꾸는지 다시 평가합니다. 패치가 들어간 세계와 패치가 빠진 세계를 나란히 놓고 보는 것이죠. 이렇게 하면 자기 패치에 맞춘 테스트의 후광이 옅어집니다.
이 절차를 거친 에이전트는 SWE-bench Verified에서 52.8%를 풉니다. 제가 이 논문에서 가장 흥미롭게 본 부분은 뒤에 붙은 조건입니다. 이때 쓴 에이전트 걸음 수가 여덟 개 샘플 기준선이 쓰는 걸음 수의 48.1%에 불과하다는 것이죠. 절반 이하의 걸음으로 더 나은 선택을 해낸 셈입니다. 숫자를 나란히 놓으면 의미가 또렷해집니다. 많이 돌려서 건진 것이 아니라 적게 돌리면서 정확히 고른 것입니다.
왜 이런 절약이 가능할까요. 이유는 후보를 보는 눈이 바뀌었기 때문입니다. 되돌린 트리 위에서는 패치가 진짜로 수리한 부분과 우연히 테스트를 통과한 부분이 갈라져 보입니다. 어떤 패치는 원래 코드에서는 깨지던 지점을 정확히 메우고 어떤 패치는 테스트의 빈틈 뒤에 숨죠. 전자를 고르는 능력이 좋아지면 후자를 양산하는 헛걸음이 줄어듭니다. 그래서 연산이 줄었는데도 해결률은 올라갑니다. 좋은 선택은 좋은 생성만큼이나 연산 절약 장치이기 때문입니다.
다만 여기서 물을 점이 하나 남습니다. 이렇게 고르는 눈은 규칙만 잘 짜면 되는 걸까요. 아니면 모델 자체를 가르쳐야 하는 걸까요. 연구는 규칙에서 멈추지 않고 학습으로 넘어갑니다. 사람이 고른 기준을 모델 안으로 증류하는 작업이죠. 그 첫 단계가 가중치를 둔 지도 미세 조정입니다.

자주 풀리는 문제를 덜 외우고 드문 문제를 더 외우는 학습
지도 학습으로 에이전트를 가르칠 때 흔히 빠지는 함정이 있습니다. 자주 풀리는 쉬운 이슈가 학습 신호를 독점하는 것이죠. 이미 잘하는 문제를 또 맞혀봤자 남는 것이 없는데, 손실 함수는 그 반복에 후한 점수를 줍니다. 연구는 이 쏠림을 바로잡기 위해 가중치를 둡니다. 드물게 풀리는 문제에 더 큰 가중치를 주고 흔하게 풀리는 문제의 비중을 낮추는 방식입니다.
효과는 270개로 묶은 별도 이슈 집합에서 확인됩니다. 가중치를 두기 전에는 pass@1이 31.9%였고 pass@8이 46.7%였는데, 가중치를 둔 뒤에는 pass@1이 35.2%로, pass@8이 51.1%로 올랐습니다. 한 번에 맞히는 힘과 여러 번 가운데 건지는 힘이 함께 자란 것이죠. 저는 이 숫자 쌍을 볼 때 학습이 외우는 양보다 외우는 배분을 바꿨다고 읽습니다. 어려운 사례에 눈을 더 오래 두게 만든 것입니다.
그렇다면 이 처방은 언제 통하고 언제 안 통할까요. 가중치는 분포가 치우쳐 있을 때 약발이 섭니다. 쉬운 문제에만 매달리던 학습을 어려운 문제 쪽으로 당기니까요. 반면 데이터 자체에 어려운 사례가 거의 없으면 가중치를 줘도 끌어올릴 것이 없습니다. 그래서 연구는 여기서 멈추지 않습니다. 지도 학습으로 몸을 푼 뒤 검증기를 따로 단련하는 단계로 넘어가죠. 고르는 눈을 직접 겨냥하는 학습입니다.
정답 옆에 오답을 놓고 가르친 검증기
검증기를 가르치는 가장 확실한 방법은 정답만 보여주는 것이 아니라 오답과 나란히 놓고 비교하게 만드는 것입니다. 연구가 쓴 강화 학습 목표는 그런 장면을 만듭니다. 금빛 정답과 틀린 변형을 함께 주고 어느 쪽이 진짜 수리인지 가리게 하죠. 맞히면 보상을 주고 틀리면 벌점을 주는 대신, 틀린 답을 그럴듯하게 만드는 경우를 특히 엄하게 다룹니다.
학습 뒤의 숫자는 꽤 선명합니다. pass@1이 43.0%까지 오르고 pass@8은 60.7%에 닿습니다. 검증기의 정밀도는 26.8%에서 41.7%로 뛰었고 틀린 답을 맞는다고 받아들이는 경우는 절반 이하로 줄었습니다. 통과율과 정밀도가 함께 움직였다는 점이 눈에 띕니다. 고르는 눈이 밝아지자 한 번의 시도도 좋아지고 여러 번의 시도도 좋아진 것이죠. (참고로 이 대목의 보완 평가는 세 가지 분포 밖 묶음에서도 이어지는데요. 해결률은 두 묶음에서 올랐고 정밀도는 세 묶음 모두에서 올랐습니다.)
규모를 바꿔도 흐름은 유지됩니다. 7B와 14B와 30B에서 같은 방향의 개선이 나타났기 때문입니다. 작은 검증기에서만 통하는 요령이 아니라 크기 전반에 통하는 습관이라는 뜻이죠. 실제 시스템 관점에서 보면 이 대목이 꽤 실용적입니다. 검증기는 생성기보다 가볍게 둘 수 있어 서빙 부담이 덜한데, 그 가벼운 부품의 정밀도가 올라가면 전체 파이프라인의 헛걸음이 줄기 때문입니다. 그렇다면 남은 질문은 이것입니다. 이렇게 단련된 검증기가 현장의 다양한 저장소에서도 같은 눈을 유지할까요.

크기를 바꿔도 유지되는 것과 그렇지 않은 것
분포 밖 평가는 언제나 솔직합니다. 익숙한 저장소를 떠나면 해결률이 흔들리니까요. 이 연구에서는 세 묶음을 따로 재봤는데 해결률은 두 묶음에서 올랐고 한 묶음에서는 그렇지 않았습니다. 반면 검증기의 정밀도는 세 묶음 모두에서 올랐습니다. 저는 이 엇갈림을 정직하게 읽고 싶습니다. 고르는 눈은 이사해도 통하는데 푸는 손은 이사한 집에 적응이 더 필요하다는 뜻으로 말이죠.
여기서 잠깐 뒤로 물러나면 더 큰 그림이 보입니다. 생성과 선택을 나누어 본 덕분에 어디가 튼튼하고 어디가 여린지 가려졌기 때문입니다. 선택이 튼튼하면 연산 절약으로 이어지고, 생성이 여리면 분포가 바뀌었을 때 바로 표가 납니다. 그래서 다음 연구는 생성 쪽의 적응력을 더 파고들 가능성이 큽니다. 낯선 저장소에서 낯선 실패를 만났을 때 어디부터 읽어야 하는지, 어떤 명령으로 재현해야 하는지 같은 손놀림 말이죠.
제가 앞으로 보고 싶은 것은 세 가지입니다. 첫째, 실행으로 이끄는 탐색이 긴 작업에서도 길을 잃지 않는지 궁금합니다. 걸음이 길어지면 초반의 단서가 희석되니까요. 둘째, 되돌린 트리 점수가 테스트가 빈약한 저장소에서도 같은 변별력을 내는지 보고 싶습니다. 잣대 자체가 흔들리면 선택도 흔들리니까요. 셋째, 가벼운 검증기를 실제 서빙 라인에 올렸을 때 지연과 처리량이 어떻게 바뀌는지 궁금합니다. 연구실 점수와 밤샘 운영은 다른 문제이니까요. 이런 질문을 손에 쥐고 있으면 이 논문은 숫자 모음이 아니라 다음 실험을 위한 지도로 읽힙니다.
참고 자료
- Teaching Agents to Code Reliably · arxiv.org
리뷰 원문