# SDD: [route-02] IOP 단일 요청 Agent 실행

## 위치

- Milestone: [IOP 단일 요청 Agent 실행](../../../../archive/phase/knowledge-tool-optimization-extension/milestones/iop-owned-single-request-agent-execution.md)
- Phase: [PHASE.md](../../../../phase/knowledge-tool-optimization-extension/PHASE.md)

## 상태

[승인됨]

## SDD 잠금

- 상태: 해제
- 사용자 리뷰: 없음
- 잠금 항목:
  - [x] [D01] one-shot은 사용자 prompt나 logical `request_id`가 아니라 Claude→IOP `/v1/messages` POST 정확히 1회다.
  - [x] [D02] IOP Edge가 외부 요청과 stage state machine, 하나의 outer Anthropic stream과 최종 terminal을 소유한다.
  - [x] [D03] request-scoped workspace와 tool execution은 preset이 승인한 IOP Node가 소유하며 Node 운영체제는 기능 요구가 아니다.
  - [x] [D04] 외부 Claude는 internal tool call/result를 실행하지 않으며 IOP가 두 번째 Messages 요청을 요구하지 않는다.
  - [x] [D05] 초기 stage는 Gemini 3.6 Flash high plan → ornith-fast work → Gemini 3.6 Flash high review/repair 순서다.
  - [x] [D06] 범용 interactive shell·desktop·scheduler는 제외하고 bounded request-scoped tool executor만 포함한다.
  - [x] [D07] Pi/OpenAI Chat Completions는 이 마일스톤에서 사용하지 않는다.
  - [x] [D08] workspace tool wire/runtime은 provider `RunRequest`, provider execution package와 closed `NodeCommand`를 확장하지 않고 별도 typed request-scoped 경계로 둔다.
  - [x] [D09] 초기 preset은 mode selection 없는 fixed `light` Plan/Work/Review 경로이며 direct/heavy/mixed mode 선택은 후속 마일스톤 범위다.
  - [x] [D10] outer stream에는 진행 요약과 최종 사용자 결과만 공개하고 internal provider reasoning, tool protocol과 stage terminal은 공개하지 않는다.

## 문제 / 비목표

- 문제: 현재 compatibility 경로는 provider tool call을 Claude-facing `tool_use`로 종료하고 caller의 다음 `/v1/messages` tool-result 요청에 의존할 수 있다. 이는 사용자가 확정한 단일 요청 모델 동작이 아니다. IOP가 외부 요청을 열린 상태로 유지하면서 plan, workspace 작업, review/repair와 tool result를 모두 내부에서 소유해야 한다.
- 비목표:
  - 범용 shell/desktop/PTY 서비스와 장기 agent process
  - `heavy`의 재계획·여러 review cycle·durable resume
  - Pi/OpenAI Chat Completions one-shot 지원
  - cross-Edge coordinator state replication

## Source of Truth

| 영역 | 기준 | 메모 |
|------|------|------|
| Roadmap | [Milestone 문서](../../../../archive/phase/knowledge-tool-optimization-extension/milestones/iop-owned-single-request-agent-execution.md) | 목표, Task와 완료 상태 원장 |
| Edge Runtime | `apps/edge/internal/openai`, `apps/edge/internal/service` | single ingress, coordinator, stage dispatch, Anthropic outer stream |
| Node Runtime | `apps/node/internal/node`, `apps/node/internal/transport`와 전용 workspace executor | 승인된 IOP Node의 request-scoped workspace/tool 실행; provider execution runtime과 분리 |
| Config/Wire | `packages/go/config`, `proto/iop`, `configs/edge.yaml` | 새 preset model/workspace reference와 전용 Edge-Node tool request/result 계약의 구현 원본 |
| Stream Runtime | `packages/go/streamgate` | internal terminal hold, repetition/no-progress와 final commit |
| API Contract | [Anthropic-Compatible Messages API](../../../../../agent-contract/outer/anthropic-compatible-api.md) | 외부 단일 Messages request/stream/error 계약 |
| Runtime Contract | [Edge-Node Runtime Wire](../../../../../agent-contract/inner/edge-node-runtime-wire.md) | 현재 provider wire 기준; 전용 workspace tool wire 구현 시 함께 갱신 |
| User Decision | D01-D10 | 2026-08-05 최종 합의와 2026-08-08 플랫폼 중립화 결정, 기존 provider/runtime 계약에 따른 책임 분리 |

## State Machine

| 상태 | 진입 조건 | 다음 상태 | 근거 |
|------|-----------|-----------|------|
| `accepted` | Claude `/v1/messages` POST 하나를 인증·admission하고 request/preset/workspace generation을 고정 | `planning`, `failed`, `cancelled` | ingress count, request id, principal/preset/workspace binding |
| `planning` | Gemini 3.6 Flash high가 작은 plan과 검증 기준을 생성하고 internal write를 요청 | `internal_tool`, `working`, `failed`, `cancelled` | plan stage/provider attempt/tool call |
| `working` | ornith-fast가 plan을 읽고 workspace 작업·검증을 수행 | `internal_tool`, `reviewing`, `failed`, `cancelled` | work stage/provider attempt/tool call/completion candidate |
| `reviewing` | Gemini 3.6 Flash high가 결과·검증 evidence를 검사 | `internal_tool`, `repairing`, `finalizing`, `failed`, `cancelled` | review stage verdict/tool call |
| `repairing` | 같은 review stage가 잔존 작업을 수정·재검증 | `internal_tool`, `finalizing`, `failed`, `cancelled` | review/repair tool call/result |
| `internal_tool` | active stage가 canonical workspace tool call을 생성 | 저장된 active stage, `failed`, `cancelled` | Node tool request/result; 외부 Anthropic terminal 없음 |
| `finalizing` | review pass 또는 repair 완료, cleanup 대기 | `completed`, `failed`, `cancelled` | cleanup result, pending final terminal |
| `completed` | cleanup과 최종 response commit 성공 | 종료 | Anthropic terminal 1회, ingress count 1 |
| `failed` | admission/provider/tool/validation/timeout/context 실패 | 종료 | endpoint-native error terminal 1회 |
| `cancelled` | caller disconnect/abort | 종료 | provider/tool process cancel과 bounded cleanup |

State invariant:

- 한 external request에는 하나의 active stage만 있으며 planner, worker, reviewer binding과 workspace generation은 시작 시 고정한다.
- `internal_tool`은 외부 `tool_use` terminal이 아니다. IOP Node result가 active stage provider continuation으로 돌아가고 outer Anthropic stream은 열린 상태를 유지한다.
- Gemini plan/review stage에는 `reasoning_effort=high`를 적용하고 ornith-fast work stage에는 그 옵션을 전파하지 않는다.
- internal provider response-start/terminal은 stage transition evidence이며 public Anthropic envelope을 새로 열거나 닫지 않는다.
- stage별 tool iteration/output/deadline과 request 전체 wall-clock budget은 request 시작 시 고정하고, exhaustion은 다른 stage/model 또는 외부 Claude 요청으로 우회하지 않는다.
- cleanup은 request-owned process와 `.iop/job/<request_id>` artifact만 회수한다. 사용자 요청으로 생성·수정한 workspace 결과는 success/failure cleanup 대상이 아니다.
- completed/failed/cancelled는 서로 배타적이고 final terminal은 exactly-once다.

## Interface Contract

- 계약 원문: [Anthropic-Compatible Messages API](../../../../../agent-contract/outer/anthropic-compatible-api.md), [Edge Config And Runtime Refresh](../../../../../agent-contract/inner/edge-config-runtime-refresh.md), [Edge-Node Runtime Wire](../../../../../agent-contract/inner/edge-node-runtime-wire.md)
- 외부 입력:
  - `POST /v1/messages`: Claude가 보내는 유일한 작업 ingress다.
  - `model`: fixed `light` single-request execution preset에 매핑되는 public model id다.
  - caller body의 `tools[]`는 이 preset의 workspace 실행 권한이나 Node/path selector가 아니다.
- preset/config 입력:
  - `plan`: canonical `gemini-3.6-flash` reference와 high reasoning option.
  - `work`: canonical `ornith-fast` reference; planner/reviewer high option을 상속하지 않는다.
  - `review`: canonical `gemini-3.6-flash` reference와 high reasoning option.
  - `workspace_ref`: operator가 승인한 IOP Node와 workspace root capability reference다. Node 운영체제는 계약에 포함하지 않으며 raw absolute path나 credential을 preset에 직접 넣지 않는다.
  - `limits`: request `wall_clock_ms`와 stage별 `timeout_ms`, `max_tool_iterations`, `max_output_bytes`를 양수와 server absolute cap 안에서 고정한다. refresh는 active request limit을 바꾸지 않는다.
  - 초기 preset은 dynamic selector나 `allowed_modes` advisory를 실행하지 않고 plan → work → review entry를 고정한다. unknown/direct/heavy/mixed binding은 시작 전에 거부한다.
- 내부 tool 입력/출력:
  - provider `RunRequest.metadata`, provider execution package나 closed `NodeCommand`를 workspace 실행 표면으로 재사용하지 않고 전용 typed Edge-Node request/result를 사용한다.
  - canonical operation은 read/list/write/delete/command이며 Edge가 bounded typed request로 만들고 Node가 structured result를 반환한다.
  - command는 fixed workspace cwd, process group, timeout, output cap과 environment allowlist를 가진다.
  - path는 workspace root containment와 symlink escape 방지를 통과해야 한다.
- 외부 출력:
  - plan/work/review/repair의 redacted 진행 요약과 최종 사용자 결과는 하나의 Anthropic stream에서 보일 수 있고, 긴 내부 stage 동안 endpoint-native ping으로 연결 liveness를 유지할 수 있다.
  - internal provider reasoning, tool protocol, provider id, credential, raw command output과 stage terminal은 공개하지 않는다.
  - 최종 response model은 caller가 선택한 public model id를 유지하고 terminal은 한 번만 emit한다.
- 금지:
  - `tool_use` terminal로 외부 Claude에 internal workspace 작업을 넘기거나 두 번째 `/v1/messages`를 요구한다.
  - “사용자 prompt 1회” 또는 “request_id 1개”만 확인하고 one-shot PASS로 판정한다.
  - caller가 arbitrary Node, absolute path, command environment, provider credential 또는 preset 밖 model을 선택하게 한다.
  - review defect를 숨기고 work candidate를 성공으로 반환하거나 provider 실패를 다른 stage/model로 암묵 fallback한다.

## Acceptance Scenarios

| ID | Milestone Task | Given | When | Then |
|----|----------------|-------|------|------|
| S01 | `single-ingress` | Claude가 작은 workspace 작업을 public preset model로 요청 | 작업이 최종 종료 | Edge가 관측한 `/v1/messages` POST가 정확히 1회이고 추가 caller ingress가 없다. |
| S02 | `preset-binding` | authorized Gemini, ornith-fast와 승인된 IOP Node workspace route가 있는 principal | preset을 list/admit/execute | fixed light plan/work/review/workspace binding이 immutable하게 고정되고 public model id가 유지되며 dynamic mode binding은 거부된다. |
| S03 | `stream-terminal` | 여러 internal provider stage가 response-start/content/terminal을 생성하고 stage 사이 대기가 발생 | outer Anthropic SSE를 관측 | redacted progress/ping으로 연결을 유지하고 private reasoning/tool wire 없이 outer envelope 하나, 충돌 없는 block 순서와 최종 terminal 하나만 보인다. |
| S04 | `workspace-binding` | 승인/미승인 workspace, 다른 Node/path와 symlink escape 후보 | request admission과 tool 실행 | 승인된 IOP Node workspace만 실행되고 임의 path/Node/escape는 provider/tool 실행 전에 거부된다. |
| S05 | `tool-executor` | read/list/write/delete/command 성공·실패·timeout·large output | Node tool을 실행 | typed result, containment, process cancel과 output bound가 일관되게 적용된다. |
| S06 | `tool-loop` | internal model이 여러 workspace tool call을 생성 | IOP가 결과를 stage에 반환 | tool loop가 IOP 내부에서 계속되고 Claude-facing `tool_use` terminal이나 두 번째 HTTP request가 없다. |
| S07 | `cleanup-observation` | 성공·오류·cancel 요청이 request artifact/process와 사용자 결과 파일을 생성 | terminal 정리를 수행 | request process와 `.iop/job` artifact만 정책대로 정리되고 사용자 결과는 보존되며 raw content 없이 stage/tool/total timing과 outcome이 연결된다. |
| S08 | `plan-stage` | immutable user task와 empty request job | plan stage 실행 | Gemini 3.6 Flash high가 작은 plan·검증 기준을 만들고 internal `plan.md` write가 성공한다. |
| S09 | `work-stage` | plan과 writable workspace | work stage 실행 | ornith-fast가 high 옵션 없이 plan을 읽고 실제 변경·검증과 completion candidate를 만든다. |
| S10 | `review-stage` | pass 또는 defect work candidate | review stage 실행 | Gemini 3.6 Flash high가 pass를 확정하거나 잔존 작업을 수정·재검증하고 final 결과를 만든다. |
| S11 | `error-cancel` | stage/request budget exhaustion, repetition/no-progress, malformed tool call, provider/tool timeout, output/context limit 또는 disconnect | 요청이 종료 | 추가 Claude 요청, 암묵 stage/model fallback이나 partial-success 없이 표준 error/cancel/length terminal과 내부 cancel로 수렴한다. |
| S12 | `claude-smoke` | 실제 Claude와 선택된 IOP Node의 writable test workspace | 작은 수정·검증 작업을 한 번 요청 | Gemini → ornith-fast → Gemini 순서, stage/total 순수 시간, 최종 파일/검증, ingress POST 1회와 terminal 1회를 redacted 로그로 재현한다. |

## Evidence Map

| Scenario | Required Evidence | `agent-task` 연결 | 완료 Evidence 기대 |
|----------|-------------------|------------------|---------------------------|
| S01 | Edge ingress counter, Claude invocation integration test와 Anthropic contract sync | `agent-task/m-iop-owned-single-request-agent-execution/single-ingress/` | `single-ingress` request-count=1/API contract evidence |
| S02 | preset decode/authorization/model echo/workspace snapshot test와 config contract sync | `agent-task/m-iop-owned-single-request-agent-execution/preset-binding/` | `preset-binding` immutable fixed-light binding evidence |
| S03 | multi-stage fragmented SSE와 single terminal test | `agent-task/m-iop-owned-single-request-agent-execution/stream-terminal/` | `stream-terminal` one-envelope/one-terminal evidence |
| S04 | workspace route/path/symlink admission table test | `agent-task/m-iop-owned-single-request-agent-execution/workspace-binding/` | `workspace-binding` fail-closed evidence |
| S05 | Node tool operation/process/output bound integration test, proto와 Edge-Node contract sync | `agent-task/m-iop-owned-single-request-agent-execution/tool-executor/` | `tool-executor` typed wire/success/error/cancel evidence |
| S06 | internal multi-tool round-trip test with zero public tool terminal | `agent-task/m-iop-owned-single-request-agent-execution/tool-loop/` | `tool-loop` no-external-continuation evidence |
| S07 | cleanup race, user-result preservation과 raw-free timing/log/metric allowlist test | `agent-task/m-iop-owned-single-request-agent-execution/cleanup-observation/` | `cleanup-observation` scoped lifecycle/timing evidence |
| S08 | Gemini plan request/options/artifact fixture | `agent-task/m-iop-owned-single-request-agent-execution/plan-stage/` | `plan-stage` high option과 small-plan evidence |
| S09 | ornith-fast tool work fixture와 high-option absence test | `agent-task/m-iop-owned-single-request-agent-execution/work-stage/` | `work-stage` actual workspace/verification evidence |
| S10 | review pass/defect/repair fixture와 finalization test | `agent-task/m-iop-owned-single-request-agent-execution/review-stage/` | `review-stage` review/repair evidence |
| S11 | budget/error/cancel/length/repetition terminal matrix test | `agent-task/m-iop-owned-single-request-agent-execution/error-cancel/` | `error-cancel` bounded/no-partial/no-second-request evidence |
| S12 | actual Claude, ingress counter, Edge/Node/provider stage+total timing log와 workspace before/after | `agent-task/m-iop-owned-single-request-agent-execution/claude-smoke/` | `claude-smoke` request-count=1 end-to-end/elapsed evidence |

공통 완료 검증은 최소 `go test -race -count=1 ./packages/go/config ./packages/go/streamgate ./apps/edge/internal/openai ./apps/edge/internal/service ./apps/node/internal/node ./apps/node/internal/transport`, 전용 workspace executor package test, `make proto`, `git diff --check`를 포함한다.
실제 provider smoke는 credential과 writable test workspace를 갖춘 승인된 IOP Node에서 실행하되 secret과 raw prompt/tool output을 tracked evidence에 기록하지 않는다.

## Cross-repo Dependencies

- 없음

## Drift Check

- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다.
- [x] Evidence Map이 code-review/complete.log에서 검증 가능하다.
- [x] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
- [x] 사용자 리뷰가 필요한 항목은 없고 확정된 D01-D10을 반영했다.

## 사용자 리뷰 이력

- 2026-08-05: 사용자가 Claude→IOP 요청 정확히 1회, IOP/Mac Node-owned workspace tool execution, Gemini 3.6 Flash high plan → ornith-fast work → Gemini 3.6 Flash high review/잔존 수정과 Pi 제외를 최종 방향으로 확정했다.
- 2026-08-08: 사용자가 Mac/Darwin을 기능 요구에서 제거하고 플랫폼 중립적인 승인 IOP Node workspace로 정정했다. 이번 S12 검증은 dev 인벤토리가 선택한 원격 runner가 Mac인 경우일 뿐 운영체제를 계약으로 고정하지 않는다.

## 작업 컨텍스트

- 표준선: 기존 Anthropic bridge, provider-pool authorization/lease, Stream Evidence Gate와 Edge-Node transport를 재사용하되 caller tool continuation을 one-shot 내부 tool runtime으로 대체한다.
- 구현 순서: preset/workspace config → Edge-Node tool wire와 Node executor → single-request coordinator → plan/work/review stage → stream/error/cleanup → actual Claude smoke.
- 후속 SDD: [Heavy Plan/Review 실행과 검증 MVP](../../../../phase/knowledge-tool-optimization-extension/milestones/knowledge-tool-validation-optimization.md)
