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

# node

## 목적 / 책임

Edge에 연결되어 실제 adapter execution을 수행하는 IOP 노드 에이전트 영역이다. edge에서 들어온 실행·취소·조회성 명령을 runtime 요청으로 변환하고, 라우팅된 어댑터를 실행하며, 실행 이벤트와 현재 단계의 로컬 실행 이력을 관리한다.

## 포함 경로

- `apps/node/cmd/node/` — node CLI 진입점과 서브커맨드
- `apps/node/internal/bootstrap/` — fx 의존성 주입과 adapter registry 구성
- `apps/node/internal/node/` — transport handler 구현과 실행 오케스트레이션
- `apps/node/internal/runtime/` — node 도메인 타입과 핵심 인터페이스
- `apps/node/internal/router/` — RunRequest를 ExecutionSpec으로 해석하는 라우팅
- `apps/node/internal/transport/` — edge와의 TCP/protobuf 세션 및 메시지 처리
- `apps/node/internal/adapters/` — mock/ollama/vllm/cli 실행 어댑터
- `apps/node/internal/store/` — SQLite 실행 이력 저장
- `apps/node/internal/terminal/` — persistent terminal session과 tail buffer helper
- `apps/node/README.md` — node 실행 흐름과 adapter/session 경계 설명

## 제외 경로

- `apps/edge/` — Node를 관리하는 실행 그룹 컨트롤러 영역
- `apps/control-plane/` — 여러 Edge 연결 관리와 운영 제어 API 제공 영역
- `apps/worker/` — 비동기 작업 처리 예정 영역
- `packages/go/` — 여러 앱이 공유하는 Go 공통 패키지
- `proto/` — 앱 간 메시지 계약

## 주요 구성 요소

- `runtime.Adapter` — adapter target 실행 계약
- `runtime.Router` — 실행 요청을 구체적인 `ExecutionSpec`으로 변환하는 계약
- `runtime.CommandHandler` — adapter별 `NodeCommandRequest` 처리 optional 계약
- `runtime.SessionTerminator` — logical session 종료를 지원하는 optional 계약
- `runtime.ProviderProber` / `runtime.ProviderProbeResult` — provider endpoint와 target availability probe optional 계약
- `runtime.ProviderTunnelAdapter` / `ProviderTunnelRequest` / `ProviderTunnelFrame` — OpenAI-compatible provider raw HTTP/SSE tunnel optional 계약
- `node.Node` — `transport.Handler` 구현체이자 실행 파이프라인 조정자
- `node.runManager` — run ID 기준 `runHandle`(cancel, done) 등록/해제/취소 관리; `node.Node` 내부에서만 사용
- `node.Node.OnConfigRefresh()` — Edge가 보낸 `NodeConfigRefreshRequest`를 적용하고 adapter registry를 live swap
- `node.Node.OnProviderTunnelRequest()` — provider tunnel 요청을 지원 adapter에 전달하고 tunnel frame을 edge session으로 반환
- `node.sessionSink` — adapter `RuntimeEvent`를 proto `RunEvent`로 변환해 edge session으로 보내는 sink
- `transport.Session` — edge와 연결된 node 세션 및 메시지 처리
- `adapters.Registry` — 어댑터 등록/조회 및 `LifecycleAdapter` start/stop lifecycle 관리 (실패 시 역순 롤백)
- `adapters.LifecycleAdapter` — start/stop lifecycle이 필요한 어댑터의 optional 인터페이스
- `adapters.ConfigSet` / `adapters.DiffConfigSets()` — Edge config payload에서 adapter registry/runtime snapshot을 만들고 refresh diff를 산출
- `adapters.BuildFromPayload()` — edge에서 받은 `NodeConfigPayload`로 `Registry`를 초기화하는 factory
- `adapters/cli.CLI` — one-shot, persistent TUI, persistent-lazy, codex-exec, antigravity-print, opencode-sse profile을 실행하는 CLI adapter
- `adapters/cli.clineJSONEmitter` — Cline JSON output을 `RuntimeEvent` delta/error로 변환하는 emitter
- `adapters/cli.executeAntigravityPrint()` — Antigravity print mode conversation id를 IOP logical session별로 보관하고 resume_args로 후속 요청을 재개
- `adapters/cli.executeOpencodeSSE()` — opencode serve HTTP/SSE session을 실행하거나 `--attach`로 외부 server에 연결해 delta를 relay
- `adapters/cli.executePersistent()` — terminal/persistent profile의 completion marker, idle timeout, output filter를 처리
- `adapters/cli/status` — claude/codex/antigravity CLI 상태 파서 (사용량 한도, reset 시각 등)
- `adapters/cli.lineEmitter` — stdout 한 줄을 파싱해 `RuntimeEvent`를 반환하는 내부 인터페이스; `emitters.go`에서 format별로 등록
- `adapters/ollama.Ollama` — Ollama `/api/chat` streaming, `/api/tags` capabilities, `/api/*` command passthrough를 처리하는 adapter
- `adapters/openai_compat.Adapter` — OpenAI-compatible `/v1/models`, chat completions, provider label/header/options passthrough, provider tunnel을 처리하는 adapter
- `adapters/vllm.Vllm` — vLLM/SGLang류 OpenAI-compatible endpoint를 직접 호출하고 provider tunnel을 처리하는 adapter
- `terminal.Session` / `terminal.TailBuffer` — persistent TUI session I/O, resize/signal/close, visible output buffer helper
- `store.Store` — 실행 상태와 결과 저장

## 유지할 패턴

- `runtime` 패키지에는 도메인 타입과 인터페이스를 두고 구체 구현 의존성을 넣지 않는다.
- transport/proto 타입은 `node.Node` 경계에서 runtime 타입으로 변환한다.
- 내부 실행 식별자는 `adapter + target`을 사용한다. 외부 OpenAI-compatible API나 legacy placeholder를 제외하고 `model`을 내부 실행 대표 용어로 되돌리지 않는다.
- Edge-Node runtime wire와 Edge가 내려주는 config payload 계약 상세는 `agent-contract/inner/edge-node-runtime-wire.md`와 `agent-contract/inner/edge-config-runtime-refresh.md`를 기준으로 확인한다.
- 어댑터 추가 시 `runtime.Adapter`를 구현하고 `adapters.BuildFromPayload()` 또는 bootstrap registry에 등록한다.
- 여러 adapter instance는 `adapters.Registry.RegisterKeyed(instanceKey, typeName, adapter)`로 등록하고, router lookup은 instance key를 우선한다. legacy type-name lookup은 단일 instance일 때만 안전하다.
- field Node의 기본 시작 경로는 Edge bootstrap script가 만든 최소 config와 Edge가 RegisterResponse로 내려주는 adapter/runtime payload다. 사용자가 기본 경로에서 node config를 직접 작성하거나 adapter/provider 세부값을 명령줄에 넣는 흐름을 만들지 않는다.
- Node runtime 작업 디렉터리나 store/workspace 경로는 대상 OS에서 쓰기 가능한 기본값이어야 한다. Edge가 특정 node에 `workspace_root`를 내려줄 때 macOS/dev host 절대 경로(`/Users/...`) 같은 값을 Linux/Windows node에 재사용하지 않으며, OS별 경로가 필요하면 Edge 설정에 미리 굽는다.
- 실행 취소는 run ID 기준으로 `runManager`에 등록하고 실행 종료 시 반드시 `deregister`로 해제한다.
- `CancelAction_CANCEL_RUN`은 현재 run 취소, `CancelAction_TERMINATE_SESSION`은 logical session 종료로 구분한다.
- `ProviderTunnelRequest`는 run ID/tunnel ID 기준으로 `runManager`에 등록하고, `ProviderTunnelFrame`은 RunEvent stream과 별도 proto message로 edge에 반환한다. tunnel 지원은 `runtime.ProviderTunnelAdapter`를 구현한 adapter에만 허용한다.
- `NodeCommandRequest`는 실행 요청과 분리해 `USAGE_STATUS`, `CAPABILITIES`, `SESSION_LIST`, `TRANSPORT_STATUS` 같은 조회/제어성 명령으로 처리한다.
- `OLLAMA_API` command는 Ollama adapter 내부의 제한된 `/api/*` passthrough로 처리하고, Edge/OpenAI surface가 node HTTP client를 우회해 직접 Ollama에 붙는 구조로 확장하지 않는다.
- `adapters.Registry`의 start/stop은 bootstrap lifecycle에서만 호출하고 개별 adapter에서 직접 호출하지 않는다.
- cli adapter의 출력 format별 파싱 로직은 `lineEmitter` 구현체로 분리하고 `node.Node`에 분기문으로 박지 않는다.
- CLI profile mode별 세부 실행(`persistent-lazy`, `codex-exec`, `antigravity-print`, `opencode-sse`)은 `adapters/cli` 내부에 두고, `runtime.Adapter` 계약 밖으로 새 transport를 노출하지 않는다.
- `cline-json`, `opencode-json`, `codex-json`, `claude-json` 같은 provider별 stdout parser는 `adapters/cli` emitter로 등록하고 runtime 공통 이벤트로만 외부에 노출한다.
- CLI logical session은 `(target, session_id)`로 식별한다. Antigravity conversation id, Codex external id, opencode session/server 상태를 전역 target 단위로 공유하지 않는다.
- `response_idle_timeout_ms`, `startup_idle_timeout_ms`, `completion_marker`, `resume_args`, `mode` 같은 CLI profile 설정은 edge config/proto payload를 통해 주입하고 node 코드에 target별 상수를 늘리지 않는다.
- config refresh는 `adapters.BuildConfigSet()`로 next registry를 만들고 start 성공 후 router registry를 live swap한다. 기존 in-flight run은 old adapter snapshot으로 마무리하고, old registry stop은 active run drain 뒤에 처리한다.
- Node-wide runtime concurrency는 admission source로 되살리지 않는다. per-adapter `Capabilities().MaxConcurrency`가 adapter gate capacity의 기준이다.
- Ollama adapter는 내부 target을 model 이름으로 사용하고, `context_size`는 `options.num_ctx`의 강제 소유값으로 주입한다. 요청 input에 명시된 `options.num_ctx`가 있어도 Edge-owned `context_size`가 항상 우선한다. `context_size`가 0이면 request 값을 그대로 사용한다.
- vLLM/openai_compat adapter는 OpenAI-compatible provider endpoint를 호출하되, Edge가 선택한 served model target과 provider header/auth/passthrough 정책을 보존한다.
- `RuntimeEvent`는 start/delta/reasoning_delta/complete/error/cancelled 타입을 유지하고, adapter별 streaming 표현을 node 외부로 새 이벤트 체계로 노출하지 않는다.
- node 내부 변경은 가능한 대상 패키지 테스트를 먼저 추가하거나 갱신한다.
- `apps/node/cmd/node/**`, `apps/node/internal/bootstrap/**`, `apps/node/internal/transport/**`, `apps/node/internal/node/**`, `apps/node/internal/router/**`, `apps/node/internal/adapters/**`, `apps/node/internal/terminal/**`, `apps/node/internal/store/**`의 실행 요청/응답/stream/cancel/status/session/config-refresh/provider-tunnel 경로를 바꾼 뒤에는 `testing` domain rule의 작업 후 검증 기준을 따른다.

## 다른 도메인과의 경계

- **edge**: edge는 node 연결 등록, adapter/runtime 설정 전달, 라우팅 진입, stream relay를 담당한다. node는 edge가 보낸 실행/취소/명령 요청을 처리하고 이벤트와 명령 응답을 돌려준다.
- **platform-common**: node는 `packages/go/config`, `packages/go/events`, `packages/go/observability`, `proto/gen/iop` 등을 사용하지만 공통 타입/설정/event helper 자체의 소유자는 platform-common이다.
- **control-plane**: control-plane은 Node가 아니라 Edge를 통해 시스템을 제어한다. node는 control-plane 직접 연결/직접 스케줄링을 전제로 하지 않는다.

## 금지 사항

- node 도메인 내부에서 gRPC, WebSocket 기본 transport, actor/FSM/plugin framework를 새 기본 구조로 도입하지 않는다.
- `proto/gen/iop/*.pb.go` 생성 파일을 직접 수정하지 않는다.
- 새 어댑터 구현을 `node.Node`에 직접 분기문으로 박아 넣지 않는다.
- provider tunnel 지원을 RunEvent delta에 섞거나 OpenAI-compatible raw response를 node stdout parser처럼 취급하지 않는다.
- config refresh 중 old registry를 in-flight run이 끝나기 전에 stop해 기존 실행을 끊지 않는다.
- edge-local console, OpenAI-compatible HTTP, A2A 같은 입력 표면 책임을 node로 끌어오지 않는다.
- placeholder 상태인 control-plane/worker 책임을 node에 임시로 흡수하지 않는다.
- CLI provider별 session/conversation 상태를 `runtime` 공통 인터페이스로 성급히 승격하지 않는다. provider 세부 상태는 `adapters/cli` 내부에 둔다.
- field bootstrap 기본 안내에서 사용자가 `IOP_HOME`, `IOP_NODE_CONFIG`, `IOP_NODE_METRICS_PORT` 같은 환경 변수를 먼저 선언해야만 동작하는 형태를 요구하지 않는다. 필요한 값은 bootstrap 기본값 또는 Edge-provided config로 처리하고, 환경 변수는 optional override로만 둔다.
