iop/apps/node
toki c50c1df0b3 refactor(bridge): 터미널 경계 안정화를 반영한다
원격 터미널 브리지 선행 작업을 위해 PTY session core를 node-owned terminal package로 분리하고, CLI persistent executor가 새 경계를 사용하도록 정리한다.

검증 루프 산출물과 roadmap 컨텍스트도 함께 반영해 완료 근거와 후속 포트 표준화 범위를 남긴다.
2026-06-07 10:51:26 +09:00
..
cmd/node refactor: migrate packages to packages/go/ structure 2026-06-01 10:03:55 +09:00
internal refactor(bridge): 터미널 경계 안정화를 반영한다 2026-06-07 10:51:26 +09:00
README.md refactor: migrate packages to packages/go/ structure 2026-06-01 10:03:55 +09:00

node — Node Agent

디바이스당 1개 실행되는 IOP 노드 에이전트.
Edge에 연결되어 adapter + target 실행을 수행하고, TCP/protobuf 소켓을 통해 요청, 명령, 이벤트 스트림을 처리한다. mTLS helper는 packages/go/auth에 있지만 현재 Edge-Node transport에는 아직 연결하지 않았다.

내부 구조 (Hexagonal Architecture)

cmd/node/            — CLI 진입점 (cobra)
internal/
  bootstrap/         — fx 의존성 주입 모듈
  node/              — 핵심 노드 서비스 (transport.Handler 구현)
  runtime/           — 도메인 타입: ExecutionSpec, RuntimeEvent, 인터페이스
  router/            — RunRequest → ExecutionSpec 라우팅
  transport/         — Edge로 연결하는 TCP/protobuf client와 session 처리
  adapters/
    mock/            — 에코 테스트 어댑터
    ollama/          — Ollama /api/chat 스트리밍 어댑터
    vllm/            — vLLM OpenAI-compatible 어댑터 skeleton
    cli/             — CLI 프로세스 어댑터 (claude/antigravity/codex/opencode/cline)
  store/             — SQLite 실행 이력

실행 경계

field 사용자 기본 경로에서 Node는 별도 설정 파일을 만들거나 편집하지 않는다. Edge가 제시한 bootstrap 명령 한 줄을 실행하면 Node binary 다운로드, 검증, Edge 연결, foreground 실행까지 이어져야 한다.

아래 명령은 Node binary 자체의 저수준 개발/진단용 표면이다. 사용자-facing field bootstrap 안내로 쓰지 않는다.

# 빌드
make build-local

# 버전 확인
./build/bin/iop-node version

# 저수준 직접 실행은 내부 진단에서만 사용한다.

호스트 환경 준비 참고

setup은 현재 구현된 host setup 보조 기능이다. field 사용자 기본 경로는 Node setup/config가 아니라 Edge가 제시한 bootstrap 명령이다. 새 사용자-facing 흐름을 설계할 때 이 표면을 기본 경로로 확장하지 않는다.

현재 구현된 node CLI 표면은 다음과 같다.

iop-node serve
iop-node version

원격 edge에 붙는 사용자/field 테스트는 Edge가 제시한 bootstrap 명령을 실행한다. repo root의 scripts/dev/node.sh는 과거 개발 진단 helper로만 취급하고, 문서나 runbook의 공식 사용자 경로로 안내하지 않는다.

edge 예시 설정은 cli adapter의 opencode profile을 사용한다. configs/edge.yamlconsole.targetclaude, claude-tui, antigravity, codex, opencode, cline-dgx 같은 profile 이름으로 바꾸면 같은 실행 파이프라인에서 다른 CLI target을 검증할 수 있다.

edge에서 실행 요청을 받으면 node는 해당 입력을 선택된 adapter target으로 전달하고, adapter가 emit한 delta/event를 Edge로 스트리밍한다.

[edge-message] hello
[node-event] start run_id=manual-...
[node-event] complete run_id=manual-... detail="opencode sse execution complete"
[node-message] <adapter output>

Edge-Node 메시지 경계

node는 같은 TCP/protobuf session 위에서 여러 메시지 계열을 처리한다.

  • RunRequest: adapter execution 시작. CLI adapter에서는 prompt 전달로 해석된다.
  • CancelRequest: 실행 취소 또는 logical session 종료.
  • NodeCommandRequest: 실행이 아닌 node/adapter 조회성 명령. 현재 USAGE_STATUS, CAPABILITIES, SESSION_LIST, TRANSPORT_STATUS를 지원한다.
  • RunEvent: adapter execution stream. start, delta, complete, error, cancelled 같은 실행 이벤트를 edge로 보낸다.
  • EdgeNodeEvent: edge-node lifecycle/control event envelope. edge 연결 해제 등 실행 스트림이 아닌 이벤트를 다룬다.

즉 CLI에 메시지를 보내는 요청 파이프라인과 이벤트 파이프라인은 논리적으로 분리되어 있고, 물리 transport만 공유한다.

Local Execution History

Node local SQLite store는 node가 직접 수행한 run의 최소 상태(run_id, adapter, target, session_id, background, status, timestamps, error)를 보관한다. 이 DB는 현재 node-local 복구/진단용 실행 이력이며 Edge event aggregation이나 Control Plane 전역 이력 저장소가 아니다. Edge와 상위 운영면은 실행 stream/event를 Edge 경유로 관찰하고, Node DB를 직접 읽는 구조를 전제로 하지 않는다.

Logical Session (transport 1개 · session 여러 개)

edge-node transport 연결은 호스트당 1개를 유지한다. 그 연결 위에서 CLI adapter는 session_id가 다른 여러 장수 worker process를 독립적으로 관리한다.

개념 설명
transport 연결 edge-node 호스트 쌍당 1개 TCP 연결
logical session (adapter, target, session_id) 로 식별되는 장수 worker process
run session 위에서 실행되는 단일 요청

cancel vs terminate:

  • CancelAction_CANCEL_RUN (기본값): 현재 실행 중인 run만 중단. session process는 살아있다.
  • CancelAction_TERMINATE_SESSION: session process를 명시적으로 종료. 이후 같은 session_id로의 요청은 새 process를 만들거나(CREATE_IF_MISSING) 에러를 반환한다(REQUIRE_EXISTING).

session_mode:

  • RUN_SESSION_MODE_CREATE_IF_MISSING (기본값): session이 없으면 새로 생성.
  • RUN_SESSION_MODE_REQUIRE_EXISTING: session이 없으면 에러 반환. 새 process 생성 금지.

Node Commands

node는 adapter execution(RunRequest) 외에도 edge가 보내는 NodeCommandRequest를 처리한다. 현재 구현된 command:

  • USAGE_STATUS: 선택된 adapter/target (예: cli/codex)의 사용량 한도와 초기화 시간을 조회한다.
    • Codex의 경우 TUI를 시작하고 /status 명령을 전송한 뒤 출력된 % left 정보를 파싱해 AgentUsageStatus로 반환한다.
    • 그 외 adapter/target은 지원하지 않는 경우 명시적 에러를 반환한다.
  • CAPABILITIES: 요청한 adapter의 capability를 조회한다. 현재 응답은 adapter 이름, target 목록, max_concurrency를 포함한다.
  • SESSION_LIST: CLI adapter가 관리하는 logical session 목록을 조회한다. 응답 result map에 count, sessions(쉼표 구분 mode:target/session_id 레이블) 외에 session.N.mode, session.N.target, session.N.session_id structured key가 포함된다. edge console은 이 key를 감지해 session별 grouped output으로 표시한다.
  • TRANSPORT_STATUS: node 관점의 edge 연결 여부와 요청 echo fields(node_id, adapter, target, session_id)를 조회한다. heartbeat 상세 카운터는 현재 응답에 포함되지 않는다.

어댑터

어댑터 설명 상태
mock 입력 에코, 스트리밍 테스트용 구현 완료
ollama 로컬 Ollama /api/chat 스트리밍 연동 기본 구현 완료
vllm vLLM OpenAI-compatible API /v1/models 조회 skeleton
cli claude/antigravity/codex/opencode/cline CLI 실행 구현 완료

Ollama adapter는 edge에서 받은 내부 target을 Ollama model 이름으로 사용한다. Edge OpenAI-compatible HTTP API에서 openai.target을 지정하면 외부 model 값과 무관하게 해당 Ollama model로 라우팅하고, openai.target을 비우면 요청의 model 값을 그대로 내부 target으로 전달한다. adapters.ollama.context_size가 설정되어 있으면 Ollama /api/chat 요청의 options.num_ctx로 전달한다.

claude, antigravity, codex, opencode, cline처럼 기본이 interactive TUI인 CLI는 기본적으로 non-interactive 모드로 설정한다. 예: claude -p --dangerously-skip-permissions, agy --dangerously-skip-permissions --print <prompt>, codex exec --dangerously-bypass-approvals-and-sandbox, cline -y --json --config /config/.cline/profiles/ollama-dgx <prompt>.

Claude는 용도별로 두 profile을 둔다. claudeclaude -p 기반 one-shot profile이며 Agent SDK/credit 경로 검증용으로 유지한다. claude-tuipersistent: true, terminal: true, mode: "persistent-lazy"로 일반 Claude Code TUI를 첫 요청 시 lazy start한다. TUI 출력은 structured JSON이 아니므로 idle timeout 기반으로 completion을 판단한다.

Antigravity는 mode: "antigravity-print"로 설정해 TUI가 아닌 print mode에서도 IOP session_id별 conversation을 이어간다. 첫 실행은 agy --print <prompt>로 새 conversation을 만들고 로그에서 conversation id를 읽어 저장한다. 같은 IOP session의 다음 실행은 resume_args를 사용해 agy --conversation <conversation_id> --print <prompt> 형태로 재개한다.

opencode는 profile mode: "opencode-sse"로 설정해 opencode serve의 HTTP/SSE 인터페이스를 사용한다. profile.Command가 가리키는 opencode 바이너리를 serve --hostname 127.0.0.1 --port 0으로 띄우고 /global/event SSE에서 text delta를 RuntimeEvent delta로 relay한다. 외부에서 이미 실행 중인 server에 연결하려면 --attach <url>을 args에 추가한다. legacy output_format: "opencode-json" stdout JSONL 경로는 opencode run --format json 사용 시에만 의미가 있다.

Cline은 mutable --config 디렉터리를 프로필처럼 나눈다. /config/.cline/profiles/ollama-dgx/config/.cline/profiles/ollama-m1opencodeollama-dgx/ollama-m1 provider 설정을 Cline의 ollama provider 설정으로 변환한 값이다.

Transport

  • TCP 4-byte length-prefix framing
  • protobuf message framing via common-proto-socket
  • edge 연결 해제는 EdgeNodeEvent로 로컬 표시되며, 실행 스트림 RunEvent와 분리한다.
  • mTLS helper는 존재하지만 현재 transport 설정에는 아직 연결되지 않음
  • 하트비트: 30초 간격