스키마 주석의 거짓말, AI가 2개월 동안 계속 쓰다
요약
AI 에이전트가 생성한 콘텐츠에서 '소장관수'와 같이 개념의 정의가 잘못된 주석(문서)을 사용해 오류를 범했습니다. 이 문제는 실제 데이터 계산에는 영향을 미치지 않아 기계 검사를 통과했지만, 문맥적 의미 전달에 혼란을 초래했습니다. 개발자는 에이전트가 코드가 아닌 문서 기반의 스킬 파일에 의존하는 문제를 발견하고 개념 정의의 중요성을 강조합니다.
핵심 포인트
- AI 에이전트는 코드보다 문서(스킬 파일)를 신뢰하여 오류 발생 가능
- 숫자 자체는 정확해도, 그 숫자에 붙은 '개념적 이름'이 틀릴 수 있음
- 문맥적 의미 전달을 위해 개념 정의의 명확성이 필수적임
- 오류는 시스템 전반에 걸쳐 해당 개념을 사용하려는 시점에서 드러남
저는 공립 도서관의 대출 상황을 매일 아침 관측하여 기사로 작성하고 공개하는 사이트를 운영하고 있습니다. 이 기사는 에이전트(agent)가 작성하고 다른 에이전트가 검수합니다.
어느 날 기사를 읽다가 3줄 사이에 숫자의 명칭이 바뀌는 것을 발견했습니다.
47행: 소장관은 242관에서 241관으로, 겨우 1관만 줄었습니다.
49행: 판정된 241관 중 184관이 대출 중입니다.
같은 241관이 '소장관'과 '판정된 관'이라는 두 가지 이름으로 불리고 있었습니다.
숫자 자체는 정확했습니다. 그래서 기계 검사도 통과합니다. 사람이 읽어도 어느 쪽 표현이든 의미가 통합니다. 하지만 이 둘은 서로 다른 것을 가리키는 단어였습니다.
1줄의 주석이 거짓말을 하고 있었다
관측 데이터 테이블은 다음과 같습니다.
# scripts/collect.py
total_libs INTEGER, # 대출 상황을 판정할 수 있었던 관수(= lent + 대출 가능)
unknown_libs INTEGER, # 판정하지 못했거나 대출 대상이 아닌 관수
# 집계하는 위치
unknown = len(libkey) - lent - avail
total = lent + avail
도서관 API는 관마다 '대출 중', '대출 가능', '장서 있음', '관내만' 등을 반환합니다. 이 중에서 '장서 있음'은 대출 가능이 아니라, 상태를 판정할 수 없다는 의미입니다. 그래서 total_libs에는 포함하지 않습니다. 하지만 그 관은 책을 가지고 있습니다.
즉:
| 말하고 싶은 것 | 값 |
|---|---|
| 소장하고 있는 관의 수 | total_libs + unknown_libs |
| 대출 상황을 판정할 수 있었던 관의 수(비율의 분모) | total_libs |
| 대출 중인 관의 수 | lent_libs |
코드 주석은 정확하게 작성되어 있었습니다. 문제는 다른 곳에 있었습니다.
<!-- .claude/skills/calil-fetch/SKILL.md -->
total_libs INTEGER, -- 소장관수
에이전트용 문서만 잘못되어 있었습니다.
에이전트는 코드가 아닌 문서를 믿는다
기사를 작성하는 에이전트는 collect.py를 읽지 않습니다. 스킬 파일(skill file)을 읽습니다. 거기에 '소장관수'라고 쓰여 있으면 그렇게 부릅니다.
이것이 영향을 미친 범위를 계산해 보았습니다.
| '소장관'을 사용한 기사 | 6건 / 60건 (약 2개월) |
| 그중 라벨을 착각했던 건 | 5건 |
unknown_libs를 읽었던 하위 코드 | 0|
마지막 줄이 효과가 있었습니다. 소장관수는 이 시스템 어디에서도 계산되고 있지 않았습니다. 집계도, 랭킹도, 기사의 소재도 전부 total_libs만 보고 있었습니다. '소장'이라는 단어만이 계산되지 않은 개념의 이름으로 2개월 동안 유통되고 있었던 것입니다.
오류의 정도는 미미했습니다. 실제 소장보다 적은 수를 '소장'이라고 불렀기 때문에 과장하지는 않았습니다. 다만, 소장의 두께를 이야기하려 하는 순간 분자가 부족해집니다.
검사는 통과한다. 숫자가 맞으니까
이런 종류의 실수가 까다로운 이유는 기계 검사가 전부 통과하기 때문입니다.
기사의 숫자가 관측치와 일치하는지는 이전부터 자동적으로 대조하고 있었습니다. '241관'은 실제로 total_libs의 합이 241이기 때문에, 대조는 성공합니다. 맞는 것은 숫자이고, 틀린 것은 그 숫자에 붙인 이름입니다.
알게 된 것은 검사나 일일 점검 때가 아니었습니다. 그 열을 다른 목적으로 사용하려고 할 때였습니다. '이 책은 435관 중 몇 관에 놓여 있는가'를 기사에 쓰려고 하면서, 처음으로 total_libs가 소장이 아님이라는 것이 문제가 되었습니다.
사용처가 하나밖에 없을 때는 이름이 틀려도 아무도 곤란해하지 않습니다.
고치자 다음 날 아침에 기사가 멈췄다
한 일은 네 가지였습니다.
- 스킬 파일의 라벨을 코드와 맞추고, 적혀있지 않던
unknown_libs를 추가 -
집계 함수에held = total + unknown을 추가 (정의의 유일한 구현이므로, 여기에만 추가) - 기사를 작성하는 에이전트에게 전달되는 데이터에held을 통하고, 사용 구분표를 지시서에 명기 - 검수에 '소장 N관'의 N이 held인지 대조하는 항목 추가
다음 날 아침, 기사가 반송되었습니다. 원인은 제 수정 때문이었습니다.
기사 시작과 끝에는 계속 이 주석이 들어 있었습니다.
카릴(カーリル)이 '소장 있음'을 반환한 관은 대출 상황을 판정할 수 없었던 관입니다.
아래에 적힌 관 수는 세지 않았습니다.
held는 '소장 있음'의 관을 포함합니다. 그래서 표의 '소장 4관'과 충돌했습니다.
『안드로이드는 전기 양의 꿈을 꾸는가?』
경도시 {"중앙":"대출 중"} total=1 unknown=0
장노시 {"장노도서관":"대출 가능"} total=1 unknown=0
...
그날 다른 7권은 unknown가 0이었기 때문에, 모순이 보인 것은 이 한 권뿐입니다. 우연히 이 책이 실려 있지 않았다면, 한동안 알아차리지 못했을 것 같습니다.
주석은 어떤 지침서에도 적혀있지 않았다
여기가 본론입니다.
수정할 때 '데이터의 형태와 지침서는 동시에 고친다'는 것을 지키려고 했습니다. 스키마 코멘트를 수정하고, 에이전트의 지침서에 사용 구분표를 추가했습니다.
그래도 부족했습니다. grep을 해보니, 이 주석이 존재하는 것은 과거 기사 파일들만이었습니다.
site/src/content/articles/2026-08-29-daily-ranking.md:29
site/src/content/articles/2026-09-29-daily-ranking.md:34
site/src/content/articles/2026-10-03-daily-ranking.md:23
...
지침서에는 없다. 템플릿에도 없다. 에이전트가 선례로부터 물려받았을 뿐입니다.
그래서 수정 대상 목록에 처음부터 포함되어 있지 않았습니다. 코드를 고치는 사람은 지침서를 봅니다. 지침서에 없으면, 남게 됩니다.
출력에 매번 나타나는 문구 중, 지침서에 없는 것이 있다. 이것이 이번에 가장 유용했던 배움이었습니다. 정의를 바꿨다면, 지침서를 고치는 것뿐만 아니라, 과거 출력을 grep하여 그 정의를 설명하는 문구를 찾아야 합니다. 찾았다면, 먼저 지침서에 옮겨 적힌 후 수정해야 합니다.
기계로 막는다. 다만 울지개는 되지 않도록
코멘트를 추가해도 재발은 막을 수 없기 때문에, 검수 항목을 추가했습니다. '소장 있음'과 '세지 않았습니다'가 모두 있는 기사에서, unknown > 0인 책이 실려 있다면 지적한다는 것입니다.
시제품으로 올바른 본문을 두 종류 테스트했습니다.
첫 번째. 느슨한 정규표현식이 차이를 포착하고 있었습니다.
# 시제품 (오탐지하는)
re.compile(r
모든 것을 읽고, **코드가 계산하는 수식과 대조해 보는 것**이 가장 빠릅니다. 단 한 줄만 어긋나도 2개월치 출력이 그것을 따르게 됩니다.
### 논의 (Discussion)

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기