# IOP Architecture ## 개요 IOP(Inference Operations Platform)는 모델 서빙과 CLI Agent/Automation 실행을 함께 다루는 실행 오케스트레이션 플랫폼을 지향한다. 단순한 OpenAI-compatible proxy나 모델 라우터가 아니라, Control Plane - Edge - Node 계층으로 로컬 실행 그룹을 관리하는 구조를 목표로 한다. 현재 구현은 Edge-Node 실행 스켈레톤을 검증하는 단계다. Control Plane과 worker는 placeholder이며, 세부 계약은 후속 작업에서 정리한다. ## 계층 구조 ```text Control Plane ├─ Edge Group A │ ├─ Node 1 │ │ ├─ adapter: cli │ │ └─ adapter: ollama │ └─ Node 2 │ └─ adapter: vllm └─ Edge Group B └─ Node 3 └─ adapter: cli ``` Control Plane은 Node에 직접 연결하지 않는다. Control Plane은 Edge 연결을 관리하고, Edge 상태를 조회하고, Edge 설정을 조정하고, Edge에 명령을 전달하는 중앙 관리 계층이다. Edge는 여러 Node를 묶는 실행 그룹 컨트롤러다. Edge는 Node registry, adapter/profile configuration, routing, stream relay, session handling, event aggregation을 담당한다. Node는 실제 실행자다. Node는 Edge에 연결되어 adapter execution을 수행하고, RuntimeEvent를 Edge로 돌려준다. 연결 생명주기와 이후 제어/상태성 이벤트는 RunEvent와 분리된 EdgeNodeEvent envelope로 다룬다. ## 현재 Edge-Node 흐름 현재 검증 중인 기본 흐름은 다음과 같다. ```text Edge └─ accepts Node connection └─ validates token └─ sends NodeConfigPayload └─ relays RunRequest / NodeCommandRequest Node └─ receives adapter/runtime config └─ builds adapter registry └─ resolves adapter + target └─ executes adapter └─ emits RuntimeEvent / EdgeNodeEvent ``` 현재 실행 이력은 Node의 local SQLite store에서 검증 중이다. Edge 단위 이력 집계와 로컬 실행 그룹 상태 소유권은 로드맵에 따라 정리한다. edge-local ops console의 `/` 명령은 ops console의 수동 테스트 표면이다. ops console은 `edge/internal/service`를 호출하는 얇은 어댑터로 유지하고, 이후 HTTP/API 표면(central/remote management surface)도 같은 service와 `edge/internal/events` bus를 재사용한다. ops console과 HTTP/API를 구분한다: ops console은 edge-local diagnostic surface이고, HTTP/API는 central/remote management surface다. ## 내부 실행 모델 내부 실행은 `adapter + target`으로 표현한다. - `adapter`: 실행 방식. 예: `mock`, `ollama`, `vllm`, `cli` - `target`: adapter 안의 실행 대상. 예: 모델 이름, CLI profile, agent/toolchain - `execution`: 특정 adapter와 target으로 수행되는 단일 실행 외부 OpenAI-compatible API에서는 호환성을 위해 `model` 필드가 남을 수 있다. 그러나 내부 실행 계약에서는 `target`을 우선한다. ## 외부 API 표면 Edge의 외부 입력 표면은 두 방식을 병행한다. - **OpenAI-compatible HTTP API**: 외부 모델 클라이언트, Cline류 도구, 단순 chat completion/inference 호환을 위한 입력이다. 이 경계에서는 `model`을 받을 수 있지만 Edge 내부에서는 `adapter + target`으로 변환한다. - **A2A JSON-RPC HTTP API**: NomadCode Core나 외부 agent가 작업을 위임하고 `Task` 상태, artifact, cancel/polling을 공유해야 할 때 사용하는 agent delegation 입력이다. 이 경계도 Edge 내부에서는 `adapter + target`으로 변환한다. 두 입력 방식은 Edge inbound/input surface로 함께 관리하고, 내부 실행은 기존 `RunRequest`/`RunEvent` 흐름으로 수렴시킨다. IOP native protocol은 Control Plane, Portal, 운영 CLI, 고급 자동화 클라이언트를 위한 IOP 고유 wire protocol이다. 기존 protobuf-socket 흐름을 기준으로 `RunRequest`, `RunEvent`, `NodeCommandRequest`, `EdgeNodeEvent` 계열 계약을 확장한다. OpenAI-compatible API와 A2A API는 외부 호환 입력으로 유지하지만, node 선택, logical session, background run, terminate-session, capabilities/status/session/transport command, node lifecycle event 같은 운영 기능을 억지로 `model`이나 vendor extension에 싣지 않는다. 이런 기능은 IOP native protocol에서 명시적인 필드와 이벤트로 다룬다. ## apps/node 내부 구조 ```text Hexagonal Architecture ┌───────────────────────────────┐ │ node.Node │ application core │ │ TCP │ transport.Handler interface │ ─────────► OnRunRequest() │ edge │ OnCancel() │ │ OnCommandRequest() │ └───────┬───────────────────────┘ │ router.Resolve() -> ExecutionSpec │ adapter.Execute() -> RuntimeEvent stream │ store.CompleteRun() -> SQLite ``` ### 어댑터 패턴 ```go type Adapter interface { Name() string Capabilities(ctx) (Capabilities, error) Execute(ctx, spec ExecutionSpec, sink EventSink) error } ``` 새 어댑터를 추가할 때는 `runtime.Adapter`를 구현하고 registry에 등록한다. CLI Agent, 모델 런타임, 도구 실행은 모두 같은 adapter execution 경로로 다루는 방향을 유지한다. ## 통신 방향 - Edge-Node 내부 통신은 TCP + protobuf 기반 소켓 흐름을 우선한다. - Node는 Edge에 연결한다. - Control Plane은 향후 Edge와 소켓 기반 연결을 맺는다. - Control Plane이 Node에 직접 연결하거나 매 요청마다 Node를 직접 스케줄링하는 구조는 목표가 아니다. - Portal-Control Plane, Control Plane-Edge, Edge-Node의 장기 통신 기준은 IOP Wire Protocol(protobuf-socket)이다. 브라우저가 직접 TCP를 사용할 수 없는 구간은 Web Portal이 직접 wire client가 되기보다 Control Plane의 server-side bridge를 통해 연결한다. - OpenAI-compatible HTTP API와 A2A JSON-RPC HTTP API는 Edge 외부 입력 표면으로 유지하며, Control Plane 운영 제어 프로토콜의 기본값으로 삼지 않는다. ## 배포 철학 Control Plane과 Web은 중앙 운영면으로 묶어 compose 기반 서비스로 배포한다. dev 필드 테스트 compose에는 Control Plane DB와 Redis 후보를 함께 포함해 이후 schema, audit, event, queue 작업을 같은 배포 단위 안에서 진행한다. Edge와 Node는 Docker 이미지로 만들지 않고 호스트 단일 바이너리로 배포한다. Jenkins는 Edge/Node 바이너리만 빌드하고, 각 호스트는 배포된 바이너리의 `setup` 명령을 통해 systemd 실행 환경을 준비한다. 운영 CLI는 분기된 설치 방식을 만들지 않는다. 공식 경로는 `iop-edge setup`과 `iop-node setup` 하나로 고정하고, 검토나 CI 확인은 별도 `render` 명령이 아니라 `--dry-run` 옵션으로 흡수한다. ```text build: Jenkins -> bin/build/field-binaries.sh -> iop-edge, iop-node control host: docker compose -> postgres, redis, control-plane, web edge host: iop-edge setup -> systemd unit + config/data directories [구현 완료] systemd -> iop-edge serve --config /etc/iop/edge.yaml node host: iop-node setup -> systemd unit + config/data directories [구현 완료] systemd -> iop-node serve --config /etc/iop/node.yaml ``` `setup`은 idempotent해야 한다. 기존 설정 파일은 기본적으로 덮어쓰지 않고, 필요한 디렉터리와 unit만 생성/갱신하며, `--enable`, `--start`, `--restart`, `--dry-run` 같은 옵션으로 운영 차이를 표현한다. `status`, `logs`, `start`, `stop`, `restart` 같은 명령은 초기 CLI에 넣지 않고 `systemctl`과 `journalctl`을 기준 운영 도구로 둔다. ## 라이브러리 선택 | 라이브러리 | 역할 | |------------|------| | `go.uber.org/fx` | 의존성 주입 | | `go.uber.org/zap` | 구조화 로깅 | | `github.com/spf13/cobra` | CLI | | `github.com/spf13/viper` | 설정 파일 | | `google.golang.org/protobuf` | Protobuf | | `github.com/prometheus/client_golang` | 메트릭 | | `modernc.org/sqlite` | 현재 Node local 실행 이력 저장 | ## 금지 사항 - gRPC (`grpc-go`) 사용 금지 - WebSocket 기본 transport 사용 금지 - Actor framework 사용 금지 - External worker pool 사용 금지 - FSM framework 사용 금지 - Plugin framework 사용 금지 - Kubernetes 의존성 금지