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

# Plan - Retry 대상 격리와 pinned alternate quota 재평가

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

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

## 배경

두 번째 리뷰에서 explicit retry의 probe 자체는 cloud-primary 단일 task에서 복구됐지만, refresh marker가 정상 task까지 전파되고 pinned local-primary 뒤의 cloud alternate는 probe되지 않는 수명 결함이 확인됐다. 제출된 통합 테스트도 quota subprocess deny 아래에서 호출 1회를 만들고 production 예외 정규화가 이를 삼켜 PASS했다. 실제 blocked worker만 fresh snapshot을 소비하고 pinned candidate/history를 유지한 채 alternate eligibility를 재평가하도록 경계를 닫는다.

## Archive Evidence Snapshot

- Prior plan: `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/plan_local_G07_1.log`
- Prior review: `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/code_review_cloud_G07_1.log`
- Verdict: `FAIL`
- Required:
  - selected group의 정상 resume task까지 retry refresh marker와 probe가 전파되고, canonical 야간 `local-G08`의 pinned Laguna 뒤 Gemini alternate는 first-local break 때문에 `probe_calls=[]`, `batch_snapshot=None`이 됨.
  - `test_retry_blocked_quota_refresh_lifecycle`가 helper 단일 cloud task만 검증하고, focused command의 `test_context_budget_and_retry_blocked_lifecycle`는 quota subprocess deny patch 아래에서도 호출 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 blocked/normal 복수 task 재현은 Claude와 Codex가 함께 probe되는 전파를 확인했고, 야간 Laguna retry 재현은 pending marker가 있어도 0 probe임을 확인했다. 제출 명령의 focused 12개, BlockerDrain 6개, 전체 Python 250개는 PASS했으나 deny 재현에서 integration quota subprocess 호출 1회가 별도로 확인됐다.
- 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_1.log`
- `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/code_review_cloud_G07_1.log`
- `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/plan_local_G07_0.log`
- `agent-task/m-agent-task-runtime-target-selector/11+10_unknown_isolation/code_review_cloud_G07_0.log`
- `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`
- `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/select_execution_target.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 없음.

### SDD 기준

- SDD: `agent-roadmap/sdd/automation-runtime-bridge/agent-task-runtime-target-selector/SDD.md`, 상태 `[승인됨]`, 잠금 `해제`.
- 대상 Acceptance Scenario: S11 → Milestone Task `throughput-policy`.
- Evidence Map: target cap 없는 admission, 같은 batch snapshot 재사용, 조회 실패의 `unknown` 격리 evidence.
- State Machine의 `retry_blocked`는 blocker와 stage budget만 초기화하고 route history를 보존하며, quota exhaustion으로 대기하던 단방향 failover eligibility를 새 snapshot으로 재평가할 수 있어야 한다. 구현 체크리스트는 blocked-only refresh와 pinned unused alternate probe를, 최종 검증은 야간 local-primary/cloud-alternate와 정상 sibling 격리 조합을 직접 요구한다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`를 읽었다.
- testing domain의 `agent-test/local/testing-smoke.md`를 읽었다. 이 변경은 Edge/Node user-flow가 아니라 agent-task dispatcher 내부 persisted-state 회귀이므로 dev helper, mock Edge-Node smoke, live provider preflight는 적용하지 않는다.
- dispatcher unit/integration simulation은 실제 provider CLI, provider session, quota subprocess와 network를 실행하지 않고 가장 높은 runner/probe seam을 deterministic fake로 차단한다.
- 현재 checkout의 Python test manifest를 직접 근거로 focused `unittest`, 전체 discovery, `py_compile`, `git diff --check`를 사용한다. test-rule 유지보수는 필요하지 않다.
- 리뷰 재실행 기준선: focused 12개 PASS, BlockerDrain 6개 PASS, 전체 Python 250개 PASS. 이 성공만으로 verification trust 결함은 해소되지 않으며 deny guard 호출 수가 0이어야 한다.

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

- 기존 `test_retry_blocked_quota_refresh_lifecycle`는 `cloud-G07` 단일 state helper 호출만 다뤄 실제 `dispatch_with_store(..., retry_blocked=True)` scope, 정상 sibling 비전파, local-primary 뒤 cloud alternate probe와 failover 소비를 검증하지 않는다.
- `test_context_budget_and_retry_blocked_lifecycle`는 최초 daytime `local-G08` decision에 deterministic quota snapshot을 주입하지 않아 실제 `selector.subprocess.run`을 1회 호출하고 예외가 삼켜져도 PASS한다.
- 일반 resume 0 probe, 새 generation fresh probe, batch-key unknown, task-local derived exhausted, 독립 branch drain은 기존 테스트가 커버하므로 보존한다.

### 심볼 참조

- rename/remove 없음.
- 영향 호출 경계: `StateStore.mark_retry_quota_refresh`, `retry_quota_refresh_pending`, `build_admission_batch_snapshot`, `persisted_execution_decision`, `commit_execution_decision`, `dispatch_with_store`, `run_worker`, `run_escalating`.

### 분할 판단

- split decision policy를 먼저 평가했다. 이 follow-up은 기존 `11+10_unknown_isolation` subtask 안에서 retry intent 생성, admission key 수집, decision 소비와 동일 테스트 fixture를 하나의 persisted-state 수명으로 닫는다.
- shared API와 broad call-site rollout, 다른 domain/ownership, 별도 배포, 상이한 검증 profile이 없고 production/test를 분리하면 blocked-only marker와 alternate snapshot 소비 evidence가 끊기므로 단일 plan을 유지한다.
- 선행 index `10`은 기존 archived sibling의 `complete.log`로 충족된 상태를 유지한다.

### 범위 결정 근거

- `select_execution_target.py`의 공개 transition/schema, route matrix, provider adapter, roadmap/SDD, target cap 정책은 변경하지 않는다. 기존 `initial | resume | failover` 계약 안에서 dispatcher가 structured retry evidence와 fresh snapshot을 올바른 transition에 전달한다.
- global quota cache, target별 semaphore, generic stderr 추론, used target bounce 또는 새 initial route 생성은 추가하지 않는다.
- unrelated dirty worktree와 sibling task artifacts는 수정하지 않는다.

### 최종 라우팅

- evaluation_mode: `isolated-reassessment`.
- finalizer=`finalize-task-policy.sh`, finalizer_mode=`pair`; build/review status=`routed`.
- build closures: `scope_closed=true`(두 Required와 blocked/normal 및 local/cloud variant가 고정됨), `context_closed=true`(StateStore, admission, selector handoff, runner와 단일 test module로 닫힘), `verification_closed=true`(focused deterministic reproducer와 전체 suite 명령이 확정됨), `evidence_trusted=true`(새 pass 조건은 subprocess/runner deny 호출 0회를 직접 assertion함), `ownership_closed=true`(dispatcher task-local state만 수정), `decision_closed=true`(승인 SDD의 retry/failover 계약으로 결정 가능).
- 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`.
- build loop-risk observations: ordered transitions=`blocked → retry-intent → fresh snapshot → pinned failover/resume → intent consumed`; concurrent actors=`scheduler admission + worker StateStore commit`; boundary components=`StateStore + admission batch provider + selector handoff + runner`; variant axes=`blocked/normal`, `local-primary/cloud-primary`, `explicit retry/ordinary resume/new generation`. matched signatures=`temporal_state`, `concurrent_consistency`, `boundary_contract`, `variant_product`; triggered=`true`이며 lane/G에 영향을 주지 않는다.
- 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`; capability_gap=`none`.
- review scores(scope_coupling/state_concurrency/blast_irreversibility/evidence_diagnosis/verification_complexity)=`1/2/1/1/2`, final=`cloud-G07`, filename=`CODE_REVIEW-cloud-G07.md`.

## 구현 체크리스트

- [ ] `--retry-blocked`는 실제 blocked worker에만 one-shot refresh intent를 만들고 정상 sibling의 persisted route/quota evidence를 바꾸지 않으며, pinned decision의 아직 사용하지 않은 cloud alternate가 local 후보 뒤에 있어도 fresh snapshot으로 eligibility를 재평가해 기존 `failover` transition에 전달한다.
- [ ] retry 회귀는 dispatcher CLI 경계에서 blocked/normal 복수 task, 야간 Laguna→Gemini alternate, ordinary resume과 새 generation을 조합하고 모든 quota subprocess/provider runner seam의 호출 수를 deterministic fake/deny guard로 assertion한다.
- [ ] focused retry/throughput, `BlockerDrainTest`, 전체 Python suite, `py_compile`, `git diff --check`를 실행해 unknown 격리, pinned history와 독립 branch drain을 보존한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

## 구현 항목

### [REVIEW_REVIEW_REFACTOR-1] Blocked-only retry intent와 pinned alternate snapshot 소비

- 문제: `dispatch.py:580`은 선택 group의 모든 persisted state에 `retry_quota_refresh_pending=True`를 기록하고, `dispatch.py:1647-1668`은 pending task도 현재 policy 후보를 훑다가 첫 local candidate에서 중단한다. 그 결과 정상 sibling이 불필요한 probe/evidence 갱신에 포함되고, SDD가 직접 요구한 야간 pinned Laguna 뒤 Gemini alternate 회복은 새 snapshot을 얻지 못한다.
- 해결 방법:

```python
# Before: agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py:580,1647
store.mark_retry_quota_refresh(args.task_group)
for task, stage in ready_items:
    state = store.peek_task_state(task)
    if has_persisted_worker_decision(state, task) and not retry_quota_refresh_pending(state):
        continue
    for cand in policy_mod.select_policy(...).candidates:
        if cand.execution_class == "local_model":
            break
```

```python
# After
retry_targets = store.retry_blocked_tasks(args.task_group)
for task, stage in ready_items:
    state = store.peek_task_state(task)
    if retry_quota_refresh_pending(state):
        candidates = persisted_unused_retry_candidates(state)
    elif has_persisted_worker_decision(state, task):
        continue
    else:
        candidates = policy_mod.select_policy(...).candidates
    # pending retry scans the pinned unused candidates, including cloud
    # alternates after a local selected target; nonblocked siblings are untouched.
```

  Retry 시점의 structured blocker/failure evidence를 marker와 함께 보존하고, fresh snapshot이 있으면 pinned `work_unit_id`, selected/used candidates와 transition history를 초기화하지 않은 채 기존 qualified `failover` 입력으로 전달한다. Snapshot을 실제 decision/state에 commit한 뒤에만 해당 task의 intent를 지우며, quota 대상이 없는 blocked task는 명시적으로 no-refresh resume으로 정리해 marker가 영구 잔류하지 않게 한다.
- 수정 파일 및 체크리스트:
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`: blocker가 실제 존재하는 selected worker state만 retry intent로 표시하고 정상 sibling은 clear/reset 또는 quota refresh 대상에서 제외한다.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`: pending retry의 probe key를 현재 시간 policy가 아니라 persisted candidate/used history에서 만들고 local 뒤 unused cloud alternate도 포함한다.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py`: fresh snapshot을 qualified failover/resume 경계에 전달하고 successful commit에서 exactly-once로 intent를 소비하며 route/history를 보존한다.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: blocked/normal 서로 다른 target과 야간 Laguna/Gemini alternate의 state/snapshot/transition을 검증한다.
- 테스트 작성: 작성. `ThroughputQuotaBatchTest.test_retry_blocked_scopes_to_blocked_worker_and_refreshes_pinned_alternate`에서 group retry의 blocked-only marker, normal task 0 probe/evidence 불변, 야간 alternate 1 probe, fresh snapshot id, selected failover target, used/history 보존과 다음 ordinary resume 0 probe를 assertion한다.
- 중간 검증: `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` — 두 focused reproducer가 닫히고 PASS.

### [REVIEW_REVIEW_REFACTOR-2] Dispatcher retry 검증의 subprocess/provider 완전 격리

- 문제: `test_dispatch.py:7838`의 `test_context_budget_and_retry_blocked_lifecycle`는 최초 daytime cloud decision에 deterministic quota evidence가 없어 실제 `subprocess.run`을 호출하며, `test_dispatch.py:8887`은 helper 직접 호출만으로 CLI retry 수명을 주장해 scope/alternate 결함을 놓친다.
- 해결 방법:

```python
# Before: agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py:7847
initial, spec = dispatch.persisted_execution_decision(
    store, task, stage="worker", evaluated_at=daytime
)
```

```python
# After
with mock.patch.object(
    selector.subprocess,
    "run",
    side_effect=AssertionError("unexpected quota subprocess"),
) as quota_subprocess:
    initial, spec = dispatch.persisted_execution_decision(
        store,
        task,
        stage="worker",
        evaluated_at=daytime,
        quota_snapshot=deterministic_snapshot,
    )
quota_subprocess.assert_not_called()
```

  실제 CLI 경계는 `dispatch_with_store(..., retry_blocked=True)`와 fake `scan_tasks`/quota provider/runner를 사용한다. Production이 deny 예외를 삼켜도 test가 통과하지 않도록 context 전체가 끝난 뒤 subprocess와 provider runner call count를 별도로 assertion한다.
- 수정 파일 및 체크리스트:
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: `test_context_budget_and_retry_blocked_lifecycle`의 최초 decision과 retry 단계에 deterministic snapshot/deny guard를 적용한다.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: helper-only retry 테스트를 CLI-path 복수 task/야간 alternate 회귀로 교체하거나 보강한다.
  - [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py`: `invoke`, `build_command`, quota subprocess와 quota probe 각각의 기대 호출 횟수를 assertion한다.
- 테스트 작성: 작성. 기존 두 named test를 강화하고 REVIEW_REVIEW_REFACTOR-1의 조합 회귀를 추가한다. 실제 provider CLI, provider 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` — retry/ordinary/new-generation 조합과 모든 deny guard PASS.

## 의존 관계 및 구현 순서

1. REVIEW_REVIEW_REFACTOR-1에서 structured retry intent와 admission/decision 소비를 먼저 닫는다.
2. REVIEW_REVIEW_REFACTOR-2에서 production 경계를 통과하는 deterministic 회귀를 고정한다.
3. 전체 회귀로 SDD S11과 독립 branch drain을 확인한다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/dispatch.py` | REVIEW_REVIEW_REFACTOR-1 |
| `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_dispatch.py` | REVIEW_REVIEW_REFACTOR-1, 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` — blocked-only retry, pinned alternate refresh, ordinary resume/new generation, unknown 격리와 subprocess/runner 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`의 구현 에이전트 소유 섹션을 채운다.
