diff --git a/agent-contract/outer/gemini-compatible-api.md b/agent-contract/outer/gemini-compatible-api.md index b17a367c..19ea44e2 100644 --- a/agent-contract/outer/gemini-compatible-api.md +++ b/agent-contract/outer/gemini-compatible-api.md @@ -56,7 +56,7 @@ x-goog-api-key: 초기 호환 범위는 official `agy` 1.1.12가 보내는 다음 top-level field다. -- `contents[]`: `role`, `parts[].text`, `parts[].functionCall`, `parts[].functionResponse`, optional opaque `thoughtSignature` +- `contents[]`: `role`, `parts[].text`, `parts[].functionCall`, `parts[].functionResponse`, optional opaque `thoughtSignature`. Official `agy` 1.1.12가 tool 실행 뒤 독립 content에 `role: model`로 보내는 `functionResponse`도 허용하고 기존 호출과 매칭한 Chat `tool` message로 변환한다. 같은 model content에 assistant text/thought/function call과 `functionResponse`를 섞는 모호한 형식은 거부한다. - `systemInstruction`: official `agy`의 `role: user`와 `parts[].text` - `generationConfig`: `candidateCount`, `maxOutputTokens`, `stopSequences`, `temperature`, `topK`, `topP`, `thinkingConfig.includeThoughts`, `thinkingConfig.thinkingBudget`, `responseMimeType`, 그리고 상호 배타적인 `responseSchema`/`responseJsonSchema` - `tools[].functionDeclarations[]`: `name`, `description`, 상호 배타적인 `parameters`/`parametersJsonSchema`, optional 상호 배타적인 `response`/`responseJsonSchema` @@ -104,3 +104,5 @@ HTTP commit 전 오류는 다음 Gemini envelope 한 건으로 반환한다. 2026-08-12 dev 검증에서 normal/boundary/auth/tool/SSE 회귀 테스트와 공식 `agy` 1.1.12 direct·hybrid 실호출이 통과해 이 계약을 active로 전환했다. 2026-08-12 실호출 보정에서 official agy planner의 structured-output schema field를 Chat `response_format`으로 변환했다. + +2026-08-13 실호출 보정에서 official agy 1.1.12의 model-role tool `functionResponse` continuation을 허용했다. diff --git a/agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md b/agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md index ae644eaf..9fa4d77f 100644 --- a/agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md +++ b/agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md @@ -69,6 +69,10 @@ Phase를 가로지르는 실제 다음 작업 선택은 [전역 마일스톤 실 - 경로: [[bench-lite-01] 초경량 Agent 모델 비교](milestones/thin-agent-model-comparison-benchmark.md) - 요약: 최소 HTML 스모크를 통과한 동일 경로를 복구·재개·자동 채점 없는 단일 시도로 실행하고, 성공 여부·경과 시간·제공된 usage·산출물만 한 표에 기록한다. +- [계획] [surface-01] Inference API Surface와 실행 Lifecycle 책임 경계 리팩터링 + - 경로: [[surface-01] Inference API Surface와 실행 Lifecycle 책임 경계 리팩터링](milestones/inference-api-surface-execution-lifecycle-refactor.md) + - 요약: `iop-s0`의 bench-02 결과와 제품 delta가 `dev`에 정합화된 뒤 OpenAI Chat/Responses, Anthropic Messages, Gemini의 wire 계약은 surface별로 유지하고, 기존 provider service 경계를 재사용하며 cross-surface handler 재진입과 단계별 lifecycle 소유권만 정리한다. + - [스케치] [output-03] OpenAI-compatible Runtime Output Integrity Filter - 경로: [[output-03] OpenAI-compatible Runtime Output Integrity Filter](milestones/openai-compatible-runtime-output-integrity-filter.md) - 요약: terminal assistant 응답이 content, valid tool call, 명시 허용 structured/error finish 중 하나를 만족해야 한다는 runtime invariant를 정의하고, empty terminal, reasoning-only, incomplete tool-call syntax 같은 deterministic violation을 공통 filter pipeline과 bounded retry 정책으로 묶는다. diff --git a/agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/inference-api-surface-execution-lifecycle-refactor.md b/agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/inference-api-surface-execution-lifecycle-refactor.md new file mode 100644 index 00000000..e9bea455 --- /dev/null +++ b/agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/inference-api-surface-execution-lifecycle-refactor.md @@ -0,0 +1,99 @@ +# Milestone: [surface-01] Inference API Surface와 실행 Lifecycle 책임 경계 리팩터링 + +## 위치 + +- Roadmap: [ROADMAP.md](../../../ROADMAP.md) +- Phase: [PHASE.md](../PHASE.md) + +## 목표 + +OpenAI Chat/Responses, Anthropic Messages, Gemini ingress의 서로 다른 외부 계약은 surface별 adapter가 소유하고, 인증 이후 provider admission·dispatch·attempt·cancel은 기존 `runService`/`SubmitProviderPool` 경계를 재사용한다. Protocol별 실행 준비와 endpoint commit·terminal·usage projection은 각 기존 runtime에 남겨 단계별 소유권을 명확히 한다. +현재 외부 응답과 provider-native option 전달을 보존하면서 Gemini의 Chat handler 내부 HTTP 재진입과 surface/lifecycle 책임 혼재를 제거해, 이후 provider 보완이 다른 API surface에 미치는 회귀 범위를 줄인다. + +## 상태 + +[계획] + +## 승격 조건 + +- 없음 + +## 구현 잠금 + +- 상태: 잠금 +- SDD: 필요 +- SDD 문서: [SDD.md](../../../sdd/knowledge-tool-optimization-extension/inference-api-surface-execution-lifecycle-refactor/SDD.md) +- SDD 사유: 세 외부 API 계약과 stream lifecycle, cross-repo benchmark baseline을 함께 보존해야 하는 경계 리팩터링이다. +- SDD 상태: 승인됨 +- SDD 잠금: 해제 +- SDD 사용자 리뷰: 없음 +- 잠금 해제 조건: 아래 체크리스트 + - [x] SDD 잠금이 해제되어 있다. + - [x] SDD 사용자 리뷰가 없거나 승인/해결되었다. + - [x] Acceptance Scenario가 Milestone 기능 Task와 연결되어 있다. + - [x] Evidence Map이 완료 시 `complete.log`의 `milestone-task` id별 집계와 최종 검증 evidence로 검증 가능하게 연결되어 있다. + - [ ] workspace lock `iop:inference-api-surface-execution-lifecycle-refactor`의 모든 의존 상태가 `enable`이다. + - [ ] 구현 plan 시작 시 `iop-s0` 벤치마크 이후 변경을 제품 code/spec/contract와 benchmark-only harness/data로 분류하고, 제품 동작에 필요한 변경만 현재 `dev`에 반영됐거나 명시적으로 제외됐는지 확인한 뒤 baseline을 고정한다. +- 결정 필요: 없음 + +## 범위 + +- 리팩터링 시작 전 `iop-s0`의 `[bench-02]` 결과, 대상 revision과 그 시점의 API·provider 동작을 baseline으로 고정하고, 제품 변경과 benchmark-only 변경을 분리한 외부 계약별 characterization matrix를 만든다. +- OpenAI Chat/Responses, Anthropic Messages, Gemini `streamGenerateContent`가 credential header 추출, request decode·validation, wire error·response/SSE projection을 각 surface에서 계속 소유하되 기존 공통 principal/projection resolver는 중복 구현하지 않도록 책임 지도를 고정한다. +- provider admission·dispatch·attempt attribution·Node cancellation은 기존 `runService`, `SubmitProviderPool`, `SubmitProviderTunnel`을 source of truth로 유지한다. 새 all-surface service interface는 기존 capability의 구체적 결손이 characterization으로 확인된 경우에만 허용한다. +- surface validation 이후 protocol별 실행 준비, provider execution, pre-commit/commit, endpoint terminal과 usage finalization의 단계별 소유자를 명시한다. Stream Evidence Gate와 single-request coordinator의 기존 terminal state machine은 대체하지 않는다. +- Gemini ingress가 내부 `http.Request`/`ResponseWriter`를 합성해 `handleChatCompletions`로 재진입하지 않고, 검증된 Gemini→Chat 변환 결과로 operation-specific 실행 함수와 기존 service capability를 직접 사용하도록 변경한다. +- raw passthrough의 unknown/provider-native field와 translated Gemini `extra_body.google.thinking_config`가 model rewrite 이후에도 손실되지 않도록 보존 경계를 검증한다. +- 이미 올바르게 분리된 handler·codec은 이동하지 않고 cross-surface handler 호출이나 중복 lifecycle 소유권이 확인된 경로만 단계적으로 수정한다. + +## 기능 + +### Epic: [architecture-baseline] 동작 baseline과 책임 경계 고정 + +리팩터링이 공통 API 스키마 설계가 아니라 외부 동작 보존과 내부 책임 분리임을 먼저 고정한다. + +- [ ] [baseline-freeze] `iop-s0` `[bench-02]`가 `[검토중]` 또는 `[완료]`로 진입한 시점의 호출 matrix와 revision을 기록하고 제품 code/spec/contract delta와 benchmark-only harness/data delta를 분리한다. 현재 `dev`에 반영할 제품 delta, 명시적 제외 근거, direct·single-request terminal과 provider-native option characterization을 baseline으로 고정한다. 검증: benchmark report/evidence 포인터, 양쪽 commit과 대상 workspace lock `true`를 확인한다. +- [ ] [boundary-map] OpenAI Chat/Responses, Anthropic Messages, Gemini ingress별 credential 추출·decode·validation·execution preparation·provider execution·wire projection·terminal 소유권과 허용 의존 방향을 확정한다. 공통 principal resolver, 기존 service와 endpoint runtime의 재사용 지점을 함께 표시한다. + +### Epic: [execution-boundary] Surface와 실행 lifecycle 분리 + +API별 wire 형식과 terminal state machine은 합치지 않고, 기존 provider service 경계를 재사용하며 cross-surface handler 결합만 제거한다. + +- [ ] [execution-entrypoint] 기존 `runService`/`SubmitProviderPool`을 재사용하는 operation-specific ingress 실행 함수를 추출해 Gemini의 Chat handler 내부 HTTP 재진입을 제거한다. 새 service-wide port는 기존 capability 결손 evidence가 있을 때만 추가한다. 검증: production Gemini 경로에서 `handleChatCompletions` 재호출이 없고 direct/preset dispatch 결과가 baseline과 같다. +- [ ] [surface-adapters] OpenAI Chat/Responses, Anthropic Messages, Gemini의 request codec, validation, error envelope, JSON/SSE projector가 surface별 계약 타입과 정책을 독립적으로 소유하도록 cross-surface handler 의존을 제거한다. 이미 분리된 경로의 package/file 재배치는 요구하지 않는다. +- [ ] [lifecycle-owner] provider admission·attempt·cancel은 service, pre-commit·commit·endpoint terminal은 기존 endpoint runtime/Stream Evidence Gate/single-request coordinator가 소유하도록 단계별 single-owner 규칙을 고정한다. 검증: 정상·provider 오류·pre/post-commit 오류·timeout·caller cancel matrix에서 terminal/usage 중복이 없다. +- [ ] [native-option-preservation] raw passthrough unknown field, Anthropic native body, Gemini translated `extra_body`, model alias rewrite가 기존 provider 실행 경계를 통과해도 보존되도록 lossless 전달과 fail-closed 검증을 적용한다. + +### Epic: [behavior-regression] 계약 회귀와 문서 정합성 검증 + +구조 변화가 caller-visible behavior나 benchmark 조건을 바꾸지 않았음을 증명한다. + +- [ ] [contract-regression] OpenAI Chat/Responses, Anthropic Messages, Gemini direct·single-request의 stream/non-stream 해당 조합과 auth/error/tool/reasoning/usage/terminal deterministic characterization test를 통과시킨다. 검증: 관련 Edge package test와 기존 benchmark evidence 비교가 baseline 대비 의도하지 않은 차이 없이 통과한다. 외부 provider live smoke는 별도 환경·비용 승인이 있을 때만 추가하며 완료 필수 조건이 아니다. +- [ ] [spec-sync] 책임 경계와 실제 source path가 확정되면 외부 계약은 동작 변경 없이 source pointer만 필요한 범위에서 갱신하고, `agent-spec`을 최종 구현과 동기화한다. 기존 계약과 다른 제품 동작이 발견되면 이 리팩터링에서 암묵 수정하지 않고 별도 변경 후보로 분리한다. + +## 완료 리뷰 + +- 상태: 없음 +- 요청일: 없음 +- 완료 근거: Milestone과 승인된 SDD를 만들었고 실제 리팩터링 및 회귀 evidence는 아직 없다. +- 검토 항목: 없음 +- 리뷰 코멘트: 없음 + +## 범위 제외 + +- OpenAI, Anthropic, Gemini 요청/응답을 하나의 공통 DTO나 최저공통분모 API로 통합하는 작업 +- 외부 endpoint, field, error envelope, SSE event 순서, auth 방식 또는 provider selection 의미 변경 +- 신규 provider·protocol 추가, provider 품질/성능 tuning, benchmark runner·fixture·scoring 변경 +- Control Plane projection, Edge-Node wire, config/proto schema의 기능 변경 +- Stream Evidence Gate 의미 필터, single-request stage 정책, retry/fallback 정책의 신규 기능 추가 +- 기존 `runService`/`SubmitProviderPool`과 겹치는 범용 all-surface executor 또는 universal terminal DTO 추가 +- 별도 승인 없는 외부 provider live smoke나 전체/부분 benchmark 재실행을 완료 필수 조건으로 두는 작업 +- 전체 `apps/edge/internal/openai` package를 한 번에 물리적으로 분할하거나 파일명 정리만을 목적으로 하는 대규모 이동 + +## 작업 컨텍스트 + +- 관련 경로: `apps/edge/internal/openai/`, `apps/edge/internal/service/`, `agent-contract/outer/`, `agent-spec/input/openai-compatible-surface.md` +- 표준선: 외부 API는 surface별 anti-corruption adapter로 유지하고, 기존 service의 provider 실행 capability를 재사용한다. 공통화는 검증된 중복과 cross-surface handler 의존에만 적용하며 endpoint-native commit/terminal state machine을 중앙화하지 않는다. +- 실행 순서와 차단 관계: [전역 마일스톤 실행 순서](../../../priority-queue.md) +- 관련 Milestone: [[bench-02] IOP 원샷 Agent 모델 비교 벤치마크](iop-one-shot-agent-model-comparison.md) +- 확인 필요: 구현 시작 시 `iop-s0` 벤치마크 완료 revision과 현재 `dev` drift를 제품 변경/benchmark-only 변경으로 분류하고 제품 baseline의 반영 또는 제외 근거를 남긴다. diff --git a/agent-roadmap/priority-queue.md b/agent-roadmap/priority-queue.md index 7dc29495..1361250d 100644 --- a/agent-roadmap/priority-queue.md +++ b/agent-roadmap/priority-queue.md @@ -27,6 +27,11 @@ cloud-first route evidence가 품질·규모 gate를 통과하면 RAG local router를 shadow/canary로 검증해 운영 기본 경로로 점진 전환한다. - 선행 차단: `[observe-03]`, `[provider-02]` +### surface + +1. [[surface-01] Inference API Surface와 실행 Lifecycle 책임 경계 리팩터링](phase/knowledge-tool-optimization-extension/milestones/inference-api-surface-execution-lifecycle-refactor.md) + OpenAI, Anthropic, Gemini wire 계약은 분리하고 기존 provider service 경계를 재사용하면서 cross-surface handler 재진입과 단계별 lifecycle 소유권을 정리한다. 구현 시작 조건은 workspace lock과 Milestone의 제품-delta 정합성 gate에서 관리한다. + ### output 1. [[output-01] OpenAI-compatible 출력 검증 필터](phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md) diff --git a/agent-roadmap/sdd/knowledge-tool-optimization-extension/inference-api-surface-execution-lifecycle-refactor/SDD.md b/agent-roadmap/sdd/knowledge-tool-optimization-extension/inference-api-surface-execution-lifecycle-refactor/SDD.md new file mode 100644 index 00000000..5d19de34 --- /dev/null +++ b/agent-roadmap/sdd/knowledge-tool-optimization-extension/inference-api-surface-execution-lifecycle-refactor/SDD.md @@ -0,0 +1,136 @@ +# SDD: [surface-01] Inference API Surface와 실행 Lifecycle 책임 경계 리팩터링 + +## 위치 + +- Milestone: [Milestone 문서](../../../phase/knowledge-tool-optimization-extension/milestones/inference-api-surface-execution-lifecycle-refactor.md) +- Phase: [PHASE.md](../../../phase/knowledge-tool-optimization-extension/PHASE.md) + +## 상태 + +[승인됨] + +## SDD 잠금 + +- 상태: 해제 +- 사용자 리뷰: 없음 +- 잠금 항목: + - [x] [D01] OpenAI, Anthropic, Gemini wire contract는 공통 DTO로 합치지 않고 각 surface adapter에 남긴다. + - [x] [D02] provider admission·dispatch·attempt·cancel은 기존 `runService`/`SubmitProviderPool` 경계를 재사용하고, protocol별 실행 준비와 endpoint commit·terminal은 각 기존 runtime이 소유한다. 하나의 universal lifecycle owner나 terminal DTO를 만들지 않는다. + - [x] [D03] 리팩터링은 caller-visible behavior, provider selection, native option passthrough와 benchmark 조건을 바꾸지 않는다. + - [x] [D04] `iop-s0` `[bench-02]`가 검토중 또는 완료 상태로 진입하고 workspace lock이 해제된 뒤 구현 plan을 시작한다. + - [x] [D05] 구현 plan 시작 시 benchmark 이후 drift를 제품 code/spec/contract와 benchmark-only harness/data로 분류한다. 제품 delta는 현재 `dev` 반영 여부 또는 제외 근거를 고정하고, benchmark-only delta는 제품 baseline에 섞지 않는다. + - [x] [D06] 완료 evidence는 deterministic characterization과 기존 benchmark artifact 비교를 기본으로 한다. 외부 provider live smoke나 benchmark 재실행은 별도 환경·비용 승인이 있을 때만 추가한다. + +## 문제 / 비목표 + +- 문제: 현재 Edge는 `apps/edge/internal/service`의 `SubmitProviderPool`/`SubmitProviderTunnel`로 provider admission과 dispatch를 이미 공통화했지만, ingress orchestration과 endpoint lifecycle 책임은 `apps/edge/internal/openai` 안에서 경로별로 다르게 조립된다. 특히 Gemini ingress는 Chat request와 내부 HTTP 요청/response writer를 합성해 `handleChatCompletions`에 재진입하므로 Chat handler의 validation·오류·commit 방식에 간접 결합된다. Anthropic native/bridge, OpenAI passthrough/normalized, single-request는 서로 다른 terminal contract를 가지므로 기존 service 경계를 넘어 request/terminal schema까지 공통화하면 회귀 위험이 크다. +- 비목표: + - 서로 다른 외부 API field와 event를 하나의 범용 request/response schema로 축소 + - 신규 기능, provider 최적화, routing/retry/filter 정책 변경 + - Control Plane, Edge-Node wire, config/proto 계약 변경 + - benchmark pipeline 또는 비교 결과 수정 + - 기존 `SubmitProviderPool`과 겹치는 새 all-surface executor 또는 모든 endpoint terminal을 소유하는 중앙 state machine 도입 + +## Source of Truth + +| 영역 | 기준 | 메모 | +|------|------|------| +| Roadmap | [Milestone 문서](../../../phase/knowledge-tool-optimization-extension/milestones/inference-api-surface-execution-lifecycle-refactor.md) | 범위, Task와 구현 잠금 원장 | +| API Contracts | [OpenAI-Compatible API](../../../../agent-contract/outer/openai-compatible-api.md), [Anthropic-Compatible Messages API](../../../../agent-contract/outer/anthropic-compatible-api.md), [Gemini-Compatible API](../../../../agent-contract/outer/gemini-compatible-api.md) | caller-visible 동작 baseline | +| Current Spec | [OpenAI-Compatible 입력 표면](../../../../agent-spec/input/openai-compatible-surface.md), [Stream Evidence Gate](../../../../agent-spec/runtime/stream-evidence-gate.md) | 현재 구현 책임과 lifecycle 설명 | +| Surface/Ingress Code | `apps/edge/internal/openai/` | surface codec, validation, projector, Stream Evidence Gate와 operation-specific ingress orchestration 구현 | +| Provider Execution | `apps/edge/internal/service/`의 `SubmitProviderPool`, `SubmitProviderTunnel`, `SubmitRun` | provider admission·dispatch·attempt·Node cancellation의 기존 source of truth | +| Benchmark Baseline | [iop-s0 `[bench-02]` Milestone](../../../../../iop-s0/agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/iop-one-shot-agent-model-comparison.md) | 구현 시작을 잠그는 비교 work owner와 최종 evidence | +| User Decision | 없음 | D01-D06은 기존 code/contract와 사용자 요청 범위에서 도출한 기술 기준이며 신규 제품 결정은 없다. | + +## State Machine + +| 상태 | 진입 조건 | 다음 상태 | 근거 | +|------|-----------|-----------|------| +| `surface_received` | endpoint가 request와 surface auth header를 수신 | `surface_validated`, `terminal_error`, `cancelled` | surface adapter가 credential form과 body를 소유 | +| `surface_validated` | surface validation, 공통 principal resolver와 route snapshot 완료 | `admitted`, `terminal_error`, `cancelled` | surface adapter + shared auth/projection resolver | +| `admitted` | immutable protocol-specific execution input이 준비됨 | `attempting`, `terminal_error`, `cancelled` | ingress orchestration이 기존 service request를 구성 | +| `attempting` | 기존 service가 provider candidate와 operation을 확정하고 attempt 시작 | `pre_commit`, `terminal_error`, `cancelled` | `SubmitProviderPool`/`SubmitProviderTunnel` | +| `pre_commit` | endpoint runtime이 아직 caller-visible byte를 commit하지 않음 | `committed`, `terminal_error`, `cancelled` | Stream Evidence Gate, endpoint projector 또는 single-request coordinator | +| `committed` | endpoint-native status/header/opening event가 최초 commit됨 | `terminal_success`, `terminal_error`, `cancelled` | surface별 wire writer/terminal state machine | +| `terminal_success` | endpoint-native success terminal과 해당 경로의 usage finalization 완료 | 종료 | endpoint runtime의 exactly-once terminal evidence | +| `terminal_error` | pre-commit envelope 또는 post-commit endpoint-native error terminal 확정 | 종료 | surface error projection과 기존 terminal winner | +| `cancelled` | caller context 취소가 해당 경로의 terminal winner가 됨 | 종료 | endpoint가 service/Node cancel을 전파하고 이후 wire write 금지 | + +State invariant: + +- 한 public request의 각 lifecycle 전이에는 owner가 하나만 있고 endpoint terminal winner도 하나만 존재한다. 모든 전이를 하나의 새 중앙 owner로 이동하지 않는다. +- surface adapter는 credential form, decode/validation/error/SSE 형식을 소유한다. 공통 auth/projection resolver와 service provider selection/attempt identity/Node cancellation은 중복 구현하지 않는다. +- operation-specific ingress 실행 함수는 OpenAI/Anthropic/Gemini public DTO나 `http.ResponseWriter`를 `apps/edge/internal/service`로 넘기지 않는다. 기존 service request/result를 사용하고 endpoint projector hook도 service에 주입하지 않는다. +- Stream Evidence Gate와 single-request coordinator는 기존 pre-commit/terminal state machine을 계속 소유하며 새 universal terminal DTO로 치환하지 않는다. +- raw passthrough body는 route에 필요한 bounded field만 읽고 unknown/provider-native field를 제거하지 않는다. translated body도 `extra_body`를 포함한 생성 결과를 손실 없이 전달한다. +- commit 전후의 오류 projection은 surface 계약을 따르며, 다른 surface의 handler나 HTTP round-trip을 통해 얻지 않는다. + +## Interface Contract + +- 계약 원문: [OpenAI-Compatible API](../../../../agent-contract/outer/openai-compatible-api.md), [Anthropic-Compatible Messages API](../../../../agent-contract/outer/anthropic-compatible-api.md), [Gemini-Compatible API](../../../../agent-contract/outer/gemini-compatible-api.md) +- 입력: + - authenticated request snapshot: principal, public route/model, immutable projection/binding revision과 cancellation context다. + - protocol operation: Chat Completions, Responses, Anthropic Messages 또는 surface가 명시적으로 변환한 upstream operation이다. + - response mode: buffered, normalized stream 또는 raw tunnel stream의 실행 의미다. + - execution payload: raw passthrough body 또는 surface가 검증해 만든 provider request body/normalized input이다. +- 출력: + - 기존 service result: selected execution path, caller에 노출하지 않는 attempt attribution과 run/tunnel handle이다. + - endpoint runtime input: 기존 RunEvent/tunnel frame/single-request progress 중 해당 operation이 이미 사용하는 bounded 결과다. + - endpoint terminal: surface projector나 기존 coordinator가 자기 error/terminal contract로 확정하며 공통 service 결과 타입으로 만들지 않는다. +- 금지: + - Gemini가 내부 `http.Request`/`ResponseWriter`를 만들어 Chat handler를 호출한다. + - 공통 service가 Gemini/Anthropic/OpenAI public response envelope나 SSE event 이름을 생성한다. + - endpoint projector callback이나 `http.ResponseWriter`를 provider service interface에 추가한다. + - 기존 `SubmitProviderPool`과 같은 capability를 포장만 바꿔 중복 구현한다. + - 공통 DTO를 만들기 위해 surface-specific field, unknown field 또는 provider-native option을 버린다. + - 리팩터링 중 provider fallback, retry budget, output filter, single-request stage semantics를 변경한다. + +## Acceptance Scenarios + +| ID | Milestone Task | Given | When | Then | +|----|----------------|-------|------|------| +| S01 | `baseline-freeze` | `iop-s0` benchmark가 검토중 또는 완료이고 final report/evidence가 고정됨 | 구현 plan 전 consistency check를 수행 | target/current revision, 제품 delta 반영·제외 근거, benchmark-only delta 분리, 대표 terminal/native-option baseline과 workspace lock `true`가 연결된다. | +| S02 | `boundary-map` | 세 외부 API와 raw/normalized/single-request 경로가 존재 | 책임/의존 지도를 검토 | surface, shared auth resolver, 기존 service, endpoint runtime의 단계별 소유권이 겹치지 않고 API/terminal DTO 공통화가 비범위로 표시된다. | +| S03 | `execution-entrypoint` | Gemini direct와 virtual preset 요청 | Gemini ingress를 실행 | Chat handler 내부 HTTP 재진입이나 중복 service port 없이 기존 provider/preset execution capability를 호출하고 Gemini contract 결과를 반환한다. | +| S04 | `surface-adapters` | 각 surface의 정상·validation·auth·provider 오류 입력 | endpoint별 adapter/projector를 실행 | 외부 status, error envelope, model echo와 SSE event 순서가 baseline과 동일하다. | +| S05 | `lifecycle-owner` | 정상, provider 오류, timeout, pre/post-commit failure, caller cancel | stream/non-stream lifecycle을 종료 | 단계별 owner가 하나이고 public terminal과 request usage가 exactly once이며 취소 뒤 추가 wire write가 없다. | +| S06 | `native-option-preservation` | unknown OpenAI field, Anthropic native extension, Gemini thinking `extra_body`와 model alias | raw 또는 translated dispatch를 수행 | selected provider body에 허용 field가 보존되고 unsupported 의미는 기존 provider/surface 오류로 귀결된다. | +| S07 | `contract-regression` | OpenAI Chat/Responses, Anthropic Messages, Gemini direct/single-request characterization matrix와 기존 benchmark artifact | deterministic package test와 artifact 비교를 실행 | caller-visible behavior와 benchmark admission 조건에 의도하지 않은 차이가 없고 별도 승인 없는 live provider 호출은 발생하지 않는다. | +| S08 | `spec-sync` | 리팩터링 구현과 회귀 검증 완료 | contract source pointer와 implementation spec을 점검 | 외부 계약 의미는 유지되고 source path·책임 경계·현재 스펙이 실제 코드와 일치하며 기존 계약과 다른 제품 동작은 별도 후보로 분리된다. | + +## Evidence Map + +| Scenario | Required Evidence | `agent-task` 연결 | 완료 Evidence 기대 | +|----------|-------------------|------------------|---------------------------| +| S01 | benchmark target/current revision, 제품/benchmark-only delta 분류, lock checker `true`, baseline matrix와 digest | `agent-task/m-inference-api-surface-execution-lifecycle-refactor/baseline-freeze/` | `baseline-freeze` task id별 dependency/drift/baseline evidence | +| S02 | surface/common responsibility map와 forbidden dependency check | `agent-task/m-inference-api-surface-execution-lifecycle-refactor/boundary-map/` | `boundary-map` task id별 architecture evidence | +| S03 | Gemini production call graph/search, 기존 service 호출과 direct/preset regression | `agent-task/m-inference-api-surface-execution-lifecycle-refactor/execution-entrypoint/` | `execution-entrypoint` task id별 no-handler-reentry/no-duplicate-port evidence | +| S04 | endpoint codec/projector ownership tests와 dependency review | `agent-task/m-inference-api-surface-execution-lifecycle-refactor/surface-adapters/` | `surface-adapters` task id별 wire-contract evidence | +| S05 | terminal/cancel/usage concurrency matrix | `agent-task/m-inference-api-surface-execution-lifecycle-refactor/lifecycle-owner/` | `lifecycle-owner` task id별 exactly-once evidence | +| S06 | raw/translated request capture와 native field preservation tests | `agent-task/m-inference-api-surface-execution-lifecycle-refactor/native-option-preservation/` | `native-option-preservation` task id별 lossless evidence | +| S07 | deterministic Edge package test 결과와 기존 benchmark artifact comparison | `agent-task/m-inference-api-surface-execution-lifecycle-refactor/contract-regression/` | `contract-regression` task id별 baseline comparison 및 no-unapproved-live-call evidence | +| S08 | agent-spec/contract drift check와 문서 diff | `agent-task/m-inference-api-surface-execution-lifecycle-refactor/spec-sync/` | `spec-sync` task id별 final source-pointer/spec evidence | + +## Cross-repo Dependencies + +- 대상: [iop-s0 `[bench-02]` IOP 원샷 Agent 모델 비교 벤치마크](../../../../../iop-s0/agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/iop-one-shot-agent-model-comparison.md) +- `.agent-roadmap-sync/locks.yaml` entry: `iop:inference-api-surface-execution-lifecycle-refactor` +- workspace lock 해제 기준: 대상 Milestone이 `[검토중]` 또는 `[완료]`로 전환되어 benchmark matrix/report와 baseline evidence가 더 이상 실행 중 변경되지 않고, lock status가 `enable`로 동기화된다. +- 로컬 구현 잠금 추가 기준: 대상/current revision 사이의 변경을 제품 delta와 benchmark-only delta로 분류하고, 제품 delta가 현재 `dev`에 반영됐거나 명시적으로 제외됐다는 근거를 `baseline-freeze`에 남긴다. + +## Drift Check + +- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다. +- [x] Evidence Map이 code-review/complete.log에서 검증 가능하다. +- [x] agent-contract를 쓰며 SDD에 계약 원문을 복제하지 않았다. +- [x] 사용자 리뷰가 필요한 항목은 없고 D01-D06으로 경계를 확정했다. + +## 사용자 리뷰 이력 + +- 2026-08-13: API 방식 자체가 달라 범용 request/terminal interface가 어렵다는 사용자 문제 제기를 바탕으로, benchmark 완료 뒤 구현하되 현시점에는 surface 계약 분리와 기존 provider service 경계 재사용을 기준으로 Milestone을 먼저 작성했다. + +## 작업 컨텍스트 + +- 표준선: surface-specific anti-corruption adapter + existing provider service capability + transition별 single owner. Operation-specific ingress 함수는 필요한 경로에만 추출하고 물리 package 이동은 의존 방향을 검증하는 최소 범위로 제한한다. +- 후속 SDD: 없음 diff --git a/agent-spec/input/openai-compatible-surface.md b/agent-spec/input/openai-compatible-surface.md index 540e9d7b..fc249b8e 100644 --- a/agent-spec/input/openai-compatible-surface.md +++ b/agent-spec/input/openai-compatible-surface.md @@ -15,6 +15,12 @@ source_evidence: - type: code path: apps/edge/internal/openai/chat_handler.go notes: Chat Completions request validation, route dispatch, tool/reasoning 정책 + - type: code + path: apps/edge/internal/openai/gemini_handler.go + notes: Gemini-native request validation과 official agy tool continuation의 Chat bridge 변환 + - type: test + path: apps/edge/internal/openai/gemini_handler_test.go + notes: official agy model-role functionResponse와 thought signature/tool-call 매칭 회귀 검증 - type: code path: apps/edge/internal/openai/route_resolution.go notes: model catalog attribution policy와 direct provider id 해석 @@ -199,6 +205,7 @@ Edge가 OpenAI-compatible HTTP 요청을 받아 내부 `adapter + target` 실행 | Anthropic caller auth | Anthropic ingress accepts `Authorization: Bearer ` or `X-Api-Key: `. If both are present they must match; shared principal-token and legacy bearer fallback apply after this validation. | | Anthropic provider-pool dispatch | Messages and count-tokens require a provider-pool model route. Native Messages requires `messages` capability and operation, while the Chat bridge requires `chat` capability and `chat_completions` operation; streaming and tools add their own capability checks. | | provider-normalized Messages bridge | Supported Messages compatibility headers are consumed at the bridge. Edge derives caller-neutral tool/effort/token-budget/stream requirements, selects a profile operation that preserves them, and maps effort to exact or nearest lower provider grade. Chat-compatible providers may therefore use Chat or Responses without caller-name branches. JSON schema and tool shapes are converted for the selected wire; Gemini tool thought signatures still round-trip through opaque tool-use ids. | +| Gemini-native agy tool continuation | Official agy 1.1.12의 Gemini-native 요청을 Chat 실행 경로로 변환한다. tool 실행 뒤 독립 `role:model` content로 전달되는 `functionResponse`는 앞선 function call과 매칭해 Chat `tool` message로 변환하며, assistant content와 response가 한 model content에 섞인 모호한 요청은 거부한다. | | bounded ingress and StreamGate ownership | Chat/Responses bodies are limited to 16 MiB before the first read. Every supported path delegates response-start staging, applicable filter arbitration, bounded liveness recovery, and the single terminal to `runtime/stream-evidence-gate`; `enabled` controls configured semantic policy only. | | typed stall terminal | Supported Chat/Responses normalized and tunnel attempts always translate only Edge-confirmed `response_stalled` terminals into a raw-free liveness recovery candidate; post-commit, cancelled, tool-bearing, missing-snapshot, exhausted, unsupported, unconfirmed, generic, and no-owner paths stay terminal. | | liveness operational evidence | Each private liveness cycle emits one closed eligibility counter and at most one closed final-result counter. Constructor-owned generic logs use a safe projection without identifiers or payloads, while application-installed observation sinks retain the original immutable events. | @@ -336,6 +343,7 @@ sequenceDiagram ## 변경 기록 +- 2026-08-13: Added official agy 1.1.12 model-role `functionResponse` continuation support while retaining fail-closed rejection for mixed assistant/tool-response content (`apps/edge/internal/openai/gemini_handler.go`). - 2026-08-12: Admitted Claude Code's `advisor-tool-2026-03-01` beta as a consumed compatibility marker for both direct and marked-preset Messages ingress. It grants no internal capability and is not forwarded through the Chat bridge (`apps/edge/internal/openai/anthropic_types.go`). - 2026-08-12: Replaced free-form PlanMD generation with a stage-owned strict `goal`/`steps`/`verification` JSON response and deterministic Edge rendering of the frozen Plan template. Internal artifact customization and all caller-visible Messages schemas remain unchanged (`apps/edge/internal/openai/single_request_plan_stage.go`, `packages/go/singlerequesttemplate/template.go`). - 2026-08-12: Made the private Plan response provider-independent by representing steps and verification as bounded one-line string arrays; Edge now owns Markdown bullet and newline formatting (`apps/edge/internal/openai/single_request_plan_stage.go`, `packages/go/singlerequesttemplate/template.go`). diff --git a/agent-task/archive/2026/08/remote_bench_workspace/code_review_cloud_G02_0.log b/agent-task/archive/2026/08/remote_bench_workspace/code_review_cloud_G02_0.log new file mode 100644 index 00000000..ed7e0465 --- /dev/null +++ b/agent-task/archive/2026/08/remote_bench_workspace/code_review_cloud_G02_0.log @@ -0,0 +1,153 @@ + + +# Code Review Reference - 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 `Implementation Checklist`; the final checklist item is mandatory before saving. +> Fill implementation-owned sections, then stop with active files in place and report ready for review. +> Execute the plan's selected scope and write boundary as written. Do not expand into benchmark execution, credential work, deployment, or roadmap changes. +> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields. +> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, classify the next state, archive logs, or write `complete.log`. +> Finalization (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only. +> Follow the ownership table at the bottom of this file. + +## Overview + +date=2026-08-13 +task=remote_bench_workspace, plan=0, tag=TEST + +## For the Review Agent + +> **[REVIEW AGENT ONLY]** Verify only that the remote directory is empty and writable, the three callers resolve, and excluded systems were untouched. Finalization and archive operations are review-agent-only. + +Run the applicable verification directly and record fresh output in `Verification Results`; implementation-owned output is handoff evidence, not a substitute for reviewer verification. If verification exposes a defect, collect the evidence, determine the exact root cause, and select one concrete fix before creating a follow-up plan. + +Review completion means: + +1. Append verdict and `review_rework_count` / `evidence_integrity_failure`. +2. Archive `CODE_REVIEW-cloud-G02.md` and `PLAN-local-G02.md` using their next log numbers. +3. If PASS, write `complete.log` and move this task directory to `agent-task/archive/YYYY/MM/remote_bench_workspace/`. +4. If WARN/FAIL, write the next filesystem state required by the code-review skill. + +--- + +## Implementation Item Completion + +| Item | Status | +|------|---------| +| TEST-1 원격 작업 디렉터리 준비 | [x] | + +## Implementation Checklist + +- [x] Prepare `/Users/toki/agent-work/iop-bench-workspace` on the remote dev host and verify it is an empty writable directory with `claude`, `agy`, and `codex` executable from the login shell. +- [x] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output. + +## Review-Only Checklist + +> **[REVIEW AGENT ONLY]** Implementing agents must not modify this checklist. + +- [x] Append one verdict and verified routing signals. +- [x] Verify that verdict, dimension assessment, and finding classifications match. +- [x] Run the required remote verification and record fresh output; implementation evidence alone is not sufficient. +- [ ] For every Required/Suggested finding, record evidence, exact root cause, one selected fix, affected files, and acceptance commands before creating a follow-up plan. +- [x] Verify no benchmark call, credential mutation, deployment, or roadmap change was made. +- [x] Archive `CODE_REVIEW-cloud-G02.md` and `PLAN-local-G02.md` using their next log numbers. +- [x] Verify the Agent-Ops managed `.gitignore` block before finalization. +- [x] On PASS, write `complete.log`, archive the task directory, and leave no active Markdown files. +- [ ] On WARN/FAIL, write the next filesystem state required by the code-review skill and do not write `complete.log`. + +## Deviations from Plan + +None. + +## Key Design Decisions + +None. + +## Reviewer Checkpoints + +- Scope is limited to `/Users/toki/agent-work/iop-bench-workspace` and this review evidence file. +- Existing `/Users/toki/agent-work/iop-dev`, Edge/Node runtime, credentials, caller versions, roadmap, and benchmark evidence remain unchanged. + +## Verification Results + +### TEST-1 / Final verification + +```bash +ssh -o BatchMode=yes -o ConnectTimeout=8 toki@toki-labs.com '/bin/zsh -lc '\''set -eu; bench_root=/Users/toki/agent-work/iop-bench-workspace; test -d "$bench_root"; test -w "$bench_root"; test -z "$(find "$bench_root" -mindepth 1 -maxdepth 1 -print -quit)"; claude --version; agy --version; codex --version; printf "remote_bench_workspace=READY path=%s\n" "$bench_root"'\''' +``` + +```text +2.1.177 (Claude Code) +1.0.8 +codex-cli 0.146.0 +remote_bench_workspace=READY path=/Users/toki/agent-work/iop-bench-workspace +``` + +Exit code: 0 + +- `/Users/toki/agent-work/iop-bench-workspace` exists, is writable, and is empty. +- `claude` resolves to 2.1.177 (Claude Code). +- `agy` resolves to 1.0.8. +- `codex` resolves to codex-cli 0.146.0. +- No changes to existing `/Users/toki/agent-work/iop-dev`, Edge/Node runtime, credentials, roadmap, or benchmark evidence. + +--- + +> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?** +> If anything is blank, fill it before saving. Leave review-agent-only sections unchanged and keep active files in place. + +### Reviewer Fresh Verification (2026-08-13) + +```bash +printf '%s\n' 'set -eu' 'bench_root=/Users/toki/agent-work/iop-bench-workspace' 'test -d "$bench_root"' 'test -w "$bench_root"' 'test -z "$(find "$bench_root" -mindepth 1 -maxdepth 1 -print -quit)"' 'command -v claude' 'claude --version' 'command -v agy' 'agy --version' 'command -v codex' 'codex --version' 'printf "remote_bench_workspace=READY path=%s\\n" "$bench_root"' | ssh -o BatchMode=yes -o ConnectTimeout=8 toki@toki-labs.com '/bin/zsh -l -s' +``` + +```text +/opt/homebrew/bin/claude +2.1.177 (Claude Code) +/Users/toki/.local/bin/agy +1.0.8 +/opt/homebrew/bin/codex +codex-cli 0.146.0 +remote_bench_workspace=READY path=/Users/toki/agent-work/iop-bench-workspace +``` + +Exit code: 0 + +## Code Review Result + +**Overall Verdict:** PASS + +**Dimension Assessment:** +- Correctness: Pass — Remote target is an existing, writable, empty directory. +- Completeness: Pass — TEST-1 and both implementation checklist items are complete. +- Test coverage: Pass — Product-code change is excluded; the planned remote state check was rerun by the reviewer. +- API contract: Pass — No API or protocol surface changed. +- Code quality: Pass — No source change was introduced beyond review evidence. +- Implementation deviation: Pass — No benchmark call, credential mutation, deployment, or roadmap update is present in the task scope. +- Verification trust: Pass — Reviewer SSH verification independently reproduced the claimed readiness and caller resolution. + +**Findings:** None. + +**Routing Signals:** +- review_rework_count=0 +- evidence_integrity_failure=false + +**Next Step:** PASS finalization: archive the pair, write `complete.log`, and move the task directory to the monthly task archive. + +--- + +## Section Ownership + +| Section | Owner | Note | +|---|---|---| +| Header comment, Overview, Review Agent Instructions | Fixed at stub creation | Implementing agent must not modify or execute these | +| Implementation Item Completion item names | Fixed at stub creation | Implementing agent checks `[ ]` to `[x]` only | +| Implementation Checklist text/order | Fixed at stub creation | Implementing agent checks `[ ]` to `[x]` only | +| Review-Only Checklist | Review agent only | Implementing agent must not modify or check this section | +| Deviations from Plan, Key Design Decisions | Implementing agent | Replace placeholders with actual content | +| Reviewer Checkpoints | Fixed at stub creation | Pre-filled from the plan | +| Verification Results | Implementing agent, then review agent | Implementer records initial output; reviewer reruns and may append fresh output | +| Code Review Result | Review agent appends | Not included in stub | diff --git a/agent-task/archive/2026/08/remote_bench_workspace/complete.log b/agent-task/archive/2026/08/remote_bench_workspace/complete.log new file mode 100644 index 00000000..c70c7387 --- /dev/null +++ b/agent-task/archive/2026/08/remote_bench_workspace/complete.log @@ -0,0 +1,34 @@ + + +# Complete - remote_bench_workspace + +## 완료 일시 + +2026-08-13 + +## 요약 + +원격 벤치 작업공간을 준비하고 검토자 재검증으로 PASS 완료했다. 루프 수는 1회다. + +## 루프 이력 + +| Plan | Review | Verdict | 메모 | +|------|--------|---------|------| +| `plan_local_G02_0.log` | `code_review_cloud_G02_0.log` | PASS | 빈 쓰기 가능 작업공간과 세 caller의 login-shell 해석을 검토자가 재검증했다. | + +## 구현/정리 내용 + +- 원격 `/Users/toki/agent-work/iop-bench-workspace`가 비어 있고 쓰기 가능한 상태로 준비됐다. +- `claude`, `agy`, `codex`가 원격 login shell에서 모두 해석되고 버전을 출력한다. + +## 최종 검증 + +- `printf '%s\\n' 'set -eu' 'bench_root=/Users/toki/agent-work/iop-bench-workspace' 'test -d "$bench_root"' 'test -w "$bench_root"' 'test -z "$(find "$bench_root" -mindepth 1 -maxdepth 1 -print -quit)"' 'command -v claude' 'claude --version' 'command -v agy' 'agy --version' 'command -v codex' 'codex --version' 'printf "remote_bench_workspace=READY path=%s\\n" "$bench_root"' | ssh -o BatchMode=yes -o ConnectTimeout=8 toki@toki-labs.com '/bin/zsh -l -s'` - PASS; `claude` 2.1.177, `agy` 1.0.8, `codex` 0.146.0 및 READY 출력 확인. + +## 잔여 Nit + +- 없음 + +## 후속 작업 + +- 없음 diff --git a/agent-task/archive/2026/08/remote_bench_workspace/plan_local_G02_0.log b/agent-task/archive/2026/08/remote_bench_workspace/plan_local_G02_0.log new file mode 100644 index 00000000..c9a7454c --- /dev/null +++ b/agent-task/archive/2026/08/remote_bench_workspace/plan_local_G02_0.log @@ -0,0 +1,117 @@ + + +# 원격 벤치 작업공간 준비 + +## For the Implementing Agent + +원격 작업공간 준비와 검증만 수행한다. 검증 후 `CODE_REVIEW-cloud-G02.md`의 구현자 소유 섹션에 실제 명령과 출력을 기록하고 active 파일을 그대로 둔 채 리뷰 준비 완료를 보고한다. 차단되면 시도한 명령, 실제 출력, 재개 조건만 기록한다. 사용자 질문, `USER_REVIEW.md`, archive, `complete.log`, 로드맵 수정은 수행하지 않는다. + +## Background + +현재 호스트의 벤치 작업이 다른 에이전트와 같은 작업 위치를 사용해 충돌할 수 있다. 이번 작업은 기존 벤치 마일스톤을 수행하지 않고, 원격 dev host에 충돌하지 않는 빈 작업 디렉터리를 준비해 기존 작업자에게 넘기는 운영 작업이다. + +## Analysis + +### Files Read + +- `agent-test/local/rules.md` +- `agent-test/dev/testing-smoke.md` +- `agent-test/dev/edge-smoke.md` +- `agent-test/dev/iop-benchmark-route-minimal-html-smoke.md` +- `agent-test/inventory-dev.yaml` +- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/benchmark-route-minimal-html-smoke.md` +- `agent-ops/skills/common/plan/SKILL.md` +- `agent-ops/skills/common/finalize-task-routing/SKILL.md` +- `agent-ops/skills/common/plan/templates/review-stub-template.md` + +### SDD Criteria + +Not applicable. 이 작업은 활성 마일스톤과 연결하지 않는 운영 준비 작업이다. + +### Verification Context + +- 환경: 원격 runner `toki@toki-labs.com`. +- 읽기 전용 사전 확인: SSH 접속 가능, macOS arm64, `/Users/toki/agent-work` 여유 공간 약 115 GiB, login shell에서 `claude`, `agy`, `codex` 확인. +- 제약: 기존 `/Users/toki/agent-work/iop-dev`, 실행 중인 Edge/Node, caller 버전, credential, 벤치 마일스톤 문서와 evidence를 변경하지 않는다. +- gap: 없음. HTML 호출과 인증 검증은 이 작업 범위가 아니다. +- confidence: high. 작업공간 준비 성공 여부는 디렉터리의 존재·쓰기 가능 여부로 결정할 수 있다. + +### Test Coverage Gaps + +- 제품 동작 변경이 없으므로 단위 테스트 대상이 아니다. +- 원격 디렉터리 존재·쓰기 가능 여부와 세 caller의 실행 경로만 직접 검증한다. + +### Symbol References + +None. + +### Split Judgment + +하나의 원격 디렉터리를 준비하고 인계하는 단일 운영 경계이므로 분할하지 않는다. + +### Scope Rationale + +HTML 9개 실행, token 발급·매핑, Edge/Node 배포·재시작, caller 업데이트·버전 정렬, 로드맵·벤치 evidence 갱신은 제외한다. 최소 HTML 테스트는 빈 workspace와 기존 caller만 사용하므로 repo clone도 만들지 않는다. + +### Final Routing + +- evaluation_mode: `first-pass` +- finalizer: `finalize-task-policy.sh`, mode=`pair` +- build: closures 모두 true, scores=`0/0/0/0/2`, route=`local-fit`, lane=`local`, grade=`G02`, file=`PLAN-local-G02.md` +- review: closures 모두 true, scores=`0/0/0/0/2`, route=`official-review`, lane=`cloud`, grade=`G02`, file=`CODE_REVIEW-cloud-G02.md` +- large_indivisible_context: false +- matched_loop_risk_signatures: none, count=0 +- recovery signals: review_rework_count=0, evidence_integrity_failure=false +- capability gap: none + +## Implementation Checklist + +- [ ] Prepare `/Users/toki/agent-work/iop-bench-workspace` on the remote dev host and verify it is an empty writable directory with `claude`, `agy`, and `codex` executable from the login shell. +- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output. + +### [TEST-1] 원격 작업 디렉터리 준비 + +#### Problem + +현재 벤치용 빈 workspace 요구는 `agent-test/dev/iop-benchmark-route-minimal-html-smoke.md:9`에 있지만, 현재 호스트 작업과 분리된 원격 실행 위치가 아직 준비되지 않았다. + +#### Solution + +원격 dev host에 `/Users/toki/agent-work/iop-bench-workspace` 하나를 만들고 빈 디렉터리인지, 현재 사용자에게 쓰기 가능한지 확인한다. login shell에서 세 caller의 실행 경로만 확인한 뒤 해당 경로를 인계한다. 다른 파일이나 설정은 만들지 않는다. + +#### Modified Files and Checklist + +- [ ] 원격 `/Users/toki/agent-work/iop-bench-workspace`를 생성한다. +- [ ] 디렉터리가 비어 있고 현재 사용자에게 쓰기 가능한지 확인한다. +- [ ] login shell에서 `claude`, `agy`, `codex`의 `--version`이 모두 성공하는지 확인한다. +- [ ] 실제 결과를 `agent-task/remote_bench_workspace/CODE_REVIEW-cloud-G02.md`에 기록한다. + +#### Test Strategy + +제품 코드를 변경하지 않으므로 테스트 코드는 추가하지 않는다. 아래 원격 상태 검증을 fresh 실행한다. + +#### Verification + +```bash +ssh -o BatchMode=yes -o ConnectTimeout=8 toki@toki-labs.com '/bin/zsh -lc '\''set -eu; bench_root=/Users/toki/agent-work/iop-bench-workspace; mkdir -p "$bench_root"; test -d "$bench_root"; test -w "$bench_root"; test -z "$(find "$bench_root" -mindepth 1 -maxdepth 1 -print -quit)"; claude --version; agy --version; codex --version; printf "remote_bench_workspace=READY path=%s\n" "$bench_root"'\''' +``` + +기대 결과: exit code 0, 세 caller 버전과 `remote_bench_workspace=READY` 한 줄이 출력된다. + +## Modified Files Summary + +| File | Item | +|---|---| +| `agent-task/remote_bench_workspace/CODE_REVIEW-cloud-G02.md` | TEST-1 | + +## Final Verification + +다음 명령을 fresh 실행한다. 캐시된 결과는 허용하지 않는다. + +```bash +ssh -o BatchMode=yes -o ConnectTimeout=8 toki@toki-labs.com '/bin/zsh -lc '\''set -eu; bench_root=/Users/toki/agent-work/iop-bench-workspace; test -d "$bench_root"; test -w "$bench_root"; test -z "$(find "$bench_root" -mindepth 1 -maxdepth 1 -print -quit)"; claude --version; agy --version; codex --version; printf "remote_bench_workspace=READY path=%s\n" "$bench_root"'\''' +``` + +기대 결과: exit code 0이며 기존 repo, runtime, credential, 로드맵 파일에는 변경이 없다. + +After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`. diff --git a/agent-task/archive/2026/08/remote_bench_workspace/work_log_0.log b/agent-task/archive/2026/08/remote_bench_workspace/work_log_0.log new file mode 100644 index 00000000..d729de24 --- /dev/null +++ b/agent-task/archive/2026/08/remote_bench_workspace/work_log_0.log @@ -0,0 +1,12 @@ +# Milestone Work Log + +> Dispatcher-owned execution timeline. Workers and reviewers do not edit this file. + +| seq | time | event | task | loop | role | attempt | model | result | locator | +|---:|---|---|---|---:|---|---:|---|---|---| +| 1 | 26-08-13 22:19:07 KST | START | remote_bench_workspace/PLAN-local-G02.md | 0 | worker | 0 | pi/ornith:35b high | running | /config/workspace/iop/.git/agent-task-dispatcher/runs/20260813T221907+0900__remote_bench_workspace__p0__worker__a00/locator.json | +| 2 | 26-08-13 22:20:27 KST | FINISH | remote_bench_workspace/PLAN-local-G02.md | 0 | worker | 0 | pi/ornith:35b high | succeeded:0 | /config/workspace/iop/.git/agent-task-dispatcher/runs/20260813T221907+0900__remote_bench_workspace__p0__worker__a00/locator.json | +| 3 | 26-08-13 22:20:28 KST | START | remote_bench_workspace/PLAN-local-G02.md | 0 | selfcheck | 0 | pi/ornith:35b high | running | /config/workspace/iop/.git/agent-task-dispatcher/runs/20260813T222028+0900__remote_bench_workspace__p0__selfcheck__a00/locator.json | +| 4 | 26-08-13 22:23:40 KST | FINISH | remote_bench_workspace/PLAN-local-G02.md | 0 | selfcheck | 0 | pi/ornith:35b high | succeeded:0 | /config/workspace/iop/.git/agent-task-dispatcher/runs/20260813T222028+0900__remote_bench_workspace__p0__selfcheck__a00/locator.json | +| 5 | 26-08-13 22:23:40 KST | START | remote_bench_workspace/CODE_REVIEW-cloud-G02.md | 0 | review | 0 | codex/gpt-5.6-terra high | running | /config/workspace/iop/.git/agent-task-dispatcher/runs/20260813T222340+0900__remote_bench_workspace__p0__review__a00/locator.json | +| 6 | 26-08-13 22:27:12 KST | FINISH | remote_bench_workspace/CODE_REVIEW-cloud-G02.md | 0 | review | 0 | codex/gpt-5.6-terra high | succeeded:0 | /config/workspace/iop/.git/agent-task-dispatcher/runs/20260813T222340+0900__remote_bench_workspace__p0__review__a00/locator.json | diff --git a/apps/edge/internal/openai/gemini_handler.go b/apps/edge/internal/openai/gemini_handler.go index b52fbda3..aa3c9e77 100644 --- a/apps/edge/internal/openai/gemini_handler.go +++ b/apps/edge/internal/openai/gemini_handler.go @@ -468,7 +468,7 @@ func geminiContentToChat(content geminiContent, contentIndex int, pending *gemin continue } response := part.FunctionResponse - if role != "user" || !geminiPathToken.MatchString(response.Name) { + if !geminiPathToken.MatchString(response.Name) { return nil, fmt.Errorf("functionResponse is invalid") } if !json.Valid(response.Response) { @@ -481,6 +481,12 @@ func geminiContentToChat(content geminiContent, contentIndex int, pending *gemin toolMessages = append(toolMessages, map[string]any{"role": "tool", "tool_call_id": callID, "content": string(response.Response)}) } if role == "model" { + if len(toolMessages) > 0 { + if len(texts) > 0 || len(reasoning) > 0 || len(toolCalls) > 0 { + return nil, fmt.Errorf("model functionResponse cannot be mixed with assistant content") + } + return toolMessages, nil + } message := map[string]any{"role": "assistant", "content": strings.Join(texts, "\n")} if len(reasoning) > 0 { message["reasoning_content"] = strings.Join(reasoning, "") diff --git a/apps/edge/internal/openai/gemini_handler_test.go b/apps/edge/internal/openai/gemini_handler_test.go index 66e0817c..eecf483e 100644 --- a/apps/edge/internal/openai/gemini_handler_test.go +++ b/apps/edge/internal/openai/gemini_handler_test.go @@ -185,6 +185,54 @@ func TestGeminiRequestBridgeKeepsIDLessFIFOCompatibility(t *testing.T) { } } +func TestGeminiRequestBridgeAcceptsOfficialAgyModelRoleFunctionResponse(t *testing.T) { + converted, err := prepareGeminiChatBridge([]byte(`{ + "contents":[ + {"role":"user","parts":[{"text":"create index.html"}]}, + {"role":"model","parts":[ + {"text":"I will inspect the workspace."}, + {"functionCall":{"name":"list_dir","args":{"path":"."}},"thoughtSignature":"opaque"} + ]}, + {"role":"model","parts":[ + {"functionResponse":{"name":"list_dir","response":{"entries":[]}}} + ]} + ] + }`), "gemini-3.6-flash") + if err != nil { + t.Fatal(err) + } + var body struct { + Messages []map[string]any `json:"messages"` + } + if err := json.Unmarshal(converted, &body); err != nil { + t.Fatal(err) + } + if len(body.Messages) != 3 { + t.Fatalf("messages=%+v", body.Messages) + } + if body.Messages[1]["role"] != "assistant" || body.Messages[2]["role"] != "tool" { + t.Fatalf("agy tool continuation roles changed: %+v", body.Messages) + } + if body.Messages[2]["tool_call_id"] != "gemini_call_1_1" { + t.Fatalf("agy tool response did not match its call: %+v", body.Messages[2]) + } +} + +func TestGeminiRequestBridgeRejectsModelRoleFunctionResponseMixedWithAssistantContent(t *testing.T) { + _, err := prepareGeminiChatBridge([]byte(`{ + "contents":[ + {"role":"model","parts":[{"functionCall":{"name":"lookup","args":{}}}]}, + {"role":"model","parts":[ + {"text":"mixed"}, + {"functionResponse":{"name":"lookup","response":{}}} + ]} + ] + }`), "gemini-3.6-flash") + if err == nil { + t.Fatal("model functionResponse mixed with assistant content must fail") + } +} + func TestGeminiRequestBridgeRejectsInvalidToolCallIdentity(t *testing.T) { for _, tc := range []struct { name string