<!-- task=m-iop-agent-cli-runtime/01_common_runtime_node_bridge plan=0 tag=API -->

# Common Agent Runtime와 Node Bridge 구현 계획

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

`CODE_REVIEW-cloud-G10.md`의 구현 에이전트 소유 섹션을 채우는 것이 구현의 필수 마지막 단계다. 아래 검증을 실행하고 실제 변경·결정·stdout/stderr를 기록한 뒤 active 파일을 그대로 둔 채 리뷰 준비 완료를 보고한다. 막히면 정확한 blocker, 시도한 명령과 출력, 재개 조건만 구현 소유 evidence 필드에 남긴다. 사용자에게 선택을 묻거나 user-input 도구와 control-plane stop 파일을 사용하거나 다음 상태를 분류하지 않으며, log/`complete.log`/archive 처리는 code-review skill에 맡긴다.

## 배경

현재 CLI provider 실행 계약과 lifecycle은 `apps/node/internal`에 결합돼 있어 독립 `iop-agent` host가 재사용할 공통 구현이 없다. S01의 provider/runtime 기반과 S04를 함께 처리해 공통 Go package를 단일 source of truth로 만들고 Node를 얇은 호환 bridge로 전환한다. S01의 AgentTaskManager 완료와 `common-runtime` Roadmap Completion은 04 plan이 소유한다.

## 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:
  - `node-consumer`: Node의 공통 runtime bridge 전환
- 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/priority-queue.md`, `agent-roadmap/ROADMAP.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/edge-node-runtime-wire.md`, `agent-contract/inner/edge-config-runtime-refresh.md`, `agent-spec/index.md`, `agent-spec/runtime/edge-node-execution.md`, `agent-spec/runtime/provider-pool-config-refresh.md`.
- 구현: `apps/node/internal/runtime/types.go`, `apps/node/internal/adapters/registry.go`, `apps/node/internal/adapters/config_set.go`, `apps/node/internal/adapters/factory.go`, `apps/node/internal/adapters/cli/cli.go`, `apps/node/internal/adapters/cli/emitters.go`, `apps/node/internal/adapters/cli/persistent.go`, `apps/node/internal/adapters/cli/status/status.go`, `apps/node/internal/adapters/cli/status/quota.go`, `apps/node/internal/terminal/session.go`, `apps/node/internal/bootstrap/module.go`, `apps/node/internal/node/run_handler.go`, `apps/node/internal/node/runtime_sink.go`, `apps/node/internal/router/router.go`, `packages/go/config/provider_types.go`, `packages/go/config/node_types.go`, `packages/go/config/load.go`, `packages/go/config/validate.go`.
- 테스트: `apps/node/internal/adapters/adapters_blackbox_test.go`, `apps/node/internal/adapters/config_set_test.go`, `apps/node/internal/adapters/cli/cli_emitters_test.go`, `apps/node/internal/adapters/cli/cli_session_test.go`, `apps/node/internal/adapters/cli/lifecycle_blackbox_test.go`, `apps/node/internal/bootstrap/module_test.go`, `apps/node/internal/router/router_test.go`, `apps/node/internal/terminal/session_test.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`.

### SDD 기준

- SDD는 `승인됨`, SDD 잠금은 `해제`다.
- S01의 provider/runtime 부분은 Node와 `iop-agent`가 같은 provider profile, run/stream/resume/cancel lifecycle, failure 의미를 공유하고 중복 구현이 없어야 한다. AgentTaskManager 부분과 S01 최종 Completion은 04 plan으로 이관한다.
- S04는 `node-consumer`에 대해 기존 Edge-Node wire와 config fixture 및 provider behavior를 보존해야 한다.
- Evidence Map S01의 common provider conformance/duplicate search는 04의 최종 S01 evidence 입력으로 남기고, S04의 Node wire/config compatibility suite를 이 plan의 Completion evidence로 고정했다.

### 테스트 환경 규칙

- `test_env=local`; local rules와 platform-common/node/testing profile을 읽었다. Go 변경은 `go test -count=1`과 `go test -race -count=1`을 사용하고 cache 결과는 허용하지 않는다.
- 규칙에 적힌 repo root `/config/workspace/iop`와 실제 checkout `/config/workspace/iop-s0`가 다르므로 실제 `git rev-parse --show-toplevel` 결과를 workdir로 사용한다. test-rule 유지보수는 이 작업 범위가 아니다.
- 프리플라이트: branch `dev`, HEAD `0565d2be66cc`, 기존 roadmap/SDD 변경이 있는 dirty checkout이다. `/config/.local/bin/go`는 `/config/opt/go/bin/go`를 가리키며 `go1.26.2 linux/arm64`, GOROOT `/config/opt/go`다. 구현 에이전트는 사용자 변경을 보존하고 새 binary를 repo 안에 만들지 않는다.
- 이 계획의 검증은 checkout 내부 Go test/search뿐이며 외부 Edge, port, config, 배포 artifact를 요구하지 않는다.

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

- 기존 tests는 Node 내부 adapter lifecycle, emitter, session, registry/config 및 router 동작을 덮지만 공통 package의 두 host conformance는 없다.
- 공통 failure codec, cancel/resume, terminal exactly-once와 Node bridge wire 호환을 새 계약/회귀 test로 추가해야 한다.
- 실제 독립 `iop-agent` binary wiring은 후속 task-manager 계획 범위이므로 이 계획에서는 host-neutral fixture로만 검증한다.

### 심볼 참조

- 이동 후보인 `runtime.Adapter`, `RuntimeEvent`, `ExecutionSpec`, `RunRequest`, `Capabilities`, `Router`, `EventSink`는 `apps/node/internal/adapters/**`, `apps/node/internal/node/**`, `apps/node/internal/router/**`, `apps/node/internal/bootstrap/**`에서 참조된다.
- `adapters.Registry`는 `apps/node/internal/bootstrap/module.go`, `apps/node/internal/node/*`, `apps/node/internal/router/router.go`, config refresh tests에서 참조된다.
- 이름을 제거하거나 바꿀 경우 `rg --sort path` 결과의 모든 import/call site를 갱신해야 하며, compatibility alias로 중복 구현을 숨기지 않는다.

### 분할 판단

- `01_common_runtime_node_bridge`: 공통 provider lifecycle/failure contract와 Node 호환 bridge가 하나의 원자적 invariant다. PASS는 `node-consumer`만 닫고 공통 conformance 결과를 04의 `common-runtime` 완료에 전달한다.
- `02+01_provider_catalog`: YAML discovery/readiness contract이며 01의 공통 provider API 완료에 의존한다.
- `03+01,02_guardrail_admission`: canonical workspace/provider capability preflight이며 01·02에 의존한다.
- `04+01,02,03_task_manager`: scheduling/state/concurrency orchestration이며 앞의 세 계약에 의존한다.
- 이 subtask에는 predecessor가 없다. 그래프는 비순환이고 producer index가 consumer보다 낮다.

### 범위 결정 근거

- Edge/Control Plane proto와 wire 의미는 변경하지 않는다. 호환 bridge로 기존 계약을 보존한다.
- YAML catalog, canonical workspace grant, scheduler/state store, Python dispatcher 변경은 후속 계획으로 제외한다.
- provider별 parsing을 재작성하지 않고 기존 CLI 구현을 공통 package로 이동/정리한다. 새 외부 Go dependency는 필요하지 않으며 추가하려면 먼저 `go.mod`를 확인한다.

### 최종 라우팅

- `evaluation_mode=first_pass`, finalizer=`finalize-task-routing` 1회.
- build: closure `cloud`, grade `G10`, route `routed`; review: closure `cloud`, grade `G10`, route `routed`.
- `large_indivisible_context=false`; positive loop risks 5개: public contract extraction, wide import graph, lifecycle compatibility, wire regression, duplicate-removal proof. Roadmap Completion target은 `node-consumer` 1개다.
- recovery: `review_rework_count=0`, `evidence_integrity_failure=false`; capability gap evidence 없음.
- canonical files: `PLAN-cloud-G10.md`, `CODE_REVIEW-cloud-G10.md`.

## 구현 체크리스트

- [ ] API-1 공통 agent runtime inner contract와 public Go API를 확정한다.
- [ ] API-2 CLI provider, emitter/stream/session, status/quota, failure codec의 단일 공통 구현과 conformance tests를 만든다.
- [ ] API-3 Node를 공통 runtime consumer bridge로 전환하고 기존 wire/config/provider 회귀 tests를 통과시킨다.
- [ ] API-4 전체 fresh/race 검증과 duplicate implementation search를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [API-1] 공통 runtime 계약과 API 확정

- 문제: `apps/node/internal/runtime/types.go:32-220`에 execution/event/provider interface가 Node internal API로만 존재하고, SDD가 요구하는 독립 host 계약과 typed failure codec이 없다.
- 해결 방법: `agent-contract/inner/agent-runtime.md`를 index에 등록하고 host-neutral request/event/session/cancel/status/failure semantics를 먼저 정의한다. 이후 `packages/go/agentruntime` public types/interfaces가 이 계약을 직접 표현하게 한다.

```go
// Before: apps/node/internal/runtime/types.go:199-204
// type Adapter interface { Name(); Capabilities(...); Execute(...) }
// After: packages/go/agentruntime public Provider interface
// type Provider interface { Name() string; Capabilities(context.Context) (Capabilities, error); Run(context.Context, ExecutionSpec, EventSink) error }
```

- 수정 파일 및 체크리스트:
  - [ ] `agent-contract/index.md`에 inner contract pointer 추가.
  - [ ] `agent-contract/inner/agent-runtime.md`에 lifecycle, terminal event, resume/cancel, status/quota, typed failure 계약 작성.
  - [ ] `packages/go/agentruntime/types.go`, `failure.go`에 계약 타입과 codec 구현.
- 테스트 작성: `packages/go/agentruntime/failure_test.go`에 round-trip, unknown code, cancellation boundary table tests를 추가한다.
- 중간 검증: `/config/.local/bin/go test -count=1 ./packages/go/agentruntime/...`가 PASS해야 한다.

### [API-2] CLI runtime 단일 구현 추출

- 문제: `apps/node/internal/adapters/cli`와 `apps/node/internal/terminal`이 process/session/emitter/status/quota를 소유해 다른 host가 internal import 규칙상 재사용할 수 없다.
- 해결 방법: provider-neutral lifecycle은 `packages/go/agentruntime`, CLI process/profile/status 구현은 `packages/go/agentprovider/cli`로 이동한다. 기존 Node package에는 type alias가 아닌 얇은 constructor/translation bridge만 남기고 terminal exactly-once, cancel과 session resume 의미를 보존한다.

```go
// Before: apps/node/internal/adapters/config_set.go:114-122 — Node internal constructor
// cli.New(config.CLIConf, *zap.Logger)
// After: shared constructor consumed by hosts
// cliprovider.New(cliprovider.ProfileSet, agentruntime.Logger)
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/agentprovider/cli/**`에 기존 CLI implementation을 이동하고 internal Node/proto import 제거.
  - [ ] `packages/go/agentruntime/emitter.go`, `session.go`, `status.go`에 공통 orchestration 배치.
  - [ ] 기존 `apps/node/internal/adapters/cli/**`와 `terminal/**`의 중복 구현 제거 또는 translation-only bridge화.
  - [ ] public package docs와 error wrapping 규칙 유지.
- 테스트 작성: 기존 CLI tests를 공통 package로 이동하고 run/resume/cancel, terminal exactly-once, quota/status 정상·경계 table tests와 두 host fixture conformance suite를 추가한다.
- 중간 검증: `/config/.local/bin/go test -race -count=1 ./packages/go/agentruntime/... ./packages/go/agentprovider/cli/...`가 PASS해야 한다.

### [API-3] Node consumer bridge와 호환성 보존

- 문제: `apps/node/internal/bootstrap/module.go:58-136`, `node/run_handler.go:25-154`, `router/router.go:14-109`, `adapters/registry.go:16-153`가 Node-owned runtime/registry를 직접 조립한다.
- 해결 방법: Node wire request/response는 translation layer에서 공통 API로 변환하고 bootstrap/router/registry는 공통 runtime을 주입받는다. Edge proto, config refresh locking, admission ticket release 및 terminal flush 순서는 유지한다.

```go
// Before: apps/node/internal/node/run_handler.go:25-37
// rr := runtime.RunRequest{...proto fields...}
// After: wire translator + shared request
// rr := nodebridge.RunRequestFromProto(req)
```

- 수정 파일 및 체크리스트:
  - [ ] `apps/node/internal/node/runtime_bridge.go`에 proto ↔ common runtime translation 구현.
  - [ ] `apps/node/internal/bootstrap/module.go`, `router/router.go`, `adapters/config_set.go`, `registry.go`를 common implementation 소비로 전환.
  - [ ] `apps/node/internal/node/run_handler.go`, runtime sink/cancel/command paths의 wire 의미 보존.
  - [ ] `apps/node/internal/runtime` 및 Node CLI/terminal duplicate를 제거하고 `rg` evidence를 남김.
- 테스트 작성: Node wire/config fixtures, registry refresh, run/session/status/cancel tests를 common runtime 기반으로 갱신하고 이전 event/failure 값의 golden compatibility assertion을 추가한다.
- 중간 검증: `/config/.local/bin/go test -count=1 ./apps/node/internal/...`가 PASS해야 한다.

### [API-4] 통합 및 중복 제거 검증

- 문제: package tests만으로는 Node와 공통 host의 의미 일치 및 Node 내부 duplicate 제거를 증명하지 못한다.
- 해결 방법: fresh full Go suite와 race 범위를 실행하고, 제거 대상 선언/구현이 `packages/go` 한 곳에만 남는지 deterministic search로 확인한다.
- 수정 파일 및 체크리스트:
  - [ ] `packages/go/agentruntime/conformance_test.go`에 host fixture parity 추가.
  - [ ] 검증 실패 시 원인을 범위 내에서 해결하고 실제 출력을 review stub에 기록.
- 테스트 작성: API-1~3에서 작성하므로 별도 test 파일은 만들지 않는다.
- 중간 검증: `/config/.local/bin/go test -count=1 ./packages/go/... ./apps/node/...`와 아래 `rg`가 모두 기대값을 만족해야 한다.

## 수정 파일 요약

| 파일/영역 | 항목 |
|---|---|
| `agent-contract/index.md`, `agent-contract/inner/agent-runtime.md` | API-1 |
| `packages/go/agentruntime/**` | API-1, API-2, API-4 |
| `packages/go/agentprovider/cli/**` | API-2 |
| `apps/node/internal/adapters/**`, `terminal/**`, `runtime/**` | API-2, API-3 |
| `apps/node/internal/bootstrap/**`, `node/**`, `router/**` | API-3 |

## 최종 검증

```bash
test "$(git rev-parse --show-toplevel)" = "/config/workspace/iop-s0"
/config/.local/bin/go test -count=1 ./packages/go/agentruntime/... ./packages/go/agentprovider/cli/...
/config/.local/bin/go test -count=1 ./apps/node/internal/...
/config/.local/bin/go test -race -count=1 ./packages/go/agentruntime/... ./packages/go/agentprovider/cli/... ./apps/node/internal/adapters/... ./apps/node/internal/router/...
/config/.local/bin/go test -count=1 ./packages/go/... ./apps/node/...
rg --sort path -n 'type (Adapter|RuntimeEvent|ExecutionSpec|RunRequest|Capabilities|EventSink|Registry) ' packages/go apps/node/internal
rg --sort path -n 'apps/node/internal/(runtime|adapters/cli|terminal)' packages/go || true
git diff --check
```

기대 결과: 모든 test가 fresh 실행으로 PASS하고, 첫 `rg`는 공통 package의 단일 정의와 허용된 Node bridge만 보여야 하며, 두 번째 `rg`는 `packages/go`에서 Node internal import를 출력하지 않아야 한다. `git diff --check`는 출력이 없어야 한다.

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