검증 루프 — 횡단분석

한 줄 요약

“AI가 제대로 했는지”를 모델의 자기선언이 아니라 코드·심판·영수증으로 확인하는 장치가 검증 루프다. 7개 프레임워크가 같은 문제를 서로 다른 위치에서 막는데, 그 차이를 알아야 내 스택의 “감지만 하고 막지 못하는” 구멍을 메울 수 있기 때문에 배운다.

그림

검증 메커니즘이 작업 흐름의 어느 지점에 끼어드는지를 보면 7개 프레임워크의 차이가 한눈에 정리된다.

flowchart TD
    U[사용자 지시] --> W[AI 작업 수행]
    W --> D{"어디서 막는가?"}

    D -->|런타임 훅 차단| H["① Stop 훅<br/>Claude Code · fable-ish<br/>완료 선언을 거부"]
    D -->|LLM 심판 외주| L["② 별도 AI 세션<br/>Codex ReviewTask · Guardian<br/>의미 결함을 잡음"]
    D -->|결정적 코드 게이트| C["③ admit / gate.mjs<br/>내 패턴<br/>형식·계약 전수검사"]
    D -->|무결성 도장| G["④ sha256 봉투<br/>gajae<br/>상태 변조 여부"]
    D -->|진화적 수렴| E["⑤ 평가→반성→재생성<br/>ouroboros<br/>수렴까지 반복"]

    H --> J{"통과?"}
    L --> J
    C --> J
    G --> J
    E --> J

    J -->|FAIL| R["재시도 / 루프 재개"]
    R -.->|상한 도달| STOP[양보 또는 quarantine]
    R --> W
    J -->|PASS| OK[완료 인정]

    style H fill:#e8f0ff
    style L fill:#fff0e8
    style C fill:#e8ffe8
    style G fill:#f0e8ff
    style E fill:#ffe8f0

쉽게 풀기

직원에게 일을 시켰다고 상상하자. 직원이 “다 끝냈습니다”라고 말한다. 이 말을 그대로 믿어도 될까? 검증 루프의 제1원리는 “다 했습니다”는 증거가 아니다이다. AI도 똑같다. 통계적으로 “완료했다”고 말하는 데는 비용이 들지 않으니, 완료 선언과 완료 증거를 구조적으로 분리해야 한다.

그럼 누가 확인할까? 7개 프레임워크는 다섯 가지 방식으로 갈린다.

  1. 문지기(Stop 훅) — 사무실 출입문에 경비를 세운다. 직원이 “퇴근합니다(=완료)” 하면 경비가 “검증 돌렸어? 안 했으면 못 나가” 하고 막는다. Claude Code는 문에 경비 자리만 만들어 두고 비워뒀고(범용 플랫폼이라 의도적), fable-ish가 그 자리를 규칙·예외처리까지 갖춘 진짜 경비로 채웠다.

  2. 검사관(LLM 심판) — 일을 또 다른 사람(AI)에게 보내 “이거 괜찮은지 봐줘” 한다. 테스트로 못 잡는 설계 결함까지 본다. Codex가 이 방식인데, 검사관이 시간 안에 답을 못 주거나 말이 꼬이면 “일단 거부”(fail-closed)로 안전하게 처리한다.

  3. 양식 검사기(결정적 코드 게이트) — 서류 양식이 맞는지 기계가 도장 찍듯 검사한다. 인용이 없으면 자동 반려. 같은 입력엔 항상 같은 판정(재현 100%). 내 패턴의 admit/gate가 이것이다. 단, “양식은 맞는데 내용이 틀린” 경우는 못 잡는다.

  4. 봉인 스티커(무결성 도장) — “결과가 옳은가”가 아니라 “중간에 누가 손댔는가”를 본다. 택배 상자에 봉인 스티커(sha256)를 붙여 뜯긴 흔적이 있는지 확인하는 식. gajae는 긴 워크플로의 상태가 변조되지 않았음을 보증한다.

  5. 반복 다듬기(진화적 수렴) — 한 번에 끝내지 않고 “평가 → 반성 → 다시 만들기”를 답이 안정될 때까지 돈다. ouroboros는 “단발 생성은 숨은 가정 때문에 실패한다”는 전제에서 출발해, 검증 피드백으로 작업 지시(사양) 자체를 진화시킨다.

여기에 모두가 공유하는 안전장치 두 가지가 더 있다. 하나는 무한루프 방지(거부→재시도가 영원히 핑퐁 치지 않게 상한을 둔다). 다른 하나는 만든 사람 ≠ 검사하는 사람(같은 머리가 자기 답을 채점하면 확증편향이 생긴다).

핵심 정리

프레임워크검증 방식실패 시
Claude CodeStop 훅 골격(빈 슬롯)fail-open, 8회 후 양보
fable-ishStop 훅 완성형 + 증거원장fail-open, 2회 후 양보
Codex별도 LLM 리뷰 + Guardian 심판승인 경계는 fail-closed
OMC역할분리 + critic 적대리뷰ADVISORY 비차단(구멍)
gajaesha256 무결성 영수증쓰기 fail-closed / 읽기 fail-open
ouroboros진화 루프 다축 수렴 게이트다신호 미충족 시 계속 반복
내 패턴결정적 코드 게이트(admit/gate)exit 2 물리거부 / quarantine

모두가 수렴하는 5가지 공통 설계

  1. 자기선언은 증거가 아니다 — 완료 선언과 완료 증거를 구조적으로 분리한다(왜: LLM은 “완료했다”고 말하는 비용이 0).
  2. 판정을 LLM 밖 결정적 레이어로 — Stop 훅·gate.mjs·sha256·임계값이 코드로 판정한다(왜: soft 지시는 안 지키는 순간이 온다).
  3. 무한루프 방지가 1급 설계 — MAX_STOP_BLOCKS·8회 상한·서킷브레이커·quarantine(왜: 거부→재시도 루프는 상한 없으면 영원히 갇힌다).
  4. writer ≠ reviewer — self-approve 금지(왜: 같은 컨텍스트가 자기 출력을 검증하면 확증편향).
  5. 상태/증거의 원자적 영속화tmp.write → rename 원자 교체(왜: 검증 상태가 깨지면 잘못된 PASS/FAIL).

검증의 종류를 섞지 말 것 (4열 분해)

Codex 교차검증의 핵심 지적: 한 축으로 묶지 말고 “무엇을 / 언제 / 누가 / 실패 시” 4열로 나눠 봐야 한다.

  • 완료 차단 gate — Claude Stop, fable-ish Stop gate
  • 승인/보안 gate — Codex Guardian
  • 의미론적 리뷰(advisory) — Codex ReviewTask, OMC critic/verifier
  • 상태 무결성/provenance — gajae 영수증, 본문 에코금지, sha256
  • 산출물 schema/pipeline gate — 내 패턴 admit/gate
  • 반복 수렴/evaluation loop — ouroboros

차단력의 강도(되돌릴 수 없는 경계일수록 강하게):

  • 강함: 내 패턴 exit 2·quarantine = gajae 쓰기 fail-closed = Codex Guardian 승인 경계
  • 중간: fable-ish Stop-block(2회 후 양보) > Claude Code(8회 후 양보)
  • 약함: OMC verify-deliverables ADVISORY 비차단 — 문화는 최강, 강제력엔 구멍(#3209 회피로 후퇴)

fail-open vs fail-closed 철학

“되돌릴 수 있는 검증”(Stop 훅·재시도)은 fail-open(모델을 안 가둠 → 생산성 보호), “되돌릴 수 없는 행동”(승인·상태 쓰기·commit/deploy·외부 API)은 fail-closed(막힐 때 거부 → 품질 보호)가 합리적 기본값. 주의: “디스크 쓰기 전부 fail-closed”는 너무 넓다 — 코드 파일은 git으로 되돌릴 수 있으니 차라리 완료 선언을 막는 게 낫다.

실제 예시

각 프레임워크가 실제로 쓰는 형식·스키마 핵심이다.

# Claude Code — Stop 훅 계약 (CC_30_hooks)
# stdin:  {stop_hook_active, transcript_path, last_assistant_message}
# stdout: {"decision":"block", "reason":"..."}   # reason이 다음 입력으로 주입(사용자 지시처럼)
# PostToolUse 차단: top-level decision:block 계열 (permissionDecision:deny는 PreToolUse용)
# 안전장치: stop_hook_active 재진입 차단 + 8회 연속 block이면 하네스가 훅 무시
# fable-ish — should_block_stop() 핵심 (hooks/stop_gate.py)
# verified = verification_results[].success is True 가 "하나라도" 있어야 함
# (명령을 실행만 한 것으로는 불충분 — 이 한 줄이 핵심)
def should_block_stop(ledger, mode):
    if ledger.changed_files_seen and not ledger.verified:
        return {"decision": "block",
                "reason": f"{mode} 모드라 변경 동작 검증이 필요합니다"}
    # MAX_STOP_BLOCKS=2 로 2번만 막고 양보, stop_hook_active로 재진입 차단, 전 훅 fail-open
// Codex — 검증 = 별도 sub-codex 리뷰 작업 (codex-rs/core/src/tasks/review.rs)
// ReviewTask(TaskKind::Review): 자식 스레드가 노이즈를 가두고 "요약 한 장"만 부모에 중계
// reviewer role은 developer_instructions로 "severity순 findings + file:line, summary만"
// JSON 파싱 실패 시 텍스트를 설명으로 보존하는 fallback 존재 (review.rs:190)
//
// Guardian(core/src/guardian/review.rs): 승인 검토 "경계"에서 동작 — 여기가 fail-closed
//   타임아웃/파싱 실패 → 차단 쪽. 트랜스크립트를 "untrusted evidence"로 명시(인젝션 방어)
//   연속 거부 3/10회면 서킷브레이커
// gajae — 이중 영수증 (packages/coding-agent/src/gjc-runtime/)
// WorkflowStateReceipt: version·skill·owner·command·content_sha256·status(fresh/stale)·mutation_id
// CliWriteReceipt(cli-write-receipt.ts): 본문 에코 금지 — 모델은 라우팅 필드만 받음(토큰 누수 방지)
//   { ok, path, sha256 }   ← parent는 path+sha256으로 원문을 다시 조회·검증 가능해야 함
// 비대칭: 쓰기=strict fail-closed, 읽기=lenient fail-open
# ouroboros — 수렴은 단일 신호 금지 (src/ouroboros/evolution/convergence.py)
# similarity 경로(eval>=0.7 AND sim>=0.95)는 종료 신호의 "하나"일 뿐
# 그 외: max generations, stagnation, oscillation, repetitive feedback 도 종료 신호
# drift 게이트: goal0.5 / constraint0.3 / ontology0.2, 합 <=0.3 (observability/drift.py)
# LedgerEntry: evidence vs assumption 분리 (추측/근거 절대 안 섞음)
// 내 패턴 — 결정적 코드 게이트 (akh2/pipeline/admit.mjs, gate.mjs)
// admit.mjs: frontmatter 계약 전수검사(slug-unique·domain=폴더·0<confidence<=1·sources 강제)
//            + raw source + phase gate 검사
// gate.mjs:  requireMilestone 미통과 시 exit 2 로 후속 물리거부
// COPYFILE_EXCL 로 동시입고 race 차단
// 주의: claim-card 인용 강등(인용없으면 strength=약)은 별도 AGENTS.md 규칙
//       "2회 실패→quarantine"은 운영 정책 (admit/gate.mjs 코드 자체엔 없음)

요약 & 셀프체크

  • 검증 루프 = “AI의 자기선언을 안 믿고 코드·심판·영수증으로 확인”하는 장치이며, 5가지 위치(훅 차단 / LLM 심판 / 코드 게이트 / 무결성 도장 / 진화 수렴)로 갈린다.
  • 7개를 한 축으로 묶지 말고 “무엇을 / 언제 / 누가 / 실패 시 fail-open인가 closed인가” 4열로 분해해야 정확하다(검증 실행 증거변경 커버리지도 별개 축).
  • 내 스택의 처방: fable-ish 차단형 Stop 게이트(강제력) + 내 영수증(추적성) + Codex fail-closed 심판(의미검증, 비가역 경계에서만) + gajae 본문 에코금지(토큰효율)를 OMC 역할분리 문화 위에 얹는다.

스스로 답해보기:

  1. fable-ish에서 verified를 “명령 실행”이 아니라 “verification_results[].success is True 하나라도”로 정의한 이유는?
  2. 같은 스택 안에서 Stop 훅은 fail-open인데 commit/승인은 fail-closed로 두는 기준은 무엇인가?
  3. OMC가 “검증 문화는 최강인데 강제력엔 구멍”이라는 역설이 생긴 직접 원인은?

내 스택에 차용할 구체안

내 운영 스택 = Claude Code 호스트 + Codex 보조 + OMC 오케스트레이션. 각 프레임워크의 최강점만 합성하되, “어느 경계에서 막을 것인가”를 다시 설계한다.

  1. fable-ish Stop-게이트를 차단형으로 이식 (최우선). OMC의 ADVISORY 비차단 구멍을 메운다. 단 SubagentStop이 아니라 leader/main Stop 또는 phase-transition gate에 붙여 #3209 루프 재발을 피한다. {changed_files_seen && !verified → decision:block} + PostToolUse(^(Bash|Edit|Write|MultiEdit)$)로 session-scoped ledger 누적 + MAX_STOP_BLOCKS=2 + stop_hook_active + 전체 fail-open. changed-path attribution과 coverage check를 함께 넣는다.
  2. 영수증 3종 표준화. git trailer(Constraint/Rejected/Confidence/Scope-risk/Not-tested)에 더해 게이트 PASS/FAIL JSON({pass, failures, retry_targets})과 샤드 decision-log(phase/input/decision/output/gate/next_action). 오케스트레이터는 산출물 전체가 아니라 요약 신뢰신호만 읽어 토큰 절약.
  3. gajae 본문 에코금지 채택. 서브에이전트는 {ok, path, sha256}만 반환. 단 parent가 path+sha256으로 원문을 다시 조회·검증할 수 있게 해야 디버깅성·신뢰성이 유지된다.
  4. Codex식 별도 LLM reviewer + fail-closed는 비가역 경계에서만. commit·release·destructive write·외부 API에만 hard block. 일반 개발 루프에서 reviewer 타임아웃을 전부 차단으로 만들면 생산성이 깨진다. self-approve 금지 + 트랜스크립트를 untrusted evidence로 명시.
  5. fail 정책을 되돌림 가능성 기준으로 명문화. Stop 훅·재시도 = fail-open. approval receipt·state ledger·commit/push/deploy·외부 API = fail-closed. (코드 파일 쓰기 자체는 fail-closed 대상에서 제외 — git으로 되돌릴 수 있으니 완료 선언을 막는 편이 낫다.)
  6. (선택) ouroboros 다축 게이트는 ralph/ultraqa 루프에만, 축약형으로. full ontology loop 대신 tests pass + review pass + coverage evidence + no known blockers + max iteration로 줄여 조기 종료를 막는다.

근거 — 참조한 기능노트 및 소스/문서 경로

기능노트

소스/문서

  • /home/seunghyeong/harness-work/fable-ish/hooks/stop_gate.py, scripts/verify_state.py, scripts/ledger.py, hooks/post_tool_use.py
  • /home/seunghyeong/harness-work/codex/codex-rs/core/src/tasks/review.rs, core/src/guardian/review.rs, core/src/guardian/approval_request.rs
  • /home/seunghyeong/harness-work/ouroboros/src/ouroboros/{evolution/loop.py,evolution/convergence.py,observability/drift.py,evaluation/pipeline.py,auto/ledger.py}
  • packages/coding-agent/src/gjc-runtime/{cli-write-receipt.ts,state-schema.ts,state-writer.ts,deep-interview-recorder.ts} (gajae-code)
  • /mnt/d/akh2/pipeline/admit.mjs, gate.mjs, golden-queries.json; /mnt/d/human-token-workflow/AGENTS.md (내 패턴)
  • /home/seunghyeong/.claude/plugins/marketplaces/omc/skills/verify/SKILL.md; OMC CLAUDE.md <execution_protocols>(self-approve 금지)·<team_pipeline>
  • /home/seunghyeong/.claude/plugins/marketplaces/omc/scripts/verify-deliverables.mjs (continue:true·suppressOutput:true 비차단)
  • /home/seunghyeong/harness-work/claude-code/plugins/ralph-wiggum/hooks/stop-hook.sh (Stop decision:block 실동작)
  • 공식문서: /mnt/d/6study/10_프레임워크분석/_원문아카이브/claude-code/(135p), _원문아카이브/codex/02_agent-approvals-security.md(88p)

연결

_분석축_루브릭 · HOME

Codex 교차검증 — 메타검증 보존

전체 판정: 큰 구도는 맞지만, “검증 루프”라는 한 축으로 서로 다른 장치를 너무 세게 묶었다. 수정의 출발점은 각 프레임워크를 “무엇을 막는가 / 언제 막는가 / 누가 판정하는가 / 실패 시 fail-open인가 closed인가” 4열로 재분해하는 것.

주요 교정점:

  • Codex: ReviewTask(별도 sub-codex 리뷰, JSON 실패 시 텍스트 fallback 존재 → strict/fail-closed 아님)와 Guardian(승인 경계, 타임아웃/파싱실패=차단)은 다른 메커니즘. “유일하게 AI로 외주”는 과장(OMC verifier·ouroboros semantic도 AI judge). 특이점은 “승인 경계에서 Guardian을 fail-closed로 둔다”는 점. 근거 review.rs:124,190, guardian/review.rs:168.
  • gajae deep-interview 단조성: 방향이 반대였음. “질문할수록 ambiguity↓“가 아니라, active trigger가 남으면 다음 라운드 ambiguity가 더 높아야 하고 해당 차원 clarity가 개선되면 안 된다는 일관성 검사. 직접적 실행 승인 로직이 아님. 근거 deep-interview-recorder.ts:178.
  • ouroboros 수렴: eval>=0.7 AND sim>=0.95가 전부 아님 — max generations/stagnation/oscillation/repetitive feedback도 종료 신호. consensus는 조건부(항상 3단계 아님). drift-monitor.py는 경량 advisory 알림이고 실제 metric 계산은 core. 근거 convergence.py:41,100, pipeline.py:241, drift-monitor.py:7.
  • Claude Code: PostToolUse permissionDecision:deny는 형식 오류 — permissionDecision은 주로 PreToolUse용, PostToolUse/Stop 차단은 top-level decision:block 계열.
  • OMC verify-deliverables: “감지하지만 non-blocking”보다 더 약함 — continue:true·suppressOutput:true로 조용히 통과, 되먹임 경로가 사실상 꺼져 있음. 근거 verify-deliverables.mjs:14,221.
  • 내 패턴: admit/gate.mjs(frontmatter·raw source·phase gate)와 claim-card 인용 강등(AGENTS.md 규칙)은 별개. “2 fail quarantine”은 코드 근거 미확인(운영 정책). 근거 admit.mjs:109, gate.mjs:19, AGENTS.md:7.

빠진 축: ① 검증 종류를 안 나눔(위 4열) ② “검증을 실행했는가” vs “변경 범위를 커버했는가”는 별도 열로. 차용안 현실성: fable-ish gate는 main Stop/phase-transition에(SubagentStop 금지), Codex fail-closed reviewer는 비가역 경계에서만, “디스크 쓰기 fail-closed”는 너무 넓다(approval/ledger/commit/외부 API로 한정), ouroboros 다축은 축약형으로.