Agora

SkillMedia

Use when a design or decision needs adversarial multi-round scrutiny from several independent model vendors. Runs anonymized A/B/C reviewer rounds with a rotating judge and produces a consensus report.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Agora skill

What this skill tells your AI

The instructions your AI receives, as published by baekenough/oh-my-customcode in .claude/skills/agora/SKILL.md and read by ahel’s review.

개요

agora는 하나의 설계·결정 주제를 3개 독립 벤더 CLI에게 익명 A/B/C 라벨로 검토시키고, 라운드마다 모델이 바뀌는 심판이 판정하는 다중턴 합의 스킬입니다. 라벨-벤더 매핑은 SEALED/에 격리되어 심판의 입력 경로에 오르지 않으며, 벤더 공개는 최종 보고서 생성 단계에서만 이루어집니다.

이 설계는 결정적 익명성을 주장하지 않습니다. 목표는 "벤더 식별 불가"가 아니라 "벤더 식별이 심판의 기본 관찰 경로에 놓이지 않음" 수준입니다. 라벨 뒤에 벤더가 계속 숨겨진다고 가정하지 마십시오 — 남는 누출 경로는 신뢰 경계에 열거되어 있습니다.

리뷰어 3벤더(claude -p, omx exec, agy -p)와 심판 로테이션 3슬롯은 모델 단위로만 겹치지 않습니다(스펙 REQ-3). 겹치는 축과 겹치지 않는 축을 구분해 적습니다.

아래 표는 리뷰어를 벤더 슬러그(claude/omx/agy)로 지칭합니다 — A/B/C가 아닙니다. A/B/C는 라운드마다 시드 agora-<epoch>-r<N>으로 다시 섞이는 익명 라벨이므로 어떤 벤더에도 고정되지 않으며, "리뷰어 A" 같은 고정 벤더는 존재하지 않습니다. 따라서 게이트가 출력하는 리뷰어: A BUILD_WITH_CHANGES · B BUILD · C REDESIGN을 벤더 판정으로 읽지 마십시오 — 같은 세션 안에서도 다음 라운드의 A는 다른 벤더입니다. 라벨과 벤더의 대응은 report.md에서만 공개됩니다.

겹침
모델 ID없음 — 리뷰어는 claude-opus-4-8(claude CLI) / omx 기본 모델(omx CLI) / gemini-3.1-pro-high(agy CLI), 심판 슬롯은 1: claude-opus-5(claude CLI) / 2: claude-opus-4-6-thinking(agy CLI) / 3: gpt-oss-120b-medium(agy CLI)
CLI 바이너리있음claude 바이너리는 claude 리뷰어와 심판 슬롯 1이, agy 바이너리는 agy 리뷰어와 심판 슬롯 2·3이 공유합니다
모델 계열있음 — 심판 3슬롯 중 2슬롯(claude-opus-5, claude-opus-4-6-thinking)이 claude 리뷰어의 모델(claude-opus-4-8)과 같은 Claude 계열입니다. 이 2슬롯은 CLI로는 각각 claudeagy이므로 CLI 축과 계열 축은 서로 다른 모양으로 겹칩니다

따라서 "리뷰-판정 간 교차 오염이 없다"고 말할 수 있는 범위는 모델 ID 단위까지입니다. 같은 계열 모델이 공유하는 사전 학습 편향은 이 분리로 제거되지 않습니다.

라운드 파이프라인

각 라운드는 세 스크립트를 순서대로 호출합니다.

단계명령산출
리뷰어bash scripts/reviewers.sh --run --session-dir <dir> --round <N> --prompt-file <f>SEALED/raw/round-N/{claude,omx,agy}.json
익명화bash scripts/anonymize.sh --build --session-dir <dir> --round <N> --seed agora-<epoch>-r<N> --topic <t> --attachments <json> --agenda <json>SEALED/mapping/round-N.json + anon/round-N.json
심판bash scripts/judge.sh --run --anon-file <dir>/anon/round-N.json --out-file <dir>/verdict/round-N.json --round <N>verdict/round-N.json
종료 판정bash scripts/agora.sh --decide-stop < <dir>/state.jsonCONSENSUS|STALLED|MAX_ROUNDS|USER|CONTINUE

라운드 1은 백지 상태로 진행됩니다 — agenda, prior_rounds, 심판 초안 없이 topicattachments만 리뷰어에게 전달됩니다. 세션 전체에서 진짜 독립 의견을 얻는 유일한 라운드이기 때문입니다. 라운드 2부터는 직전 심판의 agenda + 직전 2라운드의 prior_rounds + 직전 초안이 함께 전달되어 수렴을 유도합니다.

CLI 진입점

bash agora.sh --decide-stop < <dir>/state.json
bash agora.sh --start "<topic>" [--attach <path>]... [--max-rounds <N>] [--auto]
bash agora.sh --round <N> --session-dir <dir> [--extra-agenda <json-array>]
bash agora.sh --gate --session-dir <dir> --round <N>
bash agora.sh --set-stop <CODE> --session-dir <dir>
bash agora.sh --report --session-dir <dir>
진입점파일을 쓰는가실행 주체
--decide-stop아니오agora-runner (라운드 실행 직후 정지 코드 산출)
--startagora-runner
--roundagora-runner
--gate아니오오케스트레이터 (사용자 상호작용)
--set-stopagora-runner
--reportagora-runner
채널 계약

--startstdout에는 세션 디렉토리 절대경로 한 줄만 실립니다. 게이트드 모드에서 라운드 1의 게이트 블록은 stderr로 나갑니다 — 문서화된 관용구 dir=$(bash agora.sh --start ...)가 stdout을 통째로 캡처하므로, 게이트가 stdout에 있으면 $dir이 게이트 17줄에 경로가 붙은 문자열이 되어 쓸 수 없게 되기 때문입니다.

반면 독립 서브커맨드 --gate는 블록을 stdout에 출력합니다 — 그 서브커맨드에서는 게이트 렌더링이 부산물이 아니라 산출물 전부이기 때문입니다.

agora-runner--start를 실행하면 게이트 블록은 러너의 stderr로 흘러가 반환 계약에 담기지 못합니다. 오케스트레이터는 --start 반환 후 --gate --session-dir <dir> --round 1로 게이트를 다시 렌더해 사용자에게 표시하십시오.

--set-stop <CODE>

.stop 필드(= report.md종료 사유)를 기록하는 유일한 수단입니다. 게이트드 모드에서 라운드 2 이후에는 .stop에 다른 writer가 없으므로, 이 명령을 건너뛰면 세션이 아무리 깨끗하게 끝나도 report.md종료 사유: UNKNOWN을 출력합니다.

항목
허용 코드CONSENSUS · STALLED · MAX_ROUNDS · USER
거부 코드CONTINUE → exit 64. "아직 정지하지 않았다"는 뜻이므로 종료 사유로 기록될 수 없습니다 — 다음 라운드를 도십시오
그 밖의 미지 코드exit 64. stderr에 허용 목록을 출력합니다
코드 자체가 없거나 --로 시작exit 64
범위세션 단위(--round 없음). .stop은 세션이 어떻게 끝났는지를, .round어디서 끝났는지를 담습니다. 재기록은 멱등이라 무해합니다

허용 코드 집합은 하드코딩이 아니라 decide_stop 함수 본문에서 런타임에 읽어냅니다. 정지 조건이 추가·개명되면 자동으로 따라갑니다.

절차: 호출자는 매 라운드 --decide-stop 결과가 CONTINUE아니면 그 코드를 그대로 --set-stop으로 기록한 뒤 --report를 실행합니다. 사용자가 게이트에서 s를 고른 경우도 --set-stop USER로 직접 기록해야 합니다. (--start --auto와 게이트드 --start의 라운드 1은 이 기록을 스크립트가 내부에서 수행하므로 예외입니다.)

--extra-agenda는 게이트의 e 옵션을 실행하는 유일한 수단입니다. 값은 JSON 배열 문자열이어야 하며(예: --extra-agenda '["롤백 명령 시퀀스를 제시할 것"]'), 심판 agenda[] 뒤에 이어붙습니다. 문자열 하나를 그대로 넘기면 jq 병합이 실패합니다. 생략 시 기본값은 []입니다.

환경 오버라이드:

변수기본값용도
AGORA_SESSION_EPOCH없음 (자동 생성)세션 시드 고정 (재현용)
AGORA_OUTPUT_ROOT.claude/outputs/sessions아티팩트 루트
AGORA_TIMEOUT_SECS300리뷰어/심판 CLI 타임아웃
AGORA_CLAUDE_BINclaude (PATH 탐색)리뷰어·심판의 Claude CLI 경로 오버라이드
AGORA_AGY_BINagy (PATH 탐색)리뷰어·심판의 agy CLI 경로 오버라이드
AGORA_OMX_BIN/opt/homebrew/bin/omx (절대 경로)리뷰어 omx CLI 경로 오버라이드. 다른 셋과 달리 PATH를 탐색하지 않으므로, omx가 이 경로에 없는 머신에서는 반드시 설정해야 합니다
AGORA_FORCE_TIMEOUT_FALLBACK0테스트 전용gtimeout 존재 여부와 무관하게 wait 기반 타임아웃 폴백 경로를 강제합니다. 운영 실행에서는 설정하지 마십시오
AGORA_VERDICT_SCHEMA스크립트 옆의 verdict-schema.json테스트 전용judge.sh가 검증에 쓰는 스키마 경로 대체. 존재하지 않는 경로를 가리켜 exit 68 경로를 재현하는 용도

이 변수들은 자식 CLI에는 전달되지 않습니다 — 아래 벤더 호출 환경 정화를 참조하십시오.

권한 우회 플래그 (실행 전 반드시 확인)

이 스킬은 리뷰어 벤더 CLI 중 둘을 권한 프롬프트를 건너뛰는 플래그와 함께 실행합니다. 운영자 승인을 요구하지 않고 자동으로 붙으므로, 스킬을 돌리기 전에 이 사실을 알고 있어야 합니다.

호출CLI붙는 플래그
리뷰어claude--enable-auto-mode
리뷰어omx없음
리뷰어agy--dangerously-skip-permissions
심판 슬롯 1claude없음
심판 슬롯 2·3agy없음

리뷰어에 붙는 이유: 이 두 이름은 사용자 셸의 **별칭(alias)**이며 별칭 정의에 해당 플래그가 이미 들어 있습니다. 별칭은 비대화형 bash script.sh 실행에서 확장되지 않으므로, 스크립트가 명시적으로 붙이지 않으면 CLI가 대화형 실행과 다르게 동작합니다 — claude는 auto mode 없이 돌고, agy는 권한 프롬프트에서 멈춰 라운드 타임아웃(→ 결측)에 걸립니다. omx는 별칭이 아닌 실제 바이너리라 추가 플래그가 없습니다.

심판에는 붙지 않습니다 — 비대칭은 실재합니다. judge.sh는 같은 두 CLI를 호출하면서 두 플래그를 모두 생략하며, 심판 CLI에 넘어가는 인자는 -p --model (그리고 agy의 경우 --output-format json --json-schema)와 프롬프트 문자열뿐입니다. 스크립트 주석은 리뷰어 쪽 플래그의 근거만 적고 심판 쪽 생략의 근거는 적지 않으므로, 여기서는 관측된 사실만 기술합니다: 심판은 리뷰어보다 낮은 도구 권한으로 실행되며, 심판이 권한 프롬프트를 띄우는 환경에서는 그 슬롯이 타임아웃되어 로테이션이 다음 슬롯으로 전진합니다(3슬롯 모두 그러면 exit 4). 이 비대칭은 심판이 익명 번들 외의 자료에 손대지 못하게 하는 방향과는 정합하지만, 코드가 그 의도를 명시하지는 않았습니다.

종료 코드

스크립트 4종이 실제로 반환하는 코드 전부입니다. agora.shreviewers.sh/anonymize.sh/judge.sh의 코드를 그대로 전파하므로(예외: 68은 자체 진단 한 줄을 덧붙인 뒤 전파), 오케스트레이터가 --round에서 보는 코드는 아래 전부가 될 수 있습니다.

코드반환 스크립트의미재시도
0전부성공
1anonymize.sh지문 검출 — 검사 대상 텍스트에서 금칙 패턴이 걸림. 익명성 파기를 막기 위한 하드 스톱아니오
3reviewers.sh · anonymize.sh유효 리뷰어 2인 미만으로 라운드 중단. 두 스크립트가 같은 코드를 쓰는 것은 의도적이며, 어느 단계에서 걸렸는지는 stderr로 구분합니다(아래 결측 판정)아니오
4judge.sh심판 로테이션 3슬롯이 전부 실패 — CLI 실패·타임아웃·JSON 파싱 실패·스키마 위반을 모두 포함아니오 (로테이션 대체까지가 이미 시도의 전부)
64전부사용법 오류 — 미지의 옵션, 필수 플래그 누락, --extra-agenda가 JSON 배열이 아님, --set-stop의 코드 누락·미지 코드·CONTINUE아니오
65anonymize.sh직전 라운드의 sealed 데이터가 존재하는데 파싱 불가(무결성 문제) 또는 미지의 벤더 식별자. 1(지문)과 일부러 구분합니다 — "번들이 벤더를 누출할 뻔했다"와 "직전 라운드 데이터가 손상됐다"는 운영자 대응이 다릅니다아니오
66agora.sh · judge.sh입력 부재 — 세션 디렉토리를 찾을 수 없음(agora.sh) / --anon-file이 없음(judge.sh)아니오
68judge.sh설정 오류verdict-schema.json을 읽거나 파싱할 수 없음. 심판의 실패가 아니라 배포 설정의 결함이므로 재시도해도 같은 원인으로 즉시 재실패합니다아니오
73agora.sh기록 실패state.json 또는 report.md를 쓸 수 없음. 아래 별항 참조아니오

내부 전용 코드: 124(타임아웃)는 run_with_timeout이 GNU timeout 관례에 맞춰 정규화한 값이며 호출자에게 그대로 노출되지 않습니다 — 리뷰어에서는 결측으로(→ 3), 심판에서는 로테이션 전진으로(→ 4) 흡수됩니다. reviewers.sh/judge.sh65(미지 CLI 슬러그)도 로스터가 하드코딩이라 실제로는 도달하지 않습니다.

exit 73 — 라운드는 돌았는데 기록되지 않음

73(EX_CANTCREAT)은 다른 코드와 성격이 완전히 다릅니다. 여기까지 왔다는 것은 리뷰어 3벤더와 심판이 이미 호출되어 과금까지 끝났다는 뜻이고, 실패한 것은 그 결과를 state.json(또는 report.md)에 남기는 마지막 단계뿐입니다.

오케스트레이터의 대응:

  • 산출물을 소비하지 마십시오. state.json이 갱신되지 않았으므로 .round가 뒤처져 있고, 그 상태에서 --decide-stopMAX_ROUNDS/STALLED를 영원히 내지 못하며 --report는 뒤처진 라운드의 verdict를 읽습니다.
  • 라운드를 재실행하지 마십시오. 벤더가 다시 과금됩니다.
  • 실패 원인(디렉토리 권한, 디스크, 심판이 내놓은 비정수 new_findings 등)을 stderr 진단 줄에서 확인해 사용자에게 보고하고 지시를 기다리십시오.

--set-stop이 73을 반환한 경우도 같습니다 — 종료 사유가 기록되지 않았으므로 --report를 실행하면 종료 사유: UNKNOWN이 박힌 보고서가 나옵니다. 보고서를 만들기 전에 멈추십시오.

결측 판정은 2단계입니다

리뷰어 결측은 서로 다른 두 스크립트가 순차로 판정하며, 둘 다 하한은 "유효 응답 2인"이고 둘 다 exit 3입니다.

단계스크립트무엇을 결측으로 세는가
1reviewers.shCLI가 응답하지 못한 벤더 — 타임아웃(124), 비영 종료, 구문상 JSON이 아닌 출력. 각 벤더는 2회 시도(최초 + 재시도 1회) 후 결측 처리되며, 결측 벤더는 파일을 아예 남기지 않습니다. 2개 이상 결측이면 exit 3
2anonymize.sh파일이 없거나, 있어도 응답 스키마 계약을 위반한 벤더(overall enum, findings[]severity/verdict enum, 빈 문자열 금지 필드 등). 이 검사는 reviewers.sh가 통과시킨 뒤에 걸리므로 별도 하한이 필요합니다 — 유효 응답이 2 미만이면 exit 3

2단계 하한이 없으면 "1개 유효 + 2개 스키마 위반"이 리뷰어 1인짜리 번들을 만들고, 심판이 단일 의견을 놓고 합의를 판정하게 됩니다. 두 단계의 코드가 같으므로 운영자에게 보이는 의미("리뷰어 부족으로 라운드 중단")는 어느 쪽이 걸렸든 동일하며, 구분이 필요하면 stderr 메시지를 보십시오 — 1단계는 2 or more reviewers missing, 2단계는 valid reviewer response(s); 2 or more are required입니다.

심판 응답 검증

judge.sh는 심판 출력이 구문상 JSON인지만 보지 않고, verdict-schema.json을 기준으로 세 가지를 검사합니다. 필수 필드 목록·선언 타입·enum 값을 모두 스키마 파일에서 읽어오므로 스키마가 바뀌면 검사도 따라갑니다.

검사위반 예
필수 필드 존재(그리고 non-null)draft 누락
선언 타입 일치스키마가 배열로 선언한 agenda"1. 단일 의제" 문자열이 옴
enum 값(consensus·verdict)verdict: "MERGE"

셋 중 하나라도 걸리면 그 슬롯은 실패로 처리되어 로테이션이 다음 슬롯으로 전진하고, 3슬롯이 모두 소진되면 exit 4입니다. 타입 검사가 특히 중요한 이유는, 타입이 틀린 agenda가 검증을 통과해 저장되면 다음 라운드의 jq '. + $extra' 병합이 죽고 → 의제가 빈 값으로 붕괴하고 → 리뷰어 프롬프트에 의제 섹션이 비어 있는 채로 3벤더가 전부 호출·과금된 뒤에야 문제가 드러나기 때문입니다.

사용자 게이트

mode: gated(기본값)에서 매 라운드 종료 후 오케스트레이터가 아래 형식을 표시합니다. 벤더는 노출하지 않고 라벨만 노출합니다 — 벤더 공개는 report.md 생성 단계에서만 이루어집니다.

─── Agora Round 2/5 ───────────────────────────────
심판:      (모델 로테이션 #2)
Consensus: MAJORITY          Verdict: BUILD_WITH_CHANGES
리뷰어:    A BUILD_WITH_CHANGES · B BUILD · C REDESIGN
신규 지적: 2건               최고 심각도: HIGH
라운드 소요: 412초 (참고: 심판 로테이션 3슬롯 순차 재시도 시 최악 약 15분)

해소됨(1)
  F1  상태 파일 경합 — 세션 디렉토리 격리로 충분

미해소(1)
  F4  [HIGH] 롤백 경로 부재 — A REJECT / C KEEP / B 미언급

다음 라운드 의제
  - F4 의 심각도 판정 근거를 각자 제시할 것
  - 롤백 경로의 구체적 명령 시퀀스

[c] 계속  [s] 중단하고 보고서  [e] 의제 추가 후 계속

리뷰어: 줄의 A/B/C는 벤더가 아닙니다. 이 라벨은 해당 라운드의 시드로 새로 섞인 것이라 라운드마다 가리키는 벤더가 바뀝니다. A BUILD_WITH_CHANGES를 특정 벤더의 입장으로 읽지 마십시오 — 라운드 간 비교도 불가합니다. 벤더 대응은 report.md의 「라운드별 참여자(익명 해제)」에서만 확인할 수 있습니다. 결측 벤더가 있으면 이 줄에는 라벨이 2개만 나옵니다.

토큰 누적은 표시되지 않습니다. 벤더 CLI들이 토큰 사용량을 일관된 형식으로 보고하지 않아 계측을 신뢰할 수 없으므로 구현하지 않았습니다 — state.jsonhistory[].tokens는 항상 0으로 기록됩니다. 게이트에 누적 토큰 줄을 두는 설계 스펙 §10과의 의도적 차이입니다. 비용 감각은 아래 --auto 경고의 라운드당 추정치로 대체하고, 게이트가 제공하는 실측값은 라운드 소요 초 하나뿐임을 전제하십시오.

동작
c다음 라운드 진행. 심판 의제를 그대로 사용
s루프 종료(stop: "USER"), 현재까지의 결과로 report.md 생성
e사용자 의제를 입력받아 심판 agenda[]추가한 뒤 다음 라운드 진행

e는 추가만 가능하며 심판 의제를 덮어쓰지 못합니다. 사용자가 심판 의제를 삭제할 수 있으면 심판의 의제 설정 권한이 형해화되고, 사용자가 불편해하는 쟁점이 조용히 사라지는 경로가 생기기 때문입니다.

게이트드 모드의 책임 분리

게이트드 모드에서 루프를 도는 주체는 스크립트가 아니라 호출자입니다. 스크립트는 라운드 하나만 실행하고 반환하며, 정지 여부를 스스로 판단하지 않습니다.

명령하는 일하지 않는 일
--start (--auto 없이)세션 생성 + 라운드 1만 실행 후 반환라운드 2 이후를 돌지 않음
--round <N>지정 라운드를 실행하고 state.json에 기록--decide-stop을 호출하지 않음. .stopmax_rounds검사하지 않음
--gate게이트 화면 렌더 (stdout)사용자 입력을 받지 않음
--set-stop.stop에 종료 사유 기록정지 여부를 스스로 판단하지 않음 — 호출자가 준 코드를 그대로 씀
--reportreport.md 생성정지 판단을 하지 않음. .stop을 쓰지도 않음 — 미기록이면 UNKNOWN을 출력
  • 라운드 상한은 --round를 막지 않습니다. max_rounds를 넘긴 라운드도, 이미 .stop이 기록된 세션의 라운드도 거부 없이 실행됩니다. 정지 판단은 전적으로 호출자 몫입니다.
  • 게이트드 --start가 게이트 없이 끝나는 경로가 있습니다. 라운드 1에서 종료 조건이 걸리면 .stop 기록과 report.md 생성까지 수행한 뒤 게이트를 렌더하지 않고 반환합니다. 따라서 --start 반환 후에는 게이트 출력 유무가 아니라 state.json.stop을 읽어 분기하십시오.

여기서 호출자는 두 층입니다 — 오케스트레이터(메인 대화)와 agora-runner 서브에이전트. 파일을 쓰는 명령(--start·--round·--set-stop·--report)은 agora-runner가, 파일을 쓰지 않는 순수 출력 명령(--gate)은 오케스트레이터가 실행합니다(R010). 매 라운드 흐름은 다음과 같습니다.

  1. 오케스트레이터 → agora-runner 위임: 러너가 --round <N> --session-dir <dir> 를 실행한 뒤 --decide-stop < <dir>/state.json 으로 정지 코드를 산출해, verdict 요약 + stop_code 를 반환합니다.
  2. 오케스트레이터가 직접 실행: --gate --session-dir <dir> --round <N> 로 게이트를 표시하고 사용자 응답(c/s/e)을 받습니다. --gate는 파일을 쓰지 않으므로 오케스트레이터가 실행해도 R010에 걸리지 않으며, 서브에이전트는 사용자와 상호작용하지 않으므로 이 단계를 위임할 수도 없습니다.
  3. 계속이면 다음 라운드를 다시 위임하고(사용자가 e를 골랐으면 --extra-agenda <json-array> 동반), 정지면 --set-stop <CODE> + --report --session-dir <dir>agora-runner에 위임합니다. <CODE>는 1단계의 stop_code를 그대로 쓰되, 사용자가 s를 골라 멈추는 경우에는 USER입니다. 이 기록을 건너뛰면 보고서가 종료 사유: UNKNOWN으로 나옵니다.

--auto 경고

--auto는 매 라운드 게이트를 생략합니다. 비용 상한이 명확할 때만 사용하십시오.

항목
라운드당 토큰 추정60~120k (리뷰어 3 + 심판 1)
기본 라운드 상한5 (--max-rounds로 조정)
최악의 경우 총 토큰약 600k

소요 시간 주의: 리뷰어 단계와 심판 단계는 병렬성이 다르므로 벽시계 계산이 다릅니다.

단계실행 형태최악 벽시계 (AGORA_TIMEOUT_SECS=300 기준)
리뷰어3벤더 병렬 팬아웃(& + wait), 벤더당 최초 1회 + 재시도 1회300초 × 2시도 = 600초. 벤더 수는 벽시계에 곱해지지 않습니다
심판로테이션 3슬롯 순차 시도300초 × 3슬롯 = 900초
라운드 합계두 단계는 순차약 1500초 ≈ 25분

기본 상한 5라운드를 --auto로 끝까지 돌면 최악 약 2시간이며, 게이트가 없으므로 이 지연이 중단 없이 누적됩니다. (게이트가 표시하는 참고: 심판 로테이션 3슬롯 순차 재시도 시 최악 약 15분심판 단계만의 값이며 라운드 전체가 아닙니다.)

신뢰 경계

익명성은 프롬프트 지시가 아니라 디렉토리 경계로 유지됩니다.

주체SEALED/ 접근anon/ 접근
오케스트레이터 (메인 대화)금지허용
agora-runner 에이전트금지허용
리뷰어 CLI 3종금지금지 (라운드 내에서 서로를 보지 않음)
심판 CLI금지허용 (익명 번들만이 유일한 입력)
anonymize.sh허용SEALED/raw/(원문 읽기)와 SEALED/mapping/(직전 라운드 매핑 읽기 + 이번 라운드 매핑 봉인)허용 (anon/round-N.json을 생산하는 주체)
report.md 생성 단계허용SEALED/mapping/ 읽기허용 (익명 해제가 이 단계의 목적)

SEALED/를 읽는 주체는 입니다 — 파이프라인 안쪽의 anonymize.sh와 마지막의 보고서 생성 단계(스펙 §4). anonymize.sh는 봉인된 원문·매핑을 읽지만 그 산출물인 anon/ 번들에는 라벨만 남기므로, 봉인을 읽는 것과 봉인을 푸는 것은 구분됩니다. 익명 해제는 report.md 생성 단계에서만 일어납니다.

한계 1 — 격리를 지켜도 남는 누출(스펙 §6): 위 표가 완벽히 지켜져도 심판은 벤더를 추론할 단서를 갖습니다. 익명성은 라벨이 유지되는 동안에도 아래 세 경로로 새어 나갑니다.

  1. 라운드 내 일관성 — 한 라운드 안에서 하나의 라벨은 그 벤더의 전체 응답을 묶습니다. 심판은 "A의 findings 5건이 모두 성능 축에 집중되어 있다"와 같은 스타일 패턴을 관찰할 수 있습니다.
  2. 라운드 간 일관성 — 재라벨링(과거 라운드의 라벨을 현재 라운드 매핑으로 변환)을 도입한 이상 prior_rounds[]를 통해 라운드 간 일관성도 관찰 가능해집니다. 이는 의도된 트레이드오프입니다 — "A가 입장을 바꿨다"는 오독을 막는 편익이 벤더 추론 위험보다 크다고 판단했습니다.
  3. 논증 스타일 지문 — 서식 정규화는 문체 지문을 줄이지만 논증 스타일 지문은 줄이지 못합니다. 특정 모델이 반복적으로 특정 축을 파고드는 경향은 템플릿으로 흡수되지 않습니다.

그러므로 개요에 적은 대로 이 설계는 결정적 익명성을 주장하지 않습니다. 달성하는 것은 "벤더 식별 불가"가 아니라 "벤더 식별이 심판의 기본 관찰 경로에 놓이지 않음"이며, 심판이 추론을 시도하면 부분적으로 성공할 수 있다고 전제해야 합니다.

한계 2 — 격리 자체가 강제되지 않음: 이 격리는 관례이지 하드 블록이 아닙니다. 오케스트레이터도 심판 CLI도 SEALED/를 읽는 파일 읽기 능력 자체는 갖고 있습니다 — 읽지 못하는 것이 아니라 읽지 않도록 설계된 것입니다. PreToolUse 훅으로 SEALED/ 경로 Read를 하드 블록하는 방안은 검토했으나, 훅은 프로젝트 전역에 영향을 주므로 초기 도입에서는 제외했습니다(위반율 관측 시 R021 Hard Enforcement Candidates 승격 검토 대상). 실질 방어선은 아래 지문 검사이며, 이는 "누출이 발생했는가"를 사후 탐지할 뿐 "누출을 시도할 수 없게" 만들지는 않습니다.

한계 3 — 자식 CLI가 cwd를 상속함: 벤더·심판 CLI는 AGORA_* 환경변수를 제거한 채 실행되지만(아래 벤더 호출 환경 정화), 작업 디렉토리는 상속합니다. 기본 출력 루트 AGORA_OUTPUT_ROOT가 상대경로(.claude/outputs/sessions)이므로, 상속받은 cwd에서 .claude/outputs/sessions/**를 훑으면 세션 트리 — SEALED/ 포함 — 에 도달할 수 있습니다. 환경 정화는 "세션 좌표를 건네주지 않는다"를 보장할 뿐 "찾을 수 없게 한다"를 보장하지 않습니다. 절대경로 AGORA_OUTPUT_ROOT를 세션 트리 밖으로 지정하면 이 경로는 좁아지지만, cwd 상속 자체는 남습니다.

지문 검사

anon/round-N.json을 그대로 검사하지 않습니다. anonymize.sh가 번들에서 벤더 유래 텍스트만 뽑아낸 파생 문서를 만들어 그것을 검사합니다.

필드검사 대상이유
reviewers[] (이번 라운드)리뷰어 저작 — 익명화 대상 당사자
prior_rounds[].reviewers[]재라벨링되어 다음 라운드 심판에게 전달되는 리뷰어 저작 텍스트
prior_rounds[].draft · .verdict (운영자 어휘 제거 후)심판 저작. 심판은 익명화의 주체이지만, 그 초안·판정은 relabel_prior를 타고 다음 라운드의 새 심판에게 넘어갑니다. 심판이 쓴 귀속 추정("A는 Claude 계열로 보인다")이 후임에게 무검사로 전달되는 것이 바로 이 검사가 막으려는 누출입니다
topic · agenda · attachments아니오운영자 저작 — 의도적 제외(Ruling 10). 토론 주제 자체가 "제미나이를 도입할까"일 수 있으며, 주제가 벤더를 언급했다는 이유로 매 라운드가 중단되면 스킬이 성립하지 않습니다
round 등 나머지 메타데이터아니오벤더 유래 텍스트가 아님

심판 저작 필드는 검사 전에 운영자 어휘를 단어 단위로 걸러냅니다 — 심판이 주제·의제를 정당하게 인용할 수 있기 때문입니다. 필터는 토큰화 → 운영자 단어 제거 → 재결합 방식이며, 부분 문자열이 아니라 온전한 단어에만 작동합니다(운영자 단어 "mini"가 "gemini"를 쪼개 검사를 무력화하는 역방향 회피를 막기 위함). /, -, ., _는 단어 문자로 취급되어 경로 형태와 버전 표기가 보존됩니다. 잔여 오탐(의도적, 차단 쪽으로 기움): 심판 텍스트의 운영자 단어에 한국어 조사가 붙으면(주제의 제미나이 vs 심판의 제미나이는) 다른 토큰이라 면제되지 않고 중단시킵니다. 리뷰어 텍스트는 필터하지 않습니다 — 리뷰어는 익명화 대상이므로 주제를 그대로 되뇌는 것도 누출로 취급합니다.

금칙 패턴이 실제로 막는 것

패턴은 대소문자 무시(grep -Eiq)로 적용되며 3계층입니다. "모델명을 막는다"고 뭉뚱그리면 사실과 다릅니다 — 계층마다 걸리는 조건이 다릅니다.

계층토큰매칭 방식
1. 명백한 벤더·제품 토큰codex omx gpt claude gemini antigravity anthropic openai + 한글 음차(클로드 제미나이 지피티 앤트로픽 오픈에이아이)단어 경계 없는 부분 문자열. 경계를 두면 claudecodechatgpt 같은 실제 자기 식별을 통과시키므로 일부러 뺐습니다
1'. agyagy양쪽 단어 경계 유지. 3글자라 무관한 단어(stagy, cagy, 성씨) 안에 들어갈 수 있고, 모델이 자기를 "agycode"라 부르는 형태도 없기 때문
2. 영어 일반 단어와 겹치는 모델 계열명opus sonnet haiku flash두 형태에서만 — (a) 버전 인접(Opus 4.8, sonnet-5, flash 2.0) (b) 비ASCII 문자 인접(Sonnet 관점에서). 맨 단어를 막으면 정상 리뷰 산문("magnum opus", "flash memory")에서 중단됩니다
3. 봉인 경로 형태SEALED/ /mapping/ raw/round-부분 문자열

실제로 이 세션에서 쓰이는 모델 ID는 전부 계층 1에 걸립니다 — claude-opus-4-8/claude-opus-5/claude-opus-4-6-thinkingclaude로, gemini-3.1-pro-highgemini로, gpt-oss-120b-mediumgpt로, omx:defaultomx로 걸립니다.

감수한 미탐(false negative): 버전도 한글 인접도 없이 영어 산문 안에 놓인 맨 모델 계열명 — 예: Sonnet would argue — 은 sonnet-length prose 와 정규식으로 구별할 수 없어 통과합니다. 감수한 오탐(false positive): flash 메모리 같은 한영 혼용 기술 용어는 계층 2-(b)에 걸려 라운드를 중단시킵니다. 오탐은 시끄럽고 복구 가능하지만 미탐은 조용히 익명성을 깨므로, 과차단 쪽을 택한 결과입니다.

지문이 걸리면 anonymize.sh가 exit 1로 중단하며, SEALED/mapping/anon/에는 아무것도 쓰이지 않습니다 — 매핑과 번들은 검사를 통과할 때까지 임시 디렉토리에 머뭅니다.

벤더 호출 환경 정화

reviewers.shjudge.sh는 자식 CLI를 실행할 때 접두사 AGORA_로 시작하는 모든 환경변수를 제거합니다(env -u, 실제 export된 이름을 compgen -e로 수집하므로 이름 목록 하드코딩이 아님). 스크립트가 그 값을 넘기지 않는다는 것만으로는 부족했기 때문입니다 — 운영자 셸에 AGORA_OUTPUT_ROOT가 export되어 있으면 그 값이 agora.sh를 거쳐 모든 벤더 CLI에 그대로 상속되고, 세션 트리(봉인 기록 포함)는 그 아래 한 단계입니다.

AGORA_ 밖의 변수는 건드리지 않습니다 — PATH·HOME과 벤더 자신의 인증 토큰·프록시 설정은 그대로 살아 있어야 CLI가 인증할 수 있습니다. 읽는 시점과 넘기는 시점은 분리되어 있어, 스크립트 자신은 계속 AGORA_TIMEOUT_SECS 등을 평범한 셸 변수로 사용합니다(테스트 주입 설정이 여전히 동작하는 이유).

이 정화가 덮지 못하는 것은 cwd입니다 — 위 한계 3을 참조하십시오.

R010 위임 구조

오케스트레이터는 파일을 직접 쓸 수 없습니다(R010). agora는 라운드마다 다수의 아티팩트 파일을 기록하므로, 라운드 실행은 agora-runner 에이전트에 위임합니다.

  • 위임 단위는 라운드 1개 = 위임 1건입니다. 다중 라운드를 한 위임에 묶지 않습니다 — Phase 경계가 곧 mid-step 종료 지점이 되는 것을 방지하기 위함입니다(R020).
  • 파일을 쓰는 진입점은 --start·--round·--set-stop·--report이며 모두 러너가 실행합니다. --set-stop은 이름이 판정처럼 보이지만 state.json을 변경하므로 오케스트레이터가 직접 실행하지 않습니다.
  • agora-runner는 스크립트 실행과 아티팩트 기록을 담당하고, verdict 요약만 오케스트레이터에 반환합니다. 리뷰어 원문, 벤더 출처, SEALED/ 경로는 반환하지 않습니다.
  • 사용자 게이트 표시와 게이트 응답 처리는 오케스트레이터 전담입니다 — 서브에이전트는 사용자와 상호작용하지 않습니다.
  • agora-runner 에이전트 정의 자체는 이 스킬의 범위 밖입니다. .claude/agents/agora-runner.md를 참조하십시오.

Signals

GitHub stars
34
Forks
6
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
agora
Source
github.com/baekenough/oh-my-customcode