---
name: orchestrate-agent-task-loop
description: agent-task의 작업들 실행해 요청 시 활성 PLAN/CODE_REVIEW 루프를 무인 실행한다. 번호와 선행 complete.log를 기준으로 준비된 작업을 병렬 dispatch하고, lane/G별 Codex·Claude·agy·Pi worker, Pi 자가검증, Codex 공식 리뷰, cloud context 승격을 끝날 때까지 반복할 때 사용한다.
---

# Orchestrate Agent Task Loop

## 목적

`agent-task/`의 file-based 상태 계약을 감시하고 준비된 PLAN 구현부터 공식 코드 리뷰와 후속 PLAN까지 자동으로 수렴시킨다. 파일명·의존성·슬롯·세션 locator는 스크립트가 판별하고, 구현과 리뷰의 의미 판단은 각 CLI agent가 맡는다.

## 언제 호출할지

- 활성 `PLAN-*-G??.md` 작업들을 번호·의존성 순서로 무인 실행할 때
- 독립적인 split subtask들을 가능한 범위에서 병렬 실행할 때
- Pi 작업에 자가검증을 한 번 추가하고 모든 공식 리뷰를 Codex로 반복할 때
- cloud worker의 context/quota/model 오류를 상위 CLI가 native session을 찾아 이어받게 할 때

## 입력

- `workspace`: `agent-task/`가 있는 신뢰된 repository root (선택, 기본값: 현재 디렉터리)
- `task_group`: 특정 `agent-task/<task_group>`만 실행할 때의 이름 (선택)
- `dry_run`: 실제 CLI를 시작하지 않고 상태·route·의존성만 검사 (선택)
- `retry_blocked`: non-dry-run에서 이전 dispatcher 실행이 차단한 같은 PLAN을 명시적으로 다시 시도 (선택). `task_group` 지정 시 그 그룹의 차단·10회 카운터만 초기화하고 다른 그룹 상태는 보존한다.

## 먼저 확인할 것

- [ ] `agent-ops/skills/common/plan/SKILL.md`와 `agent-ops/skills/common/code-review/SKILL.md`의 현재 상태 계약을 읽는다.
- [ ] `codex`, `claude`, `agy`, `pi`가 PATH에 있고 로그인·provider 설정이 유효한지 확인한다.
- [ ] 자동 승인은 현재 workspace 안의 PLAN 실행에만 허용되며, 외부 시스템 변경이나 파괴적 작업으로 범위를 넓히지 않는다.
- [ ] 다른 dispatcher가 같은 workspace를 실행 중이지 않은지 확인한다. 스크립트의 workspace lock 실패를 우회하지 않는다.
- [ ] 첫 실행 전 `--dry-run`으로 active task 분류와 dependency 상태를 확인한다.

## Route 계약

| PLAN route | Worker |
|---|---|
| `local-G01`~`local-G06` | Pi `iop/ornith-fast`, thinking high |
| `local-G07`~`local-G08` | Pi `iop/laguna-s:2.1`, thinking high |
| `local-G09`~`local-G10` | Claude `claude-opus-4-8`, effort xhigh |
| `cloud-G01`~`cloud-G02` | agy `Gemini 3.5 Flash (Low)` |
| `cloud-G03`~`cloud-G04` | agy `Gemini 3.5 Flash (Medium)` |
| `cloud-G05`~`cloud-G06` | agy `Gemini 3.5 Flash (High)` |
| `cloud-G07`~`cloud-G08` | Claude `claude-opus-4-8`, effort xhigh |
| `cloud-G09`~`cloud-G10` | Codex `gpt-5.6-sol`, reasoning xhigh |
| 모든 `CODE_REVIEW-*` | Codex `gpt-5.6-sol`, reasoning xhigh |

동시 실행 제한:

- Pi `ornith-fast`: 3
- Pi `laguna-s:2.1`: 2
- agy: 1
- 공식 Codex review: 별도 숫자 제한 없음
- worker/selfcheck와 공식 review는 task가 다르고 dependency가 충족되면 write-set과 무관하게 병렬 실행한다. 같은 task의 중복 실행만 막는다.
- 공식 review도 dependency가 충족된 서로 다른 task이면 모두 병렬 실행한다.
- 첫 review batch 전에 dispatcher가 Agent-Ops 관리 `.gitignore` block을 한 번 정규화하여 review끼리 같은 shared control file을 동시에 수정할 필요가 없게 한다.
- `수정 파일 요약`은 review 범위와 정체 판정용 정보이며 dispatch 순서를 제한하지 않는다.

## Prompt 계약

제어 프롬프트는 영어로 통일하고 절대경로만 넣으며 아래 문장을 불필요하게 확장하지 않는다.

- Cloud worker: `Read {PLAN_PATH} and complete the task. Final in Korean.`
- Pi worker: `Think in English. Final in Korean. Read {PLAN_PATH} and complete the task.`
- Pi 자가 재검토: `Think in English. Final in Korean. Read {CODE_REVIEW_PATH} and fill every missing implementation field. Do not finish until all implementation fields are complete. This is a self-check of completed work, not a review. Read {PLAN_PATH} and finish any missing work. Recheck and fix your work.`
- 공식 리뷰: `Read {CODE_REVIEW_PATH} and start the review. Final in Korean.`
- 리뷰 종료 복구: `Continue the review for {TASK_PATH}. Final in Korean.`
- 컨텍스트 승격: `Continue from {LOCATOR_PATH}. Check the saved context and current workspace. Final in Korean.`

어떤 worker/selfcheck/review 모델에도 `WORK_LOG.md` 생성·수정·요약을 요구하지 않는다.

Pi 자가 재검토의 exit code `0`만으로 성공 처리하지 않는다. `CODE_REVIEW_PATH`의 구현 항목 완료 표, 구현 체크리스트, 계획 대비 변경 사항, 주요 설계 결정, 검증 결과, 마지막 CODE_REVIEW 동기화 항목이 모두 실제 내용으로 채워져야 `selfcheck_done=true`로 기록한다. 하나라도 비어 있으면 같은 prompt로 재시도하되 10회 연속 미완성이면 해당 task를 차단하고 독립 작업 drain을 계속한다.

Pi 복구 attempt는 locator 설명을 전달하지 않고 `Read {PLAN_PATH}. Continue.`만 사용한다. 다른 CLI 승격 attempt에는 `Continue from {LOCATOR_PATH}. Check the saved context and current workspace. Final in Korean.`을 사용한다. 공식 review 승격·복구 프롬프트에는 collaboration 금지와 next-state materialization 문장을 유지한다. 모델에게 별도 handoff 요약 작성을 요구하지 않는다.

Pi `laguna-s`에서 `context-limit`/max-token 또는 tool 실행 중이 아닌 상태에서 확인된 terminal session-stall이 발생하면 fresh session-id를 만들지 않는다. 직전 locator의 native session 파일을 `pi --session`과 기존 `--session-dir`로 그대로 재개하고 `Think in English. Final in Korean. Continue this session and complete the current task.`를 전달한다. dispatcher 재기동 뒤에도 failed locator를 찾아 같은 session을 재개한다. 이 same-session 재시작도 동일 stage의 연속 실패 10회 한도에 포함한다.

## 작업 로그 계약

- task group마다 `agent-task/{task_group}/WORK_LOG.md` 하나만 둔다. split subtask 디렉터리에는 만들지 않는다.
- dispatcher만 이 파일을 수정한다. worker/selfcheck/review 모델은 읽거나 갱신할 의무가 없고 성공 판정도 로그 본문에 의존하지 않는다.
- 각 실행을 시간순 `START`/`FINISH` 행으로 append하고 time, task, role, attempt, model, result, locator를 기록한다. time은 KST(`UTC+09:00`) ISO 8601 형식으로 기록한다. 병렬 실행 순서는 이 단일 timeline으로 확인한다.
- attempt locator는 모델 stdout/stderr만 담는 `stream.log`와 dispatcher 상태만 담는 `heartbeat.log`를 분리해 기록한다. health는 `stream.log` mtime과 native session event 중 최신 진행만 사용하며, heartbeat mtime은 절대 진행 근거로 쓰지 않는다. `WORK_LOG.md`에는 어느 로그도 복제하지 않는다.
- PASS archive의 `complete.log`가 검증되면 dispatcher는 해당 task의 모든 attempt 디렉터리(locator, native session, `stream.log`, `heartbeat.log`, CLI 보조 로그)를 삭제한다. 실패·차단 attempt 로그는 복구 근거로 유지한다.
- 로그 생성·append 실패는 `work-log-setup` 또는 `work-log-runtime-write`로 locator에 기록하고 차단한다.
- dispatcher가 직접 쓰는 `WORK_LOG.md` 변경은 공식 review의 의미 진행·무진행 signature에서 제외한다. PLAN/CODE_REVIEW, review log, write-set의 실제 변경만 진행으로 인정한다.

## 호출자 생명주기와 상태 표시

- **절대 규칙 — task-local blocker가 생겨도 전체 task group을 즉시 중단하지 않는다.** caller는 blocker task와 그 task의 미완료를 predecessor로 요구하는 의존 task만 대기시키고, 의존하지 않는 모든 ready/running task를 완료할 때까지 turn을 유지한다. 진행 상황과 부분 성공은 `commentary`로 보고하며, 성공 terminal state 또는 모든 독립 작업을 소진한 blocker terminal state에서만 `final`을 보낸다.
- **절대 규칙 — 새 task 시작 후보군의 전체 스캔은 dispatcher 최초 진입과 task의 검증된 `complete.log` 생성 직후에만 수행한다.** worker/selfcheck/review attempt 종료나 같은 task의 단계 전환 때는 그 task만 다시 분류한다. `complete.log`가 생성되면 현재 실행 중인 task만 제외하고 실행 가능한 모든 task를 같은 pass에서 즉시 병렬 시작한다. 다른 task의 실행·대기·dependency·review·복구 상태는 후보를 막지 않는다. 후보와 running task가 없고 blocker·그 의존 대기만 남으면 exit code `2`로 종료한다.
- 생명주기의 기준 주체는 child dispatcher process가 아니라 이 스킬을 실행한 caller agent 자신이다. dispatcher는 실행 수단일 뿐이며 child의 종료, yield, session/cell 유실만으로 caller가 `final`을 보내지 않는다.
- caller는 자기 turn을 유지한 채 dispatcher를 foreground의 하나의 지속 실행으로 시작하고, 실행 도구가 live session/cell id를 반환하면 같은 id를 poll한다. child가 살아 있는 동안 중복 dispatcher를 시작하지 않는다.
- child가 예상보다 일찍 종료되거나 session/cell을 잃으면 caller가 active task, locator, PID, state를 다시 확인하고 가능한 재연결·복구·재실행을 계속한다. exit code `0`은 성공 terminal state, `2`는 drained blocker terminal state다. exit code `3`은 다른 dispatcher가 소유한 active task가 남은 non-terminal 추적 상태이므로 caller는 `final`을 보내지 않고 해당 PID·locator를 계속 추적한다.
- drained blocker terminal state에서는 `.git/agent-task-dispatcher/state.json`의 orchestration group을 `blocked`, 직접 차단 task를 `blocked`, 그 predecessor에 의존한 task를 `waiting`, 검증된 독립 완료 task를 `complete`로 저장한다. 재실행에 진입하면 미완료 관찰 task의 orchestration 추적 상태를 `active`로 되돌리고 실제 task-local blocker와 dependency를 다시 판정한다.
- dispatcher는 관찰 task와 시작 시점의 같은 이름 archive 전체(`complete.log` 유무와 무관)를 baseline으로 `.git/agent-task-dispatcher/state.json`에 영속화한다. child 재시작 후 active task가 사라졌으면 baseline에 없던 새 `complete.log` archive가 정확히 하나일 때만 완료를 복구하고, 없거나 여러 개면 차단한다. 실행 전부터 있던 불완전 archive에 뒤늦게 `complete.log`가 생겨도 현재 실행의 완료로 간주하지 않는다.
- 기존 `state.json`을 읽거나 JSON object로 검증할 수 없으면 빈 상태로 대체하거나 10회 예산을 초기화하지 않고 dispatcher를 차단한다. 파일을 복구하거나 명시적으로 처리한 뒤 재실행한다.
- 새 user turn이 와도 기존 요청을 취소하라는 명시가 없으면 caller가 같은 전체 작업을 계속 추적한다.
- 각 `작업시작`, `자가검증시작`, `리뷰시작`, `리뷰재시도`, `Pi복구재시도`, `세션응답복구재시도`, `세션연결재시도`, `리뷰결과`, `작업대기`, `작업차단`, `디스패치추적대기`, `작업완료` banner를 사용자 commentary에 전달한다. 새 banner가 없어도 실행 중이면 60초 안에 현재 stage와 마지막 locator·task-group `WORK_LOG.md` 경로를 한 번 표시한다.
- 모든 CLI의 health/progress는 실제 stdout/stderr `stream.log`를 우선으로, native session event가 있는 CLI는 그 event도 함께 본다. heartbeat mtime은 절대 진행 근거가 아니다. locator에는 dispatcher PID와 agent PID를 즉시 기록하며, 다른 dispatcher는 둘 중 살아 있는 PID가 있으면 조용한 stream만으로 중복 attempt를 시작하지 않는다. Pi는 `--mode json`으로 실행해 `thinking_delta`/`text_delta`/tool stream을 stdout으로 남긴다. Pi의 **정확한** toolCall→모든 toolResult 구간만 native event와 stream을 함께 보는 30분 tool 실행 구간이며, 직전 assistant의 모든 `toolCall.id`와 이후 `toolResult.toolCallId`가 일치할 때만 끝난다. `starting`·`unknown`·모델 추론·toolResult 뒤 상태는 tool 실행으로 추정하지 않는다. 그 밖의 Pi liveness는 `stream.log` 갱신만 기준으로 하며 toolResult 자체는 모델 응답 silence clock을 초기화하지 않는다. tool 실행 구간 밖에서 stream이 3분 멈추면 dispatcher는 마지막 stream 일부를 `pi_silence_inspection`에, 다른 CLI는 `stream_silence_inspection`에 기록하고 `모델응답점검`을 출력하되 model process를 종료하지 않는다. 실제 terminal 오류·process 종료만 복구 근거로 사용한다.
- `provider-connection`, `provider-stream-disconnect`, `session-stall`, `generic-error`, `process-terminated`, context/quota/model 오류와 review control violation은 원인을 합치지 않되 동일 task stage의 연속 자동 복구 실패 예산 10회를 공유한다. 10번째 실패에서 해당 task를 차단하고 자동 cooldown 재개하지 않는다. 성공하면 그 stage의 누적 실패를 초기화한다.
- Pi 자가검증 결과가 10회 연속 구현 필드 미완성이거나 공식 review가 10회 연속 무변경이면 해당 task를 각각 명시적 terminal blocker로 기록한다.
- 하나의 task가 복구·차단되는 동안에도 scheduler는 그 task를 predecessor로 요구하지 않는 모든 ready/running task를 계속 실행한다. 내부 복구나 차단은 전체 후보군 스캔을 임의로 유발하지 않는다.
- `laguna-s`의 `context-limit`/`session-stall`은 Prompt 계약의 same-session 재개 규칙을 우선하고 `Pi세션연속재시작`으로 표시한다. 그 외 Pi `session-stall`만 fresh session과 `세션응답복구재시도`를 사용한다.
- user-review 정지는 파일명만으로 판정하지 않는다. active task의 `USER_REVIEW.md`가 `상태: USER_REVIEW`, `유형: milestone-lock`, 실제 `agent-roadmap/**/milestones/*.md` 연결 대상, `없음`·`미정`이 아닌 차단 판단 근거·미해결 결정 항목·재개 조건을 모두 가져 현재 task의 다음 안전한 구현을 막을 때만 `user-review` terminal blocker로 인정한다. 형식이 불완전하거나 active PLAN/CODE_REVIEW와 충돌하면 user-review가 아니라 task 상태 계약 오류로 차단한다.
- review 판정은 `## 코드리뷰 결과` 섹션 안의 유일한 `종합 판정: PASS|WARN|FAIL` 필드만 인정한다. 구현 증거, 명령 출력, 예시 문구에 같은 문자열이 있어도 runtime verdict로 해석하지 않는다.
- `.git/agent-task-dispatcher/runs/`의 locator/raw log는 복구용 내부 상태라 일반 project tree에 보이지 않을 수 있다. dispatcher가 attempt 시작 즉시 출력하는 `locator=`와 task-group `WORK_LOG.md` 경로를 상태 업데이트에 포함한다.
- caller가 성공 `final`을 보내는 조건은 관찰된 전체 task group의 모든 task마다 검증된 archive `complete.log`가 있고 active pair·blocked/pending task·실행 dispatcher가 없으며 최종 dispatcher exit code가 `0`인 때다.
- caller가 blocker `final`을 보내는 조건은 실행 가능한 독립 task와 running dispatcher가 없고, 남은 task가 검증된 `USER_REVIEW`, 10회 반복 실패, 명시적 상태 오류 또는 그 predecessor에 의존한 대기 task뿐이며 최종 dispatcher exit code가 `2`인 때다. blocker 종료는 성공으로 표현하지 않고 완료한 독립 task, 차단 task, 의존 대기 task, 재개 조건을 구분해 보고한다.
- child failure가 repo 안에서 복구 가능하면 10회 예산 안에서 계속한다. 예산 소진, 사용자 결정, 외부 권한처럼 caller가 현재 turn에서 해제할 수 없는 blocker는 독립 작업 drain 이후 경로·근거·재개 조건과 함께 terminal blocker로 보고한다.

## 실패 분류와 보고 계약

- 모든 attempt locator에 dispatcher process PID와 실제 agent PID, import 시각, source path, import 시점 SHA-256, attempt 시작 시점의 current SHA-256과 `dispatcher_source_matches_loaded`를 기록한다. 모든 실패 배너와 후속 상태 보고는 locator의 `failure_class`, `failure_source`, `provider_transport_failure_confirmed`, `dispatcher_pid`, `agent_pid`, `dispatcher_source_sha256`, source 일치 여부, `locator`를 그대로 제시하며 원인을 상위 범주로 바꾸어 요약하지 않는다.
- 실행 중인 Python dispatcher는 source hot edit를 반영하지 않는다. `dispatcher_source_matches_loaded=false`이면 새 규칙이 적용됐다고 보고하지 않고, 해당 process 소유 세션이 안전하게 종료·재시작할 때까지 loaded/current hash와 실행 버전 차이를 명시한다.
- `provider-connection`과 `provider-stream-disconnect`는 CLI terminal diagnostic에서 provider/backend/SSE 문맥을 포함한 강한 provider 패턴과 일치한 원문이 있을 때만 사용한다. 일반 tool/test stderr의 `connection refused`, `dial tcp`, `curl` peer failure만으로 provider 오류를 만들지 않는다. 확정한 attempt에는 `failure_source=provider-terminal-diagnostic`, `provider_transport_failure_confirmed=true`, `failure_evidence_source`, `failure_evidence_excerpt`를 locator에 함께 보존한다.
- `session-stall`은 provider 오류가 아니라 dispatcher 정책에 의한 timeout이다. `failure_source=dispatcher-timeout`, `provider_transport_failure_confirmed=false`, `termination_initiator=dispatcher`, timeout phase/seconds를 보고한다. 같은 attempt가 SIGTERM 계열 exit code로 끝나도 provider/SSE 오류라고 표현하지 않는다.
- dispatcher가 시작하지 않은 SIGTERM 계열 종료는 `process-terminated`, `failure_source=process-termination`, `termination_initiator=unknown`으로 기록한다. 실제 provider terminal evidence가 없는 exit code `143`을 provider 오류로 분류하지 않는다.
- `pi -p` fresh/isolated session의 한 attempt 결과를 Pi TUI 또는 provider 전체 장애로 일반화하지 않는다. 시스템 수준 provider 장애라는 표현은 동일 명령·모델·prompt의 통제된 재현이나 backend health 근거가 추가로 있을 때만 사용한다.
- `process-terminated`도 다른 자동 복구 failure class와 같은 stage별 연속 실패 예산에 포함한다. 10번째 연속 실패에서 해당 task를 terminal blocker로 기록하며 cooldown 뒤 예산을 초기화하거나 자동 재개하지 않는다. 여러 failure class가 같은 예산을 쓴다는 사실을 원인의 동일성이나 provider 장애 근거로 사용하지 않는다.

## 실행 절차

1. **상태 점검**
   - 다음 명령으로 active task, route, stage, dependency를 출력한다.

     ```bash
     python3 agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py --dry-run
     ```

   - `NN_...`은 즉시 후보이고, `NN+PP[,QQ...]_...`은 같은 task group의 각 predecessor `complete.log`가 active 또는 좁은 archive lookup에서 하나씩 확인되어야 한다.
   - 숫자 자체를 암묵적 dependency로 해석하지 않는다.

2. **Dispatcher 실행**
   - 전체 active task:

     ```bash
     python3 agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py
     ```

   - 특정 task group:

     ```bash
     python3 agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py --task-group <task_group>
     ```

   - worker/selfcheck/review future가 `complete.log` 없이 끝나면 그 task만 다시 읽어 다음 단계를 실행한다. 전체 후보군은 다시 스캔하지 않는다.
   - 실행 중인 task는 persistent `active_stage`로 기록한다. dispatcher 재기동 뒤에도 그 task만 후보에서 제외하고, 나머지 dependency-ready task는 즉시 모두 병렬 dispatch한다. active task가 있어도 후보 탐색이나 다른 task의 실행을 기다리게 해서는 안 된다.
   - **절대 규칙:** 전체 후보군 스캔은 최초 진입과 검증된 `complete.log` 생성 직후에만 한다. 이 스캔에서는 persistent state와 native session/locator로 실행 중인 task만 제외하고, dependency-ready task를 모두 즉시 병렬 실행한다. dependency 미충족은 해당 task만 제외한다. 후보가 없으면 polling하지 않고 dispatcher를 종료한다.
   - Pi worker 성공, Pi 자가검증 성공, 공식 review를 각각 별도 persistent stage로 기록한다. 재시작 시 `worker_done=true`, `selfcheck_done=false`이면 worker나 review가 아니라 같은 Pi 모델의 새 자가검증 session부터 재개한다.
   - persistent state는 PLAN 첫머리의 `task/plan/tag` 세대를 키로 사용한다. 같은 PLAN의 체크리스트·본문 갱신은 stage를 초기화하지 않고, 후속 PLAN의 plan 번호 변경은 새 stage로 초기화한다.
   - dispatcher 실행 기록 없이 review stub의 마지막 구현 체크가 이미 완료된 작업은 review로 보낸다. dispatcher가 실행한 Pi worker 성공 기록은 자가검증 완료 전까지 review로 보내지 않는다.
   - 공식 review와 worker/selfcheck는 다른 task이고 dependency가 충족되면 동시에 시작한다. 다른 task의 checkout·write-set은 대기 사유가 아니다.
   - 모든 worker/selfcheck/review attempt의 시작과 종료를 dispatcher가 task-group `WORK_LOG.md`에 기록한다.

3. **승격과 context 복구**
   - CLI의 terminal provider 오류 이벤트나 stderr에서 확인된 context/output limit, provider quota/rate limit, model unavailable만 `agy -> Claude -> Codex` 또는 `Claude -> Codex`로 승격한다. agent 응답, source, tool/test 출력의 같은 문자열은 승격 근거로 쓰지 않는다.
   - Codex가 같은 오류를 내면 locator를 전달한 fresh Codex session으로 재시도하되 동일 stage의 연속 실패 10회 한도를 공유한다. 복구 중에도 다른 task dispatch는 계속한다.
   - timeout/crash/permission/일반 구현 오류는 해당 task의 실제 failure class와 locator를 보존한 채 동일 stage 연속 실패 10회 안에서 복구한다. 한도 소진 시 해당 task만 차단하고 다른 독립 task dispatch는 계속한다.
   - Pi는 cloud 승격하지 않는다.
   - attempt identity는 `<task-name>__p<plan>__<role>__aNN`이다. locator에는 workspace, CLI/model, PLAN/review, `WORK_LOG.md`, session id, native session path, raw output log를 기록한다.
   - locator는 repository의 `.git/agent-task-dispatcher/runs/`에 둔다. `.git` state를 쓸 수 없는 환경에서만 `${XDG_STATE_HOME}/agent-task-dispatcher/<workspace-id>/runs/`를 사용한다.

4. **리뷰 수렴**
   - 공식 review는 각각 독립된 Codex one-shot session으로 실행하며 별도 숫자 제한을 두지 않는다. 준비된 review는 모두 병렬 dispatch한다.
   - active PLAN이 없는 finalization recovery는 동일 task/plan/tag의 archived plan log에서 review 대상을 복구한다. write-set은 dispatch 차단 사유가 아니다.
   - 공식 review는 collaboration/sub-agent 도구를 사용하지 않고 현재 one-shot session 안에서 끝낸다. 해당 도구 호출이 감지되면 그 attempt의 독립 subprocess group을 정리하고 fresh review session으로 재시도한다. 이 실패도 같은 stage의 연속 실패 10회 한도에 포함한다.
   - PASS archive, WARN/FAIL follow-up pair, review finalization recovery는 `code-review` 파일 계약에 맡긴다.
   - active pair가 남아 있으면 다시 분류하여 worker 또는 review로 보낸다.
   - 계획 write-set의 source snapshot과 review/finding artifact가 모두 동일할 때만 정체로 판정한다. 동일 상태는 `루프정체경고`와 backoff로 재시도하되 10회째에도 변화가 없으면 해당 task를 `review-no-progress-limit`으로 차단한다.
   - 검증된 `USER_REVIEW.md`, dependency ambiguity, 10회 반복 실패, 작업 로그 setup/runtime-write 실패는 해당 task의 blocker로만 기록한다. blocker와 그 predecessor에 의존하는 task만 대기시키고, 의존하지 않는 모든 ready/running task는 계속 완료한다. 더 실행할 독립 작업이 없을 때만 exit code `2`로 drained terminal blocker를 반환한다.

## 실행 결과 검증

- [ ] 최초 진입과 검증된 `complete.log` 생성 직후에만 전체 후보군을 스캔하고, 실행 중 task를 제외한 모든 준비된 후보를 같은 pass에서 병렬 시작했는가
- [ ] route별 실제 CLI/model이 표와 일치하는가
- [ ] Pi 작업만 fresh-session 자가검증을 정확히 한 번 거쳤는가
- [ ] 모든 공식 review가 Codex `gpt-5.6-sol` xhigh로 실행되고, dependency가 준비된 review가 숫자 제한 없이 병렬 dispatch됐는가
- [ ] 각 attempt locator에서 native session과 output log를 찾을 수 있는가
- [ ] task-group `WORK_LOG.md` 하나에 모든 worker/selfcheck/review attempt의 START/FINISH 순서가 기록됐는가
- [ ] PASS task가 archive되고 새로 풀린 dependent task가 이어서 시작됐는가
- [ ] 성공 종료는 모든 task의 `complete.log`, blocker 종료는 ready/running task 부재와 task-local blocker·의존 대기만 남았음을 각각 확인했는가
- [ ] 차단 시 task, 원인, locator가 출력됐는가
- 검증 실패 시: dispatcher를 중단하고 active PLAN/CODE_REVIEW 파일을 직접 이동하거나 덮어쓰지 않은 채 원인만 보고한다.

## 출력 형식

```text
------------------------------------------
작업시작: 03+01_event_contract_unit_tests
------------------------------------------
model=pi/iop/ornith-fast
plan=/absolute/path/PLAN-local-G05.md
work_log=/absolute/path/WORK_LOG.md
[03+01_event_contract_unit_tests][worker][a00] ...

------------------------------------------
리뷰시작: 03+01_event_contract_unit_tests
------------------------------------------
model=codex/gpt-5.6-sol xhigh
review=/absolute/path/CODE_REVIEW-local-G05.md
```

`작업대기`, `자가검증시작`, `로그보완재시도`, `모델승격`, `리뷰결과`, `루프정체경고`, `작업차단`, `작업완료`도 같은 separator 형식을 사용한다.

## 금지 사항

- 성공 또는 drained terminal blocker 조건을 확인하기 전 `final`을 보내거나 caller turn을 종료하지 않는다. 단일 blocker가 생겼다는 이유로 즉시 전체를 끝내지 말고, 그 blocker의 의존 작업만 제외한 모든 ready/running 독립 작업을 끝까지 추적한다.
- PLAN/CODE_REVIEW lane 또는 G를 dispatcher가 재평가하거나 파일명을 바꾸지 않는다.
- predecessor index가 없는 숫자 순서를 dependency로 만들지 않는다.
- archive 전체를 탐색하거나 dependency 후보 이외의 archive 파일을 읽지 않는다.
- worker에게 공식 review, archive, `complete.log` 작성을 시키지 않는다.
- Pi 자가검증을 공식 review로 취급하지 않는다.
- model-authored handoff 요약에 context 복구를 의존하지 않는다.
- generic failure를 token/quota 오류로 간주해 상위 모델로 넘기지 않는다.
- `USER_REVIEW.md`를 자동 해소하거나 사용자 결정을 추측하지 않는다.
