diff --git a/ai_scientist/03_routing_and_artifact_contract.md b/ai_scientist/03_routing_and_artifact_contract.md index 7aa8ded..7d6c37f 100644 --- a/ai_scientist/03_routing_and_artifact_contract.md +++ b/ai_scientist/03_routing_and_artifact_contract.md @@ -97,6 +97,18 @@ - `schemas/hypothesis.schema.json` — 가설 출력 구조. 모든 hypothesis 출력에 `claim_level`(반드시 `"hypothesis_only"`) + `critic_status`(`pass`/`caution`/`reject`) 필수 (`AGENTS.md` §4). - 실험 `metrics.json` 필수필드: `auc·auprc·balanced_accuracy·n_train·n_val·model·embedding_model·commit_hash` (`AGENTS.md` §5). +### 스키마는 "선언"이 아니라 "검증"이어야 한다 + +한동안 이 계약에는 구멍이 있었다. 산출 스크립트들이 docstring에서 *"hypothesis.schema.json 형식 출력"* 이라고 **말할 뿐 검증하지 않았고**, 리포에 그 스키마를 검사하는 코드가 아예 없었다. 그래서 위반(필수 필드 누락 등)이 **최소 4주간 아무에게도 안 보였다.** + +대응은 두 갈래였다: +1. **스키마를 확장**한다 — 현장에서 실제로 필요해 붙은 필드(예: *fallback 값인지 실측인지* 구분하는 표시)를 **삭제하는 대신 정식 등재**. 규율을 지키려고 유용한 정보를 버리지 않는다. +2. **검증기를 만든다** — `agents/critic/scripts/validate_hypothesis.py`. 이제 "형식"이 **주장이 아니라 검사**다. + +> ⚠️ **다만 이 검증기는 아직 CI에 blocking으로 걸지 않았다 — 의도적이다.** +> 기존 산출물에 위반이 남아 있는 상태로 blocking을 켜면 **"원래 빨간 CI"** 가 되고, 그러면 아무도 CI를 보지 않는다. 먼저 위반을 해소하고, 그다음에 켠다. +> **순서를 지키는 것도 설계다** — 게이트를 켜는 시점이 잘못되면 게이트 자체가 무력해진다. + ## "지어내지 않기"를 계약에 박았다 산출물 계약 곳곳에 `` 플레이스홀더가 있다(예: `SKILL.md:18`의 ``). 이는 **아직 없는 것을 없다고 표시**하는 장치다. diff --git a/ai_scientist/04_automated_review_and_governance.md b/ai_scientist/04_automated_review_and_governance.md index 7fe3631..48f8c76 100644 --- a/ai_scientist/04_automated_review_and_governance.md +++ b/ai_scientist/04_automated_review_and_governance.md @@ -40,7 +40,7 @@ > ⚠️ **2026-07-27 갱신(BIOP02-103): ③에서 `venue-reviewer`를 부르지 않는다.** 자동 리뷰 루프(5단계)는 **`paper-critic`의 자리**이고, 외부 referee 시뮬레이션은 **검증 게이트 ① 통과 후 8단계**에서만 호출한다. 검증 안 된 숫자를 리뷰에 보내지 않기 위해서다. > -> 🔴 **리포 내부 불일치(2026-08-03 실측, 팀 확인 필요):** `SKILL.md`는 5단계를 `paper-critic` 단독으로 고쳤는데 **`auto_review_config.json`의 `ai_review.agents`는 여전히 `["paper-critic", "reviewer"]`** 이다. 즉 config를 켜면 폐기된 `reviewer`(현 `venue-reviewer`)를 5단계에서 부르게 된다. 지금은 `enabled=false`(dry-run)라 실害가 없지만 **활성화 전 반드시 정리**해야 한다. +> ✅ **해소(2026-08-20).** 한때 `auto_review_config.json`의 `ai_review.agents`가 `["paper-critic", "reviewer"]`로 남아 **config를 켜면 폐기된 `reviewer`를 5단계에서 부르는** 불일치가 있었다. **Leader 승인 후 `["paper-critic"]` 단독으로 정렬**됐다. ⚠️ 이 수정이 `anti-self-reference`에 걸리지 않은 이유는 **Critic이 스스로 정한 게 아니라 Leader가 승인**했기 때문이다 — 판정 어휘·검수 절차 변경이 아직 열려 있는 것과 같은 기준이다. ## 3단계 티어 게이트 — "사람을 어디에 둘지"의 설계 @@ -105,21 +105,93 @@ AI가 리뷰 노동(7-point 대조 · 적대적 다중패스 · 헤드라인 수 > *"리포에 검증 자산은 갖춰져 있는데 `.github/workflows/`가 없어, 만들어 둔 검증이 PR에서 **한 번도 자동으로 돌지 않았다**(사람이 기억해서 돌릴 때만). 이번 주 불일치 3건이 전부 눈으로 대조해야만 발견되는 종류였고 5일~2주씩 방치됐다."* -`pull_request`(→ main)와 `push`(main)에서 **blocking**으로 도는 검증기 3종: +`pull_request`(→ main)와 `push`(main)에서 **blocking**으로 도는 검증기 — **7종**(2026-08-20 실측): -| 검증기 | 무엇을 막나 | -|---|---| -| `evals/critic_pilot/mutation_check.py` | scorer가 케이스에 실제로 제약되는지 — **Critic이 무조건 통과시키는 회귀** 탐지 | -| `evals/citation_verifier/mutation_check.py` | `medsci` 회귀 버그(**DOI 실패 시 약한 제목검색으로 `OK`**)가 죽어 있는지 | -| `agents/critic/scripts/check_number_drift.py --strict` | **JSON 정본 ↔ markdown 표 수치 드리프트**(BIOP02-107) | +| # | 검증기 | 무엇을 막나 | +|---|---|---| +| 1 | `evals/critic_pilot/mutation_check.py` | scorer가 케이스에 실제로 제약되는지 — **검수기가 무조건 통과시키는 회귀** | +| 2 | `evals/citation_verifier/mutation_check.py` | 인용 검증 회귀 버그(**조회 실패 시 약한 제목검색으로 `OK`**)가 죽어 있는지 | +| 3 | `agents/critic/scripts/check_number_drift.py --strict` | **JSON 정본 ↔ markdown 표** 수치 드리프트 | +| 4 | `evals/validation_harness/run_validation.py --strict` | ⭐ **게이트 mutation 하네스** — 실수를 심었을 때 게이트가 잡는가 | +| 5 | `agents/modeling/scripts/verify_split_integrity.py` | 환자·사이트 단위 **분할 누수**(disjointness assert) | +| 6 | `agents/critic/tests/test_gate_vacuous_pass.py` | ⭐ **공허통과(vacuous pass) 회귀** 10케이스 | +| 7 | `agents/critic/scripts/manuscript_parity_ko_en.py` | 국·영문 **판본이 같은 사실을 담는지**(산문은 대조 안 함) | 설계상 중요한 점 3가지: 1. **결정론·오프라인 검증만 blocking으로 건다** — LLM 판단은 CI에 넣지 않는다. -2. **명시적 비범위**: `auto_review_config.json`의 `enabled` 플래그는 **건드리지 않는다**("M7: 팬텀 배선. BIOP02 하네스 스왑 전 활성화 금지"). 라우터 문서도 손대지 않는다. -3. 검증기 3번은 이 프로젝트가 반복해서 데인 실패 — **문서의 표 숫자가 결과 JSON과 어긋나는 것** — 를 기계가 잡게 만든 것이고, 2번은 아래 금지 항목 *"도구가 '못 찾겠다'고 한 것을 통과로 처리"* 를 **회귀 테스트로 못박은** 것이다. +2. **명시적 비범위**: `auto_review_config.json`의 `enabled` 플래그는 **건드리지 않는다**("팬텀 배선 — 하네스 스왑 전 활성화 금지"). 팬텀이 살아 있는 채로 blocking을 켜면 **"원래 빨간 CI"** 가 되고, 그러면 아무도 CI를 안 본다. +3. 검증기 3번은 반복해서 데인 실패(**문서의 표 숫자가 결과 JSON과 어긋남**)를 기계가 잡게 한 것이고, 2번은 아래 금지 항목 *"도구가 '못 찾겠다'고 한 것을 통과로 처리"* 를 **회귀 테스트로 못박은** 것이다. + +즉 이 하네스의 검수는 **3층**이다: ① CI 결정론 검증기(기계) → ② 자동 리뷰 루프(AI 적대) → ③ 사람 게이트(Tier C·공개). + +### ⭐ 검증기를 검증한다 — "통과만 하는 게이트는 게이트가 아니다" + +위 표의 **4·6번**이 v02 이후 생긴 가장 큰 변화다. 이 둘은 결과를 검사하지 않는다 — **검사기 자신을 검사한다.** + +**게이트 mutation 하네스**(`evals/validation_harness/`)가 재는 것은 하나다: + +> *"실무자가 저지를 법한 실수를 심었을 때, 우리 게이트가 실제로 잡는가?"* + +**control vs mutated 델타**로 판정한다 — 결함을 심지 않았을 때 통과하고(오탐 0), 심었을 때 잡아야(구멍 0) 합격이다. +`--case split_leak_patient` 처럼 케이스 단위 실행도 된다. + +**공허통과 회귀**(`test_gate_vacuous_pass.py`, 10케이스)는 그 반대편을 막는다 — 게이트가 **아무것도 검사하지 않고 초록불을 주는 상태**(단일 그룹만 있어 비교가 성립 안 함, 경로가 없는데 통과 등)를 회귀로 고정한다. + +> 🔑 **이 층이 왜 필요한지는 실제 사고가 증명했다.** 두 가드를 동시에 무력화했더니 **기존 CI 스텝은 "구멍 0 / exit 0"으로 초록**이었고, 신규 스텝만 실패를 잡았다. 즉 **수정이 통째로 되돌아가도 CI는 초록**이었다는 뜻이다. +> 그래서 규칙이 하나 생겼다 — **게이트를 만들면 일부러 깨뜨려 본다.** 통과만 확인하고 끝내면 "잡는 게이트"인지 "통과시키는 게이트"인지 구분되지 않는다. + +### 검증기가 스스로 터지지 않게 — 회귀 테스트의 회귀 테스트 + +`test_schema_resolvable.py`는 더 미묘한 것을 막는다. 스키마의 `$id`가 상대경로면 내부 `$ref` 해석 시 base URI와 합쳐져 **없는 경로**가 만들어지고 검증기가 예외로 죽는다. +고약한 점은 **`$ref`가 지연 해석이라 터지는 시점이 늦다**는 것 — 빈 인스턴스에서는 통과하고 실제 데이터에서만 죽는다. 그래서 별도 회귀로 고정했다. + +> 이 프로젝트에서 **검증기 자체의 실패**는 결과 오류보다 위험하다. 결과 오류는 빨간불로 보이지만, **검증기가 죽거나 공허통과하면 초록불로 보인다.** + +### 판정하지 않는 검증기 — 드리프트 체커의 철학 + +`check_number_drift`(v1·v2)는 **판정하지 않는다.** 어느 숫자가 옳은지 고르지 않고, **정본과 문서가 어긋난다는 사실만** 보고한다. 판정은 사람 몫이다. + +v1은 *"JSON 정본 ↔ markdown의 endpoint별 표 행"* 만 보고 **범위 밖을 스스로 명시**했고(FM별 비교표·산문 속 수치·비용 JSON), v2가 정확히 그 셋을 이어받았다. +**"내가 무엇을 안 보는지"를 도구가 먼저 적어두는 것** — 그것이 다음 사람이 v2를 만들 수 있게 한 조건이다. + +### 픽스처 자기충족을 피한다 — 실제 산출물로 돌리기 + +`evals/critic_pilot/run_real_artifacts.py`가 존재하는 이유는 명시돼 있다: **픽스처를 만든 사람과 채점기를 만든 사람이 같으면** 그 통과는 자기충족이다. +그래서 같은 채점기를 **실제 `critic_report.json`** 에 돌린다. 단 이 실행은 **읽기 전용**이고 그 판정은 `critic_status`가 **아니다** — 최종 판정은 사람이 owns한다(Owner ≠ Reviewer). + +## 메타-학습 루프 — 실수를 장치로 바꾸는 파이프라인 + +위 검증기들은 우연히 생기지 않았다. 이 프로젝트는 **실수를 지우지 않고 레지스트리에 쌓고, 각 항목에 재발방지 장치를 강제로 붙인다.** + +``` +사고 발생 → docs/PITFALLS_REGISTRY.md 등재 → 재발방지 장치를 반드시 적는다 → 장치를 박는다 + ├─ CLAUDE.md 금지조항 + ├─ 검증 스크립트 + └─ CI blocking 검증기 +``` + +레지스트리의 **규칙 ②** 가 이 루프의 심장이다: + +> *"**재발방지 장치(무엇을 박았나)를 반드시 적는다** — 교훈만 적고 장치가 없으면 재발한다."* + +**분류는 4개**(`A` 분석·통계 / `T` 도구·검증 / `G` git·인프라 / `C` 협업·기록)이고, 각 줄은 **ID · 날짜 · 한 줄 · 재발방지 장치**로 끝난다. 즉 **장치 칸이 비어 있는 항목은 미완결**로 보인다. + +실제 항목이 이 설계서의 금지 조항들과 1:1로 이어진다: + +| 레지스트리 | 무엇이 박혔나 | +|---|---| +| `A5` 발표자료의 **관측값**을 eval **합격 기준**으로 옮겨 적음 | `CLAUDE.md` 금지조항 — 기준은 봉인 사전등록·실물 코드만, `파일:줄` 인용 | +| `A6` **계획을 자산으로 착각**("앞으로 할 일"이 몇 줄 아래서 "이미 있는 씨앗"으로 승격) | 금지조항 — *"있다고 적혀 있으면 열어서 확인한다"* | +| `T1` 인용 도구가 조회 실패 시 제목검색으로 내려가 **가짜 DOI를 `OK`** | 자체 `verify_citations.py`(조회 실패=통과 금지) + **CI 회귀 테스트**로 고정 | +| `A4` 행정렬 버그를 **진단 스크립트에도 복제** → 버그가 "데이터 특성"으로 보임 | 교훈 = **검증 스크립트가 같은 버그를 쓰면 버그가 "현상"으로 보인다** | + +> **`A4`가 특히 중요하다.** 검증기가 피검증 대상과 같은 실수를 공유하면 **버그가 결과처럼 보인다.** +> 위 "검증기를 검증한다"(mutation·공허통과)와 "픽스처 자기충족을 피한다"가 바로 이 교훈의 구조적 대응이다. + +깊이 있는 서사형 사례는 `docs/ai-collaboration-cautions.md`에 따로 있다 — 레지스트리는 **스캔·누적용**, 그쪽은 **왜 그렇게 틀렸는지**를 남긴다. 그 문서의 핵심 명제 한 줄: -즉 이 하네스의 검수는 **3층**이 됐다: ① CI 결정론 검증기(기계) → ② 자동 리뷰 루프(AI 적대) → ③ 사람 게이트(Tier C·공개). +> *"개별 사실이 모두 맞아도 그것들이 조합된 주장(프레이밍·비교·설계)은 틀릴 수 있다."* ## 거버넌스: 절대 금지 사항 (자동화가 넘지 못하는 선) diff --git a/ai_scientist/05_human_collaboration.md b/ai_scientist/05_human_collaboration.md index 1e04569..2912682 100644 --- a/ai_scientist/05_human_collaboration.md +++ b/ai_scientist/05_human_collaboration.md @@ -82,7 +82,29 @@ JIRA Smart Commits 자동 연동 (이슈 상태 자동 업데이트) - `SESSION_LOG.md` — "그날 한 일의 날짜별 기록"(매 세션 필수, HANDOFF와 같은 턴에 함께 기록) - `TODO.md` — 남은 일 -이 파일들은 git 미추적(개인 작업일지)이고, durability는 `/workspace/kkkim_private/session_logs/` 백업으로 확보한다. 팀 공유 영구 기록은 **JIRA·Confluence·PR 본문·`experiments/registry/`** 가 담당. +이 파일들은 git 미추적(개인 작업일지)이고, durability는 **공유 볼륨 백업**으로 확보한다. 팀 공유 영구 기록은 **JIRA·Confluence·PR 본문·`experiments/registry/`** 가 담당. + +### 한 층 더 — 세션을 넘어 지속되는 사실 (`memory/`) + +핸드오프 3종이 **"이 프로젝트의 지금 상태"** 를 넘긴다면, `memory/`는 **"세션이 바뀌어도 다시 배우지 않아야 할 사실"** 을 넘긴다. 둘은 수명이 다르다. + +| | 수명 | 예 | +|---|---|---| +| `HANDOFF` / `SESSION_LOG` / `TODO` | 며칠~몇 주 | 지금 막힌 것, 어제 한 일 | +| `memory/` | 프로젝트 내내 | 사람의 작업 방식·선호, 반복 확인된 운영 사실, 외부 자원 위치 | + +한 항목 = 한 파일 = 한 사실이고, 색인(`MEMORY.md`)이 있어 다음 세션이 **먼저 읽는다.** +실제로 이 프로젝트 메모리에는 *"상태 판단 전 반드시 fetch/pull"*, *"로컬에서 막힌 재계산은 GPU 머신 공유 경로에서 가능"* 처럼 **반복해서 대가를 치른 사실**이 들어 있다. + +> ⚠️ **메모리도 낡는다.** 기록 시점의 사실이므로, 파일·명령·플래그를 지목하는 항목은 **쓰기 전에 아직 존재하는지 확인**한다 — 이 설계서가 반복하는 *"열어서 확인한다"* 가 여기에도 적용된다. + +### 이 층의 실패는 조용하고 되돌릴 수 없다 + +핸드오프 3종은 **개인 작업일지라 git 미추적**이다. 그래서 로컬 디스크와 함께 사라진다 — 이 프로젝트는 실제로 **한 달치 세션 로그를 잃었고**, 그 구간은 **트래커 코멘트가 유일한 기록**으로 남았다. + +그 사고가 규칙 둘을 낳았다: +1. **공유 볼륨에 백업**한다(개인 홈은 컨테이너와 함께 사라질 수 있다) +2. 상태를 인용할 땐 **기억이 아니라** `파일:줄`·커밋·코멘트 id ## 협업 채널 diff --git a/ai_scientist/README.md b/ai_scientist/README.md index 8f7f5d3..165c6c8 100644 --- a/ai_scientist/README.md +++ b/ai_scientist/README.md @@ -56,6 +56,21 @@ --- +## 이 하네스의 계보 — 어디서 왔고 어디로 복제됐나 + +이 설계는 처음부터 **재사용 스캐폴드**로 만들어졌고, 실제로 분야가 다른 두 프로젝트에 얹혔다. + +``` +원본(upstream) paper-production-harness ← Designed by Ka-Kyung Kim, CC BY 4.0 + ├─▶ BioProject01 (velocity/전사체) + └─▶ BioProject02 (병리/H&E) ← 이 설계서가 기술하는 인스턴스 +``` + +3자 상세 대조는 `docs/HARNESS_COMPARISON.md`에 있다(공용 자산 — 특정 프로젝트 소유가 아님). + +> **"복사"가 아니라 "벤치마킹 이식"이라는 구분이 중요하다.** 예컨대 검수 하네스(`evals/validation_harness/`)는 BIOP01의 `reproducibility_pilot` **골격만** 가져오고(러너·control↔mutated 델타), **mutation과 detector는 BIOP02 도메인으로 새로 썼다** — 남의 도메인 판정 기준을 그대로 베끼지 않는다. +> 같은 원칙이 자동 리뷰 루프에도 있다: **코드는 project-agnostic**, 프로젝트별 값은 **전부 config로**. 그래서 다른 프로젝트는 *스크립트 복사 + 자기 config*만으로 작동한다. + ## 변경 이력 (설계 자체가 바뀐 지점) 이 설계서는 **살아 있는 하네스**를 기술한다. 초판(`89848ed`, 2026-07-22) 이후 실제 설계가 바뀐 부분: @@ -80,12 +95,27 @@ | `required_followups` — 사람에게 넘기는 것은 **노동이 아니라 판단** | [04](04_automated_review_and_governance.md) | | 🔴 **정정**: "집필-단계 산출물이 아직 없다" → 원고는 **존재**(`manuscript/sections/` 5섹션 + Discussion 한계) | [01](01_two_layer_architecture.md) | +### 2026-08-20 — 하네스 기법 전수 대조 + +git으로 하네스 자산을 전수 조사해 **설계서에 반영 안 된 기법 11건**을 찾아 보강했다(설계 변경이 아니므로 위 표에 넣지 않는다). + +| 보강 | 어디에 | +|---|---| +| **CI 검증기 3종 → 7종** (게이트 mutation · split 누수 · 공허통과 회귀 · 국영문 정합 추가) | [04](04_automated_review_and_governance.md) | +| ⭐ **검증기를 검증한다** — mutation 하네스(*"실수를 심으면 게이트가 잡는가"*, control↔mutated 델타) + **공허통과(vacuous pass) 회귀** | [04](04_automated_review_and_governance.md) | +| **검증기가 스스로 터지지 않게** — `$ref` 지연 해석이라 늦게 터지는 실패를 회귀로 고정 | [04](04_automated_review_and_governance.md) | +| **판정하지 않는 검증기** — 드리프트 체커는 옳고 그름을 고르지 않고 *어긋남만* 보고. v1이 **범위 밖을 스스로 명시**해 v2가 이어받음 | [04](04_automated_review_and_governance.md) | +| **픽스처 자기충족 회피** — 픽스처 작성자 = 채점기 작성자면 통과가 자기충족 → 실제 산출물로 돌리되 **판정은 사람**(Owner≠Reviewer) | [04](04_automated_review_and_governance.md) | +| ⭐ **메타-학습 루프** — 사고 → `PITFALLS_REGISTRY` 등재 → **재발방지 장치 필수**(규칙 ②) → 금지조항/스크립트/CI | [04](04_automated_review_and_governance.md) | +| **`memory/` 층** — 핸드오프(며칠~몇 주)와 수명이 다른 *"다시 배우지 않아야 할 사실"* + 로그 유실 사고 | [05](05_human_collaboration.md) | +| **하네스 계보** — 원본(CC BY 4.0) → BIOP01·BIOP02, *"복사가 아니라 벤치마킹 이식"* | 위 §계보 | +| ✅ **닫힘**: `auto_review_config.json` 정렬(Leader 승인) | [04](04_automated_review_and_governance.md) | + **미해결로 남은 것 (설계서가 기록하는 미완성 지점):** - 🔴 **검증 게이트 ①의 실행 명령이 없다** — BIOP02용 결정론 재계산 스크립트가 리포에 부재. `auto_review_gate.py`는 문서 규칙 검사이지 수치 재계산이 아니다. 채워질 때까지 사람이 수동 대조. -- 🔴 **`auto_review_config.json`의 `ai_review.agents`가 아직 `["paper-critic", "reviewer"]`** — `SKILL.md`는 5단계를 `paper-critic` 단독으로 고쳤는데 config가 따라오지 않았다. `enabled=false`(dry-run)라 실害는 없으나 **활성화 전 정리 필요**. - 🔴 **게이트 ① 이후의 "수정"에 권한 제약이 없다** — `manuscript-writer`가 `Write`를 보유해 리뷰 반영 중 검증된 숫자를 다시 쓸 수 있고, 방어는 게이트 ②의 **사후 재대조**뿐이다. 같은 규율이 `paper-critic`에는 **도구 수준으로**(쓰기 권한 없음) 걸려 있어, 하네스 안에 *권한으로 막은 곳*과 *말로만 막은 곳*이 섞여 있다. 부수로 `venue-reviewer`는 `tools:` **미선언 → 전체 도구 상속**이라 격리가 프롬프트로만 강제된다. → [01](01_two_layer_architecture.md), [02](02_agents_and_roster.md), [03](03_routing_and_artifact_contract.md) - 🔴 **판정 어휘에 "더 해도 pass가 안 되는 것"을 적을 칸이 없다** — `critic_status`(`pass·caution·reject`)에 *"현재 데이터로는 식별 불가"* 가 없어 그런 항목이 `caution`으로 뭉뚱그려진다. BIOP02-75가 이 빈칸 때문에 **티켓 성공 기준 자체를 재정의**해 우회했다. → [04](04_automated_review_and_governance.md) - 🔴 **비판 자체를 검증하는 층이 없다** — 루프가 비판을 생산(③)한 뒤 곧바로 확인 주체 배정(④)으로 넘어가, **Critic의 산출물만은 Critic을 거치지 않는다.** 2026-07-27 하루에 Critic 코멘트 4건이 작성자 본인에게 사후 정정된 것이 그 비용이다. → [04](04_automated_review_and_governance.md) - 🔴 **`SKILL.md` 자체가 현실보다 뒤처져 있다** — L10 *"집필 이전 단계 산출물이 아직 없다"* · L18 ``. 실제로는 `manuscript/sections/`에 5개 섹션이 있고 Discussion 한계까지 작성됐다. **` ⚠️ 위 3건은 **관찰된 갭이지 채택된 설계 변경이 아니다.** 따라서 위 "변경 이력"에는 넣지 않았다. 셋 다 고치려면 `.claude/agents/*`·`schemas/*`·`auto_review_config.json`을 건드려야 하고, 그것은 **Critic이 자기 검수 기준을 스스로 정하는 일**이라 `CLAUDE.md`의 `❌ anti-self-reference`에 걸린다 → **Leader 승인 사안.** 특히 판정 어휘는 기존 `critic_report.json`의 유효성에 영향을 주므로 **BIOP02-75 최종 서명 이후**가 맞다. +> ⚠️ 위 항목들은 **관찰된 갭이지 채택된 설계 변경이 아니다.** 따라서 위 "변경 이력"에는 넣지 않았다. 셋 다 고치려면 `.claude/agents/*`·`schemas/*`·`auto_review_config.json`을 건드려야 하고, 그것은 **Critic이 자기 검수 기준을 스스로 정하는 일**이라 `CLAUDE.md`의 `❌ anti-self-reference`에 걸린다 → **Leader 승인 사안.** 특히 판정 어휘는 기존 `critic_report.json`의 유효성에 영향을 주므로 **BIOP02-75 최종 서명 이후**가 맞다. diff --git a/ai_scientist/template/03_routing_and_artifact_contract.md b/ai_scientist/template/03_routing_and_artifact_contract.md index 43d2069..da2139d 100644 --- a/ai_scientist/template/03_routing_and_artifact_contract.md +++ b/ai_scientist/template/03_routing_and_artifact_contract.md @@ -52,6 +52,20 @@ | 검수 리포트 | 항목별 판정 + 종합 판정 (어휘는 [04](04_automated_review_and_governance.md) 참조) | | 실험 결과 | `{{METRIC_FIELDS}}` + 실행 시점 커밋 해시 | +### ⚠️ 스키마는 "선언"이 아니라 "검증"이어야 한다 + +가장 흔한 함정: 산출 스크립트가 docstring에 *"○○ 스키마 형식으로 출력한다"* 라고 **적어만 두고 검증은 하지 않는 것.** +그러면 위반은 **아무 신호도 내지 않고** 몇 주씩 쌓인다 — 형식이 *주장*일 뿐 *검사*가 아니기 때문이다. + +**규칙 셋:** +1. **산출 시점에 검증한다.** 검증에 실패하면 파일을 쓰지 않는다. +2. **현장에서 필요해 붙은 필드는 삭제가 아니라 스키마 확장으로 흡수한다.** 규율을 지키려고 유용한 정보를 버리지 않는다(예: 값이 *실측인지 fallback인지* 구분하는 표시). +3. **스키마 자체의 참조 해석을 회귀로 고정한다** — 참조는 지연 해석돼서 **빈 입력에선 통과하고 실제 데이터에서만 터진다.** + +> ⚠️ **CI에 blocking으로 거는 시점을 주의한다.** +> 기존 산출물에 위반이 남아 있는 상태로 켜면 **"원래 빨간 CI"** 가 되고, 그러면 아무도 CI를 보지 않는다. +> **먼저 위반을 해소하고, 그다음에 켠다. 순서를 지키는 것도 설계다** — 게이트를 켜는 시점이 잘못되면 게이트 자체가 무력해진다. + ## "지어내지 않기"를 계약에 박는다 계약은 *무엇을 넘길지*뿐 아니라 **없는 것을 있는 척 넘기지 않기**까지 규정한다. diff --git a/ai_scientist/template/04_automated_review_and_governance.md b/ai_scientist/template/04_automated_review_and_governance.md index 20a29a4..93d3f93 100644 --- a/ai_scientist/template/04_automated_review_and_governance.md +++ b/ai_scientist/template/04_automated_review_and_governance.md @@ -18,17 +18,56 @@ ### ① CI 결정론 검증기 -**결정론·오프라인 검증만** blocking으로 건다. 최소 3종을 권장한다: +**결정론·오프라인 검증만** blocking으로 건다. 두 부류로 나뉜다. + +**(a) 산출물을 검사하는 것 — 최소 3종** | 검증기 | 무엇을 막나 | |---|---| | 검수기 회귀 테스트 | **검수기가 무조건 통과시키는 회귀** — 채점이 케이스에 실제로 제약되는지 | | 인용 검증기 회귀 테스트 | 조회 실패 시 약한 대체 검색으로 통과시키던 **버그가 죽어 있는지** | | **숫자 드리프트 검사** | **정본(결과 파일) ↔ 문서 표**의 수치 불일치 | +| (도메인) 누수·분할 검사 | 학습/평가 분할의 **disjointness assert** | + +**(b) ⭐ 검사기 자신을 검사하는 것 — 잊기 쉽고, 없으면 (a) 전체가 허사다** + +| 검증기 | 무엇을 막나 | +|---|---| +| **mutation 하네스** | *"실무자가 저지를 법한 실수를 심으면 게이트가 잡는가"* — **control ↔ mutated 델타**로 판정 | +| **공허통과(vacuous pass) 회귀** | 게이트가 **아무것도 검사하지 않고 초록불**을 주는 상태(비교군이 하나뿐, 대상 경로가 없는데 통과 등) | +| 검증기 크래시 회귀 | 검증기가 **예외로 죽는** 경로 — 특히 **지연 해석**되는 참조는 빈 입력에선 통과하고 실제 데이터에서만 터진다 | > **왜 CI인가:** 검증 자산을 만들어 두고도 워크플로가 없으면 **사람이 기억할 때만 돈다.** 눈으로 대조해야만 발견되는 불일치는 며칠~몇 주씩 방치된다. > **명시적 비범위:** 자동 리뷰 루프의 활성화 플래그는 CI가 건드리지 않는다. 라우터 문서도 손대지 않는다. +#### ⭐ "통과만 하는 게이트는 게이트가 아니다" + +(b)가 왜 (a)보다 중요할 수 있는지는 실패 모드의 **색깔**로 설명된다: + +| 실패 | 어떻게 보이나 | +|---|---| +| 결과가 틀림 | **빨간불** — 누군가 본다 | +| **검증기가 죽음 / 공허통과** | **초록불** — 아무도 안 본다 | + +그래서 규칙이 하나 필요하다 — **게이트를 만들면 일부러 깨뜨려 본다.** +통과만 확인하고 끝내면 *"잡는 게이트"* 인지 *"통과시키는 게이트"* 인지 구분되지 않는다. +가드를 **동시에 무력화**했을 때 CI가 여전히 초록이라면, **그 수정이 통째로 되돌아가도 아무도 모른다**는 뜻이다. + +#### 판정하지 않는 검증기 — 무엇을 안 보는지 스스로 적는다 + +드리프트 계열 검증기는 **판정하지 않는다.** 어느 숫자가 옳은지 고르지 않고 **어긋난다는 사실만** 보고한다. 판정은 사람 몫이다. + +그리고 **자기 범위 밖을 문서에 명시한다** — *"나는 표 안의 숫자만 보고 산문 속 수치는 안 본다"* 처럼. +이게 있어야 다음 사람이 **그 빈칸만 이어받아** 확장할 수 있다. 범위를 안 적으면 **검사됐다고 착각**하게 된다. + +#### 픽스처 자기충족을 피한다 + +**픽스처를 만든 사람과 채점기를 만든 사람이 같으면** 그 통과는 자기충족이다. +같은 채점기를 **실제 산출물**에 돌려 보는 경로를 따로 둔다. 단 그 실행은 **읽기 전용**이고 그 판정은 공식 상태값이 **아니다** — 최종 판정은 사람이 owns한다(Owner ≠ Reviewer). + +> 이 세 절(mutation · 범위 명시 · 자기충족 회피)은 뿌리가 하나다 — +> **검증기가 피검증 대상과 같은 맹점을 공유하면, 버그가 "현상"으로 보인다.** + ### ② 자동 리뷰 루프 ``` diff --git a/ai_scientist/template/05_human_collaboration.md b/ai_scientist/template/05_human_collaboration.md index bdedf7f..1f906a3 100644 --- a/ai_scientist/template/05_human_collaboration.md +++ b/ai_scientist/template/05_human_collaboration.md @@ -77,6 +77,21 @@ > ⚠️ **세션 로그·기억은 상태의 근거가 아니다.** 상태를 인용할 땐 **기록 id 또는 `파일:줄`** 을 밝힌다. +### 한 층 더 — 수명이 다른 두 종류의 기록 + +핸드오프 3종이 **"지금 상태"** 를 넘긴다면, 별도의 기억 저장소는 **"세션이 바뀌어도 다시 배우지 않아야 할 사실"** 을 넘긴다. 섞으면 둘 다 못 쓴다. + +| | 수명 | 담는 것 | +|---|---|---| +| 핸드오프 · 세션로그 · TODO | 며칠~몇 주 | 지금 막힌 것, 어제 한 일 | +| **`{{MEMORY_STORE}}`** | 프로젝트 내내 | 사람의 작업 방식·선호, 반복 확인된 운영 사실, 외부 자원 위치 | + +**한 항목 = 한 파일 = 한 사실**로 두고 색인을 둔다. 다음 세션이 **먼저 읽는** 자리이기 때문이다. +넣을 가치가 있는 것은 **반복해서 대가를 치른 사실**이다(예: *"상태를 판단하기 전에 반드시 원격을 먼저 당긴다"*). + +> ⚠️ **기억도 낡는다.** 기록 시점의 사실이므로, **파일·명령·플래그를 지목하는 항목은 쓰기 전에 아직 존재하는지 확인**한다. +> 리포에 없는 것을 "있다"고 기억하는 순간, 그 기억은 보호가 아니라 **오정보**가 된다. + ## 채널 `{{CHANNELS}}` 를 목적별로 분리한다 — 공지 / 작업 진행 / **결과 공유(검수 통과 후만)** / 자원·장애. diff --git a/ai_scientist/template/APPENDIX_CASE_BIOP02.md b/ai_scientist/template/APPENDIX_CASE_BIOP02.md index dc894dd..cc45018 100644 --- a/ai_scientist/template/APPENDIX_CASE_BIOP02.md +++ b/ai_scientist/template/APPENDIX_CASE_BIOP02.md @@ -128,3 +128,42 @@ 결과적으로 게이트는 **사람의 수동 대조**로 메워졌고, "무엇을 검증했는가"가 개인의 기억에 남았다. **교훈:** 게이트는 **문서에 있는 것으로 존재하지 않는다.** 실행되는 명령이 있어야 존재한다. 그리고 새 프로젝트는 이 자리를 **물려받는 부채가 아니라 처음부터 만들 기회**로 다룰 수 있다. + +--- + +## 사례 10 — 가드를 동시에 무력화했더니 CI가 초록이었다 + +**규칙:** [04](04_automated_review_and_governance.md) *"통과만 하는 게이트는 게이트가 아니다"* + +게이트 수정과 그 회귀 테스트가 **다른 PR로 따로 병합**되면서, 회귀 테스트가 **CI 어디에도 걸리지 않은** 상태가 됐다. 워크플로에 해당 테스트 참조가 0건이었다. + +확인 방법이 결정적이었다 — **두 가드를 동시에 무력화**해 봤다. 기존 CI 스텝은 **"구멍 0 / exit 0"으로 초록**이었고, 신규 스텝만 실패를 잡았다. +즉 **그 수정이 통째로 되돌아가도 CI는 초록**이었다는 뜻이다. + +**교훈:** 게이트를 만들면 **일부러 깨뜨려 본다.** 그리고 *수정*과 *그 수정을 지키는 회귀 테스트*는 **같이 병합**한다 — 순서가 어긋나면 보호막이 없는 구간이 생긴다. + +--- + +## 사례 11 — 검증기가 늦게 터진다 + +**규칙:** [03](03_routing_and_artifact_contract.md) *스키마 자체의 참조 해석을 회귀로 고정한다* + +스키마의 식별자가 상대경로면 내부 참조를 해석할 때 base와 합쳐져 **없는 경로**가 만들어지고 검증기가 예외로 죽는다. +고약한 점은 **참조가 지연 해석**이라 **빈 입력에서는 통과하고 실제 데이터에서만 터진다**는 것이다. 그래서 "검증기가 잘 돈다"고 믿은 채로 시간이 흘렀다. + +**교훈:** 검증기의 **크래시 경로**도 회귀로 고정한다. 검증기가 죽으면 **빨간불이 아니라 아무 신호도 없는 경우**가 있다. + +--- + +## 사례 12 — "형식대로 출력한다"는 말만 있고 검사는 없었다 + +**규칙:** [03](03_routing_and_artifact_contract.md) *스키마는 "선언"이 아니라 "검증"이어야 한다* + +산출 스크립트들이 docstring에 *"○○ 스키마 형식 출력"* 이라고 적어두었지만 **어디에서도 검증하지 않았다.** 리포 전체에 그 스키마를 검사하는 코드가 없었다. +결과적으로 **필수 필드 누락이 최소 4주간 아무에게도 보이지 않았다.** + +대응에서 두 가지가 갈렸다: +* 현장에서 필요해 붙은 필드를 **삭제하지 않고 스키마로 흡수**했다(값이 실측인지 fallback인지 구분하는 표시 등). 규율 때문에 유용한 정보를 버리지 않는다. +* 검증기를 만들되 **CI blocking은 미뤘다** — 위반이 남은 채 켜면 *"원래 빨간 CI"* 가 되고 아무도 CI를 안 보게 된다. + +**교훈:** **"형식"은 주장이 아니라 검사여야 한다.** 그리고 **게이트를 켜는 순서도 설계다.** diff --git a/ai_scientist/template/PROJECT_SLOTS.md b/ai_scientist/template/PROJECT_SLOTS.md index c2c4a1e..c61098d 100644 --- a/ai_scientist/template/PROJECT_SLOTS.md +++ b/ai_scientist/template/PROJECT_SLOTS.md @@ -70,7 +70,9 @@ | `{{CRITIC_LEAD}}` | 검수 총괄 (판정 owns) | | | `{{CROSS_REVIEW_MAP}}` | 작성자 → 리뷰어 매핑 (**owner ≠ reviewer**) | | | `{{SUBCHECK_RULE}}` | 도메인 sub-check 선정규칙 (후보 목록이 아니라 **규칙**) | | -| `{{HANDOFF_FILES}}` | 상태 인수인계 파일 | | +| `{{HANDOFF_FILES}}` | 상태 인수인계 파일 (수명: 며칠~몇 주) | | +| `{{MEMORY_STORE}}` | **세션을 넘어 지속되는 사실** 저장소 + 색인 (수명: 프로젝트 내내) | | +| `{{PITFALLS_REGISTRY}}` | 실수 누적 레지스트리 — **각 항목에 재발방지 장치 필수** | | > ⚠️ 매핑은 **"기본 후보"가 아니라 "owner가 아닌 사람"** 이 이긴다. 고정 배정으로 적어두면 후보가 owner인 순간 규율을 어기게 된다. diff --git a/ai_scientist/template/README.md b/ai_scientist/template/README.md index 22583c1..fb74f0e 100644 --- a/ai_scientist/template/README.md +++ b/ai_scientist/template/README.md @@ -30,6 +30,23 @@ | [06_positioning_method.md](06_positioning_method.md) | **포지셔닝 방법론**(결론 아님) | 기획 초기 | | [APPENDIX_CASE_BIOP02.md](APPENDIX_CASE_BIOP02.md) | 실사례·사고 기록 (참고용) | 규칙의 이유가 궁금할 때 | +## 이식 원칙 — "복사"가 아니라 "벤치마킹 이식" + +이 템플릿을 새 프로젝트에 얹을 때 가장 흔한 실패는 **통째로 복사하는 것**이다. 남의 도메인 판정 기준까지 따라오면, 자기 분야의 과잉주장은 못 잡고 엉뚱한 문장만 막는다. + +| 가져오는 것 (골격) | 새로 쓰는 것 (도메인) | +|---|---| +| 러너·오케스트레이션 구조 | **판정 기준**(`{{FORBIDDEN_PHRASES}}`·`{{CHECKLIST}}`) | +| control ↔ mutated 델타 방식 | **어떤 실수를 심을 것인가**(mutation 케이스) | +| 티어·게이트 배치 | **무엇이 Tier C인가**(`{{TIER_C_PATHS}}`) | +| 계약·스키마의 *형태* | 필드의 *내용*(`{{METRIC_FIELDS}}`) | + +이 분리를 코드에서도 지킨다 — **로직은 도메인 무관, 프로젝트별 값은 전부 config로.** +그래야 다음 프로젝트가 *스크립트 복사 + 자기 config*만으로 작동한다. + +> 하네스가 여러 프로젝트에 얹혀 있다면 **3자 대조표**(원본 ↔ 인스턴스 A ↔ 인스턴스 B)를 공용 자산으로 두면 좋다. +> 어디가 공통이고 어디가 도메인 특수인지가 그 표에서 드러나고, **다음 이식이 무엇을 바꿔야 하는지**도 거기서 나온다. + ## 표기 규칙 - `{{SLOT}}` — [PROJECT_SLOTS.md](PROJECT_SLOTS.md)에서 채우는 자리. **채우지 않은 `{{ }}`가 남아 있으면 그 문서는 아직 쓰면 안 된다.**