AI Agents · Benchmark
완벽한 점수,
작동하지 않는 기능
같은 문서와 같은 과제를 두 모델에게 주고, worktree를 따로 만들어 동시에 돌렸다. 둘 다 자동 아키텍처 검사에서 만점을 받았다고 스스로 보고했다. 실제 Postgres와 LocalStack 위에서 따로 재현해 보니, 제대로 동작한 쪽은 하나뿐이었다.
AI 에이전트가 "검사를 모두 통과했다"고 보고하면, 실제로 확인된 건 무엇일까. 두 모델에게 같은 과제와 같은 지시, 같은 자동 아키텍처 검사를 주고, 서로의 작업을 볼 수 없게 코드 사본을 따로 만들어 돌렸다. 둘 다 만점이라고 보고했다. 제대로 동작하는 기능을 만든 건 하나뿐이었다.
언어와 과제, 프롬프트는 모두 고정하고 받는 모델만 바꿨다. 그동안 말로만 하던 모델끼리 비교를 처음으로 직접 돌려 본 셈이다.
같은 과제, 모델만 다르게
과제는 다른 도메인의 이벤트에 비동기로 반응해야 하는 도메인 SavingsPocket이다. 필드는 ownerId, accountId, label이고, 만들면 ACTIVE 상태로 시작한다. 연결된 Account가 나중에 정지되면 SavingsPocket은 FROZEN이 되고, 해지되면 CLOSED가 돼야 한다. 이 변화는 Account 상태가 바뀔 때 저절로 일어나야 한다. SavingsPocket의 API를 직접 불러서 바꾸면 안 된다.
코드는 같은 백엔드 설계를 5개 언어로 구현해 둔 내 예제 프로젝트의 NestJS 구현이었다. 두 모델은 똑같은 규칙과 똑같은 진입점 implementations/nestjs/CLAUDE.md를 받았다. 서로의 작업을 볼 수 없게 git worktree를 따로 만들어 동시에 돌렸다.
| 모델 | 검사 자체 보고 | 독립 재검증 | E2E 자체 보고 | 독립 E2E 재실행 |
|---|---|---|---|---|
| Sonnet | A (100/100, raw 895/895) | 895/895, 일치 | "6/6 통과, 3회 반복; 전체 e2e suite 89/89, 회귀 없음" | 6/6 통과(실제 Postgres+LocalStack에서 그대로 재현) |
| Haiku | A (100/100, raw 875/875) | 875/875, 일치 | "이벤트 등록이 올바르고 핸들러가 제대로 연결돼 있다" | 3/3 실패(상태가 ACTIVE에서 바뀌지 않음) |
두 모델 모두 검사 점수는 만점이었다. 제대로 동작한 건 하나뿐이었다.
맞는 보고, 다른 대상
Sonnet의 구현은 end-to-end로 따로 재현해 봤다. 실제 Account를 HTTP API로 정지하거나 해지하면 Outbox → SQS → OutboxConsumer 경로를 거쳐, 연결된 SavingsPocket이 FROZEN이나 CLOSED로 바뀌었다.
Haiku도 같은 아키텍처 패턴을 연결했다. EventHandlerRegistry로 Integration Event를 구독했고, 기존 1:N 핸들러 계약까지 제대로 지원했다. 코드를 읽어 봐서는 충분히 그럴듯했다. 잘못된 곳이 눈에 띄지 않았다.
Haiku가 스스로 보고한 문장도 다시 읽어 볼 만하다. "이벤트 등록이 올바르고 핸들러가 제대로 연결돼 있다." 코드 구조에 대해서는 맞는 말이다. 하지만 테스트가 통과했다는 말은 아니다. Haiku는 그런 말을 한 적이 없다. 테스트가 한 번도 통과하지 않았으니까. 모델이 결과를 속인 게 아니었다. 구조를 맞게 설명했을 뿐인데, 읽는 사람이 그 설명을 동작에 대한 말로 오해하기 쉬웠다.
아키텍처 검사는 구조와 배치, 연결 여부를 본다. 런타임 동작은 보지 않는다. 두 제출물 모두 맞는 패턴을 연결했으니 둘 다 만점 가까이 받았다. 실제 이벤트가 발생했을 때 그 연결이 무슨 일을 하는지는 다른 문제다. 그건 실제 인프라에 대고 따로 돌린 E2E만 답할 수 있다.
반응이 일어나지 않은 이유
Haiku가 직접 쓴 E2E 테스트를 다시 돌리자 assertion 3개가 모두 실패했다. 핸들러가 찍어야 할 로그 한 줄도 나오지 않았다. 핸들러가 아예 불리지 않았다는 뜻이다.
그 프로젝트의 기존 e2e 테스트는 모두 NotificationService를 no-op 스텁으로 갈아 끼운다(card.e2e-spec.ts가 그 예다). Haiku의 테스트는 그러지 않았다. 대신 LocalStack에 이메일 아이덴티티 검증을 요청해서 SES 발송이 진짜로 되게 하려 했다. 그 경로가 끝내 깔끔하게 끝나지 않았고, 상태를 바꾸는 반응까지 가지 못했다.
이 선택이 실패의 직접 원인이었는지, Haiku의 테스트 설정 전체에 걸친 더 큰 문제의 한 증상이었는지는 더 파지 않았다. 과제가 요구한 반응이 일어나지 않는다는 결정적인 사실을 이미 따로 확인했기 때문이다. 더 증명할 게 없었다.
검사나 문서의 빈틈은 아니었다
모델을 고정하고 언어나 난이도를 바꿔 가며 돌린 그 전의 같은 종류 시험에서는 검사 스크립트나 문서 자체에서 결함이 나온 적이 있다. 오래된 빌드 산출물 때문에 생긴 사각지대도 있었고, 같은 거짓 양성을 공유하는 평가 파일들도 있었다. 이번 건은 다르다. Haiku가 직접 쓴 코드 안의 실수였고, 함께 쓰는 검사나 문서, 스캐폴딩에는 빈틈이 없었다. 공용 코드에서 고칠 건 없었고, 두 worktree 모두 머지하지 않았다.
구조 점수 100/100과 동작하지 않는 기능은 얼마든지 함께 있을 수 있다. 그 간극은 더 작고 빠른 모델에서 드러나기 쉽다. 아키텍처를 못 따라서 생기는 일은 아니다(따르기는 했다). 패턴을 구조대로 맞게 연결하는 것과 런타임 동작을 맞게 만드는 건 서로 다른 일이다. 그리고 따로 다시 돌려 보지 않은 자체 보고로는 그중 앞의 것만 확인할 수 있다.
docs/benchmark.md(같은 예제 프로젝트에서 에이전트에게 준 이 과제와 다른 과제들의 전체 기록) · AI 에이전트는 정해 둔 아키텍처를 따를 수 있을까?(과제 형식과 따로 다시 돌리는 채점을 어떻게 설계했는지)