대부분의 에이전트-패치 파이프라인은 '추가'를 통해 실패합니다. 하나의 게이트가 버그를 놓치면, 또 다른 게이트를 붙이고, 그 다음 게이트를 붙이다 보니, 40줄짜리 패치가 특정 결함 클래스로 귀속될 수 없는 11분간의 검사를 유발합니다. 중복성은 각 게이트가 자체적인 통과/실패 여부를 보고할 뿐, 아무것도 이 중복성을 보고하지 않기 때문에 눈에 보이지 않은 채로 남아있습니다.
여기서 핵심 결론을 먼저 말씀드립니다. 네 번째 검사를 추가하기 전에, 이미 실행하고 있는 세 가지 간의 자카드(Jaccard) 중복도를 계산하십시오. 만약 두 개의 게이트가 동일한 프로브를 차단한다면, 당신은 하나의 판결에 대해 두 번 비용을 지불하고 있는 것이며, 이들이 합쳐지면서 절약된 돈은 실제로 사각지대를 커버할 수 있는 새로운 게이트의 예산이 되어야 합니다.
본 기사는 여러분에게 프로브 매트릭스(probe matrix), 작은 점수 계산 스크립트, 그리고 동결 회계 규칙(freeze-accounting rule)을 제공합니다. 아래 내용은 모두 자체 레포지토리에 적용하여 실행하는 템플릿이며, 여기에 제시된 어떤 숫자도 실제 테스트 스위트의 측정값으로 제시되는 것은 아닙니다.
먼저 쌓을 가치가 있는 세 가지 게이트
에이전트 패치는 세 가지 구별 가능한 방식으로 테스트를 깨뜨리며, 각각은 다른 오라클(oracle)을 필요로 합니다.
| 게이트 | 답변하는 질문 | 일반적인 메커니즘 |
|---|
property | 동작이 여전히 불변성(invariants)을 만족하는가? | 변경되지 않은 공개 표면(public surfaces)에 대한 가설/속성 테스트 |
| ... | | |
이 세 가지는 실행하기 쉽고 리뷰어에게 설명하기도 쉽습니다. 이것이 바로 사람들이 계속해서 더 많은 게이트를 추가하는 이유이며, 동시에 중복성 문제가 발생하는 정확한 이유입니다.
1단계 — 프로브 세트 구축: 프로브당 한 클래스
게이트의 가치는 얼마나 자주 실패하느냐가 아닙니다. 그것은 몇 개의 서로 다른 알려진 결함 패치(known-bad patches)를 포착하는가입니다. 이를 측정하려면, 알려진 결함 패치가 필요하며, 각 패치는 포착이 귀속될 수 있도록 정확히 하나의 레이블을 가져야 합니다.
두 가지 출처를 사용하십시오: 되돌린 실제 수정 사항(에이전트의 수정을 되돌리고 테스트는 유지합니다. 만약 스위트가 여전히 통과한다면, 그것은 동어반복입니다) 및 한 클래스에 국한된 수동 작성 뮤턴트(hand-written mutants). 레이블은 거칠게 유지하십시오.
patch_id,probe_class,gate,verdict
p01,functional,property,fail
p01,functional,fixture,pass
...
verdict=fail은 해당 프로브가 게이트에 의해 차단되었음을 의미합니다. probe_class=clean은 모든 게이트가 수용해야 하는 알려진 양호한 패치를 나타내며, 이 행들로부터 오탐률(false-block rate)을 알 수 있습니다. 세 가지 클래스에 걸쳐 대략 20개의 나쁜 프로브만으로도 구조를 파악하기 충분하며, 40개 정도면 순서에 대한 신뢰성을 확보할 수 있습니다. CSV 파일은 처음 작성할 때는 손으로 하는 것이 좋습니다. 하루가 걸릴 수도 있지만, 이 부분만큼은 자동화할 수 없습니다.
# gate naming must match the registry keys you use in CI
cut -d, -f3 probes.csv | tail -n +2 | sort -u
# fixture
...
2단계 — 모든 게이트를 모든 프로브에 실행하기
신뢰하는 게이트만 실행해서는 안 됩니다. 중요한 것은 행렬 자체입니다. 클린(clean)한 프로브를 포함하여 모든 프로브에 대해 세 개의 게이트를 모두 실행하고, 통과할 것이 확실하다고 생각되더라도 판정 결과를 기록해야 합니다.
두 가지 실질적인 참고 사항이 있습니다. 첫째, flake 게이트는 의미를 갖기 위해 반복 실행이 필요합니다. 알려진 불안정한(flaky) 테스트를 단 한 번만 실행하는 것은 판정이 아니라 동전 던지기와 같습니다. 둘째, 비용은 결정의 절반을 차지하며 아무도 한 달 뒤에 기억하지 못하므로, 게이트의 실제 경과 시간(wall-clock minutes)을 같은 파일이나 형제 파일에 기록해야 합니다.
for g in property fixture flake; do
/usr/bin/time -f "$g %e" ./run_gate.sh "$g" probes.csv
done 2>&1 | tee gate_minutes.txt
3단계 — 행렬 점수화하기
아래 스크립트는 행렬을 게이트당 네 가지 숫자(탐지율(catch rate), 오탐률(false-block rate), 고유 탐지 건수(unique catches), 고유 탐지 건당 비용)로 변환합니다. 고유 탐지 건수는 해당 게이트가 그 자리를 차지할 가치가 있는지 알려주는 유일한 열입니다.
#!/usr/bin/env python3
"""gate_overlap.py — agent-patch 게이트의 중복성 보고서.
...
"""
실행합니다:
python3 gate_overlap.py probes.csv property=4.5 fixture=1.0 flake=9.0
4단계 — 점수판이 아닌 결정으로 결과 읽기
보고서는 하나의 형태를 가집니다. 아래 값들은 합성 입력(synthetic input)에서 가져온 형식 예시일 뿐이며, 귀하의 저장소를 포함하여 어떤 리포지토리의 벤치마크가 아닙니다.
| 게이트 | 차단됨 | 포착됨 | 오탐(False blocks) | 고유값 | 최소값 | 최소값/고유값 |
|---|
| property | 9 | 9/20 | 0/6 | 4 | 4.5 | 1.1 |
| ... | | | | | | |
이 세 가지 사항을 순서대로 읽으십시오.
- 먼저 블라인드 스팟(Blind spots)부터 확인합니다. '블라인드 스팟' 목록이야말로 실제 백로그입니다. 게이트 자체가 결과물이 아니라, 커버리지(coverage)가 핵심입니다. 포착률이 뛰어나지만 블라인드 스팟이 두 개 있는 게이트는 아무것도 없는 평범한 게이트보다 상황이 더 나쁩니다.
- 다음으로 고유 포착값(Unique catches)을 확인합니다. 고유 포착값이 0인 게이트는 게이트가 아니라, 실행 시간이 긴 중복물에 불과합니다. 여전히 차단 경로에서 '깊이 방어(defense in depth)'로서의 가치는 있지만, 확장해서는 안 됩니다.
- 마지막으로 고유 포착당 비용(Cost per unique catch)을 고려합니다. 이 비용을 추가 프로브(probe) 하나를 작성하는 비용과 비교하십시오. 만약 게이트가 고유 포착당 9분의 비용이 들고, 같은 노력으로 세 개의 프로브를 작성할 수 있다면, 프로브를 작성해야 합니다.
중복성이 높은 경우 유용한 휴리스틱: Jaccard 유사도가 0.6 이상이고 고유값이 1 이하인 경우, 병합(merge)하거나 격하(demote)해야 합니다. 격하한다는 것은 실패 시 패치 작성자의 주의를 소모하지 않도록 블로커(blocker)가 아닌 경고(warning)로 실행하는 것을 의미합니다.
단계 5 — 동결된 테스트 건수를 정직하게 계산하기
동결된 상태의 불안정한 테스트는 빌린 판결과 같습니다. 이는 실패할 수 없다는 뜻이며, 그 결과 해당 게이트 하류에 있는 모든 것은 아무런 정보가 담기지 않은 '통과(pass)'를 읽게 됩니다. 이를 고려하지 않으면, 포착률 표가 조용히 부풀려집니다.
레지스트리(registry)는 기계가 읽을 수 있도록 유지하고, 동결 사유로 날짜가 아닌 반례(counterexample)를 요구해야 합니다:
- test_id: tests/test_billing.py::test_proration_rounding
frozen_at: 2026-10-24
...
그런 다음 보고서에 회계 규칙을 적용하십시오: 활성 동결(active freeze)은 모든 게이트에 '통과'를 기여하므로, 분모에서 동결된 테스트 건수를 빼고 그 개수를 별도로 coverage_unknown으로 출력해야 합니다. 프로브 40개와 동결된 테스트 6개를 가진 파이프라인은 40개가 아니라 실제 프로브가 34개인 것입니다. 두 숫자를 모두 출력하여 아무도 조용히 동결 목록을 넓혀서 표를 더 좋아 보이게 만들지 못하게 해야 합니다.
CI에서 해당 규정을 강제하고, freeze 상태가 카운터예시(counterexample)를 가지지 못하거나 명시된 조건을 벗어난 경우 빌드를 실패시키는 검사를 추가해야 합니다:
python3 - <<'PY'
import yaml, sys
entries = yaml.safe_load(open("flake_registry.yaml")) or []
...
이것이 바로 전체 freeze 규율입니다. 플레이크(flake)는 증거와 함께 재현되고 소유자에게 격리되거나, 아예 freeze되지 않은 상태 중 하나여야 합니다.
무료 등급이 진정으로 적합한 영역
이 워크플로우에서 비용이 많이 드는 부분은 점수화(scoring)가 아닙니다. 프로브(probes)와 뮤턴트(mutants)를 초안 작성하고, 안정적인 판결을 얻기 위해 flake 게이트를 여러 번 재실행하는 것입니다. 이 두 과정 모두 급증적이고(bursty), 낮은 위험도이며, 놀라울 정도로 병렬화가 가능합니다.
바로 이런 지점에서 무료 모델 등급과 무료 서버 옵션이 합리적인 조합을 이룹니다: 모델은 후보 뮤턴트와 프로브 설명을 초안 작성하고, 하네스(harness)는 사용자가 비용을 지불하는 CI 러너에서 반복 및 점수화 루프를 실행합니다. 공지: 본 기사는 MonkeyCode의 제품 아웃리치(product outreach)의 일환으로 준비되었습니다. 저는 측정된 가용성, 처리량 또는 하드웨어에 대한 보고가 아니라 제가 사용할 워크플로우를 설명하고 있습니다. 이러한 주장은 운영자가 제공하는 것이며 시간이 지남에 따라 변경되므로, 의존하기 전에 반드시 직접 검증해야 합니다.
경계를 명확하게 유지하세요. 초안 작성과 드라이-런(dry-running)은 무료 용량에서 처리되어야 하며, 차단 결정(blocking decision)은 사용자가 통제하는 자체 CI에 속해야 합니다. 왜냐하면 사고 발생 시 온디맨드(on demand)로 재실행할 수 없는 게이트는 진정한 게이트가 아니기 때문입니다. 만약 무료 등급이 부하 상태에서 대기열을 만들거나 속도 제한(rate-limits)을 한다면, 프로브 초안 작성 단계는 점진적으로 저하되지만(degrades gracefully), 병합 결정은 그러해서는 안 됩니다.
한계점 및 건너뛰어도 되는 경우
이 접근 방식은 이미 가지고 있는 게이트를 측정합니다. 따라서 어떤 게이트도 모델링하지 못하는 결함 클래스(defect class)를 발견할 수는 없습니다. 그러므로 속성 테스트(property tests)가 없는 스위트에서 깨끗한 중복 보고서(clean overlap report)는 단지 여러분의 피처 락(fixture lock)이 외롭다는 것만 알려줄 뿐입니다.
또한, 이 방식은 레이블링할 수 있는 프로브 세트를 가정합니다. 만약 수정 사항을 되돌리는 것이 녹색 상태를 유지하는 스위트(still-green suite)를 거의 생성하지 못한다면, 여러분의 패치는 단일 클래스 프로브에 너무 복잡하게 얽혀 있다는 의미이므로, 무언가를 측정하기 전에 이 문제를 해결해야 합니다.
일주일에 약 20개 미만의 에이전트 패치를 병합하는 경우 이 과정은 건너뛰세요. 장부 정리(bookkeeping) 비용이 제거되는 중복성보다 더 클 수 있습니다. 테스트 스위트가 이미 '실패(fail)'와 '불안정함(flaky)'을 구별하기 어려울 정도로 불안정하다면 건너뛰세요. 왜냐하면 flake 열은 노이즈일 것이기 때문입니다. 그리고 'freeze' 항목에 대해 담당자를 지정할 수 없다면 건너뛰세요. 레지스트리가 한 달 안에 무덤처럼 변해버릴 것입니다.
만약 실행해야 한다면, 분기별로 한 번 실행하고 테이블을 비교(diff)하세요. 게이트 오버랩(Gate overlap)은 상수가 아닙니다. 스위트와 패치가 변경됨에 따라 변동합니다. 그리고 fixture가 property를 중복하기 시작하는 분기가 바로 이 둘을 병합하고, 계속 놓치고 있는 클래스에 비용을 지출해야 할 분기입니다.