--- spec_doc_type: spec spec_id: runtime/provider-pool-config-refresh status: 부분 source_evidence: - type: contract path: agent-contract/inner/edge-config-runtime-refresh.md notes: Edge config, provider pool, config refresh, Node payload 연결 계약 - type: code path: packages/go/config/provider_types.go notes: provider/model catalog 설정 타입 - type: code path: packages/go/config/edge_types.go notes: Edge root provider_pool canonical queue policy 타입과 기본값 - type: code path: packages/go/config/load.go notes: Edge config load와 default 적용 - type: code path: packages/go/config/validate.go notes: provider/model 참조와 numeric bound 검증 - type: code path: configs/edge.yaml notes: provider-pool 권장 설정 예시와 live refresh 설정 예시 - type: code path: apps/edge/internal/service/provider_resolution.go notes: provider 후보 해석과 capacity/priority policy 구성 - type: code path: apps/edge/internal/service/model_queue_admission.go notes: provider 전역 lease, 공통 pending 상한, global enqueue 순서와 long-context admission - type: code path: apps/edge/internal/service/model_queue_release.go notes: lease 반환, disconnect/reconnect candidate 재구성과 global queue pump - type: code path: apps/edge/internal/service/status_provider.go notes: lease state와 candidate pressure 기반 online/offline provider snapshot - type: code path: apps/edge/internal/configrefresh/classify.go notes: dry-run/apply classification과 changed path report 생성 - type: code path: apps/edge/internal/bootstrap/runtime.go notes: mutable config apply, runtime snapshot 교체, Node refresh push - type: code path: apps/edge/internal/node/mapper.go notes: provider-first NodeConfigPayload compile과 OpenAI-compatible provider label 보존 - type: test path: packages/go/config/provider_catalog_config_test.go notes: provider/model catalog load와 queue 설정 검증 - type: test path: packages/go/config/provider_catalog_validation_config_test.go notes: provider/model 참조와 validation 검증 - type: test path: apps/edge/internal/node/mapper_test.go notes: provider-first adapter payload, Seulgivibe alias/provider label 검증 - type: test path: apps/edge/internal/configrefresh/node_runtime_classify_test.go notes: node runtime과 model catalog refresh classification 검증 - type: test path: apps/edge/internal/configrefresh/provider_classify_test.go notes: provider field refresh classification 검증 - type: test path: apps/edge/internal/service/model_queue_admission_test.go notes: cross-model capacity, terminal unavailable, disconnect/event-drop 회귀 검증 - type: test path: apps/edge/internal/service/status_provider_test.go notes: cross-model candidate pressure와 offline/reconnect snapshot 검증 - type: test path: apps/edge/internal/bootstrap/reconnect_readiness_integration_test.go notes: dispatch-ready reconnect가 기존 queued waiter를 실제 Node terminal까지 수렴시키는 검증 - type: test path: scripts/e2e-provider-capacity-smoke.sh notes: loopback OpenAI-compatible provider에서 two-alias capacity-1 queue와 final counter 회복 검증 --- # 스펙: Provider Pool과 Config Refresh ## 목적 Edge 설정에서 provider-pool이 어떻게 모델 실행 후보를 고르고, 어떤 설정 변경이 재시작 없이 반영되는지 설명한다. ## 기능 목록 | 기능 | 설명 | |------|------| | model catalog | `models[].id`는 외부 OpenAI-compatible `model` key이자 provider-pool `ModelGroupKey`다. | | provider mapping | `models[].providers`는 provider id를 실제 served model name으로 매핑한다. | | node provider catalog | `nodes[].providers[]`는 Node 아래 resource/provider catalog이며 provider id는 Edge config에서 전역 유일해야 한다. | | config validation | config load가 provider id 참조, served model membership, numeric bounds, long-context budget을 검증한다. | | provider 후보 필터링 | dispatch는 dispatch-ready connection을 가진 Node의 provider 후보 중 catalog match, enabled, healthy/available, capacity 조건을 만족하는 후보만 사용한다. | | provider 전역 capacity/priority dispatch | `node_id + provider_id` lease가 여러 model group의 일반·long in-flight를 합산한다. available 후보 중 낮은 in-flight를 고르고 동률이면 낮은 `priority`와 round-robin을 적용한다. | | provider-pool 공통 queue policy | Edge root `provider_pool.max_queue`가 모든 model group의 전체 pending 상한을, `queue_timeout_ms`가 각 pending request timeout을 소유한다. | | global queue 재평가 | lease 반환, capacity/priority/enabled refresh, disconnect/reconnect 뒤 global enqueue 순서에서 현재 dispatch 가능한 가장 이른 waiter부터 candidate를 다시 구성한다. | | provider snapshot | 일반·long in-flight는 provider lease state, queued 값은 Edge queue에서 해당 provider를 후보로 포함하는 고유 pending request pressure에서 계산한다. offline provider는 catalog identity를 유지하고 effective 수치를 0으로 보고한다. | | mixed provider execution path | 같은 model group의 OpenAI-compatible provider와 Ollama/CLI/native provider를 같은 후보군으로 두며, 선택된 provider capability로 passthrough 또는 normalized 실행 경로를 결정한다. | | long-context admission | estimated input token이 threshold 이상이면 `context_class=long`으로 분류하고, provider long slot이 있으면 일반 capacity slot과 함께 점유한다. | | config refresh dry-run/apply | loopback admin HTTP `POST /refresh`가 candidate config를 dry-run 또는 apply한다. | | refresh classification | listener, Edge identity, bootstrap path, adapter structural 변경 등은 restart-required로 분류한다. | | mutable apply | 적용 가능한 변경은 Edge `Cfg`, `NodeStore`, service/input model catalog, OpenAI long-context threshold를 copy-on-write로 교체한다. | | Node config refresh push | 변경이 있으면 Edge가 dispatch-ready Node에 node-specific `NodeConfigRefreshRequest`를 push한다. accepted지만 pending인 Node는 register response config를 적용한 뒤 ready가 될 때까지 push 대상이 아니다. | | Node registry swap | Node는 refresh payload로 새 adapter registry를 만들고 router registry를 swap한다. old registry stop은 active run이 있으면 drain 이후로 지연한다. | | principal token mapping config | `openai.principal_tokens[]`는 raw token 없이 `token_ref`, `token_hash_sha256`, `principal_ref`, optional alias를 관리하고 OpenAI usage metering의 principal/token label 후보를 제공한다. 같은 principal에 여러 token entry를 둘 수 있다. | | provider auth forwarding config | `openai.provider_auth`는 caller가 요청 header로 제공한 raw provider token을 selected OpenAI-compatible provider tunnel header로 전달하는 규칙만 저장한다. | | Seulgivibe provider aliases | `seulgivibe_claude`, `seulgivibe_openai` provider type은 runtime adapter type을 `openai_compat`로 정규화하고, 명시 provider label이 없으면 canonical Seulgivibe alias를 Node payload provider label로 보존한다. | ## 범위 - 포함: Edge config load/default/validation, provider-pool dispatch, queue admission, long-context admission, config refresh classification/apply, Node config refresh payload. - 제외: 개별 adapter의 provider API 호출 세부, OpenAI HTTP request/response shape, Control Plane 원격 config 변경 UX, private credential 관리. ## 주요 흐름 ```mermaid sequenceDiagram participant Caller participant Service as Edge service participant Queue as Provider queue participant Node Caller->>Service: SubmitRun(model) Service->>Queue: dispatch-ready provider 후보 선택(capacity + priority) Queue-->>Service: selected provider + served target alt selected provider supports OpenAI-compatible call Service->>Node: ProviderTunnelRequest(adapter, served target) else selected provider is Ollama/CLI/native Service->>Node: RunRequest(adapter, served target) end participant Operator participant Refresh as Config refresh Operator->>Refresh: POST /refresh(apply) Refresh->>Refresh: load, validate, classify Refresh->>Node: NodeConfigRefreshRequest ``` ## 계약 - `iop.edge-config-runtime-refresh`: `agent-contract/inner/edge-config-runtime-refresh.md` - `iop.edge-node-runtime-wire`: `agent-contract/inner/edge-node-runtime-wire.md` - proto 원문: `proto/iop/runtime.proto` ## 설정/데이터/이벤트 - `long_context_threshold_tokens` 기본 예시는 `100000`이고 0 이하 값은 config load에서 거부된다. - `provider_pool.max_queue`는 0/생략 시 기본값 `16`, `queue_timeout_ms`는 생략 시 `30000`이고 명시적 0은 timeout 없음이다. canonical root key가 없을 때만 서로 같은 legacy provider queue pair를 승격하며 값이 다르면 load를 거부한다. - `nodes[].providers[].capacity`와 `long_context_capacity`는 provider resource 속성이고 같은 provider를 공유하는 model alias가 합산 점유한다. `total_context_tokens`는 runtime ledger가 아니라 `context_window_tokens * long_context_capacity` 정적 validation 값이다. - provider `enabled=false`는 dispatch pool에서 제외하지만 adapter process lifecycle 변경을 의미하지 않는다. - accepted registration은 provider candidate를 바로 복구하지 않는다. Node가 config 적용과 handler 설치 뒤 ready ack를 받아야 해당 generation이 candidate, connected snapshot, refresh push 대상이 되며 이 transition이 stranded provider-pool waiter를 재평가한다. - provider capacity, long-context capacity, priority, enabled toggle, root queue policy와 model generation policy는 live apply 대상으로 분류된다. apply는 기존 lease를 보존하고 이후 admission 및 모든 관련 waiter의 live candidate/deadline을 새 값으로 재평가한다. - Edge listener, control plane, openai/a2a listener, bootstrap artifact path, node 추가/삭제, node token/alias, adapter 설정 변경은 restart-required 대상이다. - `openai.principal_tokens[]`는 `token_ref`와 `token_hash_sha256` 중복을 거부하고, raw token 원문은 tracked config에 저장하지 않는다. - 여러 `openai.principal_tokens[]` entry가 같은 `principal_ref`를 공유할 수 있으며, 이때 `token_ref`가 앱/통합/용도별 사용량 분해 기준이다. - `openai.principal_tokens[]` 변경은 credential/hash 변경으로 보고 restart-required로 분류된다. - `openai.provider_auth.enabled=true`이면 생략된 header fields는 `from_header=X-IOP-Provider-Authorization`, `target_header=Authorization`, `scheme=Bearer`, `required=true`로 해석된다. raw provider token 값은 config/spec/docs에 저장하지 않는다. - Seulgivibe provider catalog는 top-level `models[]`의 정적 provider mapping을 source of truth로 사용한다. provider `/models` endpoint는 IOP catalog source가 아니다. - `models[]`는 mixed provider group과 Ollama-only group을 모두 표현할 수 있다. Ollama는 후보에서 제외하지 않고 `capacity`/`priority`로 운영자가 낮은 동시성과 낮은 선호도를 표현한다. - refresh result는 changed nodes/providers/models와 restart-required paths를 stable non-nil slice로 보고한다. ## 검증 - `go test ./packages/go/config` - `go test ./apps/edge/internal/configrefresh` - `go test ./apps/edge/internal/service` - `go test ./apps/edge/internal/node` - `go test ./apps/node/internal/adapters ./apps/node/internal/node` - `go test ./apps/edge/internal/bootstrap -run '^TestActualNodeReconnectReadyPumpsQueuedWaiterExactlyOnce$'` - `./scripts/e2e-provider-capacity-smoke.sh` ## 한계와 주의사항 - provider health는 현재 config/provider snapshot 기반이다. 모든 runtime에 대한 active health probe가 완성된 것은 아니다. - refresh admin API는 operator-local 표면이다. 접근 제어 없이 public interface에 노출하지 않는다. - adapter structural 변경은 contract상 restart-required로 분류된다. Node handler가 registry swap을 지원하더라도 Edge refresh classifier가 허용한 변경만 apply해야 한다. - no-change apply는 runtime snapshot을 교체하지만 Node push는 생략한다. - `NodeRuntimeConfig.concurrency`는 legacy metadata이며 provider-pool admission의 node-wide capacity로 쓰지 않는다. - provider가 full인 상태는 일시적인 queue block이지만, disconnect/disable로 live candidate가 모두 사라지면 waiter는 기존 queue timeout까지 남지 않고 unavailable로 종료된다. - credential, private endpoint, bearer token 원문은 tracked config/docs/spec에 남기지 않는다. - principal token mapping은 완성된 사용자/테넌트 source of truth가 아니라 외부 `principal_ref` 또는 내부 alias에 대한 얇은 운영 매핑이다. - Seulgivibe provider endpoint와 raw user token은 환경별 private config 또는 request-time header로 주입해야 하며 tracked 예시에 실제 값을 남기지 않는다. ## 변경 기록 - 2026-07-07: 현재 코드, 계약, config 예시 기준으로 bootstrap spec 작성. - 2026-07-07: 기능 목록 중심으로 축소하고 주요 흐름을 Mermaid sequence diagram으로 정리. - 2026-07-10: OpenAI usage metering용 principal token hash mapping config와 restart-required 기준을 반영. - 2026-07-11: Seulgivibe OpenAI-compatible provider aliases, provider auth forwarding config, static catalog 기준을 현재 코드/계약/테스트 기준으로 반영. - 2026-07-12: Model Group Mixed Provider Dispatch 종료 검토 기준으로 mixed provider group과 Ollama-only group, capacity/priority 가중 예시를 반영. - 2026-07-18: 저장소 구조 분해 뒤 config, provider resolution/admission, split test의 `source_evidence`를 현재 경로로 동기화. - 2026-07-22: pending accepted connection과 dispatch-ready connection을 구분하고, ready ack 뒤에만 provider candidate 복구·refresh push·queued waiter pump가 일어나는 현재 동작을 반영. - 2026-07-22: root provider-pool queue policy, cross-model provider lease, global queue 재평가, refresh lease 보존과 connectivity 기반 snapshot 의미를 현재 구현·계약·테스트 기준으로 동기화.