iop/agent-task/m-node-resource-model-unification/03+01_edge_dispatch_status/PLAN-cloud-G07.md
toki 135259619e fix(openai): Cline 도구 호출 변환을 지원한다
Cline이 텍스트 XML 블록으로 도구 호출을 내보내는 경우 OpenAI-compatible tool_calls 응답으로 변환해야 클라이언트 실행 루프가 멈추지 않는다.

Node resource model 로드맵과 agent-task 문서는 요청된 전체 변경 범위에 포함한다.
2026-06-27 13:16:49 +09:00

11 KiB

Edge Dispatch And Status Resource Plan

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

이 계획은 01_contract_schema 완료 후 구현한다. 구현 뒤에는 반드시 CODE_REVIEW-cloud-G07.md의 구현 에이전트 소유 섹션을 채운다. Milestone lock 결정 외 이유로 사용자에게 직접 질문하지 않는다.

배경

Edge는 OpenAI-compatible 요청에 ModelGroupKey를 항상 담고, service는 ModelGroupKey != ""이면 queue를 탄다. 사용자의 방향에서는 provider pool만 concurrency/queue를 관리해야 하며 CLI route는 queue/global gate에 묶이지 않아야 한다. 또한 Control Plane status는 node 아래 실제 정의된 resource/provider를 중복 없이 보여야 한다.

사용자 리뷰 요청 흐름

선택된 Milestone의 구현 잠금은 해제되어 있으므로 기본값은 없음이다. 외부 provider 접근 실패나 dev smoke 증거 공백은 04번 검증 또는 후속 계획으로 기록한다.

Roadmap Targets

  • Milestone: agent-roadmap/phase/operational-observability-provider-management/milestones/node-resource-model-unification.md
  • Task ids:
    • edge-routing-status: Edge provider-only queue routing and resource-first status
  • Completion mode: check-on-pass

분석 결과

읽은 파일

  • apps/edge/internal/service/run_dispatch.go
  • apps/edge/internal/service/model_queue.go
  • apps/edge/internal/service/status_provider.go
  • apps/edge/internal/openai/chat_handler.go
  • apps/edge/internal/openai/responses_handler.go
  • apps/edge/internal/openai/routes.go
  • apps/edge/internal/edgevalidate/validate.go
  • apps/edge/internal/node/mapper.go
  • apps/edge/internal/node/store.go
  • apps/edge/internal/bootstrap/runtime.go
  • apps/edge/internal/transport/server.go
  • apps/edge/internal/service/model_queue_test.go
  • apps/edge/internal/service/service_test.go
  • apps/edge/internal/service/service_internal_test.go
  • apps/edge/internal/service/status_provider_test.go
  • apps/edge/internal/openai/server_test.go
  • apps/edge/internal/node/mapper_test.go
  • apps/edge/internal/node/store_test.go
  • apps/edge/internal/bootstrap/runtime_test.go
  • apps/edge/internal/transport/server_test.go
  • agent-test/local/edge-smoke.md
  • agent-test/local/platform-common-smoke.md
  • agent-contract/outer/openai-compatible-api.md
  • agent-contract/inner/control-plane-edge-wire.md
  • agent-contract/inner/edge-config-runtime-refresh.md
  • agent-roadmap/phase/operational-observability-provider-management/milestones/node-resource-model-unification.md
  • agent-roadmap/sdd/operational-observability-provider-management/node-resource-model-unification/SDD.md

SDD 기준

선택된 Milestone은 SDD: 필요이며 agent-roadmap/sdd/operational-observability-provider-management/node-resource-model-unification/SDD.md[승인됨], SDD 잠금 해제 상태다. 이 subtask는 Acceptance Scenario S05, S06과 Milestone Task edge-routing-status를 대상으로 한다.

Evidence Map은 provider-only queue test, CLI/direct route direct dispatch evidence, resource catalog status snapshot evidence를 요구한다. 따라서 구현 체크리스트는 ProviderPool && ModelGroupKey != "" && queue != nil queue 조건, provider adapter validation, resource-first status snapshot, provider별 accounting regression을 포함한다.

테스트 환경 규칙

test_env는 local이다. Edge service/OpenAI/status 변경이므로 agent-test/local/edge-smoke.mdplatform-common-smoke.md를 적용한다. 필수 unit은 go test -count=1 ./apps/edge/...이며, OpenAI-compatible boundary는 /v1/models, /v1/responses, /v1/chat/completions unit/e2e coverage를 확인한다.

테스트 커버리지 공백

  • CLI route가 ModelGroupKey를 가져도 provider queue를 타지 않는 regression이 필요하다.
  • provider adapter가 missing/disabled/ambiguous일 때 config validation 또는 candidate resolution에서 제외되는 test가 필요하다.
  • getSnapshotForNode가 adapter-level snapshot과 provider catalog snapshot을 중복으로 내는 현상은 status provider test 갱신이 필요하다.

심볼 참조

의미 변경 대상:

  • SubmitRun: apps/edge/internal/service/run_dispatch.go:107
  • resolveAdapterForNode: apps/edge/internal/service/run_dispatch.go:297
  • providerDispatchable: apps/edge/internal/service/run_dispatch.go:476
  • getSnapshotForNode: apps/edge/internal/service/model_queue.go:529
  • ModelGroupKey writes: apps/edge/internal/openai/chat_handler.go:75, apps/edge/internal/openai/responses_handler.go:89

분할 판단

이 subtask는 03+01_edge_dispatch_status이므로 predecessor 01_contract_schema 완료가 필요하다. 현재 predecessor는 active plan 상태이며 complete.log가 없다. 02+01_node_admission과 병렬 가능하나 04번은 둘 다 완료 후 진행한다.

범위 결정 근거

Node-local gate 제거는 02번 범위다. dev-runtime remote deploy와 actual build/dev-runtime/edge.yaml 반영은 04번 범위다. 이 subtask는 Edge runtime code와 tests만 변경한다.

빌드 등급

cloud-G07: OpenAI outer API, service dispatch, provider queue, Control Plane status가 같이 걸린 cross-domain 변경이다.

의존 관계 및 구현 순서

03+01_edge_dispatch_status는 sibling 01_contract_schema 완료 후 구현한다. 02와 직접 dependency는 없지만 04가 두 결과를 통합 검증한다.

구현 체크리스트

  • service queue 진입 조건을 ProviderPool && ModelGroupKey != "" && queue != nil로 제한한다.
  • CLI/openai legacy route는 ModelGroupKey가 있어도 direct dispatch로 흐르고 provider pool queue/inflight를 사용하지 않게 한다.
  • provider-pool candidate resolution에서 provider adapter가 같은 node의 enabled adapter instance인지 확인한다.
  • status snapshot은 정의된 resource/provider catalog를 우선하고, legacy adapter snapshot은 catalog가 없을 때만 compat로 낸다.
  • provider별 in_flight/queued accounting이 같은 node의 여러 provider에서 독립적으로 유지되는지 test를 보강한다.
  • CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

[REFACTOR-1] Provider-only Queue Admission

문제: apps/edge/internal/service/run_dispatch.go:107ModelGroupKey != ""이면 모든 route를 queue로 보낸다. OpenAI handlers는 legacy CLI route에도 ModelGroupKey를 채운다(chat_handler.go:75, responses_handler.go:89).

해결 방법: queue path는 req.ProviderPool true일 때만 사용한다. OpenAI handler는 그대로 ProviderPool을 전달하므로 handler 변경은 최소화한다.

Before:

// apps/edge/internal/service/run_dispatch.go:107
if req.ModelGroupKey != "" && s.queue != nil {
    return s.submitRunQueued(ctx, req)
}

After:

if req.ProviderPool && req.ModelGroupKey != "" && s.queue != nil {
    return s.submitRunQueued(ctx, req)
}
return s.submitRunDirect(req)

수정 파일 및 체크리스트:

  • apps/edge/internal/service/run_dispatch.go
  • apps/edge/internal/openai/server_test.go
  • apps/edge/internal/service/service_test.go

테스트 작성: legacy CLI model route request with non-empty ModelGroupKey가 queue groups/inflight를 만들지 않고 direct dispatch되는 test를 추가한다. provider-pool route는 기존처럼 queue를 탄다는 test를 유지한다.

중간 검증:

go test -count=1 ./apps/edge/internal/service ./apps/edge/internal/openai

[REFACTOR-2] Provider Adapter Dispatch Validation

문제: providerDispatchableapps/edge/internal/service/run_dispatch.go:476에서 non-empty adapter만 확인한다. nodes[].providers[].adapter가 실제 node adapter와 어긋나도 provider-pool candidate가 될 수 있다.

해결 방법: 01번 validation helper를 재사용하거나 service-local resolver를 추가해 provider adapter가 enabled exact instance 또는 unambiguous type route인지 확인한다. Config validation에서 막되, service candidate resolution도 defensive check를 둔다.

수정 파일 및 체크리스트:

  • apps/edge/internal/service/run_dispatch.go
  • apps/edge/internal/service/model_queue_test.go
  • apps/edge/internal/edgevalidate/validate.go
  • apps/edge/internal/edgevalidate/*_test.go

테스트 작성: missing adapter, disabled adapter, ambiguous openai_compat type route, valid named adapter 각각 provider-pool candidate 결과를 검증한다.

중간 검증:

go test -count=1 ./apps/edge/internal/service ./apps/edge/internal/edgevalidate

[REFACTOR-3] Resource-first Status Snapshot

문제: getSnapshotForNodeapps/edge/internal/service/model_queue.go:540부터 CLI/Ollama/vLLM/OpenAICompat adapter snapshots를 만들고, 다시 :626부터 provider catalog snapshots를 추가한다. 같은 OpenAICompat provider가 adapter snapshot과 provider snapshot으로 중복 노출된다.

해결 방법: rec.Providers가 있으면 그 catalog를 source of truth로 삼아 snapshots를 만들고 providerID stats만 붙인다. rec.Providers가 비어 있는 old config에서만 adapter-level compat snapshots를 낸다. CLI resource를 status에 보이게 하려면 config에 category cli provider/resource entry가 있어야 한다.

수정 파일 및 체크리스트:

  • apps/edge/internal/service/model_queue.go
  • apps/edge/internal/service/status_provider.go
  • apps/edge/internal/service/status_provider_test.go
  • apps/edge/internal/service/model_queue_test.go
  • proto/iop/control.proto comments only if needed

테스트 작성: node with CLI + OpenAICompat + providers catalog가 catalog snapshots만 반환하고, provider pool stats가 provider id별로 붙는지 확인한다. providers empty legacy node는 old adapter snapshot fallback을 확인한다.

중간 검증:

go test -count=1 ./apps/edge/internal/service

수정 파일 요약

파일 항목
apps/edge/internal/service/run_dispatch.go REFACTOR-1, REFACTOR-2
apps/edge/internal/service/model_queue.go REFACTOR-2, REFACTOR-3
apps/edge/internal/service/status_provider.go REFACTOR-3
apps/edge/internal/openai/server_test.go REFACTOR-1
apps/edge/internal/service/model_queue_test.go REFACTOR-2, REFACTOR-3
apps/edge/internal/service/service_test.go REFACTOR-1
apps/edge/internal/service/status_provider_test.go REFACTOR-3
apps/edge/internal/edgevalidate/validate.go REFACTOR-2

최종 검증

go test -count=1 ./apps/edge/...

모든 코드 변경 완료 후 반드시 CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.