# Plan: IOP Responses Surface

## 작업 범위

Milestone `External Integration`의 `[iop-responses]` 완료를 막는 IOP 측 누락을 먼저 닫는다. sibling IOP repo의 Edge OpenAI-compatible input surface에 non-streaming `POST /v1/responses`를 추가하고, NomadCode가 보내는 `input`, `instructions`, `metadata`, `model`, `stream=false` 요청을 edge service의 기존 `SubmitRun` 경로로 변환한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/external-integration/milestones/external-integration.md`
- 완료 대상 Task id: 없음. 이 subtask는 선행 IOP 표면 구현이며, 최종 `[iop-responses]` 체크는 `02+01_nomadcode_core_iop_smoke`에서 원격 Core enqueue smoke까지 통과한 뒤 주장한다.

## 분석 결과

- NomadCode Core는 이미 `services/core/internal/adapters/openai/client.go`에서 `POST /v1/responses`를 호출하고 `model.GenerateInput.Metadata`를 request `metadata` field에 싣는다.
- sibling IOP repo의 `apps/edge/internal/openai/server.go`는 현재 `/healthz`, `/v1/models`, `/v1/chat/completions`, `/api/`만 등록한다.
- sibling IOP docs와 edge smoke rule은 `/v1/models`, `/v1/chat/completions`를 OpenAI-compatible baseline으로 두고, `/v1/responses`는 계획 표면으로 남긴다.
- 원격 `18081` endpoint는 `healthz`, `v1/models`, `v1/responses` smoke에서 empty reply가 나왔고, 기본 model endpoint의 `/v1/responses` POST는 timeout됐다. 이 상태는 NomadCode 완료 근거가 아니다.
- 분할 판단: cross-repo IOP API 표면 구현과 NomadCode Core 원격 smoke는 서로 다른 소유 경계와 검증 전략을 갖는다. 먼저 IOP 표면 구현을 완료하고, 그 완료 로그를 전제로 두 번째 subtask에서 Core smoke를 수행한다.

## 구현 계획

1. sibling IOP repo 작업 전 `~/agent-work/iop`의 dirty worktree를 재확인하고, 사용자 변경으로 보이는 unrelated files는 건드리지 않는다.
2. IOP repo의 해당 규칙을 따른다: `AGENTS.md`, project rules, edge domain rule, testing rule, edge smoke rule.
3. `apps/edge/internal/openai/server.go`에 `POST /v1/responses` handler를 추가한다.
4. Responses request DTO는 최소한 NomadCode가 보내는 shape를 지원한다: `model`, string `input`, optional `instructions`, optional `metadata`, `stream`, sampling fields는 무시 또는 명시적 passthrough 정책을 정한다.
5. `stream=true`는 이번 범위에서 명시적 400 또는 501로 처리하고, non-streaming만 완료 기준으로 둔다.
6. prompt 구성은 `instructions`가 있으면 system/instruction prefix로 포함하고, `input`을 user prompt로 변환한다. 기존 chat completion의 strict output policy와 `SubmitRun` 호출 방식을 재사용한다.
7. `metadata`는 secret 원문 로깅 없이 `SubmitRunRequest.Metadata`에 전달한다. IOP가 추가하는 `source`, `openai_model`, `openai_stream`, `strict_output` 같은 keys와 사용자 metadata 충돌 정책을 테스트로 고정한다.
8. Responses response DTO는 NomadCode parser가 읽을 수 있게 `id`, `model`, `output_text`, `output[].content[].text`, `usage` 중 최소 `output_text`와 usage mapping을 제공한다.
9. `apps/edge/internal/openai/server_test.go`에 Responses handler tests를 추가한다. metadata가 service stub에 전달되는지 반드시 assert한다.
10. `apps/edge/cmd/edge/main.go`의 `smoke openai`를 확장하거나 별도 option을 추가해 `/v1/responses` non-streaming smoke를 실행할 수 있게 한다.
11. IOP docs/test rules는 필요 최소로 갱신하되, 공식 사용자 경로는 `iop-edge` command 표면을 유지한다.

## 검증 계획

- IOP repo: `go test ./apps/edge/...`
- IOP repo: 변경 범위가 command/test helper까지 닿으면 `go test ./...`
- IOP repo: 임시 edge/node 또는 test server로 `iop-edge smoke openai`의 Responses path가 성공하는지 확인한다.
- NomadCode repo: 이 subtask에서는 Core smoke를 완료 주장하지 않는다. 다음 subtask가 최종 통합 smoke를 맡는다.

## 위험과 주의점

- sibling IOP repo는 현재 dirty 상태다. unrelated 사용자 변경을 되돌리거나 덮어쓰지 않는다.
- `/v1/responses`를 OpenAI 공식 shape 전체로 구현하려 하지 않는다. NomadCode completion에 필요한 non-streaming subset과 향후 확장 가능한 DTO 경계를 우선한다.
- metadata 값은 non-secret smoke marker 정도만 사용하고 raw token/secret은 로그, docs, roadmap에 남기지 않는다.
