Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions ai_scientist/03_routing_and_artifact_contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -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를 보지 않는다. 먼저 위반을 해소하고, 그다음에 켠다.
> **순서를 지키는 것도 설계다** — 게이트를 켜는 시점이 잘못되면 게이트 자체가 무력해진다.

## "지어내지 않기"를 계약에 박았다

산출물 계약 곳곳에 `<FILL>` 플레이스홀더가 있다(예: `SKILL.md:18`의 `<FILL: docs/manuscript/preprint.md (미존재)>`). 이는 **아직 없는 것을 없다고 표시**하는 장치다.
Expand Down
92 changes: 82 additions & 10 deletions ai_scientist/04_automated_review_and_governance.md
Original file line number Diff line number Diff line change
Expand Up @@ -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단계 티어 게이트 — "사람을 어디에 둘지"의 설계

Expand Down Expand Up @@ -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·공개).
> *"개별 사실이 모두 맞아도 그것들이 조합된 주장(프레이밍·비교·설계)은 틀릴 수 있다."*

## 거버넌스: 절대 금지 사항 (자동화가 넘지 못하는 선)

Expand Down
24 changes: 23 additions & 1 deletion ai_scientist/05_human_collaboration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

## 협업 채널

Expand Down
Loading
Loading