# task-685: engine_v2 멀티엔진 집단지성 구현 (Phase 0~5)

## 한정승인
- **단일팀 한정승인**: 1팀(헤르메스)이 Phase 0~5 전체를 자율 진행
- Phase 순서: 0→1→2→3→4→5 (순차 필수, 병렬 금지)
- 각 Phase 완료 시 산출물 저장 후 다음 Phase 즉시 진행

## 참조 문서 (반드시 읽을 것)
- 미팅 기록: `memory/meetings/2026-03-18-three-engine-architecture-cycle2-5.md`
- 3대 엔진 합의도출 스펙: `memory/specs/three-engine-consensus.md`
- 기존 engine.py: `services/multimodel-bot/engine.py`
- Octopus 분석: `memory/research/octopus-analysis.md`
- 조직도: `memory/organization-structure.json`

## 아키텍처 (미팅 확정)

```
services/multimodel-bot/
├── engine.py                        # DEPRECATED 헤더 추가 (기존 봇 보호, 삭제 금지)
├── engine_v2/                       # 범용 레이어 (신규)
│   ├── __init__.py
│   ├── engine_result.py
│   ├── content_sanitizer.py
│   ├── cli_runner.py
│   └── engine_orchestrator.py
└── publishing/                      # 출판팀 전용 (신규)
    ├── publishing_adapter.py
    ├── step_templates.py
    ├── consensus_pipeline.py
    └── chapter_runner.py

config/
└── engine_budget.yaml

memory/
└── engine_usage_YYYY-MM.jsonl
```

- engine_v2 = 범용 (출판팀 미인지)
- publishing = 출판팀 전용 (engine_v2만 의존)
- 기존 engine.py 유지 + DEPRECATED 헤더/docstring 경고

---

## Phase 0: OAuth 보안 패치 (긴급)

### 문제
engine.py 10~11번 라인에 OAuth Client Secret 하드코딩:
```python
_OAUTH_CLIENT_ID = "681255809395-..."
_OAUTH_CLIENT_SECRET = "GOCSPX-..."
```

### 작업
1. `_require_env()` 헬퍼 함수 추가:
```python
def _require_env(key: str) -> str:
    val = os.environ.get(key)
    if not val:
        raise RuntimeError(
            f"필수 환경변수 '{key}' 미설정. "
            f"source /home/jay/workspace/.env.keys 실행 후 재시작하세요."
        )
    return val
```
2. 하드코딩된 값을 환경변수로 교체:
```python
_OAUTH_CLIENT_ID     = _require_env("GEMINI_OAUTH_CLIENT_ID")
_OAUTH_CLIENT_SECRET = _require_env("GEMINI_OAUTH_CLIENT_SECRET")
```
3. `/home/jay/workspace/.env.keys`에 두 변수 추가 (기존 값 이전)
4. engine.py 최상단에 `# DEPRECATED` 헤더 추가
5. 각 함수(call_gemini, call_codex, call_claude) docstring에 "DEPRECATED: engine_v2 사용 권장" 경고 추가
6. Git history 시크릿 노출 여부 확인 → 보고서에 기록

### 산출물
- engine.py 수정본
- .env.keys 업데이트
- `memory/reports/task-685-phase0.md` 보안 패치 보고서

---

## Phase 1: CLI 정리 + Codex Fallback (G01+G02)

### G01: Claude CLI 정리
- 기존 engine.py의 call_claude() CLI 호출 방식 분석
- engine_v2/cli_runner.py에 CLIRunner 클래스 기반 구현 (Claude용)
- asyncio.subprocess 기반, timeout 파라미터 지원

### G02: Codex Fallback 매핑
- Codex free plan 제한 대응:
```python
_CODEX_MODEL_FALLBACK: dict[str, str] = {
    "gpt-5.2-codex":  "gpt-5.1-codex-mini",
    "gpt-5.1-codex":  "gpt-5.1-codex-mini",
}
```
- engine_v2/cli_runner.py에 Codex CLI 래퍼 추가
- `--skip-git-repo-check` 플래그 필수 포함
- fallback_used 필드로 모델 변경 추적

### 산출물
- `engine_v2/__init__.py`
- `engine_v2/cli_runner.py` (Claude + Codex CLI 래퍼)
- 독립 테스트: CLI 호출 성공/실패/타임아웃 시나리오

---

## Phase 2: Gemini CLI + engine_v2 기반 (G03+G04+G05+G06)

### G03: Gemini CLI 전환
- 기존 engine.py의 call_gemini()은 REST API (cloudcode-pa.googleapis.com) 사용
- engine_v2에서는 **Gemini CLI 우선** (하이브리드: CLI first → API fallback)
- `gemini` CLI v0.31.0 확인, `--output-format json` 옵션으로 노이즈 제거
- Gemini CLI 버전 체크 로직 포함

### G04: EngineResult 데이터클래스
```python
@dataclass
class EngineResult:
    engine:         EngineRole      # "claude" | "gemini" | "codex"
    content:        str             # raw output
    clean:          str             # sanitized output
    task_id:        str
    step:           int
    timestamp:      datetime = field(default_factory=datetime.utcnow)
    token_est:      int      = 0
    error:          bool     = False
    fallback_used:  bool     = False
    flagged_count:  int      = 0
```

### G05: Content Sanitizer (인젝션 방어)
3레이어 방어:
- L1: 패턴 기반 정규식 Sanitizer (`content_sanitizer.py`)
- L2: 구조적 격리 — `<UPSTREAM_DATA>` 태그 + 태그 이스케이프
```python
def _escape_envelope_tags(text: str) -> str:
    return text.replace("</UPSTREAM_DATA>", "&lt;/UPSTREAM_DATA&gt;")
```
- L3: 오케스트레이터 게이트 — `flagged_count >= 3` → 파이프라인 중단
- L4: 에러 자동 제외 — `error=True AND !fallback_used` → 중단

### G06: CLIRunner 통합
- Phase 1의 Claude/Codex + Phase 2의 Gemini를 통합 CLIRunner로 관리
- 하이브리드 모드: CLI 60초 → API 30초 fallback → 전체 deadline 120초

### 산출물
- `engine_v2/engine_result.py`
- `engine_v2/content_sanitizer.py`
- `engine_v2/cli_runner.py` (Gemini 추가, 통합)
- 테스트: EngineResult 생성, Sanitizer 정규식 패턴, CLI 타임아웃

---

## Phase 3: Orchestrator + Publishing Adapter (G07+G08)

### G07: EngineOrchestrator
```python
class EngineOrchestrator:
    async def run(
        self,
        mode:    Literal["SEQUENTIAL", "PARALLEL", "BROADCAST"],
        prompts: list[str],
        engines: list[EngineRole],
        task_id: str,
        step:    int,
        timeout: int = 600,
    ) -> list[EngineResult]: ...
```
- SEQUENTIAL: 직렬 실행 (Semaphore 불필요)
- PARALLEL: asyncio.gather + Semaphore(3)
- BROADCAST: 동일 프롬프트를 모든 엔진에 병렬 전송

### G08: Publishing Adapter
- `publishing/publishing_adapter.py`: 출판팀 기존 워크플로우 보호하는 얇은 래퍼
- `publishing/step_templates.py`: 3대 엔진 합의도출 Step 1~5 프롬프트 템플릿
  - 출처 주석 필수: `memory/specs/three-engine-consensus.md`
  - Step 1: 병렬 초안 (Claude + Gemini)
  - Step 2: Gemini 집대성 (내용 축소 금지)
  - Step 3: ChatGPT 비평 (4개 검토시각)
  - Step 4-5: 피드백 반복 (최대 3라운드)
  - Step 5: Claude 최종 통합

### 산출물
- `engine_v2/engine_orchestrator.py`
- `publishing/publishing_adapter.py`
- `publishing/step_templates.py`
- 테스트: 각 모드 동작 검증 (mock CLI)

---

## Phase 4: CONSENSUS + Circuit Breaker + 비용 추적 (G09+G10+G11+G12)

### G09: CONSENSUS 모드
- `publishing/consensus_pipeline.py`: 합의도출 전용 파이프라인
  - 합의 게이트 수치화 (Octopus 내재화 항목)
  - 소수 의견 보존 로직
  - MAX_CONSENSUS_ROUNDS = 3 (하드캡)

### G10: Circuit Breaker
```python
fail_threshold = 3       # fluke 2회 허용
recovery_sec = 120       # 600초 타임아웃의 1/5
half_max_calls = 1       # 복구 확인 최소
```
- 상태: CLOSED → OPEN (fail≥3) → HALF_OPEN (recovery 후) → CLOSED (성공 시)

### G11: 비용 추적
- JSONL append-only 로그: `memory/engine_usage_YYYY-MM.jsonl`
- 월별 파일 분리 (무한 성장 방지)
- 스키마:
```json
{
    "ts": "ISO8601",
    "task_id": "task-XXX",
    "step": 2,
    "engine": "gemini",
    "prompt_chars": 4500,
    "output_chars": 8200,
    "token_est": 3200,
    "fallback_used": false,
    "flagged_count": 0,
    "duration_sec": 45.2,
    "error": false
}
```
- DAILY_LIMITS: `config/engine_budget.yaml` (하드코딩 금지)

### G12: Semaphore + 모델/비용 메타데이터
- asyncio.Semaphore(3) 적용 (PARALLEL/BROADCAST 모드)
- 모델/비용 메타데이터를 EngineResult에 포함 (Octopus 내재화 항목)

### Octopus 내재화 7건 (즉시 대상) — 이 Phase에서 구현
1. 합의 게이트 수치화
2. 소수 의견 보존
3. 모델/비용 메타데이터
4. 환경변수 보안 정책 (Phase 0에서 일부 완료)
5. 참조 무결성 verifier
6. QC 병렬 투입 훅
7. 이슈 점수 수치화

### 산출물
- `publishing/consensus_pipeline.py`
- `config/engine_budget.yaml`
- Circuit Breaker 로직 (engine_orchestrator.py에 통합 또는 별도 모듈)
- 비용 추적 모듈
- 테스트: CB 상태 전이, 비용 로그 기록, 합의 게이트

---

## Phase 5: 통합 + 엔드투엔드 (G13+G14+G15+G16)

### G13: 3대 엔진 Step 1~5 엔드투엔드
- publishing/chapter_runner.py: 챕터 단위 실행 진입점
- CLI 인터페이스: `python3 chapter_runner.py --chapter N --task-id task-XXX`
- step_templates.py + consensus_pipeline.py + engine_orchestrator.py 연결
- 4개 검토시각 구현: 자산관리전문가, 세금/연금전문가, 집필/편집전문가, 레드팀

### G14: Deprecation 마무리
- engine.py → engine_v2 봇 마이그레이션 계획서 작성
- 기존 봇이 engine.py를 어디서 호출하는지 전수조사
- 마이그레이션 단계별 로드맵 (계획서만, 실행은 별도 작업)

### G15: QC 훅
- engine_v2 실행 결과를 QC 파이프라인에 연결하는 훅 포인트
- PostToolUse 훅과 연동 가능하도록 인터페이스 정의

### G16: 대시보드 연동
- engine_usage JSONL → 대시보드(http://100.76.130.39:8000/dashboard/) 표시용 API
- 최소한의 엔드포인트 설계 (구현은 별도)

### Octopus 내재화 5건 (이번 주 대상) — 이 Phase에서 구현
1. 리액션 엔진 v1 (CI 실패 자동 대응 + 에스컬레이션 타이머)
2. 비용 추적 대시보드 연동
3. 역할별 모델 라우팅
4. CVE 조회 (보안 취약점 자동 체크)
5. 중앙 이슈 트래커

### 산출물
- `publishing/chapter_runner.py`
- 봇 마이그레이션 계획서: `memory/reports/task-685-migration-plan.md`
- QC 훅 인터페이스
- 대시보드 API 설계서
- 엔드투엔드 테스트
- 최종 보고서: `memory/reports/task-685.md`

---

## 공통 규칙

### 코딩 규칙
- 모듈화: 파일당 200줄 이하
- 타입 힌트 필수, pyright strict 통과
- 테스트: 각 Phase별 독립 테스트 작성
- 기존 engine.py 동작 절대 깨뜨리지 말 것

### Circuit Breaker 수치 (확정)
- fail_threshold: 3
- recovery_sec: 120
- half_max_calls: 1
- MAX_CONSENSUS_ROUNDS: 3 (하드캡)
- Semaphore: 3

### 보안 규칙
- 키/시크릿 하드코딩 절대 금지
- 환경변수 로드: `_require_env()` 패턴 사용
- 인젝션 방어 3레이어 필수 적용
- `.env.keys`에 민감 정보 저장

### Phase 완료 프로토콜
- 각 Phase 완료 시: 산출물 저장 → 다음 Phase 즉시 진행
- 최종 Phase 5 완료 시: `memory/events/task-685.done` 생성
- 최종 보고서: `memory/reports/task-685.md`