-
에러가 없으면 에이전트는 멈추지 않는다: 전진 본능과 브레이크를 설계에 넣는 법
AI 2026. 11. 4. 21:02반응형에이전트에게 데이터 1,000건을 변환하라고 시켰다고 하자. 프로세스는 exit code 0으로 끝나고, 요약에는 "99%를 성공적으로 변환했습니다. 다음 단계로 넘어가겠습니다"라고 적힌다. 이 문장에서 가장 중요한 정보는 빠졌다. 나머지 10건이 무엇이고 왜 실패했는지 아무도 모른다.
에이전트가 스택 트레이스를 읽고 고치는 솜씨는 이제 놀랍지 않다. 어려운 문제는 그 반대편에 산다. 아무것도 터지지 않았는데 결과가 틀린 경우다. 이 글은 그 실패 모드를 다룬다. 이름은 한 연구자의 강의에서 빌리고, 사례는 공개 저장소의 PR에서 가져온다.
요약
- 에이전트는 에러 메시지를 신호 삼아 잘 고친다. 신호가 없는 오류 앞에서는 그대로 다음 단계로 간다. 안상진 교수는 이 경향을 "전진 본능"이라 불렀다.
- 사람의 감각에만 브레이크를 맡기면 사람이 지켜보는 동안만 작동한다. 일부는 코드로 옮겨야 한다.
- 옮기기 쉬운 브레이크는 세 가지다. 분모 대조, 너무 좋은 결과 경보, 계획 대비 실행 대조.
- 가장 강한 브레이크는 결과를 보기 전에 건다. Memento의 사전등록 평가는 관문을 못 넘자 비교 실험을 아예 돌리지 않았다.
- 브레이크에는 비용이 든다. 같은 평가가 두 번 연속 답을 내지 못했다.
1. 전진 본능이라는 이름
인제대학교 의과대학 약리학교실의 안상진 교수는 연구자 대상 AI Agent 입문 강의에서 실전 경험을 정리하며 이 현상에 이름을 붙였다. 에이전트는 다음 단계 아이디어를 내고 일을 앞으로 밀어붙이는 데 탁월하다. 그만큼 놓치는 것도 많다. 통계 분석의 전제 조건을 확인하지 않고, 100% 처리를 끝까지 챙기지 않으며, 이상한 낌새를 느껴도 지금까지 한 일을 갈아엎는 대신 "일단 전진하시죠" 쪽으로 간다.
강의가 든 장면은 세 가지다. 데이터 변환이 99%에서 멈췄는데 남은 1%를 찾지 않고 훌륭하다며 넘어간다. 밸리데이션이 100%가 나왔는데 설계 오류나 누수를 의심하지 않고 좋은 결과라며 넘어간다. 분석 방법을 전부 시도하라고 지시했는데 몇 개 해 보고 잘 나왔다며 넘어가려 해서, 결국 "1번부터 10번까지 전부 다 해"라고 다시 명령했다.
강의의 진단에서 눈여겨볼 대목은 경계선의 위치다. 필드명 혼동 같은 문제는 에이전트가 오류 메시지를 직접 보기 때문에 대부분 알아서 해결했다. 문제는 오류가 발생하지 않는 경우였다. 강의는 사람의 역할을 "가속 페달이 눌려 있는 그런 느낌"의 차 옆에서 계기판을 보며 브레이크를 잡는 일로 비유한다.
🔍 클릭하여 확대flowchart TD Run["에이전트 실행"] --> Exit{"실패 신호가 나왔나?"} Exit -- "예: 예외, 빌드 실패, 테스트 실패" --> Fix["메시지를 읽고 고친다"] Fix --> Run Exit -- "아니오: exit 0" --> Truth{"결과가 실제로 맞나?"} Truth -- "맞다" --> Next["다음 단계"] Truth -- "틀렸다" --> Silent["틀린 결과 위에 다음 단계를 쌓는다"] Silent -.-> Next Brake["브레이크: 사람의 위화감, 기계 게이트"] -.-> Truth연구 현장의 관찰이지만 소프트웨어 개발자에게도 낯익다. 마이그레이션 스크립트는 정상 종료했는데 일부 행이 조용히 건너뛰어졌다. 테스트는 전부 통과했는데 새 코드를 지나는 테스트가 하나도 없다. 리뷰 봇은 승인했는데 근거로 든 파일이 변경 목록에 없다. 셋 다 프로세스 입장에서는 성공이다.
2. 왜 하필 조용한 오류에서 무너질까
여기부터는 강의 내용이 아니라 이 글의 해석이다. 에이전트 루프는 텍스트로 돌아오는 피드백에 반응한다. 스택 트레이스는 강한 신호다. 위치와 원인 후보를 알려 주고, 고친 뒤 다시 돌리면 성공 여부까지 바로 확인된다. 이 짧은 고리 안에서 에이전트는 사람보다 끈질기다.
조용한 오류에는 그 고리가 없다. "99% 성공"이라는 문자열은 도구가 돌려준 사실이고, 그 문자열을 나쁜 소식으로 읽으려면 바깥 지식이 필요하다. 이 데이터에서 1%가 왜 중요한지, 이 문제에서 100%가 왜 불가능에 가까운지는 출력 어디에도 적히지 않는다. 신호가 없는 자리에 신호를 만들어 넣는 일이 브레이크 설계의 전부다.
사람이 지켜보는 방식에는 한계가 분명하다. 강의도 사람이 코드를 일일이 뜯어보는 대신 에이전트가 단계마다 읊는 설명과 결과물에서 위화감을 잡아낸다고 설명한다. 이 감각은 값지지만 그 분야를 아는 사람이 화면 앞에 앉았을 때만 작동한다. 에이전트를 여러 개 돌리거나 밤새 돌리는 순간 계기판을 보는 눈이 사라진다.
3. 사례: 크래시 없이 틀린 판정 2건
공개 저장소 Memento는 AI 에이전트의 기억을 저장하고 검색하는 MCP 서버다. 새 기억이 낡은 기억을 대체하는지 LLM으로 판정해 후보를 올리는 기능이 있다. 2026년 11월 2일 운영에서 이 스캔을 켠 뒤 처음 나온 confirmed 후보 2건은 모두 오판정이었다.
새 기억은 다른 에이전트 환경(Codex)의 워크플로를 요약한 LLM 산출물이었고, 낡은 기억으로 지목된 쪽은 사용자가 Claude Code 환경에 직접 남긴 지침이었다. 판정 LLM은 경로가
~/.claude에서~/.codex로 바뀌었다는 점을 대체 근거로 들었다. 실제로는 두 환경이 공존한다. 후보 단계에서 거절됐지만, 이슈 기록대로 승인했다면 유효한 지침이 검색과 주입에서 빠졌다.엄밀히 말해 이 사례는 강의의 전진 본능과 같은 현상이 아니다. 작업을 밀어붙이는 코딩 에이전트가 아니라 한 번 답하는 판정기의 오류다. 실패의 모양은 같다. 예외도, 타임아웃도, 파싱 오류도 없었다. 시스템은 정상 동작했고 답은 그럴듯한 이유를 달고 틀렸다.
수정 PR #1623에서 눈여겨볼 부분은 수정 자체보다 검증 방식이다. 시스템이 만든 요약이 사용자 기억을 대체하는 쌍을 짝짓기에서 빼고, 판정 프롬프트에 공존 기준을 넣었다. 그 뒤 세 가지를 확인했다.
- 실패 재현. 수정 전 코드에서 새 테스트 5건이 실패함을 먼저 보였다.
- 반증(mutant).
canSupersede를return true로 바꾸면 테스트 3건이 실패한다. 테스트가 실제로 이 동작을 잡는지 일부러 망가뜨려 확인했다. - 운영 DB 사본 재생. 사본에서 오판정 2쌍은 후보에서 빠지고 기존 compatible 12쌍은 그대로 남았다.
분모를 숨기지 않은 점도 기록할 만하다. 새 프롬프트 실호출은 Gemini 429 때문에 12건 중 3건만 쟀다고 PR 본문에 적었다. "3건 모두 통과"만 적었다면 전진 본능이 좋아하는 문장이 됐다.
4. 세 장면을 게이트로 옮기기
강의의 세 장면은 뜻밖에 코드로 옮기기 쉽다. 각각 분모 대조, 상한 경보, 계획 대비 실행 대조로 바뀐다. 다음은 표준 라이브러리만 쓴 최소 형태다.
class Stop(Exception): """exit 0으로 끝나도 다음 단계로 넘어가면 안 되는 상태.""" def check_denominator(total: int, converted: int, rejected: list[str]) -> None: unaccounted = total - converted - len(rejected) if unaccounted: raise Stop(f"행방불명 {unaccounted}건: 입력 {total}, 변환 {converted}, 격리 {len(rejected)}") if rejected: raise Stop(f"격리 {len(rejected)}건 원인 확인 필요: {rejected[:5]}") def check_too_good(name: str, score: float, ceiling: float) -> None: if score >= ceiling: raise Stop(f"{name}={score:.3f}: 상한 {ceiling} 이상, 누수와 라벨 오염부터 확인") def check_coverage(planned: set[str], executed: set[str]) -> None: missing = planned - executed if missing: raise Stop(f"계획했으나 돌리지 않은 조건: {sorted(missing)}")check_denominator(1000, 990, [])는 이렇게 멈춘다.Stop: 행방불명 10건: 입력 1000, 변환 990, 격리 0세 함수 모두 판단을 하지 않는다. 조용하던 상황을 에러로 바꿀 뿐이다. 여기에 이 패턴의 요점이 담겼다. 예외가 올라오면 에이전트는 자기가 잘하는 고리로 돌아간다. 격리된 행을 열어 보고, 누수를 찾고, 빠진 조건을 돌린다. 브레이크가 에이전트의 약점을 고치는 대신 강점이 작동하는 자리로 문제를 옮긴다.
쓸 때 조심할 점도 분명하다. 격리 건수 0을 강제하면 원래 지저분한 데이터에서 파이프라인이 영영 멈춘다. 허용치를 두려면 그 값을 코드 밖 설정으로 빼고 누가 왜 정했는지 남기는 편이 낫다. 에이전트가 허용치를 고칠 권한을 가지면 브레이크는 장식이 된다.
check_too_good의 상한은 문제마다 다르고 오경보도 잦다. 경보가 울려도 결과가 틀렸다는 뜻은 아니다. 확인 없이 넘어가지 못한다는 뜻이다.게이트가 게이트로서 일하는지도 따로 확인해야 한다. 앞서 본 mutant 방식이 여기에 맞는다. 일부러 10건을 버리는 입력을 넣어 멈추지 않으면 그 게이트는 없는 셈이다.
5. 가장 강한 브레이크는 결과를 보기 전에 건다
세 게이트는 실행 뒤에 작동한다. 평가와 실험에는 더 이른 자리가 있다. Memento의 수면 통합(sleep consolidation) 평가가 그 예다. 에피소드 기억을 통합하는 방식 세 가지를 비교하려 했다. A는 통합 없음, B는 묶기만 함, D는 LLM 요약이다.
consolidation-replay-v2는 입력을 만들기 전에 관문부터 커밋했다. G1은 비교 가능한 짝이 16개 이상, 층마다 2개 이상이어야 한다는 조건이다. 정답은 서로 다른 모델의 판정자 2명과 조정자 2명이 만들었다. 두 판정이 모두 확정되고 두 조정자가 모두 동의한 시점만 gold로 삼았고, 나머지는 보류로 뺐다.결과는 다음과 같았다.
층 시점 gold test complete conditional 22/24 5/7 task_noise 22/24 5/7 generalization 15/24 0/7 source_preservation 14/24 0/7 correction 13/24 0/7 합계 86/120 10/35 G1은 정답지 단계에서 이미 무너졌다. 결론은
inconclusive였고 A, B, D arm은 실행하지 않았다. 모델 호출 0회, 운영 변경 없음. PR이 적은 이유가 이 글의 핵심이다.- G1 실패로 결론이 이미 정해졌다. 산출물 단계의 보류는 짝을 더 줄일 뿐이다.
- 프로토콜이 결손 층을 뺀 유리한 하위 집합 계산을 금지한다.
- 실행하면 complete test 10시퀀스가 시스템 결과에 노출돼 다음 판본의 held-out으로 쓰지 못한다.
전진 본능이라면 정반대로 움직일 자리다. 데이터가 10개라도 있으니 일단 돌려 보자, 두 층만이라도 비교해 보자, 숫자가 나오면 그때 판단하자. 이런 제안은 하나하나 합리적으로 들린다. 그 합리성을 미리 막아 두는 장치가 사전등록이다. 결과를 보기 전에 정한 규칙은 결과에 끌려가지 않는다.
🔍 클릭하여 확대flowchart LR Reg["관문과 금지 사항을 먼저 커밋"] --> Input["입력과 판정 패킷 생성"] Input --> Panel["블라인드 판정과 조정"] Panel --> Gate{"G1: 짝 16 이상, 층마다 2 이상"} Gate -- "통과" --> Run["A, B, D 비교 실행"] Gate -- "미달" --> Stop["inconclusive 기록, 비교 실행 안 함"]이 사례를 미담으로만 읽으면 곤란하다. 한계와 비용이 함께 기록됐다.
- 사람 gold가 아니다. 판정과 조정은
agent_panel결과이고 사람이 확정한 정답이 아니다. - 독립성이 완전하지 않다. 조정자 한 명과 판정자 한 명의 공급사가 같고, 등록과 입력 문장과 합산을 같은 작성자가 맡았다.
- 사전 추정이 크게 빗나갔다. 프로토콜은 test complete 비율을 평균 0.667로 추정했으나 실제는 0.286이었다.
- 답을 얻지 못했다. 앞선 v1도 gold 58/90, complete 10/25로 inconclusive였다. 두 번의 평가 뒤에도 어느 방식이 나은지는 모른다. 운영은 현행 D를 그대로 유지한다.
브레이크는 공짜가 아니다. "모른다"를 결론으로 받아들이는 일에는 시간과 판정 비용이 들고, 원하던 답도 오지 않는다. 그 대가로 얻는 것은 근거 없는 숫자로 운영을 바꾸지 않았다는 사실 하나다. 이 거래가 언제나 옳지는 않다. 되돌리기 쉬운 결정이라면 작게 실행해 보는 편이 낫다. 기억 시스템처럼 틀린 결과가 조용히 오래 남는 영역이라면 값을 치를 만하다.
6. 끝까지 기계로 옮기지 못하는 것
게이트는 미리 상상한 실패만 잡는다. 강의가 말하는 위화감은 아직 이름 없는 이상함을 알아채는 능력이다. 분포가 어딘가 이상한 그래프, 설명이 매끄러운데 앞뒤가 맞지 않는 요약, 너무 수월하게 끝난 작업. 새 게이트는 대개 이런 위화감에서 태어난다. 사람이 한 번 잡은 실패를 다음부터 기계가 잡는 순서다.
되돌리는 결단도 사람 몫으로 남는다. 강의는 에이전트가 문제의 출발점을 역추적해 지금까지 한 일을 "싹 다 갈아엎고" 다시 하자는 판단을 잘 하지 못한다고 지적한다. 오류 위에 살을 붙이는 방향이 언제나 더 쉬워 보이기 때문이다. 어디까지 폐기할지는 비용 판단이고, 그 비용을 책임지는 사람이 정해야 한다.
강의에서 가장 무거운 표현은 "검토할 수 없는 결론"이다. 에이전트는 내가 평가할 능력이 없는 분야에서도 그럴듯한 결과를 만들어 낸다. 아는 범위를 조금 넘을 때는 설명을 요구하고 자료를 찾아 공부하며 메우면 된다. 범위를 크게 넘으면 그대로 쓰면 안 된다. 게이트와 사전등록은 이 경계를 넓혀 주지만 없애 주지는 못한다. 작업을 검토 가능한 크기로 나누는 일이 중요한 이유도 여기에 있다. 검증 가능한 목표를 정의하는 방법은 SQuaRE 기반 품질 목표 글에서, 한 번의 성공을 믿지 않고 반복 측정하는 방법은 자기 교정 하네스 글에서 다뤘다.
핵심 정리
- exit code 0은 프로세스가 끝났다는 뜻이다. 일이 맞게 끝났다는 뜻이 아니다.
- 조용한 오류를 에러로 바꾸면 에이전트는 다시 자기가 잘하는 고리로 돌아온다.
- 분모, 상한, 계획 대비 실행은 지금 파이프라인에 바로 넣을 만큼 싸다. 허용치를 고칠 권한은 에이전트에게 주지 않는다.
- 게이트도 코드다. 일부러 망가뜨려 멈추는지 확인한다.
- 평가의 관문과 금지 사항은 입력을 만들기 전에 커밋한다. 관문에 걸리면
inconclusive를 결론으로 받아들인다. - 위화감, 폐기 결단, 검토 불가능한 결론의 책임은 사람에게 남는다.
참고 자료
- 연구자를 위한 AI Agent 입문 강의, 안상진 (MahlerLab): 전진 본능 개념과 세 장면 (45:01~51:36, 인용은 자동 자막 기준)
- Memento #1622, #1623: 대체 후보 오판정과 수정
- Memento #1571: consolidation-replay-v1 inconclusive 동결
- Memento #1626, v2 판정 합산 보고서: 사전등록 관문과 gold 단계 inconclusive
반응형'AI' 카테고리의 다른 글
에이전트를 늘릴수록 왜 실패할까: 앤트로픽이 멀티 에이전트를 단일 루프로 합치라고 권고한 이유 (0) 2026.11.03 프롬프트 대신 코드베이스를 고쳐라: Cursor 1,000개 PR 자동 머지와 에이전트 신뢰 곡선 (0) 2026.10.29 Playwright를 두고 왜 Ego Lite가 등장했을까: 드라이버에서 에이전틱 브라우저 런타임으로의 진화 (0) 2026.10.23 마이크로서비스 게이트웨이를 넘어: 에이전트 게이트웨이와 MCP 호출 통제 아키텍처 (0) 2026.10.22 AI 클러스터에서 TCP의 종말: GPU 아이들링을 끝내는 차세대 전송 프로토콜 Homa (0) 2026.10.20