---
test_env: dev
test_profile: node-smoke
domain: node
verification_type: smoke
last_rule_updated_at: 2026-08-02
---

# node-smoke dev 테스트

## 읽기 조건

- `apps/node/**` 변경 또는 node 실행, adapter, transport, router, store dev 검증 판단이 필요한 경우

## 적용 범위

- `apps/node/cmd/node/**`
- `apps/node/internal/bootstrap/**`
- `apps/node/internal/node/**`
- `apps/node/internal/runtime/**`
- `apps/node/internal/router/**`
- `apps/node/internal/transport/**`
- `apps/node/internal/adapters/**`
- `apps/node/internal/store/**`

## 분류

- domain: node
- verification_type: smoke
- scope: node 실행 파이프라인과 edge 연결 baseline

## 환경

- host: local checkout. dev host, external CLI profile, shared Edge runtime evidence가 필요하면 원격 runner를 사용한다.
- port: compose dev Edge-Node TCP transport `19003`; dev-runtime provider pool native Edge-Node TCP transport `18084`
- runtime: Go `1.24`
- package manager: Go modules / Makefile
- docker: unit/smoke quick check는 Docker를 요구하지 않는다. compose dev 검증은 `docker compose --env-file .env.dev.example ...`로 수행한다.
- external service: compose Edge runtime 주소 후보 `toki-labs.com:19003`; dev-runtime provider pool native Edge 주소 후보 `toki-labs.com:18084`
- model endpoint: dev OpenAI-compatible base URL 후보 `http://toki-labs.com:18083/v1`
- credential: token/secret 원문은 문서에 기록하지 않는다.

## dev-runtime Node 접속 기준

dev-runtime의 실제 4-node 연결을 점검할 때는 원격 runner `ssh toki@toki-labs.com`의 `/Users/toki/agent-work/iop-dev` checkout과 `build/dev-runtime/edge.yaml`을 기준으로 한다. Node는 dev-runtime provider pool 기준에서 Edge-Node TCP `toki-labs.com:18084`로 붙는다.

- mac CLI + MLX provider node: `mac-codex-node` / `mac-codex`
  - SSH/user: `ssh toki@toki-labs.com`
  - 목적: `cli` adapter, `codex app-server` + `mac-mlx-vllm` provider resource
  - workspace: `/Users/toki/agent-work/iop-workspace/nomadcode`
  - MLX vLLM provider: `mac-mlx-vllm`
    - provider endpoint: `http://127.0.0.1:8002/v1`
    - served model: `mlx-community/Qwen3.6-35B-A3B-4bit`
    - capacity baseline: `2`
    - priority baseline: `2`
    - workdir: `/Users/toki/agent-work/iop-mlx-vllm`
    - runtime baseline: `vllm-mlx`, `--max-num-seqs 2`, `--max-kv-size 262144`, `--max-request-tokens 262144`, `--use-paged-cache --paged-cache-block-size 64 --max-cache-blocks 4096`, `--enable-auto-tool-choice --tool-call-parser qwen --reasoning-parser qwen3`, `--default-chat-template-kwargs {"enable_thinking": true}`
    - KV policy: per-call window bound `262144`, one full context-size setting
    - long-context admission baseline: `total_context_tokens=262144`, `long_context_capacity=1` (KV budget `262144` fits one full 262144-window request; long slot count is not the normal capacity `2`)
- GX10 vLLM node: `gx10-vllm-node` / `gx10-vllm`
  - SSH/user: `ssh toki@192.168.0.91`
  - provider endpoint: `http://192.168.0.91:8001/v1`
  - served model: `laguna-s:2.1`
  - capacity baseline: `4`
  - priority baseline: `1`
  - runtime baseline: custom image `iop-vllm-laguna-s21:v0.25.1-cu130`, model `poolside/Laguna-S-2.1-NVFP4` revision `07614121b31898586430f189d27a25a0be310843`, `--served-model-name laguna-s:2.1`, `--max-model-len 262144`, `--max-num-seqs 4`, `--gpu-memory-utilization 0.70`, `--enable-prefix-caching`, `--enable-auto-tool-choice --tool-call-parser poolside_v1`, `--reasoning-parser poolside_v1`, `--chat-template /run/iop/laguna-s-2.1-thinking.jinja`, `--default-chat-template-kwargs '{"enable_thinking":true}'`, `--override-generation-config '{"temperature":0.7,"top_p":0.95}'`
  - thinking template baseline: host `/home/toki/iop-gx10-vllm/laguna-s-2.1-thinking.jinja`를 container `/run/iop/laguna-s-2.1-thinking.jinja`에 read-only bind하고, generation prefix를 `<think>\n`으로 둔다. stock `<think>` prefix는 첫 생성 토큰으로 `</think>`를 내보내 reasoning이 비는 현상이 재현됐다.
  - speculative decoding baseline: quantization-matched `poolside/Laguna-S-2.1-DFlash-NVFP4` revision `723794750422b3efbf3a7b3af76dffb4ba035943`, `method=dflash`, `num_speculative_tokens=7`
  - long-context admission baseline: observed GPU KV cache `373711` tokens, `long_context_capacity=1` (vLLM max concurrency `1.43x` for a 262144-token request)
  - reasoning baseline: provider-native stream field is `reasoning`; Pi `openai-completions` accepts `reasoning_content`, `reasoning`, and `reasoning_text`. Pi Laguna model compat sends `chat_template_kwargs.enable_thinking` from the selected thinking level and keeps `preserve_thinking=true`.
  - workspace: `/home/toki/iop-gx10-vllm`
- OneXPlayer Lemonade node: `onexplayer-lemonade-node` / `onexplayer-lemonade`
  - SSH/user: `ssh r0bin@192.168.0.59`
  - 접속 기준: 현재 작업 호스트에서 직접 SSH
  - provider endpoint: `http://192.168.0.59:13305/v1`
  - served model: `Ornith-1.0-35B-GGUF-llamacpp-tp1-Q5_K_M`
  - capacity baseline: `1` (agent 장문 요청의 provider 독점 실행 기준)
  - priority baseline: `2`
  - load baseline: checkpoint `LordNeel/Ornith-1.0-35B-GGUF-llamacpp-tp1:Q5_K_M`, backend `vulkan`, ctx size `262144`, `llamacpp_args="--spec-type none -np 1 -cb -fa on -b 4096 -ub 1024 --kv-unified --temp 0.6 --top-p 0.95 --top-k 20"`, `save_options=true`
  - long-context admission baseline: `total_context_tokens=262144`, `long_context_capacity=1` (IOP의 provider 직렬 admission과 llama-server의 `-np 1`을 일치시켜 agent turn별 prefix cache가 서로 다른 slot에 분산되지 않게 한다. `/slots`는 slot 1개와 `n_ctx=262144`를 보고한다)
  - workspace: `C:/Users/r0bin/iop-field`
- RTX5090 Lemonade node: `rtx5090-lemonade-node` / `rtx5090-lemonade`
  - SSH/user: `ssh iop-dev-rtx5090`
  - 접속 기준: 현재 작업 호스트의 local SSH config alias와 public-key batch 인증. host identity와 공개키 지문은 `agent-test/inventory-dev.yaml`의 `ssh_access`를 따른다.
  - provider endpoint: `http://192.168.0.111:13305/v1`
  - served model: `Ornith-1.0-35B-GGUF-llamacpp-tp1-Q5_K_M`
  - capacity baseline: `1`
  - priority baseline: `0`
  - load baseline: checkpoint `LordNeel/Ornith-1.0-35B-GGUF-llamacpp-tp1:Q5_K_M`, backend `cuda`, ctx size `262144`, `llamacpp_args="--chat-template-kwargs '{\"preserve_thinking\":true}' --kv-unified --min-p 0.00 --repeat-penalty 1.0 --spec-type none --temp 0.6 --top-k 20 --top-p 0.95 -b 512 -cb -ctk q8_0 -ctv q8_0 -fa on -np 1 -ub 256"`
  - network baseline: Lemonade `host=0.0.0.0`; localhost-only bind는 Node의 provider endpoint 접속 실패를 유발한다.
  - long-context admission baseline: `total_context_tokens=262144`, `long_context_capacity=1`
  - process baseline: IOP Node와 Lemonade Server의 Windows 부팅 자동 실행은 비활성이다. 수동 운영 owner는 `C:/Users/r0bin/iop-field/remote-llm-toggle.ps1`이며 UP은 Lemonade Server -> Ornith load -> Node, DOWN은 Node -> Ornith unload -> Lemonade Server 순서다. IOP dev 배포는 Startup shortcut, Run entry, Task Scheduler, Windows service를 생성하지 않는다.
  - workspace: `C:/Users/r0bin/iop-field`

GX10은 Laguna S 2.1 NVFP4 + DFlash NVFP4를 사용한다. OneXPlayer와 RTX5090 Lemonade는 Ornith Q5 GGUF를 사용하고 runtime speculative decoding은 `--spec-type none`으로 끈다. provider family별 parser/template을 섞지 않는다.

GX10은 Linux/ARM64 bootstrap, OneXPlayer와 RTX5090은 Windows native PowerShell bootstrap을 기본으로 한다. 두 Windows node는 현재 작업 호스트에서 직접 접속해 세팅하며, 원격 runner나 Edge host에서 proxy 실행하지 않는다.

mac-mlx-vllm provider는 mac-codex-node 소속 resource로, Edge host와 같은 macOS host에서 vllm-mlx process로 실행한다. vllm-mlx API는 외부에 직접 노출하지 않고 `127.0.0.1:8002`에 bind하며, Edge의 OpenAI-compatible adapter가 local bearer header로 호출한다. 운영 확인은 `/Users/toki/agent-work/iop-mlx-vllm/vllm-mlx.pid`, `logs/vllm-mlx.stdout.log`, `logs/vllm-mlx.stderr.log`를 기준으로 한다. Docker와 macOS 여유 메모리를 고려해 capacity는 `2`를 기본선으로 유지한다.

OneXPlayer에서 SSH 세션 안의 `Start-Process`로 `iop-node.exe`를 띄우면 SSH 세션 종료와 함께 process가 정리될 수 있다. dev 반복 배포에서는 `Win32_Process.Create` 또는 동등한 세션 독립 실행 방식으로 `C:/Users/r0bin/iop-field`에서 `iop-node.exe --config node.yaml serve`를 시작하고, WMI/process query와 `iop-node.log`의 `connected to edge` 로그로 유지 여부를 확인한다.

Node binary 교체 시 최초 원본은 `pre-<release>`로 한 번만 보존한다. 같은 release에서 후속 candidate를 배포하면 원본 backup을 덮어쓰지 말고 실패 candidate를 `pre-<release>-<short-sha>`로 별도 보존한다. candidate hash를 host에서 확인한 뒤에만 current binary로 이동한다.

Edge config payload가 거부되어 Node가 `internal config error`로 종료되면 일반 reconnect 대상이 아니다. fixed Edge가 올바른 source와 listener로 실행 중인지 먼저 확인하고 mac, GX10, OneXPlayer, RTX5090 Node를 각각 명시적으로 다시 시작한다.

RTX5090은 `ssh iop-dev-rtx5090`으로 batch 접속을 먼저 확인하고 Node process, toggle status의 `edge_connected`, Control Plane의 connected node 상태를 함께 확인한다. `RemoteLLM_mode.ahk`는 모니터 profile 적용 후 수동 toggle script를 한 번만 실행하며 Windows 부팅, Task Scheduler, 직접 Lemonade load 명령에 연결하지 않는다. 자동화 검증에서는 상태에 따라 반전되는 기본 Toggle 대신 `-Action Status|Up|Down`을 명시하고, `ready`는 Node의 Edge TCP `18084` 연결까지 포함해야 한다. Lemonade 복구 시에는 `0.0.0.0:13305`, 정확한 Ornith profile, llama slot `1`/`n_ctx=262144`를 확인한다. 비밀번호나 개인키 경로는 문서와 실행 로그에 기록하지 않는다.

GX10 Laguna 공식 vLLM baseline은 `temperature=0.7`, `top_p=0.95`, model `generation_config`의 `top_k=20`, `tool_call_parser=poolside_v1`, `reasoning_parser=poolside_v1`, `enable_thinking=true`다. 현재 dev는 stock generation prefix가 think block을 즉시 닫는 현상을 막기 위해 `<think>\n` 로컬 템플릿을 추가한다. Pi의 `high` thinking은 `chat_template_kwargs.enable_thinking=true`로 전달하고 이전 reasoning block은 `preserve_thinking=true`로 재사용한다. think smoke는 `high`에서 `thinking_start`/`thinking_delta`/`thinking_end`, `off`에서 thinking event 0개와 최종 text를 확인하고, agentic multi-turn에서는 tool-call 전후 reasoning과 최종 text까지 확인한다.

OneXPlayer/RTX5090 Ornith 공식 sampling baseline은 `temperature=0.6`, `top_p=0.95`, `top_k=20`이다. caller의 명시값이 provider 기본값보다 우선한다.

Qwen provider를 agent/tool-call 용도로 검증할 때는 일반 chat smoke와 별도로 forced tool call, auto tool call, streaming `delta.tool_calls`, multi-turn tool result 후 최종 답변을 확인한다. raw native marker나 reasoning text가 assistant content로 새면 해당 model/runtime의 parser/template profile 미확정으로 보고한다.
Qwen runtime에는 Qwen 전용 parser/template 검증값만 사용한다. dev-corp Gemma 계열의 `tool_call_parser=gemma4`, `reasoning_parser=gemma4`, Gemma4 chat template/profile을 Qwen provider에 복사하지 않는다.

## 명령

- setup:
- lint:
- unit: `go test ./apps/node/...`
- smoke: `./scripts/e2e-smoke.sh`는 기본 포트 임시 설정을 쓰는 보조 smoke이므로 dev 포트 override 필요 여부를 먼저 확인한다.
- e2e: `make test-e2e`
- model:
- full-cycle: repo 내부 edge-node 진단과 사용자 실행 cycle 수동 검증

## 필수 검증

- 변경한 node 패키지 또는 `go test ./apps/node/...`를 실행한다.
- Windows Node candidate는 cross-build 성공만으로 완료하지 않는다. `packages/go/credentiallease` Windows test executable을 실제 Windows host에서 실행해 현재 사용자/System/Administrators만 읽을 수 있는 key file은 허용하고 Everyone read ACE는 거부하는지 확인한다.
- 실행 요청, stream, cancel, status, session, adapter registry 경로를 바꾼 경우 repo 내부 edge-node 진단과 full-cycle 실제 구동 기준을 함께 적용한다.
- CLI profile 변경 시 `/capabilities`, `/transport`, `/sessions`, persistent profile이면 `/terminate-session`을 확인한다.
- compose Edge profile로 연결하는 경우 Node가 `19003`을 사용하고 local/test `19090` field baseline으로 붙지 않는지 확인한다. dev-runtime provider pool native Edge로 연결하는 경우에는 Node가 `18084`를 사용한다. 두 profile의 transport를 섞어 연결 성공을 판정하지 않는다.

## 보조 검증

- `./scripts/e2e-smoke.sh`는 mock adapter 기반 보조 smoke로 사용한다.
- `make test-e2e`는 보조 smoke이며 full-cycle 실제 구동을 대체하지 않는다.

## 판정 기준

- node 등록 후 edge console에서 node가 조회된다.
- 같은 session에서 메시지 2회가 start, 기대 payload를 포함한 `[node-*-message]`, 같은 run의 complete 순서로 edge 화면에 도착한다.
- node 로컬 `[node-message]` payload 라인 목록과 edge `[node-*-message]` payload 라인 목록이 run별로 내용/순서까지 동일해야 한다.
- edge complete는 같은 run의 마지막 `[node-*-message]` 이후에만 정상이다.
- command 결과가 `[node-*-<command>]` 또는 명확한 성공/unsupported/error 출력으로 edge 화면에 표시된다.

## 기준 출력 예시

```text
edge> /nodes
edge> Convert token iop_manual_one and reply only with converted token
[node-*-event] start
[node-*-message] IOP_MANUAL_ONE_OK
[node-*-event] complete
```

## 차단 기준

- dev host 또는 Edge-Node TCP port 접근이 불가능하다.
- 외부 CLI profile 검증에 필요한 CLI 설치, 계정, provider 상태가 없다.
- Edge 로그에 provider/adapter instance key 충돌이 있고 Node가 `internal config error`로 종료된다. Node 재시작을 반복하지 말고 Edge mapper/source identity를 먼저 복구한다.

## 보고 항목

- 실행한 명령:
- 성공한 검증:
- 실패/차단된 검증:
- 생략 사유:
- 남은 위험:

## 금지 사항

- 사용자 실행 파이프라인 변경을 unit test만으로 완료 처리하지 않는다.
- `proto/gen/iop/*.pb.go` 생성 파일을 직접 수정하지 않는다.
- secret, token, 개인 endpoint 원문은 tracked 파일에 기록하지 않는다.
