update docs and roadmap files

This commit is contained in:
toki 2026-05-28 09:57:07 +09:00
parent eb74d041b7
commit 0ac1217730
12 changed files with 18 additions and 875 deletions

View file

@ -238,6 +238,10 @@ NomadCode
Portal의 장기 UI 기준은 Flutter 앱이며, 필요한 웹 표면은 Flutter Web 산출물로 제공한다. `apps/control-plane`은 Go 기반 운영 제어 서버다. 주요 통신은 edge-node에서 사용 중인 proto-socket을 IOP Wire Protocol 기준으로 Portal-Control Plane, Control Plane-Edge, Edge-Node 방향으로 확장한다. Portal-Control Plane은 앱/브라우저 경계를 고려해 proto-socket WebSocket/WSS를 우선하고, `net/http`는 health/readiness/bootstrap 같은 보조 endpoint 용도로 유지한다.
## Guide
사람이 읽는 최신 실행 가이드는 [Edge-local Dev Guide](docs/edge-local-dev-guide.md) 하나로 유지한다.
## 로드맵
제품 방향, 단계, 마일스톤, 우선순위의 단일 기준 문서는 `agent-roadmap/ROADMAP.md`다.

View file

@ -27,7 +27,7 @@
- `bin/` — 개발 보조 shell entrypoint와 field binary build entrypoint이다. Edge/Node 운영 UX의 공식 표면은 `iop-edge`/`iop-node` 바이너리 command로 모은다.
- `scripts/` — 보조 E2E smoke와 입력 표면 검증 스크립트이다.
- `Makefile` — 빌드와 테스트 진입점을 정의한다.
- `docs/`아키텍처 및 운영 방향 문서이다. field 테스트 환경, 외부 테스트 포트, one-line bootstrap 기준은 `docs/deploy-dev.md`를 우선 진입점으로 본다.
- `docs/`사람용 최신 가이드만 둔다. 환경별 값은 `agent-ops/rules/private/`에 두고, `rules.md`는 라우터로만 쓴다.
## 기술 스택
@ -57,9 +57,8 @@
- 앱 설정 구조 변경 시 `packages/config`의 struct/default와 `configs/*.yaml` 예시를 함께 확인한다.
- 테스트는 변경 범위에 맞춰 `go test ./...` 또는 대상 패키지 테스트를 실행한다.
- 사용자 실행 파이프라인에 닿는 작업을 한 경우, 작업 완료 후 `agent-ops/rules/project/domain/testing/rules.md`의 검증 기준을 따른다.
- field 테스트 환경 또는 one-line bootstrap 작업은 `docs/deploy-dev.md`의 Field 테스트 환경 라우팅과 `docs/field-bootstrap-work-guide.md`를 먼저 확인한다.
- field/bootstrap 작업은 `testing` domain rule을 따르고, 실제 환경값이 필요하면 private rule 라우팅을 따른다.
- Node, OTO, specialized agent, Control Plane enrollment 등 사용자가 대상 host에서 실행하는 bootstrap/install command 작업은 `agent-ops/rules/project/domain/testing/rules.md`의 one-line bootstrap UX 기준을 따른다.
- code-server 기반 field 테스트 포트는 원격 `ssh toki@toki-labs.com``~/docker/services/code-server/compose/docker-compose.yml`에서 관리한다. 기준 host port는 web/dev `13000-13099`, artifact/bootstrap HTTP `18080`, OpenAI-compatible HTTP `18081`, Edge-Node transport `19090`, Edge metrics `19092`, OTO/specialized agent transport `19190`이다.
- 상세 DB schema, event schema, permission/policy/audit model, federation, mTLS 구현 세부, Portal UI 세부 기획은 각 작업에서 별도로 결정한다.
## 도메인 룰 로딩
@ -90,6 +89,6 @@
## 스킬 라우팅
- 사용자 실행 파이프라인 검증, repo 내부 edge-node 진단, 메시지 2회 왕복, edge command 응답, 보조 E2E smoke, full-cycle 실제 구동, `bin/edge.sh`/`bin/node.sh` 진단 테스트: `agent-ops/skills/project/e2e-smoke/SKILL.md`
- field 테스트 포트, artifact/bootstrap HTTP, one-line Node bootstrap, code-server compose 기반 외부 테스트 환경: `docs/deploy-dev.md``docs/field-bootstrap-work-guide.md`
- bootstrap/install UX, Agent Bootstrap, OTO 등록, Control Plane enrollment처럼 사용자가 대상 host에서 복사해 실행하는 명령을 설계하거나 바꿀 때: `agent-ops/rules/project/domain/testing/rules.md`의 one-line bootstrap UX 기준과 `docs/deploy-dev.md`의 field bootstrap 기준을 확인한다.
- field 테스트 포트, artifact/bootstrap HTTP, 외부 테스트 환경: private rule 라우팅을 따른다.
- bootstrap/install UX, Agent Bootstrap, OTO 등록, Control Plane enrollment: `testing` domain rule과 private rule 라우팅을 따른다.
- 반복 작업이 확인되면 `agent-ops/skills/project/<skill-name>/SKILL.md`를 생성하고 이 표에 등록한다.

View file

@ -73,7 +73,7 @@ OTO는 build/deploy domain agent의 대표 후보로 둔다.
## 작업 컨텍스트
- 관련 경로: `apps/edge`, `docs/deploy-dev.md`, `docs/architecture.md`, `../oto/agent-roadmap/phase/edge-direct-oto-agent/milestones/oto-agent-registration.md`, `../oto/agent-roadmap/phase/message-based-build-agent/milestones/agent-message-protocol.md`, `../oto/assets/script/shell/oto_agent_bootstrap.sh`
- 관련 경로: `apps/edge`, `README.md`, `agent-ops/rules/private/rules.md`, `../oto/agent-roadmap/phase/edge-direct-oto-agent/milestones/oto-agent-registration.md`, `../oto/agent-roadmap/phase/message-based-build-agent/milestones/agent-message-protocol.md`, `../oto/assets/script/shell/oto_agent_bootstrap.sh`
- 전환 상태: Ollama 실테스트와 후속 안정화를 우선 열어두는 동안 이 마일스톤은 계획 후보로 대기한다. 구현 시작 전 `enrollment_token` 범위와 HTTP local/insecure bootstrap 정책 결정을 먼저 해소한다.
- 표준선(선택): OTO agent는 `iop-node` 하위 프로세스가 아니라 Edge에 직접 outbound 연결하는 specialized domain agent로 둔다.
- 표준선(선택): Edge는 `oto-agent` record를 만들고, agent id/alias/enrollment token/release base URL/bootstrap script URL을 포함한 one-line command를 발급한다.

View file

@ -60,7 +60,7 @@ Edge는 terminal session broker가 되고, 대상에 도달 가능한 Node가 SS
## 작업 컨텍스트
- 관련 경로: `apps/edge`, `apps/node`, `docs/architecture.md`, `docs/deploy-dev.md`
- 관련 경로: `apps/edge`, `apps/node`, `README.md`, `agent-ops/rules/private/rules.md`
- 표준선(선택): Control Plane/Portal/운영 CLI는 Edge에 terminal session을 요청하고, Edge가 Node terminal transport로 중계한다. OpenAI-compatible/A2A payload에 terminal 제어를 싣지 않는다.
- 선행 작업: Edge-Node 실행 스켈레톤, CLI Automation Runtime 안정화
- 후속 작업: 정책, 이력, 감사; Control Plane과 Portal의 terminal session 운영 표면

View file

@ -49,7 +49,7 @@ Client는 NomadCode 전용 화면이 아니라 IOP가 독립적으로 운영되
- [x] [server-bridge] 브라우저 직접 TCP가 어려운 구간은 Control Plane server-side bridge를 둔다.
- [x] [portal-wire-hello] Client-Control Plane proto-socket WebSocket hello handshake scaffold가 구현되고 테스트되어 있다. 검증: Control Plane과 client 테스트에서 hello handshake가 통과한다.
- [x] [http-boundary] `net/http`는 health/readiness/bootstrap 같은 보조 endpoint 용도로 유지한다.
- [x] [deploy-docs] Control Plane과 Flutter client의 배포/검증 절차가 `docs/deploy-dev.md`와 일치한다.
- [x] [deploy-docs] Control Plane과 Flutter client의 배포/검증 절차가 `README.md`의 운영 경계와 private local testing rule의 환경 기준에 맞게 정리되어 있다.
## 완료 리뷰
@ -71,7 +71,7 @@ Client는 NomadCode 전용 화면이 아니라 IOP가 독립적으로 운영되
## 작업 컨텍스트
- 관련 경로: `apps/control-plane`, `apps/portal`, `apps/control-plane/README.md`, `apps/portal/README.md`, `docs/deploy-dev.md`, `docker-compose.yml`, `bin/web.sh`
- 관련 경로: `apps/control-plane`, `apps/portal`, `apps/control-plane/README.md`, `apps/portal/README.md`, `agent-ops/rules/private/rules.md`, `docker-compose.yml`, `bin/web.sh`
- 표준선(선택): Control Plane은 Node를 직접 스케줄링하지 않고 Edge를 통해 관찰/제어한다.
- 표준선(선택): Client-Control Plane의 첫 wire surface는 기존 `/portal` WebSocket hello handshake를 유지할 수 있으며, Edge 운영 조회와 명령 전달은 후속 IOP Wire Protocol 확장에서 붙인다.
- 선행 작업: Flutter-first Portal 마이그레이션, Client Integration Clone Adoption, Agent Bootstrap과 OTO 등록, 정책/이력/감사 일부 기준

View file

@ -60,7 +60,7 @@ Multi-edge scheduling과 fleet-wide state ownership은 Control Plane과 IOP nati
## 작업 컨텍스트
- 관련 경로: `apps/control-plane`, `apps/portal`, `apps/edge`, `docs/architecture.md`
- 관련 경로: `apps/control-plane`, `apps/portal`, `apps/edge`, `README.md`
- 표준선(선택): Edge는 로컬 런타임 상태와 registry 소유권을 유지하고, Control Plane은 관찰/집계 계층으로 시작한다.
- 선행 작업: Control Plane과 Client, 정책/이력/감사
- 후속 작업: 없음

View file

@ -63,7 +63,7 @@ Ollama 실테스트에서 안정화한 모델 조회, non-streaming/streaming ch
## 작업 컨텍스트
- 관련 경로: `apps/edge`, `apps/node`, `packages/config`, `proto/iop`, `configs`, `bin/edge.sh`, `bin/node.sh`, `docs/deploy-dev.md`
- 관련 경로: `apps/edge`, `apps/node`, `packages/config`, `proto/iop`, `configs`, `bin/edge.sh`, `bin/node.sh`, `agent-ops/rules/private/rules.md`
- 표준선(선택): 내부 실행 계약은 `adapter + target`을 유지하고, OpenAI-compatible API는 외부 호환 입력 표면으로 둔다.
- 표준선(선택): Ollama provider에서 안정화한 field smoke 기준을 SGLang provider에도 그대로 적용한다.
- 선행 작업: Ollama 실테스트와 후속 안정화

View file

@ -59,7 +59,7 @@ NomadCode는 이 계층의 소비자 중 하나이며, 이 Milestone은 특정 A
## 작업 컨텍스트
- 관련 경로: `apps/edge`, `apps/node`, `apps/control-plane`, `packages/policy`, `packages/observability`, `proto/iop`, `docs/architecture.md`, `agent-roadmap/archive/phase/serving-routing-optimization/milestones/model-serving-load-routing.md`
- 관련 경로: `apps/edge`, `apps/node`, `apps/control-plane`, `packages/policy`, `packages/observability`, `proto/iop`, `README.md`, `agent-roadmap/archive/phase/serving-routing-optimization/milestones/model-serving-load-routing.md`
- 표준선(선택): 기본 모델 serving/load routing이 먼저 안정화된 뒤 최적화 계층을 붙인다.
- 선행 작업: Ollama 서빙 안정화, Control Plane과 Client 운영
- 후속 작업: 정책/이력/감사와 품질 기반 routing/fallback 고도화

View file

@ -1,189 +0,0 @@
# 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-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가 맡는다. 저장 범위는 node가 직접 수행한 run의 상태와 완료/error 정보이며, Edge event aggregation의 durable backend가 아니다. Edge는 `internal/events.Bus``RunEvent``EdgeNodeEvent`를 in-process fanout하고 bounded replay를 제공한다. 이 replay는 운영 표면의 최근 이벤트 관찰용이며 전역 이력/audit 저장소는 후속 Control Plane/Portal 경계에서 별도 설계한다.
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(proto-socket)이다. Edge-Node와 Control Plane-Edge의 기본 transport는 TCP 기반 proto-socket으로 유지하고, Portal-Control Plane은 Flutter mobile/desktop과 Flutter Web을 고려해 proto-socket WebSocket/WSS를 우선한다. Flutter Web 브라우저 타깃은 raw TCP가 아니라 WebSocket binary frame 기반 transport를 사용한다.
- OpenAI-compatible HTTP API와 A2A JSON-RPC HTTP API는 Edge 외부 입력 표면으로 유지하며, Control Plane 운영 제어 프로토콜의 기본값으로 삼지 않는다.
## 배포 철학
Control Plane과 Portal은 중앙 운영면으로 묶어 compose 기반 서비스로 배포한다. Portal의 장기 UI 기준은 Flutter-first 앱이며, compose `web` 서비스는 `apps/portal/Dockerfile` 다단계 빌드로 Flutter Web 산출물을 빌드해 nginx로 정적 서빙한다. dart `proto-socket` path dependency 접근을 위해 build context는 repo parent(`..`)로 두고 dockerfile은 `iop/apps/portal/Dockerfile`로 지정한다. 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는 분기된 설치 방식을 만들지 않는다. Edge 운영 표면은 `iop-edge` command에 모으고, Node 사용자 연결은 Edge가 제시한 bootstrap 명령 하나로 끝나게 한다. 검토나 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:
edge bootstrap command -> iop-node binary + runtime state
foreground/system service -> bootstrap-managed node execution
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`) 사용 금지
- Edge-Node 기본 transport를 WebSocket으로 전환 금지. Portal-Control Plane의 proto-socket WebSocket/WSS 사용은 예외로 허용
- Actor framework 사용 금지
- External worker pool 사용 금지
- FSM framework 사용 금지
- Plugin framework 사용 금지
- Kubernetes 의존성 금지

View file

@ -1,580 +0,0 @@
# Dev Field Deployment
이 문서는 dev 필드 테스트 기준의 배포 단위를 정리한다. Control Plane과 Web은 compose 서비스로 묶고, Edge와 Node는 호스트에서 직접 실행하는 단일 바이너리로 배포한다.
## 배포 단위
```text
control host
docker compose
- postgres
- redis
- control-plane
- web
edge host
iop-edge binary
edge.yaml
node host
iop-node binary
bootstrap-managed runtime state
oto build host
oto binary
agent.yaml
```
Edge와 Node는 Docker 이미지로 만들지 않는다. Jenkins는 repo의 shell entrypoint를 호출해 Edge/Node 바이너리 산출물을 만든다.
OTO는 별도 프로젝트의 단일 바이너리 산출물로 두고, IOP Edge는 장기적으로 OTO agent를 설치/등록시키는 bootstrap provider 역할을 가진다.
Agent 설치가 어렵거나 일회성 유지보수 대상인 host/device는 별도 배포 단위로 보지 않고, 대상에 접근 가능한 Node의 terminal profile을 통해 Edge가 remote terminal bridge session을 중계하는 방향으로 둔다.
## Edge/Node 바이너리 빌드
기본 빌드는 현재 호스트의 `GOOS/GOARCH` 대상으로 `edge`, `node`를 모두 만든다.
```bash
bin/build/field-binaries.sh
```
Jenkins에서 Linux amd64/arm64를 함께 만들 때는 다음처럼 호출한다.
```bash
TARGETS="linux/amd64 linux/arm64" \
VERSION="${BUILD_TAG}" \
COMMIT="${GIT_COMMIT}" \
bin/build/field-binaries.sh
```
산출물은 `dist/field/<version>/<goos>-<goarch>/` 아래에 생성된다.
```text
iop-edge
iop-edge.sha256
iop-node
iop-node.sha256
SHA256SUMS
manifest.env
```
## Control Plane/Web Compose
루트의 `.env.example`을 참고해 필요한 값만 `.env`로 복사해 조정한다.
```bash
cp .env.example .env
docker compose up --build -d
```
서비스 포트 기본값은 다음과 같다.
| Service | Port | Purpose |
|---|---:|---|
| web | 3000 | Web Portal |
| control-plane | 9080 | HTTP health/bootstrap |
| control-plane wire | 19080 | IOP wire endpoint |
| postgres | 5432 | Control Plane DB |
| redis | 6379 | Control Plane cache/queue 후보 |
현재 Control Plane 구현은 DB/Redis를 아직 사용하지 않지만, dev compose에는 먼저 포함해 이후 schema, queue, audit, event 처리 작업을 같은 배포 단위 안에서 진행할 수 있게 둔다.
로컬에서 Control Plane 바이너리를 직접 실행하는 테스트는 외부 `code-server-postgres``iop-control-plane-local` DB와 외부 `code-server-redis`의 Redis DB 1, `iop:control-plane:local:` key prefix를 사용한다. dev compose 환경은 Postgres `iop-control-plane-dev` DB와 Redis DB 2, `iop:control-plane:dev:` key prefix를 사용한다. compose의 `control-plane` 서비스는 `IOP_DATABASE_URL`, `IOP_REDIS_URL`, `IOP_REDIS_KEY_PREFIX` 환경변수로 dev 값을 지정해 `configs/control-plane.yaml`의 로컬 기본값을 덮어쓴다.
헬스체크는 다음 명령으로 확인한다.
```bash
docker compose ps
curl -fsS http://localhost:${IOP_CONTROL_PLANE_HTTP_PORT:-9080}/healthz
curl -fsS http://localhost:${IOP_CONTROL_PLANE_HTTP_PORT:-9080}/readyz
```
## Edge 실행
Jenkins 산출물의 `iop-edge`를 edge host에 배포한 뒤, 초기에는 dev 환경의 `edge.yaml`을 지정해 직접 실행한다.
```bash
./iop-edge serve --config /etc/iop/edge.yaml
```
콘솔 기반 현장 smoke가 필요하면 다음처럼 실행한다.
```bash
./iop-edge console --config /etc/iop/edge.yaml
```
dev 초기 단계에서는 `iop-edge config init` 명령을 실행해 생성한 bundle-local `edge.yaml`을 기준으로 포트와 node token을 맞추며, 이 bundle-local `edge.yaml`이 개발/테스트의 primary source of truth다. repo의 `configs/edge.yaml`은 직접 편집하거나 참조하는 공식 기본 경로가 아니며 단지 로컬 개발 환경용 fallback 또는 예시 템플릿일 뿐이다. 이후 Control Plane enrollment/config sync가 붙으면 edge 설정 파일은 bootstrap 정보만 남기는 방향으로 줄인다.
## Node 실행
사용자/field 기본 경로에서 Node는 직접 설정 파일을 만들거나 `iop-node serve --config ...`로 실행하지 않는다.
Edge가 제시한 bootstrap 명령을 대상 host에서 실행하면 다운로드, 검증, 연결, 실행까지 이어져야 한다.
## Field 테스트 환경 라우팅
field 테스트의 1차 기준 환경은 `ssh toki@toki-labs.com`의 code-server 컨테이너다.
컨테이너 compose 원본은 원격 `~/docker/services/code-server/compose/docker-compose.yml`이며, 재기동 타이밍은 운영자가 정한다.
이 저장소의 작업자는 compose를 수정할 수 있지만, 사용자가 명시하지 않으면 code-server 컨테이너를 재시작하지 않는다.
| Host port | Container port | Purpose |
|---:|---:|---|
| `13000-13099` | `3000-3099` | web/dev app preview range |
| `18080` | `18080` | IOP field artifact/bootstrap HTTP |
| `18081` | `8080` | Edge OpenAI-compatible HTTP |
| `19080` | `19080` | Portal/Control Plane wire test endpoint |
| `19090` | `9090` | Edge-Node TCP transport |
| `19092` | `9092` | Edge metrics |
| `19190` | `9190` | OTO/specialized agent field transport reserve |
field bootstrap UX는 Jenkins agent 연결처럼 Edge가 command를 제시하고 대상 host가 실행하는 형태를 목표로 한다.
현재 field 기준 artifact/bootstrap 외부 주소는 `http://toki-labs.com:18080`이며, Edge-Node 런타임 주소는 `toki-labs.com:19090`이다.
초기 field 단계의 Node bootstrap은 다음 요구를 만족해야 한다.
- Edge 또는 field artifact server가 `dist/field/<version>/<goos>-<goarch>/iop-node`를 정해진 URL로 노출한다.
- Apple Silicon Mac은 `darwin/arm64` 산출물을 사용한다. `linux/arm64`는 Mac 실행 대상이 아니다.
- 사용자가 실행하는 명령은 한 줄 수준으로 유지하고, 내부에서 다운로드, 실행 권한 부여, 필요한 내부 상태 준비, `iop-node` 실행까지 이어진다.
- 사용자가 `node.yaml`을 만들거나 확인하거나 편집하는 흐름은 기본 경로에 두지 않는다.
- 직접 포트가 아직 적용되지 않았거나 외부망에서 막히면 SSH tunnel 또는 reverse tunnel은 fallback으로만 사용한다.
Apple Silicon Node용 field artifact는 다음처럼 만든다. Node token은 artifact에 굽지 않고 사용자 명령의 첫 번째 인자로 전달한다.
```bash
VERSION=<field-version> \
APPS=node \
TARGETS="darwin/arm64" \
ARTIFACT_BASE_URL="http://toki-labs.com:18080" \
BOOTSTRAP_EDGE_ADDR="toki-labs.com:19090" \
DIST_DIR=dist/field \
./bin/build/field-binaries.sh
```
산출물 layout과 artifact URL 규칙은 다음을 기준으로 한다.
```text
dist/field/<field-version>/darwin-arm64/iop-node
dist/field/<field-version>/darwin-arm64/SHA256SUMS
dist/field/<field-version>/bootstrap/node-darwin-arm64.sh
http://toki-labs.com:18080/darwin-arm64/iop-node
http://toki-labs.com:18080/darwin-arm64/SHA256SUMS
http://toki-labs.com:18080/bootstrap/node-darwin-arm64.sh
```
정적 artifact/bootstrap HTTP는 version 디렉터리를 document root로 열어 둔다.
```bash
python3 -m http.server 18080 --bind 0.0.0.0 -d dist/field/<field-version>
```
작업자는 artifact URL을 완성한 뒤 사용자가 token 값 하나만 바꾸는 한 줄 명령을 전달한다.
사용자에게 전달하는 명령에는 `<artifact-base-url>` placeholder를 남기지 않는다.
```bash
curl -fsSL http://toki-labs.com:18080/bootstrap/node-darwin-arm64.sh | bash -s CHANGE_ME_FIELD_TOKEN
```
bootstrap 스크립트는 `~/iop-field`를 만들고, `darwin-arm64/iop-node``SHA256SUMS`를 받아 checksum을 확인한 뒤 필요한 내부 상태를 준비하고 `iop-node`를 실행한다.
내부 구현이 임시 설정 파일을 쓰더라도 사용자에게 그 파일을 만들거나 관리하게 하지 않는다.
첫 번째 인자 `CHANGE_ME_FIELD_TOKEN`은 Edge config의 `nodes[].token`과 같은 값이어야 한다.
## Field Bootstrap 작업자 테스트 Runbook
이 절차는 LLM/작업자가 사용자 직접 테스트를 준비하고 실패 지점을 분리할 때 기준으로 삼는다.
사용자에게 전달할 짧은 가이드는 `docs/field-bootstrap-user-test.md`를 사용한다.
다음 작업 재개와 포트 의미 정리는 `docs/field-bootstrap-work-guide.md`를 먼저 확인한다.
토큰과 version 값은 테스트 시점에 맞춰 바꾸고, 기본 `configs/*.yaml`은 수정하지 않는다.
### 1. code-server 포트 확인
원격 host에서 code-server 컨테이너의 외부 포트가 실제로 열렸는지 먼저 확인한다.
```bash
ssh toki@toki-labs.com '/usr/local/bin/docker ps --format "{{.Names}} {{.Ports}}" | grep "^code-server "'
```
기대 포트:
```text
18080 artifact/bootstrap HTTP
18081 Edge OpenAI-compatible HTTP
19090 Edge-Node TCP transport
19092 Edge metrics
19190 OTO/specialized agent reserve
```
`docker ps`에 포트가 보이지 않으면 compose 파일은 갱신됐지만 컨테이너 recreate가 아직 안 된 상태일 수 있다.
컨테이너 recreate 타이밍은 운영자가 정한다.
### 2. Edge field config 작성
edge host에서 임시 설정 파일을 만든다.
Apple Silicon Mac에서 Ollama를 실행하고 Node도 같은 Mac에서 실행할 경우, Edge가 Node에 내려줄 Ollama base URL은 Node 관점의 `http://127.0.0.1:11434`다.
```bash
cat > /tmp/iop-edge-field-bootstrap.yaml <<'YAML'
edge:
id: "edge-field-bootstrap"
name: "Field Bootstrap Edge"
server:
listen: "0.0.0.0:9090"
openai:
enabled: true
listen: "0.0.0.0:8080"
node: "node-silicon-ollama"
adapter: "ollama"
target: ""
models:
- "gemma4:26b"
session_id: "openai-field"
timeout_sec: 300
strict_output: true
strict_stream_buffer: false
metrics:
port: 9092
nodes:
- id: "node-silicon-ollama"
alias: "silicon-ollama"
token: "CHANGE_ME_FIELD_TOKEN"
adapters:
ollama:
enabled: true
base_url: "http://127.0.0.1:11434"
context_size: 262144
YAML
```
config가 로드되는지 확인한다.
```bash
go run ./apps/edge/cmd/edge config check --config /tmp/iop-edge-field-bootstrap.yaml
```
### 3. Apple Silicon artifact 생성
repo root에서 Apple Silicon Node artifact와 bootstrap script를 만든다.
```bash
VERSION=field-bootstrap-manual \
APPS=node \
TARGETS="darwin/arm64" \
ARTIFACT_BASE_URL="http://toki-labs.com:18080" \
BOOTSTRAP_EDGE_ADDR="toki-labs.com:19090" \
DIST_DIR=dist/field \
./bin/build/field-binaries.sh
```
기대 파일:
```bash
test -x dist/field/field-bootstrap-manual/darwin-arm64/iop-node
test -f dist/field/field-bootstrap-manual/darwin-arm64/SHA256SUMS
test -x dist/field/field-bootstrap-manual/bootstrap/node-darwin-arm64.sh
```
### 4. artifact/bootstrap HTTP 시작
version 디렉터리를 document root로 열어 둔다.
```bash
python3 -m http.server 18080 --bind 0.0.0.0 -d dist/field/field-bootstrap-manual
```
다른 터미널에서 URL이 보이는지 확인한다.
```bash
curl -fsS http://127.0.0.1:18080/darwin-arm64/SHA256SUMS
curl -fsS http://127.0.0.1:18080/bootstrap/node-darwin-arm64.sh | sed -n '1,40p'
```
기준 field 환경에서는 Apple Silicon Mac에서도 같은 URL이 보여야 한다.
`18080`을 사용할 수 없는 별도 환경에서만 내부망, front/control-plane, CI artifact, 임시 로컬 HTTP 같은 artifact 채널을 사용한다.
```bash
curl -fsS http://toki-labs.com:18080/darwin-arm64/SHA256SUMS
```
### 5. Edge serve 시작
Edge process를 field config로 실행한다.
```bash
go run ./apps/edge/cmd/edge serve --config /tmp/iop-edge-field-bootstrap.yaml
```
다른 터미널에서 listener를 확인한다.
```bash
curl -fsS http://127.0.0.1:8080/healthz
curl -fsS http://127.0.0.1:8080/v1/models
```
`/v1/models`는 Node가 아직 연결되지 않아도 configured model 목록을 반환해야 한다.
### 6. Apple Silicon Mac에서 one-line bootstrap 실행
Mac에서 Ollama가 떠 있고 `gemma4:26b`를 사용할 수 있는지 먼저 확인한다.
```bash
ollama list | grep 'gemma4:26b'
```
그 다음 one-line bootstrap을 실행한다.
사용자에게 전달할 때는 완성된 URL을 포함하고, token 값만 마지막 positional value로 바꾸게 한다.
```bash
curl -fsSL http://toki-labs.com:18080/bootstrap/node-darwin-arm64.sh | bash -s CHANGE_ME_FIELD_TOKEN
```
성공하면 Mac에 다음 파일이 생기고, Node process가 foreground로 실행된다.
```text
~/iop-field/iop-node
```
Edge 로그에는 `node registered``node-silicon-ollama`가 보여야 한다.
### 7. Edge에서 모델 호출 확인
Edge host에서 Node 등록 후 OpenAI-compatible API를 호출한다.
```bash
curl -fsS http://127.0.0.1:8080/v1/models
```
non-streaming:
```bash
curl -fsS http://127.0.0.1:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"gemma4:26b","messages":[{"role":"user","content":"Reply with IOP_FIELD_BOOTSTRAP_OK only."}]}'
```
streaming:
```bash
curl -fsS -N http://127.0.0.1:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"gemma4:26b","stream":true,"messages":[{"role":"user","content":"Reply with IOP_FIELD_STREAM_OK only."}]}'
```
성공 기준:
- Mac의 bootstrap 명령이 foreground Node process 실행까지 도달한다.
- Edge 로그에 `node registered`가 나온다.
- `/v1/models``gemma4:26b`를 포함한다.
- non-streaming 응답에서 content가 비어 있지 않다.
- streaming 응답에서 SSE chunk와 `data: [DONE]`이 보인다.
### 8. 실패 시 확인 순서
다운로드 실패:
```bash
curl -v http://toki-labs.com:18080/bootstrap/node-darwin-arm64.sh
curl -v http://toki-labs.com:18080/darwin-arm64/iop-node
```
Node가 Edge에 연결하지 못함:
```bash
nc -vz toki-labs.com 19090
```
token 불일치:
- Edge config의 `nodes[].token` 값과 bootstrap 명령 마지막의 token 인자 값을 맞춘다.
Ollama 호출 실패:
```bash
curl -fsS http://127.0.0.1:11434/api/tags
```
Apple Silicon Mac에서 Node와 Ollama를 함께 실행하는 경우 Edge config의 `nodes[].adapters.ollama.base_url``http://127.0.0.1:11434`여야 한다.
### 9. 정리
Mac의 foreground Node process는 `Ctrl-C`로 종료한다.
재실행은 같은 one-line bootstrap command를 다시 실행한다.
관련 완료 Milestone 기록은 `agent-roadmap/archive/phase/serving-routing-optimization/milestones/field-bootstrap-test-port-readiness.md`에 보존되어 있다.
이후 field bootstrap 관련 구현, 문서, 테스트 포트 변경은 새 활성 Milestone 또는 후속 Phase 범위로 판단한다.
## 공통 Ollama 필드 테스트 환경
OpenAI-compatible Ollama E2E 서빙 검증은 아래 필드 환경을 공통 기준으로 사용한다.
이 환경은 특정 마일스톤에만 두지 않고, 이후 모델 서빙, routing, fallback, client 통합 검증에서도 재사용한다.
`scripts/e2e-openai-ollama.sh` 같은 fake Ollama smoke는 보조 확인이다.
완료 판정은 `iop-edge` 실행, Edge가 제시한 Node bootstrap 명령, 실제 Ollama endpoint를 연결한 full-cycle 흐름으로 한다.
`gemma4:26b`는 thinking을 먼저 stream할 수 있으므로, 기본 검증은 `think:false`로 우회하지 않는다.
Edge 응답에서 thinking/reasoning stream과 최종 content stream이 분리되어 보이는지 확인한다.
| 항목 | 값 |
|---|---|
| Node host | `ssh toki@toki-labs.com` |
| Ollama base URL | `http://192.168.0.97:11434` |
| 기준 model | `gemma4:26b` |
| 권장 node id | `node-toki-labs-ollama` |
| 권장 node alias | `toki-labs-ollama` |
기본 `configs/*.yaml`은 필드 테스트 값으로 덮어쓰지 않는다.
edge와 node는 임시 config 또는 `/etc/iop/*.yaml` 필드 설정을 사용한다.
Node가 사용할 Ollama endpoint는 node config가 아니라 edge config의 `nodes[].adapters.ollama.base_url`에 둔다.
Node는 registration 이후 Edge가 내려주는 adapter config로 Ollama를 호출한다.
edge host에서 임시 config를 만들 때의 기준은 다음과 같다.
`CHANGE_ME_FIELD_TOKEN``<edge-host>`는 테스트 시점의 값으로 바꾼다.
`openai.target`은 비워 두면 요청의 `model`을 내부 target으로 사용하고, `gemma4:26b`로 고정하면 외부 요청 model과 무관하게 해당 model로만 호출한다.
Node는 Edge로 outbound 연결하므로 `ssh toki@toki-labs.com` 환경에서 `<edge-host>:9090`에 접근할 수 있어야 한다.
로컬 개발 머신이 원격 host에서 직접 보이지 않으면 Edge를 접근 가능한 host에서 실행하거나 터널을 구성한다.
```yaml
edge:
id: "edge-toki-labs-field"
name: "Toki Labs Field Edge"
server:
listen: "0.0.0.0:9090"
openai:
enabled: true
listen: "0.0.0.0:8080"
node: "node-toki-labs-ollama"
adapter: "ollama"
target: ""
models:
- "gemma4:26b"
session_id: "openai-field"
timeout_sec: 300
strict_output: true
strict_stream_buffer: false
nodes:
- id: "node-toki-labs-ollama"
alias: "toki-labs-ollama"
token: "CHANGE_ME_FIELD_TOKEN"
adapters:
ollama:
enabled: true
base_url: "http://192.168.0.97:11434"
context_size: 262144
```
edge 실행:
```bash
iop-edge serve --config /tmp/iop-edge-toki-labs.yaml
```
node host에서는 별도 Node config를 만들지 않는다. Edge가 제시한 bootstrap 명령만 실행한다.
```bash
ssh toki@toki-labs.com
```
```bash
curl -fsSL http://toki-labs.com:18080/bootstrap/node-darwin-arm64.sh | bash -s CHANGE_ME_FIELD_TOKEN
```
edge host에서 OpenAI-compatible API를 호출해 검증한다.
```bash
curl -fsS http://127.0.0.1:8080/v1/models
```
```bash
curl -fsS http://127.0.0.1:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"gemma4:26b","messages":[{"role":"user","content":"Reply with IOP_OLLAMA_E2E_OK only."}]}'
```
streaming 검증:
```bash
curl -fsS -N http://127.0.0.1:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"gemma4:26b","stream":true,"messages":[{"role":"user","content":"Reply with IOP_OLLAMA_STREAM_OK only."}]}'
```
완료 기준은 사용자가 재현하는 실제 절차에서 node registration, `/v1/models` 응답, non-streaming chat completion, streaming SSE chunk와 `data: [DONE]` 확인이다.
`gemma4:26b`가 thinking을 내보내는 경우 thinking/reasoning chunk가 content와 섞이지 않고, content가 시작된 뒤 답변 stream이 이어지는지도 확인한다.
Edge/Node 뒤의 소켓 relay가 사용자 UX에 드러나지 않아야 하며, 실패 시 edge와 node 양쪽 로그를 함께 확인한다.
## Agent Bootstrap / Bridge References
OTO 같은 specialized domain agent의 bootstrap/enrollment 상세 계획은 `agent-roadmap/milestones/agent-bootstrap-oto-enrollment.md`에서 관리한다.
Agent 설치가 어렵거나 임시 제어만 필요한 대상의 remote terminal bridge 계획은 `agent-roadmap/milestones/remote-terminal-bridge-poc.md`에서 관리한다.
이 배포 문서에서는 현재 dev field 기준의 배포 단위와 smoke 절차만 유지한다.
## Field Smoke
최소 확인 순서는 다음을 기준으로 한다.
1. `docker compose ps`에서 `postgres`, `redis`, `control-plane`, `web`이 healthy/running인지 확인한다.
2. edge host에서 `iop-edge console --config /etc/iop/edge.yaml`을 실행한다.
3. node host에서 Edge가 제시한 bootstrap 명령을 실행한다.
4. edge console에서 `/nodes`로 node 등록을 확인한다.
5. 메시지 2회를 보내고 각 요청의 start, message, complete event가 edge console에 표시되는지 확인한다.
6. `/capabilities`, `/transport`, `/sessions`를 실행해 command 응답을 확인한다.
보조 자동 smoke는 repo root에서 다음 명령으로 실행한다.
```bash
make test-e2e
```
이 명령은 보조 확인이며, 필드 배포 완료 기준은 실제 edge/node 바이너리 실행 흐름 확인이다.
## Edge setup CLI
Edge 바이너리는 호스트 환경 준비를 `setup` 명령으로 일원화한다. 공식 운영 경로는 하나다.
```bash
sudo iop-edge setup --enable --start
```
배포 전에 결과를 검토하려면 `--dry-run`을 쓴다.
```bash
sudo iop-edge setup --dry-run --binary /usr/local/bin/iop-edge
```
`setup`은 다음을 담당한다.
- 실행 user/group(`iop`) 준비
- `/etc/iop``/var/lib/iop/edge` 디렉터리 준비
- 설정 파일이 없을 때만 기본 템플릿 생성 (`--overwrite-config`로 강제 갱신)
- systemd unit 생성 또는 갱신 (`/etc/systemd/system/iop-edge.service`)
- `systemctl daemon-reload`
- 옵션에 따른 `--enable`, `--start`, `--restart`
`setup``--config` 기본값은 `/etc/iop/edge.yaml`이다. dev 단계 명령(`serve`, `console`, `config init/print/check`, `env`, `node register`)의 root persistent `--config` 기본값은 bundle-local `edge.yaml` (없을 시 executable-adjacent `edge.yaml`, 그 다음 repo dev fallback `configs/edge.yaml`)로 확인된다.
별도 `render`, `service install`, `service status` 명령은 초기 범위에 넣지 않는다. 검토와 CI 확인은 `setup --dry-run`으로 흡수하고, 상태/로그/재시작은 `systemctl``journalctl`을 기준 운영 도구로 둔다.
현재 구현된 edge CLI 표면은 다음과 같다.
```text
iop-edge serve
iop-edge console
iop-edge setup
iop-edge env
iop-edge config init
iop-edge config print
iop-edge config check
iop-edge node register
iop-edge nodes list
iop-edge smoke openai
iop-edge version
```
`setup`은 여러 번 실행해도 같은 결과로 수렴한다. 기존 설정 파일은 기본적으로 덮어쓰지 않고, 강제 갱신이 필요하면 `--overwrite-config`를 지정한다.
## Node host setup note
Node setup/config CLI는 현재 구현에 남아 있는 저수준 보조 표면이다.
field 사용자 기본 경로와 다음 개발 방향에서는 Node setup/config를 안내하지 않는다.
Control Plane 없는 개발 단계에서도 Node별 실행 설정은 Edge config의 `nodes[]`에서 관리하고, Node host에서는 Edge가 제시한 bootstrap 명령만 실행한다.

View file

@ -1,4 +1,4 @@
# Edge-local Dev 테스트 세팅
# Edge-local Dev Guide
이 문서는 Control Plane 없이 로컬 또는 개발 서버에서 Edge/Node bundle을 빠르게 세팅하고 확인하는 사용자용 흐름이다.
사용자는 `iop-edge`를 중심으로 진행한다.
@ -93,7 +93,7 @@ Ollama Node로 등록할 때:
출력 예:
```bash
curl -fsSL http://toki-labs.com:18080/bootstrap/node-darwin-arm64.sh | bash -s <generated-token>
curl -fsSL <bootstrap-url> | bash -s <generated-token>
```
사용자는 이 한 줄만 Node host에서 실행한다.
@ -132,7 +132,7 @@ tail -f logs/edge.log
`iop-edge node register ...`가 출력한 한 줄을 Node host에서 그대로 실행한다.
```bash
curl -fsSL http://toki-labs.com:18080/bootstrap/node-darwin-arm64.sh | bash -s <generated-token>
curl -fsSL <bootstrap-url> | bash -s <generated-token>
```
Node host 사용자는 `node.yaml`을 만들거나 편집하지 않는다.

View file

@ -1,91 +0,0 @@
# Field Bootstrap 작업 가이드
이 문서는 작업자/LLM이 field bootstrap 테스트를 이어갈 때 읽는 재개 가이드다.
사용자에게 그대로 전달하는 문서는 `docs/field-bootstrap-user-test.md`를 사용하고, 상세 runbook은 `docs/deploy-dev.md`를 확인한다.
## 핵심 규칙
- Edge-Node 런타임 연결에 외부 공개가 필요한 포트는 `19090` 하나다.
- `18080`은 bootstrap shell script, `iop-node` binary, checksum을 내려받는 artifact HTTP 포트다. Edge-Node 런타임 포트가 아니다.
- 현재 field 기준 bootstrap/artifact 외부 주소는 `http://toki-labs.com:18080`이다.
- bootstrap이 사용하는 Edge runtime 주소는 `toki-labs.com:19090`으로 맞춘다.
- Cline/OpenAI-compatible 외부 주소는 `http://toki-labs.com:18081/v1`이다.
- 사용자에게 보이는 bootstrap 명령은 완성된 URL과 token positional value 하나만 포함한다. `IOP_*=` named parameter를 사용자 기본 경로에 두지 않는다.
- 사용자에게 `node.yaml` 생성, 확인, 편집, `iop-node serve --config ...` 실행을 요구하지 않는다. bootstrap 내부 상태 파일은 구현 세부로만 취급한다.
## 완료 테스트 스냅샷
- live version: `field-live-20260527-19ddd9bd38b7`
- node id: `node-silicon-ollama`
- model: `gemma4:26b`
- edge config 후보: `/tmp/iop-edge-field-bootstrap.yaml`
위 값은 완료된 field 테스트의 임시 값이다. token은 테스트마다 새로 발급하고 문서값을 재사용하지 않는다.
## 재개 전 확인
stale process가 남아 있는지 먼저 본다.
```bash
ps -ef | rg 'python3 -m http.server 18080|go run ./apps/edge/cmd/edge|/tmp/go-build.*/edge serve|iop-edge serve'
netstat -ltnp 2>/dev/null | rg ':(18080|8080|9090)\b|Proto' || true
```
이전 테스트 프로세스가 남아 있으면 정리한 뒤 새 설정으로 띄운다.
## 권장 테스트 흐름
Apple Silicon bootstrap artifact를 만들 때 artifact URL과 edge runtime 주소를 분리한다.
```bash
VERSION=field-live-20260527-19ddd9bd38b7 \
APPS=node \
TARGETS="darwin/arm64" \
ARTIFACT_BASE_URL="http://toki-labs.com:18080" \
BOOTSTRAP_EDGE_ADDR="toki-labs.com:19090" \
DIST_DIR=dist/field \
./bin/build/field-binaries.sh
```
artifact HTTP가 필요하면 `18080`으로 연다.
```bash
python3 -m http.server 18080 --bind 0.0.0.0 -d dist/field/field-live-20260527-19ddd9bd38b7
```
Edge는 field config로 실행한다.
```bash
go run ./apps/edge/cmd/edge serve --config /tmp/iop-edge-field-bootstrap.yaml
```
기본 확인:
```bash
curl -fsS http://127.0.0.1:8080/healthz
curl -fsS http://127.0.0.1:8080/v1/models
nc -vz toki-labs.com 19090
```
사용자에게 전달하는 bootstrap 명령의 URL은 artifact 전달 채널에 맞춰 작업자가 완성한다.
사용자 명령은 token 값 하나만 인자로 받는다. 사용자에게 `<artifact-base-url>` 같은 placeholder를 채우게 하지 않는다.
bootstrap script 안의 기본 Edge runtime 주소는 `19090`으로 들어가야 한다.
```bash
curl -fsSL http://toki-labs.com:18080/bootstrap/node-darwin-arm64.sh | bash -s <token>
```
## 성공 기준
- Node host에서 `toki-labs.com:19090`에 연결할 수 있다.
- Edge 로그에 `node registered`가 나온다.
- `/v1/models``gemma4:26b`를 반환한다.
- OpenAI-compatible 호출에서 비어 있지 않은 응답 또는 SSE delta가 나온다.
## 주의
- field 기본 경로에서는 `curl http://toki-labs.com:18080/... | bash -s <token>` 형태를 사용한다.
- `18080`을 사용할 수 없는 별도 환경에서만 내부망, front/control-plane, CI artifact, 임시 로컬 HTTP 같은 artifact 전달 채널로 바꾼다.
- Cline 전용 감지나 provider별 분기는 만들지 않는다. OpenAI-compatible protocol 차원에서 안정화한다.
- 기본 `configs/*.yaml`에 field token이나 임시 host 값을 넣지 않는다.
- 테스트 후에는 임시 edge와 artifact server를 내려둔다.