# STATE — 말포이 5편 파이프라인 상태 공간
- 정본: `state_schema.json`(키·타입·reducer·쓰는/읽는 노드) · `state.py`(TypedDict + Annotated reducer) · `pipeline.yaml`(노드 reads/writes)
- 이 문서는 위 3파일에서 뽑은 사람용 정리. 노드 정본 = `설명/05_배선도/roster.json` v2(77노드·113배선·반려 15)
- 원칙: 키는 도메인별 12개로만 묶음 → 노드는 자기 도메인 하위 키만 씀, 병렬로 같은 키에 써도 reducer가 합침
- 표기: W = 씀, R = 읽음, RW = 둘 다. 모든 노드는 추가로 `runtime.trace`에 자기 id를 누적(표에서 생략)
## 1. 한눈에
| 도메인 | 키 | 누적 방식(reducer) |
|---|---|---|
| 입력·계획 | `brief` · `plan` · `rules` | merge_dict · merge_dict · upsert_by_id |
| 에셋 | `assets` · `audio` · `blocks` | merge_nested_by_id · merge_nested_by_id · merge_by_id |
| 산출 | `renders` · `docs` | merge_by_id · merge_by_id |
| 관리 | `qa` · `feedback` · `budget` · `runtime` | merge_qa · upsert_by_id · merge_budget · merge_runtime |
- 흐름 한 줄: `brief` → `plan` → (`assets.images` → `assets.clips` → `assets.mattes`) ∥ `audio` ∥ `blocks` → `renders` → `docs`, 옆에서 `qa`·`budget`·`runtime`이 계속 쌓이고 `feedback`이 `rules`를 키움
## 2. 상태 키 표
| 키 | 담는 것 | 타입 | reducer | 쓰는 노드 | 읽는 노드 | 실제 값 예 |
|---|---|---|---|---|---|---|
| `brief` | 밈 리서치·레퍼런스 | object{reference, meme_research, flow_research, pinterest} | merge_dict: 딕셔너리 얕은 병합(뒤가 이김) | S0 ref·r_meme·r_flow·r_pin | S0 sb·sb3
S1 img_design
S2 flow_test | `00_ref/ref_DeKsEh0sBPy.mp4`, `00_ref/research-flow.md`, 핀터레스트 122장 |
| `plan` | storyboard·EDL·박 격자·배치 정의 | object{episodes, storyboard, edl, beat_grid, image_batches, flow_jobs, credit_plan} | merge_dict: 딕셔너리 얕은 병합(뒤가 이김) | S0 sb·sb3
S6 c_v4 | S0 sb3
S1 img_design·img_frames·img_outfits·img_s6·img_v3
S2 전부(6)
S4 au_v1_bgm·au_v1_vo·au_v1_sfx·au_v3_song·au_v3_vo·au_v4
S5 mo_a·mo_b·mo_c
S6 c_v1·c_v3o
S7 lay_v1·lay_v2·lay_v3·lay_v4
S8 pr_P1·pr_P2·graph | STORYBOARD.md S1~S7, `V1-EDL.md`, `06_flow/v1_jobs.json` 11잡 |
| `rules` | 누적 제약(사용자 반려·사고 교훈) — 이후 전 노드 적용 | list[Rule{id,text,source,scope}] | upsert_by_id: 리스트, 같은 id면 필드 병합 아니면 추가 | S2 flow_test
user_review | S0 r_meme·r_flow·r_pin·sb·sb3
S1 img_design·img_frames·img_outfits·img_s6·img_v3
S2 전부(6)
S4 au_v1_bgm·au_v1_vo·au_v3_song·au_v3_vo·au_v4·au_v5_song
S5 mo_a·mo_b·mo_c
S6 c_v1·c_v2·c_v2_fix·c_v3·c_v4·c_v5
S8 pr_P4 | R-I01 텍스트만 영상 생성 금지 · heavy 동시 2 |
| `assets` | 이미지·클립·매트 (id → 레코드) | object{images: id→ImageRec, clips: id→ClipRec, mattes: id→MatteRec} | merge_nested_by_id: 하위 맵마다 merge_by_id | S1 전부(6)
S2 전부(6)
S3 matte_q1·matte_q2·matte_q4 | S1 img_frames·img_outfits·img_s6·img_v3·img_fix
S2 전부(6)
S3 matte_rd·matte_q1·matte_q2·matte_q4
S5 mo_c
S6 c_v1·c_v2·c_v3·c_v4
S7 fx_A·fx_B·fx_C·fx_D·fx_E·lay_v1·lay_v2·lay_v3·lay_v4
S8 pr_P1·pr_P2·wire_prev | `02_frames/S4_fullbody.png` → `06_flow/v1/V1_S4.mp4`(8초·12크레딧·채택) → `05_matte/v1/V1_S4.webm` |
| `audio` | 곡·VO·SFX·박 실측·후보 | object{tracks, vo, sfx, beats, candidates} (각 id→레코드) | merge_nested_by_id: 하위 맵마다 merge_by_id | S4 전부(7) | S4 au_v5_song
S5 mo_b
S6 c_v1·c_v2·c_v3o·c_v3·c_v4·c_v5
S7 fx_A·fx_B·fx_C·fx_D·fx_E
S8 pr_P3·wire_prev | `04_audio/bgm/BGM_MAIN_aligned.wav` 128.17BPM, beats 드롭 7.484 |
| `blocks` | 코드 배경·오버레이 블록 (id → Block) | id→Block | merge_by_id: id → 레코드, 레코드끼리 필드 병합 | S5 mo_a·mo_b·mo_c
S6 c_v3o | S5 mo_b·mo_c
S6 c_v1·c_v2·c_v3·c_v4·c_v5
S7 fx_A·fx_B·fx_C·fx_D·fx_E·lay_v1·lay_v2·lay_v3·lay_v4 | `03_motion/compositions/kinetic_wall.html` layer=bg/fg |
| `renders` | 편별 패스·드래프트·최종·업로드본·컨택트시트 (V1~V5 → RenderRec) | V1~V5→RenderRec | merge_by_id: id → 레코드, 레코드끼리 필드 병합 | S6 c_v1·c_v2·c_v2_fix·c_v3·c_v4·c_v5
user_review | S6 c_v2_fix·c_v5
S7 fx_A·fx_B·fx_C·fx_D·fx_E·lay_v1·lay_v2·lay_v3·lay_v4
S8 wire_prev | V1 `07_render/V1_SRANK_PULL.mp4` 26.5초 · `V1_contact.jpg` |
| `qa` | 판정 결과·재시도 횟수·게이트 상태 | object{verdicts: list[Verdict], retries: id→int, gates: id→GateState} | merge_qa: verdicts 리스트 누적 · retries 합산 · gates 덮어쓰기 | S1 전부(6)
S2 전부(6)
S4 au_v1_bgm·au_v1_vo·au_v3_song·au_v3_vo·au_v4·au_v5_song
S6 c_v1·c_v2·c_v3·c_v4·c_v5
S7 fx_merge
S8 pr_merge·news_JA·news_JB | S8 pipe_T1·pipe_T2·pipe_T3·pipe_T4·pipe_T5·pipe_T6·news_merge | retries {c_v4: 2}(r1→r3), gates {flow_b_ref: retry} |
| `feedback` | 사용자 반려 큐(quote·target_node·status) | list[Feedback{id,quote,target_node,status}] | upsert_by_id: 리스트, 같은 id면 필드 병합 아니면 추가 | user_review | S0 sb
S4 au_v5_song
S6 c_v2_fix
S8 pr_P4·graph·news_merge·wire | I08 「왜 아래쪽에 악역영애 말포이 좀 없애봐」 → c_v2_fix |
| `budget` | Flow·ElevenLabs·codex 원장 | object{flow_credits, eleven_credits, codex} (각 Ledger) | merge_budget: 원장별 spent 합산 · entries 누적 · start/cap 덮어쓰기 | S0 sb
S2 전부(6)
S4 전부(7) | S2 전부(6)
S4 전부(7)
S8 pipe_T1·pipe_T2·pipe_T3·pipe_T4·pipe_T5·pipe_T6 | Flow 1,050 → 664(386 사용) · ElevenLabs 19,475 |
| `runtime` | 동시성 슬롯·heavy 세마포어·도구 설정·장애/재개 로그·실행 trace | object{slots, heavy, toolchain, incidents, resume_log, trace} | merge_runtime: trace·incidents·resume_log 누적 · slots·heavy·toolchain 병합 | S2 flow_test
S3 전부(5)
S5 gpu_render
S6 sema | S2 flow_b_v1·flow_b_ref·flow_b_extra·flow_b_v3·flow_b_v4
S3 matte_gpu·matte_q1·matte_q2·matte_q4
S5 gpu_render
S6 c_v1·c_v2·c_v3·c_v4·c_v5·sema
S8 pipe_T1·pipe_T2·pipe_T3·pipe_T4·pipe_T5·pipe_T6 | slots {heavy 2, flow 5, eleven 2, codex 4, matte 2, subagents 8} |
| `docs` | 설명 산출물 (id → Doc) | id→Doc | merge_by_id: id → 레코드, 레코드끼리 필드 병합 | S7 전부(11)
S8 pr_P1·pr_P2·pr_P3·pr_P4·pr_merge·pipe_T1·pipe_T2·pipe_T3·pipe_T4·pipe_T5·pipe_T6·pipe_merge·graph·news_S1·news_S2·news_S3·news_S4·news_S5·news_S6·news_merge·wire·wire_prev | S7 fx_merge·lay_merge
S8 pr_merge·pipe_merge·graph·news_S1·news_S2·news_S3·news_S4·news_S5·news_S6·news_JA·news_JB·news_merge·wire | `설명/04_도구별_파이프라인.md`(pipe_merge) |
### 하위 키
| 하위 키 | 쓰는 노드 | 읽는 노드 |
|---|---|---|
| `brief.reference` | S0 ref | S0 sb
S1 img_design |
| `brief.meme_research` | S0 r_meme | S0 sb |
| `brief.flow_research` | S0 r_flow | S0 sb
S2 flow_test |
| `brief.pinterest` | S0 r_pin | S0 sb·sb3
S1 img_design |
| `plan.episodes` | S0 sb | - |
| `plan.storyboard` | S0 sb·sb3 | S0 sb3
S4 au_v1_bgm·au_v1_vo·au_v3_song·au_v3_vo
S5 mo_a·mo_b·mo_c
S6 c_v3o
S8 graph |
| `plan.edl` | S0 sb
S6 c_v4 | S4 au_v1_sfx
S5 mo_a
S6 c_v1
S7 lay_v1·lay_v2·lay_v3·lay_v4 |
| `plan.beat_grid` | S0 sb·sb3 | S4 au_v1_bgm·au_v3_song·au_v4 |
| `plan.image_batches` | S0 sb·sb3 | S1 img_design·img_frames·img_outfits·img_s6·img_v3
S8 pr_P1 |
| `plan.flow_jobs` | S0 sb·sb3 | S2 전부(6)
S8 pr_P2 |
| `plan.credit_plan` | S0 sb | - |
| `assets.images` | S1 전부(6) | S1 img_frames·img_outfits·img_s6·img_v3·img_fix
S2 전부(6)
S6 c_v3
S7 lay_v1·lay_v2·lay_v3·lay_v4
S8 pr_P1·wire_prev |
| `assets.clips` | S2 전부(6) | S3 matte_rd·matte_q1·matte_q2·matte_q4
S5 mo_c
S6 c_v3
S7 lay_v1·lay_v2·lay_v3·lay_v4
S8 pr_P2·wire_prev |
| `assets.mattes` | S3 matte_q1·matte_q2·matte_q4 | S6 c_v1·c_v2·c_v3·c_v4
S7 fx_A·fx_B·fx_C·fx_D·fx_E·lay_v1·lay_v2·lay_v3·lay_v4 |
| `audio.tracks` | S4 au_v1_bgm·au_v3_song·au_v4·au_v5_song | S4 au_v5_song
S6 c_v1·c_v2·c_v3o·c_v3·c_v4·c_v5
S7 fx_A·fx_B·fx_C·fx_D·fx_E
S8 pr_P3·wire_prev |
| `audio.vo` | S4 au_v1_vo·au_v3_vo | S6 c_v1·c_v3o·c_v3
S8 pr_P3 |
| `audio.sfx` | S4 au_v1_sfx·au_v4 | S6 c_v1·c_v4
S8 pr_P3 |
| `audio.beats` | S4 au_v1_bgm·au_v3_song·au_v4·au_v5_song | S4 au_v5_song
S5 mo_b
S6 c_v1·c_v3o·c_v3·c_v4·c_v5 |
| `audio.candidates` | S4 au_v1_bgm·au_v1_vo·au_v3_song·au_v3_vo·au_v4·au_v5_song | S8 pr_P3 |
| `qa.verdicts` | S1 전부(6)
S2 전부(6)
S4 au_v1_bgm·au_v1_vo·au_v3_song·au_v3_vo·au_v4·au_v5_song
S6 c_v1·c_v2·c_v3·c_v4·c_v5
S7 fx_merge
S8 pr_merge·news_JA·news_JB | S8 pipe_T1·pipe_T2·pipe_T3·pipe_T4·pipe_T5·pipe_T6·news_merge |
| `qa.retries` | S1 전부(6)
S2 전부(6)
S4 au_v1_bgm·au_v3_song·au_v4·au_v5_song
S6 c_v1·c_v2·c_v3·c_v4·c_v5 | - |
| `qa.gates` | - | - |
| `budget.flow_credits` | S0 sb
S2 전부(6) | S2 전부(6)
S8 pipe_T1·pipe_T2·pipe_T3·pipe_T4·pipe_T5·pipe_T6 |
| `budget.eleven_credits` | S0 sb
S4 전부(7) | S4 전부(7)
S8 pipe_T1·pipe_T2·pipe_T3·pipe_T4·pipe_T5·pipe_T6 |
| `budget.codex` | - | S8 pipe_T1·pipe_T2·pipe_T3·pipe_T4·pipe_T5·pipe_T6 |
| `runtime.slots` | S6 sema | S3 matte_q1·matte_q2·matte_q4 |
| `runtime.heavy` | S6 sema | S6 c_v1·c_v2·c_v3·c_v4·c_v5 |
| `runtime.toolchain` | S2 flow_test
S3 matte_rd·matte_gpu
S5 gpu_render | S2 flow_b_v1·flow_b_ref·flow_b_extra·flow_b_v3·flow_b_v4
S3 matte_gpu·matte_q1·matte_q2·matte_q4
S5 gpu_render
S6 sema
S8 pipe_T1·pipe_T2·pipe_T3·pipe_T4·pipe_T5·pipe_T6 |
| `runtime.incidents` | S3 matte_q1·matte_q2·matte_q4 | S6 sema
S8 pipe_T1·pipe_T2·pipe_T3·pipe_T4·pipe_T5·pipe_T6 |
| `runtime.resume_log` | - | S8 pipe_T1·pipe_T2·pipe_T3·pipe_T4·pipe_T5·pipe_T6 |
| `runtime.trace` | - | - |
## 3. reducer — 병렬 쓰기가 안 깨지는 이유
| reducer | 규칙 | 실제로 막는 사고 |
|---|---|---|
| merge_dict | 얕은 병합, 뒤가 이김 | 리서치 3갈래가 `brief`에 동시에 써도 각자 하위 키만 바뀜 |
| merge_nested_by_id | 하위 맵마다 id 병합 | Flow 워커 5기가 `assets.clips`에 동시에 써도 컷 id별로 들어감 |
| merge_by_id | id → 레코드 필드 병합 | 섹션 워커 5~6기가 `docs`에 동시 기록, `renders.V2`를 c_v2 → c_v2_fix가 덧씀 |
| upsert_by_id | 같은 id면 갱신, 아니면 추가 | 반려 I08 status open → rewound → resolved 갱신, 규칙 중복 방지 |
| merge_qa | verdicts 누적 · retries 합산 · gates 덮어쓰기 | 재시도 횟수가 병렬 판정에서도 정확(재생성 1회 상한 집행) |
| merge_budget | spent 합산 · entries 누적 | Flow 동시 5잡이 각자 12크레딧을 내도 합계 정확 → 배치 상한(v1 110·v4 90) 판정 |
| merge_runtime | trace·incidents·resume_log 누적, 나머지 병합 | 재부팅 사고·되감기 기록이 덮이지 않음 |
## 4. 스테이지 × 키 읽기/쓰기 행렬
| 스테이지 | 노드 수 | `brief` | `plan` | `rules` | `assets` | `audio` | `blocks` | `renders` | `qa` | `feedback` | `budget` | `runtime` | `docs` |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| S0 기획·리서치 | 6 | RW | RW | R | | | | | | R | W | | |
| S1 이미지 (codex) | 6 | R | R | R | RW | | | | W | | | | |
| S2 영상 (Flow via Aside) | 6 | R | R | RW | RW | | | | W | | RW | RW | |
| S3 매팅 (사람만 오려내기) | 5 | | | | RW | | | | | | | RW | |
| S4 소리 (ElevenLabs) | 7 | | R | R | | RW | | | W | R | RW | | |
| S5 코드 배경·효과 | 4 | | R | R | R | R | RW | | | | | RW | |
| S6 편별 합성·render | 8 | | RW | R | R | R | RW | RW | W | R | | RW | |
| S7 분석 정리 (효과·레이어) | 11 | | R | | R | R | R | R | W | | | | RW |
| S8 문서·뉴스레터·배선도 | 24 | | R | R | R | R | | R | RW | R | R | R | RW |
| Send 워커 `*__job`(9) | - | | R | | W | | | | W | | W | | |
| user_review(interrupt) | - | | | W | | | | RW | | RW | | | |
| 게이트 라우터(21) | - | | | | | | | | R | | | | |
| 드라이버 rewind_to | - | | | W | | | | | | RW | | | |
- 읽는 법: 열 하나를 세로로 보면 그 키의 생산 → 소비 순서가 보임(예: `assets`는 S1·S2·S3에서 W, S6~S8에서 R)
- `plan`을 S6이 W하는 건 4편 c_v4가 `edl.py`로 78컷 EDL을 직접 만들기 때문
- `rules`를 S2가 W하는 건 360p 테스트 교훈(프레임 모드·flat solid blue·One continuous shot)을 규칙으로 박기 때문
## 5. 노드별 읽기/쓰기 (77)
| 스테이지 | 노드 | 이름 | 담당 | stub 핸들러 | 읽기 | 쓰기 | 동시성 | 재시도·게이트 |
|---|---|---|---|---|---|---|---|---|
| S0 | `ref` | 레퍼런스 확보 | main | research | - | brief.reference | - | - |
| S0 | `r_meme` | 밈 조사 | sub | research | rules | brief.meme_research | subagents 8 | 재시도 ≤1 |
| S0 | `r_flow` | Flow 조사 | sub | research | rules | brief.flow_research | subagents 8 | 재시도 ≤1 |
| S0 | `r_pin` | 핀터레스트 수집 | sub | research | rules | brief.pinterest | subagents 8 | 재시도 ≤1 |
| S0 | `sb` | 스토리보드·EDL | main | plan | brief.reference, brief.meme_research, brief.flow_research, brief.pinterest, rules, feedback | plan.episodes, plan.storyboard, plan.edl, plan.beat_grid, plan.image_batches, plan.flow_jobs, plan.credit_plan, budget.flow_credits, budget.eleven_credits | - | - |
| S0 | `sb3` | 3편 OP 기획 (opus) | sub | plan | plan.storyboard, brief.pinterest, rules | plan.storyboard, plan.beat_grid, plan.image_batches, plan.flow_jobs | subagents 8 | 재시도 ≤1 |
| S1 | `img_design` | 캐릭터 시안 4종 | codex | codex_batch | plan.image_batches, brief.reference, brief.pinterest, rules | assets.images, qa.verdicts, qa.retries | codex 4 | Send ×4 · 게이트 ≤1(Send 재시도) |
| S1 | `img_frames` | 1편 첫 프레임 8장 | codex | codex_batch | plan.image_batches, assets.images, rules | assets.images, qa.verdicts, qa.retries | codex 4 | Send ×8 · 게이트 ≤1(Send 재시도) |
| S1 | `img_outfits` | 의상 7벌 | codex | codex_batch | plan.image_batches, assets.images, rules | assets.images, qa.verdicts, qa.retries | codex 4 | Send ×7 · 게이트 ≤1(Send 재시도) |
| S1 | `img_s6` | 대연회장 귀족 드레스 1장 | codex | codex_batch | plan.image_batches, assets.images, rules | assets.images, qa.verdicts, qa.retries | codex 4 | 게이트 ≤1(자기 루프) |
| S1 | `img_v3` | 3편 일러 15장 | codex | codex_batch | plan.image_batches, assets.images, rules | assets.images, qa.verdicts, qa.retries | codex 4 | Send ×15 · 게이트 ≤1(Send 재시도) |
| S1 | `img_fix` | 내용↔파일명 대조 | main | name_check | assets.images | assets.images, qa.verdicts, qa.retries | - | 게이트 ≤2(자기 루프) |
| S2 | `flow_test` | Flow 정찰·360p 테스트 | sub | flow_test | assets.images, brief.flow_research, plan.flow_jobs, budget.flow_credits, rules | assets.clips, qa.verdicts, qa.retries, rules, budget.flow_credits, runtime.toolchain | flow 2 | 게이트 ≤1(자기 루프) |
| S2 | `flow_b_v1` | 1편 첫 프레임 11컷 | sub | flow_batch | plan.flow_jobs, assets.images, runtime.toolchain, budget.flow_credits, rules | assets.clips, qa.verdicts, qa.retries, budget.flow_credits | flow 5 | Send ×11 · 게이트 ≤1(Send 재시도) |
| S2 | `flow_b_ref` | 참조(Ingredients) 4컷 | sub | flow_batch | plan.flow_jobs, assets.images, runtime.toolchain, budget.flow_credits, rules | assets.clips, qa.verdicts, qa.retries, budget.flow_credits | flow 5 | Send ×4 · 게이트 ≤1(Send 재시도) |
| S2 | `flow_b_extra` | 추가 9컷 + 2편 변신 | sub | flow_batch | plan.flow_jobs, assets.images, runtime.toolchain, budget.flow_credits, rules | assets.clips, qa.verdicts, qa.retries, budget.flow_credits | flow 5 | Send ×9 · 게이트 ≤1(Send 재시도) |
| S2 | `flow_b_v3` | 3편 7컷 | sub | flow_batch | plan.flow_jobs, assets.images, runtime.toolchain, budget.flow_credits, rules | assets.clips, qa.verdicts, qa.retries, budget.flow_credits | flow 5 | Send ×7 · 게이트 ≤1(Send 재시도) |
| S2 | `flow_b_v4` | 4편 빠른 동작 10컷 | sub | flow_batch | plan.flow_jobs, assets.images, runtime.toolchain, budget.flow_credits, rules | assets.clips, qa.verdicts, qa.retries, budget.flow_credits | flow 5 | Send ×10 · 게이트 ≤1(Send 재시도) |
| S3 | `matte_rd` | 매팅 방식 비교 | sub | matte_dev | assets.clips | runtime.toolchain | subagents 8 | 재시도 ≤1 |
| S3 | `matte_gpu` | GPU 포팅 | sub | matte_dev | runtime.toolchain | runtime.toolchain | subagents 8 | 재시도 ≤1 |
| S3 | `matte_q1` | 매팅 큐 1 (1편→추가) | gpu | matte_queue | assets.clips, runtime.toolchain, runtime.slots | assets.mattes, runtime.incidents | matte_lines 1 | 재시도 ≤1 |
| S3 | `matte_q2` | 매팅 큐 2 (참조·3편) | gpu | matte_queue | assets.clips, runtime.toolchain, runtime.slots | assets.mattes, runtime.incidents | matte_lines 1 | 재시도 ≤1 |
| S3 | `matte_q4` | 매팅 큐 4편 | gpu | matte_queue | assets.clips, runtime.toolchain, runtime.slots | assets.mattes, runtime.incidents | matte_lines 1 | 재시도 ≤1 |
| S4 | `au_v1_bgm` | 1편 BGM | eleven | eleven_music | plan.beat_grid, plan.storyboard, budget.eleven_credits, rules | audio.candidates, audio.tracks, audio.beats, budget.eleven_credits, qa.verdicts, qa.retries | eleven 2 | 게이트 ≤2(자기 루프) |
| S4 | `au_v1_vo` | 1편 VO 오디션 | eleven | eleven_tts | plan.storyboard, budget.eleven_credits, rules | audio.vo, audio.candidates, budget.eleven_credits, qa.verdicts | eleven 2 | 재시도 ≤1 |
| S4 | `au_v1_sfx` | 효과음 19개 | eleven | eleven_sfx | plan.edl, budget.eleven_credits | audio.sfx, budget.eleven_credits | eleven 2 | - |
| S4 | `au_v3_song` | 3편 한국어 OP곡 | eleven | eleven_music | plan.beat_grid, plan.storyboard, budget.eleven_credits, rules | audio.candidates, audio.tracks, audio.beats, budget.eleven_credits, qa.verdicts, qa.retries | eleven 2 | 게이트 ≤2(자기 루프) |
| S4 | `au_v3_vo` | 3편 한국어 VO | eleven | eleven_tts | plan.storyboard, budget.eleven_credits, rules | audio.vo, audio.candidates, budget.eleven_credits, qa.verdicts | eleven 2 | 재시도 ≤1 |
| S4 | `au_v4` | 4편 170BPM 곡 | eleven | eleven_music | plan.beat_grid, budget.eleven_credits, rules | audio.candidates, audio.tracks, audio.beats, audio.sfx, budget.eleven_credits, qa.verdicts, qa.retries | eleven 2 | 게이트 ≤2(자기 루프) |
| S4 | `au_v5_song` | 5편 영어 곡 | eleven | eleven_music | audio.beats, audio.tracks, budget.eleven_credits, rules, feedback | audio.candidates, audio.tracks, audio.beats, budget.eleven_credits, qa.verdicts, qa.retries | eleven 2 | 게이트 ≤2(자기 루프) |
| S5 | `mo_a` | 모션 워커 A | sub | motion_block | plan.storyboard, plan.edl, rules | blocks | subagents 8 | 재시도 ≤1 |
| S5 | `mo_b` | 모션 워커 B | sub | motion_block | plan.storyboard, blocks, audio.beats, rules | blocks | subagents 8 | 재시도 ≤1 |
| S5 | `mo_c` | 모션 워커 C | sub | motion_block | plan.storyboard, blocks, assets.clips, rules | blocks | subagents 8 | 재시도 ≤1 |
| S5 | `gpu_render` | render 가속 실측 | sub | render_bench | runtime.toolchain | runtime.toolchain | subagents 8 | 재시도 ≤1 |
| S6 | `c_v1` | 1편 합성 | sub | composite | assets.mattes, audio.tracks, audio.vo, audio.sfx, audio.beats, blocks, plan.edl, runtime.heavy, rules | renders, qa.verdicts, qa.retries | heavy 2 | 게이트 ≤3(자기 루프) |
| S6 | `c_v2` | 2편 합성 | sub | composite | assets.mattes, blocks, audio.tracks, runtime.heavy, rules | renders, qa.verdicts, qa.retries | heavy 2 | 게이트 ≤3(자기 루프) |
| S6 | `c_v2_fix` | 2편 수정 | main | fix | renders, feedback, rules | renders | heavy 2 | - |
| S6 | `c_v3o` | 3편 오버레이 | sub | motion_block | audio.tracks, audio.beats, audio.vo, plan.storyboard | blocks | subagents 8 | 재시도 ≤1 |
| S6 | `c_v3` | 3편 장면 조립 | sub | composite | blocks, assets.images, assets.clips, assets.mattes, audio.tracks, audio.vo, audio.beats, runtime.heavy, rules | renders, qa.verdicts, qa.retries | heavy 2 | 게이트 ≤3(자기 루프) |
| S6 | `c_v4` | 4편 하이퍼 에딧 | sub | composite | assets.mattes, audio.tracks, audio.beats, audio.sfx, blocks, runtime.heavy, rules | plan.edl, renders, qa.verdicts, qa.retries | heavy 2 | 게이트 ≤3(자기 루프) |
| S6 | `c_v5` | 5편 영어판 재render | sub | composite | renders, audio.tracks, audio.beats, blocks, runtime.heavy, rules | renders, qa.verdicts, qa.retries | heavy 2 | 게이트 ≤3(자기 루프) |
| S6 | `sema` | heavy.sh 세마포어 | gpu | semaphore | runtime.toolchain, runtime.incidents | runtime.heavy, runtime.slots | - | - |
| S7 | `fx_A` | 효과 A 배경 블록 | sub | section_worker | renders, blocks, assets.mattes, audio.tracks | docs | doc_subagents 12 | 섹션 fx · 재시도 ≤1 |
| S7 | `fx_B` | 효과 B 1·2편 | sub | section_worker | renders, blocks, assets.mattes, audio.tracks | docs | doc_subagents 12 | 섹션 fx · 재시도 ≤1 |
| S7 | `fx_C` | 효과 C 3·5편 | sub | section_worker | renders, blocks, assets.mattes, audio.tracks | docs | doc_subagents 12 | 섹션 fx · 재시도 ≤1 |
| S7 | `fx_D` | 효과 D 4편 | sub | section_worker | renders, blocks, assets.mattes, audio.tracks | docs | doc_subagents 12 | 섹션 fx · 재시도 ≤1 |
| S7 | `fx_E` | 효과 E 매팅·오디오 | sub | section_worker | renders, blocks, assets.mattes, audio.tracks | docs | doc_subagents 12 | 섹션 fx · 재시도 ≤1 |
| S7 | `fx_merge` | 효과 병합 | sub | merge | docs | docs, qa.verdicts | - | - |
| S7 | `lay_v1` | 레이어 1편 | sub | section_worker | renders, blocks, assets.images, assets.clips, assets.mattes, plan.edl | docs | doc_subagents 12 | 섹션 lay · 재시도 ≤1 |
| S7 | `lay_v2` | 레이어 2편 | sub | section_worker | renders, blocks, assets.images, assets.clips, assets.mattes, plan.edl | docs | doc_subagents 12 | 섹션 lay · 재시도 ≤1 |
| S7 | `lay_v3` | 레이어 3·5편 | sub | section_worker | renders, blocks, assets.images, assets.clips, assets.mattes, plan.edl | docs | doc_subagents 12 | 섹션 lay · 재시도 ≤1 |
| S7 | `lay_v4` | 레이어 4편 | sub | section_worker | renders, blocks, assets.images, assets.clips, assets.mattes, plan.edl | docs | doc_subagents 12 | 섹션 lay · 재시도 ≤1 |
| S7 | `lay_merge` | 레이어 병합 | main | merge | docs | docs | - | - |
| S8 | `pr_P1` | 원문집 이미지 | sub | section_worker | assets.images, plan.image_batches | docs | doc_subagents 12 | 섹션 pr · 재시도 ≤1 |
| S8 | `pr_P2` | 원문집 영상 | sub | section_worker | assets.clips, plan.flow_jobs | docs | doc_subagents 12 | 섹션 pr · 재시도 ≤1 |
| S8 | `pr_P3` | 원문집 소리 | sub | section_worker | audio.candidates, audio.tracks, audio.vo, audio.sfx | docs | doc_subagents 12 | 섹션 pr · 재시도 ≤1 |
| S8 | `pr_P4` | 원문집 규칙·공식 | sub | section_worker | rules, feedback | docs | doc_subagents 12 | 섹션 pr · 재시도 ≤1 |
| S8 | `pr_merge` | 원문집 병합 | main | merge | docs | docs, qa.verdicts | - | - |
| S8 | `pipe_T1` | 파이프라인 Aside | sub | section_worker | runtime.toolchain, runtime.incidents, runtime.resume_log, qa.verdicts, budget | docs | doc_subagents 12 | 섹션 pipe · 재시도 ≤1 |
| S8 | `pipe_T2` | 파이프라인 codex | sub | section_worker | runtime.toolchain, runtime.incidents, runtime.resume_log, qa.verdicts, budget | docs | doc_subagents 12 | 섹션 pipe · 재시도 ≤1 |
| S8 | `pipe_T3` | 파이프라인 매팅 | sub | section_worker | runtime.toolchain, runtime.incidents, runtime.resume_log, qa.verdicts, budget | docs | doc_subagents 12 | 섹션 pipe · 재시도 ≤1 |
| S8 | `pipe_T4` | 파이프라인 합성 | sub | section_worker | runtime.toolchain, runtime.incidents, runtime.resume_log, qa.verdicts, budget | docs | doc_subagents 12 | 섹션 pipe · 재시도 ≤1 |
| S8 | `pipe_T5` | 파이프라인 오디오 | sub | section_worker | runtime.toolchain, runtime.incidents, runtime.resume_log, qa.verdicts, budget | docs | doc_subagents 12 | 섹션 pipe · 재시도 ≤1 |
| S8 | `pipe_T6` | 파이프라인 지휘 | sub | section_worker | runtime.toolchain, runtime.incidents, runtime.resume_log, qa.verdicts, budget | docs | doc_subagents 12 | 섹션 pipe · 재시도 ≤1 |
| S8 | `pipe_merge` | 파이프라인 병합 | main | merge | docs | docs | - | - |
| S8 | `graph` | 파이프라인 그래프 | sub | section_worker | docs, plan.storyboard, feedback | docs | doc_subagents 12 | 재시도 ≤1 |
| S8 | `news_S1` | 뉴스레터 도입 | sub | section_worker | docs | docs | doc_subagents 12 | 섹션 news · 재시도 ≤1 |
| S8 | `news_S2` | 뉴스레터 이미지 | sub | section_worker | docs | docs | doc_subagents 12 | 섹션 news · 재시도 ≤1 |
| S8 | `news_S3` | 뉴스레터 영상 | sub | section_worker | docs | docs | doc_subagents 12 | 섹션 news · 재시도 ≤1 |
| S8 | `news_S4` | 뉴스레터 코드 | sub | section_worker | docs | docs | doc_subagents 12 | 섹션 news · 재시도 ≤1 |
| S8 | `news_S5` | 뉴스레터 소리 | sub | section_worker | docs | docs | doc_subagents 12 | 섹션 news · 재시도 ≤1 |
| S8 | `news_S6` | 뉴스레터 지휘 | sub | section_worker | docs | docs | doc_subagents 12 | 섹션 news · 재시도 ≤1 |
| S8 | `news_JA` | 문체 판정 앞 | sub | judge | docs | qa.verdicts | doc_subagents 12 | - |
| S8 | `news_JB` | 문체 판정 뒤 | sub | judge | docs | qa.verdicts | doc_subagents 12 | - |
| S8 | `news_merge` | 뉴스레터 병합·반영 | main | merge | docs, qa.verdicts, feedback | docs | - | - |
| S8 | `wire` | 배선도 | sub | section_worker | docs, feedback | docs | doc_subagents 12 | 재시도 ≤1 |
| S8 | `wire_prev` | 배선도 시안 에셋 | sub | section_worker | assets.images, assets.clips, audio.tracks, renders | docs | doc_subagents 12 | 재시도 ≤1 |
## 6. 상태 전이 다이어그램
- 스테이지 단위. 분기(fork)·합류(join)·판정 루프·재시도·사용자 반려 되감기만 표시. 노드 단위 배선은 `python3 graph.py --dry-run`
```mermaid
stateDiagram-v2
direction TB
state "기획·리서치 S0
brief·plan W" as S0
state "이미지 배치 S1
Send 행 단위 · codex 4" as S1
state "이름 대조 img_fix" as FIX
state "360p 테스트 flow_test" as TEST
state "Flow 배치 S2
Send 잡 단위 · 동시 5" as S2
state "클립 판정" as JUDGE
state "매팅 큐 S3 · 2줄" as S3
state "곡 후보 → 실측 S4 · 동시 2" as S4
state "코드 배경 블록 S5" as S5
state "편별 합성 S6 · heavy 2" as S6
state "드래프트 컨택트시트" as SHEET
state "user_review
interrupt()" as REVIEW
state "효과·레이어 섹션 S7
Send 섹션 워커" as S7
state "문서·뉴스레터·배선도 S8
Send 섹션 워커 → 판정 → 병합" as S8
state fork1 <>
state join1 <>
[*] --> S0
S0 --> fork1
fork1 --> S1
fork1 --> S4
fork1 --> S5
S1 --> S1 : timeout 행만 재시도 1회 (TIMEOUT=600)
S1 --> FIX
FIX --> FIX : 내용↔파일명 불일치 → 교정·재대조 (≤2)
FIX --> TEST
TEST --> TEST : 불합격 → 교훈을 rules에 넣고 재테스트 (≤1)
TEST --> S2 : 통과
S2 --> JUDGE
JUDGE --> S2 : regen 컷만 _r2 재생성 1회
JUDGE --> S3 : 블루 컷
JUDGE --> join1 : 인월드 컷(매팅 생략)
S3 --> join1
S4 --> S4 : 기준 충족 후보 0 → 요청 수정 재생성 (≤2)
S4 --> join1
S4 --> S5 : beats.json 실측 상수(점선)
S5 --> join1
join1 --> S6
S6 --> SHEET
SHEET --> S6 : 배치·박·자막 어긋남 (r1→r3)
SHEET --> REVIEW : 최종·업로드본
REVIEW --> S7 : 승인
REVIEW --> S0 : 반려 → rewind_to(원인 노드)
+ rules 누적
S7 --> S8
S8 --> REVIEW : 뉴스레터·배선도
S8 --> [*]
```
- 반려 되감기 15건(원인 노드): I01→`flow_test` · I02→`img_design` · I03→`img_outfits` · I04→`mo_b` · I05→`flow_b_ref` · I06→`flow_b_v1` · I07→`matte_gpu` · I08→`c_v2_fix` · I09→`au_v5_song` · I10→`c_v4` · I11→`fx_merge` · I12→`lay_merge` · I13→`news_merge` · I14→`wire` · I15→`wire`
- 되감기 방식: 체크포인트 이력에서 원인 노드가 next인 지점으로 포크 재실행, 반려 규칙은 `configurable.inject`로 실어 첫 노드가 `rules`·`feedback`에 넣음(`graph.py` rewind_to)
- 되감기 뒤 다시 도는 노드는 산출 파일이 있으면 건너뛰어야 함(실제 파이프라인도 `imagegen_runner.py`·`matte_queue.sh`가 멱등)
## 7. 그래프에만 있는 가상 노드
| 종류 | 이름 규칙 | 개수 | 하는 일 |
|---|---|---|---|
| dispatch | `<노드>__dispatch` | 9 | 배치 행·잡 목록을 읽어 `Send(<노드>__job, item)` |
| worker | `<노드>__job` | 9 | 이미지 1장 / Flow 클립 1개 |
| pass | `<노드>__pass` | 21 | 게이트 통과 출구. 원래 배선은 여기서 나감(대기 엣지가 조건 엣지에 막히지 않게) |
| group_dispatch | `fx·lay·pr·pipe·news__dispatch` | 5 | 멤버 선행 노드 합집합을 기다렸다가 섹션 워커 동시 Send(실제로도 같은 시각 동시 기동) |
| interrupt | `user_review` | 1 | 산출물 검토, `interrupt()`로 정지 |
- 합계: roster 77 + 가상 45 = 그래프 노드 122, compile 후 `get_graph()` 노드 122 일치
## 8. 실행·검증
```bash
python3 verify.py # yaml·json 파싱, roster 77·113·15 대조, Schema 유효성, state.py↔schema, dry-run
python3 graph.py --dry-run # 구조(노드·엣지) 출력. langgraph 있으면 compile 요약 추가
python3 graph.py --simulate # stub으로 끝까지(외부 호출 0), 판정 루프는 실제 관측 횟수 재현
python3 graph.py --simulate --reject c_v2_fix # 반려 → 되감기 경로 시험
python3 graph.py --simulate --review all # 반려 대상 14노드 뒤마다 interrupt_after 정지
```
- langgraph 설치는 C 오염 없이: `python3 -m venv /mnt/d/_cache/venv-langgraph && PIP_CACHE_DIR=/mnt/d/_cache/pip /mnt/d/_cache/venv-langgraph/bin/pip install langgraph pyyaml jsonschema`
- 시뮬레이션 관측 재현값: img_s6 재시도 1 · flow_b_ref 2컷 재생성 · flow_b_v3 1컷 재생성 · c_v2 라운드 2 · c_v4 라운드 3