fix(edge): agy 도구 연속 호출을 지원한다
공식 agy의 model-role functionResponse를 Chat tool 메시지로 변환한다. 원격 벤치 검증 기록과 후속 API surface 리팩터링 계획을 함께 반영한다.
This commit is contained in:
parent
b7fc808a56
commit
ad70fcb46e
12 changed files with 626 additions and 2 deletions
|
|
@ -56,7 +56,7 @@ x-goog-api-key: <IOP principal token>
|
|||
|
||||
초기 호환 범위는 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을 허용했다.
|
||||
|
|
|
|||
|
|
@ -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 정책으로 묶는다.
|
||||
|
|
|
|||
|
|
@ -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의 반영 또는 제외 근거를 남긴다.
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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: 없음
|
||||
|
|
@ -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 <token>` or `X-Api-Key: <token>`. 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`).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,153 @@
|
|||
<!-- task=remote_bench_workspace plan=0 tag=TEST -->
|
||||
|
||||
# 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 |
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<!-- task=remote_bench_workspace plan=0 tag=TEST -->
|
||||
|
||||
# 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
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
<!-- task=remote_bench_workspace plan=0 tag=TEST -->
|
||||
|
||||
# 원격 벤치 작업공간 준비
|
||||
|
||||
## 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`.
|
||||
|
|
@ -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 |
|
||||
|
|
@ -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, "")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue