iop/docs/architecture.md

189 lines
12 KiB
Markdown

# IOP Architecture
## 개요
IOP(Inference Operations Platform)는 모델 서빙과 CLI Agent/Automation 실행을 함께 다루는 실행 오케스트레이션 플랫폼을 지향한다. 단순한 OpenAI-compatible proxy나 모델 라우터가 아니라, Control Plane - Edge - Node 계층으로 로컬 실행 그룹을 관리하는 구조를 목표로 한다.
현재 구현은 Edge-Node 실행 스켈레톤을 검증하는 단계다. Control Plane과 worker는 placeholder이며, 세부 계약은 후속 작업에서 정리한다.
제품 Phase와 Milestone 상세는 `agent-ops/roadmap/ROADMAP.md`에서 관리하고, 이 문서는 상대적으로 덜 변하는 구조 원칙과 책임 경계를 설명한다.
## 계층 구조
```text
Control Plane
├─ Edge Group A
│ ├─ Node 1
│ │ ├─ adapter: cli
│ │ └─ adapter: ollama
│ ├─ Node 2
│ │ └─ adapter: vllm
│ └─ OTO Agent
│ └─ domain: build/deploy
└─ 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만 관리하는 컨트롤러로 고정하지 않는다. OTO처럼 독립 바이너리와 독립 실행 도메인을 가진 도구는 specialized domain agent로 Edge에 직접 연결할 수 있다. 이 경우 OTO agent는 `iop-node`의 하위 실행물이 아니라 Edge가 별도 agent type으로 등록하고 제어하는 build/deploy 실행자다.
Agent를 설치하기 어려운 대상은 remote terminal bridge로 다룬다. Edge는 terminal session broker가 되고, 대상에 도달 가능한 Node가 local shell, SSH, WinRM, serial 같은 transport를 열어 stdin/stdout/stderr, resize, signal, session lifecycle을 relay한다. 이 경로는 target device에 IOP agent를 설치하지 않는 운영 방식이지만, 네트워크나 credential 같은 접근 경로 없이 대상을 제어하는 완전 agentless 모델은 아니다.
## 현재 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
OTO Agent
└─ installed by Edge bootstrap command
└─ registers as build/deploy domain agent
└─ receives run/cancel/status/capability messages
└─ executes YAML pipeline
└─ emits step/log/artifact/result events
```
현재 실행 이력은 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에 연결한다.
- OTO 같은 domain agent도 Edge에 직접 outbound 연결한다.
- Remote terminal bridge는 Control Plane이 Node에 직접 붙는 구조가 아니라, Control Plane/Portal/운영 CLI가 Edge에 terminal session을 요청하고 Edge가 적절한 Node terminal transport로 중계하는 구조로 둔다.
- Control Plane은 향후 Edge와 소켓 기반 연결을 맺는다.
- Control Plane이 Node에 직접 연결하거나 매 요청마다 Node를 직접 스케줄링하는 구조는 목표가 아니다.
- OTO agent는 Control Plane이나 `iop-node`에 직접 종속되지 않고 Edge의 agent registry와 bootstrap/enrollment 계약을 따른다.
- 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 실행 환경을 준비한다. OTO는 별도 프로젝트의 단일 바이너리 build/deploy agent로 두고, 장기적으로는 Edge가 OTO agent 생성과 bootstrap command 발급을 담당한다.
Edge bootstrap UX는 Jenkins node 연결 방식과 유사하게 둔다. Edge에서 agent를 먼저 만들고 one-time token이 포함된 bootstrap command를 발급하면, 대상 머신이 이 command를 실행해 agent 바이너리 설치, 설정 생성, service 등록, Edge 등록을 완료한다. 폐쇄망과 개발망을 고려해 HTTPS를 권장하되 강제하지 않고, HTTP local/insecure 모드는 one-time token, Edge fingerprint 또는 public key pinning, checksum/signature 검증을 함께 요구한다.
Remote terminal bridge UX는 bootstrap과 분리한다. 대상에 agent를 설치할 수 있으면 bootstrap/enrollment를 우선하고, 설치가 어렵거나 일회성 유지보수 대상이면 Edge가 사전에 허용된 Node terminal profile을 통해 대상 terminal을 연다. 이 기능은 target allowlist, credential 관리, timeout, 동시 실행 제한, session audit를 전제로 한다.
운영 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
oto build host:
edge bootstrap command -> oto binary + agent config/service
systemd -> oto agent --config /etc/oto/agent.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 의존성 금지