<!-- task=m-agent-task-runtime-target-selector/11+10_unknown_isolation plan=3 tag=REVIEW_REVIEW_REVIEW_REFACTOR -->

# Plan - Retry blocker evidence와 qualified failover 연결

## 이 파일을 읽는 구현 에이전트에게

구현과 검증을 끝낸 뒤 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 내용과 원문 출력으로 채운다. active 파일은 그대로 두고 review 준비 완료만 보고한다. 종결은 code-review skill 전용이다. 차단되면 정확한 원인, 시도한 명령/출력, 재개 조건만 구현 에이전트 소유 evidence에 기록한다. 사용자에게 질문하거나 user-input 도구를 호출하지 않고, control-plane stop file 생성, 다음 상태 분류, archive/log 이동, `complete.log` 또는 roadmap 수정은 하지 않는다.

## 배경

세 번째 리뷰에서 blocked-only marker와 local 뒤 cloud candidate probe는 확인됐지만, explicit retry가 원래 blocker의 qualified failure/locator를 잃고 기존 decision을 `resume`으로 복사하는 결함이 남았다. 새 테스트도 helper를 직접 호출해 fresh snapshot 저장만 확인했으므로 실제 dispatcher→worker→selector 경계의 Laguna→Gemini failover를 증명하지 못했다. task-local blocker evidence와 one-shot retry intent를 fresh alternate snapshot에 연결하고 실제 dispatcher 경계에서 검증한다.

## Archive Evidence Snapshot

- Prior plan: `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/plan_local_G07_2.log`
- Prior review: `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/code_review_cloud_G07_2.log`
- Verdict: `FAIL`
- Required:
  - `mark_retry_quota_refresh`가 blocker의 qualified failure/locator evidence를 보존하지 않고 boolean marker만 남기며, `run_worker`가 fresh snapshot과 함께 `failover`/failure class를 전달하지 않아 기존 decision을 `resume`으로 복사한 뒤 marker를 소비함.
  - retry 회귀가 고정 야간 시각과 실제 `dispatch_with_store(..., retry_blocked=True)`/runner를 통과하지 않고 selected target, failover transition, used/history를 assertion하지 않으며, 기존 integration test는 deny patch 아래 실제 quota subprocess를 1회 호출해도 PASS함.
- Suggested: 없음
- Nit: 없음
- Affected files: `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`, `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`
- Verification evidence: focused retry 13개, BlockerDrain 6개, 전체 Python 251개, `py_compile`, `git diff --check`는 PASS했다. 별도 deny 재현은 `test_success=True quota_subprocess_calls=1`을 확인해 verification trust 결함을 증명했다.
- Roadmap carryover: `throughput-policy`와 SDD S11의 task-local retry, pinned route/history, batch snapshot 재사용, unknown 격리 evidence를 유지한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/agent-task-runtime-target-selector.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/automation-runtime-bridge/milestones/agent-task-runtime-target-selector.md)
- Task ids:
  - `throughput-policy`: target별 정적 cap 없이 ready task dispatch와 batch quota snapshot 재사용
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/plan_local_G07_2.log`
- `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/code_review_cloud_G07_2.log`
- `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`
- `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/testing-smoke.md`
- `agent-ops/rules/common/philosophy.md`
- `agent-roadmap/phase/automation-runtime-bridge/milestones/agent-task-runtime-target-selector.md`
- `agent-roadmap/sdd/automation-runtime-bridge/agent-task-runtime-target-selector/SDD.md`
- `agent-contract/index.md` — agent-task dispatcher에 매칭되는 계약 문서 없음.
- `agent-ops/rules/common/rules-agent-spec.md`
- `agent-spec/index.md` — agent-task dispatcher에 매칭되는 living spec 없음.
- `agent-task/archive/2026/07/m-agent-task-runtime-target-selector/10+09_target_cap_removal/complete.log` — split 선행 의존성 완료 확인.

### SDD 및 테스트 기준

- SDD는 `[승인됨]`, 구현 잠금은 `해제`이며 대상 Acceptance Scenario는 S11, Milestone Task는 `throughput-policy`다.
- SDD State Machine의 explicit retry는 route history를 보존하고 quota exhaustion으로 대기한 단방향 failover eligibility를 새 snapshot으로 재평가할 수 있어야 한다.
- `test_env=local`; 이 변경은 dispatcher 내부 persisted-state simulation이므로 Edge/Node user-flow, mock smoke, live provider preflight는 적용하지 않는다.
- 실제 provider CLI/session/network/host quota subprocess를 실행하지 않고 `run_escalating`, quota provider와 subprocess seam을 deterministic fake/deny로 고정한다.

### 원인과 경계

- `StateStore.mark_retry_quota_refresh`는 blocked task만 고르지만 `blocked`를 지우기 전에 role, last qualified failure class, locator, current selected/used candidates를 구조화해 보존하지 않는다.
- `build_admission_batch_snapshot`은 pending retry의 unused cloud alternate를 probe하지만 snapshot만 반환한다. 그 snapshot을 왜 만들었는지 나타내는 blocker transition context는 worker handoff에 없다.
- `run_worker`는 `persisted_execution_decision(..., quota_snapshot=...)`만 호출한다. prior decision이 있으면 selector는 `resume`을 선택해 fresh snapshot을 decision quota와 failover eligibility에 반영하지 않는다.
- `commit_execution_decision`은 이 resume commit에서 marker를 지우므로 한 번의 잘못된 소비 뒤 재평가 근거가 사라진다.

### 테스트 커버리지 공백

- `SelectorDispatcherIntegrationTest.test_context_budget_and_retry_blocked_lifecycle`의 최초 selection은 deterministic snapshot 없이 quota subprocess seam에 닿는다. production의 `unknown` 정규화 때문에 deny 예외가 테스트 실패로 전파되지 않는다.
- `ThroughputQuotaBatchTest.test_retry_blocked_scopes_to_blocked_worker_and_refreshes_pinned_alternate`는 `datetime.now()`와 helper 직접 호출만 사용하고 실제 dispatcher/worker 경계를 건너지 않는다.
- 현재 테스트는 정확한 Gemini alternate probe, `transition.trigger=provider-*`, selected Gemini, 기존 `work_unit_id`, used/promotion/history 보존, 정상 sibling 불변을 검증하지 않는다.

### 심볼 참조와 범위

- rename/remove 없음.
- 영향 경계: `StateStore.mark_retry_quota_refresh`, `build_admission_batch_snapshot`, `persisted_execution_decision`, `commit_execution_decision`, `dispatch_with_store`, `run_worker`.
- `select_execution_target.py`의 공개 decision schema와 route matrix는 변경하지 않는다. dispatcher가 기존 `transition="failover"`와 qualified `failure_class` 입력을 정확히 전달한다.
- global quota cache, target semaphore, reverse failover, generic stderr 추론, 새 initial route 생성은 범위 밖이다.
- unrelated dirty worktree와 sibling task artifact는 수정하지 않는다.

### 분할 판단

- retry context 생성, admission snapshot, worker decision commit과 dispatcher-path regression은 동일 task-local state 수명의 한 원자적 변경이다. production/test를 분리하면 exactly-once intent 소비와 sibling isolation evidence가 끊기므로 단일 plan을 유지한다.
- 선행 index `10`은 archived `complete.log`로 충족됐다.

### 최종 라우팅

- evaluation_mode=`isolated-reassessment`; prior route는 입력으로 재사용하지 않았다.
- finalizer=`finalize-task-policy.sh`, finalizer_mode=`pair`; build/review status=`routed`.
- build/review closures: `scope_closed=true`, `context_closed=true`, `verification_closed=true`, `evidence_trusted=true`, `ownership_closed=true`, `decision_closed=true`.
- build route_basis=`local-fit`; capability_gap=`none`.
- loop-risk observations: ordered transitions=`blocked → retry context → fresh snapshot → qualified failover → commit/consume`; actors=`scheduler admission + worker StateStore commit`; components=`StateStore + admission provider + selector handoff + runner`; variants=`blocked/normal`, `night local/cloud alternate`, `explicit retry/ordinary resume/new generation`. matched=`temporal_state`, `concurrent_consistency`, `boundary_contract`, `variant_product`; triggered=`true`, 라우팅 점수에는 영향 없음.
- build scores(scope_coupling/state_concurrency/blast_irreversibility/evidence_diagnosis/verification_complexity)=`1/2/1/1/2`, final=`local-G07`, filename=`PLAN-local-G07.md`.
- review route_basis=`official-review`; official execution=`codex/gpt-5.6-sol xhigh`; review scores=`1/2/1/1/2`, final=`cloud-G07`, filename=`CODE_REVIEW-cloud-G07.md`.

## 구현 체크리스트

- [ ] blocked worker의 qualified failure/locator와 pinned decision identity를 task-local retry context로 보존하고 fresh snapshot을 기존 failover 입력에 전달한다.
- [ ] retry context는 successful decision commit 뒤 exactly once로 소비하며 정상 sibling, ordinary resume, 새 generation의 state/probe 규칙을 보존한다.
- [ ] 고정 야간 실제 dispatcher 경계 회귀와 subprocess/provider deny assertion으로 Laguna→Gemini failover를 증명한다.
- [ ] focused retry/throughput, `BlockerDrainTest`, 전체 Python suite, `py_compile`, `git diff --check`를 통과한다.
- [ ] `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 원문 출력으로 채운다.

## 구현 항목

### [REVIEW_REVIEW_REVIEW_REFACTOR-1] Structured retry context와 qualified failover handoff

- 문제: fresh alternate snapshot은 생성되지만 prior blocker의 failure class/locator가 지워져 worker decision이 `failover`가 아닌 `resume`으로 commit된다.
- 해결 방법:
  - blocked state 해제 전에 worker role, qualified failure class, locator, selected target, work unit identity를 task-local structured retry context로 보존한다. 기존 문자열 blocker만 재파싱하는 방식보다 blocker 생성 시점의 typed evidence를 우선한다.
  - pending admission은 prior decision의 아직 사용하지 않은 cloud candidate만 probe하고 normal sibling의 marker/quota/decision을 바꾸지 않는다.
  - `run_worker`는 pending context와 fresh snapshot이 있으면 기존 decision에 `transition="failover"`, 보존한 qualified failure class를 전달한다. selector가 만든 decision의 work unit, used candidates, promotion path와 history 계약을 그대로 commit한다.
  - fresh decision commit이 성공한 뒤에만 retry context/marker를 exactly once로 지운다. cloud quota 대상이 없는 retry는 명시적인 no-refresh resume decision commit으로 정리하고 latent marker를 남기지 않는다.
- 수정 파일 및 체크리스트:
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`: structured retry context 생성·조회·소비와 blocked-only scope를 구현한다.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`: fresh batch snapshot과 qualified failover를 worker decision 경계에서 연결한다.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: retry commit 전후 context, marker, work unit, used/history와 sibling 불변을 검증한다.
- 테스트 작성: 작성. 고정 야간 `local-G08` Laguna blocker가 정확히 Gemini alternate 1개를 probe하고 existing work unit의 qualified failover로 Gemini를 선택하는 dispatcher-path 회귀를 작성한다.
- 중간 검증: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py ThroughputQuotaBatchTest.test_retry_blocked_scopes_to_blocked_worker_and_refreshes_pinned_alternate -v` — PASS.

### [REVIEW_REVIEW_REVIEW_REFACTOR-2] 실제 dispatcher 경계와 외부 호출 0 evidence

- 문제: helper-only 회귀와 예외를 삼키는 deny patch 때문에 green test가 production 경계를 증명하지 못한다.
- 해결 방법:
  - 기존 context-budget integration의 최초 decision에 deterministic batch snapshot을 주입하고 `selector.subprocess.run` 호출 수 0을 context 종료 후 assertion한다.
  - retry 회귀는 `dispatch_with_store(..., retry_blocked=True)`를 실제 호출하고 고정 KST 야간, blocked Laguna task와 normal sibling, deterministic quota provider/runner를 사용한다.
  - exact probe key=`agy/Gemini 3.6 Flash (Medium)`, probe 1회, selected Gemini, failover trigger/failure class, work unit/used/history 보존, normal sibling state 불변을 assertion한다.
  - 다음 ordinary resume은 probe 0회, 새 generation은 fresh initial probe 규칙을 유지한다. `invoke`, `build_command`, host quota subprocess는 모두 0회이고 fake runner/provider 호출 수는 기대값과 정확히 일치해야 한다.
- 수정 파일 및 체크리스트:
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: `test_context_budget_and_retry_blocked_lifecycle`에 deterministic snapshot과 post-context deny assertion을 추가한다.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: helper-only retry 회귀를 실제 dispatcher-path 조합 회귀로 교체 또는 보강한다.
- 테스트 작성: 작성. 실제 provider CLI/session/network와 host `iop-node`는 실행하지 않는다.
- 중간 검증: `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py ThroughputQuotaBatchTest SelectorDispatcherIntegrationTest.test_context_budget_and_retry_blocked_lifecycle -v` — PASS.

## 의존 관계 및 구현 순서

1. REVIEW_REVIEW_REVIEW_REFACTOR-1에서 retry context와 failover commit 경계를 닫는다.
2. REVIEW_REVIEW_REVIEW_REFACTOR-2에서 실제 dispatcher-path deterministic evidence를 고정한다.
3. 전체 회귀로 SDD S11, unknown isolation과 독립 branch drain을 확인한다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py` | REVIEW_REVIEW_REVIEW_REFACTOR-1 |
| `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py` | REVIEW_REVIEW_REVIEW_REFACTOR-1, REVIEW_REVIEW_REVIEW_REFACTOR-2 |

## 최종 검증

- `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py ThroughputQuotaBatchTest SelectorDispatcherIntegrationTest.test_context_budget_and_retry_blocked_lifecycle -v` — retry/ordinary/new-generation, exact alternate probe/failover와 모든 deny guard PASS.
- `python3 agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py BlockerDrainTest -v` — 독립 branch drain 회귀 PASS.
- `python3 -m unittest discover -s agent-ops/skills/project/orchestrate-agent-task-loop/tests -p 'test_*.py'` — fresh process 전체 Python suite PASS.
- `python3 -m py_compile agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py` — exit 0.
- `git diff --check` — 출력 없음.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다.
