16 KiB
16 KiB
Edge Config And Runtime Refresh Contract
계약 메타
- id:
iop.edge-config-runtime-refresh - boundary:
inner - status: active
- 원본 경로:
packages/go/config/edge_types.gopackages/go/config/provider_types.gopackages/go/config/load.goconfigs/edge.yamlapps/edge/internal/configrefresh/request.goapps/edge/internal/configrefresh/result.goapps/edge/internal/configrefresh/classify.goproto/iop/runtime.protoapps/edge/internal/node/mapper.goapps/node/internal/adapters/config_set.go
- human docs:
apps/edge/README.md
읽는 조건
configs/edge.yaml,packages/go/config, provider pool,openai.model_routes,models[],nodes[].providers[], adapter instance 설정을 바꿀 때iop-edge config refresh의 dry-run/apply 결과 schema나 restart/applied 분류를 바꿀 때- Edge가 Node에 전달하는
NodeConfigPayload또는NodeConfigRefresh*payload를 바꿀 때
범위
이 계약은 Edge 설정 YAML, Go config struct, config refresh 결과, Edge-to-Node runtime config payload의 연결 규칙이다. tracked config에는 public 예시와 기본 구조만 두고, 실제 endpoint/credential/private host 값은 환경별 private 설정으로 주입한다.
핵심 규칙
openai.principal_tokens[]는 raw token을 저장하지 않고 hash/reference로 principal 매핑을 관리한다. 각 entry는token_ref(non-empty, unique),token_hash_sha256(64-char hex, duplicate hash rejection),principal_ref(non-empty), optionalprincipal_alias필드를 갖는다. 여러 entry가 같은principal_ref와principal_alias를 공유할 수 있으며, 이때token_ref가 앱/통합/용도별 사용량 분해 기준이 된다. tracked config에는 raw token을 저장하지 않고 hash/reference만 둔다.protocol_profilesis the top-level map of custom profile overlays, keyed by stable profile id. EachProtocolProfileConfcan declarebase,driver,base_url, an operation-path map,auth,capabilities,model_mapping, andextensions. A custom overlay extends one built-in or custom base; cycles, unknown bases, and invalid driver/operation/capability combinations are rejected during config normalization.nodes[].providers[].profileselects a built-in or custom catalog entry. If the selector is empty, legacy provider-type normalization can select a compatibility profile; this is distinct frombaseinheritance. Normalization resolves the selection into the runtime-onlyProviderDefinition.RuntimeProfilesnapshot, which is not serialized back into YAML. The resolved snapshot is copied into the nested OpenAI-compatible adapter config, not into a per-request tunnel message.ConcreteProtocolProfile.MapModel(model)은 provider의 model alias 정규화를 수행한다. provider가 model mapping을 정의하면 IOP externalmodelkey를 provider served target으로 변환한다. 매핑이 없으면 original model을 그대로 사용한다.ConcreteProtocolProfile.HasCapability(cap)는 provider capability admission에 사용된다. closed vocabulary (models,chat,messages,responses,streaming,tool_calling,count_tokens)만 허용한다.ConcreteProtocolProfile.ResolveOperationURL(op)는 완성된 resolved upstream URL을 반환한다. absolute operation URL은 그대로 보존하며 relative operation path는 normalized base URL에 1회 join된다. 표기된/v1/...값은 return value가 아니라 operation-path input이다 (models→GET /v1/models또는GET /anthropic/v1/models,chat_completions→POST /v1/chat/completions,messages→POST /v1/messages,count_tokens→POST /v1/messages/count_tokens,responses→POST /v1/responses).validOperationsByDriver는 driver별 허용 operation의 closed set이다.openai_chat은models,chat_completions,responses,count_tokens를 허용한다.anthropic_messages는models,messages,count_tokens를 허용한다.openai_responses는models,responses,count_tokens를 허용한다.openai.provider_auth는 request-time raw provider token forwarding rule이다.enabled=false가 기본이며 raw token 값은 저장하지 않는다.enabled=true이고 header fields가 생략되면from_header=X-IOP-Provider-Authorization,target_header=Authorization,scheme=Bearer,required=true로 해석한다.openai.stream_evidence_gate는 request-local Recovery Coordinator 기본값·절대 상한·ingress snapshot 제한 설정이다.enabled는 지원되는 Chat Completions, normalized Responses, provider tunnel passthrough, provider-pool dispatch, tool-validation recovery를packages/go/streamgaterequest runtime이 소유하도록 라우팅할지 여부이며 omitted 기본값 false(legacy eager-write path와 legacy tool-validation retry loop를 그대로 유지)이다.max_request_fault_recovery는 요청당 전체 fault recovery 상한(0..3, omitted 기본값 3, explicit 0은 모든 fault recovery 비활성화)이다.max_strategy_fault_recovery는 fault strategy(exact_replay/continuation_repair/schema_repair)별 상한(0..max_request_fault_recovery, omitted 기본값은 effective request total 상속, explicit 0은 해당 strategy 비활성화)이며 request-start 시점에 immutable runtime option snapshot으로 각 fault strategy에 동일하게 적용된다.max_ingress_snapshot_bytes는 ingress snapshot 바이트 상한(1..16777216[16 MiB], omitted/0 기본값 16 MiB)이다.environment는 request-start selector snapshot이며dev|dev-corp만 허용하고 omitted 기본값은dev다.filters[]는 uniquefilter(repeat_guard|schema_gate|provider_error) policy이다.enabledomitted=true,enforcementomitted=blocking,capabilityomitted=output.<filter>,hold_evidence_runesomitted=500,timeout_msomitted=5000으로 정규화하며 selector는environment|model_group|model|provider로만 filter enablement/enforcement를 보정한다. base-disabled filter도 registry snapshot에 남아 더 구체적인 selector가 활성화할 수 있고, 실제 target에서 활성화된blockingfilter만 provider capability admission에 참여한다.observe_only는 evidence를 만들지만 admission을 막지 않는다.repeat_guarduses the configured rune bound for active request-local history/current-stream inspection and stores only bounded fingerprints, counts, and offsets in its semantic snapshot and observations.schema_gateandprovider_errorremain lifecycle foundations until their matcher Tasks; an unmatched provider error never creates exact replay. Config accepts no caller/agent selector.openai.stream_evidence_gate설정은 request-start 시점에 snapshot으로 고정되며 in-flight request의 실행 중 refresh 영향에서 격리된다 (generation isolation). 새 generation의 설정은 이후 시작되는 새 request에만 적용된다.- The request-start
models[].context_window_tokenssnapshot is the resume builder's target context bound. Each Chat/Responses runtime shares one request-local content/reasoning recorder across its initial and recovery event sources. A continuation rebuild uses only that recorder and the fixed directive; unknown or exceeded context rejects the rebuild before re-admission. An omitted caller temperature selects0.2,0.4, then0.6by continuation strategy attempt, while an explicit value is preserved. Recorder state and its raw values remain request-local, are consumed once per attempt, and are never added to config refresh state or observations. Repeat history and counters are pinned to the same request-start config generation and are not refreshable TTL/session state. openaideep diff는 restart-required로 분류한다.openai.principal_tokens[],openai.stream_evidence_gate, top-level 및openai.model_routes[].provider_id변경은 restart-required classifier에 포함된다.- Changes to either
protocol_profilesornodes[].providers[].profileare restart-required. Immutable runtime snapshots describe the loaded configuration only; they do not make profile catalog or selector changes live-refreshable. openai.model_routes[]는 외부 OpenAI-compatiblemodelid를 내부adapter + targetroute로 매핑하는 compatibility catalog다. direct dispatch의 provider attribution identity는 route-levelprovider_id를 우선하고, 없으면 top-levelopenai.provider_id를 사용한다.openai.enabled=true이면 단일 target fallback도 dispatch 가능하므로 top-level fallback은 nonblank여야 하며, 이 검증은 기존 route/provider/model 진단 뒤에 수행한다. legacy direct provider id는 명시적 attribution identity이며nodes[].providers[].id참조를 요구하지 않고 adapter 문자열에서 추론하지 않는다.long_context_threshold_tokens는 Edge root의 입력 토큰 추정 기준 long-context 분류 threshold다. 기본값은100000이며 0 이하 값은 config load에서 거부한다.provider_pool.max_queue와provider_pool.queue_timeout_ms는 모든 model group과 provider candidate에 공통인 Edge provider-pool queue policy의 canonical owner다.max_queue는 Edge provider-pool 전체 pending 상한이며 0/생략은 기본값16으로 정규화된다.queue_timeout_ms는 각 pending request의 최대 대기 시간이며 명시적0은 timeout 없음, 생략은 기본값30000이다.- canonical
provider_poolkey가 없을 때만 legacynodes[].providers[].max_queue/queue_timeout_ms를 compatibility 입력으로 읽는다. 참여 provider의 유효 pair가 모두 같으면 root policy로 승격하고, 하나라도 다르면 first-candidate 값을 택하지 않고 load를 거부한다. canonical root key가 있으면 legacy provider queue 값은 effective policy와 refresh diff에 영향을 주지 않는다. models[]는 provider pool 방향의 canonical routing key이며nodes[].providers[].id를 참조한다.usage_attribution은provider|model_group만 허용하고 생략 시provider로 해석한다.model_group은 운영자가 model-group 귀속을 명시적으로 승인하는 opt-in이다.context_window_tokens는 해당 model group의 provider 공통 단일 요청 최대 context 계약이다.default_max_tokens,min_max_tokens,default_thinking_token_budget은 OpenAI-compatible 요청을 내부 실행으로 넘기기 전에 적용하는 모델 단위 generation policy다.- 하나의
models[]entry는 OpenAI-compatible provider와 normalized-only provider를 함께 참조할 수 있다. 선택된 provider가 OpenAI-compatible 호출 방식을 지원하면 passthrough 실행 경로를 사용하고,ollama/cli같은 normalized-only provider면 normalized 실행 경로를 사용한다. Ollama 후보는 model group에서 제거하지 않고capacity와priority로 낮은 동시성/선호도를 표현한다. nodes[].providers[]는 Node 아래 resource/provider catalog다.category는api,cli,local_inferenceresource kind를 나타낸다.nodes[].providers[].type의seulgivibe_claude와seulgivibe_openai는 runtime type을openai_compat로 정규화한다. Edge가 Node adapter payload를 만들 때 명시 provider label이 없으면 원래 Seulgivibe type alias를OpenAICompatAdapterConfig.provider로 보존한다.nodes[].providers[].id는 전체 Edge config 안에서 중복되면 안 된다.nodes[].providers[].adapter는 같은 Node 안의 enabled adapter instance key를 참조해야 한다. Exact instance key를 우선하고, legacy type-name route는 같은 type의 enabled instance가 정확히 하나일 때만 허용한다.category: cliresource는 enabled CLI adapter가 필요하다.nodes[].providers[].enabled: 생략 또는true→ provider pool dispatch 후보에 포함.false→ dispatch pool에서 제외. 비활성화된 provider는 status snapshot에status=disabled,health=disabled,capacity=0으로 표시된다. adapter process lifecycle 변경 없음. config refresh 시enabled토글은 live-apply(restart 불필요)로 분류된다. disabled provider의 adapter reference check는 skip되지만 structural validation(type, category, models, numeric bounds)은 수행된다.nodes[].providers[].capacity와long_context_capacity는node_id + provider_idresource가 소유한다. 같은 provider를 참조하는 여러models[].id는 일반·long slot을 합산 공유한다.total_context_tokens는 runtime counter가 아니라context_window_tokens * long_context_capacity이상이어야 하는 정적 load/refresh validation 값이다.nodes[].providers[].priority: provider-pool dispatch tie-breaker다. 기본값은0이고 음수는 validation error다. dispatch는in_flight < capacity후보 중 가장 낮은in_flight를 먼저 선택하며,in_flight가 같은 후보에서만 낮은 숫자의priority를 우선한다.in_flight와priority가 모두 같으면 기존 순환을 유지한다. priority 변경은 live-apply(restart 불필요)로 분류된다.- legacy single-instance adapter 설정은 load 시 named instance slice로 normalize된다.
NodeConfigPayload는 Edge가 Node에 내려주는 실행 adapter/runtime payload다.provider_id와 effectiveusage_attribution은 OpenAI route에서 Edge service dispatch result까지 보존되는 Edge-local attribution binding이다. 기존RunRequest/ProviderTunnelRequestprotobuf payload에는 새 필드를 추가하지 않으며 Edge-Node wire schema를 바꾸지 않는다.- refresh 결과는
applied,restart_required,rejected를 구분하고, changed node/provider/model/report slice는 안정적으로 non-nil이어야 한다.
refresh 분류 기준
- live apply 가능: Edge root
long_context_threshold_tokens,provider_pool.max_queue,provider_pool.queue_timeout_ms, provider capacity, provider long-context capacity, provider total-context validation budget, provider priority, providerenabledtoggle,models[]display/context window/provider/generation/usage_attributionpolicy mapping, legacy node runtime concurrency metadata. 기존 lease는 유지하며 새 admission과 모든 pending item은 새 policy/candidate 상태로 재평가한다. - restart required: Edge identity/listen/bootstrap/logging/metrics/console/control-plane/openai/a2a listener config, node 추가/삭제, node token/alias/agent kind, adapter 설정, provider type/category/adapter/models/health/lifecycle capability, provider-first execution fields(
provider,endpoint,base_url,headers,command,args,env,mode,resume_args,output_format,context_size,request_timeout_ms) 변경. - rejected: candidate config load/validate 실패, invalid refresh mode, apply failure.
금지 사항
- 설정 파일만 바꾸고
packages/go/configloading/default/validation과 불일치하게 두지 않는다. - provider id, model id, route id의 의미를 섞지 않는다. 외부
model은 OpenAI-compatible 경계의 route key이고, 내부 실행은adapter + target이다. - credential, bearer token, private endpoint 원문을 tracked docs/roadmap/contract에 기록하지 않는다.
- provider auth raw token 원문을 config, docs, task artifact, metric label에 기록하지 않는다.
- Node bootstrap 기본 경로에서 사용자가 직접 node config를 작성하거나 adapter/provider 값을 명령줄로 넣어야 하는 계약을 만들지 않는다.
변경 시 확인할 코드/테스트
packages/go/config/edge_openai_config_test.gopackages/go/config/edge_runtime_config_test.gopackages/go/config/node_config_test.gopackages/go/config/provider_catalog_config_test.gopackages/go/config/provider_catalog_validation_config_test.goapps/edge/internal/configrefresh/node_runtime_classify_test.goapps/edge/internal/configrefresh/path_refresh_test.goapps/edge/internal/configrefresh/provider_classify_test.goapps/edge/internal/edgecmd/edgecmd_test.goapps/edge/internal/node/mapper_test.goapps/node/internal/adapters/config_set_test.goagent-test/local/platform-common-smoke.mdagent-test/local/edge-smoke.md