<!-- task=m-iop-agent-cli-runtime/04+01,02,03_task_manager plan=1 tag=REVIEW_API -->

# AgentTaskManager lifecycle·lease·identity 보정 계획

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

구현과 검증을 모두 마친 뒤 `CODE_REVIEW-cloud-G06.md`의 구현 에이전트 소유 섹션에 실제 변경 내용과 명령별 stdout/stderr를 채운다. active pair는 그대로 두고 리뷰 준비 완료를 보고한다. blocker가 생기면 정확한 원인, 시도한 명령과 출력, 재개 조건만 구현 소유 evidence에 기록한다. 사용자에게 질문하거나 user-input 도구·control-plane stop 파일을 만들거나 다음 상태를 분류하지 않으며, log archive와 `complete.log`는 코드리뷰 에이전트에게 맡긴다.

## 배경

첫 리뷰에서 fresh/race suite는 통과했지만 explicit stop 지속성, CAS 충돌 시 lease 승자 판정, durable idempotency/event identity의 충돌 방지가 깨지는 Required 3건이 확인됐다. 이 후속은 공통 `AgentTaskManager`의 lifecycle과 at-most-once 외부 호출 불변식을 한 번에 보정하고 S03/S16 증거를 다시 닫는다.

## Archive Evidence Snapshot

- 현재 loop evidence:
  - `agent-task/m-iop-agent-cli-runtime/04+01,02,03_task_manager/plan_cloud_G10_0.log`
  - `agent-task/m-iop-agent-cli-runtime/04+01,02,03_task_manager/code_review_cloud_G10_0.log`
- verdict: `FAIL`
- findings: Required 3, Suggested 0, Nit 0.
  - stopped project가 다음 reconcile에서 재활성화된다.
  - CAS loser가 project/integration lease claim 성공을 반환할 수 있다.
  - raw delimiter 기반 idempotency/event key가 충돌하고 logical event discriminator가 부족하다.
- affected files: `packages/go/agenttask/{types,manager,workflow,intent}.go`, 관련 `*_test.go`, `agent-contract/inner/agent-runtime.md`.
- reviewer verification:
  - fresh/race `./packages/go/agenttask/...`와 `agentruntime`/`agentprovider`/`agentguard` suite는 PASS했다.
  - 집중 재현 `TestReviewProbeLifecycleAndLeaseInvariants`는 stop 직후 provider invocation 1회와 CAS loser claim 성공을 각각 재현해 FAIL했다. 재현용 임시 파일은 제거됐다.
  - predecessor `complete.log` 3개, common manager 단일 구현, Python production/fallback 참조 0개를 재확인했다.
- roadmap carryover: `common-runtime`, `task-manager`; SDD S03/S16와 Evidence Map을 그대로 완료 대상으로 유지한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md)
- Task ids:
  - `common-runtime`: AgentTaskManager를 포함한 공통 runtime 단일 구현 완성
  - `task-manager`: 수동 start/auto-resume/dependency-ready isolated dispatch/review/serial integration
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- 규칙/설계: `agent-ops/rules/project/rules.md`, `agent-ops/rules/common/rules-roadmap.md`, `agent-ops/rules/common/rules-agent-spec.md`, `agent-ops/rules/project/domain/platform-common/rules.md`, `agent-ops/rules/project/domain/node/rules.md`, `agent-ops/rules/project/domain/testing/rules.md`, `agent-roadmap/current.md`, `agent-roadmap/phase/automation-runtime-bridge/PHASE.md`, `agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md`, `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-cli-runtime/SDD.md`.
- 계약/스펙: `agent-contract/index.md`, `agent-contract/inner/agent-runtime.md`, `agent-spec/index.md`, `agent-spec/runtime/edge-node-execution.md`, `agent-spec/runtime/provider-pool-config-refresh.md`.
- AgentTask source: `packages/go/agenttask/types.go`, `ports.go`, `manager.go`, `state_machine.go`, `intent.go`, `workflow.go`, `dependency.go`, `scheduler.go`, `reconcile.go`, `dispatch.go`, `review.go`, `followup.go`, `integration.go`, `integration_queue.go`.
- AgentTask tests: `packages/go/agenttask/test_support_test.go`, `manager_test.go`, `manager_integration_test.go`, `state_machine_test.go`, `dependency_test.go`, `scheduler_test.go`, `review_test.go`, `integration_queue_test.go`.
- Guard 연결: `packages/go/agentguard/types.go`, `packages/go/agentguard/permit.go`.
- 테스트 규칙: `agent-test/local/rules.md`, `agent-test/local/platform-common-smoke.md`, `agent-test/local/node-smoke.md`, `agent-test/local/testing-smoke.md`.
- archive evidence: `agent-task/m-iop-agent-cli-runtime/04+01,02,03_task_manager/plan_cloud_G10_0.log`, `agent-task/m-iop-agent-cli-runtime/04+01,02,03_task_manager/code_review_cloud_G10_0.log`.

### SDD 기준

- SDD: `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-cli-runtime/SDD.md`, 상태 `[승인됨]`, 잠금 `해제`.
- 대상 S03/`task-manager`: manual start, explicit stop, default auto-resume와 local override를 durable state로 구분하고 미선택/중단 project를 임의 실행하지 않아야 한다.
- 대상 S16/`task-manager`: explicit predecessor만 gate로 쓰며 admitted sibling을 provider capacity와 task isolation 안에서 병렬 dispatch한다.
- S01/`common-runtime`의 단일 manager와 stable lifecycle/identity 계약도 유지한다.
- Evidence Map의 S03 no-unselected-start/default-resume trace와 S16 dependency-only/parallel trace에 stop 지속성, CAS conflict, collision-free replay 회귀를 추가해 완료 evidence를 보강한다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`와 platform-common/node/testing profile을 읽었다.
- 실제 checkout은 `/config/workspace/iop-s0`, host Go는 `/config/.local/bin/go` → `/config/opt/go/bin/go`, `go1.26.2 linux/arm64`, `GOROOT=/config/opt/go`다.
- follow-up 변경은 host-neutral `packages/go/agenttask` durable state/API와 계약에 한정되고 Node wire/config/user entrypoint를 바꾸지 않으므로 외부 provider, Edge-Node full-cycle, proto 생성은 필수 범위가 아니다.
- fresh package tests는 `-count=1`, concurrency/state tests는 `-race -count=1`을 사용한다. `go vet`, deterministic `rg --sort path`, `git diff --check`를 보조 oracle로 사용하며 cache 결과만으로 통과 처리하지 않는다.
- profile의 `<확인 필요>` 값이나 구조적 공백은 없다. 원격/field preflight는 적용하지 않는다.

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

- 기존 `TestManualStartStopCancelsInvocationAndReleasesCapacity`는 같은 reconcile의 in-flight cancel만 확인하고 다음 reconcile에서 stop이 유지되는지 검증하지 않는다.
- `TestInterruptedResumeOverrideFalseStops`는 stop 직후 상태만 확인하며 새 explicit start/resume이 stopped stage를 안전하게 복구하는지 검증하지 않는다.
- duplicate lease test는 이미 저장된 foreign lease만 확인하고 CAS retry 사이에 owner가 바뀌는 경쟁을 검증하지 않는다.
- idempotency/event tests는 delimiter variant, 서로 다른 command, integration attempt/change-set과 replay 안정성을 검증하지 않는다.
- `EventAutoResume` 상수는 있지만 실제 emission 회귀 검증이 없다.

### 심볼 참조

- rename/remove 예정 심볼은 없다.
- public `Event`에 durable discriminator field를 추가하면 `packages/go/agenttask` 내부 event literal과 테스트 call site를 모두 갱신한다. 외부 call site는 현재 검색 결과 없다.

### 분할 판단

- 단일 plan을 유지한다. stop/resume stage 복구, committed lease decision, collision-free external/event identity는 동일 durable state transition과 replay/at-most-once 불변식을 공유해 하나만 고치면 중간 상태가 안전하게 PASS하지 않는다.
- split predecessor는 모두 archive `complete.log`로 충족됐다.
  - `01`: `agent-task/archive/2026/07/m-iop-agent-cli-runtime/01_common_runtime_node_bridge/complete.log`
  - `02`: `agent-task/archive/2026/07/m-iop-agent-cli-runtime/02+01_provider_catalog/complete.log`
  - `03`: `agent-task/archive/2026/07/m-iop-agent-cli-runtime/03+01,02_guardrail_admission/complete.log`

### 범위 결정 근거

- 실제 overlay/worktree/clone materialization과 three-way apply backend는 S18/S19 owner이므로 변경하지 않는다.
- provider catalog/CLI, Node bridge, Edge wire/config, Python dispatcher와 `iop-agent` CLI surface는 이번 세 Required의 직접 수정 경로가 아니므로 건드리지 않는다.
- manager public lifecycle, durable event/identity 의미와 테스트만 보정한다. 새로운 persistence backend나 dependency framework를 추가하지 않는다.

### 최종 라우팅

- `evaluation_mode=isolated-reassessment`, finalizer=`finalize-task-policy.sh`, mode=`pair`.
- closures(build/review): `scope_closed=true`, `context_closed=true`, `verification_closed=true`, `evidence_trusted=true`, `ownership_closed=true`, `decision_closed=true`.
- build scores: scope=1, state=2, blast=1, evidence=1, verification=1 → `G06`; base=`local-fit`, route=`risk-boundary`, lane=`cloud`.
- review scores: scope=1, state=2, blast=1, evidence=1, verification=1 → `G06`; route=`official-review`, lane=`cloud`.
- `large_indivisible_context=false`.
- positive loop risks: `temporal_state`, `concurrent_consistency`, `boundary_contract`, `structured_interpretation`; count=4.
- recovery: `review_rework_count=1`, `evidence_integrity_failure=false`; recovery boundary=false.
- capability gap: 없음.
- canonical files: `PLAN-cloud-G06.md`, `CODE_REVIEW-cloud-G06.md`.

## 구현 체크리스트

- [ ] REVIEW_API-1 explicit stop을 reconcile 경계에서 보존하고 새 manual start/resume만 stopped stage를 복구하게 한다.
- [ ] REVIEW_API-2 CAS retry의 provisional 결과를 폐기하고 committed project/integration lease claim과 workflow activation만 반환한다.
- [ ] REVIEW_API-3 external idempotency key와 EventID를 collision-free canonical identity로 만들고 command/resume/integration discriminator를 보존한다.
- [ ] REVIEW_API-4 lifecycle·CAS·identity 회귀와 전체 fresh/race/contract 검증을 통과시킨다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

## 의존 관계 및 구현 순서

1. 위 predecessor `complete.log` 3개를 유지한다.
2. REVIEW_API-1의 durable stop/resume stage → REVIEW_API-2의 committed mutation result → REVIEW_API-3의 canonical identity/event → REVIEW_API-4 전체 검증 순서로 진행한다.

### [REVIEW_API-1] Explicit stop과 manual resume stage

- 문제: `packages/go/agenttask/workflow.go:86-133`은 `ProjectStatusStopped`를 별도 gate로 보존하지 않고 intent가 있으면 project를 다시 running으로 만든다. `manager.go:152-171`은 work를 `stopped`로 바꾸면서 원래 crash/replay stage를 잃는다.
- 해결 방법: project explicit-stop을 durable gate로 두고, work별 pre-stop stage를 보존한다. `StopProject`는 stage와 cancel intent를 기록하고 다음 reconcile은 stopped project를 관측만 한다. 새 command의 `StartProject`만 selected Milestone의 stopped work를 `observed|ready|reviewing|pending_integration` 중 안전한 replay stage로 복구한다.

```go
// Before: workflow.go:86-133
interrupted := project.Status == ProjectStatusRunning
// ...
project.Status = ProjectStatusRunning
activated = true

// After
if project.Status == ProjectStatusStopped {
    return committedInactive, nil
}
if project.Status == ProjectStatusStarted {
    recoverExplicitlyStartedWorks(&project)
}
project.Status = ProjectStatusRunning
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/agenttask/types.go`: stopped work의 durable resume stage 또는 동등한 explicit-stop state를 추가한다.
  - [ ] `packages/go/agenttask/manager.go`: `StartProject`/`StopProject`가 explicit stop과 새 command resume를 원자적으로 기록한다.
  - [ ] `packages/go/agenttask/workflow.go`, `state_machine.go`: stopped project gate와 stage별 replay transition을 구현한다.
  - [ ] completed/terminal-deferred work를 임의 재실행하지 않고 attempt/ordinal/artifact identity를 보존한다.
- 테스트 작성: `manager_test.go`에 `TestStopProjectPersistsUntilExplicitRestart`, `TestStoppedWorkResumesFromDurableStage`, `TestAutoResumeOverrideFalseRequiresExplicitRestart`를 추가한다. stop-before-observation, in-flight stop 뒤 추가 reconcile 0 invocation, 새 command 이후 exactly-once completion을 검증한다.
- 중간 검증:

```bash
/config/.local/bin/go test -race -count=1 ./packages/go/agenttask -run 'Test(StopProjectPersists|StoppedWorkResumes|AutoResumeOverrideFalseRequires)'
```

### [REVIEW_API-2] CAS committed decision과 lease 승자

- 문제: `packages/go/agenttask/intent.go:8-31`, `47-66`의 `claimed`와 `workflow.go:48-145`의 `activated`는 CAS 실패 시도에서 바뀐 closure 값을 다음 retry에 남긴다. 최종 committed state가 foreign lease/stopped project여도 caller가 성공으로 진행할 수 있다.
- 해결 방법: `mutate`의 각 retry가 provisional decision을 만들고 CAS 성공 시도에서만 그 결과를 반환하는 내부 helper를 추가한다. project claim, integration claim, workflow activation/auto-resume 판정을 이 helper로 옮기고 CAS conflict에서는 이전 decision/event를 폐기한다.

```go
// Before: intent.go:12-31
err := m.mutate(ctx, func(state *ManagerState) error {
    // ...
    claimed = true
    return nil
})
return claimed, err

// After
return m.mutateDecision(ctx, func(state *ManagerState) (bool, error) {
    if foreignLeaseIsLive(state) {
        return false, nil
    }
    persistOwnedLease(state)
    return true, nil
})
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/agenttask/manager.go`: CAS 성공 시도만 decision을 publish하는 helper를 구현한다.
  - [ ] `packages/go/agenttask/intent.go`: project/integration claim을 committed decision으로 전환한다.
  - [ ] `packages/go/agenttask/workflow.go`: activation과 auto-resume event 판정도 committed result만 사용한다.
  - [ ] `packages/go/agenttask/test_support_test.go`: 첫 CAS 사이에 foreign owner가 lease/status를 commit하는 deterministic store fixture를 추가한다.
- 테스트 작성: `manager_test.go`와 `integration_queue_test.go`에 `TestClaimProjectCASConflictReturnsCommittedDecision`, `TestClaimIntegrationCASConflictReturnsCommittedDecision`, `TestWorkflowActivationCASConflictUsesCommittedState`를 추가하고 provider/integrator 호출 0회를 검증한다.
- 중간 검증:

```bash
/config/.local/bin/go test -race -count=1 ./packages/go/agenttask -run 'Test(ClaimProjectCASConflict|ClaimIntegrationCASConflict|WorkflowActivationCASConflict)'
```

### [REVIEW_API-3] Collision-free idempotency와 event identity

- 문제: `packages/go/agenttask/manager.go:217-223`, `254-275`는 validation이 허용하는 `/`·`#` 포함 ID를 raw delimiter로 조합한다. 다른 durable tuple이 같은 dispatch/review/integration/EventID가 될 수 있고 manual command와 integration revision 구분도 event에 없다.
- 해결 방법: versioned length-prefix 또는 canonical structured bytes를 digest하는 단일 encoder를 사용한다. attempt, dispatch, review, integration, event identity를 모두 같은 injective 규칙으로 생성하고 `Event`에 `CommandID`, workflow revision, integration attempt/change-set 등 logical discriminator를 추가한다. auto-resume는 committed transition 뒤 `EventAutoResume`으로 한 번 emit한다.

```go
// Before: manager.go:258-264
return fmt.Sprintf("dispatch/%s/%s/%d", projectID, workID, attempt)

// After
return durableIdentity("dispatch-v1",
    string(projectID), string(workID), strconv.FormatUint(uint64(attempt), 10))
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/agenttask/manager.go`, `types.go`: canonical encoder와 event discriminator를 구현한다.
  - [ ] `packages/go/agenttask/workflow.go`, `review.go`, `integration_queue.go`: 각 event caller가 stable command/revision/attempt/change-set identity를 전달한다.
  - [ ] `agent-contract/inner/agent-runtime.md`: event/idempotency identity가 collision-free canonical tuple이고 replay 시 동일하다는 기준을 명시한다.
  - [ ] delimiter를 금지해 기존 valid identity를 축소하는 방식은 사용하지 않는다.
- 테스트 작성: `state_machine_test.go`에 `TestDurableIdentityEncodingIsInjective`, `TestEventIdentityDistinguishesCommandsAndReplays`, `TestInterruptedResumeEmitsStableEvent`를 추가한다. delimiter variant는 서로 다르고 같은 tuple replay는 같으며 서로 다른 command/change-set은 다른 ID임을 검증한다.
- 중간 검증:

```bash
/config/.local/bin/go test -race -count=1 ./packages/go/agenttask -run 'Test(DurableIdentityEncoding|EventIdentityDistinguishes|InterruptedResumeEmits)'
```

### [REVIEW_API-4] 통합 회귀와 evidence 재확정

- 문제: 기존 suite는 정상 progression 중심이라 이번 세 race/lifecycle boundary를 통과해도 S03/S16 trace가 유지되는지 한 번에 보장하지 못한다.
- 해결 방법: 위 deterministic regressions와 기존 S03/S16 integration trace를 fresh/race로 함께 실행한다. predecessor, 단일 concrete manager, Python production/fallback 부재와 diff/vet도 재확인한다.
- 수정 파일 및 체크리스트:
  - [ ] `packages/go/agenttask/manager_integration_test.go`: 기존 multi-project/parallel trace에 stop/resume와 event uniqueness assertion을 필요한 최소 범위로 보강한다.
  - [ ] `packages/go/agenttask/*_test.go`: 새 fixture가 실제 외부 provider나 repo-local tool artifact를 만들지 않게 한다.
  - [ ] 계획된 파일 밖 변경이 없고 contract/source/test가 일치하는지 확인한다.
- 테스트 작성: 기존 integration test를 보강하며 별도 외부/field 테스트는 추가하지 않는다. fake clock/store/provider/isolation/reviewer/integrator로 결정적 결과와 race-free ordering을 검증한다.
- 중간 검증:

```bash
/config/.local/bin/go test -race -count=1 ./packages/go/agenttask/... -run 'Test(ManagerS03S16|StopProject|Claim|DurableIdentity|EventIdentity|InterruptedResume)'
```

## 수정 파일 요약

| 파일/영역 | 항목 |
|---|---|
| `packages/go/agenttask/types.go`, `manager.go`, `state_machine.go`, `workflow.go` | REVIEW_API-1, REVIEW_API-2, REVIEW_API-3 |
| `packages/go/agenttask/intent.go`, `review.go`, `integration_queue.go` | REVIEW_API-2, REVIEW_API-3 |
| `packages/go/agenttask/manager_test.go`, `state_machine_test.go`, `integration_queue_test.go`, `manager_integration_test.go`, `test_support_test.go` | REVIEW_API-1~REVIEW_API-4 |
| `agent-contract/inner/agent-runtime.md` | REVIEW_API-3 |

## 최종 검증

```bash
test -f agent-task/archive/2026/07/m-iop-agent-cli-runtime/01_common_runtime_node_bridge/complete.log
test -f agent-task/archive/2026/07/m-iop-agent-cli-runtime/02+01_provider_catalog/complete.log
test -f 'agent-task/archive/2026/07/m-iop-agent-cli-runtime/03+01,02_guardrail_admission/complete.log'
/config/.local/bin/go test -count=1 ./packages/go/agenttask/...
/config/.local/bin/go test -race -count=1 ./packages/go/agenttask/...
/config/.local/bin/go test -count=1 ./packages/go/agentruntime/... ./packages/go/agentprovider/... ./packages/go/agentguard/...
/config/.local/bin/go vet ./packages/go/agenttask/...
rg --sort path -n 'type AgentTaskManager|type Manager struct' packages/go apps cmd || true
rg --sort path -n 'orchestrate-agent-task-loop|dispatch\.py|python(3)?' packages/go/agenttask cmd/iop-agent 2>/dev/null || true
git diff --check
```

기대 결과: predecessor 3개가 존재하고 모든 fresh/race/vet 명령이 PASS한다. stop은 explicit restart 전 provider invocation 0회, CAS loser는 claim false와 external call 0회, identity variant는 충돌 0건이며 replay는 동일 ID로 수렴한다. S03/S16 trace와 단일 manager/Python fallback 부재가 유지되고 `git diff --check`는 무출력이어야 한다.

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