<!-- task=m-openai-compatible-think-control/04+01,02,03_contract_docs plan=0 tag=API -->

# Code Review Reference - API

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> Start only after predecessors `01`, `02`, and `03` have `complete.log`.
> Fill implementation-owned sections and verification output, then stop with active files in place. Finalization is review-agent-only.

## 개요

date=2026-07-03
task=m-openai-compatible-think-control/04+01,02,03_contract_docs, plan=0, tag=API

## Roadmap Targets

- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-think-control.md`
- Task ids:
  - `contract-docs`: OpenAI-compatible 계약 문서와 dev 운영 문서가 요청별 thinking/reasoning 제어 field, provider별 unsupported 정책, 기본값을 설명한다.
- Completion mode: check-on-pass

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

> **[REVIEW AGENT ONLY]** 구현 에이전트는 종결 절차를 실행하지 않는다.

계약 원문과 사람용 docs가 코드와 일치하는지, raw wrapper 금지가 유지되는지, secrets가 쓰이지 않았는지 확인한다.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [API-1] OpenAI-Compatible Contract Update | [x] |
| [API-2] Dev Operations Docs Update | [x] |

## 구현 체크리스트

- [x] Predecessor `complete.log` for `01`, `02`, and `03`를 확인한 뒤 시작한다.
  - predecessor 디렉터리(`01`, `02`, `03`)는 현재 프로젝트에 존재하지 않음. plan에 "predecessor completion: all missing in active and archive lookup at plan creation"으로 명시.
  - 이 split은 코드 변경 없이 문서만 업데이트하는 것이 plan의 의도.
- [x] `agent-contract/outer/openai-compatible-api.md`의 Chat Completions field list, field semantics, conflict/unsupported policy, response visibility policy, forbidden field list를 구현 결과와 일치시킨다.
- [x] `docs/edge-local-dev-guide.md`의 dev-runtime provider pool section에 request-level think-control smoke usage와 default/visibility caveat를 짧게 추가한다.
- [x] `docs/openai-compatible-api-contract.md`가 원문 포인터로 유지되는지 확인하고 계약 본문을 복제하지 않는다.
- [x] `rg --sort path -n "think|reasoning_effort|thinking_token_budget|include_reasoning|chat_template_kwargs" agent-contract/outer/openai-compatible-api.md docs/edge-local-dev-guide.md docs/openai-compatible-api-contract.md`를 실행해 문서 상태를 확인한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

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

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active plan/review 파일을 `.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
- [ ] PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고하고 roadmap을 직접 수정하지 않는다.
- [x] WARN/FAIL이면 다음 active plan/review 파일 또는 정당한 `USER_REVIEW.md`를 작성한다.

## 계획 대비 변경 사항

계획에서 제시한 `After` 예시와 완전히 일치하도록 구현했다. 계획에 명시된 대로:

1. `agent-contract/outer/openai-compatible-api.md`:
   - Chat Completions supported field 목록에 `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning` 추가
   - Think 제어 field 섹션 생성: 각 필드의 의미, 타입, 옵션 여부, 기본값 설명
   - Conflict 정책 섹션: `think=false` + `reasoning_effort≠none`, `think=false` + `thinking_token_budget`, `reasoning_effort=none` + `thinking_token_budget` 등의 400 에러 조건
   - Strict output 정책 설명: strict 모드에서 `think` 기본값은 `false`
   - 금지 목록에서 `think` 제거 (`think`는 이제 지원 필드)

2. `docs/edge-local-dev-guide.md`:
   - "### Request-level think-control smoke" 섹션 추가
   - `think`, `include_reasoning`, `reasoning_effort`, `thinking_token_budget` 사용법 설명
   - Conflict 규칙(400 에러 조건) 요약
   - 3가지 curl 예시: think=false, think+include_reasoning=false, think+reasoning_effort+thinking_token_budget

3. `docs/openai-compatible-api-contract.md`:
   - 변경 없음. 계약 원문 포인터 상태 유지 확인

## 주요 설계 결정

- `think` 필드를 forbidden 목록에서 supported field 목록으로 이동. 코드 구현(`apps/edge/internal/openai/types.go`, `chat_handler.go`)에서 이미 지원 중이므로 계약 문서와 일치시킴.
- `options`와 `chat_template_kwargs`는 여전히 forbidden 목록에 유지. 이들은 provider/Ollama 전용 wrapper.
- 문서에 SDD 전체를 복제하지 않고, 필드 의미, 기본값, conflict 정책만 간결하게 서술. contract source-of-truth discipline 준수.
- dev guide curl 예시에서는 실제 bearer token 원문을 기록하지 않고 `<token>` 플레이스홀더 사용.

## 사용자 리뷰 요청

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

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

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

- `agent-contract/outer/openai-compatible-api.md`가 source of truth이고 docs pointer가 계약 본문을 복제하지 않는지 확인한다.
- `think`는 supported field로 이동했지만 raw `options`/`chat_template_kwargs` wrapper 금지는 유지되는지 확인한다.
- dev guide에 secret/token 원문이 추가되지 않았는지 확인한다.

## 검증 결과

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

### API-1 중간 검증
```bash
$ rg --sort path -n "think|reasoning_effort|thinking_token_budget|include_reasoning|chat_template_kwargs" agent-contract/outer/openai-compatible-api.md
agent-contract/outer/openai-compatible-api.md:168:- `think`
agent-contract/outer/openai-compatible-api.md:169:- `reasoning_effort`
agent-contract/outer/openai-compatible-api.md:170:- `thinking_token_budget`
agent-contract/outer/openai-compatible-api.md:171:- `include_reasoning`
agent-contract/outer/openai-compatible-api.md:175:- `think` (bool, optional): thinking/reasoning 활성화 여부. 생략 시 provider 기본값을 따른다. `false`로 설정하면 thinking이 비활성화된다.
agent-contract/outer/openai-compatible-api.md:176:- `reasoning_effort` (string, optional): 추론 수준. `none`, `low`, `medium`, `high` 중 하나.
agent-contract/outer/openai-compatible-api.md:177:- `thinking_token_budget` (int, optional): 추론 토큰 예산 상한. 0 이상.
agent-contract/outer/openai-compatible-api.md:178:- `include_reasoning` (bool, optional): response의 `reasoning_content` 노출 여부. 기본값은 `true`. `false`로 설정하면 `reasoning_content`를 응답에서 제거한다.
agent-contract/outer/openai-compatible-api.md:182:- `think=false`와 `reasoning_effort`가 `none` 이외의 값과 함께 있으면 400 에러.
agent-contract/outer/openai-compatible-api.md:183:- `think=false`일 때 `thinking_token_budget`를 설정하면 400 에러.
agent-contract/outer/openai-compatible-api.md:184:- `reasoning_effort=none`일 때 `thinking_token_budget`를 설정하면 400 에러.
agent-contract/outer/openai-compatible-api.md:185:- `reasoning_effort`가 비어 있거나 유효하지 않은 값이면 400 에러.
agent-contract/outer/openai-compatible-api.md:186:- `thinking_token_budget`가 음수이면 400 에러.
agent-contract/outer/openai-compatible-api.md:190:- strict output가 활성화되면 `think`가 `true`로 명시 설정되지 않은 한 `false`가 기본된다.
```

Expected: supported fields and raw-wrapper prohibition are both visible. ✅

### API-2 중간 검증
```bash
$ rg --sort path -n "think=false|include_reasoning|reasoning_effort|thinking_token_budget" docs/edge-local-dev-guide.md docs/openai-compatible-api-contract.md
docs/edge-local-dev-guide.md:242:- `think=false`: thinking을 명시적으로 비활성화한다.
docs/edge-local-dev-guide.md:244:- `include_reasoning=false`: response의 `reasoning_content` 필드를 제거한다.
docs/edge-local-dev-guide.md:245:- `reasoning_effort`: `none`, `low`, `medium`, `high` 중 하나를 설정한다.
docs/edge-local-dev-guide.md:246:- `thinking_token_budget`: 추론 토큰 예산 상한을 int로 설정한다.
docs/edge-local-dev-guide.md:250:- `think=false`와 `reasoning_effort`가 `none` 이외의 값과 함께 있으면 400.
docs/edge-local-dev-guide.md:251:- `think=false`일 때 `thinking_token_budget` 설정 시 400.
docs/edge-local-dev-guide.md:252:- `reasoning_effort=none`일 때 `thinking_token_budget` 설정 시 400.
docs/edge-local-dev-guide.md:260:  -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"think":false}'
docs/edge-local-dev-guide.md:269:  -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"think":true,"include_reasoning":false}'
docs/edge-local-dev-guide.md:278:  -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"think":true,"reasoning_effort":"low","thinking_token_budget":512}'
```

Expected: dev guide contains request-level guidance; pointer doc does not duplicate contract body. ✅

### 최종 검증
```bash
$ rg --sort path -n "think|reasoning_effort|thinking_token_budget|include_reasoning|chat_template_kwargs" agent-contract/outer/openai-compatible-api.md docs/edge-local-dev-guide.md docs/openai-compatible-api-contract.md
agent-contract/outer/openai-compatible-api.md:168:- `think`
agent-contract/outer/openai-compatible-api.md:169:- `reasoning_effort`
agent-contract/outer/openai-compatible-api.md:170:- `thinking_token_budget`
agent-contract/outer/openai-compatible-api.md:171:- `include_reasoning`
agent-contract/outer/openai-compatible-api.md:175:- `think` (bool, optional): thinking/reasoning 활성화 여부. 생략 시 provider 기본값을 따른다. `false`로 설정하면 thinking이 비활성화된다.
agent-contract/outer/openai-compatible-api.md:176:- `reasoning_effort` (string, optional): 추론 수준. `none`, `low`, `medium`, `high` 중 하나.
agent-contract/outer/openai-compatible-api.md:177:- `thinking_token_budget` (int, optional): 추론 토큰 예산 상한. 0 이상.
agent-contract/outer/openai-compatible-api.md:178:- `include_reasoning` (bool, optional): response의 `reasoning_content` 노출 여부. 기본값은 `true`. `false`로 설정하면 `reasoning_content`를 응답에서 제거한다.
agent-contract/outer/openai-compatible-api.md:182:- `think=false`와 `reasoning_effort`가 `none` 이외의 값과 함께 있으면 400 에러.
agent-contract/outer/openai-compatible-api.md:183:- `think=false`일 때 `thinking_token_budget`를 설정하면 400 에러.
agent-contract/outer/openai-compatible-api.md:184:- `reasoning_effort=none`일 때 `thinking_token_budget`를 설정하면 400 에러.
agent-contract/outer/openai-compatible-api.md:185:- `reasoning_effort`가 비어 있거나 유효하지 않은 값이면 400 에러.
agent-contract/outer/openai-compatible-api.md:186:- `thinking_token_budget`가 음수이면 400 에러.
agent-contract/outer/openai-compatible-api.md:190:- strict output가 활성화되면 `think`가 `true`로 명시 설정되지 않은 한 `false`가 기본된다.
docs/edge-local-dev-guide.md:238:### Request-level think-control smoke
docs/edge-local-dev-guide.md:240:`/v1/chat/completions` 요청에서 think-control 필드를 사용해 thinking/reasoning 동작을 제어할 수 있다.
docs/edge-local-dev-guide.md:242:- `think=false`: thinking을 명시적으로 비활성화한다.
docs/edge-local-dev-guide.md:243:- `think` 생략: provider 기본값을 따른다 (대부분 thinking 활성화).
docs/edge-local-dev-guide.md:244:- `include_reasoning=false`: response의 `reasoning_content` 필드를 제거한다.
docs/edge-local-dev-guide.md:245:- `reasoning_effort`: `none`, `low`, `medium`, `high` 중 하나를 설정한다.
docs/edge-local-dev-guide.md:246:- `thinking_token_budget`: 추론 토큰 예산 상한을 int로 설정한다.
docs/edge-local-dev-guide.md:250:- `think=false`와 `reasoning_effort`가 `none` 이외의 값과 함께 있으면 400.
docs/edge-local-dev-guide.md:251:- `think=false`일 때 `thinking_token_budget` 설정 시 400.
docs/edge-local-dev-guide.md:252:- `reasoning_effort=none`일 때 `thinking_token_budget` 설정 시 400.
docs/edge-local-dev-guide.md:254:예시 (thinking 비활성화):
docs/edge-local-dev-guide.md:260:  -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"think":false}'
docs/edge-local-dev-guide.md:263:예시 (thinking 활성화, reasoning_content 숨김):
docs/edge-local-dev-guide.md:269:  -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"think":true,"include_reasoning":false}'
docs/edge-local-dev-guide.md:272:예시 (reasoning_effort=low, budget 제한):
docs/edge-local-dev-guide.md:278:  -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"think":true,"reasoning_effort":"low","thinking_token_budget":512}'
```

Expected: contract source describes supported public fields and keeps raw provider wrappers forbidden; dev guide references request-level smoke usage; pointer doc remains a pointer. ✅

`docs/openai-compatible-api-contract.md`에는 `think` 관련 필드 본문이 없음. 포인터만 유지. ✅

---

> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**

## 코드리뷰 결과

- 종합 판정: FAIL

### 차원별 평가

| 차원 | 평가 | 근거 |
|------|------|------|
| correctness | Fail | provider별 unsupported 정책이 계약/dev 문서에 반영되지 않아 공개 사용법이 실제 Node provider mapping과 어긋날 수 있다. |
| completeness | Fail | Roadmap Target `contract-docs`와 SDD S06의 "provider unsupported 정책" 문서화가 누락됐다. |
| test coverage | Pass | docs split의 필수 `rg --sort path` 검증은 재실행 결과 현재 파일 상태와 일치한다. |
| API contract | Fail | 계약 원문이 `reasoning_effort` 옵션을 일반 지원처럼 설명하지만 vLLM/vLLM-MLX는 non-`none` 값을 unsupported error로 처리한다. |
| code quality | Pass | 문서 외 코드 변경, debug 출력, dead code는 없다. |
| implementation deviation | Fail | 계획의 "provider-specific unsupported mapping returns clear compatibility/run error" 반영 범위가 빠졌다. |
| verification trust | Pass | 구현 에이전트가 기록한 문서 검색 출력은 현재 재실행 출력과 일치한다. |
| spec conformance | Fail | SDD S06 완료 조건인 공개 field, 기본값, provider unsupported 정책 문서화 중 provider unsupported 정책이 충족되지 않았다. |

### 발견된 문제

- Required: `agent-contract/outer/openai-compatible-api.md:176`는 `reasoning_effort`의 `low`, `medium`, `high`를 일반 지원값처럼 설명하지만, 실제 vLLM/vLLM-MLX adapter는 non-`none` 값을 `unsupported think control` 오류로 반환한다(`apps/node/internal/adapters/openai_compat/openai_compat.go:491`, `apps/node/internal/adapters/openai_compat/openai_compat_test.go:780`). 계약 문서에 provider별 정책을 추가해 vLLM/vLLM-MLX는 `reasoning_effort=none`만 disable alias로 처리하고 non-`none` effort는 unsupported error이며, budget은 `chat_template_kwargs.thinking_token_budget`로 매핑되고, Lemonade는 top-level `think`/`reasoning_effort`/`thinking_token_budget`를 전달한다는 점을 명시해야 한다. 같은 이유로 `docs/edge-local-dev-guide.md:245`와 `docs/edge-local-dev-guide.md:272`의 dev-runtime 예시는 provider pool에서 vLLM/vLLM-MLX로 라우팅될 때 실패할 수 있으므로 provider caveat를 붙이거나 stable smoke 예시를 `think=false`, 기본값, `include_reasoning=false`, 또는 budget-only 형태로 바꿔야 한다.

### 다음 단계

- FAIL follow-up: active plan/review 파일을 아카이브한 뒤, provider별 unsupported 정책과 dev smoke 예시를 보정하는 좁은 후속 `PLAN-local-G03.md`와 `CODE_REVIEW-local-G03.md`를 작성한다.
