# 프롬프트 덜어내기 (prompt diet) — 2026-08-03

> 브랜치 `feat/prompt-diet` (분기점 `bd6f7af4`, 푸시됨).
> 되돌리려면 그 커밋으로 돌아가거나 각 단계 커밋을 개별 revert 한다.

## 배경

사용자 지시 — *"프롬프트 사이즈 전수조사하자. 너무나 큰 것들은 무조건
할루시네이션 발생해서 조사만 해 고치지 말고."* → 조사 완료 후
*"그래 시작하자 대신 이전으로 복구 되기 쉽도록 해."*

## 조사 결과 (Claude·Codex 독립 측정 후 대조)

### 두 측정이 일치한 것

| 항목 | Claude | Codex |
|---|---:|---:|
| 팩 `.md` | 301개 / 436,857자 | 301개 / 436,857자 |
| 팩 `.json` | 67개 / 98,044자 | 67개 / 98,044자 |
| 팩 실효 합계 | 370개 / 535,281자 | 368개 / 534,901자 |
| 판 drift | 50 stem | 48개 / 28,852자 |
| DB `prompt_template` active | 54행 / 18모듈 | 54행 / 45,768자 / 18모듈 |
| **DB 가 실제로 나가는가** | **0건** | **교집합 0** |

팩 파일 수 2개 차이 = Claude 가 `.TAG` 2개(380자)를 포함했기 때문.

**DB 결론**: 로더는 `db=` 세션을 받을 때만 DB 를 본다. 297개 `load_prompt`
호출 중 db 를 넘기는 21개의 모듈과 DB active 18모듈은 **교집합이 0**이라,
DB 에 남은 3~5월 옛 판은 실제로 나가지 않는다. 파일만 고치면 된다.

### 실제 송신량 (Opik `theroad-scene-lab`)

`messages` 의 role 로 지시/데이터를 가르고, 팩 원문과 줄 단위로 대조해
**고정 지시분**을 분리했다.

| 대상 | 한 콜 전체 | 고정 지시 | 데이터 | 지시 비율 |
|---|---:|---:|---:|---:|
| shot_aware_bg_render_plan | 130,685 (최대) | 13,732 + schema | 114,089 | 11% |
| scene_detail | 68,625 | 38,853 (+schema) | 29,772 | 57% |
| shot_staging | 28,996 | 15,897 (+schema 10,993) | 13,099 | 55% |
| seed_variant_author | 22,186 | 18,036 | 4,150 | 81% |
| structure_form_ref_brief | 68,364 | 2,988 | 65,758 | 4% |

★ `structure_form_ref_brief` 의 65.8k 는 **지시가 아니라 시나리오 원문**이다.
처음에 "압도적 1위"로 잘못 봤다가 갈라내며 정정했다.

### 팩 밖 프롬프트

`prompts/_base` 밖 `backend/app` 코드에 박힌 프롬프트가 별도로 있다
(AST 스캔, 200자 이상 227건 146,209자 — f-string 이중 계산 제거 후).
**판 번호도 되돌릴 이력도 없다.** 최대는 `space_set_bg.py` 20,459자.

### 곁가지 발견

- **관측 결함**: 2026-08-01 `scene_detail` 트레이스 9건이
  `{"role":"system","content":"sys"}, {"role":"user","content":"u"}`
  (`prompt_tokens=3`). 연결 시험 흔적 — 집계에서 제외해야 한다.
- **판 drift 50건**: 최신 판에 stem 이 없어 옛 판에서 로드된다. 최대
  `background_prompt/system.md` 5,744자(v14 로드, 최신 v15),
  `search_grounded_ref/scope_system.md` 2,809자(v3 로드, 최신 v10).
- `outlook_extractor/extract_prompt` 는 파일에 없고 DB 에만 있어 호출되면
  `FileNotFoundError` — 다만 그 스텝은 `deprecated` + `on_demand`.

## 문제의 성격은 넷이고 해법이 다르다

| 유형 | 실례 | 덜어낼 때 잃는 것 | 위험 |
|---|---|---|---|
| A 데이터 중복 | shot_aware 의 동일 JSON 재첨부 | 없음 | **낮음** |
| B 역할 중복 | scene_detail: 카드가 확정한 사안을 system 이 산문으로 재설명 | 없음(권위가 이미 카드) | 낮음~중간 |
| C 스키마 비대 | shot_staging schema 10,993 | 형식 설명 일부 | 중간 |
| D 누적 조항 | system.md 30섹션 · seed author | **육안 반려를 막던 조항** | **높음** |

어제 교훈(*금지를 쌓으면 악화된다 — 모델에게 재료가 남았는지 본다*)의
역방향 적용: **A·B 는 재료를 뺏지 않고, D 는 뺏는다.** 그래서 A→B→C→D 순.

### B 의 근거 — `card-wins precedence` 는 이미 코드 계약

`detail_steps.py:2966` 주석에 "R1-I4 card-wins precedence" 가 있다. 충돌 시
카드가 이기기로 되어 있는데 system 이 같은 사안을 다시 가르친다.
**값보다 설명이 길다**:

| 사안 | 카드의 값 | system.md 산문 | 비율 |
|---|---:|---:|---:|
| ID 정책 | 4,275 | 7,910 (ID Policy 3,601 + Rule X-2 4,309) | 1.85배 |
| Background Binding | 464 | 993 | 2.1배 |

같은 문제의식이 이미 코드에 있다 — `_card_metadata` 를 inject 에서 빼는
이유가 주석에 "token budget + LLM confusion 방지".

## 실행 순서

| 단계 | 작업 | 상태 | 커밋 |
|---|---|---|---|
| ① | camera-only repair 의 원문 번들 재첨부 제거 | **완료** | `1409819b` |
| ② | 최초 shot_aware 의 dossier per-BG facts 중복 제거 | **완료** | `8d1bd621` |
| ③ | `scene_detail` v38 — 역할 중복을 카드 decoder 로 (-6,139자) | **완료** | `28561203` |
| ③-fix | v39 — 육안 반려로 재현 표면 조항 224자 복원 (-5,915자 유지) | **완료** | `69c3aa56` |
| ④ | 같은 입력 before/after 좌우 교대 A/B | **완료 — 육안 판정 받음** | 갤러리 |
| ⑤ | `structure_form_ref` 언어 판정용 원문 → 앞부분 발췌 (-약 64,500자/콜) | **완료** | `19f20c5f` |
| ⑥ | `shot_staging` v18 — 스키마 중복 description 축소 (-1,225자, 400 위험 완화) | **완료** | `65a8d370` |
| ⑦ | `shot_dependency_t2i` v9 — 스키마 description 을 system.md 포인터로 (-1,735자 / -51.5%) | **완료** | `23c4f015` |
| ⑧ | form_ref 심판 머리말에서 배치 서술 제거 (**979자 × 311콜 ≈ 30만자**) | **완료** | `7c9f66ff` |
| ⑨ | 판 단언 하드코딩 제거 — 4-point sync 를 최신 판 파생으로 | **완료** | `e481eb90` |
| ⑩ | 판 목록에서 dot 디렉토리 제외 + 팩 안 pytest 캐시 제거 | **완료** | `4adb951b` |
| — | Codex 리뷰 3건 수용 — 프롬프트 계약 변경이 CP 식별자에 잡히게 | **완료** | `102fc00f` |
| ⑪ | `scene_detail` v40 — 육안 판정 결함 3건에 재료 (+399자) | **완료 — 육안 승인** | (미커밋) |

### ④ 통합 A/B 와 ⑪ 검증의 결론

- **스키마 축소 2건은 출력을 흔들지 않았다.** 대조군(같은 판 2회) 대비
  `shot_dependency_t2i` **1.08배** · `shot_staging` **0.98배** — 둘 다 변동
  폭 안. ★대조군이 1건뿐일 때 후자가 2.30배로 나와 정반대로 읽힐 뻔했다.
  **대조군 표본이 적을 때의 비율은 읽지 말 것.**
- **⑪ 은 판정 축을 문자열로 세면 오독한다.** 분수 표현(`a third of the
  frame`)만 세면 v39 2 → v40 3 으로 악화로 보이는데, 실제로 v40 은 분수를
  버리고 정성 표현(`dominating`)으로 갔다. **이미지에서는 3쌍 모두 v40 의
  사진 크기가 손 기준으로 자연스러웠고 사용자 승인을 받았다.**
  갤러리 = `artifact/20260804_v40_verify/`.

## 하지 않기로 한 것

- **카드 자체 축소** — 20,785자 중 `continuity_elements_used` 37.9% +
  `render_strategy` 36.0% 가 typed 계약이자 감사 기록. 기록 손상 위험.
- **geometry 의 camera 후보 중복 제거** — 코드 주석상 exact-copy 실패를
  막으려는 **의도적 중복**(`:232-235`).
- **graph/anchor repair 의 원문 유지** — 누락 노드·관계를 다시 만들 수
  있어 원문 컨텍스트가 필요하다.
- **누적 조항(D) 정리** — 실험 없이 손대면 막고 있던 결함이 돌아온다.
- **금지 추가** — 어제 교훈.

## 데이터 전체 첨부에 대한 사용자 예외 (2026-08-03)

> *"단순한 정보를 얻기 위해서 전체를 넣는 것보다 부분만 넣어도 되는
> 경우에는 예외로 하자 (단 무조건 전체가 아니어도 되는 경우만!!!)"*

CLAUDE.md 최상단 절대 규칙(데이터를 자르지 마라)의 **조건부 예외**다.
적용 조건 = ①목적이 단순 정보 획득이고 ②전체가 필요 없음이 확인된 경우.
각 사용처의 목적을 확인한 뒤에만 적용하며, 전체가 필요한 곳은 그대로 둔다.

## 검증

이 영역은 테스트로 판정되지 않는다([[feedback-focused-tests-over-broad-regression]]).
같은 입력에 대해 before/after 산출물을 **좌우 교대 2회 A/B** 로 육안 비교한다
(절대 문턱 금지 — 어제 실측에서 엄한 문턱이 실사를 선화로 붕괴시켰다).

## 산출물

- 조사 스크립트·원자료: `artifact/20260803_프롬프트_전수조사/`
  (`audit_prompt_size.py`, `scan_inline_v2.py`, `opik_payload_audit.py`,
  `report.json`, `inline_prompts_v2.json`, `opik_payload.json`)
- 변경 이력: `01_변경이력.md`


## ④ A/B 실행 결과 (2026-08-03)

산출: `artifact/20260803_prompt_diet_ab/`
갤러리: `http://192.168.35.42:8940/artifact/20260803_prompt_diet_ab/index.html`

**방법** — Opik 에 남은 실제 요청 전문을 재사용하고 `system` 만 갈아끼웠다.
트레이스 system = `v37 파일 + 런타임 주입분` 이므로 앞부분만 v38 로 치환해
**주입분 1,659자까지 동일**하게 만들었다. 두 콜의 차이는 오직 덜어낸
6,139자뿐(41,470 → 35,331자).

- 표본 6 shot — 정책·구성이 다르게 선정(`base_id_required` 1건,
  multi-character 2건, `reproduction_surface_rule.applies` 4건, prop 3건)
- LLM 12/12콜 성공, 이미지 12/12장 생성(PIL 무결성 0 손상, 1376×768)

**관측된 차이 1건 — 원인 미확정**

`s25sh7` 에서 배경 ID 표현이 달라졌다.
before = `The exact **L04B03** background recedes behind her` /
after = `The **disturbed living room** recedes behind her`.

단정하지 않는다 — 배경 ID 를 bases 에 가진 shot 3건 중 **before 에서도 2건
(s29sh1 의 L18, s26sh3 의 L16·L17)은 ID 를 쓰지 않았다.** 원래 일관되지
않았고, LLM 출력은 확률적이라 1회 차이로는 원인을 가릴 수 없다. 게다가
배경 ID 는 `background_binding` 섹션(993자, **손대지 않음**)과 카드가
담당하고, 내가 뺀 것은 character ID 규칙이었다.
확정하려면 같은 shot 반복 실행으로 등장 빈도를 비교해야 한다.

**한계 2가지**

1. 수집된 21건에 `generic_descriptor_allowed` shot 이 하나도 없어 그 규칙은
   시험되지 않았다.
2. 이미지에 **참조를 붙이지 않았다** — short_id → 자산 매핑이 프로젝트마다
   달라(같은 `C01` 이 프로젝트마다 다른 인물) 재현 비용이 컸다. 따라서
   인물 동일성은 판정 불가이고 구도·자세·배경·조명의 상대 비교만 가능하다.
