본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 23. 23:23

쿼럼 코스튬(A quorum costume): 에이전트 검증에 결함 주입(fault injection)이 필요한 이유

요약

에이전트 시스템의 검증 체계가 검증 대상과 동일한 소스를 공유함으로써 발생하는 '검증되지 않은 독립성 가정'의 위험성을 경고합니다. 다수의 에이전트가 투표하더라도 근본적인 데이터나 경로가 같다면 단일 장애점과 다를 바 없음을 지적합니다.

핵심 포인트

  • 에이전트 검증 표면이 검증 대상과 독립적이지 않은 구조적 결함 존재
  • 다중 에이전트 시스템이 'N개의 관점'이 아닌 'N개의 모자를 쓴 하나의 관점'일 위험
  • 단순한 불일치율(disagreement rate) 측정만으로는 시스템의 신뢰성을 보장할 수 없음
  • 검증 체계의 유효성을 확인하기 위해 의도적인 결함 주입(fault injection) 필요

어제 저는 제 AI 파트너가 세 가지 서로 다른 리뷰 환경에서, 세 가지 다른 형태로, 동일한 진실의 근원(source-of-truth) 문제를 세 번 연속으로 놓치는 것을 목격했습니다.

그것은 잘못된 어조로 초안을 작성했습니다. 동일한 모델의 리뷰어 세션(reviewer-session)은 그것을 두 번 읽고 점수를 점진적으로 더 높게 매겼습니다. 포스트 끝에 있는 메타 영수증(meta-receipt)은 리뷰 라운드 횟수를 잘못 계산했습니다 — 사실 드리프트(fact drift)에 관한 단락 안에서 발생한 사실 드리프트였습니다. 제가 리뷰어들이 가지지 못한 권한을 가지고 루프(loop) 밖에 앉아 있었기에 이 세 가지를 모두 잡아낼 수 있었습니다.

저는 그날 저녁에 이러한 실패 자체에 대해 글을 썼습니다. 이것은 그 밑바닥에 깔린 부분에 관한 이야기입니다.

이러한 포착 사례들은 현재 에이전트 스택(agent stack) 전반에서 발생하는 훨씬 더 큰 범주의 실패와 동일한 구조를 가지고 있습니다: 즉, 검증 대상으로부터 독립적이어야 하는 검증 표면(verification surface)이 실제로는 독립적이지 않다는 것입니다. 확인(check)이 주장(claim)과 계보(lineage)를 공유합니다. 리뷰어는 작성자와 동일한 소스(source)를 읽습니다. 합의 루프(agreement loop)는 불일치(disagreement)가 드러났어야 할 동일한 경로를 따라 걷습니다.

이것은 특정 프레임워크의 결함이 아닙니다. 프레임워크를 출시하기 전에 아무도 검증하지 않았던 가정의 문제입니다.

한 단계 위에서의 진단

현재 에이전트 스택의 거의 모든 검증 체계는 경로 간의 독립성에 조용히 도박을 걸고 있습니다.

멀티 에이전트 투표(Multi-agent voting)도, 교차 계층 일관성 검사(Cross-layer coherence checks)도 이에 도박을 겁니다. 쿼럼 읽기(Quorum reads), 일관성 루프(consistency loops), 제작자/검토자 패턴(maker/checker patterns), 2패스 LLM 리뷰(two-pass LLM reviews), 앙상블 프롬프트 설정(ensemble-of-prompts setups) — 이 모든 것들은 결합되는 관점들이 어떤 유용한 의미에서 서로 분리되어 있다는 전제를 가지고 출시됩니다. 불일치는 실제 발산(divergence)을 드러내야 합니다. 합의는 실제 신호가 구조적 테스트를 통과했음을 의미해야 합니다.

제가 보는 대부분의 체계는 이러한 독립성 가정을 관찰 가능하게 만들지 못하고 있습니다.

Self-Correcting Systems는 이 포스트를 위한 시운전(commissioning) 스레드에서 이를 명확하게 명명했습니다: 검증되지 않은 독립성 가정(unverified independence assumption)은 쿼럼 코스튬(quorum costume)을 입은 단일 장애점(single point of failure)과 구별할 수 없습니다. 이 한 문장이 이 포스트의 핵심적인 역할을 수행합니다. 경로들이 실제로 서로 불일치해야 하는 지점에서 정말로 불일치하는지 확인하기 전까지는, 당신에게 N개의 뷰(N views)가 있는 것이 아닙니다. 당신은 단지 N개의 모자를 쓴 하나의 뷰를 가지고 있을 뿐이며, 이를 구별할 방법도 없습니다.

'코스튬(costume)'은 당신을 속이는 부분입니다. 투표 결과는 만장일치로 돌아왔습니다. 검토자들은 동의했습니다. 교차 검증(cross-check)은 통과했습니다. 시스템 내부에서는 검증이 제 역할을 다한 것처럼 보입니다. 하지만 모든 경로가 상류(upstream)를 공유하고 있다는 것을 볼 수 있는 외부의 관점에서는, 그것은 단지 반복되는 하나의 신호일 뿐입니다.

불일치율(Disagreement rate)은 테스트가 아니다

이 문제가 제기되었을 때 나오는 반사적인 대응은 다음과 같습니다: "좋아, 그럼 스모크 테스트(smoke test)로서 불일치율(disagreement rate)을 측정하자. 만약 뷰들이 결코 불일치하지 않는다면, 그것들은 독립적이지 않은 것이다."

이것은 유용한 확인 방법이지만, 테스트 그 자체는 아닙니다.

두 경로가 표현 방식(phrasing)에는 불일치하면서도 동일한 잘못된 사실에는 동의할 수 있습니다. 신뢰도(confidence)에는 불일치하면서도 동일한 환각(hallucination)으로 수렴할 수 있습니다. 어조(tone)에는 불일치하면서도 두 경로 모두에게 잘못된 컨텍스트를 제공한 동일한 상류 검색(upstream retrieval)을 공유할 수 있습니다. 중요한 합의는 실제로 부하를 짊어지는 요소에 대한 합의이며, 바로 그 차원이 공유된 계보(shared lineage)를 가장 파악하기 어려운 지점입니다. 표면적인 단어들은 다르기 때문입니다.

실제로 중요한 상류를 공유하면서 노이즈(noise)에 대해서만 불일치하는 것은 독립성 주장(independence claim)에 있어 최악의 실패 모드입니다. 이는 건강해 보입니다. 보기 좋게 다양한 출력을 생성합니다. 스모크 테스트를 통과합니다. 그리고 상류가 거짓을 말할 때마다 매번 동일한 방향으로 실패합니다.

진정한 테스트는 뷰들이 스스로 불일치하는지 여부가 아닙니다. 시스템을 섭동(perturbing)함으로써 그들이 불일치하게 만들 수 있는지 여부입니다. 그것은 완전히 다른 형태의 측정입니다.

항상 염두에 두어야 할 간결한 진단법은 다음과 같습니다:

경로 (Path)공유된 상류 (Shared upstream)주입된 결함 (Injected fault)기대되는 불일치 (Expected divergence)"결합 감지(coupling detected)"의 양상
Retriever-A vs Retriever-B임베딩 모델 (embedding model) M한쪽 경로의 인덱스에만 모순된 사실을 심음A는 오염된 결과를 반환하고, B는 깨끗한 결과를 반환함둘 다 오염된 결과를 반환함 (동일한 임베딩이 어느 쪽이든 잘못된 셀을 끌어옴)
...

각 열은 운영상의 형태를 나타냅니다. 마지막 열이 없다면, 검증이 실제로 작동하고 있는 것인지, 아니면 아무도 방해(perturb)하지 않아서 단순히 녹색(정상) 상태로 머물러 있는 것인지 구분할 수 없습니다.

이 분야에서 실질적인 방법론이 존재하는 곳

분산 시스템 (Distributed systems)은 약 20년 전에 이 문제에 직면했으며, 특정 장애 가정 (failure assumptions)을 테스트하기 위한 실질적인 방법들을 개발했습니다. 이러한 방법들이 일반적인 경우에 독립성을 증명해 주는 것은 아닙니다. 다만, 의존성 (dependence)이 존재할 때 이를 드러내 줄 뿐입니다.

Jepsen은 일관성 (consistency)을 주장하는 데이터베이스를 대상으로 파티션 테스트 (partition tests)를 실행하며, 그 주장이 네트워크 장애 (network failure) 상황에서도 유지되는지 확인하는 유일한 방법은 네트워크 장애를 직접 일으키는 것입니다. Chaos Monkey는 의도적으로 운영 환경의 인스턴스들을 종료시킵니다. Game-day 연습은 리전 (regions)을 차단하고, 지역적 중복성 (regional redundancy)을 주장하는 시스템이 실제로 이를 갖추고 있는지 관찰합니다. 속성 기반 테스트 (Property-based testing)는 모든 입력값에 대해 정확하다고 주장하는 코드에 구조화된 무작위 입력 (structured-random inputs)을 던집니다. 패턴은 반복됩니다. 시스템이 잘 돌아가는 것을 지켜봄으로써 회복탄력성 (resilience)을 측정하는 것이 아닙니다. 운영자가 허가한 안전 경계 내에서, 해당 주장이 견뎌내야 했던 바로 그 장애 모드 (failure mode)를 유도함으로써 측정하는 것입니다.

에이전트 (Agent) 및 메모리 아키텍처 (memory architectures)는 이러한 규율을 물려받지 못했습니다. 그 이유는 기술적인 문제가 아닙니다. 도구 (tooling)는 간단합니다. 이유는 문화적입니다. 분산 시스템은 상태 (state)를 신뢰하기 위해 반드시 방해 (perturb)해야 하는 대상으로 취급합니다. 반면 에이전트 스택 (Agent stacks)은 상태를 기본적으로 쓰고 읽으며 신뢰할 수 있는 대상으로 취급합니다. 전체 설계 어휘 (design vocabulary)가 저장 (storing), 검색 (retrieving), 임베딩 (embedding), 그리고 랭킹 (ranking)을 중심으로 구축되어 있으며, 이 중 그 어느 것도 방해 (perturbation)를 위한 기본 기능 (primitive)을 포함하고 있지 않습니다.

그것이 바로 간극(gap)입니다. "독립성(independence)을 검증하는 방법을 모른다"는 뜻이 아닙니다. 우리가 테스트하는 특정 사례들에 대해서는 알고 있습니다. 간극은 테스트 하네스(harness)가 여전히 필수적인 입장권(price of admission)으로 취급되는 경우가 드물다는 점에 있습니다.

여기서 결함 주입(fault injection)이 실제로 의미하는 것

이 용어를 핵심만 추려보겠습니다. 주장의 계보(lineage)를 추적하고, 제한된 영향 범위(blast radius) 내에서 해당 주장을 생성한 경로 중 하나에 알려진 잘못된 상태(bad state)를 유도한 뒤, "독립적인" 체크가 그 편차(divergence)를 포착하는지, 아니면 조용히 흡수해 버리는지를 확인하는 것입니다.

제가 실행 중인 스택 유형에 대한 세 가지 구체적인 조치입니다:

합성된 잘못된 메모리 엔트리(synthetic-bad memory entry) 주입. 에이전트의 시맨틱 저장소(semantic store)에서 사실 하나를 선택합니다. 샌드박스(sandbox) 또는 스테이징(staging) 환경에서, 한쪽 검색 경로의 인덱스에는 모순되는 버전을 심되 다른 쪽에는 심지 않습니다. 합의 루프(agreement loop)를 실행합니다. 주입된 결함(fault)은 심어진 모순이며, 관찰 가능한 단언(assertion)은 두 경로가 서로 다른 값을 반환하는지 여부입니다. 음성 대조군(negative control)은 주입 전 동일한 쿼리를 실행하여 두 경로 모두에서 일관된 값을 반환하는 것이며, 결합(coupling)을 구성하는 조건은 오염된 값에 대한 만장일치 합의와 두 경로 모두에서 일치하는 출처 ID(provenance IDs)입니다. 만약 루프가 만장일치 결과를 반환한다면, 루프는 두 경로를 읽은 것이 아니라 하나의 경로를 서로 다른 형식으로 두 번 읽은 것이며, 출처 추적(provenance trail)이 이를 증명할 것입니다.

오프라인에서 라우터 규칙(router rule) 변이. 에이전트가 하나의 결정 경로를 통해 적용하는 규칙을 가져옵니다. 메이커/체커(maker/checker) 패턴이 감지해야 하는 바를 명시적으로 정의합니다. 예를 들어, 정책(policy)에는 여전히 버전 N이 기록되어 있는데 버전 N+1에서 계산된 판결이 나올 경우 체커가 이를 플래그(flag)해야 한다는 식입니다. 오프라인 복사본의 규칙을 변이시켜, 이전에는 판결 X로 라우팅되었던 알려진 요청이 이제 판결 Y로 라우팅되도록 합니다. 해당 특정 요청에 대해 메이커/체커를 다시 실행합니다. 예상되는 편차는 정책에 기록된 버전과 정책에 적용된 버전 사이의 불일치를 체커가 지적하는 것입니다. 만약 메이커와 체커가 모두 통과한다면, 그들은 캐시된 정책을 공유하고 있었던 것이며 두 번째 역할은 검증(check)이 아닌 프레젠테이션 레이어(presentation layer)에 불과했던 것입니다.

텔레메트리(telemetry)에 잘못된 response.model을 심으세요. 용어를 먼저 정의하겠습니다: 선행성 검사(anteriority check)는 텔레메트리에 기록된 서빙 모델 식별자(served-model identifier)를 서빙 모델 의도 명세(served-model intent-spec)와 비교하는 진단 과정입니다. 여기서 의도 명세란 어떤 모델이 어떤 요청 유형을 처리해야 하는지를 명시한 빌드 타임 매니페스트(build-time manifest) 또는 외부 출처 저장소(external provenance store)를 의미합니다. 독립적으로 제어되는 기록은 빌드 타임에 작성되어 런타임(runtime)이 수정할 수 없는 곳에 저장되는 의도 명세입니다. 의도적으로 잘못된 서빙 모델 식별자를 포함하여 답변을 로그로 남기세요. 기대되는 결과는 선행성 검사가 의도 명세를 읽고, 이를 심어진 텔레메트리와 비교하여 불일치를 표시하는 것입니다. 만약 검사가 통과된다면, 해당 검사는 작성자가 쓴 것과 동일한 기록을 읽고 있었던 것입니다. 즉, 작성자의 영향력 밖에 있는 앵커(anchor)가 없다는 뜻입니다.

세 가지 경우 모두에서 가치는 발견한 특정 버그 그 자체에 있지 않습니다. 그것은 시스템이 스스로에 대해 다른 방법으로는 답할 수 없었던 질문에 대한 이진적(binary) 답변입니다: 두 번째 경로가 실제로 첫 번째 경로를 잡아내는가, 아니면 코스튬(costume)이 양쪽 모두를 작성한 팀을 포함하여 모두를 속이고 있는가?

기저에 깔린 운영상의 분리. cowork-os에서 느린 루프 메모리 업데이트(slow-loop memory updates)를 설계하던 Raffaele Zarrelli는 이 과정을 일회성 테스트에서 지속적인 규율로 전환하는 질문을 던졌습니다: 검증기(verifier)를 실제로 작성하는 주체는 누구인가? 유지되는 구조는 다음과 같습니다: 운영자(operator)가 잠금 시점(lock time)에 검증기 선택을 작성합니다. 즉, 앵커를 선택하고, 예상되는 결과(resolution)를 명명하며, 주기(cadence)를 설정합니다. 왜냐하면 올바른 앵커를 선택하는 것은 메커니즘(mechanism)으로 확장될 수 없는 판단의 영역이기 때문입니다. 진단 실행은 기계적입니다: 크론(cron)이 정해진 주기에 따라 잠긴 모든 항목을 순회하며, 현재의 실제 상황(current reality)에 대해 검증기를 실행하고, 결과가 어긋나거나 검증기 자체가 실행되지 않은 항목을 표시합니다. 운영자가 앵커를 선택하고, 메커니즘이 검사를 실행합니다. 이는 적용/권고(apply/advisory) 분리와 동일한 구조입니다. 운영자가 바인딩(binding)을 승인하면, 메커니즘은 상태를 변경할 권한 없이 드리프트(drift, 편차)를 드러냅니다.

이와 함께 따르는 하나의 제약 사항이 있습니다. 검증기(verifier) 자체는 기록 세션(writing session)이 도달할 수 없는 대상을 목표로 삼아야 합니다. 운영자는 잠금(lock) 시점에 앵커(anchor)를 선택하지만, 만약 그 앵커가 결정을 기록한 것과 동일한 세션 내에서 운영자 자신의 파일 시스템을 대상으로 하는 grep(텍스트 검색)이라면, 전체 시스템은 한 단계 높은 수준의 문구 확인(wording-check)으로 전락하고 맙니다. 이는 동일한 질병이 더 긴 사슬을 갖게 되는 것과 같습니다. 가장 작으면서도 유용한 버전은 다음과 같습니다: 검증 대상은 외부에서 작성되어야 합니다. 운영자가 작성하지 않은 서비스에 의해 서명된 CI 실행. 상대방(counterparty)에 의한 커밋. 감사 추적(audit trail)이 포함된 벤더 영수증. 운영자의 쓰기 경로(write path) 외부에 있는 세계 상태(world-state) 기록. 잠금 세션이 내부에서 스스로 다시 쓸 수 있는 것이라면 그것은 검증기가 아니라 레이블(label)일 뿐입니다.

정제된 5단계 작업 체크리스트:

  1. 주장의 계보(lineage)를 매핑할 것 — 누가 작성했는지, 어떤 컨텍스트가 유입되었는지, 어떤 상류(upstream)가 무엇을 생성했는지 확인합니다.
  2. 교란할 경계(boundary)를 하나 선택할 것 — 운영 환경을 망가뜨리지 않고 교란할 수 있는 경로를 선택합니다. 기본적으로 합성 데이터(synthetic data), 샌드박스(sandbox), 또는 스테이징(staging) 환경을 활용합니다.
  3. 제어된 결함(fault)을 주입할 것 — 알려진 잘못된 상태(known-bad state), 단일 경로, 제한된 폭발 반경(blast radius), 롤백(rollback) 준비가 된 상태여야 합니다.
  4. 대체 경로를 관찰할 것 — 독립적이라고 가정된 체크가 편차(divergence)를 포착하는지, 아니면 조용히 흡수해 버리는지 확인합니다. 두 경로 모두에서 출처(provenance)를 캡처합니다.
  5. 통과 기준을 기록할 것 — 무엇이 주입되었는지, 무엇이 작동했는지, 무엇이 작동하지 않았는지, 무엇이 깨끗한 통과(clean pass)를 구성했을지를 기록합니다.

동일한 논리가 일반화됩니다. 검증 주장을 선택하십시오. 단일 경로를 선택하십시오. 안전한 경계 내에서 해당 경로를 교란하십시오. 검증 과정을 지켜보십시오. 만약 입력값이 거짓임에도 검증이 미동도 하지 않는다면, 그 검증은 결코 검증이 아니었습니다.

독립성은 퇴화한다

여기서 멈춘다면 당신은 하네스(harness)를 한 번 구축하고 끝낸 것입니다. 그것이 바로 버려야 할 다음 가정입니다.

독립성은 당신이 확립하는 속성이 아닙니다. 그것은 당신이 재검증(re-verify)해야 하는 속성입니다.

그 이유는 작지만 잔혹합니다. 당신은 오늘 두 개의 경로를 연결하고, 깨끗한 결함 주입 (fault-injection) 테스트를 통해 그것들이 서로 분리되어 있음을 증명한 뒤 결과를 배포할 수 있습니다. 하지만 다음 달에 누군가가 두 경로의 중간에 공유 캐시 (shared cache)를 삽입하는 리팩터링 (refactor)을 수행할 수 있습니다. 투표는 여전히 반환됩니다. 합의 루프 (agreement loop)는 여전히 작동합니다. 스모크 테스트 (smoke test)는 여전히 정상적으로 보입니다. 그리고 당신의 6월 결함 주입 (fault-injection) 테스트는 8월에는 더 이상 존재하지 않는 시스템을 인증한 셈이 됩니다. 당신은 이미 붕괴해 버린 독립성을 측정했으며, 루프 내의 그 어떤 것도 그것이 붕괴했다는 사실을 알려주지 않습니다. 왜냐하면 모든 가시적인 신호가 공유 캐시 (shared cache)의 하류 (downstream)에 있기 때문입니다.

이것은 직교 축 (orthogonal axis)에 적용된 '무결성은 선행성이 아니다 (integrity-is-not-anteriority)'와 동일한 질병입니다. 특정 시점의 무결성 (integrity)은 검증 가능한 이력이 아닙니다. 특정 시점의 독립성 (independence)은 지속되는 분리된 경로가 아닙니다. 둘 다 상태 (state)의 속성이 아니라 시간 (time)의 속성입니다.

이와 병행하여 실행할 가치가 있는 운영 모델은 다음과 같습니다:

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0