---
domain: edge
last_rule_review_commit: 7ca329ac9e03bf7cfebfac4517559fc1e2f0bca8
last_rule_updated_at: 2026-07-14
---

# edge

## 목적 / 책임

여러 Node를 하나의 로컬 실행 그룹으로 묶는 백엔드 실행 그룹 컨트롤러 영역이다. node 연결을 수락하고 token 기반 등록을 검증한 뒤 adapter/runtime 설정을 내려주며, `iop-edge` command 중심의 local/field 운영 UX, ops console, OpenAI-compatible HTTP, A2A JSON-RPC, Control Plane outbound connector를 내부 Edge-owned operation으로 수렴시킨다. Edge는 자신의 설정, Node registry, runtime/automation 상태의 원본 소유자다.

## 포함 경로

- `apps/edge/cmd/edge/` — edge CLI thin entrypoint와 serve/console runtime wiring
- `apps/edge/internal/bootstrap/` — fx 의존성 주입과 서버 시작/종료 lifecycle
- `apps/edge/internal/configrefresh/` — Edge config 후보 로딩, 변경 분류, dry-run/apply 평가
- `apps/edge/internal/controlplane/` — Edge에서 Control Plane으로 outbound 연결하는 proto-socket connector
- `apps/edge/internal/edgecmd/` — config/env/setup/bootstrap/node/nodes/smoke/version CLI command 구현
- `apps/edge/internal/edgevalidate/` — Edge config 구조와 provider/model/adapter 참조 검증
- `apps/edge/internal/events/` — run/node event in-process fanout bus
- `apps/edge/internal/input/` — OpenAI-compatible/A2A 입력 서버 lifecycle 관리
- `apps/edge/internal/node/` — 연결된 node registry와 node 선택
- `apps/edge/internal/openai/` — OpenAI-compatible HTTP 입력 표면
- `apps/edge/internal/opsconsole/` — edge-local 운영 콘솔
- `apps/edge/internal/service/` — console/HTTP 입력 표면이 공유하는 실행·명령 application service
- `apps/edge/internal/transport/` — node 연결 수락, RegisterRequest/Response 처리, run event 수신
- `apps/edge/README.md` — edge 실행 흐름과 운영 표면 설명
- `apps/edge/Dockerfile` — Edge 배포용 이미지 빌드 표면

## 제외 경로

- `apps/node/` — 실제 모델 실행과 adapter 관리
- `apps/control-plane/` — 여러 Edge 연결 관리와 운영 제어 API 제공 영역
- `apps/client/` — Control Plane을 소비하는 Flutter client UI/UX 영역
- `apps/worker/` — 비동기 작업 처리 예정 영역
- `packages/go/` — 공통 설정/관측성/인증 Go 패키지
- `proto/` — 메시지 계약 원본과 생성물

## 주요 구성 요소

- `transport.Server` — proto-socket TCP 서버 wrapper; NodeStore와 Registry를 모두 주입받아 등록 흐름을 처리
- `node.Registry` — 연결된 node의 `NodeEntry` 저장/조회 (ID·alias 이중 인덱스, 동시성 안전)
- `node.NodeEntry` — node ID, alias, TCP client 정보
- `node.NodeStore` — 사전 등록된 `NodeRecord`를 token/ID로 조회; 설정 파일에서 seed
- `node.NodeRecord` — 사전 등록된 node 정의 (ID, alias, token, adapter/runtime config)
- `node.BuildConfigPayload()` — `NodeRecord`의 adapter 설정을 proto `NodeConfigPayload`로 변환하는 mapper
- `edgecmd.NewRoot()` / `edgecmd.LoadEdgeConfig()` — bundle-local `edge.yaml`, binary-adjacent config, repo-dev fallback을 처리하는 CLI command root와 config loader
- `config init` / `config check` / `config print` — bundle-local Edge 설정 생성·검증·출력 command
- `config refresh` — Edge refresh admin API에 dry-run/apply 요청을 보내 runtime mutable config를 갱신하는 command
- `env` — config discovery, advertise host, log path, node bootstrap URL, OpenAI/A2A URL을 서버 기동 없이 출력하는 진단 command
- `node register` — `edge.yaml`의 `nodes[]`를 보존형 YAML patch로 추가/갱신하고 one-line bootstrap command를 출력하는 command
- `nodes list` — 설정된 node record를 offline/configured 상태로 조회하는 command
- `smoke openai` — `/healthz`, `/v1/models`, `/v1/chat/completions`, `/v1/responses`를 확인하는 OpenAI-compatible 진단 command
- `bootstrap.Module` — fx lifecycle에 `bootstrap.Runtime` start/stop을 연결
- `bootstrap.Runtime` — logger, registry, node store, event bus, service, transport server, input manager, artifact server, refresh admin server, Control Plane connector를 묶는 실행 조립체
- `bootstrap.ArtifactServer` — Node bootstrap artifact를 제공하는 Edge-local HTTP server
- `bootstrap.RefreshAdminServer` / `bootstrap.Runtime.RefreshConfig()` — Edge-local config refresh dry-run/apply HTTP 표면과 runtime config swap
- `configrefresh.Classify()` / `configrefresh.Evaluate()` — Edge config 변경을 rejected/applied/restart_required로 분류하고 candidate config를 산출
- `edgevalidate.ValidateEdgeConfig()` — provider/model/adapter 참조와 mock surface 설정을 포함한 Edge config 검증
- `controlplane.Connector` — Control Plane TCP wire에 outbound로 연결하고 hello/status/event relay를 처리하는 connector
- `controlplane.StatusProvider` — Control Plane status request에 답할 Edge-owned node snapshot provider boundary
- `events.Bus` — `RunEvent`와 `EdgeNodeEvent` subscriber fanout 및 bounded replay
- `service.Service` — node 선택, run dispatch, provider pool admission, provider tunnel routing, cancel/terminate-session, node/edge command 요청을 표면 중립 DTO로 제공
- `service.NodeSnapshot` — Control Plane status response에 쓰는 Edge-owned node snapshot DTO
- `service.RunHandle` — foreground run event stream과 dispatch metadata를 함께 들고 있는 handle
- `service.modelQueueManager` — provider/model group capacity, queue, long-context slot admission과 release를 관리
- `service.SubmitProviderPool()` / `service.SubmitProviderTunnel()` — OpenAI-compatible provider-pool 요청을 단일 admission 후 normalized run 또는 raw provider tunnel로 dispatch
- `service.ExecuteCommand()` — Control Plane `EdgeCommandRequest`를 Edge-owned operation으로 실행하고 command event를 emit
- `service.NodeCommandView` / `UsageStatusView` — console/HTTP/RPC surface가 공유할 수 있는 node command 결과 DTO
- `input.Manager` — OpenAI-compatible 서버와 A2A 서버 lifecycle 소유자
- `openai.Server` — `/v1/models`, `/v1/chat/completions`, `/v1/responses`, SSE stream, strict output/tool validation, usage metering, principal token auth, provider pool/passthrough, Ollama API passthrough를 `service`로 연결하는 HTTP 표면
- `a2a.Server` / `a2a.TaskStore` — A2A `message/send`, `tasks/get`, `tasks/cancel`과 task 상태 보관
- `opsconsole.Run` — edge-local console loop와 slash command 처리
- `opsconsole.EventRouter` — run/node event를 edge console 출력으로 라우팅

## 유지할 패턴

- edge는 사전 등록된 node 정의를 검증하고 연결 상태를 registry에 반영하는 역할을 먼저 안정화한다.
- edge는 단순 gateway가 아니라 Node registry, adapter/profile configuration, routing, stream relay의 소유자다.
- edge는 `edge.yaml`, node 정의, adapter/runtime 설정, 로컬 runtime/automation 상태의 canonical owner다. Control Plane 연결 대상이 바뀌어도 Edge 실질 데이터 migration을 요구하지 않는 구조를 유지한다.
- 공식 local/field 운영 UX는 `iop-edge` command 표면에 모은다. repo helper script나 README 절차를 공식 사용자 경로로 승격하지 않는다.
- `iop-edge` command 표면은 bootstrap, bundle-local config, env/config 진단, node 등록 command 발급, smoke, Control Plane 없는 단일 Edge 유지보수 범위로 유지한다. 후속 specialized agent enrollment command도 같은 local/field fallback 원칙을 따른다. 상시 multi-edge 운영 UI/API 역할을 CLI에 확대하지 않는다.
- `config init`은 bundle-local `edge.yaml` template을 만들고, `env`는 서버를 기동하지 않고 effective environment를 설명해야 한다.
- CLI command 지원 로직은 `apps/edge/internal/edgecmd`에 두고 `apps/edge/cmd/edge`는 cobra root 조립, serve/console runtime wiring 같은 얇은 entrypoint로 유지한다.
- `node register`는 사용자에게 대상 OS에 맞는 완성된 bootstrap URL과 실제 token 값을 포함한 한 줄 명령을 출력하는 경로를 유지한다. Linux/macOS는 `curl -fsSL ... | bash -s ...`, Windows native는 PowerShell `.ps1` bootstrap과 `Start-IopNode`를 기준으로 한다. 사용자가 기본 경로에서 `node.yaml`을 직접 만들거나 `iop-node serve --config ...`를 직접 실행하게 하지 않는다.
- bootstrap 사용자 기본 명령은 완성된 bootstrap URL과 positional token 하나만 포함한다. Edge address, artifact URL, target/platform, install/config path, metrics port, workspace path 같은 값은 Edge 설정이나 bootstrap script 기본값에 굽고, 사용자가 직접 추가 설정해야 하는 경우에는 기본 명령과 분리된 optional override로만 안내한다.
- 사용자에게 실행 방법을 안내할 때는 placeholder 형식을 쓰지 않는다. token 같은 secret을 현재 채널에 표시할 수 없으면 runnable command처럼 꾸미지 말고, 먼저 안전한 secret 전달 절차를 끝낸 뒤 실제 값이 들어간 완성 명령만 제공한다.
- 사용자 기본 명령 안에 `ssh`, `awk`, 원격 파일 조회, command substitution을 섞어 token을 즉석에서 추출하게 하지 않는다.
- 외부 OpenAI-compatible HTTP API는 입력 표면일 뿐이며, 내부 실행 모델 전체를 대표하지 않는다.
- node가 연결 직후 RegisterRequest를 보내고 edge가 RegisterResponse로 중앙 설정을 응답하는 흐름을 유지한다.
- Registry는 동시성 안전성을 유지하고 외부로 내부 map을 노출하지 않는다.
- NodeStore는 설정 파일에서 한 번 seed되며 런타임 중 변경하지 않는다; token 중복·빈 token은 LoadFromConfig에서 즉시 거부한다.
- adapter config 변환(mapper)은 `node.BuildConfigPayload()`에서만 수행하고 transport 레이어에 변환 로직을 두지 않는다.
- config refresh는 `configrefresh`가 후보 config를 검증·분류하고, runtime mutable 변경만 `Runtime.RefreshConfig()`에서 service/node store/openai catalog에 반영한다. listener address, Edge identity, bootstrap artifact dir처럼 restart가 필요한 변경을 live apply로 조용히 적용하지 않는다.
- 내부 실행 요청은 `adapter + target`으로 표현한다. 외부 API 호환 경계의 `model` 표현을 edge 내부 책임 전체로 확장하지 않는다.
- ops console과 HTTP 입력 표면은 `apps/edge/internal/service`를 호출하는 얇은 어댑터로 유지한다. node 선택, run dispatch, command request 생성은 service 계층에서 공유한다.
- OpenAI-compatible 경계의 `model`과 A2A 경계의 `Task`/JSON-RPC 표현은 입력 표면 안에서만 유지하고, edge 내부 실행은 `service.SubmitRun()`의 `adapter + target` 요청으로 변환한다.
- provider pool에서는 top-level `models[]`의 id를 canonical model group key로 보고, `models[].providers`와 `nodes[].providers[]`를 통해 provider id별 served model로 rewrite한다. caller metadata나 request body의 임의 field가 provider 선택권을 갖지 않게 한다.
- OpenAI-compatible raw passthrough는 `ProviderTunnelRequest`/`ProviderTunnelFrame` 경계와 `service.SubmitProviderTunnel()`을 통해서만 수행한다. HTTP handler가 node transport client에 직접 provider tunnel message를 쓰지 않는다.
- OpenAI principal token auth는 raw token을 config에 저장하지 않고 `principal_tokens[].token_hash_sha256`과 resolved principal metadata만 사용한다. caller-supplied metadata의 `iop_principal_*` 값은 인증된 principal을 덮어쓸 수 없다.
- OpenAI-compatible `/v1/models`는 `openai.models`를 우선하고, 없으면 `openai.target`을 advertised model로 사용한다. 내부 target override와 외부 model echo 정책을 혼동하지 않는다.
- OpenAI/Ollama passthrough성 옵션은 입력 표면에서 명시적으로 변환하고, node adapter의 Ollama 실행 계약을 우회하지 않는다.
- OpenAI-compatible, A2A, Edge-Node, Control Plane connector, config/runtime refresh 계약 상세는 `agent-contract/index.md`에서 매칭 문서를 확인하고 README/rules에 schema 본문을 반복하지 않는다.
- run/node event fanout은 `events.Bus`를 통해 수행하고, transport handler에서 console/HTTP 표면으로 직접 출력하거나 응답하지 않는다.
- 입력 서버 lifecycle은 `input.Manager`가 소유하며, bootstrap runtime이 transport server와 함께 시작/종료한다.
- Control Plane connector는 disabled 설정 또는 빈 `wire_addr`에서 no-op이어야 한다. 연결 재시도와 stop은 Edge serve lifecycle에 묶으며, Control Plane attachment 변경이 Edge-owned 설정/상태의 소유권을 바꾸지 않는다.
- Control Plane status 응답은 `service.Service`의 `ListNodeSnapshots()` 같은 Edge-owned 표면에서 만든다. connector가 registry나 transport internals를 직접 읽어 제품 계약을 만들지 않는다.
- Control Plane node lifecycle event relay는 `events.Bus` 구독으로 수행하고, event metadata에는 필요한 Edge identity만 보강한다.
- CLI, ops console, HTTP/A2A 입력 표면, Control Plane wire connector가 같은 Edge operation을 노출할 때는 `service.Service` 같은 표면 중립 application service로 수렴시킨다. registry 변경, run dispatch, command request 로직을 표면별로 복제하지 않는다.
- `apps/edge/cmd/edge/**`, `apps/edge/internal/bootstrap/**`, `apps/edge/internal/configrefresh/**`, `apps/edge/internal/controlplane/**`, `apps/edge/internal/edgecmd/**`, `apps/edge/internal/edgevalidate/**`, `apps/edge/internal/transport/**`, `apps/edge/internal/service/**`, `apps/edge/internal/events/**`, `apps/edge/internal/input/**`, `apps/edge/internal/openai/**`, `apps/edge/internal/opsconsole/**`, `apps/edge/internal/node/**`, console/HTTP/Control Plane 입출력, run/node event relay를 바꾼 뒤에는 `testing` domain rule의 작업 후 검증 기준을 따른다.

## 다른 도메인과의 경계

- **node**: edge는 node 내부 adapter를 직접 실행하지 않는다. edge는 사전 등록 정보와 연결 registry를 기반으로 요청을 보낼 대상과 실행 설정을 관리하고, TCP/protobuf로 `RunRequest`/`CancelRequest`/`NodeCommandRequest`를 보낸다.
- **platform-common**: edge 설정, metrics, protobuf 타입은 platform-common 계약을 따른다.
- **external input surfaces**: OpenAI-compatible HTTP와 A2A JSON-RPC는 edge inbound adapter이며, 내부 transport/protobuf 경계를 대체하지 않는다.
- **control-plane**: control-plane은 Edge를 통해 시스템을 제어한다. Edge domain은 outbound connector와 Edge-owned status/event/command 응답을 소유하고, control-plane domain은 server endpoint와 Edge connection/control view를 소유한다. Control Plane 없는 bootstrap/local/field/진단 fallback은 `iop-edge` command 표면에 남긴다.

## 금지 사항

- edge를 구현하면서 node adapter 실행 로직을 복제하지 않는다.
- OpenAI-compatible HTTP API를 추가할 때 내부 TCP/protobuf 경계를 우회하지 않는다.
- A2A/OpenAI/console 입력 표면에서 registry client에 직접 `RunRequest`를 조립·전송하지 않는다. 공유 로직은 `service.Service`에 둔다.
- Control Plane 구현 전이라도 Node를 Control Plane에 직접 연결하는 경로를 edge 도메인에 추가하지 않는다.
- Control Plane을 Edge 설정, Node registry, runtime/automation 상태의 원본 저장소로 전제하지 않는다.
- Control Plane connector에서 Node token, Node address, transport client 내부 상태를 Control Plane status 계약으로 노출하지 않는다.
- config refresh에서 `restart_required`로 분류된 변경을 runtime에 부분 적용하지 않는다.
- OpenAI-compatible provider pool에서 authenticated principal, provider auth header, workspace 검증을 우회하거나 caller metadata로 대체하지 않는다.
- Control Plane 도입만을 이유로 `iop-edge config`, `env`, `node register`, `nodes list`, `smoke`, `setup` 같은 local/field fallback command 경로를 제거하거나 제품 기본 계약에서 제외하지 않는다. 축소는 별도 roadmap 결정과 대체 fallback 기준이 있을 때만 다룬다.
- `node register`와 bootstrap UX에 named environment parameter 조합을 기본 사용자 경로로 노출하지 않는다.
- bootstrap 안내를 `IOP_*=` 환경 변수 나열, shell wrapper, 원격 token 조회, 수동 `node.yaml` 작성, 수동 `iop-node serve --config ...` 흐름으로 길게 만들지 않는다. 이런 값은 디버그/고급 override 문서로만 분리한다.
- gRPC 또는 WebSocket을 기본 내부 transport로 바꾸지 않는다.
- `proto/gen/iop/*.pb.go` 생성 파일을 직접 수정하지 않는다.
