ADR 0064 — Self-review external-LLM cross-rating judge
ADR 0064 — Self-review external-LLM cross-rating judge
- Status: Accepted
- Date: 2026-05-19
- Authors: Hyunsoo Kim
- Related: ADR 0006 (real-data LLM-judge), ADR 0016 (judge↔human agreement gate), ADR 0056 (rationality_judge measurement surface)
- Augments: red-team self-critique (private plan
~/.claude/plans/iterative-stirring-balloon.md) — 자기참조 cycle (외부 anchor 0개) + 인프라:사용 15:1 + signal 6 외부 노출 - Issue: #1032
Context
/self-review-quarterly skill 의 5축 collaboration 채점은 LLM 판정자 = self-review 보고서 작성자 의 폐쇄 루프. self-critique 에서 식별된 구조적 결함 4가지:
- 자기참조 cycle (외부 anchor 0개) —
feedback_portfolio_evaluation.md→project_portfolio_plan.md→feedback_q2_2026_collaboration_review.md→feedback_agent_utilization_strategy.md→ 다시 rubric 의 순환. 외부 검증 (peer review / 다른 판정자) 0개. rubric 결함이 cycle 마다 복제. - 인프라:사용 15:1 — self-review skill 도입 (PR #488) 후 15+ 인프라 PR (#499 / #597 / #604 / #723 / #729 / #745 / #747 / #748 / #771 / #783 / #885 / #1013 / #505 …) 구축했지만 실제 실행 산물은
docs/self-review/Q2-2026.md1개. 측정 인프라 구축이 활동 목적이 됨 (Goodhart’s law). - 판정 비재현성 — 같은 분기에
docs/self-review/Q2-2026.md(5축 1✓4△, 2026-05-13 관측) 와 메모리feedback_q2_2026_collaboration_review.md(재진단 거버넌스ROI △→✓ 회복) 가 불일치. 같은 raw signal 에 LLM session/시점마다 다른 등급. - signal 6 외부 노출 위험 — PR #505 가 “Claude 협업 측정” 을 채용 시그널로 포지셔닝. 외부 reviewer 가 코드를 열면 위 결함 발견 → signal 6 가 역효과.
해결 = 외부 LLM anchor 도입으로 자기참조 cycle 을 깸. 단 paid-API “신규” 의존성을 만들지 않기 위해 기존 LLM judge 인프라 재사용 (ADR 0006 / 0056 의 eval/judges/judge_common.build_openai_client + stub-default 패턴).
Decision
- 신규 judge
eval/judges/self_review_judge.py— 기존 judge 패턴 (llm_judge.py/rationality_judge.py) 동일 구조.- 시그니처:
judge_self_review(stats: dict, operator_verdicts: dict[str, str] | None = None, *, backend: str = "stub") -> tuple[dict, dict]—(local, aggregate). - 입력:
scripts/claude-hooks/_self_review.py --quarter <Qx-YYYY> --emit-stats출력 stats.
- 시그니처:
- 두 backend (stub-default 패턴):
stub— deterministic. SKILL.md 라인 127-140 임계값 표를 직접 적용 (보류했던 결정론 채점기를 흡수). 비용 0, CI 경로. 3 자기 통과 가드 포함:evidence_age_days < 1.0자동 △ (시계열 순환논증 차단) /pretooluse_loadbearing_fires == 0자동 ✗ (Goodhart 침묵 차단) /prs_evaluated < 10자동 △ (sample sanity).openai_compatible—build_openai_client()+call_openai_json()로 외부 LLM (BIDMATE_JUDGE_MODEL, vendor-agnostic) 에게 같은 rubric prompt 전달. opt-in (env 미설정 시RuntimeError, stub-only 경로 무비용).
-
Verdict 어휘 =
JUDGE_STATUSES재사용 —✓ → supported,△ → partial,✗ → insufficient. 기존judge_agreement.py의cohens_kappa/compute_agreement를 LABELS 매핑만으로 재사용. -
inter-rater agreement — operator verdict (
docs/self-review/Qx-YYYY.md5축 파싱) vs judge verdict →cohens_kappa. aggregate ={n, cohens_kappa, agreement_rate, confusion}. 결과는reports/self_review_agreement/<Qx-YYYY>.json(committable, body excerpt 0). - 신규 의존성 0 —
openai>=2.36.0은 이미requirements-dev.txt(opt-in). production 미import. stub backend 는 stdlib only.
Why these specific choices
| 결정 | 근거 |
|---|---|
| 외부 LLM anchor | 자기참조 cycle 을 깨는 방법은 다른 판정자. 사람 평가자는 섭외 의존 (dead-code 위험), 외부 LLM 은 자동 + 재현 |
| 기존 judge 인프라 재사용 | B 도구화의 “인프라 또 쌓기” (16:1) 함정 회피. 새 코드 50-80줄, 재사용 ~200줄 |
| stub = 결정론 채점기 흡수 | 보류한 결정론 채점기를 별도 PR 대신 judge 의 stub backend 로 — 단일 surface |
✓/△/✗ → JUDGE_STATUSES 3값 |
judge_agreement.py 의 kappa/rho 를 LABELS 매핑만으로 재사용 |
| opt-in openai backend | CLAUDE.md Non-goals “신규 paid-API 의존성” 준수 — 기존 opt-in dep 재사용, stub 이 기본 |
Consequences
긍정:
- 자기참조 cycle 에 외부 판정자 1개 도입 — agreement κ < 0.6 (Cohen’s “substantial” 미만, ADR 0016 기준) 시 rubric 자체 의심 신호.
- stub backend 가 결정론 채점기 역할 — LLM session noise 제거 + CI 재현성 100%.
- 기존 judge 패밀리 일관 — 유지보수 표면 단일.
부정 / 위험:
- stats 시점 불일치 —
docs/self-review/Q2-2026.md(2026-05-13 관측) 와 현재--emit-stats재생성분이 그 사이 commit/memory 변화로 다를 수 있음. agreement 측정의 noise. prototype 한계로 명시. - “LLM 이 LLM 채점” — same-family 편향. 다른 vendor 모델 권장하지만 완전한 외부성 (사람) 아님.
- agreement 가 인프라:사용 비율을 직접 고치지 않음 — 실행 규율 (분기 1회 실제 돌리기) 은 별도. 이 judge 도 안 돌리면 16:1 이 됨.
Verification
첫 실행 결과 (Q2-2026, stub backend)
reports/self_review_agreement/Q2-2026.json (재현: python eval/judges/self_review_judge.py --stats <Q2 stats> --operator-verdicts <Q2 verdicts> --backend stub):
| 지표 | 값 |
|---|---|
| n | 5 (분기 1개, 축 전체) |
| raw 일치 | 0/5 (5축 전부 불일치) |
| Cohen’s κ (unweighted) | −0.389 |
| weighted κ (linear / quadratic) | −0.316 / −0.190 |
| Spearman ρ | −0.395 |
| passes (κ ≥ 0.6) | false |
해석 — “rubric 신뢰성의 정량적 반증” 이 아니라 1차 방향성 신호. 세 caveat 가 주장 강도를 제한한다:
- n=5, 분기 1개. 5는 표본이 아니라 축 전체 모집단이라 축 resample bootstrap 은 부적절 — 진짜 불확실성은 분기 수 로만 줄어든다. 다분기 누적 전까지는 점추정.
- Ordinal 척도에 unweighted κ 는 과벌점. ✓↔△(인접)와 ✓↔✗(반대)를 동일 처벌한다. 거리 가중 κ (linear −0.316, quadratic −0.190) 가 덜 극단적 — 본 PR 이 aggregate 에 두 값을 함께 기록하는 이유 (gate 는 ADR 0016 관례대로 unweighted κ 유지, 가중치는 정직성용 병기).
- stats 시점 드리프트 (가장 큰 교란). axis_3 stub=✓ (오늘 재생성분
fires=66) vs operator=△ (Q2 보고서 사유 “.hook-fires.log미생성, 정량화 불가”) — operator 는 Q2 시점 빈 로그로, stub 은 누적 후 stats 로 채점. 이 불일치는 rubric 결함이 아니라 위 Consequences 의 “stats 시점 불일치” 위험의 실측 사례. agreement ≠ validity (κ 는 “누가 맞나” 가 아니라 “둘이 다르다” 만 잰다).
→ 외부/결정론 anchor 가 operator Q2 verdict 와 0/5 불일치 — 자기참조 cycle 에 외부 anchor 1개 도입이라는 본 ADR 의 목적 자체는 달성. 단 “rubric 재설계 트리거” 로 승격하기 전 (a) 시점정합 stats (operator 채점 시점 = stub 입력 시점), (b) 다분기 n 확보, (c) weighted κ gate 채택 여부 결정이 선행되어야 한다.
후속:
- 사용자 key 설정 시
--backend openai_compatible1회 실행 → 외부 LLM verdict 와의 agreement (same-family 편향은 다른 vendor 모델로 완화). - [완료 #1166] collector 가
evidence_age_days를 emit → 시간분리 가드 실제 발화._self_review.compute_evidence_age_days가 freshest 근거 timestamp (PR merge·ADR accepted·memory mtime·ghmerged_at) 대비 나이를 산출하고, 가드는None(미측정)도 보수적으로 ✓→△ 처리. 재실행 델타 (Q2-2026, 2026-05-21,evidence_age_days≈0.014 < 1.0): 가드 발화로 stub axis_3/4/5 가 ✓→△ → operator (axis_1=✓, 나머지 △) 대비 raw 일치 0/5 → 3/5, κ −0.389 → −0.111 (여전히passes=False). 첫 실행 결과(위 표, 2026-05-19 스냅샷)가 진단한 “axis_3 시점 드리프트”의 근본 해결 경로가 실제로 작동함을 확인. 합쳐서, operator JSON 이 5축 전체·유효 verdict 가 아니면_validate_operator_verdicts가ValueError(이전엔 누락 축이 조용히 drop → n=1 → κ=1.0 통과).
Alternatives considered
- 사람 평가자 — 가장 강한 외부 anchor (완전한 다른 판정자). 단 섭외 의존, 안 구해지면 aspirational dead-code. 외부 LLM 이 자동 + 재현 가능해 first step 으로 우선.
- 결정론 채점기 단독 (외부 LLM 없이) — LLM 주관 제거하나 자기참조 cycle 은 그대로 (rubric 본인 작성). 외부 anchor 아님. 본 ADR 의 stub backend 로 흡수.
- 신규 Anthropic SDK 직접 추가 — paid-API 신규 의존성 (
Non-goals). 기존openai_compatible(vendor-agnostic) 가base_url+model지정으로 Anthropic 모델도 호출 가능.