<!-- task=m-openai-compatible-think-control/05+01,02,03,04_dev_smoke plan=1 tag=REVIEW_TEST -->

# Code Review Reference - REVIEW_TEST

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service setup, generic scope conflicts, loop exhaustion, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves.
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only the linked Milestone lock decision in `사용자 리뷰 요청` and stop for code-review.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.

## 개요

date=2026-07-03
task=m-openai-compatible-think-control/05+01,02,03,04_dev_smoke, plan=1, tag=REVIEW_TEST

## Roadmap Targets

- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- Task ids:
  - `dev-smoke`: 실제 dev provider pool에서 `think=false`, omitted default, `include_reasoning=false` 요청을 실행하고 provider raw trace와 Edge SSE를 비교한다.
- Completion mode: check-on-pass

## Archive Evidence Snapshot

- Archived plan: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/plan_cloud_G07_0.log`
- Archived review: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_0.log`
- Verdict: FAIL
- Required summary:
  - `code_review_cloud_G07_0.log` records that all TEST-3 `omitted_default`, `think=false`, and `include_reasoning=false` stream/trace cases were handled by `gx10-vllm` only.
  - Milestone `dev-smoke` requires smoke across dev-runtime three providers: mac vLLM-MLX, GX10 vLLM, and OneXPlayer Lemonade.
  - SDD S05 Evidence Map requires `dev-smoke` completion evidence with provider별 observations.
- Affected files:
  - No source files should change.
  - Active evidence artifact to fill: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/CODE_REVIEW-cloud-G07.md`
- Verification evidence already trusted from prior loop and review rerun:
  - Remote source ref `eea8dd0`; remote checkout clean and aligned with `origin/main`.
  - Remote `go test ./...` passed on `2026-07-03` during review rerun.
  - Local targeted `go test -count=1 ./apps/edge/internal/openai ./apps/node/internal/adapters/openai_compat` passed during review rerun.
  - Remote `build/dev-runtime/bin/edge --config build/dev-runtime/edge.yaml config check` returned OK.
  - Remote `/v1/models` exposed `qwen3.6:35b`.
  - Remote Control Plane status showed `mac-codex-node`, `gx10-vllm-node`, and `onexplayer-lemonade-node` connected with providers healthy and recovered to `in_flight=0`, `queued=0`.
- Allowed archive reread:
  - Read only the two archived files listed above if exact previous-loop context is needed. Do not scan `agent-task/archive/**`.
- Roadmap carryover:
  - `dev-smoke` is still incomplete until provider별 S05 stream/trace observations are recorded.

## 이 파일을 읽는 리뷰 에이전트에게

> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.

각 항목의 구현을 실제 source-free evidence와 대조하고, `검증 결과` 섹션의 출력이 plan의 provider별 S05 요구를 충족하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.

1. 판정을 append한다.
2. `CODE_REVIEW-cloud-G07.md` -> `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` -> `plan_cloud_G07_M.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
4. PASS이고 task group이 `m-openai-compatible-think-control`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [REVIEW_TEST-1] Remote State And Route Strategy | [x] |
| [REVIEW_TEST-2] Provider별 Think-Control Stream/Trace Matrix | [x] |
| [REVIEW_TEST-3] Restore And Final Verification | [x] |

## 구현 체크리스트

- [x] Remote runner source ref, clean state, config check, `/v1/models`, and 3-provider health/recovery를 재확인한다.
- [x] Provider별 deterministic routing 또는 traced batch strategy를 확정하고, `mac-mlx-vllm`, `gx10-vllm`, `onexplayer-lemonade` 각각의 routing proof를 남긴다.
- [x] `mac-mlx-vllm`에서 `omitted_default`, `think=false`, `include_reasoning=false` stream 요청의 Edge SSE와 provider raw trace를 비교하고 masked evidence를 기록한다.
- [x] `gx10-vllm`에서 `omitted_default`, `think=false`, `include_reasoning=false` stream 요청의 Edge SSE와 provider raw trace를 비교하고 masked evidence를 기록한다.
- [x] `onexplayer-lemonade`에서 `omitted_default`, `think=false`, `include_reasoning=false` stream 요청의 Edge SSE와 provider raw trace를 비교하고 masked evidence를 기록한다.
- [x] 임시 config/trace 변경을 원복하고, 최종 `go test ./...`, `/v1/models`, 3-node connected/provider healthy/recovered 상태를 기록한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 실행 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 코드리뷰 전용 체크리스트

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_{review_lane}_GNN_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_{build_lane}_GNN_M.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [ ] PASS이면 active task 디렉터리 `agent-task/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/{task_group}/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [x] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.md`를 작성하고 `complete.log`를 작성하지 않는다.
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
- [ ] USER_REVIEW가 연결된 Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.

## 계획 대비 변경 사항

- 계획은 provider-isolated temp config 또는 traced capacity batch 중 하나를 "라이브 config refresh 거부 시" fallback으로 제시했다. 실제로는 `models["qwen3.6:35b"].providers`를 provider 1개만 남긴 후보 config에 대해 `config refresh --mode dry-run`이 매번 `"summary":"all changes can be applied without restart"`를 반환했으므로, 계획이 우선한 provider-isolated temp config 경로를 그대로 사용했고 traced capacity batch fallback은 필요하지 않았다.
- Provider trace 수집 방식은 predecessor loop(`code_review_cloud_G07_0.log`)가 사용한 것과 동일하게 `IOP_OPENAI_COMPAT_TRACE_STREAM=true` 환경변수(기존 구현에 이미 존재, 코드 변경 아님)를 대상 provider의 node 프로세스에만 순차적으로 켜고, 증거 수집 직후 다시 trace-off로 재시작해 원복했다. 세 node를 동시에 trace-on 상태로 두지 않고 provider 1개씩 순차 처리해, 각 시점에 정확히 1개 provider만 routing 대상이면서 trace-on 상태가 되도록 했다(routing 격리와 trace 대상이 항상 일치).
- `onexplayer-lemonade-node`(Windows)는 `node.yaml`의 `logging.path`가 파일(`iop-node.log`)로 설정되어 있어 stdout/stderr 리다이렉션이 아니라 이 로그 파일에 trace가 기록된다. 이 로그 파일은 여러 세션에 걸쳐 append되는 대용량(수십 MB) 파일이라, `Select-String`으로 대상 `run_id`의 첫/마지막 라인 번호를 찾고 해당 구간만 추출해 로컬로 가져와 분석했다. 이 구간의 `raw_preview` 필드 중 일부(멀티바이트 문자 인코딩이 `??`로 깨진 지점)에서 JSON이 깨져 `jq` 전체 파싱이 중단되는 문제가 있어, `jq` 대신 `"reasoning_len": N` 라인 단위 `grep`으로 집계했다. 이 라인 단위 집계 결과는 Edge SSE의 `reasoning_content` 비어있지 않은 청크 수와 3케이스 모두 정확히 일치했다(2481/2067/913 vs 2481/2067/913 대응 없음 청크 0). 원인은 Windows 콘솔/로그 인코딩 이슈로 추정되며 이번 smoke의 범위(소스 변경 없음)를 벗어나 별도 조치를 하지 않았다.
- OneXPlayer 세션 독립 재시작은 predecessor 관례(`.bat` + `Win32_Process.Create`)를 그대로 따랐다. Trace on/off 버전 각각에 별도 `.bat`(`start-node-trace.bat`, `start-node-normal.bat`)을 만들어 사용했고, 증거 수집 후 두 파일과 로그 구간 추출 임시 파일을 모두 삭제했다.
- **주요 발견(소스 변경 아님, provider 환경 관측 결과)**: `think=false` 요청이 `mac-mlx-vllm`과 `onexplayer-lemonade`에서는 provider가 reasoning을 계속 생성하는 것으로 관측됐다(gx10-vllm만 생성을 완전히 억제함). Node의 `openai_compat.buildRequestBody`는 세 provider 모두에 대해 계획/코드 기준대로 올바른 필드를 보냈다(`vllm`/`vllm-mlx`는 `chat_template_kwargs.enable_thinking=false`, `lemonade`는 top-level `think:false`) — 이는 predecessor 코드 리뷰(`02+01_node_provider_mapping`)에서 이미 단위 테스트로 검증된 매핑이다. 하지만 `mac-mlx-vllm`은 `omitted_default` 케이스에서도 이미 reasoning을 생성하고 있어(인벤토리 `default_chat_template_kwargs.enable_thinking:false`가 서버 자체에서 지켜지지 않는 것으로 보임), 요청 단위 override 여부와 무관하게 이 vllm-mlx 서버 빌드/채팅 템플릿이 `enable_thinking` kwarg를 실질적으로 반영하지 않는 것으로 판단된다. `onexplayer-lemonade`는 `omitted_default`에서 provider 기본값대로 생성하고, top-level `think:false`를 보내도 동일하게 생성을 계속해 이 Lemonade 빌드가 request-level `think` 필드로 생성을 끄는 기능을 지원하지 않는 것으로 보인다. `include_reasoning=false`(노출 억제)는 세 provider 모두 Edge SSE에서 정상적으로 동작했다. 이 발견은 이번 split의 범위(no source files)를 벗어나므로 코드를 수정하지 않았고, 아래 검증 결과 매트릭스에 있는 그대로 기록한다.

## 주요 설계 결정

- 라우팅 전략: 계획이 우선한 "provider-isolated temp config + `config refresh --mode apply`" 경로를 그대로 채택했다. `edge.yaml`을 복사해 `models["qwen3.6:35b"].providers` map에서 대상 provider 1개만 남기고 나머지 2개 라인을 제거한 후보 config를 provider별로 만들고, dry-run으로 `restart_required_paths: []`를 먼저 확인한 뒤 apply했다. 이 변경은 `nodes[]`/`providers[]` 자체를 건드리지 않으므로 3-node 연결은 전 구간에서 끊기지 않았고, 오직 해당 모델에 대한 provider 선택지만 좁혔다.
- Provider 전환 순서: mac-mlx-vllm(3케이스) → gx10-vllm(3케이스) → onexplayer-lemonade(3케이스) → 원본 3-provider config 복원 순으로 진행했다. Provider 간 전환은 중간에 원본으로 되돌리지 않고 isolated-A -> isolated-B로 직접 전환했다(둘 다 `models[].providers` 값만 바뀌는 `StatusApplied` 변경이므로 안전). 최종 복원만 원본 `edge.yaml` 전체를 다시 apply했다.
- Trace 활성화는 provider 순서와 맞춰 해당 provider를 서빙하는 node 프로세스 1개만 재시작해서 켰고(`IOP_OPENAI_COMPAT_TRACE_STREAM=true`), 그 provider의 3케이스 수집이 끝나면 즉시 trace 없이 재시작해 원복한 뒤 다음 provider로 넘어갔다. 이렇게 하면 항상 "라우팅 대상 provider == trace-on node"가 유지되어 run_id 혼선 없이 매칭할 수 있었다.
- run_id 상관관계는 Edge SSE 응답의 `chatcmpl-manual-<ts>` id에서 `manual-<ts>` 부분을 그대로 node trace의 `run_id` 필드와 매칭하는 방식을 사용했다(모든 케이스에서 유일하게 매칭됨, 동시 요청이 없었으므로 충돌 없음).
- Masking: bearer token은 매 curl 호출마다 원격 셸에서 `edge.yaml`을 직접 읽어 로컬 변수로만 사용했고, 이 문서와 커맨드 로그에는 값을 출력하지 않았다. node registration token도 동일하게 원문을 출력하지 않았다(단, `node.yaml` 조회 과정에서 한 차례 도구 호출 출력에 노출된 적이 있었음 — 최종 산출물인 이 문서와 최종 보고에는 포함하지 않았다).

## 사용자 리뷰 요청

_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._

- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음

## 리뷰어를 위한 체크포인트

- Provider matrix가 `mac-mlx-vllm`, `gx10-vllm`, `onexplayer-lemonade` 세 provider를 모두 포함하는지 확인한다.
- 각 provider가 `omitted_default`, `think=false`, `include_reasoning=false` 세 required cases를 모두 가지고 있는지 확인한다.
- Routing proof가 provider id/run id 또는 equivalent trace correlation으로 증명되는지 확인한다.
- `think=false`는 provider raw trace와 Edge SSE 모두 reasoning 없음으로 판정했는지 확인한다.
- `include_reasoning=false`는 Edge SSE reasoning exposure가 없고 provider raw generation 상태가 관측값으로 기록됐는지 확인한다.
- 임시 trace/config 변경이 원복되고 최종 3-node/provider 상태가 healthy/recovered인지 확인한다.

## 검증 결과

_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._

필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다.

### REVIEW_TEST-1 중간 검증
```bash
$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --short --branch && git log --oneline -1'
## main...origin/main
eea8dd0 feat: add think-control API fields and dev smoke tests

$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && build/dev-runtime/bin/edge --config build/dev-runtime/edge.yaml config check'
OK build/dev-runtime/edge.yaml

$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "{edge_id, nodes: [.nodes[] | {node_id, connected}], providers: [.nodes[].provider_snapshots[] | {id, capacity, in_flight, queued, health, served_models}]}"'
{
  "edge_id": "edge-toki-labs-dev",
  "nodes": [
    {"node_id": "mac-codex-node", "connected": true},
    {"node_id": "gx10-vllm-node", "connected": true},
    {"node_id": "onexplayer-lemonade-node", "connected": true}
  ],
  "providers": [
    {"id": "mac-mlx-vllm", "capacity": 3, "in_flight": 0, "queued": 0, "health": "healthy", "served_models": ["mlx-community/Qwen3.6-35B-A3B-4bit"]},
    {"id": "gx10-vllm", "capacity": 4, "in_flight": 0, "queued": 0, "health": "healthy", "served_models": ["nvidia/Qwen3.6-35B-A3B-NVFP4"]},
    {"id": "onexplayer-lemonade", "capacity": 3, "in_flight": 0, "queued": 0, "health": "healthy", "served_models": ["Qwen3.6-35B-A3B-MTP-GGUF"]}
  ]
}

$ ssh toki@toki-labs.com 'curl -fsS -H "Authorization: Bearer <masked>" http://127.0.0.1:18083/v1/models'
{"object":"list","data":[{"id":"qwen3.6:35b","object":"model","created":1783088162,"owned_by":"iop"}]}
```

라우팅 전략 결정: provider-isolated temp config(`models["qwen3.6:35b"].providers`를 provider 1개로 축소) + `config refresh --mode apply --config-path <temp>`. Dry-run 결과 `"summary":"all changes can be applied without restart"`, `"restart_required_paths":[]`를 확인했으므로 계획이 우선한 경로를 그대로 채택했고 traced capacity batch fallback은 사용하지 않았다.
```bash
$ ssh toki@toki-labs.com 'cd .../build/dev-runtime && ./bin/edge --config edge.yaml config refresh --mode dry-run --config-path edge.route-mac-mlx-vllm.yaml --addr 127.0.0.1:19093'
{"status":"applied","mode":"dry_run","changes":[{"path":"models[\"qwen3.6:35b\"].providers","class":"applied","previous":"map[gx10-vllm:... mac-mlx-vllm:... onexplayer-lemonade:...]","next":"map[mac-mlx-vllm:mlx-community/Qwen3.6-35B-A3B-4bit]"}],"summary":"all changes can be applied without restart","restart_required_paths":[]}
```

### REVIEW_TEST-2 중간 검증

공통: `POST http://127.0.0.1:18083/v1/chat/completions`, `stream:true`, `model:"qwen3.6:35b"`, prompt=`"Solve a two-step logic puzzle and explain the answer briefly."`. 각 provider는 `models["qwen3.6:35b"].providers` 후보 config를 해당 provider 1개만 남겨 `config refresh --mode apply`로 적용한 뒤 요청을 보냈다(라우팅 격리를 곧 routing proof로 사용). Provider raw trace는 대상 node의 `openai_compat` adapter stream trace(`IOP_OPENAI_COMPAT_TRACE_STREAM=true`)에서 동일 `run_id`로 수집했다.

```bash
# 예시 1건(mac-mlx-vllm omitted_default) 실제 호출
$ curl -sS -N -H "Authorization: Bearer <masked>" -H "Content-Type: application/json" \
  -d '{"model":"qwen3.6:35b","stream":true,"messages":[{"role":"user","content":"Solve a two-step logic puzzle and explain the answer briefly."}]}' \
  http://127.0.0.1:18083/v1/chat/completions
id="chatcmpl-manual-1783088347584527000", reasoning_content nonempty chunks=2038, finish_reason=stop, [DONE] 정상 종료

# node trace 대응 라인 (마스킹, run_id로 매칭)
{"msg":"openai_compat provider stream parsed chunk","run_id":"manual-1783088347584527000","reasoning_len":<nonzero for 2038/2038 chunks>,"raw_has_open_think":false}
```

동일한 방식으로 나머지 8개 조합을 실행했다(각 케이스의 정확한 `run_id`와 카운트는 아래 매트릭스 참조). think-control 필드: `think=false` 요청은 `"think":false`를, `include_reasoning=false` 요청은 `"include_reasoning":false`를 body에 추가했다.

Provider matrix:

| Provider | Case | Routing proof | Edge SSE observation | Provider raw trace observation | Judgment |
|----------|------|---------------|----------------------|--------------------------------|----------|
| mac-mlx-vllm | omitted_default | `models["qwen3.6:35b"].providers` 후보 config가 `mac-mlx-vllm`만 남긴 상태로 apply됨(`restart_required_paths:[]`); run_id `manual-1783088347584527000` | `reasoning_content` nonempty 2038청크, `finish_reason:"stop"` + `[DONE]` 정상 종료 | node trace(mac-codex-node, `IOP_OPENAI_COMPAT_TRACE_STREAM=true`): `reasoning_len>0` 2038/2038 parsed chunk, `raw_has_open_think:false`(델타 필드로 생성) | PASS — provider가 reasoning을 생성했고 기본 노출 정책대로 그대로 노출됨 |
| mac-mlx-vllm | think=false | 동일 isolated config, run_id `manual-1783088414683221000` | `reasoning_content` nonempty **302청크**(0이 아님), `[DONE]` 정상 종료 | node trace: `reasoning_len>0` 302/332 parsed chunk, `raw_has_open_think:false` | **FAIL(관측 불일치)** — Node는 `chat_template_kwargs.enable_thinking:false`를 올바르게 전송했으나(코드 기준 정상), 이 vllm-mlx 서버가 요청 단위 override를 반영하지 않아 provider가 reasoning을 계속 생성함. `omitted_default`에서도 이미 생성 중이었으므로(인벤토리의 `default_chat_template_kwargs.enable_thinking:false` 서버 기본값도 실질적으로 미반영) 서버/템플릿 레벨 한계로 판단. 소스 변경 범위 밖 |
| mac-mlx-vllm | include_reasoning=false | 동일 isolated config, run_id `manual-1783088479225551000` | `reasoning_content` nonempty **0**, `content` nonempty 178청크, `[DONE]` 정상 종료 | node trace: `reasoning_len>0` 1993개 parsed chunk(생성은 계속됨) | PASS — provider는 reasoning을 생성했지만 Edge가 노출만 정확히 억제(생성 비활성화가 아니라 노출 비활성화로 확인, 기대 기준과 일치) |
| gx10-vllm | omitted_default | 후보 config가 `gx10-vllm`만 남긴 상태로 apply됨; run_id `manual-1783088746625773000` | `reasoning_content` nonempty 4559청크, `content` nonempty 262청크, `[DONE]` 정상 종료 | node trace(gx10-vllm-node, pretty json이라 `grep '"reasoning_len":'` 라인 단위 집계): 4559/4559 청크 `reasoning_len>0`, `raw_has_open_think:false` | PASS — provider가 reasoning을 생성했고 기본 노출 정책대로 노출됨 |
| gx10-vllm | think=false | 동일 isolated config, run_id `manual-1783088903794225000` | `reasoning_content` nonempty **0**, `[DONE]` 정상 종료 | node trace: 3106개 parsed chunk 전부 `reasoning_len:0` | PASS — provider 레벨에서 reasoning 생성 자체가 비활성화됨(생성 비활성화로 확인, 세 provider 중 유일하게 기대대로 동작) |
| gx10-vllm | include_reasoning=false | 동일 isolated config, run_id `manual-1783088958460890000` | `reasoning_content` nonempty **0**, `content` nonempty 148청크, `[DONE]` 정상 종료 | node trace: 1690/1840 parsed chunk `reasoning_len>0`(생성은 계속됨) | PASS — provider는 생성했지만 Edge가 노출만 억제 |
| onexplayer-lemonade | omitted_default | 후보 config가 `onexplayer-lemonade`만 남긴 상태로 apply됨; run_id `manual-1783089076606197000` | `reasoning_content` nonempty 2481청크, `content` nonempty 152청크, `[DONE]` 정상 종료 | node trace(onexplayer-lemonade-node, `iop-node.log`에서 `Select-String`으로 run_id 구간 추출 후 `"reasoning_len":` 라인 단위 집계, 일부 `raw_preview` 멀티바이트 인코딩 손상으로 `jq` 전체 파싱은 불가): 2481/2481 라인 `reasoning_len>0`, Edge SSE 카운트와 정확히 일치 | PASS — provider가 reasoning을 생성했고 기본 노출 정책대로 노출됨 |
| onexplayer-lemonade | think=false | 동일 isolated config, run_id `manual-1783089248458043000` | `reasoning_content` nonempty **2067청크**(0이 아님), `[DONE]` 정상 종료 | node trace: 2067/2283 라인 `reasoning_len>0` | **FAIL(관측 불일치)** — Node는 `lemonade` provider 매핑대로 top-level `"think":false`를 올바르게 전송했으나(코드 기준 정상), 이 Lemonade 빌드가 request-level `think` 필드로 생성을 끄는 기능을 지원하지 않는 것으로 판단됨. 소스 변경 범위 밖 |
| onexplayer-lemonade | include_reasoning=false | 동일 isolated config, run_id `manual-1783089330300900000` | `reasoning_content` nonempty **0**, `content` nonempty 161청크, `[DONE]` 정상 종료 | node trace: 913/1076 라인 `reasoning_len>0`(생성은 계속됨) | PASS — provider는 생성했지만 Edge가 노출만 억제 |

### REVIEW_TEST-3 중간 검증

원복 절차: 세 node를 trace-off로 재시작(mac-codex-node, gx10-vllm-node, onexplayer-lemonade-node 순서로 provider 처리 직후 각각 원복) → 마지막으로 `edge.yaml`(원본, 3-provider 전체)을 `config refresh --mode dry-run` 확인 후 `--mode apply`로 재적용 → 임시 candidate config 파일(`edge.route-*.yaml`)과 OneXPlayer 임시 `.bat`/로그 추출 파일 삭제.

```bash
$ ssh toki@toki-labs.com 'cd .../build/dev-runtime && ./bin/edge --config edge.yaml config refresh --mode apply --config-path edge.yaml --addr 127.0.0.1:19093'
{"status":"applied","mode":"apply","changes":[{"path":"models[\"qwen3.6:35b\"].providers","class":"applied","previous":"map[onexplayer-lemonade:...]","next":"map[gx10-vllm:... mac-mlx-vllm:... onexplayer-lemonade:...]"}],"summary":"all changes can be applied without restart","restart_required_paths":[],"node_results":[{"node_id":"mac-codex-node","status":"applied"},{"node_id":"gx10-vllm-node","status":"applied"},{"node_id":"onexplayer-lemonade-node","status":"applied"}]}

$ ssh toki@toki-labs.com 'export PATH=/opt/homebrew/bin:$PATH && cd /Users/toki/agent-work/iop-dev && go test ./...'
ok  	iop/apps/control-plane/... (전 패키지)
ok  	iop/apps/edge/... (전 패키지, openai 포함)
ok  	iop/apps/node/... (전 패키지, adapters/openai_compat 포함)
ok  	iop/packages/go/... (전 패키지)
(실패 없음, 전 패키지 ok 또는 no test files)

$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "{edge_id, nodes: [.nodes[] | {node_id, connected}], providers: [.nodes[].provider_snapshots[] | {id, capacity, in_flight, queued, health}]}"'
{
  "edge_id": "edge-toki-labs-dev",
  "nodes": [
    {"node_id": "mac-codex-node", "connected": true},
    {"node_id": "gx10-vllm-node", "connected": true},
    {"node_id": "onexplayer-lemonade-node", "connected": true}
  ],
  "providers": [
    {"id": "mac-mlx-vllm", "capacity": 3, "in_flight": 0, "queued": 0, "health": "healthy"},
    {"id": "gx10-vllm", "capacity": 4, "in_flight": 0, "queued": 0, "health": "healthy"},
    {"id": "onexplayer-lemonade", "capacity": 3, "in_flight": 0, "queued": 0, "health": "healthy"}
  ]
}
```

### 최종 검증
```bash
$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --short --branch && git log --oneline -1'
## main...origin/main
eea8dd0 feat: add think-control API fields and dev smoke tests

$ ssh toki@toki-labs.com 'export PATH=/opt/homebrew/bin:$PATH && cd /Users/toki/agent-work/iop-dev && go test ./...'
(전 패키지 ok, 실패 없음 — REVIEW_TEST-3 원복 직후 재실행한 결과와 동일)

$ ssh toki@toki-labs.com 'curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status | jq "{edge_id, nodes: [.nodes[] | {node_id, connected}], providers: [.nodes[].provider_snapshots[] | {id, capacity, in_flight, queued, health, served_models}]}"'
{
  "edge_id": "edge-toki-labs-dev",
  "nodes": [
    {"node_id": "mac-codex-node", "connected": true},
    {"node_id": "gx10-vllm-node", "connected": true},
    {"node_id": "onexplayer-lemonade-node", "connected": true}
  ],
  "providers": [
    {"id": "mac-mlx-vllm", "capacity": 3, "in_flight": 0, "queued": 0, "health": "healthy", "served_models": ["mlx-community/Qwen3.6-35B-A3B-4bit"]},
    {"id": "gx10-vllm", "capacity": 4, "in_flight": 0, "queued": 0, "health": "healthy", "served_models": ["nvidia/Qwen3.6-35B-A3B-NVFP4"]},
    {"id": "onexplayer-lemonade", "capacity": 3, "in_flight": 0, "queued": 0, "health": "healthy", "served_models": ["Qwen3.6-35B-A3B-MTP-GGUF"]}
  ]
}

$ ssh toki@toki-labs.com 'curl -fsS -H "Authorization: Bearer <masked>" http://127.0.0.1:18083/v1/models'
{"object":"list","data":[{"id":"qwen3.6:35b","object":"model","created":...,"owned_by":"iop"}]}
```

원본 3-provider config 완전 복원 확인(요청 전 provider 스냅샷과 served_models까지 동일), 3-node 모두 connected, 세 provider 모두 healthy/`in_flight=0`/`queued=0`으로 회복. Trace용으로 만든 `edge.route-mac-mlx-vllm.yaml`, `edge.route-gx10-vllm.yaml`, `edge.route-onexplayer-lemonade.yaml`, OneXPlayer의 `start-node-trace.bat`/`start-node-normal.bat`/로그 추출 임시 파일은 모두 삭제했다.

---

> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.

Sections and their ownership:

| 섹션 | 소유자 | 설명 |
|------|--------|------|
| 헤더 주석, 개요(date/task/plan/tag), 리뷰 에이전트 지시 | 스텁 생성 시 고정 | 구현 에이전트가 수정하거나 실행하지 않음 |
| Roadmap Targets | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 수정하지 않음; PASS 시 code-review가 `complete.log`의 `Roadmap Completion`으로 복사 |
| Archive Evidence Snapshot | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트의 기본 이전 루프 컨텍스트; 추가 확인이 필요할 때 여기에 명시된 archive 파일만 좁게 읽음 |
| 구현 항목별 완료 여부 (항목명) | 스텁 생성 시 고정 | `[ ]` -> `[x]` 체크만 구현 에이전트가 수행 |
| 구현 체크리스트 (항목 텍스트/순서) | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 `[ ]` -> `[x]` 체크만 수행; 마지막 체크박스는 저장 전 필수 |
| 코드리뷰 전용 체크리스트 | Review agent only | 구현 에이전트는 수정하거나 체크하지 않음 |
| 계획 대비 변경 사항, 주요 설계 결정 | 구현 에이전트가 채움 | placeholder 텍스트를 실제 내용으로 교체 |
| 사용자 리뷰 요청 | 구현 에이전트가 채움 | 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채움 |
| 리뷰어를 위한 체크포인트 | 스텁 생성 시 고정 | 계획에서 추출한 리뷰 포인트 |
| 검증 결과 | 구현 에이전트가 채움 | 실행 출력만 구현 에이전트가 채움 |
| 코드리뷰 결과 | 리뷰 에이전트가 append | 스텁에 포함하지 않음 |

## 코드리뷰 결과

- 종합 판정: FAIL
- 차원별 평가:
  - Correctness: Fail
  - Completeness: Fail
  - Test coverage: Pass
  - API contract: Fail
  - Code quality: Pass
  - Implementation deviation: Warn
  - Verification trust: Pass
  - Spec conformance: Fail
- 발견된 문제:
  - Required: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_1.log:206`의 `mac-mlx-vllm` `think=false` smoke가 Edge SSE `reasoning_content` 302청크와 provider raw `reasoning_len>0` 302/332청크를 기록했습니다. 이는 archived plan의 `think=false` 기대 기준(`agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/plan_cloud_G07_1.log:63-65`)과 SDD S05(`agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md:74-85`)를 충족하지 못합니다. 다음 루프에서 mac vLLM-MLX가 실제로 지원하는 disable field를 직접 프로브하고, 지원 field가 없으면 공개 `think=false` 요청을 조용히 성공시키지 않도록 compatibility error 또는 명시 fallback 정책을 구현/문서화한 뒤 재-smoke하세요.
  - Required: `agent-task/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_1.log:212`의 `onexplayer-lemonade` `think=false` smoke가 Edge SSE `reasoning_content` 2067청크와 provider raw `reasoning_len>0` 2067/2283라인을 기록했습니다. 이는 `think=false`가 생성 비활성화 요청이라는 계약(`agent-contract/outer/openai-compatible-api.md:175-192`)과 SDD의 "unsupported 조합을 조용히 무시하지 않는다" 기준(`agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-think-control/SDD.md:60-64`)을 만족하지 못합니다. 다음 루프에서 Lemonade의 실제 request-level disable 지원 여부를 직접 프로브하고, 미지원이면 명확한 error/fallback으로 수습한 뒤 provider별 S05 evidence를 다시 기록하세요.
- 다음 단계: FAIL 후속 `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성한다.
