diff --git a/README.md b/README.md index 130c771..c275780 100644 --- a/README.md +++ b/README.md @@ -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`다. diff --git a/agent-ops/rules/project/rules.md b/agent-ops/rules/project/rules.md index 740942f..2447fd6 100644 --- a/agent-ops/rules/project/rules.md +++ b/agent-ops/rules/project/rules.md @@ -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.md`를 생성하고 이 표에 등록한다. diff --git a/agent-roadmap/phase/automation-runtime-bridge/milestones/agent-bootstrap-oto-enrollment.md b/agent-roadmap/phase/automation-runtime-bridge/milestones/agent-bootstrap-oto-enrollment.md index 82e7874..7c241d5 100644 --- a/agent-roadmap/phase/automation-runtime-bridge/milestones/agent-bootstrap-oto-enrollment.md +++ b/agent-roadmap/phase/automation-runtime-bridge/milestones/agent-bootstrap-oto-enrollment.md @@ -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를 발급한다. diff --git a/agent-roadmap/phase/automation-runtime-bridge/milestones/remote-terminal-bridge-poc.md b/agent-roadmap/phase/automation-runtime-bridge/milestones/remote-terminal-bridge-poc.md index 0a48bed..9f326b1 100644 --- a/agent-roadmap/phase/automation-runtime-bridge/milestones/remote-terminal-bridge-poc.md +++ b/agent-roadmap/phase/automation-runtime-bridge/milestones/remote-terminal-bridge-poc.md @@ -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 운영 표면 diff --git a/agent-roadmap/phase/control-plane-portal-ops/milestones/control-plane-portal.md b/agent-roadmap/phase/control-plane-portal-ops/milestones/control-plane-portal.md index 9005787..c28fc02 100644 --- a/agent-roadmap/phase/control-plane-portal-ops/milestones/control-plane-portal.md +++ b/agent-roadmap/phase/control-plane-portal-ops/milestones/control-plane-portal.md @@ -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 등록, 정책/이력/감사 일부 기준 diff --git a/agent-roadmap/phase/control-plane-portal-ops/milestones/multi-edge-operations.md b/agent-roadmap/phase/control-plane-portal-ops/milestones/multi-edge-operations.md index 4ec6a72..724587a 100644 --- a/agent-roadmap/phase/control-plane-portal-ops/milestones/multi-edge-operations.md +++ b/agent-roadmap/phase/control-plane-portal-ops/milestones/multi-edge-operations.md @@ -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, 정책/이력/감사 - 후속 작업: 없음 diff --git a/agent-roadmap/phase/inference-provider-extension/milestones/sglang-provider-serving-validation.md b/agent-roadmap/phase/inference-provider-extension/milestones/sglang-provider-serving-validation.md index c25db87..416a1e4 100644 --- a/agent-roadmap/phase/inference-provider-extension/milestones/sglang-provider-serving-validation.md +++ b/agent-roadmap/phase/inference-provider-extension/milestones/sglang-provider-serving-validation.md @@ -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 실테스트와 후속 안정화 diff --git a/agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/knowledge-tool-validation-optimization.md b/agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/knowledge-tool-validation-optimization.md index 3d86a54..9bc9ad2 100644 --- a/agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/knowledge-tool-validation-optimization.md +++ b/agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/knowledge-tool-validation-optimization.md @@ -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 고도화 diff --git a/docs/architecture.md b/docs/architecture.md deleted file mode 100644 index 1d05c67..0000000 --- a/docs/architecture.md +++ /dev/null @@ -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 의존성 금지 diff --git a/docs/deploy-dev.md b/docs/deploy-dev.md deleted file mode 100644 index d2d38b4..0000000 --- a/docs/deploy-dev.md +++ /dev/null @@ -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//-/` 아래에 생성된다. - -```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//-/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= \ -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//darwin-arm64/iop-node -dist/field//darwin-arm64/SHA256SUMS -dist/field//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/ -``` - -작업자는 artifact URL을 완성한 뒤 사용자가 token 값 하나만 바꾸는 한 줄 명령을 전달한다. -사용자에게 전달하는 명령에는 `` 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`과 ``는 테스트 시점의 값으로 바꾼다. -`openai.target`은 비워 두면 요청의 `model`을 내부 target으로 사용하고, `gemma4:26b`로 고정하면 외부 요청 model과 무관하게 해당 model로만 호출한다. -Node는 Edge로 outbound 연결하므로 `ssh toki@toki-labs.com` 환경에서 `: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 명령만 실행한다. diff --git a/docs/field-bootstrap-user-test.md b/docs/edge-local-dev-guide.md similarity index 93% rename from docs/field-bootstrap-user-test.md rename to docs/edge-local-dev-guide.md index 86710ca..8cac45a 100644 --- a/docs/field-bootstrap-user-test.md +++ b/docs/edge-local-dev-guide.md @@ -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 +curl -fsSL | bash -s ``` 사용자는 이 한 줄만 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 +curl -fsSL | bash -s ``` Node host 사용자는 `node.yaml`을 만들거나 편집하지 않는다. diff --git a/docs/field-bootstrap-work-guide.md b/docs/field-bootstrap-work-guide.md deleted file mode 100644 index f7d38ee..0000000 --- a/docs/field-bootstrap-work-guide.md +++ /dev/null @@ -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 값 하나만 인자로 받는다. 사용자에게 `` 같은 placeholder를 채우게 하지 않는다. -bootstrap script 안의 기본 Edge runtime 주소는 `19090`으로 들어가야 한다. - -```bash -curl -fsSL http://toki-labs.com:18080/bootstrap/node-darwin-arm64.sh | bash -s -``` - -## 성공 기준 - -- 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 ` 형태를 사용한다. -- `18080`을 사용할 수 없는 별도 환경에서만 내부망, front/control-plane, CI artifact, 임시 로컬 HTTP 같은 artifact 전달 채널로 바꾼다. -- Cline 전용 감지나 provider별 분기는 만들지 않는다. OpenAI-compatible protocol 차원에서 안정화한다. -- 기본 `configs/*.yaml`에 field token이나 임시 host 값을 넣지 않는다. -- 테스트 후에는 임시 edge와 artifact server를 내려둔다.