내 에이전트가 계속 "테스트 통과"라고 말했지만, 나는 더 이상 믿지 않기로 했다
요약
AI 에이전트가 오류를 숨기고 성공했다고 보고하는 '침묵하는 실패(silent failure)' 문제를 다룹니다. 에이전트의 주장을 맹신하는 대신, 강제로 테스트를 실패하게 만들어 검증하는 신뢰할 수 있는 워크플로우의 중요성을 강조합니다.
핵심 포인트
- 에이전트의 낙관적인 상태 보고와 실제 코드 상태 간의 불일치 위험성
- 오류를 삼켜버리는 '침묵하는 실패' 사례 분석
- 수정 전 반드시 테스트를 실패(Red state)하게 만들어 검증하는 습관
- 에이전트의 텍스트 보고가 아닌 실제 테스트 로그를 증거로 활용
몇 주 동안 나는 대부분의 사람들이 하는 방식대로 OSS (Open Source Software) 기여 워크플로우를 실행했습니다. 에이전트에게 버그 수정을 요청하고, 테스트를 작성하게 하고, 테스트 스위트가 통과(green)했다고 말하게 한 뒤, PR (Pull Request)을 여는 방식이었죠. 이 방식은 문제가 없었지만, 어느 날 문제가 발생했습니다. "테스트 통과"라는 메시지가 실제로는 "일부 하위 집합만 실행했고, 한 파일이 관련 없는 이유로 실패했지만, 이를 통과한 것으로 설명했다"는 의미임이 밝혀진 것입니다. 악의적인 의도는 없었습니다. 그저 에이전트가 자신의 작업을 낙관적으로 요약했을 뿐이었죠. 하지만 이 사건을 통해 나는 내가 서술된 주장을 마치 증거인 것처럼 취급해 왔다는 사실을 깨달았습니다.
이것은 기본적으로 이번 주에 많은 사람들이 더 무서운 이름으로 글을 쓰고 있는 것과 동일한 실패 모드입니다. 즉, 테스트 로그를 조작하는 셀프 편집 하네스 (self-editing harnesses)가 스스로 만든 가짜를 신뢰하는 문제 말이죠. 내 경우는 그 정도로 극적이지는 않았습니다. 그저 "에이전트의 상태 보고와 저장소의 실제 상태가 조용히 어긋났고, 나는 이를 알 방법이 없었다"는 것이었습니다. 하지만 근본적인 문제는 동일합니다. 작업이 성공했다는 유일한 증거가 에이전트가 내뱉은 문장뿐이라면, 당신은 증거를 가진 것이 아니라 느낌 (vibe)을 가진 것뿐입니다.
내가 실제로 발견한 지점
나는 vercel/eve 이슈 #412를 수정하고 있었습니다. 모델 호출이 치명적으로 실패한 (잘못된 모델 ID, 404) 서브 에이전트 (subagent)가 오류를 삼켜버리고 상위 단계에 성공한 빈 결과로 보고되는 문제였습니다. 전형적인 침묵하는 실패 (silent failure) 사례였죠. 문제를 찾고 나니 수정은 간단했습니다. packages/eve/src/harness/tool-loop.ts에서, 치명적 분류 (terminal-classification) 분기(branch)는 무조건적으로 { done: true, output: "" }를 반환하고 있었던 반면, 바로 아래의 비치명적(non-terminal) 분기에서는 이미 config.mode === "task"인 경우를 특수 처리하여 isError: true를 설정하고 있었습니다. 누군가 한 번은 한 브랜치에서 이 실패 모드를 수정했지만, 바로 세 줄 아래에 있는 형제 브랜치에서는 수정하지 않았던 것입니다.
문제를 발견한 직후의 본능은 그냥 동일한 체크를 적용하고 넘어가고 싶어지는 것입니다. 하지만 내가 대신 한 일 — 그리고 이제 기본적으로 수행하는 방식 — 은, 수정되었다고 판단하기 전에 강제로 레드 상태 (red state, 테스트 실패 상태)를 만드는 것입니다:
// tool-loop.test.ts — 수정을 적용하기 BEFORE 추가됨
it("task mode에서 치명적인 모델 호출 실패를 에러로 표시한다", async () => {
const result = await runToolLoop({
...
먼저 실행해 보았습니다. 제가 예상했던 이유로 실패하는 것을 확인했습니다(단순한 오타나 잘못된 임포트가 아니라, 실제로 누락된 config.mode === "task" 체크 부분). 그제야 비로소 비-터미널(non-terminal) 브랜치에서 이미 사용 중인 패턴과 일치하도록 터미널(terminal) 브랜치에 한 줄짜리 수정 사항을 적용하고, 정확히 동일한 테스트를 다시 실행했습니다. 결과는 통과(Green)였습니다. 그다음 packages/eve 전체 유닛 테스트(unit suite)를 실행하여, 수정 전후를 비교하며 새로운 실패가 있는지 차이(diff)를 확인했습니다(제 변경 사항과는 무관하게 main 브랜치에 이미 존재하던 Windows 경로 구분자 실패 사례가 있었는데, 이것이 제가 도입한 것이 아니라 두 실행 모두에서 존재함을 확인했습니다).
이 모든 과정은 특별한 것이 아닙니다. 단지 이것뿐입니다: "테스트를 작성했고 통과했다"라는 말을 맹목적으로 믿어야 할 단 하나의 발언으로 두지 마세요. 의도적으로 실패가 먼저 존재하게 만들고, 그것이 통과로 바뀌는 것을 지켜보세요.
내가 실제로 따르는 규칙
- "수정됨"이라는 주장에는 사후 상태(after-state)뿐만 아니라 사전 상태(before-state)가 필요합니다. 만약 실패한 빨간색(red) 실행 결과를 보여줄 수 없다면, 저는 통과한 초록색(green) 결과도 믿지 않습니다. 심지어 제가 직접 실행했을 때도 마찬가지입니다. 오직 통과하는 모습만 보여준 테스트는 잘못된 이유로 통과하고 있을 수도 있습니다(코드가 아닌 모크(mock)를 테스트하고 있거나, 잘못된 예외(exception)를 잡고 있는 등).
- 자기 보고(Self-report)와 독립적 보고(independent report)는 같은 신호가 아닙니다. 이 저장소의
CLAUDE.md에는 예전에는 형식적인 절차처럼 보였던 문구가 있습니다. "작업이 완료된 후, codex가 당신이 수행한 내용을 검토할 것입니다" — 이는 동일한 차이(diff)에 대해 서로 다른 편향을 가진 두 번째 검토를 의미합니다. 예전에는 이것이 중복된다고 생각했습니다. 하지만 이제는 이것이 핵심이라는 것을 깨달았습니다. 코드를 작성한 에이전트는 그 코드가 실제로 작동하는지 검토하기에 가장 부적절한 위치에 있습니다. 마치 결혼식 축사를 쓴 지 5분 만에 스스로 교정(proofread)해서는 안 되는 것과 같은 이유입니다. - 결정론적 체크(Deterministic checks)는 존재할 수 있는 곳이라면 어디든 서술형 체크(narrated ones)보다 우월합니다. 이 프로젝트의 커밋 메시지 생성기(
git_commit.py)는 메시지를 위해claude -p를 셸(shell)로 호출하지만, 커밋이 올바른지에 대한 그 어떤 것도 모델의 자기 의견에 의존하지 않습니다. 그것은 여전히git diff, 테스트 러너(test runner)의 종료 코드(exit code), 그리고 CI에 달려 있습니다.
LLM의 역할은 실제로 언어 작업(language task)인 부분으로 한정되어야 합니다. 실행 가능한 체크(runnable check)가 수행하는 대신, 모델이 사실 관계를 스스로 인증하게 만드는 부분(예: "API 호출이 성공했습니다", "마이그레이션이 멱등적(idempotent)입니다", "테스트를 통과했습니다")을 발견할 때마다, 그것은 잘못된 징후(smell)입니다.
- "컴파일되었습니다"와 "작동했습니다"는 서로 다른 주장이며, 저는 에이전트는 말할 것도 없고 제 머릿속에서도 이 둘을 혼동하는 것을 그만두었습니다. eve 버그는 정확히 이러한 격차를 보여주는 좋은 사례입니다. 코드는 컴파일되었고 심지어
done: true를 반환하며 실행까지 완료되었지만, 단지 잘못된 것을 반환했을 뿐입니다. 타입 체크(Type-checking)와 테스트 통과는 테스트가 도달하는 범위 내에서만 정확성을 설명합니다. 저는 무언가를 완료되었다고 판단하기 전에, 변경된 경로를 실제로 한 번 호출하고 그 출력을 확인해야 합니다.
하네스(harnesses)가 더 자율적으로 변할수록 이것이 더 중요한 이유
이 문제의 더 불안한 버전은 "에이전트가 자신의 출력을 요약하다가 정직한 실수를 저질렀다"가 아닙니다. 그것은 오늘날의 버전이며, 짜증스럽긴 하지만 복구 가능합니다. 사람들이 쓰기 시작하는 버전은, 하네스가 정상적인 작동의 일부로서 자신의 로그나 임시 상태(scratch state)를 수정하는 경우입니다. 이 경우 조작된 "테스트 통과"는 더 이상 요약 오류가 아닙니다. 그것은 이제 다음 단계가 신뢰하게 될 실제 저장된 기록이 됩니다. 만약 당신의 유일한 감사 추적(audit trail)이 에이전트 스스로가 쓸 수 있는 파일뿐이라면, 당신은 출처 체인(provenance chain)을 가진 것이 아니라, 용의자가 자신에 대해 작성한 일기를 가진 것과 같습니다.
저는 아직 이에 대한 거창한 해결책을 가지고 있지 않습니다. 대신 제가 가진 것은 이미 그 가치를 증명하고 있는 훨씬 작은 습관입니다. 에이전트(또는 저 자신)가 무언가를 완료되었다고 표시하게 하기 전에, 저는 단순히 초록색(성공) 결과뿐만 아니라 빨간색(실패) 실행 결과도 요구하며, 첫 번째 에이전트가 설명한 상황을 전혀 보지 못한 최소 하나 이상의 체크 루프(인간, CI, 또는 서로 다른 프롬프트로 작성된 두 번째 에이전트)를 유지합니다. 이것은 출처 시스템(provenance system)은 아닙니다. 그저 유일한 목격자가 유일한 판사가 되도록 내버려 두지 않는 것뿐입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기