# dispatch.py 개선 참조 문서

**작성일**: 2026-03-06
**작성자**: 토르 (개발2팀 백엔드 개발자)
**참조 패턴**: skill-composer Fork-Join / Iterative / Quality Gate
**대상 파일**: `/home/jay/workspace/dispatch.py`, `/home/jay/workspace/teams/dev1/qc/qc_verify.py`
**상태**: 설계 참조 문서 (코드 수정 없음)

---

## 목차

1. [문서 목적 및 범위](#1-문서-목적-및-범위)
2. [Fork-Join 패턴 → 멀티팀 병렬 라우팅 설계](#2-fork-join-패턴--멀티팀-병렬-라우팅-설계)
3. [Iterative 패턴 → Phase 체이닝 자동화 설계](#3-iterative-패턴--phase-체이닝-자동화-설계)
4. [Quality Gate 패턴 → QC 에스컬레이션 명문화](#4-quality-gate-패턴--qc-에스컬레이션-명문화)
5. [dispatch.py 수정 방향 매핑](#5-dispatchpy-수정-방향-매핑)
6. [설계 원칙 및 제약사항](#6-설계-원칙-및-제약사항)

---

## 1. 문서 목적 및 범위

### 1.1 작성 배경

skill-composer는 다음 세 가지 핵심 패턴을 통해 복잡한 AI 파이프라인을 조율한다.

- Fork-Join: 병렬 스킬 실행 후 결과 통합
- Iterative: 품질 기준 충족까지 반복 실행
- Quality Gate: 단계 간 통과 조건 강제

현재 `dispatch.py`는 단일 팀에게 순차적으로 작업을 위임하는 구조다. 이 문서는 위 세 패턴을 현재 시스템에 어떻게 참조·적용할 수 있는지 설계 수준에서 정리한다.

### 1.2 현재 dispatch.py 구조 요약

`dispatch()` 함수의 단일 실행 흐름:

- `generate_task_id()` → task-timer start → 프로젝트 맵 갱신 → `build_prompt()` → `cokacdir --cron` 실행 → `_register_followup()` → `_update_chain_task()`

현재는 `--team` 인자를 하나만 받으며, 하나의 팀에 하나의 task_id를 발행한다. `chain.py`를 통해 Phase 단위 순차 실행이 가능하지만, 동일 Phase 내 병렬 디스패치는 아누가 직접 여러 번 호출하는 방식에 의존한다.

### 1.3 개선 참조의 범위

이 문서에서 다루는 것:
- 각 패턴의 개념을 현재 시스템에 대응시키는 설계 방향
- 어떤 함수/모듈에 어떤 변경이 필요한지 매핑
- 도입 시 고려해야 할 트레이드오프

이 문서에서 다루지 않는 것:
- 실제 코드 수정 (설계 참조 문서이므로)
- 구체적인 구현 타임라인

---

## 2. Fork-Join 패턴 → 멀티팀 병렬 라우팅 설계

### 2.1 패턴 정의

```
         ┌→ dev1-team ─┐
[Input] →│              │→ Merge → [아누 통합 보고]
         └→ dev2-team ─┘
```

현재 방식은 아누가 `dispatch.py`를 두 번 호출하여 각각 task_id를 발행한다. Fork-Join 패턴은 이 과정을 하나의 논리 단위로 묶어 관리하는 것이다.

### 2.2 현재 방식과의 차이점

현재 방식 (아누 수동 다중 호출):
- `dispatch.py --team dev1-team --task "..."` → task-10.1 발행
- `dispatch.py --team dev2-team --task "..."` → task-11.1 발행
- 두 task_id를 개별적으로 추적
- Merge 기준 없음 (아누가 결과를 읽고 직접 통합)
- 병렬 완료 감지 없음 (followup이 각각 독립적으로 등록)

Fork-Join 개선 방향:
- 단일 진입점(`dispatch_parallel()` 또는 `dispatch()`의 팀 목록 지원)으로 여러 task_id를 원자적으로 발행
- fork_id(논리 그룹 ID)로 묶어 `task-timers.json`에 기록
- 모든 팀의 `.done` 파일 생성 시 자동 Merge 트리거
- Merge 결과를 아누에게 단일 보고로 전달

### 2.3 Fork 단계 설계

Fork 진입 조건:
- `--teams` 인자로 복수 팀 목록 전달 (예: `dev1-team,dev2-team`)
- 또는 `dispatch_parallel(teams=[...], task_desc=...)` 함수 인터페이스

Fork 실행 시 필요한 처리:
- `fork_id` 생성: `fork-{timestamp}` 형식 (task_id와 구별)
- 각 팀에 대해 `generate_task_id()` 호출하여 개별 task_id 발행
- `memory/forks/{fork_id}.json`에 포크 메타데이터 기록
  - 포함 필드: fork_id, teams, task_ids (팀→task_id 매핑), status (pending/partial/done), created_at
- 각 팀에 `chain_id` 대신 `fork_id`를 컨텍스트로 전달

### 2.4 팀별 결과 Merge 전략

Merge 트리거 조건:
- `fork_id`에 속한 모든 task_id에 대해 `memory/events/{task_id}.done` 파일 존재
- 이 감지는 아누 followup 또는 별도 fork-monitor 크론으로 수행

Merge 전략 유형:

**전략 A - 병렬 검토형** (기본 권장)
- 각 팀의 `memory/reports/{task_id}.md`를 병렬 읽기
- 아누가 두 보고서를 비교하여 제이회장님께 통합 보고
- 적용 예시: dev1-team(백엔드)과 dev2-team(프론트엔드) 동시 구현 후 통합

**전략 B - 교차 검증형**
- 팀 A의 결과물을 팀 B가 검토 (상호 리뷰)
- 두 팀의 verdict가 모두 PASS일 때만 완료 처리
- 적용 예시: Fork-Join + Quality Gate 조합

**전략 C - 선착순형**
- 먼저 완료한 팀의 결과를 주결과로 채택, 나머지는 보조로 참고
- 적합한 경우: 동일 작업을 두 팀에 병렬 시도하여 빠른 결과 채택

### 2.5 충돌 해결 원칙

동일 파일을 두 팀이 수정하는 경우:
- Fork 단계에서 팀별 작업 범위를 파일 단위로 분리 (중복 방지)
- 범위가 겹칠 수밖에 없는 경우 (예: `analyzer.py` 공유): step567-improvement-spec.md의 "1팀 Merge 고려사항" 패턴 참조
  - 같은 함수 내에서 별도 블록으로 작업 분리
  - 인터페이스(반환 타입)를 Fork 이전에 합의
- 충돌이 감지되면 Merge 전 아누에게 에스컬레이션 (자동 Merge 금지)

### 2.6 fork_id와 chain_id의 관계

- `chain.py`의 `chain_id`는 Phase 간 순차 체이닝을 관리
- `fork_id`는 단일 Phase 내 병렬 팀 묶음을 관리
- 두 개념은 중첩 가능: 하나의 chain Phase가 여러 팀의 Fork를 포함할 수 있음
- `chain.py`의 `_advance_phase()` 로직에서 한 Phase 내 모든 task가 완료될 때 다음 Phase로 전환하는 기존 메커니즘이 Fork-Join의 Join 역할을 이미 부분적으로 담당

---

## 3. Iterative 패턴 → Phase 체이닝 자동화 설계

### 3.1 패턴 정의

```
[Input] → Phase A → [Check] → PASS? → [Output]
                        ↓ No
                     Phase B(수정) → Phase A (retry)
```

현재 `chain.py`는 Phase 간 순차 자동 전환을 지원하지만, 실패 시 이전 Phase로 롤백하는 메커니즘은 없다.

### 3.2 PDP 5단계에 Iterative 패턴 적용

PDP(Product Development Pipeline) 5단계:
- 기획 → 리스크 → 구현 → 런칭 → 회고

각 단계 전환 조건과 롤백 로직:

**기획 → 리스크 전환 조건**
- 3문서(plan.md, context-notes.md, checklist.md) 완성 확인
- 에이전트 미팅 만장일치 달성
- 실패 시: 기획 단계 재실행 (에이전트 미팅 추가 사이클)
- 롤백 트리거: 리스크 단계에서 "기획 변경 필요" verdict 발생 시

**리스크 → 구현 전환 조건**
- 레드팀(로키) 리스크 리포트 작성 완료
- 식별된 모든 High 리스크에 대한 대응 방안 명시
- 실패 시: 리스크 단계 재실행 (추가 리스크 항목 조사)
- 롤백 트리거: 구현 중 리스크 단계에서 검토하지 않은 신규 리스크 발생 시

**구현 → 런칭 전환 조건**
- QC 3단 방어선 모두 통과 (Layer 1 + Layer 2 + Layer 3)
- `qc_verify.py overall == PASS`
- 실패 시: 구현 단계 재실행 (재작업 지시)
- 롤백 트리거: QC FAIL (현재 수동 처리)

**런칭 → 회고 전환 조건**
- 배포 완료 이벤트 파일 생성
- 서비스 health check 통과
- 실패 시: 런칭 단계 재실행 (롤백 배포)
- 롤백 트리거: 런칭 후 치명적 오류 발생 시

**회고 종료 조건**
- 회고 보고서 작성 완료
- 다음 프로젝트 개선 항목 체크리스트에 반영
- Iterative 종료 (더 이상 롤백 없음)

### 3.3 chain.py에서의 Iterative 지원 방향

현재 `chain.py`의 Phase 전환 로직(`_advance_phase()`)에 추가 필요한 개념:

Phase 상태 확장:
- 현재: `pending`, `in_progress`, `completed`
- 개선: `pending`, `in_progress`, `completed`, `failed`, `rolled_back` 추가

롤백 메타데이터 필드 (chain.json Phase 단위):
- `max_retries`: 최대 재시도 횟수 (기본값: 2)
- `retry_count`: 현재 시도 횟수
- `rollback_target`: 실패 시 돌아갈 Phase 인덱스 (기본값: 현재 Phase 재실행)
- `gate_condition`: 다음 Phase 진입 조건 (문자열 또는 함수 참조)

롤백 트리거 방식:
- `.failed` 이벤트 파일: `memory/events/{task_id}.failed`
  - 현재 `.done` 파일 생성 규칙과 대칭
  - 팀장이 작업 실패를 선언할 때 생성
- 아누가 `chain.py task-failed --chain {chain_id} --task {task_id}` 호출
- `chain.py`가 `rollback_target` Phase로 되감고 해당 Phase를 재디스패치

### 3.4 Iterative 패턴과 max_retries

무한 루프 방지를 위한 max_retries 정책:

- Phase 단위 max_retries: 동일 Phase의 재실행 횟수 상한
  - 기본값 2 (최초 실행 + 재시도 2회 = 총 3회 실행)
  - 초과 시 아누에게 에스컬레이션 알림 발송 후 체인 일시 중단
- 전체 체인 max_iterations: 전체 롤백 루프 횟수 상한
  - 기본값 5
  - 초과 시 제이회장님 직접 보고 필요 플래그

현재 `chain.py`에서 참조할 위치:
- `_advance_phase()` 함수: 다음 Phase 전환 전 gate_condition 평가 로직 삽입
- `task_done()` 함수: `.done` 파일 감지 시 Phase 전환 또는 롤백 분기
- chain.json 스키마: `phases[].max_retries`, `phases[].retry_count` 필드 추가

---

## 4. Quality Gate 패턴 → QC 에스컬레이션 명문화

### 4.1 패턴 정의

```yaml
gate: "step.qc.overall == PASS"
retry: 1
max_retries: 2
escalation: "on_max_retries_exceeded"
```

현재 QC 흐름은 `qc-checklist-standard.md`에 Layer 1→2→3 구조로 정의되어 있으나, 각 레이어의 실패 시 `dispatch.py`가 자동으로 재작업을 트리거하는 메커니즘이 없다.

### 4.2 현재 QC 흐름과의 차이점

현재 흐름:
- 팀장 셀프 QC → `qc_verify.py` 실행 → 마아트 독립 검증
- FAIL 시 아누가 수동으로 재작업 위임 (`dispatch.py` 재호출)
- 재시도 횟수 추적 없음
- 에스컬레이션 기준 비공식

Quality Gate 개선 방향:
- QC FAIL → 자동 재작업 트리거 → max_retries 도달 시 에스컬레이션
- 레벨별(normal/critical/security) 에스컬레이션 기준 명문화

### 4.3 에스컬레이션 3단계 기준

**Level 1: normal QC FAIL**

발생 조건:
- `qc_verify.py overall == FAIL`
- 팀장 셀프 QC 5항목 중 미충족 항목 존재

자동 처리:
- 동일 팀에 재작업 디스패치 (재시도 1회)
- task_id 형식: `{원본_task_id}.retry1` (또는 신규 task_id 발행)
- 재시도 컨텍스트에 FAIL 상세 내용 포함 (qc_verify.py JSON 결과)

max_retries 도달 시 에스컬레이션:
- 아누에게 알림: "{task_id} QC FAIL 2회 초과 — 수동 검토 필요"
- 체인 진행 일시 중단 (다음 Phase 자동 전환 보류)
- 마아트에게 독립 감사 요청

**Level 2: critical QC FAIL**

발생 조건:
- `qc_verify.py overall == FAIL` (normal 조건 포함)
- 또는 마아트 Layer 3 독립 검증 FAIL

자동 처리:
- 즉시 재작업 디스패치 (재시도 1회, 마아트 FAIL 리포트 첨부)
- critical 레벨 작업은 재시도 시에도 마아트 재검증 필수

max_retries 도달 시 에스컬레이션:
- 아누에게 알림 + 레드팀(로키) 코드 리뷰 요청
- 제이회장님 보고 대기 플래그 설정
- 해당 task에 연결된 chain의 전체 중단

**Level 3: security QC FAIL**

발생 조건:
- 마아트 Layer 3 FAIL (보안 관련 항목 포함)
- 또는 로키 Layer 3 보안 감사 FAIL
- 또는 `qc_verify.py`에서 보안 관련 파일 변경 감지

자동 처리:
- 즉시 중단 (재시도 없음)
- 아누에게 즉시 알림: "[SECURITY] {task_id} — 보안 검사 FAIL, 즉각 조치 필요"
- 로키에게 긴급 보안 감사 디스패치 (별도 task_id 발행)

max_retries 정책: security 레벨은 자동 재시도 없음, 로키 보안 감사 완료 후 아누가 수동 판단

### 4.4 자동 재작업 트리거 설계

`dispatch.py`에 추가 필요한 개념:

QC 결과 구독 방식:
- 현재: 팀장이 `.done` 파일 생성 → 아누 followup이 감지
- 개선: 팀장이 QC 결과를 `memory/events/{task_id}.qc.json`으로 저장
  - 필드: `task_id`, `overall` (PASS/FAIL/WARN), `level`, `retry_count`, `details`
- 아누 followup이 `.done` + `.qc.json` 함께 읽어 자동 재작업 여부 판단

재작업 디스패치 함수 개념:

```
dispatch_retry(
    original_task_id: str,
    qc_result_path: str,  # memory/events/{task_id}.qc.json
    retry_count: int,
    max_retries: int = 2
) -> dict
```

- original_task_id로부터 원본 task_desc 재로드 (`memory/tasks/{task_id}.md`)
- qc_result_path에서 FAIL 상세 내용 읽어 재작업 프롬프트에 첨부
- retry_count >= max_retries이면 에스컬레이션 처리 후 `{"status": "escalated"}` 반환

### 4.5 WARN 처리 방침

`qc_verify.py overall == WARN` 상태의 처리:
- WARN은 재작업 트리거 없음 (현재 정책 유지)
- 단, critical/security 레벨에서는 WARN도 마아트 수동 검토 필요
- CONDITIONAL PASS 발행 가능 (qc-checklist-standard.md 섹션 6 기준 준수)
- WARN 내역은 다음 Phase 시작 전 체크리스트에 반영 의무화

---

## 5. dispatch.py 수정 방향 매핑

### 5.1 단일 진입점 원칙 유지

모든 변경은 `dispatch()` 함수를 통해 진입한다는 원칙을 유지한다.

현재 서명:
```
dispatch(team_id, task_desc, level, session_id, project_id, chain_id, refresh_map, task_type)
```

Fork-Join 지원을 위한 확장 방향:
- `dispatch()` 단일 팀 버전 유지 (하위 호환)
- `dispatch_parallel(teams, task_desc, ...)` 신규 함수로 분리
  - 내부적으로 `dispatch()`를 팀 수만큼 호출
  - fork_id를 생성하여 task_id들을 묶음
  - Merge followup을 별도 등록

기존 진입점 보존 이유:
- `chain.py`가 `dispatch.py`를 subprocess로 호출하는 구조 (`DISPATCH_PY` 경로 하드코딩)
- `main()` 함수의 argparse 인터페이스를 변경하면 chain.py와 기존 스크립트가 영향받음
- 신규 기능은 별도 함수 및 별도 CLI 서브커맨드로 추가

### 5.2 함수별 패턴 적용 매핑

`generate_task_id()` 함수:
- 현재 역할: 단일 task_id 발행 및 `task-timers.json`에 reserved 등록
- Fork-Join 적용: fork_id 발행 함수(`generate_fork_id()`)를 별도로 추가
- Quality Gate 적용: task_id에 retry 카운터 메타데이터 필드 추가 (`retry_count`, `max_retries`)

`dispatch()` 함수:
- 현재 역할: 단일 팀 디스패치 + followup 등록
- Iterative 적용: `retry_count` 파라미터 추가, 재시도 컨텍스트 자동 삽입
- Quality Gate 적용: QC FAIL 감지 시 `dispatch_retry()` 호출 경로 추가
- 변경 시 주의: `_cleanup_task()` 호출 시점에 fork 소속 task 정리 로직 포함 필요

`_register_followup()` 함수:
- 현재 역할: 단일 task_id 완료 감지 후 아누에게 보고
- Fork-Join 적용: fork_id 기반 followup 등록 (`_register_fork_followup()` 신규)
  - 모든 팀의 `.done` 파일 존재 여부를 확인
  - 일부만 완료된 경우 나머지 팀 진행 상황 보고
- Quality Gate 적용: followup 내 qc_result 파일 읽기 → 자동 재작업 분기

`_update_chain_task()` 함수:
- 현재 역할: chain.json에 task_id, dispatched_at, status 기록
- Iterative 적용: retry_count, max_retries 필드 함께 기록
- Phase 롤백 시 `rolled_back` 상태 기록

`build_prompt()` 함수 (team_prompts 위임):
- 현재 역할: 팀에게 전달할 프롬프트 생성
- Quality Gate 적용: retry 시 qc_fail_context 파라미터 추가
  - `build_prompt(..., qc_fail_context="{qc FAIL 상세}")` 형식
  - 재작업 프롬프트에 이전 FAIL 원인과 구체적 수정 요청 자동 삽입

### 5.3 신규 모듈/함수 후보

`dispatch_parallel()`:
- 위치: `dispatch.py` 내 신규 함수
- 역할: teams 목록을 받아 fork_id 생성 + 각 팀 dispatch + fork 메타데이터 저장
- CLI 연동: `dispatch.py --teams dev1-team,dev2-team --task-file path` 형태

`_generate_fork_id()`:
- 위치: `dispatch.py` 내 신규 함수
- 역할: `fork-{YYYYMMDD}-{seq}` 형식 ID 발행, `memory/forks/` 디렉토리 관리

`dispatch_retry()`:
- 위치: `dispatch.py` 내 신규 함수
- 역할: QC FAIL 후 동일 팀에 재작업 디스패치, retry_count 관리, max_retries 초과 시 에스컬레이션

`check_fork_done()`:
- 위치: `dispatch.py` 또는 `chain.py` 내 신규 함수
- 역할: fork_id 소속 모든 task_id의 `.done` 파일 존재 여부 확인
- Merge followup 내에서 호출

### 5.4 데이터 파일 구조 변경

`memory/task-timers.json` 기존 tasks 필드에 추가 메타데이터:

```
tasks:
  task-10.1:
    status: in_progress
    fork_id: fork-20260306-001   (신규: fork 소속인 경우)
    retry_count: 0               (신규: 재시도 횟수)
    max_retries: 2               (신규: 최대 재시도 허용)
    qc_level: critical           (신규: QC 레벨 캐시)
```

신규 파일 `memory/forks/{fork_id}.json` 구조:

```
fork_id: fork-20260306-001
status: pending | partial | done | escalated
created_at: 2026-03-06T10:00:00
teams:
  dev1-team:
    task_id: task-10.1
    status: in_progress
    done_at: null
  dev2-team:
    task_id: task-11.1
    status: in_progress
    done_at: null
merge_strategy: parallel_review | cross_verify | first_wins
merge_done_at: null
```

---

## 6. 설계 원칙 및 제약사항

### 6.1 변경 불가 원칙

다음 사항은 어떤 개선에서도 변경하지 않는다:

- 단일 진입점 원칙: 모든 팀 위임은 `dispatch.py`를 통해야 함
- cokacdir --cron 기반 독립 세션: 아누 현재 대화를 블로킹하지 않는 구조 유지
- .done 파일 기반 이벤트 감지: 완료 이벤트는 파일 시스템 기반 (폴링 크론 방식 아님)
- task_id 파일 락: `fcntl.flock` 기반 중복 방지 메커니즘 유지
- BOT_KEYS 환경변수: 팀별 봇 키 분리 원칙 유지

### 6.2 도입 우선순위 제안

Phase 1 (난이도 낮음, 독립적):
- Quality Gate - normal/critical/security 에스컬레이션 기준 명문화
- task-timers.json에 qc_level 필드 추가
- followup 프롬프트에 qc_result 파일 읽기 로직 추가

Phase 2 (중간 난이도, chain.py 연동):
- Iterative 패턴 - chain.json에 max_retries, retry_count 필드 추가
- `_advance_phase()` 에 실패 시 롤백 분기 추가
- `.failed` 이벤트 파일 규약 수립

Phase 3 (높은 난이도, 신규 모듈):
- Fork-Join 패턴 - `dispatch_parallel()` 함수 신규 작성
- `memory/forks/` 디렉토리 및 fork 메타데이터 구조 도입
- fork 단위 followup 등록 및 Merge 트리거 구현

### 6.3 리스크 항목

- chain.py와 dispatch.py 간 상태 동기화 문제
  - 현재 chain.json과 task-timers.json이 별도 락을 사용
  - fork 도입 시 forks/{fork_id}.json이 세 번째 락 파일이 됨
  - 데드락 방지: 락 획득 순서를 task-timers → fork → chain 순으로 고정

- BOT_KEYS 환경변수 미설정 시 `dispatch_parallel()`의 일부 팀 실패
  - 현재: 단일 팀 키 미설정 → sys.exit(1)
  - 개선 필요: 복수 팀 중 일부 키 미설정 → 나머지 팀은 계속, 미설정 팀 에러 기록

- fork_id와 chain_id 중첩 시 Phase 자동 전환 로직 혼란
  - chain.py의 `_advance_phase()`는 단일 Phase 내 모든 task 완료 여부를 확인
  - Fork 내 task들이 모두 chain에 등록된 경우 정상 동작
  - Fork Merge 완료 전에 chain Phase가 완료 처리되는 오류 방지: fork_id 소속 task는 Merge 완료 후에만 `status: completed` 기록

---

**문서 버전**: v1.0
**최종 수정**: 2026-03-06
**다음 검토 예정**: Fork-Join Phase 3 설계 착수 전
