에이전트가 '완료했습니다'라고 하면 믿으면 안 되는 이유: 교차검수 체제를 만든 이야기
TL;DR
- LLM 에이전트의 작업 완료 보고는 거짓말이 아니라 자기 변경분을 확인 안 한 착각일 수 있습니다. 실제로 “수정 완료” 보고를 받고 열어봤더니 파일이 안 바뀌어 있었던 적이 여러 번 있었습니다.
- 해결책은 에이전트를 더 똑똑하게 만드는 게 아니라, 서로 안 믿게 만드는 것이었습니다. 워커의 보고를 비서가 git diff로 검증하고, 중요한 결정은 독립 서브에이전트 3명이 병렬로 교차검수하게 했습니다.
- 그 결과 dead code를 잡아내기도 했지만, 검수자도 틀립니다. 이번에는 기획 에이전트가 지시 본문을 잘못 읽고 엉뚱한 일을 해놓고 “완료” 처리하는 사건이 그대로 재현돼서, 그 기록까지 글에 넣었습니다.
들어가며
LLM 에이전트한테 업무를 시키기 시작하면 처음 마주치는 환상이 있습니다. 에이전트는 자기가 한 일을 아주 그럴듯하게 요약한다는 겁니다. “L703 수정 완료”, “테스트 통과”, “배포 준비됐습니다” 같은 문장들요.
문제는 그 문장이 사실과 다를 때가 있다는 겁니다. 악의가 아니라, 모델이 자기가 방금 뭘 바꿨는지 실제로 파일을 다시 읽어보지 않고 요약을 쓰기 때문입니다. 사람도 회고 없이 보고서 쓰면 비슷한 실수를 하죠. 차이는 사람보다 훨씬 유창하게 틀린다는 겁니다.
저는 혼자 개발할 때도 에이전트 여러 명을 역할별로 나눠서 씁니다: 기획(pm), 개발(dev), 인프라(infra), 검수(qa), 그리고 조율을 맡는 비서(ops). 칸반 보드에 카드를 올리고, 각 에이전트가 카드를 가져가 작업하고, 끝나면 complete 처리하는 구조입니다. 이 글은 그 과정에서 “에이전트의 말을 어디까지 믿을 것인가”를 고민하며 만든 검증 체계에 대한 기록입니다. 물론 제 관찰 하나짜리 사례(N=1)이고, 환경에 따라 다를 수 있습니다.
사건 1: “완료”인데 파일은 안 바뀌어 있었다
스펙 6곳을 수정하는 카드를 dev 에이전트에게 준 적이 있습니다. 에이전트는 작업을 끝내고 카드를 complete 처리했고, 요약에는 특정 라인 번호를 언급하며 “수정 완료”라고 적혀 있었습니다.
열어봤습니다. git diff를 돌렸습니다. 스펙 6곳 중 5곳만 반영되어 있었고, 요약이 가리키던 라인은 실제 변경과 어긋나 있었습니다.
여기서 중요한 건 두 가지입니다.
- 요약은 거짓말이 아니라 미확인이었다는 점. 모델은 “내가 뭘 고쳤는지”를 메모리로 재구성해서 씁니다. 실제 diff와 대조하지 않으면 어긋날 수밖에 없습니다.
- 그리고 저도 diff를 열어보기 전까지는 몰랐다는 점. 즉 “완료”라는 상태값 자체는 아무 정보가 없습니다.
이 경험 이후 비서(ops) 에이전트의 룰에 한 단계를 박았습니다.
워커가 complete 처리하면 바로 보고하지 말 것. 먼저
git diff로 (1) 요약이 가리킨 변경이 실재하는지 (2) 요구 지점이 전부 반영됐는지 (3) 요청 안 한 코드(불필요한 추상화, 유틸 함수)가 끼었는지 확인 후 보고할 것.
간단하지만 효과가 컸습니다. “에이전트가 끝냈다”에서 “에이전트가 끝냈다고 주장하며, 검증으로 확인됐다”로 기본값이 바뀌었습니다.
사건 2: 서로 안 믿게 하니 dead code가 잡혔다
두 번째는 더 흥미로운 케이스입니다. 어떤 기능(PR #87441, watch 툴)을 구현하며 후크 설정 값 하나(agent_busy_after)가 코드베이스에 남게 됐는데, 정작 실제 로직은 다른 이름으로 구현되어 있었습니다. 즉 설정만 남고 동작이 연결 안 된 dead code였죠.
작성자 에이전트 입장에선 당연히 “내 코드에 dead code가 있다”고 스스로 말할 이유가 없습니다. 그래서 방식을 바꿨습니다. 검수용 서브에이전트 3명을 병렬로 띄우고, 서로의 결론을 보여주지 않았습니다. 각자 독립적으로 같은 대상을 검토하고 결론만 모으는 겁니다.
이렇게 하면 두 가지가 생깁니다.
- 한 명의 오류가 전체 판정을 오염시키지 않습니다. LLM 리뷰어는 그럴듯한 허위 결함을 내기도 하는데(실제로 엉뚱한 경로를 보고 허위 결함을 낸 적이 있습니다), 다수 독립 판정이면 걸러집니다.
- 단일 리뷰어의 맹신이 사라집니다. 한 명이 PASS라고 했을 때 “3명 중 3명 PASS”와 “1명 PASS”는 신뢰도가 완전히 다릅니다.
그 결과 위 dead code는 잡혔고, 수정 후 테스트 19개가 통과하는 상태로 PR이 정리됐습니다. 포인트는 모델을 바꾼 게 아니라 판정 구조를 바꿨다는 겁니다.
사건 3: 검수자도 틀린다 (그리고 이 글이 써지게 된 직접 계기)
“교차검수 만능론”을 깨는 사건도 있었습니다. 최근 블로그 초안 작업을 기획(pm) 에이전트에게 줬습니다. 카드 본문에는 주제와 소재 세 가지, 산출물 형식까지 분명히 적어뒀습니다.
pm의 행동은 3단계로 실패했습니다.
- 본문을 읽지 않고 오판: 본문에 없는 단어(“번역 오류로 깨졌다”)를 근거로 작업 불가 판정을 걸었습니다.
- 정정 후에도 환각에 매달림: 원문을 그대로 재인용해줬는데도, 처음 지어낸 단어들에 얽힌 채 전혀 다른 작업(대시보드 검수)을 스스로 정의했습니다.
- 산출물 없이 complete: 요청된 산출물(블로그 아웃라인)은 0줄인 상태로 “완료” 처리했습니다.
교차검수 체계를 만든 제가 직접 당했으니 웃기기도 합니다만, 교훈은 명확합니다.
검증 체계는 검증자를 포함하지 않으면 완성되지 않는다. pm의 산출물을 ops가 열어보지 않았다면 “완료됐다”는 상태를 그대로 믿고 다음 단계로 넘어갔을 겁니다.
덧붙이면, 이 사건에서 배운 운영상 교훈도 있습니다. 무료/소형 모델을 역할 에이전트로 쓸 때는 카드 본문에 (1) 하는 일 (2) 안 하는 일 (3) 산출물의 실물 형태(파일 경로까지)를 명시하는 게 좋습니다. 유창한 모델일수록 부족한 정보를 창작으로 메우는 경향이 있는데, 창작할 여지를 줄이는 게 프롬프트 엔지니어링보다 효과적이었습니다.
칸반 상태 설계에 박은 검증 포인트
프로세스 차원에서는 상태 전이에 검증을 심었습니다.
| 상태 | 함정 | 설계 |
|---|---|---|
| ready | 카드를 ready로 만드는 순간 dispatcher가 즉시 실행을 시작함 | 사람 확인이 필요한 작업은 blocked로 생성, 승인 후 unblock |
| running → done | 워커가 complete하면 곧바로 done으로 믿게 됨 | complete 후 ops가 실물(diff/파일/빌드) 검증, 통과 시에만 보고 |
| blocked | blocked 사유를 맹신하게 됨 | 사유의 전제(git log, 파일 존재)를 먼저 검증, 거짓이면 해제 |
특히 blocked가 재밌는 지점인데, 에이전트는 “막혔다”고 보고할 때 그 이유까지 지어내는 경우가 있습니다. 실제로 존재하지 않는 충돌 대상을 사유로 달아놓고 6시간 넘게 하위 작업들이 멈춰있었던 적이 있습니다. blocked는 상태일 뿐이고, 사유는 또 하나의 주장이라는 걸 전제로 깔아뒀습니다.
정리: 신뢰는 디폴트가 아니라 검증의 결과다
세 사건을 관통하는 한 줄입니다.
에이전트의 보고는 데이터가 아니라 주장이다. 주장은 검증된 만큼만 신뢰한다.
- 워커의 “완료” → diff로 검증
- 리뷰어의 “PASS” → 독립 다수 판정으로 검증
- 검수자·기획자의 산출물 → 담당자가 실물로 확인
역설적이게도, 에이전트를 서로 안 믿게 만들수록 전체 시스템은 더 잘 돌아갔습니다. 서로 믿게 하면 한 명의 착각이 파이프라인을 타고 전파되고, 안 믿게 하면 착각이 단계마다 걸러집니다.
물론 비용이 있습니다. 검증 단계마다 토큰과 시간이 들고, 간단한 작업엔 과한 면이 있습니다. 저는 “변경이 외부에 노출되는 작업(커밋, PR, 배포, 발행)”은 전부 검증 게이트를 태우고, 그 외는 샘플링으로 점검합니다. 완벽주의보다는, 틀렸을 때 되돌리기 어려운 순간 앞에 게이트를 놓는 것이 현실적인 타협이었습니다.
다음에 에이전트에게 일을 시키실 때, “완료했습니다”라는 말을 들으면 한 번쯤 물어보시길 권합니다. “diff 좀 보여줄래?”
부록: 이 글의 모든 사례는 2026년 7월부터 8월 사이 hermes-agent 기반 멀티에이전트 워크플로우 운영 중 실제로 있었던 일이며, PR 번호(#87441)와 상태 설계는 실제 값을 그대로 적었습니다. 실패한 부분(기획 에이전트의 3단 실패 포함)도 과장 없이 그대로 담았습니다.
Comments