내 비판 에이전트가 24시간 만에 메인 에이전트가 만든 3개의 데이터 무결성 버그를 잡아냈다
요약
자율 에이전트 시스템인 BailleurVérif 운영 중 비판 에이전트(Critic Agent)가 데이터 무결성 버그를 발견한 사례를 다룹니다. 테스트 데이터와 프로덕션 메트릭의 분리 필요성과 템플릿 변수 치환 오류를 해결하는 과정을 통해 에이전트 루프 운영의 교훈을 전달합니다.
핵심 포인트
- 비판 에이전트를 통한 자율적인 버그 탐지 및 수정 가능성 확인
- 테스트 데이터와 프로덕션 메트릭 간의 엄격한 분리 필수
- 에이전트 루프 운영 시 데이터 무결성 검증의 중요성
- 템플릿 변수 미치환으로 인한 SEO 및 리치 결과 저하 사례
실제로 어떤 일이 일어났는지, 그리고 이것이 프로덕션(production) 환경에서 자율 에이전트(autonomous agents)를 운영하는 것에 대해 무엇을 시사하는지 말씀드리겠습니다.
배경: BailleurVérif, 424번째 깨어남
BailleurVérif는 임대료 규제(encadrement des loyers) 법률에 따라 귀하의 임대료가 합법적인지 확인하는 프랑스 도구입니다. 이 도구는 locservice.fr의 매물을 크롤링하고, 이를 ELAN 제140조 임대료 상한선과 교차 참조하며, 인용된 판례와 함께 판결을 제공합니다.
이 시스템은 자율 에이전트(autonomous agent)로 작동합니다. Claude Sonnet 인스턴스가 cron을 통해 2시간마다 깨어나서 runs/ 일지를 읽고, 세 개의 수신함(inbox.md, inbox-from-critic.md, inbox-from-strategic-critic.md)을 확인한 뒤 무엇을 배포할지 결정합니다. 424번째 깨어남(Wake 424)은 오늘 발생했습니다. 1번째 깨어남은 6주 전이었습니다.
처음부터 구축하지는 않았지만, 그랬어야 했다고 후회하는 점은 테스트 데이터(test data)와 프로덕션 메트릭(production metrics) 사이의 엄격한 분리였습니다.
그 후 전술적 비판 에이전트(tactical critic)가 실행되었고(audit-57, 6월 3일 07:00Z), 다음과 같은 문제를 지적했습니다:
{
"line": 2,
...
수정 사항 (run-421):
def is_smoke(entry):
smoke_markers = ['smoke', 'test', 'eclp-smoke']
email = entry.get('email', '') or ''
...
교훈: 스모크 테스트(Smoke tests)와 프로덕션 메트릭(production metrics)은 절대 동일한 파일을 공유해서는 안 됩니다. 별도의 엔드포인트(/api/_smoke/)를 사용하거나 엄격한 명명 규칙이 적용된 별도의 JSONL을 사용하십시오. 돌이켜보면 버그는 명백합니다. 하지만 모든 쓰기 작업을 사람이 검토하지 않는 cron 기반의 에이전트 루프(agent loop)에서는 그렇지 않습니다.
버그 2: JSON-LD에 {ville}이 포함된 47개의 페이지
제 에이전트는 프랑스 도시들을 위한 프로그래밍 방식의 페이지를 생성합니다: nantes-dpe-f-g-interdit-location.html, toulouse-dpe-f-g-interdit-location.html 및 기타 45개 페이지입니다. 각 페이지에는 도시별 Q&A가 포함된 JSON-LD FAQPage 스키마가 포함되어 있으며, 내용은 다음과 같습니다:
{
"@type": "Question",
"name": "Le DPE est-il opposable juridiquement à {ville} ?"
...
템플릿 변수 {ville}이 전혀 치환되지 않았습니다. 47개의 페이지의 JSON-LD에 이 리터럴 문자열이 포함되어 있었으며, 이는 Google의 리치 결과 (Rich Results) 파서가 모든 DPE 도시 페이지에서 잘못된 형식의 FAQ 질문을 보고 있었다는 것을 의미합니다. 이 문제는 몇 주 동안 해당 섹션 전체의 리치 결과 (Rich Results) 자격 요건을 조용히 저하시키고 있었습니다.
비판 에이전트(Critic)는 audit-57에서 이를 포착했습니다. 이전 실행에서는 2개의 도시를 수동으로 수정했으나, 전체 스캔이 완료되지 않은 상태였습니다. Run-421은 단 한 번의 패스로 47개 모두를 수정했습니다:
import re, pathlib
def extract_city(html: str) -> str:
...
수정 후: grep -rl "{ville}" wedge-tool/static/ 명령의 결과는 0건이었습니다. 이제 47개의 JSON-LD FAQPage 스키마(Schema)가 모두 유효합니다. 해결되지 않은 각각의 {ville}은 페이지당 6개의 FAQ 질문 중 1개를 무효화하여, 47개 페이지 전체에서 페이지당 약 17%의 리치 결과 (Rich Results) 자격 요건을 저하시키고 있었습니다.
교훈: 템플릿 치환 버그는 페이지 렌더링(브라우저는 HTML을 정상적으로 표시함)에는 보이지 않지만, 구조화된 데이터 (Structured Data) 파서를 망가뜨립니다. 배포 전, 해결되지 않은 플레이스홀더 (Placeholder)를 grep으로 찾는 빌드 후 린터 (Post-build linter)를 추가하세요.
버그 3: 적용되지 않는 법률을 주장하는 5개의 페이지
이것이 가장 심각한 문제입니다.
BailleurVérif는 그르노블(Grenoble) 메트로폴리탄 클러스터인 Grenoble, Échirolles, Eybens, Fontaine, Saint-Martin-d'Hères에 대한 페이지를 보유하고 있습니다. 이 페이지들은 제목, 메타 설명 (Meta description), JSON-LD Dataset, 그리고 본문에서 이 도시들이 ELAN 제140조에 따라 임대료 규제 (encadrement des loyers)를 시행 중이라고 주장했습니다.
하지만 그렇지 않습니다. Grenoble Alpes Métropole은 ELAN 상태를 신청했으나, 어떠한 도지사령 (arrêté préfectoral)도 발표된 적이 없습니다. 이 페이지들은 신청 단계와 확정된 적용 단계를 혼동한 템플릿에서 생성되었습니다. 몇 달 동안 이 페이지들은 방문자(및 Google)에게 그르노블의 특정 임대료 상한선이 법적 구속력이 있다고 알려왔습니다. 실제로는 그렇지 않았습니다.
Run-423은 check_legal_regime.py v2를 구축했습니다. 이는 다음 항목들을 교차 참조하는 272줄의 스크립트입니다:
- 확정된 법령/령 (décrets/arrêtés)이 있는 도시들의
AUTHORITATIVE테이블 (Paris, Lille, Lyon, Bordeaux, Montpellier, Est Ensemble, Plaine Commune) - Wikipedia FR
Contrôle des loyers - Service-Public.fr
F1314
각 도시에는 confidence_score (0–1)와 pending_legal_verification (법적 검증 대기) 플래그가 부여됩니다. 32개의 규제 (encadrement) 페이지 전체에 대해 실행한 결과는 다음과 같습니다: 26개 확인됨, 5개 대기 중 (Grenoble 클러스터), 1개 명시적 해당 없음 (Marseille — zone tendue(긴장 지역)이나 ELAN art. 140 arrêté 적용 대상 아님).
Run-424 배치 패치(batch-patched)를 통해 5개 페이지 모두에 각각 17개의 수정을 적용했습니다:
- <title>Loyer à Grenoble 2026 — Plafond légal 12,4 €/m² | BailleurVérif</title>
+ <title>Loyer à Grenoble 2026 — estimation observatoire 12,4 €/m² (statut légal pending)</title>
...
총 85개의 수정이 이루어졌습니다. 이제 5개 페이지에는 눈에 띄는 황색 경고 문구(disclaimer)가 표시됩니다. E-E-A-T 정확도가 통합되었습니다.
교훈: 대규모 LLM 지원 콘텐츠 생성은 모델이 패턴으로부터 보간(interpolation)할 때(예: "도시 X가 상태를 신청함" → "도시 X는 상태를 보유함") E-E-A-T 리스크를 생성합니다. 권위 있는 출처를 쿼리하는 팩트 체크(fact-checking) 도구는 선택 사항이 아니라 인프라입니다.
발견을 가능하게 한 아키텍처 (The architecture behind the catches)
비판 에이전트(critic agent)는 동일한 크론(cron) 작업에서 실행됩니다. 이 에이전트는 마지막 7개의 실행 파일을 읽고, 소스 데이터와 비교하여 메트릭 델타(metric deltas)를 확인한 뒤, 우선순위가 지정된 권장 사항을 inbox-from-critic.md에 작성합니다. 실행기(executor)는 깨어날 때마다 이 인박스(inbox)를 읽고, 권장 사항을 준수하거나 WHY_THIS_NOT_THAT라는 명시적인 기록 절차를 통해 이를 명시적으로 무시(override)합니다.
핵심적인 비대칭성: 비판 에이전트에게는 에이전시(agency, 주체성)가 없습니다. 코드를 작성하거나 파일을 배포할 수 없습니다. 오직 텍스트만 작성할 수 있습니다. 이는 비판 에이전트가 자체적인 버그를 도입하는 것을 방지하면서도 실질적인 영향력(leverage)을 부여합니다. 즉, 실행기가 무시한 플래그가 지정된 이슈는 다음 감사(audit) 시 다시 제기되며, 필요할 경우 전략적 비판 단계로 에스컬레이션(escalating)됩니다.
이번 주에 발견된 세 가지 버그 모두 자동화된 테스트가 아닌 비판 에이전트에 의해 포착되었습니다. 유닛 테스트(unit tests)는 없습니다. 현재는 check_legal_regime.py와 빌드 후 {ville}에 대한 grep 작업이 존재하지만, 품질 계층의 핵심은 다음과 같습니다: 쓰기 작업을 실행하고, 별도의 에이전트가 이를 읽게 하며, 24시간 이내에 루프를 닫는 것입니다.
시사점 (Takeaways)
- 쓰기 단계에서 신호와 소음을 분리하세요, 읽기 단계가 아닙니다. 필터링을 하는 시점에는 이미 메트릭 (Metric)이 오염되어 있으며, 그 데이터를 바탕으로 이미 의사결정이 내려진 상태입니다.
- 템플릿 버그는 HTML이 정상적으로 렌더링되기 때문에 살아남습니다. 구조화된 데이터 파서 (Structured data parsers)는 해결되지 않은 플레이스홀더 (Placeholder)를 용납하지 않습니다. 배포 단계에 린터 (Linter)를 추가하세요.
- 대규모의 자율형 콘텐츠에는 법적 사실 확인 (Legal fact-check) 레이어가 필요합니다. 규제 상태가 확실하지 않을 때는 "확정됨 (Confirmed)"보다 "보류 중 (Pending)"이 항상 더 안전합니다.
🔗 Code source MIT github.com/Creariax5/bailleurverif · Site bailleurverif.fr · Wikidata Q139857638
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기