Merge branch 'release/dev-1002'

This commit is contained in:
toki 2026-08-14 00:13:17 +09:00
commit 0ddf996fc7
153 changed files with 9055 additions and 35071 deletions

3
.gitignore vendored
View file

@ -13,6 +13,9 @@ agent-test/runs/
/iop.db
/*.log
/.cache/
/.gocache/
/.local/
/.tmp/
**/__pycache__/
*.py[cod]
/build/

View file

@ -1,4 +1,4 @@
.PHONY: all build build-local build-edge build-edge-host build-node build-node-target build-node-targets pack-node-target pack-edge archive-edge tidy test test-e2e test-control-plane-edge-wire test-credential-slot-smoke test-openai-ollama test-openai-lemonade test-openai-glm-coding test-hot-path-agent-smoke-self-test test-hot-path-agent-smoke-preflight test-hot-path-agent-smoke test-single-request-claude-smoke-self-test test-single-request-claude-smoke-preflight test-single-request-claude-smoke-validate test-single-request-claude-smoke readability-audit proto proto-dart client-test client-build-web clean test-agent-comparison-benchmark
.PHONY: all build build-local build-edge build-edge-host build-node build-node-target build-node-targets pack-node-target pack-edge archive-edge tidy test test-e2e test-control-plane-edge-wire test-credential-slot-smoke test-openai-ollama test-openai-lemonade test-openai-glm-coding test-hot-path-agent-smoke-self-test test-hot-path-agent-smoke-preflight test-hot-path-agent-smoke test-single-request-claude-smoke-self-test test-single-request-claude-smoke-preflight test-single-request-claude-smoke-validate test-single-request-claude-smoke readability-audit proto proto-dart client-test client-build-web clean
GOFLAGS ?= -trimpath
BUILD_DIR ?= build
@ -78,13 +78,6 @@ test:
readability-audit:
python3 scripts/readability_audit.py --check --input-mode worktree --output build/readability-audit.json
# Deterministic, credential-free benchmark manifest tests.
# Fresh unittest discovery for *_test.py plus the tracked example validation.
test-agent-comparison-benchmark:
cd $(CURDIR) && PYTHONPATH=$(CURDIR) python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py' -v
python3 scripts/agent_comparison_benchmark.py validate \
--manifest scripts/fixtures/agent-comparison-benchmark-manifest.example.json
test-e2e:
@echo "NOTE: test-e2e runs auxiliary smoke (Edge-Node + OpenAI) plus Control Plane-Edge wire smoke; completion still requires user-flow verification when changing runtime paths."
./scripts/e2e-smoke.sh

View file

@ -37,6 +37,5 @@ Start with `agent-contract/index.md` for protocol and runtime contracts, and `ag
Operator and client setup guides:
- [Edge Local Quickstart](docs/edge-local-dev-guide.md)
- [Agent Comparison Benchmark Dev Guide](docs/agent-comparison-benchmark-dev-guide.md)
- [dev OpenCode Settings Guide](docs/dev-opencode-settings-guide.md)
- [dev-corp Pi Settings Guide](docs/dev-corp-pi-settings-guide.md)

View file

@ -182,6 +182,8 @@ settings.setdefault("defaultThinkingLevel", "high")
settings.setdefault("hideThinkingBlock", False)
settings["httpIdleTimeoutMs"] = 0
retry = settings.setdefault("retry", {})
retry["enabled"] = False
retry["maxRetries"] = 0
provider_retry = retry.setdefault("provider", {})
provider_retry.pop("timeoutMs", None)
provider_retry["maxRetries"] = 0

View file

@ -86,6 +86,8 @@ class TestPreservesOpenaiResponsesConsumptionAndUserSettings(_TempHomeMixin, uni
"hideThinkingBlock": True, # user preference
"httpIdleTimeoutMs": 300000, # user value — must be overridden to 0
"retry": {
"enabled": True, # must be disabled
"maxRetries": 3, # must be overridden to 0
"provider": {
"timeoutMs": 120000, # must be removed
"maxRetries": 3, # must be overridden to 0
@ -152,6 +154,8 @@ class TestPreservesOpenaiResponsesConsumptionAndUserSettings(_TempHomeMixin, uni
self.assertNotIn("timeoutMs", provider_retry)
# 8) Retry policy is set
self.assertEqual(settings["retry"]["enabled"], False)
self.assertEqual(settings["retry"]["maxRetries"], 0)
self.assertEqual(provider_retry["maxRetries"], 0)
self.assertEqual(provider_retry["maxRetryDelayMs"], 60000)

View file

@ -14,7 +14,7 @@
|----|-----------|-----------|------|
| `iop.openai-compatible-api` | OpenAI-compatible API, Responses API, Chat Completions, legacy Completions, error envelope/SSE terminal error, `model` route, managed projection principal auth and slot-route binding, managed-versus-legacy provider credential selection, model-driven passthrough/normalized routing, provider-pool admission/unavailable error, safe credential-slot attribution, standard metadata, and provider-native extension fields such as `chat_template_kwargs` | `apps/edge/internal/openai/*`, `apps/edge/internal/authprojection/*`, `apps/edge/internal/service/provider_tunnel.go`, `packages/go/config/config.go`, `configs/edge.yaml` | `agent-contract/outer/openai-compatible-api.md` |
| `iop.anthropic-compatible-api` | Anthropic Messages API, count_tokens, models list, bearer or `X-Api-Key` principal auth, active managed projection auth and slot-route binding, `anthropic-version` routing, native Anthropic tunnel, Chat bridge, provider-pool-only admission, profile capability checks, managed-versus-legacy provider credentials, marked-preset single-request admission with Edge-owned internal Plan/Review template customization that leaves caller I/O unchanged, and current no-OpenAI-metric status | `apps/edge/internal/openai/anthropic_handler.go`, `apps/edge/internal/openai/anthropic_native.go`, `apps/edge/internal/openai/anthropic_bridge.go`, `apps/edge/internal/openai/anthropic_stream.go`, `apps/edge/internal/openai/anthropic_types.go`, `apps/edge/internal/openai/routes.go`, `apps/edge/internal/openai/principal.go`, `apps/edge/internal/authprojection/*`, `apps/edge/internal/openai/provider_tunnel.go`, `apps/edge/internal/openai/provider_model_rewrite.go`, `apps/edge/internal/openai/single_request_preset_binding.go`, `apps/edge/internal/openai/single_request_plan_stage.go`, `apps/edge/internal/openai/single_request_review_stage.go`, `packages/go/singlerequesttemplate/template.go`, `packages/go/config/protocol_profile.go` | `agent-contract/outer/anthropic-compatible-api.md` |
| `iop.gemini-compatible-api` | Gemini Developer API `streamGenerateContent`, route-qualified Gemini-native ingress, `x-goog-api-key` principal auth, `GOOGLE_GEMINI_BASE_URL`, official agy 1.1.12 API-key transport, Gemini function calls/thought signatures/SSE, and direct-versus-execution-preset binding | `apps/edge/internal/openai/routes.go`, `apps/edge/internal/openai/principal.go`, `apps/edge/internal/openai/gemini_handler.go`, `apps/edge/internal/openai/gemini_bridge.go`, `apps/edge/internal/openai/gemini_types.go`, `scripts/agent_benchmark/agy_iop.py`, `scripts/agent_benchmark/live_iop.py` | `agent-contract/outer/gemini-compatible-api.md` |
| `iop.gemini-compatible-api` | Gemini Developer API `streamGenerateContent`, route-qualified Gemini-native ingress, `x-goog-api-key` principal auth, `GOOGLE_GEMINI_BASE_URL`, official agy 1.1.12 API-key transport, Gemini function calls/thought signatures/SSE, and direct-versus-execution-preset binding | `apps/edge/internal/openai/routes.go`, `apps/edge/internal/openai/principal.go`, `apps/edge/internal/openai/gemini_handler.go`, `apps/edge/internal/openai/gemini_bridge.go`, `apps/edge/internal/openai/gemini_types.go` | `agent-contract/outer/gemini-compatible-api.md` |
| `iop.a2a-json-rpc-api` | A2A JSON-RPC API, `message/send`, `tasks/get`, `tasks/cancel`, A2A task state, agent card, `a2a.bearer_token`, Edge A2A input surface | `apps/edge/internal/input/a2a/*`, `packages/go/config/config.go`, `configs/edge.yaml` | `agent-contract/outer/a2a-json-rpc-api.md` |
## Inner Contracts

View file

@ -8,6 +8,7 @@
- 원본 경로:
- `packages/go/config/edge_types.go`
- `packages/go/config/provider_types.go`
- `packages/go/config/protocol_profile.go`
- `packages/go/config/execution_preset_types.go`
- `packages/go/config/load.go`
- `packages/go/config/validate.go`
@ -42,7 +43,8 @@ tracked config에는 public 예시와 기본 구조만 두고, 실제 endpoint/c
- TLS certificate/key/CA paths, credential keyring paths, issuer signing keys, and Node recipient keys are file references to operator-mounted material. Raw keys, principal tokens, and provider credentials never belong in tracked YAML. Role/name workload identities and HTTP server names must match their configured peer expectations.
- Managed provider credentials are selected only through an authenticated projected route. The effective route binds one principal, slot, profile, upstream model, resource selector, credential revision, route revision, and projection generation; caller metadata and legacy provider-auth headers cannot replace any binding field.
- `openai.principal_tokens[]`는 raw token을 저장하지 않고 hash/reference로 principal 매핑을 관리한다. 각 entry는 `token_ref` (non-empty, unique), `token_hash_sha256` (64-char hex, duplicate hash rejection), `principal_ref` (non-empty), optional `principal_alias` 필드를 갖는다. 여러 entry가 같은 `principal_ref``principal_alias`를 공유할 수 있으며, 이때 `token_ref`가 앱/통합/용도별 사용량 분해 기준이 된다. tracked config에는 raw token을 저장하지 않고 hash/reference만 둔다.
- `protocol_profiles` is the top-level map of custom profile overlays, keyed by stable profile id. Each `ProtocolProfileConf` can declare `base`, `driver`, `base_url`, an operation-path map, `auth`, `capabilities`, `model_mapping`, and `extensions`. A custom overlay extends one built-in or custom base; cycles, unknown bases, and invalid driver/operation/capability combinations are rejected during config normalization.
- `protocol_profiles` is the top-level map of custom profile overlays, keyed by stable profile id. Each `ProtocolProfileConf` can declare `base`, `driver`, `base_url`, an operation-path map, `auth`, `capabilities`, `model_mapping`, `normalization`, and `extensions`. A custom overlay extends one built-in or custom base; cycles, unknown bases, and invalid driver/operation/capability combinations are rejected during config normalization.
- `normalization.effort[operation]` declares the provider wire, supported normalized grades, whether the operation preserves effort with caller tools, and whether it preserves an explicit thinking token budget. Every normalization operation must exist in the profile operation map. Grade keys use `none|low|medium|high|xhigh|max`; exact miss falls back only to the nearest declared lower key. A canonical mapped value above its source key is rejected so config cannot silently upgrade requested effort. This Edge-local selection fact is consumed before tunnel dispatch and is not serialized into a new caller or Edge-Node wire field.
- `nodes[].providers[].profile` selects a built-in or custom catalog entry. If the selector is empty, legacy provider-type normalization can select a compatibility profile; this is distinct from `base` inheritance. Normalization resolves the selection into the runtime-only `ProviderDefinition.RuntimeProfile` snapshot, which is not serialized back into YAML. The resolved snapshot is copied into the nested OpenAI-compatible adapter config, not into a per-request tunnel message.
- `ConcreteProtocolProfile.MapModel(model)`은 provider의 model alias 정규화를 수행한다. provider가 model mapping을 정의하면 IOP external `model` key를 provider served target으로 변환한다. 매핑이 없으면 original model을 그대로 사용한다.
- `ConcreteProtocolProfile.HasCapability(cap)`는 provider capability admission에 사용된다. closed vocabulary (`models`, `chat`, `messages`, `responses`, `streaming`, `tool_calling`, `count_tokens`)만 허용한다.
@ -68,11 +70,11 @@ tracked config에는 public 예시와 기본 구조만 두고, 실제 endpoint/c
- `execution_presets[].single_request`는 operator-owned fixed single-request policy다. 설정 시 preset은 `allowed_modes=["light"]`, `stages=[plan, work, review]`의 승인된 plan→work→review 경로를 고수한다. 절대 상한은 `wall_clock_ms ≤ 1800000`, `timeout_ms ≤ 600000`, `max_tool_iterations ≤ 64`, `max_output_bytes ≤ 16777216`이며 `timeout_ms``wall_clock_ms`를 초과할 수 없다. selector와 plan/review stage는 `reasoning_effort=high`를 강제하고 work stage는 `reasoning_effort`를 선언할 수 없다. `workspace_ref`는 비어있을 수 없으며 raw path, credential, Node id, endpoint를 포함하지 않는다. `templates` 섹션을 통해 optional `plan_file``review_file` (edge.yaml 상대 경로) 커스텀 Markdown 템플릿을 지정할 수 있으며, load 시점에 8192바이트 상한 및 문법 검증이 수행되고 생략 시 built-in default 템플릿이 적용된다. config refresh diff reporting 시 템플릿 파일 경로나 본문은 노출되지 않고 SHA-256 digest만 보고된다. single_request preset은 `workspace_tools`를 선언할 수 없다. catalog 변경과 mapping 변경은 live-apply로 분류되며 refresh 이후 새로 시작되는 logical request에만 적용된다. admitted single-request binding은 refresh 이후에도 frozen public model, stage binding, workspace reference, limits, effective templates를 유지한다.
- `nodes[].providers[]`는 Node 아래 resource/provider catalog다. `category``api`, `cli`, `local_inference` resource kind를 나타낸다.
- `nodes[].providers[].type``seulgivibe_claude``seulgivibe_openai`는 runtime type을 `openai_compat`로 정규화한다. Edge가 Node adapter payload를 만들 때 명시 provider label이 없으면 원래 Seulgivibe type alias를 `OpenAICompatAdapterConfig.provider`로 보존한다.
- `nodes[].providers[].response_stall_timeout_ms`는 provider-originated response-stall timeout을 밀리초 단위로 선언한다. 양수 값은 그대로 사용되고, 0 또는 생략은 문서화된 기본값 `300000`을 적용한다. 음수 값과 safe duration bound를 초과하는 양수 값은 `NodeProviderConf.Validate()`에서 거부한다. effective 값은 `NodeProviderConf.EffectiveResponseStallTimeoutMS()`에서 계산한다. 이 필드는 config refresh에서 `restart_required`로 분류되며, effective-zero 등가성(생략 vs 명시적 0)은 변경으로 보고되지 않는다. request hard timeout, queue timeout, heartbeat/disconnect, CLI `response_idle_timeout_ms`는 기존 소유권을 유지한다.
- `nodes[].providers[].response_stall_timeout_ms`는 provider-originated response-stall timeout을 밀리초 단위로 선언한다. 양수 값은 그대로 사용되고, 0 또는 생략은 문서화된 기본값 `60000`을 적용한다. 음수 값과 safe duration bound를 초과하는 양수 값은 `NodeProviderConf.Validate()`에서 거부한다. effective 값은 `NodeProviderConf.EffectiveResponseStallTimeoutMS()`에서 계산한다. 이 필드는 config refresh에서 `restart_required`로 분류되며, effective-zero 등가성(생략 vs 명시적 0)은 변경으로 보고되지 않는다. request hard timeout, queue timeout, heartbeat/disconnect, CLI `response_idle_timeout_ms`는 기존 소유권을 유지한다.
- `nodes[].providers[].id`는 전체 Edge config 안에서 중복되면 안 된다.
- `nodes[].providers[].adapter`는 같은 Node 안의 enabled adapter instance key를 참조해야 한다. Exact instance key를 우선하고, legacy type-name route는 같은 type의 enabled instance가 정확히 하나일 때만 허용한다.
- `nodes[].providers[].enabled`: 생략 또는 `true` → provider pool dispatch 후보에 포함. `false` → dispatch pool에서 제외. 비활성화된 provider는 status snapshot에 `status=disabled`, `health=disabled`, `capacity=0`으로 표시된다. adapter process lifecycle 변경 없음. config refresh 시 `enabled` 토글은 live-apply(restart 불필요)로 분류된다. disabled provider의 adapter reference check는 skip되지만 structural validation(type, category, models, numeric bounds)은 수행된다.
- `nodes[].providers[].capacity``long_context_capacity``node_id + provider_id` resource가 소유한다. 같은 provider를 참조하는 여러 `models[].id`는 일반·long slot을 합산 공유한다. `total_context_tokens`는 runtime counter가 아니라 `context_window_tokens * long_context_capacity` 이상이어야 하는 정적 load/refresh validation 값이다.
- `nodes[].providers[].capacity``long_context_capacity``node_id + provider_id` resource가 소유한다. 같은 key의 legacy adapter가 함께 있으면 adapter capacity는 transport 상한이며 provider capacity는 그 이하의 admission 한도로 설정할 수 있고, 상한 초과만 load에서 거부한다. 같은 provider를 참조하는 여러 `models[].id`는 일반·long slot을 합산 공유한다. `total_context_tokens`는 runtime counter가 아니라 `context_window_tokens * long_context_capacity` 이상이어야 하는 정적 load/refresh validation 값이다.
- `nodes[].providers[].priority`: provider-pool dispatch tie-breaker다. 기본값은 `0`이고 음수는 validation error다. dispatch는 `in_flight < capacity` 후보 중 가장 낮은 `in_flight`를 먼저 선택하며, `in_flight`가 같은 후보에서만 낮은 숫자의 `priority`를 우선한다. `in_flight``priority`가 모두 같으면 기존 순환을 유지한다. priority 변경은 live-apply(restart 불필요)로 분류된다.
- Configured provider health remains an immutable input snapshot during request execution. Confirmed current bound runtime-unavailable evidence is stored separately under `(node_id, connection_generation, provider_id)`, gates effective admission, and projects the runtime ProviderSnapshot unavailable without changing `NodeProviderConf.Health`, refresh diffs, or Node config payloads. A later exact higher-sequence available CAPABILITIES probe or a newer connection generation clears effective exclusion under the runtime contract, not through config refresh.
- After the queue makes that authoritative overlay decision, Edge emits bounded operational evidence only: `iop_edge_provider_health_evidence_total{source,evidence_health,decision}` and `iop_edge_provider_health_transitions_total{from_health,to_health}`, plus `edge_provider_health_observation`. Sources, health values, and decisions use closed vocabularies; provider/node/run/session/adapter/target identity, payloads, and credentials are excluded. The observer is post-lock and cannot validate or mutate config/overlay state.

View file

@ -58,7 +58,7 @@ Edge는 Node 연결을 수락하고, Node는 연결 직후 등록 요청을 보
- disconnect/reconnect: current dispatch-ready owner의 close/heartbeat timeout만 해당 connection generation을 fence한다. Edge는 같은 authoritative lifecycle에서 provider lease를 정확히 한 번 반환하고 resource를 offline/excluded로 만든 뒤 queue를 live candidate 기준으로 재평가한다. accepted Node의 ready transition은 새 generation resource를 활성화하고 기존 waiter를 즉시 pump한다. stale/rejected connection callback은 live state나 lifecycle event를 바꾸지 않는다.
- execution: Edge가 `RunRequest`를 보내고 Node가 `RunEvent` stream으로 실행 상태를 보낸다.
- provider raw tunnel: Edge가 기존 Edge-Node socket으로 `ProviderTunnelRequest`를 보내고 Node가 provider HTTP/SSE 요청을 연 뒤 `ProviderTunnelFrame` stream으로 provider status/header/body/end/error/usage 후보를 sequence와 함께 돌려준다. 이 경로는 OpenAI-compatible provider passthrough용이며 `RunEvent` 실행 stream과 분리된다.
- response_stall_timeout_ms: `RunRequest.response_stall_timeout_ms``ProviderTunnelRequest.response_stall_timeout_ms`는 int64 필드로, 선택된 provider의 response-stall timeout을 밀리초 단위로 운반한다. Zero는 Node가 문서화된 기본값(300000ms)을 적용함을 의미한다. Negative 또는 overflow 값은 Node 경계에서 router/provider 호출 전에 reject된다. Edge provider-pool dispatch는 winning candidate의 effective timeout을 각 요청에 복사한다. Direct/non-pool 호출은 wire에서 zero를 사용하고 Node 기본값을 적용한다.
- response_stall_timeout_ms: `RunRequest.response_stall_timeout_ms``ProviderTunnelRequest.response_stall_timeout_ms`는 int64 필드로, 선택된 provider의 response-stall timeout을 밀리초 단위로 운반한다. Zero는 Node가 문서화된 기본값(60000ms)을 적용함을 의미한다. Negative 또는 overflow 값은 Node 경계에서 router/provider 호출 전에 reject된다. Edge provider-pool dispatch는 winning candidate의 effective timeout을 각 요청에 복사한다. Direct/non-pool 호출은 wire에서 zero를 사용하고 Node 기본값을 적용한다.
- response stall terminal: Node observes only the execution activity contract. On expiry it cancels and fences the local provider attempt, joins the bounded close-grace fence and an independent exact-target health probe without extending either serially, then emits exactly one normalized `RunEvent{type=error}` or tunnel `ProviderTunnelFrame{kind=ERROR}` with `failure_code=response_stalled` and populates the optional wire `ExecutionFailure` field (field 13 on `RunEvent`, field 15 on `ProviderTunnelFrame`). Terminal metadata is allowlisted (three-way health evidence as the `provider_health` status paired with the `liveness_classification` normalization — `available`/`request_stalled`, `unavailable`/`provider_unhealthy`, or `unknown`/`health_unknown`; idle duration; Node-owned run/attempt identity; fence; adapter; target; and an optional connection-scoped `health_observation_seq`); it contains no caller-controlled identity, raw payload, credential, or `recovery_eligible`. Nil and non-stalled failures leave wire `ExecutionFailure` absent while preserving legacy error string fields (`RunEvent.Error` / `ProviderTunnelFrame.Error`). `health_observation_seq` starts at one per connection and increases uniquely across the connection's normalized and tunnel observations; an unbound session omits it. Probe availability is evidence only and never resets progress, changes the fence, or authorizes retry. A confirmed fence is a capability hint only, not Node retry authorization.
- Edge terminal handoff: transport reception identity, not payload identity, supplies `(node_id, connection_generation)`. Before a normalized or tunnel terminal can affect provider health, Edge compares that identity and the typed adapter/target evidence with the tracked immutable provider lease. A current terminal releases that lease exactly once even when optional health evidence is rejected. Edge adds `provider_id`, validated `provider_health`, and `recovery_handoff=confirmed` to every validated current bound stall before downstream routing, including sequence-stale request-local handoff; only a fresh `unavailable` observation lowers the separate runtime overlay. The handoff token is not replay approval, and Edge never adds `recovery_eligible` here.
- CAPABILITIES recovery probe: Node resolves the requested adapter instance, runs the bounded fail-closed exact-target `ProbeHealth`, and returns stable `adapter_key`, `target`, normalized `provider_status`, and the next Session-owned `health_observation_seq`. Edge retains the command's dispatch node/generation and may clear one unavailable overlay only when a higher-sequence `available` response identifies exactly one same-generation provider binding. Empty, malformed, ambiguous, mismatched, stale, `unknown`, and `unavailable` results do not change the overlay.

View file

@ -34,22 +34,22 @@ The execution package defines host-neutral provider primitives. It owns provider
- Registry lookup uses provider identity and returns typed failures for missing or unavailable providers.
- Callers must reject commands outside the closed provider-command allowlist before provider lookup.
- Token usage remains observation data attached to execution or tunnel results.
- `DefaultResponseStallTimeoutMS = 300000` is the documented default. `ResolveStallTimeoutMS(ms)` validates then maps zero to the default; safe positive values pass through, while negative or overflow values return an error.
- `DefaultResponseStallTimeoutMS = 60000` is the documented default. `ResolveStallTimeoutMS(ms)` validates then maps zero to the default; safe positive values pass through, while negative or overflow values return an error.
- `ClassifyRuntimeEvent` returns `start` for `EventTypeStart`, `progress` for non-empty `delta`/`message` or non-terminal usage, `terminal` for `complete`/`error`/`cancelled` (before usage check), and `none` for empty/unknown events.
- `ClassifyProviderTunnelFrame` returns `progress` for `response_start` (with or without headers) and non-empty `body`, `terminal` for `end`/`error` (before payload check), `progress` for `usage`, and `none` for empty/unknown frames.
- `ValidateStallTimeoutMS(ms)` rejects negative values and values exceeding `maxSafeStallTimeoutMS`; zero is allowed (use default).
- `NodeProviderConf.EffectiveResponseStallTimeoutMS()` returns the effective timeout for a provider candidate.
- `RunRequest.ResponseStallTimeoutMS` and `ProviderTunnelRequest.ResponseStallTimeoutMS` carry the selected provider's effective timeout; zero on the wire means the Node applies the documented default.
- The Node wire boundary normalizes zero to `300000` and rejects negative or overflow values before router/provider invocation.
- The Node wire boundary normalizes zero to `60000` and rejects negative or overflow values before router/provider invocation.
- `response_stalled` is a stable typed failure. Node transport mappers (`runEventToProto` and `tunnelFrameToProto`) populate the optional wire `ExecutionFailure` message only for `FailureCodeResponseStalled`, attaching a defensive clone of allowlisted metadata keys (`failure_code`, `provider_health`, `liveness_classification`, `idle_duration_ms`, `run_id`, `attempt_id`, `attempt_fence`, `adapter`, `target`, and `health_observation_seq`); nil and non-stalled failures leave wire `ExecutionFailure` absent while preserving legacy error string fields (`RunEvent.Error` / `ProviderTunnelFrame.Error`). Caller metadata cannot override these values, and no raw payload, credential, or `recovery_eligible` signal is admitted.
- The Node watchdog starts from attempt admission, resets only on the documented progress dispositions, stops on provider terminal, and emits one typed stall terminal. It does not retry providers or infer recovery eligibility. `Retryable=true` means only that the local provider ownership fence was confirmed within the bounded close grace.
- After the watchdog claims a stall it joins two independent bounded outcomes without extending either serially — the fixed close-grace fence and the exact-target health probe — then assembles exactly one allowlisted terminal. The joined `liveness_classification`/`provider_health` pair is exactly `request_stalled`/`available`, `provider_unhealthy`/`unavailable`, or `health_unknown`/`unknown` (fail-closed default). Provider availability observed here is evidence only: it never resets progress, changes the fence, revives output, or authorizes retry, and late provider output stays fenced.
- `health_observation_seq` is a connection-scoped monotonic sequence sourced from the transport Session. A new connection starts at zero, so the first finalized observation is one; normalized and tunnel observations on the same connection share the source and receive unique, increasing values under concurrency. Internal or unbound execution paths omit the key entirely and never encode a process-global generation.
- `ProviderPoolDispatchRequest` carries two request-local recovery-hint fields: `AvoidProviderID` (non-empty to prefer a runtime-eligible alternate over the avoided provider) and `AllowAvoidedProviderFallback` (explicit permission to retain the avoided provider when no alternate exists and it remains runtime eligible). The queue applies identical avoidance filtering to both initial and queued re-resolution. Zero values preserve current selection behavior. This is selection policy only: it does not create a retry loop, reserve a slot, change provider priority, persist the hints, or count retries. The fallback permission is always derived from exact probe-backed `available` evidence by the caller (never from current overlay state).
- `ProviderPoolDispatchRequest` carries two request-local recovery-hint fields: `AvoidProviderID` (non-empty to require a runtime-eligible alternate over the avoided provider) and `AllowAvoidedProviderFallback` (a compatibility field that liveness recovery leaves false). The queue applies identical avoidance filtering to both initial and queued re-resolution. Zero values preserve current selection behavior. This is selection policy only: it does not create a retry loop, reserve a slot, change provider priority, persist the hints, or count retries. A provider whose request produced `response_stalled` is never selected again for that request, even when its exact-target health probe reports `available`.
- A Node `capabilities` command performs the same bounded exact-target `ProbeHealth` operation. Its stable result evidence is the requested adapter instance key (`adapter_key`), exact `target`, fail-closed normalized `provider_status`, and the next `health_observation_seq` from that same transport Session. Probe errors, unsupported probing, and adapter/instance/target mismatches report `unknown`; raw capability status is not recovery evidence.
- Edge accepts a typed stall observation for provider-wide projection only after authoritative reception `(node_id, connection_generation)` matches the tracked immutable dispatch lease `(node_id, connection_generation, provider_id, adapter, target)`, the local attempt fence is confirmed, and the observation sequence is strictly newer. A current terminal still releases its lease exactly once when health evidence is absent, malformed, mismatched, or stale; a reception-owner mismatch changes neither overlay nor lease state.
- Every validated current bound stall is annotated with Edge-owned `provider_id`, the validated `provider_health`, and `recovery_handoff=confirmed`, including an out-of-order terminal whose health projection is sequence-stale. Only a fresh `unavailable` observation lowers the generation-scoped runtime overlay. The token proves reception, lease binding, and local-fence handoff only; it is never `recovery_eligible` and never authorizes retry.
- Every supported OpenAI Chat/Responses normalized or tunnel request enters one request-local StreamGate runtime, which is the sole liveness owner even when configured semantic filtering is disabled. That runtime may consume the confirmed handoff as a raw-free `response_stalled` provider error while its endpoint adapters preserve the disabled-semantic native status, headers, JSON/SSE/tunnel order, validation, usage, cancellation, and terminal behavior. It retains only the stable failure code, confirmed-handoff token, and `available|unavailable|unknown` health classification; Node/provider messages and arbitrary metadata are not copied. Exact replay additionally requires the existing uncommitted, uncancelled, side-effect-safe, snapshot-backed, shared-budget gate. A confirmed old terminal closes its Edge transport without another `CancelRun`; pool re-admission consumes the provider once as `AvoidProviderID`, with same-provider fallback only for exact `available` evidence.
- Every supported OpenAI Chat/Responses normalized or tunnel request enters one request-local StreamGate runtime, which is the sole liveness owner even when configured semantic filtering is disabled. That runtime may consume the confirmed handoff as a raw-free `response_stalled` provider error while its endpoint adapters preserve the disabled-semantic native status, headers, JSON/SSE/tunnel order, validation, usage, cancellation, and terminal behavior. It retains only the stable failure code, confirmed-handoff token, and `available|unavailable|unknown` health classification; Node/provider messages and arbitrary metadata are not copied. Exact replay additionally requires the existing uncommitted, uncancelled, side-effect-safe, snapshot-backed, shared-budget gate and is bounded to one liveness replay per request. A confirmed old terminal closes its Edge transport without another `CancelRun`; pool re-admission consumes the provider once as `AvoidProviderID` and never falls back to that stalled provider. A stall on the replacement attempt terminates without another replay.
- The runtime overlay is keyed by `(node_id, connection_generation, provider_id)` and remains separate from configuration health. It excludes the provider from effective admission and projects it unavailable in status snapshots. Recovery requires a later CAPABILITIES result for the same current adapter/target mapping with strictly higher sequence and exact normalized `available`; malformed, ambiguous, stale-generation, unknown, and unavailable results are no-ops.
## Health probe contract

View file

@ -375,7 +375,7 @@ Wrong methods on Anthropic-selected endpoints return `405 invalid_request_error`
- `tools`: 각 tool은 `name`, `input_schema`를 필수로 가진다. 선택 boolean `defer_loading`은 Claude Code tool-search 호출 호환성 annotation으로만 수용한다. Native Messages raw tunnel은 원문을 보존하지만, decoded Chat bridge와 marked single-request 경로에서는 route, provider, workspace, tool policy 또는 authorization 권한으로 해석하지 않고 normalized Chat provider body에서 제거한다.
- `tool_choice`: `auto`, `any`, `none`, `tool` 타입만 허용한다.
- `thinking`: 양수 `budget_tokens`가 있는 `type="enabled"` 또는 budget 없는 `type="adaptive"`를 허용한다. 선택 `display`는 Claude Code thinking-redaction 호환성을 위해 `omitted` 또는 `summarized`만 수용한다. Native Messages raw tunnel은 원문을 보존하지만, decoded Chat bridge와 marked single-request 경로에서는 display를 route, stage, provider, workspace, tool policy 또는 authorization 권한으로 해석하지 않고 normalized Chat provider body에서 제거한다. Chat bridge의 `enabled`는 profile의 thinking/reasoning extension이 필요하고, `adaptive``output_config.effort` 기반 provider 제어를 사용한다.
- `output_config.effort`: `low`, `medium`, `high`, `xhigh`, `max`를 허용하며 Chat bridge에서 `reasoning_effort`로 변환한다. 대소문자, 별칭, 캐핑, 다운시프트는 허용하지 않는다.
- `output_config.effort`: canonical IOP 등급 `low`, `medium`, `high`, `xhigh`, `max`를 허용한다. Edge는 caller identity가 아니라 요청의 tools/thinking/stream 요구와 selected protocol profile의 operation별 normalization을 사용해 provider wire 값을 정한다. exact 등급이 없으면 선언된 가장 가까운 하위 등급으로만 매핑하고 상향하지 않는다. 예를 들어 `max`가 없고 `xhigh`가 있으면 `xhigh`를 사용한다. 어떤 하위 등급도 없으면 dispatch 전에 `not_supported_error`로 거부한다.
- `output_config.format`: `type="json_schema"`와 object `schema`를 허용하며 Chat bridge에서 OpenAI-compatible `response_format.json_schema`로 변환한다.
- `cache_control`: text/image/tool/tool-result/thinking block과 tool declaration의 compatibility annotation을 수용하되 Chat bridge에서는 정책으로 해석하거나 provider body에 전달하지 않는다.
- `metadata`: caller-defined object이며 IOP identity source로 사용하지 않는다. Native Messages 경로는 원문을 보존하고, Chat bridge는 object 여부만 검증한 뒤 provider body에서는 제거한다.
@ -520,7 +520,7 @@ Top-level `models[]` is the static catalog source for IOP model discovery and pr
### Native vs Bridge
선택된 provider의 `ConcreteProtocolProfile.Driver``anthropic_messages`이면 Edge는 provider raw tunnel을 통해 Anthropic-native request/response를 relay한다. Ordinary native routes preserve provider response model/body bytes, while authorized virtual presets rewrite successful response identity to the requested virtual model.
`openai_chat`이면 Edge는 Anthropic Messages request를 Chat Completions request로 bridge하고, Chat bridge 응답을 다시 Anthropic Messages response로 변환한다. Authorized virtual presets retain their requested virtual response model identity through that conversion; ordinary bridge responses use the bridge's converted response model semantics.
`openai_chat`이면 Edge는 caller-neutral request requirements를 먼저 만들고, profile이 보존 가능한 operation을 선택한다. Chat Completions가 tools+effort 조합을 보존하지 못하지만 같은 profile의 Responses operation이 보존할 수 있으면 `POST /v1/responses`로 bridge하고 결과를 Anthropic Messages 형식으로 되돌린다. Chat operation이 요구사항을 모두 보존할 때만 기존 Chat bridge를 사용한다. Authorized virtual presets retain their requested virtual response model identity through that conversion; ordinary bridge responses use the selected bridge's converted response model semantics.
그 외 driver는 `502 api_error` "selected provider returned an unsupported protocol driver"를 반환한다.
Chat bridge는 Gemini OpenAI-compatible tool call의 `extra_content.google.thought_signature`를 opaque Anthropic `tool_use.id`에 담아 caller에게 전달한다. Caller는 해당 id를 tool result까지 변경 없이 replay해야 하며, 다음 요청에서 Edge는 원래 tool call id와 signature를 복원한다. Signature가 없는 provider의 tool id는 변경하지 않는다.
@ -548,7 +548,8 @@ before response commitment.
Anthropic Messages 요청은 선택된 provider가 다음 capability를 가져야 한다:
- native: `messages` capability + `messages` operation
- Chat bridge: `chat` capability + `chat_completions` operation
- Chat bridge: `chat` capability + `chat_completions` operation + 요청 control을 보존하는 operation별 normalization
- Responses bridge: `responses` capability + `responses` operation + 요청 control을 보존하는 operation별 normalization
- `streaming` capability (streaming 요청인 경우)
- `tool_calling` capability (tools가 있는 요청인 경우)
- `count_tokens` capability + `count_tokens` operation (count_tokens native fallback 요청인 경우; TokenCounter local count path는 provider selection 및 capability check가 필요 없다)
@ -559,6 +560,7 @@ capability 불만족은 `400 not_supported_error`로 종료한다.
Chat bridge의 explicit `thinking.type="enabled"`와 assistant thinking block 전달은 provider profile의 `extensions.thinking` 또는 `extensions.reasoning``true`일 때만 지원한다. Claude Code가 이전 응답에서 받은 빈 signature의 thinking block을 generic Chat profile 요청에 replay하면 private reasoning block만 제거하고 visible text/tool history는 유지한다. Signed thinking block은 profile과 관계없이 Chat bridge에서 거부한다.
해당 profile extension 없이 explicit enabled thinking으로 bridge하면 `400 invalid_request_error` "selected Chat profile does not support thinking"를 반환한다. `thinking.type="adaptive"`는 별도 budget field를 만들지 않고 `output_config.effort``reasoning_effort`로 변환한다.
OpenAI profile의 Responses operation은 adaptive effort와 caller tools를 함께 보존할 수 있지만 explicit `thinking.budget_tokens`를 보존하지 않으므로, 이 조합은 Responses로 조용히 변환하지 않고 fail closed한다.
## Usage Attribution

View file

@ -9,14 +9,12 @@
- `apps/edge/internal/openai/routes.go`
- `apps/edge/internal/openai/principal.go`
- `apps/edge/internal/openai/chat_handler.go`
- `scripts/agent_benchmark/agy_iop.py`
- `scripts/agent_benchmark/live_iop.py`
- external caller surface: official Antigravity CLI `agy` 1.1.12 Gemini API-key provider
## 읽는 조건
- Gemini Developer API `streamGenerateContent`, `x-goog-api-key`, `GOOGLE_GEMINI_BASE_URL`, `GEMINI_API_KEY`, `agy` API-key provider, Gemini-native tool/function call, 또는 Gemini-native SSE ingress를 구현·검증할 때 읽는다.
- IOP execution preset을 Gemini-native caller에 노출하거나 `agy` benchmark transport를 변경할 때 읽는다.
- IOP execution preset을 Gemini-native caller에 노출하거나 공식 `agy` transport를 변경할 때 읽는다.
## 범위
@ -29,7 +27,7 @@ x-goog-api-key: <IOP principal token>
```
- `{route-id}`는 Edge가 인증된 principal에 대해 해석할 direct route 또는 virtual execution-preset id다. URL path segment 하나의 canonical token이어야 한다.
- `{caller-model}`은 caller가 선택한 Gemini 모델 id이며 관측·호환성 검증 대상이다. provider/credential 또는 execution preset 선택 권한은 갖지 않는다.
- `{caller-model}`은 caller가 선택한 Gemini 모델 id 또는 공식 CLI의 bounded 표시 label이며 관측·호환성 검증 대상이다. URL-encoded space를 포함한 `Gemini 3.6 Flash` label을 허용하지만 앞뒤 공백, slash, control character는 거부한다. provider/credential 또는 execution preset 선택 권한은 갖지 않는다.
- 현재 `agy` 호환 표면은 `alt=sse``streamGenerateContent`만 지원한다. `generateContent`, batch, files, cached content, tuning API는 비범위다.
- direct route와 marked single-request preset은 모두 기존 Edge route resolution, managed admission, provider-pool, preset coordinator를 사용한다. 별도 Gemini 전용 우회 dispatch를 만들지 않는다.
@ -41,7 +39,7 @@ x-goog-api-key: <IOP principal token>
- `GEMINI_API_KEY`: upstream provider key가 아니라 IOP principal token이다.
- `GOOGLE_GEMINI_BASE_URL`: `https://<edge>/gemini/{route-id}`다.
- 사설 dev CA를 사용하는 경우 caller child에는 표준 `SSL_CERT_FILE``NODE_EXTRA_CA_CERTS`만 명시적으로 전달한다.
- `--model`: 공식 CLI가 인식하는 Gemini 모델 label을 사용한다. benchmark의 Gemini 3.6 Flash 호출`Gemini 3.6 Flash`다.
- `--model`: 공식 CLI가 인식하는 Gemini 모델 label을 사용한다. dev 호환 확인에서 사용하는 label`Gemini 3.6 Flash`다.
- `--effort`는 API-key provider 호출에 전달하지 않는다. 요청된 high effort는 인증된 IOP route/preset의 effective binding으로 검증한다.
`GEMINI_BASE_URL`, `AGY_PROVIDER`, `AGY_OPENAI_BASE_URL`, `AGY_OPENAI_API_KEY`는 이 계약의 transport가 아니다.
@ -58,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`
@ -75,7 +73,7 @@ Edge는 이를 기존 Chat/preset ingress의 system/user/assistant/tool message,
- provider-reported usage가 있으면 `usageMetadata.promptTokenCount`, `candidatesTokenCount`, `thoughtsTokenCount`, `cachedContentTokenCount`, `totalTokenCount`의 존재하는 값만 투영한다. 누락 값을 0으로 발명하지 않는다.
- caller disconnect는 기존 request cancellation 경계를 사용하며 이후 frame을 쓰지 않는다.
## 공식 agy stream-json lifecycle
## 공식 agy 수동 호출 확인 기준
`agy` 1.1.12의 각 JSONL record는 `event` discriminator와 같은 이름의 중첩 payload를 사용한다.
@ -83,7 +81,7 @@ Edge는 이를 기존 Chat/preset ingress의 system/user/assistant/tool message,
- step: `{"event":"step_update","step_update":{"state":...,"step_type":...,"usage":{...}}}`
- terminal: `{"event":"result","result":{"status":"SUCCESS","duration_seconds":...,"num_turns":...,"usage":{...}}}`
benchmark adapter는 중첩 payload만 파싱하며 `result.status=SUCCESS` 한 건과 process exit/quiet를 success terminal로 인정한다. 구조가 유효한 `result.status=ERROR`는 stream incompatibility가 아니므로 success terminal을 만들지 않고 caller process의 non-zero exit를 lifecycle failure authority로 보존한다. `response`, `text_delta`, tool payload, conversation id는 durable evidence에 보존하지 않는다. usage는 caller가 제공한 `input_tokens`, `cache_read_tokens`, `output_tokens`, `thinking_tokens`, `total_tokens`만 원래 단위의 count로 기록하고 누락값을 합성하지 않는다.
수동 확인에서는 중첩 `result.status=SUCCESS` 한 건과 process exit 0을 성공 terminal로 본다. 구조가 유효한 `result.status=ERROR` 또는 non-zero exit는 실패로 남기며 성공으로 재해석하지 않는다. 확인 기록에는 raw 응답, tool payload, conversation id, credential을 남기지 않고 caller가 제공한 usage만 원래 단위로 요약한다.
## 오류
@ -101,9 +99,10 @@ HTTP commit 전 오류는 다음 Gemini envelope 한 건으로 반환한다.
- route/auth: `apps/edge/internal/openai/routes.go`, `apps/edge/internal/openai/principal.go`
- Gemini request/SSE bridge: `apps/edge/internal/openai/gemini_handler.go`, `apps/edge/internal/openai/gemini_bridge.go`, `apps/edge/internal/openai/gemini_types.go`
- Edge regression: `apps/edge/internal/openai/gemini_handler_test.go`, existing Chat/preset/auth tests
- caller adapter: `scripts/agent_benchmark/agy_iop.py`, `scripts/agent_benchmark/agy_iop_test.py`, `scripts/agent_benchmark/live_iop.py`
- live proof: official `agy --output-format stream-json` through the dev Edge route-specific base URL, with direct and execution-preset effective binding evidence
2026-08-12 dev 검증에서 normal/boundary/auth/tool/SSE 회귀 테스트와 공식 `agy` 1.1.12 direct·hybrid 실호출이 통과해 이 계약을 active로 전환했다.
2026-08-12 benchmark 실측 보정에서 official agy planner의 structured-output schema field를 Chat `response_format`으로 변환하고, 구조가 유효한 ERROR result를 parser failure와 분리했다.
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을 허용했다.

View file

@ -193,6 +193,7 @@ Normalized provider 실행으로 라우팅되는 요청의 최소 형태:
- `max_output_tokens`: 출력 길이 상한이다. 내부 provider option의 `max_tokens`로 전달된다.
- `temperature`: 생성 다양성 option이다. 대상 adapter가 지원하지 않으면 무시될 수 있다.
- `top_p`: nucleus sampling option이다. 대상 adapter가 지원하지 않으면 무시될 수 있다.
- `reasoning.effort`: provider tunnel route에서는 canonical IOP effort 등급을 operation별 protocol normalization으로 매핑한다. exact 등급이 없으면 선언된 가장 가까운 하위 등급만 사용하며 상향하지 않는다. 같은 `reasoning` object의 다른 field는 보존한다.
Normalized route 금지:
@ -207,7 +208,7 @@ Normalized route 금지:
현재 구현 메모:
- normalized(non-provider) `/v1/responses` route는 strict field validation을 유지하며 non-streaming string input만 지원한다.
- provider-pool model group route(`models[]`)의 `/v1/responses` 호출은 selected provider가 the Responses operation and capability를 선언한 tunnel candidate이면 raw passthrough로 provider `POST /v1/responses`에 전달한다. This admission is not exclusive to the `openai_responses` driver. caller body는 `model` field만 served target으로 rewrite하고, selected provider가 지원하는 OpenAI-compatible 표준 field와 provider extension field(`max_output_tokens`, `tools`, `store`, provider-specific knobs 등)는 보존한다. `stream:true`는 provider raw SSE로 relay한다. Managed mode injects the selected slot lease at the Node; legacy mode may apply configured provider-auth forwarding. Response model echo rewrite is not applied, and this path never falls back to normalized `SubmitRun`.
- provider-pool model group route(`models[]`)의 `/v1/responses` 호출은 selected provider가 Responses operation/capability와 요청의 tool/effort semantics를 선언한 tunnel candidate이면 provider `POST /v1/responses`에 전달한다. This admission is not exclusive to the `openai_responses` driver. caller body는 served target과, 필요한 경우 operation별로 매핑된 `reasoning.effort`만 rewrite하고 `max_output_tokens`, `tools`, `store`, provider-specific knobs 및 `reasoning`의 다른 field는 보존한다. `stream:true`는 provider raw SSE로 relay한다. Managed mode injects the selected slot lease at the Node; legacy mode may apply configured provider-auth forwarding. Response model echo rewrite is not applied, and this path never falls back to normalized `SubmitRun`.
- provider-pool model group route는 provider candidate를 먼저 선택한다. 선택된 provider가 OpenAI-compatible 호출 방식을 지원하면 `ProviderTunnelRequest` passthrough를 사용하고, Ollama/native provider이면 normalized `RunRequest`를 사용한다. provider type만으로 Ollama를 candidate set에서 제거하지 않으며, OpenAI-compatible provider의 tunnel 구현이 없으면 normalized fallback이 아니라 unsupported/implementation error다.
- provider-pool pending request는 lease 반환, config refresh, provider disable, Node disconnect/reconnect 때 live config와 dispatch-ready registry에서 candidate를 다시 계산한다. 후보가 full인 상태는 queue policy에 따라 계속 대기하지만 live candidate가 모두 사라지면 원래 queue timeout까지 기다리지 않고 terminal unavailable로 끝난다.
- provider-pool admission/unavailable 실패는 현재 외부 error envelope를 유지해 HTTP `502``type="node_dispatch_error"`로 반환한다. 별도 public status code나 response field를 추가하지 않으며 error message에는 raw token이나 private endpoint를 포함하지 않는다.
@ -271,7 +272,7 @@ Chat Completions의 실행 경로는 caller가 보낸 `model`의 route/provider
IOP 확장 think 제어 field:
- `think` (bool, optional): thinking/reasoning 생성 활성화 여부를 표현하는 IOP 확장 field다. 생략하면 provider 기본값을 유지한다. `false`는 thinking 생성을 끄도록 요청하고, `true`는 provider가 지원하면 thinking 생성을 명시 활성화한다.
- `reasoning_effort` (string, optional): `none`, `low`, `medium`, `high` 중 하나인 IOP 확장 field다. `none``think=false`와 같은 disable 의미로 처리한다. `low`/`medium`/`high`는 provider 또는 normalized backend가 지원하는 경우에만 전달한다.
- `reasoning_effort` (string, optional): `none`, `low`, `medium`, `high`, `xhigh`, `max` 중 하나인 IOP 확장 field다. `none``think=false`와 같은 disable 의미로 처리한다. provider profile이 operation별 effort scale을 더 작게 선언하면 exact 또는 가장 가까운 하위 등급으로 매핑하고, 상향 매핑은 config load에서 거부한다.
- `thinking_token_budget` (int, optional): IOP 확장 thinking token budget. 0 이상이어야 한다.
- `include_reasoning` (bool, optional): OpenAI-compatible 응답에서 `reasoning_content` 노출 여부. non-provider normalized route에서는 생략하거나 `true`이면 provider reasoning delta/message를 노출할 수 있고, `false`이면 provider가 reasoning을 생성해도 response의 `reasoning_content`를 제거한다. provider-pool pure `passthrough`는 provider body 보존이 우선이며, 현재 IOP가 이 field만으로 reasoning field를 제거한다고 보장하지 않는다.
@ -331,7 +332,7 @@ Provider pool model catalog의 `models[]` entry가 generation policy를 제공
Conflict 정책:
- `reasoning_effort`가 비어 있거나 `none|low|medium|high` 외 값이면 400 에러.
- `reasoning_effort`가 비어 있거나 `none|low|medium|high|xhigh|max` 외 값이면 400 에러.
- `thinking_token_budget`가 음수이면 400 에러.
- `think=false``reasoning_effort=low|medium|high`가 함께 있으면 400 에러.
- `think=false`일 때 `thinking_token_budget`를 설정하면 400 에러.

View file

@ -1 +1 @@
1.1.199
1.1.201

View file

@ -111,5 +111,3 @@
- field 테스트 포트, artifact/bootstrap HTTP, 외부 테스트 환경: `agent-test/local/rules.md`를 따른다.
- bootstrap/install UX, Agent Bootstrap, specialized agent 등록, Control Plane enrollment: `testing` domain rule과 `agent-test/local/rules.md`를 따른다.
- 반복 작업이 확인되면 `agent-ops/skills/project/<skill-name>/SKILL.md`를 생성하고 이 표에 등록한다.
- 벤치마크 매니페스트 검증/실행/재개/상태 확인/익명 채점/리포트: `agent-ops/skills/project/iop-agent-comparison-benchmark/SKILL.md`
- 벤치마크 validate, run, resume, status, score, report 요청, 매니페스트 검증 요청, 벤치마크 실행 요청, 벤치마크 상태 확인 요청, 벤치마크 익명 채점 요청, 벤치마크 리포트 요청

View file

@ -164,6 +164,7 @@ The diff is the starting point, not the boundary. Follow behavior and API connec
Review scope control:
- Use the plan's commands and checkpoints as the primary evidence. Add one focused, possibly table-driven reproducer only when needed to prove a suspected blocking defect; do not build speculative exhaustive probe matrices.
- Exclude unrequested generalization, future-proofing, cleanup, and architectural expansion from Required/Suggested findings unless an explicit acceptance criterion or concrete failing case makes them necessary.
- Execute the applicable plan verification commands and any focused reproducer needed for the verdict. Treat implementation-owned output as a handoff and comparison source, not as a substitute for fresh reviewer verification. If recorded output is absent or insufficient but the command is available and safe in the current authorized environment, run it and repair `Verification Results` before classifying findings. If a check fails, collect enough source/runtime data to establish the root cause and one implementable fix; never emit a diagnostic-only finding that asks the next worker to investigate or choose among alternatives.
- In a follow-up review, keep Required findings within the current plan, inherited Required findings, direct regressions from the fix, and concrete violations of the original SDD or contract acceptance criteria. Exclude unrelated pre-existing work from the verdict and Required/Suggested/Nit counts; mention it only in the final report as an out-of-scope task candidate.
- Before adding a new Required that the current plan did not state, cite the exact original plan/SDD/contract criterion it violates or provide a concrete failing case. Do not require a preferred test shape when existing deterministic evidence proves the same behavior.

View file

@ -57,7 +57,7 @@ Command templates may use only `{agent}`, `{model}`, `{reasoning_effort}`, `{tar
Each route owns its ordered `candidates` plus optional `rule_id`, `policy_priority`, and `reason_codes`. A route may use catalog-owned `windows` instead of a fixed candidate list; every window supplies an IANA timezone, start/end time, and candidates. Exactly one window must match.
The bundled review routes vary model and reasoning effort by routed grade instead of fixing every review to one target: G01-G04 use Terra/high, G05-G08 use Sol/high, and G09-G10 use Sol/xhigh. Runtime or project catalog overrides may replace this default tiering.
The bundled review routes vary model and reasoning effort by routed grade instead of fixing every review to one target: G01-G04 use Terra/high, G05-G08 use Sol/medium, and G09-G10 use Sol/high. Sol/xhigh remains cataloged for explicit runtime or project overrides but is not selected by a bundled default route. Runtime or project catalog overrides may replace this default tiering.
Before work starts, the dispatcher:
@ -111,6 +111,9 @@ Accept self-check completion only when `## Implementation Checklist` or its supp
- Record the target id, opaque agent/model identity, execution class, runtime contract, catalog evidence, process identity, workspace identity, timestamps, result, and exact failure evidence.
- Treat stderr as terminal diagnostic evidence. For JSONL, recognize generic terminal event fields such as error/fatal type or severity, rejected/failed status with an error code, explicit error flags, and a non-retrying `agent_end` whose last assistant message ends with `error` or `aborted`.
- Determine liveness from PID/start-token/process-marker evidence and actual stream or native-session progress. Heartbeat mtime is never agent progress. For Codex JSONL, an unmatched `item.started` `command_execution` is an active tool interval: suspend the model-response silence timer until its matching `item.completed`, then restore normal stall detection.
- The dispatcher model-silence safety net is 70 seconds. Downstream provider runtimes should emit their bounded terminal before that deadline; do not extend the dispatcher budget per target to cover nested retries.
- Treat a confirmed provider transport terminal as the end of the current dispatch. Do not resume or automatically resend the same native session; an operator may start a fresh dispatch after the provider/runtime state is corrected.
- Retry `session-stall` only with a fresh native conversation. Preserve workspace changes and logical locator evidence, but do not carry the silent conversation context into the next attempt or a restarted dispatcher.
- Never start a duplicate attempt while owned live evidence remains.
- Keep a 10-consecutive-failure budget per task stage. Reset only that stage's budget after success.
- Preserve failed attempt logs. Delete successful attempt logs only after verified archive completion and no live evidence.

View file

@ -161,6 +161,30 @@
"terminal_success": "turn_completed"
}
},
"codex-sol-medium": {
"agent": "codex",
"model": "gpt-5.6-sol",
"reasoning_effort": "medium",
"execution_class": "cloud_model",
"selfcheck_required": false,
"runtime": {
"command": [
"codex",
"exec",
"--json",
"-C",
"{workspace}",
"-m",
"{model}",
"-c",
"model_reasoning_effort=\"{reasoning_effort}\"",
"--dangerously-bypass-approvals-and-sandbox",
"{prompt}"
],
"output_format": "jsonl",
"terminal_success": "turn_completed"
}
},
"codex-sol-high": {
"agent": "codex",
"model": "gpt-5.6-sol",
@ -315,13 +339,13 @@
]
},
"local-G09": {
"candidates": ["codex-sol-xhigh", "codex-terra-high"],
"candidates": ["codex-sol-high", "codex-terra-high"],
"rule_id": "worker-local-g09-catalog",
"policy_priority": 30,
"reason_codes": ["worker_catalog_lane"]
},
"local-G10": {
"candidates": ["codex-sol-xhigh", "codex-terra-high"],
"candidates": ["codex-sol-high", "codex-terra-high"],
"rule_id": "worker-local-g10-catalog",
"policy_priority": 30,
"reason_codes": ["worker_catalog_lane"]
@ -363,25 +387,25 @@
"reason_codes": ["worker_catalog_lane"]
},
"cloud-G07": {
"candidates": ["codex-sol-high", "codex-terra-high"],
"candidates": ["codex-sol-medium", "codex-terra-high"],
"rule_id": "worker-cloud-g07-catalog",
"policy_priority": 30,
"reason_codes": ["worker_catalog_lane"]
},
"cloud-G08": {
"candidates": ["codex-sol-high", "codex-terra-high"],
"candidates": ["codex-sol-medium", "codex-terra-high"],
"rule_id": "worker-cloud-g08-catalog",
"policy_priority": 30,
"reason_codes": ["worker_catalog_lane"]
},
"cloud-G09": {
"candidates": ["codex-sol-xhigh"],
"candidates": ["codex-sol-high", "codex-terra-high"],
"rule_id": "worker-cloud-g09-catalog",
"policy_priority": 30,
"reason_codes": ["worker_catalog_lane"]
},
"cloud-G10": {
"candidates": ["codex-sol-xhigh"],
"candidates": ["codex-sol-high", "codex-terra-high"],
"rule_id": "worker-cloud-g10-catalog",
"policy_priority": 30,
"reason_codes": ["worker_catalog_lane"]
@ -392,22 +416,22 @@
"local-G02": {"candidates": ["codex-terra-high"], "rule_id": "review-local-g02-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"local-G03": {"candidates": ["codex-terra-high"], "rule_id": "review-local-g03-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"local-G04": {"candidates": ["codex-terra-high"], "rule_id": "review-local-g04-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"local-G05": {"candidates": ["codex-sol-high"], "rule_id": "review-local-g05-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"local-G06": {"candidates": ["codex-sol-high"], "rule_id": "review-local-g06-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"local-G07": {"candidates": ["codex-sol-high"], "rule_id": "review-local-g07-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"local-G08": {"candidates": ["codex-sol-high"], "rule_id": "review-local-g08-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"local-G09": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-local-g09-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"local-G10": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-local-g10-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"local-G05": {"candidates": ["codex-sol-medium"], "rule_id": "review-local-g05-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"local-G06": {"candidates": ["codex-sol-medium"], "rule_id": "review-local-g06-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"local-G07": {"candidates": ["codex-sol-medium"], "rule_id": "review-local-g07-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"local-G08": {"candidates": ["codex-sol-medium"], "rule_id": "review-local-g08-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"local-G09": {"candidates": ["codex-sol-high"], "rule_id": "review-local-g09-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"local-G10": {"candidates": ["codex-sol-high"], "rule_id": "review-local-g10-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"cloud-G01": {"candidates": ["codex-terra-high"], "rule_id": "review-cloud-g01-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"cloud-G02": {"candidates": ["codex-terra-high"], "rule_id": "review-cloud-g02-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"cloud-G03": {"candidates": ["codex-terra-high"], "rule_id": "review-cloud-g03-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"cloud-G04": {"candidates": ["codex-terra-high"], "rule_id": "review-cloud-g04-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"cloud-G05": {"candidates": ["codex-sol-high"], "rule_id": "review-cloud-g05-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"cloud-G06": {"candidates": ["codex-sol-high"], "rule_id": "review-cloud-g06-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"cloud-G07": {"candidates": ["codex-sol-high"], "rule_id": "review-cloud-g07-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"cloud-G08": {"candidates": ["codex-sol-high"], "rule_id": "review-cloud-g08-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"cloud-G09": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-cloud-g09-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"cloud-G10": {"candidates": ["codex-sol-xhigh"], "rule_id": "review-cloud-g10-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]}
"cloud-G05": {"candidates": ["codex-sol-medium"], "rule_id": "review-cloud-g05-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"cloud-G06": {"candidates": ["codex-sol-medium"], "rule_id": "review-cloud-g06-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"cloud-G07": {"candidates": ["codex-sol-medium"], "rule_id": "review-cloud-g07-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"cloud-G08": {"candidates": ["codex-sol-medium"], "rule_id": "review-cloud-g08-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"cloud-G09": {"candidates": ["codex-sol-high"], "rule_id": "review-cloud-g09-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]},
"cloud-G10": {"candidates": ["codex-sol-high"], "rule_id": "review-cloud-g10-catalog", "policy_priority": 10, "reason_codes": ["review_catalog_lane"]}
}
}
}

View file

@ -179,7 +179,7 @@ def validated_max_parallel(value: int) -> int:
STREAM_HEARTBEAT_SECONDS = 30
MODEL_RESPONSE_STALL_SECONDS = 3 * 60
MODEL_RESPONSE_STALL_SECONDS = 70
RECOVERY_FAILURE_LIMIT = 10
GENERIC_FAILURE_LIMIT_PER_TARGET = 3
SELF_CHECK_UNCHECKED_RETRY_LIMIT = 10
@ -209,6 +209,7 @@ RUNTIME_FAILURE_PATTERNS = {
],
"provider-connection": [
r"\bprovider[_ -]?tunnel[_ -]?error\b",
r"no provider supports the required output validation capability",
(
r"(?:provider|backend|inference (?:server|endpoint))"
r".{0,160}(?:connection refused|dial tcp)"
@ -3252,7 +3253,7 @@ def native_resume_locator(
if (
not isinstance(record.get("runtime"), dict)
or not record["runtime"].get("native_session_monitor")
or record.get("failure_class") not in {"context-limit", "session-stall"}
or record.get("failure_class") != "context-limit"
or record.get("status") != "failed"
):
return None
@ -3819,7 +3820,7 @@ async def invoke(
record["native_pending_tool_call_ids"] = list(
native_state.pending_tool_call_ids
)
record["native_stall_timeout_seconds"] = None
record["native_stall_timeout_seconds"] = MODEL_RESPONSE_STALL_SECONDS
record.setdefault("native_activity_state", "starting")
if (
spec.native_resume
@ -4645,6 +4646,37 @@ async def run_escalating(
],
)
return False, locator
if failure in PROVIDER_TRANSPORT_FAILURES:
reason = (
f"{role} provider transport failure requires a fresh dispatch"
)
selected = (
current_decision.get("selected")
if isinstance(current_decision, dict)
else None
)
store.update_task(
task,
blocked=f"{reason} locator={locator}",
blocker_evidence={
"role": role,
"failure_class": failure,
"locator": str(locator) if locator else None,
"selected": selected,
"work_unit_id": current_decision.get("work_unit_id")
if isinstance(current_decision, dict)
else None,
},
)
banner(
"작업차단",
task.name,
[
"reason=provider-transport-terminal",
*failure_report_lines(failure, locator),
],
)
return False, locator
if spec.native_resume:
if failure in {"context-limit", "session-stall"}:
native_recovery_retries += 1
@ -4658,7 +4690,7 @@ async def run_escalating(
],
)
previous_locator = locator
native_resume_locator = locator
native_resume_locator = locator if failure == "context-limit" else None
await asyncio.sleep(min(30, 2 ** min(native_recovery_retries, 5)))
continue
native_recovery_retries += 1
@ -5309,7 +5341,11 @@ async def run_worker(
resume_locator: Path | None = None,
) -> None:
retry_context = store.task_state(task).get("retry_failover_context")
if resume_locator is None and isinstance(retry_context, dict):
if (
resume_locator is None
and isinstance(retry_context, dict)
and retry_context.get("failure_class") not in PROVIDER_TRANSPORT_FAILURES
):
locator_value = retry_context.get("locator")
if isinstance(locator_value, str) and locator_value:
resume_locator = Path(locator_value)

View file

@ -393,6 +393,13 @@ class RuntimeCatalogDispatcherTests(unittest.TestCase):
self.assertEqual(failure, "provider-quota")
self.assertIsNotNone(evidence)
def test_output_validation_capability_rejection_is_provider_terminal(self):
failure, evidence = dispatch.classify_failure_with_evidence(
"no provider supports the required output validation capability"
)
self.assertEqual(failure, "provider-connection")
self.assertIsNotNone(evidence)
def test_generic_json_terminal_diagnostic_has_no_agent_branch(self):
diagnostic = dispatch.terminal_diagnostic(
"opaque-agent",

View file

@ -92,18 +92,18 @@ class SelectorTests(unittest.TestCase):
},
"local-G07": ["opencode-glm-max", "codex-terra-high"],
"local-G08": ["opencode-glm-max", "codex-terra-high"],
"local-G09": ["codex-sol-xhigh", "codex-terra-high"],
"local-G10": ["codex-sol-xhigh", "codex-terra-high"],
"local-G09": ["codex-sol-high", "codex-terra-high"],
"local-G10": ["codex-sol-high", "codex-terra-high"],
"cloud-G01": ["codex-spark-xhigh", "opencode-glm-medium", "codex-terra-high"],
"cloud-G02": ["codex-spark-xhigh", "opencode-glm-medium", "codex-terra-high"],
"cloud-G03": ["opencode-glm-high", "codex-terra-high"],
"cloud-G04": ["opencode-glm-high", "codex-terra-high"],
"cloud-G05": ["opencode-glm-max", "codex-terra-high"],
"cloud-G06": ["opencode-glm-max", "codex-terra-high"],
"cloud-G07": ["codex-sol-high", "codex-terra-high"],
"cloud-G08": ["codex-sol-high", "codex-terra-high"],
"cloud-G09": ["codex-sol-xhigh"],
"cloud-G10": ["codex-sol-xhigh"],
"cloud-G07": ["codex-sol-medium", "codex-terra-high"],
"cloud-G08": ["codex-sol-medium", "codex-terra-high"],
"cloud-G09": ["codex-sol-high", "codex-terra-high"],
"cloud-G10": ["codex-sol-high", "codex-terra-high"],
}
expected_targets = {
"pi-ornith-high",
@ -111,6 +111,7 @@ class SelectorTests(unittest.TestCase):
"opencode-glm-high",
"opencode-glm-max",
"codex-spark-xhigh",
"codex-sol-medium",
"codex-sol-high",
"codex-sol-xhigh",
"codex-terra-high",
@ -137,9 +138,9 @@ class SelectorTests(unittest.TestCase):
expected = (
["codex-terra-high"]
if grade <= 4
else ["codex-sol-high"]
else ["codex-sol-medium"]
if grade <= 8
else ["codex-sol-xhigh"]
else ["codex-sol-high"]
)
decision = selector.policy.select_policy(
catalog=catalog,
@ -196,6 +197,9 @@ class SelectorTests(unittest.TestCase):
'model_reasoning_effort="{reasoning_effort}"',
terra.runtime["command"],
)
sol_medium = catalog.targets["codex-sol-medium"]
self.assertEqual(sol_medium.reasoning_effort, "medium")
self.assertEqual(sol_medium.runtime["terminal_success"], "turn_completed")
sol_high = catalog.targets["codex-sol-high"]
self.assertEqual(sol_high.reasoning_effort, "high")
self.assertEqual(sol_high.runtime["terminal_success"], "turn_completed")

View file

@ -195,6 +195,7 @@ Before choosing plan files or task directory names, apply the split decision pol
Complete all items below before creating active plan/review files. Work through them in order; do not proceed to the next step until every checkbox is done. Keep the user request as the scope anchor and reconcile derived acceptance conditions before the split decision; do not create a separate routing summary. In `prepare-follow-up`, treat the reviewer's closed finding packet as the decision authority: repository reads validate its consistency and supply implementation mechanics, but do not reopen root cause or solution selection. If required evidence, root cause, or a selected fix is missing or contradicted, return `needs_evidence` to code-review so the reviewer corrects it in the same review pass; never pass investigation or alternatives to the worker. The only allowed file edits before writing plan/review files are local `agent-roadmap/current.md` creation or `.gitignore` block repair needed for roadmap routing.
- [ ] **Resolve verification context** — because implementation plans include verification, consume supplied `verification_context` when present and confirm its source paths, commands, expected results, preconditions, constraints, gaps, and confidence still apply. On first pass, derive missing facts from repository manifests, scripts, workflows, domain rules, related tests, user-provided environment facts, and safe read-only probes. In `prepare-follow-up`, require the reviewer to have collected every fact needed for diagnosis and fix selection; derive only mechanical command/path details, and return `needs_evidence` rather than performing missing review analysis. Record which facts came from the handoff and which came from repository-native validation. A missing optional first-pass handoff is not a user-review blocker.
- [ ] **Keep the plan minimal** — choose the smallest change that satisfies the stated goal and required acceptance criteria. Reuse existing structure; exclude unrequested generalization, future-proofing, cleanup, and architectural expansion.
- [ ] **Read all source files in full** — read every source file the change will touch, whole file. No partial reads.
- [ ] **Preflight external verification** — when any required verification leaves the current checkout, including remote runner, field/bootstrap, external provider, Docker/code-server, emulator/device, or shared long-running runtime, confirm or derive a read-only preflight before writing final verification commands. Record runner, repo root/workdir, branch/HEAD/dirty state, source sync status, binary/artifact paths, command help/version output needed by the verification, config path, runtime identity, ports/process state, external hosts, and OS/arch assumptions. If the preflight shows stale artifacts, dirty/divergent checkout, wrong identity, missing command, closed ports, host OS mismatch, or unsynced source, add an explicit setup/sync/rebuild step or report the blocker.
- [ ] **Read all test files in full** — read every test file that exercises the changed behavior, including files identified by the verification context and repository test layout.

View file

@ -140,19 +140,19 @@ dev-runtime provider pool을 `dev` 기준 git-flow release로 배포한다. `dev
- 각 node의 `provider_snapshots`에서 provider `id`, `capacity`, `in_flight`, `queued`, `health`, `served_models`를 확인한다.
- `/v1/models`가 대상 model alias를 노출하는지 확인한다.
9. **OpenAI-compatible capacity smoke**
- `/v1/responses``/v1/chat/completions`를 각각 검증한다. legacy `/v1/completions`는 구현되어 있지 않으면 실패로 보지 않는다.
- 표준 부하 프롬프트는 짧은 토큰 응답을 요구하지 않는다. 700~1200 token 수준의 구조화된 답변을 유도해 요청이 동시에 관측될 시간을 만든다.
- endpoint별로 선택한 model group의 총 provider capacity + 1개 요청을 동시에 보낸다. 현재 Laguna `laguna-s:2.1`은 GX10 capacity `4`이므로 5개, Ornith `ornith:35b`는 OneXPlayer `3` + RTX5090 `1`이므로 5개, Qwen `qwen3.6:35b`는 mac-mlx-vllm `2`이므로 3개 동시 호출이다.
- 환경 문서에 더 작은 aggregate smoke 기준이 남아 있더라도, 배포 완료 판정은 이 capacity+1 기준을 우선한다.
- 요청 실행 중 Control Plane status를 반복 polling하여 대상 provider들의 `in_flight` 합이 총 capacity에 도달하고 `queued` 합이 1 이상이 되는 순간을 증거로 남긴다.
- 각 provider의 `in_flight`가 자기 capacity를 넘지 않고, 적어도 한 번은 기대 capacity까지 차는지 확인한다.
- 모든 요청 완료 후 같은 status에서 대상 provider들의 `in_flight=0`, `queued=0`으로 돌아오는지 확인한다.
- Qwen과 Laguna reasoning/thinking 텍스트는 정상 응답으로 허용한다. Laguna think smoke는 같은 요청의 Pi `high`에서 `thinking_start`/`thinking_delta`/`thinking_end`, `off`에서 thinking event 0개와 최종 text를 대조한다. agentic smoke는 tool-call 전후 reasoning, tool result, 최종 text를 모두 확인한다. exact-output match를 smoke 성공 기준으로 삼지 않는다.
- Pi/Cline형 agent/tool-call 경계를 검증할 때는 forced tool call, auto tool call, streaming `delta.tool_calls`, multi-turn tool result 후 최종 답변을 provider direct와 Edge OpenAI-compatible 경로에서 나눠 확인한다. raw native marker나 reasoning text가 assistant content로 새면 해당 model/runtime의 parser/template profile 미확정으로 보고한다.
9. **Run the OpenAI-compatible capacity smoke**
- Qualify `/v1/responses` and `/v1/chat/completions` separately. Do not fail solely because the unimplemented legacy `/v1/completions` endpoint is absent.
- Use a short request that asks for a structured 700-1200-token answer and explicitly bounds provider-native thinking. Compute the emitted JSON Unicode rune count, `runes/4 + runes/16` estimate, and context class. Do not reuse long-context or repetition fixtures as a normal-capacity oracle.
- In managed mode, authenticate `GET /v1/credentials/routes` with the same principal token used at OpenAI ingress. Require one active exact route alias, then intersect its `resource_selector`, profile, and upstream model with one healthy connected provider snapshot.
- Run `scripts/e2e-openai-managed-capacity-smoke.sh` for one endpoint and route at a time. Require the emitted request to classify as `normal`, then send the selected provider's `capacity` plus one request. If it classifies as `long`, fail this normal-capacity gate and use the separate long-context admission smoke with `long_context_capacity`; never add capacity from a provider excluded by the authenticated route.
- Keep current projected Ornith cases separate: qualify `ornith:35b` only through `onexplayer-lemonade`, and qualify `ornith-fast` only through `rtx5090-lemonade`. Run Chat and Responses independently for each alias. Do not mutate routes, capacities, or timeout values to make the smoke pass.
- Require HTTP 200 for every request, exactly one endpoint-native success terminal and one `[DONE]` per stream, selected-provider peak `in_flight` equal to eligible capacity, `queued >= 1`, and selected final counters `0/0`. Treat a missing route match, changed capacity, wrong context class, missing status sample, or incomplete terminal as a fail-closed release blocker.
- Every invocation must allocate a distinct mode-0700 directory under ignored `agent-test/runs/**`. Accept only request/result/status files bound to that invocation's manifest and timestamps. Preserve raw routes, request bodies, response bodies, curl errors, token/header values, route ids, slot ids, prompts, and output only inside that directory; retain only the script's allowlisted sanitized summary as task or tracked evidence.
- For unprojected or legacy provider pools, retain the existing model-group capacity rule. Current baselines are Laguna `laguna-s:2.1` on GX10 capacity `4` (five requests) and Qwen `qwen3.6:35b` on mac-mlx-vllm capacity `2` (three requests).
- Allow Qwen and Laguna reasoning/thinking text as normal output. For Laguna, compare Pi `high` thinking events with `off` zero-thinking events and final text. For agent/tool-call qualification, test forced, automatic, streaming, and multi-turn tool calls separately from capacity qualification.
10. **git-flow release finish와 tag 반영**
- 빌드 전·후 테스트, 배포 후 연결 검증, `/v1/responses``/v1/chat/completions` capacity smoke가 모두 성공했는지 다시 확인한다.
- 빌드 전·후 테스트, 배포 후 연결 검증, `/v1/responses``/v1/chat/completions` capacity smoke가 모두 성공했는지 다시 확인한다. Managed route가 있으면 `scripts/e2e-openai-managed-capacity-smoke.sh`의 네 Ornith route/endpoint case와 current-run provenance gate가 모두 통과해야 한다.
- 하나라도 실패했거나 필수 검증이 실행되지 않았으면 finish하지 않고 `release/dev-<count>` branch를 유지한다.
- 현재 release HEAD가 `DEPLOY_SHA`와 같은지 확인한다. 달라졌으면 배포 산출물과 source가 달라진 것이므로 finish하지 않는다.
- finish 직전에 `git fetch origin dev main --tags`를 다시 실행하고 `origin/dev=DEV_BASE_SHA`, `origin/main=MAIN_BASE_SHA`, remote tag 없음이 모두 유지되는지 확인한다. 하나라도 달라졌으면 finish하지 않고 release branch를 유지한다.

View file

@ -1,211 +0,0 @@
---
name: iop-agent-comparison-benchmark
description: Recognize benchmark validate/preflight/run/resume/status/score/report requests, delegate supported operations to the deterministic CLI, and fail closed at execution or scoring blockers.
---
# iop-agent-comparison-benchmark
## Purpose
Route agent comparison benchmark requests to the deterministic CLI while enforcing append-only evidence, no-substitution, capability, and safety boundaries. The skill is routing and documentation, not a second implementation or orchestration dispatcher.
## When to use
- User requests benchmark manifest validation: `validate`, `validate manifest`, `manifest 검증`
- User requests route readiness: `preflight`, `preflight benchmark`, `연결 사전 점검`
- User requests benchmark execution: `run`, `run benchmark`, `벤치마크 실행`, `시작해`
- User requests benchmark resume: `resume`, `resume benchmark`, `재개`, `계속해`
- User requests benchmark status: `status`, `status benchmark`, `상태 확인`, `어디까지 왔어`
- User requests blind scoring: `score`, `score benchmark`, `익명 채점`, `품질 채점`
- User requests report or output: `report`, `report output`, `결과 보고`, `리포트`, `리포트 보여줘`
## Inputs
- `manifest`: Path to the benchmark manifest JSON file. (required for validate, preflight, run, resume, status, score, report)
- `run_id`: Harness-generated run id. (required for resume, status, score, report)
- `retry_failed`: Boolean flag for resume. (optional, default: false)
- `retry_scoring_failed`: Boolean flag for score. (optional, default: false)
## Preflight
- [ ] Confirm the request matches one of the supported trigger cases above.
- [ ] For validate/preflight/run/resume/status/score/report: confirm a manifest path is provided. If missing, return `error: manifest path is required`.
- [ ] For resume/status/score/report: confirm a harness-issued run id is provided. If missing, return `error: run id is required`.
- [ ] Confirm the CLI exists: `scripts/agent_comparison_benchmark.py` is present at the repo root.
- [ ] Confirm the manifest file exists and is readable before delegating.
## Procedure
1. **Classify the request**
- Map the user request to one of: `validate`, `preflight`, `run`, `resume`, `status`, `score`, `report`.
- If the request does not match any trigger, report that the benchmark pipeline skill does not cover the request and route to the appropriate skill.
2. **Delegate report to the CLI**
- Run: `python3 scripts/agent_comparison_benchmark.py report --manifest <manifest-path> --run-id <run-id>`
- The CLI opens immutable run state and delegates to the strict reporter with no caller adapter path.
- On exit 0, report `ok: report run_id=<run-id> path=<run-relative-path>`.
- On exit 69, report `error: benchmark report is unavailable` from stderr.
3. **Delegate validate to the CLI**
- Run: `python3 scripts/agent_comparison_benchmark.py validate --manifest <manifest-path>`
- Report the CLI exit code and stdout/stderr verbatim.
- On exit 0, report `ok: manifest is valid`.
- On exit 69, report the validation error from stderr.
- On exit 64, report the usage error from stderr.
4. **Delegate preflight to the CLI**
- Run: `python3 scripts/agent_comparison_benchmark.py preflight --manifest <manifest-path>`
- The CLI records one fresh live observation for every immutable matrix cell, including direct and execution-preset routes, in canonical matrix order in one append-only run record.
- On exit 0, report the exact closed `ready` summary from stdout.
- On exit 69, report the exact `registration_required` or `implementation_gap` summary from stderr and stop. Never bypass the blocker, substitute a route/model/effort, or treat local manifest validation as live readiness.
5. **Delegate run to the CLI**
- Run: `python3 scripts/agent_comparison_benchmark.py run --manifest <manifest-path>`
- On missing or invalid manifest, the CLI prints `error: benchmark state is unavailable` to stderr with exit 69 (or `error: invalid usage` with exit 64) before creating execution state.
- The CLI creates one run and uses its single writer to append a fresh all-cell preflight before attempt allocation.
- On `registration_required` or `implementation_gap`, it prints `error: preflight blocked ...` to stderr with exit 69, allocates no attempt, and preserves the run id for a later resume.
- On `ready`, it binds the exact caller, cell, fresh workspace, session, and attempt identity, then must invoke each eligible cell exactly once with the fixture task.
- Exit 0 when every manifest slot has complete terminal evidence (`unresolved=0`); independent failure counts remain in stdout and are classified by `score`. Exit 69 only for preflight blockers or incomplete evidence (absent/running slots). Never performs an implicit retry of a failed gate.
6. **Delegate resume to the CLI**
- Run: `python3 scripts/agent_comparison_benchmark.py resume --manifest <manifest-path> --run-id <run-id> [--retry-failed]`
- On missing or invalid manifest or state, the CLI prints `error: benchmark state is unavailable` to stderr with exit 69 (or `error: invalid usage` with exit 64) before changing the run.
- The CLI opens the exact immutable run and uses its single writer to append a fresh all-cell preflight before attempt allocation.
- On `registration_required` or `implementation_gap`, it prints `error: preflight blocked ...` to stderr with exit 69 and allocates no attempt.
- On `ready`, it reconciles interrupted state, skips only slots whose latest product/harness/process/artifact gates all pass, preserves prior attempt bytes, and allocates a new attempt only for eligible work. `--retry-failed` admits a new attempt for any latest terminal attempt whose independent gates do not all pass.
- Exit 0 when every manifest slot has complete terminal evidence (`unresolved=0`); independent failure counts remain in stdout and are classified by `score`. Exit 69 only for preflight blockers or incomplete evidence. Never performs an implicit retry of a failed gate.
- It must invoke each eligible cell exactly once with a new workspace and session identity.
7. **Delegate status to the CLI**
- Run: `python3 scripts/agent_comparison_benchmark.py status --manifest <manifest-path> --run-id <run-id>`
- On missing or invalid manifest or state, the CLI prints `error: benchmark state is unavailable` to stderr with exit 69 (or `error: invalid usage` with exit 64).
- On success, the CLI prints the controller counts, product/harness/process/artifact counts, and `unresolved=<count>` to stdout with exit 0.
- Report the exact CLI output.
8. **Delegate score to the CLI**
- Run: `python3 scripts/agent_comparison_benchmark.py score --manifest <manifest-path> --run-id <run-id> [--retry-scoring-failed]`
- The CLI classifies each failed product, harness, process, or required artifact gate with its own immutable `unscored` reason, without invoking the evaluator or assigning zero.
- Eligible attempts receive an opaque blind workspace, one manifest-bound fresh Codex evaluator session, and the exact immutable manifest-selected rubric from the closed supported catalog (`landing-quality-v1`, `one-shot-agent-comparison-v1`); no substitute rubric or reinterpretation is permitted.
- A prior `scored` result is terminal. A prior `scoring_failed` result is retried only with `--retry-scoring-failed`, which allocates a new score id and preserves every prior byte.
- On exit 0, report the exact closed `scored`, `unscored`, `scoring_failed`, and `blocked` counts from stdout.
- On exit 69, report the exact closed counts or unavailable-state line from stderr. Never substitute evaluator route/model/effort, fabricate a worksheet, or turn failure into zero.
9. **Report the result**
- Report the command executed, the exact CLI exit code, and the full stdout/stderr.
- Do not summarize, paraphrase, or fabricate CLI output.
## Validation
- [ ] The CLI command was executed and the exit code matches the documented contract.
- [ ] The reported stdout/stderr matches the CLI output exactly.
- [ ] Preflight evidence is append-only, covers every immutable matrix cell in canonical order, and uses only closed status/count fields.
- [ ] A preflight blocker created no scored attempt and was not bypassed.
- [ ] An ineligible execution attempt became `unscored` without an evaluator invocation or a zero score.
- [ ] Product, harness, process, and artifact outcomes remain separately visible in run/resume/status output and report rows.
- [ ] Each eligible score id used one opaque blind workspace and one fresh evaluator session; retry preserved prior bytes and used a new id.
- [ ] `scoring_failed` used no fallback, synthetic worksheet, or implicit retry.
- [ ] No caller or provider was invoked outside the deterministic CLI.
- [ ] No report or output was fabricated for report requests; the CLI produced the deterministic artifact.
- [ ] No public `prepare` operation was exposed or referenced.
- [ ] Run/resume exit 0 requires `unresolved=0` (every slot has complete terminal evidence); independent failure axes remain visible and are classified by `score`.
- [ ] Retry is explicit only (`--retry-failed`); a failed terminal gate is never reinterpreted as success.
- If validation fails, report the mismatch and stop without fallback.
## Output format
For validate/run/resume/status:
```
command: <validate|run|resume|status>
exit_code: <int>
stdout: <verbatim CLI stdout or "(none)">
stderr: <verbatim CLI stderr or "(none)">
```
For preflight:
```
command: preflight
exit_code: <0|69>
stdout: <verbatim closed summary or "(none)">
stderr: <verbatim closed summary or "(none)">
```
For score:
```
command: score
exit_code: <0|69>
stdout: <verbatim closed score summary or "(none)">
stderr: <verbatim closed score summary or "(none)">
```
For report:
```
command: report
exit_code: <0|69>
stdout: <verbatim CLI stdout or "(none)">
stderr: <verbatim CLI stderr or "(none)">
```
For run/resume ready completion:
```
command: <run|resume>
exit_code: 0
stdout: ok: <run|resume> run_id=<run-id> executed=<count> unresolved=0 completed=<retained-count> timed_out=<retained-count> cancelled=<retained-count> interrupted=<retained-count> running=0 product_succeeded=<count> product_failed=<count> product_unknown=<count> harness_passed=<count> harness_failed=<count> process_exited=<count> process_signalled=<count> process_timed_out=<count> process_cancelled=<count> process_not_started=<count> artifact_passed=<count> artifact_failed=<count> artifact_blocked=<count> artifact_not_run=0
stderr: (none)
```
Note: `unresolved=0` means every manifest slot has complete terminal evidence (web validation present). Independent failure axes (`product_failed`, `artifact_failed`, etc.) remain visible in the summary and are classified by `score` as `unscored` without invoking the evaluator or assigning zero.
For run/resume blocker or execution failure:
```
command: <run|resume>
exit_code: 69
stdout: (none)
stderr: <verbatim closed preflight or execution failure summary>
```
## Safety rules
- The skill delegates every supported stateful operation verbatim to `scripts/agent_comparison_benchmark.py`. It does not reproduce pipeline policy in prose.
- Durable run/attempt state and preflight evidence are persisted only under the validated run root (`agent-test/runs/<output-id>/<run-id>/`).
- Caller sessions, output workspaces, and caches are fresh and isolated for every cell, repetition, and attempt; session or cache state is never shared within a run or across runs.
- Read-only testbed/fixture inputs (such as `../iop-s2`) are not copied back or mutated; no writes occur outside the validated run root.
- Preflight never allocates a scored attempt. Every immutable matrix cell requires its own fresh live observation.
- Run/resume append a fresh all-cell preflight under the run writer before any attempt allocation; a blocker allocates no attempt.
- Ready execution binds one exact cell and immutable attempt identity to one fresh workspace/session and one task submission.
- Product, harness, process, and artifact are independent gates. Controller state `completed` only means the invocation controller reached a terminal state.
- Resolution (`unresolved=0`) requires every manifest slot to have complete terminal evidence (web validation present); it does not require every gate to pass. Failed gates remain visible as independent failure counts and are classified by `score` as `unscored`.
- Release qualification runs the five-cell direct manifest as one unscored canary and requires fresh `ready=5`, exactly five fresh attempts, `unresolved=0`, `running=0`, `interrupted=0`, terminal controller/product/harness/process/web-validation evidence for every slot, and no exhausted browser/CDP infrastructure block before a fresh nine-cell preflight. Product failure, upstream HTTP rejection, generated-missing after caller failure, and timeout remain measured outcomes and do not trigger an implicit retry. Edge pre-ingress incompatibility or an exhausted browser/CDP infrastructure block stops qualification; this step does not allocate hybrid or scored execution.
- Scoring copies only anonymous generated files, two local images, and screenshots into an opaque run-owned blind tree; the identity mapping remains outside that tree.
- Scoring records `unscored`, `scored`, and `scoring_failed` append-only, and a retry always allocates a fresh score id/session.
- The internal workspace API (`RunStore`, `Manifest`, etc.) is not a user command. Do not expose it.
## Stop conditions
- Stop immediately on a preflight `registration_required` or `implementation_gap` result. Do not substitute an alias, change an effort, or continue to attempt allocation.
- Stop immediately on a run/resume preflight blocker without allocating an attempt or invoking another execution path.
- Do not resume or retry a retained execution failure unless the user explicitly requests resume with `--retry-failed`. A release-qualification run with complete terminal evidence may continue to the fresh nine-cell preflight under the Safety rules without retrying or reinterpreting the failed result.
- Stop after `scoring_failed` unless the user explicitly requests score with `--retry-scoring-failed`.
- Stop immediately and report the unavailable-state line for report requests that cannot project the deterministic report.
- Stop immediately if the manifest path is missing or the file is not readable.
- Stop immediately if the run id is missing for resume/status.
- Stop without fallback, fabricated evidence, ad-hoc provider calls, subagents, or orchestration dispatchers.
## Prohibitions
- Do not expose a public `prepare` operation.
- Do not invoke a caller or provider outside the deterministic benchmark CLI.
- Do not bypass a preflight blocker or substitute caller, route, model, effort, or preset.
- Do not retry scoring implicitly, replace the manifest evaluator, fabricate a worksheet, or convert `unscored`/`scoring_failed` to zero.
- Do not claim execution-preset fixture validation as live readiness.
- Do not use subagents, orchestration dispatchers, or dispatch.py-equivalent tools for benchmark execution.
- Do not fabricate benchmark results, reports, or output.
- Do not reproduce pipeline policy, state machine, or allocation logic in prose.
- Do not allow user override of the read-only `../iop-s2` testbed provenance.
- Do not write output or ad-hoc state outside the validated run root (`agent-test/runs/<output-id>/<run-id>/`).
- Do not share session or cache state within a run across cells, repetitions, or attempts, or across run invocations.

View file

@ -0,0 +1,56 @@
# Milestone: [bench-02] IOP 원샷 Agent 모델 비교 벤치마크
## 위치
- Roadmap: [ROADMAP.md](../../../../ROADMAP.md)
- Phase: [PHASE.md](../../../../phase/knowledge-tool-optimization-extension/PHASE.md)
- SDD: [SDD.md](../../../sdd/knowledge-tool-optimization-extension/iop-one-shot-agent-model-comparison/SDD.md)
## 목표
동일한 정적 웹 과제를 9개 IOP 경유 caller/model 조합으로 실행해 속도·usage·품질을 비교하는 전용 benchmark를 수행하려 했다.
## 상태
[폐기]
## 구현 잠금
- 상태: 잠금
- SDD: 필요
- SDD 문서: [폐기된 SDD](../../../sdd/knowledge-tool-optimization-extension/iop-one-shot-agent-model-comparison/SDD.md)
- SDD 상태: 폐기
- SDD 잠금: 잠금
- SDD 사용자 리뷰: 없음
- 결정 필요: 없음
## 범위
- 전용 manifest/runner, 격리·재개·복구, 자동 웹 검증, 익명 채점과 deterministic report를 사용한 C01-C09 비교였다.
- 2026-08-13 사용자 결정으로 제품 안정성과 측정 경계를 분리하기 위해 전용 harness와 함께 현재 실행 범위에서 철회했다.
## 기능
### Epic: [benchmark-rollback] 전용 측정 경계 폐기
- [x] [harness-retirement] 전용 benchmark 구현, 공식 CLI/skill, manifest/schema, 자동 검증·채점·보고와 현재 living spec을 제거했다.
- [x] [milestone-retirement] 제품 안정성 전수 재검증을 이 측정 Milestone의 대체 작업으로 만들지 않고, 구체적 결함만 해당 제품 소유 영역에서 처리하도록 분리했다.
## 완료 리뷰
- 상태: 폐기
- 요청일: 2026-08-13
- 완료 근거: 사용자 요청과 전용 harness 삭제 diff
- 검토 항목: 없음
- 리뷰 코멘트: 기존 2/9 scored run과 후속 복구 계획은 완료 근거가 아니며 재개하지 않는다.
## 범위 제외
- IOP 제품 ingress, route/preset, stream terminal과 공식 caller 호환 코드의 롤백
- 구체적 제품 결함의 소유 영역별 수정과 회귀 검증
## 작업 컨텍스트
- 폐기 사유: 측정 시스템의 정합성과 복구가 제품 안정성보다 우선되는 목적 역전
- 후속 제품 Milestone: 없음. 현재 IOP 호출부를 기준선으로 유지하고 구체적 회귀만 국소 처리한다.
- 복구 가능성: 삭제된 구현과 이전 문서는 Git 이력에서 복구할 수 있다.

View file

@ -0,0 +1,80 @@
# SDD: IOP 원샷 Agent 모델 비교 벤치마크
## 위치
- Milestone: [폐기된 Milestone](../../../phase/knowledge-tool-optimization-extension/milestones/iop-one-shot-agent-model-comparison.md)
- Phase: [PHASE.md](../../../../phase/knowledge-tool-optimization-extension/PHASE.md)
## 상태
[폐기]
## SDD 잠금
- 상태: 잠금
- 사용자 리뷰: 없음
- 잠금 항목:
- 없음. 설계 자체를 폐기했다.
## 문제 / 비목표
- 문제: 전용 측정 harness의 lifecycle, 복구, 증거와 채점 정합성이 IOP 제품과 caller/model 호출 안정성보다 우선되는 목적 역전이 발생했다.
- 비목표:
- 이 SDD를 제품 안정성 설계로 재사용한다.
## Source of Truth
| 영역 | 기준 | 메모 |
|------|------|------|
| Roadmap | [폐기된 Milestone](../../../phase/knowledge-tool-optimization-extension/milestones/iop-one-shot-agent-model-comparison.md) | 폐기 결정 원장 |
| User Decision | 2026-08-13 하네스 폐기와 경계 분리 지시 | 측정 설계 철회 근거 |
## State Machine
| 상태 | 진입 조건 | 다음 상태 | 근거 |
|------|-----------|-----------|------|
| `retired` | 전용 harness와 Milestone 폐기 | 종료 | 삭제 diff와 archive 상태 |
## Interface Contract
- 계약 원문: 없음
- 입력:
- 없음
- 출력:
- 없음
- 금지:
- 이 폐기된 SDD를 현재 실행 또는 완료 조건으로 사용한다.
## Acceptance Scenarios
| ID | Milestone Task | Given | When | Then |
|----|----------------|-------|------|------|
| S01 | `harness-retirement` | 전용 측정 구현과 공식 포인터 | 폐기 반영 | 활성 구현과 라우팅에서 제거된다. |
| S02 | `milestone-retirement` | 제품과 측정이 섞인 현재 Milestone | 경계 분리 | 측정 Milestone은 폐기되고 전수 안정성 대체 Milestone을 만들지 않는다. |
## Evidence Map
| Scenario | Required Evidence | `agent-task` 연결 | 완료 Evidence 기대 |
|----------|-------------------|------------------|---------------------------|
| S01 | 삭제 diff와 dead-reference 검색 | 해당 없음 | 전용 harness 표면 없음 |
| S02 | archive, current, Phase와 queue diff | 해당 없음 | 대체 전수 검증 gate 없음 |
## Cross-repo Dependencies
- 폐기된 `[bench-02]`를 가리키던 workspace lock은 제거하며 대체 안정성 dependency를 만들지 않는다.
## Drift Check
- [x] Milestone의 폐기 상태와 일치한다.
- [x] 현재 제품 계약을 이 SDD에 복제하지 않았다.
- [x] 사용자 결정이 반영됐다.
- [x] 전수 재검증을 요구하는 대체 Milestone과 실행 차단을 만들지 않았다.
## 사용자 리뷰 이력
- 2026-08-13: 사용자가 전용 harness 폐기, benchmark Milestone 롤백, IOP 제품 안정성 우선과 측정 경계 분리를 확정했다.
## 작업 컨텍스트
- 표준선: 폐기된 측정 상태 머신과 완료 조건을 후속 제품 작업에서 재사용하지 않는다.
- 후속 SDD: 없음. 구체적 결함은 해당 제품 소유 영역에서 국소 처리한다.

View file

@ -12,7 +12,7 @@ Ollama serving 경로와 운영 기반이 안정화된 뒤, execution preset,
cloud-first route evidence가 충분히 쌓이면 동일한 mode decision contract를 쓰는 RAG 기반 local routing model을 shadow/canary로 검증해 운영 기본 경로로 점진 전환한다.
caller-neutral 누적 요청 컨텍스트 최적화, repository 장기 기억 RAG, advisor와 Context Hook은 routing evidence RAG와 서로 다른 후속 기능으로 분리한다.
이 Phase는 특정 Agent Shell에 종속되지 않고 OpenAI-compatible, A2A, IOP native protocol 중 맞는 표면에서 공통 최적화 책임을 제공하는 방향을 다룬다.
단일 요청 Agent 실행의 정식 smoke 이후 비교 검증은 별도 benchmark lane에서 수행하며, 준비 pipeline은 병렬 구축하고 실제 scored 비교는 route-02 완료 뒤 실행한다.
단일 요청 Agent 실행은 현재 완료된 제품 상태를 기준선으로 유지한다. 구체적 결함이 재현되면 해당 제품 소유 영역에서 국소 수정·검증하며, 측정 도구는 제품 완료 조건이나 실행 차단을 소유하지 않는다.
## Milestone 흐름
@ -57,9 +57,21 @@ Phase를 가로지르는 실제 다음 작업 선택은 [전역 마일스톤 실
- 경로: [[bench-01] Agent 비교 벤치마크 파이프라인 준비](../../archive/phase/knowledge-tool-optimization-extension/milestones/agent-comparison-benchmark-pipeline.md)
- 요약: 모델·caller·prompt·반복 횟수를 manifest로 바꾸고 Claude Code, agy, Codex의 IOP 연결부터 finish/idle, 시간·token·웹 검증·익명 채점·Markdown 보고까지 같은 pipeline으로 재현한다.
- [진행중] [bench-02] IOP 원샷 Agent 모델 비교 벤치마크
- 경로: [[bench-02] IOP 원샷 Agent 모델 비교 벤치마크](milestones/iop-one-shot-agent-model-comparison.md)
- 요약: route-02 정식 smoke와 benchmark pipeline 준비 뒤 dev `../iop-s2`에서 동일 정적 웹 fixture로 9개 IOP 경유 단독·하이브리드 caller 조합을 각각 한 번 실행해 속도·token·품질을 비교한다.
- [폐기] [bench-02] IOP 원샷 Agent 모델 비교 벤치마크
- 경로: [[bench-02] IOP 원샷 Agent 모델 비교 벤치마크](../../archive/phase/knowledge-tool-optimization-extension/milestones/iop-one-shot-agent-model-comparison.md)
- 요약: 전용 harness의 정합성과 복구가 제품 안정성보다 우선되는 목적 역전으로 2026-08-13 폐기했다. 기존 결과와 계획은 재개하지 않는다.
- [진행중] [bench-route-01] 벤치 경로 최소 HTML 스모크
- 경로: [[bench-route-01] 벤치 경로 최소 HTML 스모크](milestones/benchmark-route-minimal-html-smoke.md)
- 요약: 벤치에 사용할 9개 caller/model/route 조합을 고정된 최소 `index.html` 생성 요청으로 한 번씩 직접 호출하고, 실패한 경로만 귀속·국소 수정·재검증한다.
- [계획] [bench-lite-01] 초경량 Agent 모델 비교
- 경로: [[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)
@ -108,6 +120,6 @@ Phase를 가로지르는 실제 다음 작업 선택은 [전역 마일스톤 실
- plan-bearing one-shot mode는 IOP Node가 승인된 workspace root 아래 `.iop/job/<request_id>/plan.md``review.md`를 직접 생성·읽기·갱신·정리한다. 내부 model tool call/result는 IOP coordinator가 소비하며 Claude에 후속 tool result 요청을 요구하지 않는다.
- 각 stage의 routing, plan, work, review, defect와 repair는 outer stream에 redacted 진행 요약으로만 투영한다. 내부 provider reasoning, control prompt, tool protocol·argument/result, credential과 stage terminal은 공개하지 않고 최종 사용자 결과와 outer terminal만 완결된 응답으로 반환한다.
- target agent나 외부 workflow 제품의 process/state를 실행 의존성으로 연결하지 않는다. 범용 interactive shell과 장기 workflow는 제외하지만, execution preset의 request-scoped workspace/tool executor는 IOP가 소유한다.
- benchmark skill/pipeline은 제품 coordinator가 아니라 dev 검증 harness다. 준비 작업은 route-02와 병렬일 수 있지만 실제 scored 비교는 route-02 정식 기능·필수 smoke와 benchmark pipeline 완료 뒤 별도 Milestone에서 수행한다.
- 공식 caller 경로는 현재 제품 기준선을 유지한다. 벤치 준비에서는 벤치 대상 경로만 최소 HTML로 얕게 확인하고, 구체적 회귀가 발생한 경로만 해당 제품 소유 영역에서 검증한다. 전수 재검증 campaign이나 측정 도구를 제품 완료 gate로 두지 않는다.
- cloud model은 초기 semantic judge/teacher 역할을 하고, 충분한 정제 evidence가 쌓인 뒤 RAG local router로 운영 기본을 전환한다. 두 경우 모두 최종 권한은 deterministic hard gate를 적용하는 Edge arbiter에 남는다.
- routing evidence RAG는 route 판정 전용이고, repository 장기 기억 RAG·누적 요청 context·advisor·Context Hook과 corpus/index/평가를 공유하지 않는다.

View file

@ -0,0 +1,79 @@
# Milestone: [bench-route-01] 벤치 경로 최소 HTML 스모크
## 위치
- Roadmap: [ROADMAP.md](../../../ROADMAP.md)
- Phase: [PHASE.md](../PHASE.md)
## 목표
벤치마크에 사용할 caller/model/route 조합만 고정된 최소 HTML 생성 요청으로 빠르게 확인한다.
IOP 전체 안정성을 처음부터 재검증하지 않고, 실패가 재현된 경로만 제품·caller·provider·환경 중 한 경계에 귀속해 국소 수정한 뒤 그 경로만 다시 확인한다.
## 상태
[진행중]
## 구현 잠금
- 상태: 해제
- SDD: 불필요
- SDD 문서: 없음
- SDD 사유: 기존 공개 호출 경로를 임시 workspace에서 수동 확인하는 test-only 작업이며 API, wire, config, schema, lifecycle 또는 retry 계약을 새로 만들지 않는다.
- 결정 필요: 없음
## 범위
- 아래 9개 기존 벤치 대상 조합을 순서와 무관하게 한 경로씩 직접 실행한다.
- Claude Code → Claude direct
- Claude Code → Gemini direct
- agy → Gemini direct
- Claude Code → GPT direct
- Codex → GPT direct
- Claude Code → Gemini execution preset
- agy → Gemini execution preset
- Claude Code → GPT execution preset
- Codex → GPT execution preset
- 모든 경로에 같은 구현 요구를 사용한다: 외부 asset과 JavaScript 없이 exact marker가 있는 단일 `index.html`을 구현하고 종료한다.
- 각 경로는 최초 1회만 실행하고 120초 안에 terminal을 확인한다. direct 경로는 caller workspace의 `index.html`, Edge-owned execution preset은 비공개 workspace 정리 계약 때문에 caller 로컬 파일이 아니라 caller-visible `decision.output`의 완료·marker를 판정한다.
- 실패하면 자동 retry나 전체 재실행을 하지 않는다. sanitized terminal/error와 IOP request stage만 확인해 소유 경계를 정하고, 변경된 원인이 있을 때 실패 경로만 1회 재검증한다.
- 제품 코드를 수정한 결함에는 해당 소유 package의 focused regression test를 추가하고 관련 Go test를 실행한다.
## 기능
### Epic: [route-smoke] 벤치 경로 초경량 확인
- [ ] [minimal-html-calls] 9개 조합에 동일한 최소 HTML 구현 요청을 한 번씩 직접 실행하고, 경로별 caller/model/route, terminal, 경과 시간, marker 확인 결과를 한 개의 Markdown 표에 기록한다. direct는 caller workspace 파일, Edge-owned execution preset은 caller-visible terminal output을 확인한다. 사설 dev CA가 필요한 호출은 caller가 공식 지원하는 command-scoped CA 변수에 managed CA bundle을 전달한다. Codex는 `CODEX_CA_CERTIFICATE`, Node 기반 caller는 필요한 경우 `NODE_EXTRA_CA_CERTS`를 사용한다. 검증: 새 runner/manifest 없이 각 행에 실제 호출 결과가 하나만 있어야 하며, 호출 전후 ambient Codex/IDE/shell 환경에는 CA 변수가 없어야 한다.
- [ ] [failed-path-fixes] 실패한 조합마다 제품·caller·provider·환경 중 소유 경계를 기록하고, IOP 제품 결함이 재현된 경우에만 국소 수정과 focused regression을 수행한 뒤 해당 조합만 다시 호출한다. 검증: 성공한 조합의 반복 실행이 없고, 재실행 행에는 변경된 원인과 연결된 수정·테스트 근거가 있어야 한다.
- [ ] [thin-bench-handoff] 9개 조합의 통과 또는 구체적 외부 차단 상태를 짧게 정리해 `[bench-lite-01]` 실행 가능 여부를 남긴다. 검증: 비교 점수나 순위가 아니라 호출 가능 여부와 남은 소유자만 기록한다.
## 완료 리뷰
- 상태: 없음
- 요청일: 없음
- 완료 근거: 최소 HTML 호출 evidence가 아직 없다.
- 검토 항목:
- [ ] 새 benchmark script, runner, manifest, state store가 생성되지 않았다.
- [ ] 성공 경로는 한 번만 실행했고 실패 경로만 변경된 원인 뒤 재검증했다.
- [ ] 제품 수정은 재현된 결함과 focused regression으로 한정됐다.
- agent-ui 상태 반영: 해당 없음
- 리뷰 코멘트: 없음
## 범위 제외
- IOP ingress/provider/preset 전체 전수 안정화
- benchmark runner, manifest/schema, resume/recovery store, 자동 retry
- browser/CDP, screenshot, accessibility/network gate, 자동 품질 채점
- 반복 측정, 통계, 순위, token 정규화
- benchmark 성공을 다른 제품 Milestone이나 프로젝트의 완료 gate로 사용하는 것
## 작업 컨텍스트
- 계획 범위: 짧은 단일 test plan으로 즉시 실행할 수 있게 유지한다. 별도 설계·SDD·다단계 복구 계획으로 확장하지 않는다.
- 실행 방식: 기존 공식 caller 명령을 한 번씩 직접 실행한다. 공통화가 필요해 보여도 이 Milestone에서는 script로 승격하지 않는다.
- TLS 환경 경계: 개발 Edge용 사설 CA는 Edge leaf 인증서가 아니라 managed CA bundle을 해당 벤치 caller process에만 전달한다. Codex에는 공식 변수 `CODEX_CA_CERTIFICATE`, Node 기반 caller에는 필요한 경우 `NODE_EXTRA_CA_CERTS`를 사용하며 Codex/IDE 시작 환경이나 셸 전역에 `export`하지 않는다. 그렇지 않으면 공개 TLS 연결에도 같은 CA override가 적용될 수 있다.
- evidence 위치: `agent-test/dev/iop-benchmark-route-minimal-html-smoke.md`
- 현재 사전 확인: 2026-08-13 실제 원격 실행기에서 Claude Code 2.1.177, agy 1.1.12, Codex 0.146.0을 확인했고, 원격 SOPS에 보관된 기존 IOP principal token으로 token 원문을 출력하지 않은 `/v1/models`가 HTTP 200임을 확인했다. 새 벤치 전용 token은 발급하거나 사용하지 않는다.
- 현재 경로 결과: Claude Code → Claude direct와 Claude Code → Gemini direct는 최소 HTML 1회 호출을 통과했다. Claude Code → GPT direct는 Chat Completions의 tools+reasoning 조합 미지원으로 실패했고, IOP provider operation normalization 결함으로 귀속했다. caller-neutral operation 선택, Messages↔Responses 변환, nearest-lower effort mapping의 focused regression은 통과했으며 개발 런타임 재검증이 남아 있다.
- 추가 분리 결과: agy → Gemini direct는 누락된 caller `modelProvider=gemini`를 보정한 뒤 공식 URL-encoded model label을 IOP Gemini path parser가 거부하는 제품 결함까지 좁혔다. Codex → GPT direct의 최초 실패는 CA bundle 대신 Edge leaf 인증서를 전달한 측정 환경 결함이었고, 공식 Responses custom provider와 command-scoped managed CA bundle으로 바꾼 재검증은 10초 안에 `turn.completed``index.html` 생성을 통과했다. 같은 경계의 Codex → GPT execution preset도 16초 안에 `turn.completed`와 terminal marker 1회를 통과했다. Claude Code → Gemini preset의 caller workspace 파일 부재는 Edge-owned 비공개 workspace 정리 계약상 정상이라 측정 판정을 바로잡았고, 184초로 120초 상한을 넘은 지연과 최초 terminal marker 미수집만 별도 실패로 남겼다.
- 후속 측정: [초경량 Agent 모델 비교](thin-agent-model-comparison-benchmark.md)

View file

@ -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의 반영 또는 제외 근거를 남긴다.

View file

@ -1,111 +0,0 @@
# Milestone: [bench-02] IOP 원샷 Agent 모델 비교 벤치마크
## 위치
- Roadmap: [ROADMAP.md](../../../ROADMAP.md)
- Phase: [PHASE.md](../PHASE.md)
- SDD: [SDD.md](../../../sdd/knowledge-tool-optimization-extension/iop-one-shot-agent-model-comparison/SDD.md)
## 목표
`[route-02]`의 정식 기능과 필수 smoke가 완료된 뒤, 동일한 정적 웹페이지 과제를 IOP를 경유하는 3개 단독 모델과 Gemini/GPT 하이브리드 구조의 9개 caller 조합으로 각각 한 번 실행한다.
첫 output·model/tool·전체시간, 호출 횟수와 세부 token, 자동 웹 검증과 익명 100점 품질 평가를 함께 비교하고 재현 가능한 Markdown 보고서를 현재 프로젝트에 남긴다.
## 상태
[진행중]
## 구현 잠금
- 상태: 해제
- SDD: 필요
- SDD 문서: [IOP 원샷 Agent 모델 비교 벤치마크 SDD](../../../sdd/knowledge-tool-optimization-extension/iop-one-shot-agent-model-comparison/SDD.md)
- SDD 사유: 실제 dev provider/credential과 외부 CLI를 사용하는 field benchmark이며 실행 순서, 비용, 실패·재실행, secret-safe evidence와 비교 공정성을 고정해야 한다.
- SDD 상태: 승인됨
- SDD 잠금: 해제
- SDD 사용자 리뷰: 없음
- 잠금 해제 조건: 아래 체크리스트
- [x] SDD 잠금이 해제되어 있다.
- [x] SDD 사용자 리뷰가 없거나 승인/해결되었다.
- [x] Acceptance Scenario가 Milestone 기능 Task와 연결되어 있다.
- [x] Evidence Map이 완료 시 `complete.log``milestone-task` id별 집계와 최종 검증 evidence로 검증 가능하게 연결되어 있다.
- 결정 필요: 없음
## 범위
- 선행 조건은 `[bench-01]` benchmark pipeline 준비 완료와 `[route-02]` 정식 기능·필수 Claude smoke 완료다.
- 모든 scored 실행은 dev 환경의 `../iop-s2` IOP runtime을 경유하고, 동일 checksum의 이미지 2장과 vanilla HTML/CSS/JS 단일 페이지 prompt를 run별 clean workspace와 fresh caller session에 제공한다.
- 원샷은 사용자 작업 제출 1회 뒤 사람의 중간 feedback·수동 수정·재시작 없이 caller가 finish/complete event 후 idle이 될 때까지를 뜻하며 model/tool 호출 횟수는 제한하지 않고 측정한다.
- 초기 benchmark는 아래 9개 비교군을 각각 1회 실행한다.
| ID | 유형 | Caller | IOP 실행 구성 |
|----|------|--------|---------------|
| C01 | Claude 단독 | Claude Code | Claude Sonnet 5 최고 effort |
| C02 | Gemini 단독 | Claude Code | Gemini 3.6 Flash high |
| C03 | Gemini 단독 | agy | Gemini 3.6 Flash high |
| C04 | GPT 단독 | Claude Code | GPT-5.6 luna xhigh |
| C05 | GPT 단독 | Codex | GPT-5.6 luna xhigh |
| C06 | Gemini 하이브리드 | Claude Code | Gemini plan → ornith-fast work → Gemini review/repair |
| C07 | Gemini 하이브리드 | agy | Gemini plan → ornith-fast work → Gemini review/repair |
| C08 | GPT 하이브리드 | Claude Code | GPT plan → ornith-fast work → GPT review/repair |
| C09 | GPT 하이브리드 | Codex | GPT plan → ornith-fast work → GPT review/repair |
- provider가 제공하는 input/output/reasoning/cached/total token을 model·stage별로 기록하고, 제공되지 않는 값은 추정 원본과 섞지 않고 `미제공`으로 표시한다.
- 결과물 identity를 가린 뒤 Codex가 동일 rubric으로 품질을 채점하고 자동 검증 결과와 분리해 보고한다.
## 기능
### Epic: [benchmark-readiness] 비교 입력과 실행 준비 고정
실행 전에 공정한 fixture와 실제 IOP route/credential 상태를 고정한다.
- [x] [fixture-lock] 이미지 2장, 동일 one-page 요구사항, vanilla HTML/CSS/JS 초기 workspace, viewport와 자동 검증·100점 rubric을 checksum/version과 함께 고정한다.
- [x] [agy-iop-compatibility] 공식 `agy 1.1.12`의 Gemini API-key provider가 route별 `GOOGLE_GEMINI_BASE_URL``GEMINI_API_KEY=<IOP principal token>`으로 Edge의 Gemini-native `streamGenerateContent` ingress를 호출하고, Gemini request/tool/SSE를 기존 direct·execution-preset 실행에 연결하며 실제 `stream-json` lifecycle/usage를 benchmark adapter가 수집하도록 구현한다. `--effort`와 비공식 custom model에 의존하지 않고 high effort는 IOP effective binding으로 검증한다.
- [x] [managed-credential-dev] dev Control Plane·Edge·Node에 CA-signed mTLS, Edge HTTPS, at-rest/issuer/recipient key material, principal projection과 Gemini/Claude/GPT slot-route를 operator-owned secret 경로로 구성한다. legacy static credential source를 제거하고 marked hybrid preset이 같은 principal의 고정 stage route를 managed lease로 실행하는지 secret-safe smoke로 확인한다.
- [x] [route-readiness] dev `../iop-s2`에서 Claude Code·Codex의 호환 ingress와 공식 agy의 Gemini API-key transport가 각각 IOP 인증 경계를 통과하고, Sonnet/Gemini/GPT route, Gemini/GPT hybrid preset, effort와 stream/finish/idle이 모두 실제 caller preflight를 통과했는지 확인한다. 존재하지 않는 caller 환경 변수나 합성 event fixture는 live 호환 근거로 인정하지 않는다.
- [x] [matrix-lock] C01-C09의 caller, IOP route/preset, model/effort, 반복 횟수 1, 실행 순서 seed, fresh-session과 setup/cache 정책 및 timeout을 immutable run manifest로 확정한다.
### Epic: [comparison-runs] 9개 원샷 실행
각 비교군을 clean workspace에서 한 번 실행하고 실패를 포함한 attempt evidence를 보존한다.
- [ ] [claude-standalone] C01 Claude Code→IOP→Claude Sonnet 5 최고 effort 단독 원샷을 실행한다.
- [ ] [gemini-standalone] C02 Claude Code와 C03 agy가 각각 IOP→Gemini 3.6 Flash high 단독 원샷을 실행한다.
- [ ] [gpt-standalone] C04 Claude Code와 C05 Codex가 각각 IOP→GPT-5.6 luna xhigh 단독 원샷을 실행한다.
- [ ] [gemini-hybrid] C06 Claude Code와 C07 agy가 각각 IOP의 Gemini plan→ornith-fast work→Gemini review/repair 원샷을 실행한다.
- [ ] [gpt-hybrid] C08 Claude Code와 C09 Codex가 각각 IOP의 GPT plan→ornith-fast work→GPT review/repair 원샷을 실행한다.
### Epic: [comparison-report] 검증·채점·보고서
정량 evidence와 익명 품질 평가를 결합하되 원본 수치와 해석을 분리한다.
- [ ] [objective-validation] 각 결과의 build/serve, desktop·mobile screenshot, 이미지·asset, console 오류, 요구사항·반응형·접근성 gate와 최종 workspace 상태를 자동 검증한다.
- [ ] [quality-scoring] 익명화된 9개 결과에 요구사항 25, 시각 완성도 25, 반응형·접근성 15, 이미지·디테일 10, 안정성 10, 코드 품질 10, 자체 검증 5의 동일 100점 rubric으로 Codex가 점수를 기록한다.
- [ ] [performance-usage] 첫 output·첫 file write·model 호출별·tool·queue·전체 finish/idle 시간, 호출 횟수와 model/stage별 input/output/reasoning/cached/total token을 clock/source·미제공 여부와 함께 비교하고 중첩 구간이나 미관측 overhead를 임의 산술 분해하지 않는다.
- [ ] [benchmark-report] 9개 결과의 속도·품질·token 표, 실행 조건·버전·실패·한계·raw evidence 링크를 포함한 날짜별 Markdown 보고서를 `agent-test/dev/`에 남긴다.
## 완료 리뷰
- 상태: 없음
- 요청일: 없음
- 완료 근거: 사용자 확정 9개 비교군과 post-smoke 실행·평가 기준을 SDD와 기능 Task로 정리했으며 실제 비교 evidence는 아직 없다.
- 검토 항목: 없음
- 리뷰 코멘트: 없음
## 범위 제외
- `[route-02]` 정식 기능이나 필수 smoke의 완료 여부를 이 비교 점수로 대체하거나 소급 변경하는 작업
- 첫 보고서에서 비교군별 2회 이상 반복하는 실행
- React/Vite 등 dependency 설치와 cache가 속도에 섞이는 frontend framework 과제
- provider가 보고하지 않은 reasoning token을 exact 값처럼 추정하거나 서로 다른 tokenizer 수치를 무보정 단일 합계로 단정하는 방식
- 실패 attempt를 삭제하고 성공 재실행만 대표값으로 선택하는 방식
## 작업 컨텍스트
- 관련 경로: `agent-test/dev/`, `agent-test/runs/`, `../iop-s2`
- 표준선: preflight는 scored attempt와 분리하고, scored 실행이 시작된 뒤의 실패는 결과로 보존하며 재실행이 필요하면 새 attempt로 기록한다.
- 표준선: IOP credential/model route가 없으면 안전한 등록을 요청하고, alias/effort를 임의 대체하지 않는다.
- 현재 차단: packet 14의 fresh 5-cell direct 진단과 배포 qualification 진행 중. retained direct run은 `unresolved=0`인 terminal evidence지만 all-success는 아니며 기존 run을 resume/retry/수정하지 않는다. 새 qualification은 fresh `ready=5`, 정확히 5개 attempt, 모든 controller/product/harness/process/web-validation terminal evidence와 exhausted browser/CDP infrastructure block 없음으로 판정하고 제품 실패·provider rejection·timeout은 benchmark 결과로 보존한다. 이 진단이 통과하면 fresh C01-C09 `ready=9`를 확인하고, 후속 packet에서 기존 run과 다른 identity로 repetitions=1 scored run을 한 번 실행한다. 비교 Task 체크 상태는 scored evidence가 생길 때까지 변경하지 않는다.
- 실행 순서와 차단 관계: [전역 마일스톤 실행 순서](../../../priority-queue.md)
- 관련 Milestone: [[bench-01] Agent 비교 벤치마크 파이프라인 준비](agent-comparison-benchmark-pipeline.md), [[route-02] IOP 단일 요청 Agent 실행](../../../archive/phase/knowledge-tool-optimization-extension/milestones/iop-owned-single-request-agent-execution.md)
- 확인 필요: 없음

View file

@ -0,0 +1,65 @@
# Milestone: [bench-lite-01] 초경량 Agent 모델 비교
## 위치
- Roadmap: [ROADMAP.md](../../../ROADMAP.md)
- Phase: [PHASE.md](../PHASE.md)
## 목표
`[bench-route-01]`에서 호출 가능성이 확인된 동일 9개 조합을 고정된 실제 비교 과제로 한 번씩 실행해 최소 비교 결과를 남긴다.
측정 지원 코드는 만들지 않고, caller 실행과 결과 기록만 측정 경계로 둔다.
## 상태
[계획]
## 구현 잠금
- 상태: 해제
- SDD: 불필요
- SDD 문서: 없음
- SDD 사유: 기존 caller와 제품 경로를 단일 시도로 관측하는 test-only 작업이며 제품 API, 상태 머신, retry 또는 schema를 변경하지 않는다.
- 결정 필요: 없음
## 범위
- `[bench-route-01]`과 동일한 9개 caller/model/route 조합
- 모든 조합에 같은 고정 비교 prompt와 같은 빈 임시 workspace 사용
- 조합별 정확히 1회 실행
- 성공 여부, 전체 경과 시간, caller가 직접 제공한 usage, 산출물 경로와 짧은 수동 관찰만 기록
- 실패한 조합은 실패로 기록하고 같은 측정 안에서 retry, resume 또는 대체 run을 하지 않음
## 기능
### Epic: [thin-run] 단일 시도 비교
- [ ] [single-attempt-matrix] 9개 조합을 같은 prompt와 초기 상태에서 정확히 한 번씩 실행한다. 검증: 조합별 producer attempt가 하나이며 retry/resume/recovery 기록이 없어야 한다.
- [ ] [minimal-result-table] 성공 여부, 경과 시간, caller 제공 usage, 산출물 경로와 짧은 관찰을 단일 Markdown 표로 기록한다. 제공되지 않은 usage는 `미제공`으로 두고 추정하거나 0으로 바꾸지 않는다.
- [ ] [bounded-conclusion] 성공한 결과만 비교하고 실패·미제공 데이터를 점수 0으로 취급하지 않는 짧은 결론을 남긴다. 자동 채점이나 통계적 일반화는 하지 않는다.
## 완료 리뷰
- 상태: 없음
- 요청일: 없음
- 완료 근거: `[bench-route-01]`과 단일 시도 결과가 아직 없다.
- 검토 항목:
- [ ] `[bench-route-01]`이 통과 또는 사용자 승인된 외부 차단 상태다.
- [ ] 새 benchmark script와 자동화 state가 없다.
- [ ] 조합별 정확히 한 번의 실행과 최소 결과 표만 남았다.
- agent-ui 상태 반영: 해당 없음
- 리뷰 코멘트: 없음
## 범위 제외
- manifest runner, orchestration framework, lifecycle store
- retry, resume, recovery qualification, stale-run repair
- browser/CDP 자동 검증, screenshot gate, accessibility/network gate
- 익명 LLM 채점, 자동 순위, 반복·분산·유의성 분석
- 이 측정을 IOP 제품 안정성 또는 다른 Milestone의 완료 조건으로 승격하는 것
## 작업 컨텍스트
- 선행 작업: [벤치 경로 최소 HTML 스모크](benchmark-route-minimal-html-smoke.md)
- 실행 방식: 기존 공식 caller 명령을 한 번씩 직접 실행하며 공통 runner를 만들지 않는다.
- 결과 위치: `agent-test/dev/iop-thin-agent-model-comparison.md`

View file

@ -4,11 +4,21 @@
## 실행 순서
### bench-route
1. [[bench-route-01] 벤치 경로 최소 HTML 스모크](phase/knowledge-tool-optimization-extension/milestones/benchmark-route-minimal-html-smoke.md)
벤치 대상 9개 caller/model/route 조합에 고정된 최소 `index.html` 생성 요청을 한 번씩 직접 보내고 실패 경로만 국소 수정한다.
### bench-lite
1. [[bench-lite-01] 초경량 Agent 모델 비교](phase/knowledge-tool-optimization-extension/milestones/thin-agent-model-comparison-benchmark.md)
통과한 동일 경로를 복구·재개·자동 채점 없이 한 번씩 실행하고 최소 비교 표만 남긴다.
- 선행 차단: `[bench-route-01]`
### route
3. [[route-03] Heavy Plan/Review 실행과 검증 MVP](phase/knowledge-tool-optimization-extension/milestones/knowledge-tool-validation-optimization.md)
Hot Path의 lightweight Plan/Review를 장기 작업용 `heavy` mode로 확장해 `heavy-only` preset에서 재계획·검증·review/repair·resume 경계를 먼저 검증한다.
- 선행 차단: `[bench-02]`
4. [[route-04] Execution Preset 하이브리드 Mode 라우팅](phase/knowledge-tool-optimization-extension/milestones/openai-compatible-hybrid-request-execution-routing.md)
cloud model advisory와 deterministic hard gate를 결합해 Edge가 외부 model에 매핑된 preset의 허용 mode 중 요청 난이도에 맞는 실행 경로를 고르고 route evidence를 축적한다.
@ -17,10 +27,10 @@
cloud-first route evidence가 품질·규모 gate를 통과하면 RAG local router를 shadow/canary로 검증해 운영 기본 경로로 점진 전환한다.
- 선행 차단: `[observe-03]`, `[provider-02]`
### bench
### surface
2. [[bench-02] IOP 원샷 Agent 모델 비교 벤치마크](phase/knowledge-tool-optimization-extension/milestones/iop-one-shot-agent-model-comparison.md)
`[route-02]` 정식 smoke 뒤 동일 정적 웹 fixture로 Sonnet/Gemini/GPT 단독과 Gemini/GPT 하이브리드의 9개 IOP 경유 조합을 각각 한 번 비교한다.
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

View file

@ -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: 없음

View file

@ -1,164 +0,0 @@
# SDD: [bench-02] IOP 원샷 Agent 모델 비교 벤치마크
## 위치
- Milestone: [IOP 원샷 Agent 모델 비교 벤치마크](../../../phase/knowledge-tool-optimization-extension/milestones/iop-one-shot-agent-model-comparison.md)
- Phase: [PHASE.md](../../../phase/knowledge-tool-optimization-extension/PHASE.md)
## 상태
[승인됨]
## SDD 잠금
- 상태: 해제
- 사용자 리뷰: 없음
- 잠금 항목:
- [x] [D01] 실제 비교는 `[route-02]` 정식 기능·필수 smoke와 `[bench-01]` pipeline 준비가 끝난 뒤 시작한다.
- [x] [D02] 9개 scored 비교군은 모두 dev `../iop-s2` IOP runtime을 경유한다.
- [x] [D03] 단독군은 Sonnet 5 최고, Gemini 3.6 Flash high, GPT-5.6 luna xhigh이며 Gemini/GPT는 Claude Code와 전용 caller(agy/Codex)를 각각 비교한다.
- [x] [D04] 하이브리드는 Gemini 또는 GPT가 plan/review/repair를, ornith-fast가 work를 담당하고 각각 Claude Code와 전용 caller를 비교한다.
- [x] [D05] 동일 이미지 2장과 vanilla HTML/CSS/JS 한 페이지 fixture를 clean workspace에 제공한다.
- [x] [D06] 각 승인된 scored run의 repetitions는 cell별 1이며 clean workspace와 fresh caller session에서 사용자 작업 제출 1회부터 finish/complete 후 idle까지 사람 개입 없이 실행한다. caller launch 전 harness 결함으로 중단된 과거 run 뒤 사용자가 새 실행을 명시적으로 승인하면, 과거 run을 resume/retry하지 않고 새 run identity로 같은 immutable manifest를 한 번 실행할 수 있다.
- [x] [D07] 시간은 첫 output, 첫 file write, model/stage별 작업, tool, queue와 전체 finish/idle을 clock/source와 함께 기록하고 중첩 구간이나 미관측 overhead를 임의 산술 분해하지 않는다.
- [x] [D08] token은 input/output/reasoning/cached/total과 source를 model/stage별로 기록하고 미제공 값을 exact로 추정하지 않는다.
- [x] [D09] 결과 identity를 가린 뒤 동일 100점 rubric으로 Codex가 채점하고 자동 검증과 수동 점수를 분리한다.
- [x] [D10] scored failure는 보존하고 같은 run의 재실행은 명시적 retry의 새 attempt로만 기록하며 성공 결과만 골라 대표하지 않는다. 새 run 승인은 기존 실패 run/attempt를 대체하지 않는 별도 비교 cycle이며, 보고서에는 이전 실패 run과 새 run의 관계 및 한계를 함께 남긴다.
- [x] [D11] 공식 `agy 1.1.12`는 Gemini API-key provider의 route별 `GOOGLE_GEMINI_BASE_URL`을 IOP Edge로 지정하고 `GEMINI_API_KEY`에는 upstream key가 아닌 IOP principal token을 넣는다. `--effort`와 비공식 custom model은 사용하지 않고 high effort는 IOP effective binding으로 검증한다.
- [x] [D12] marked hybrid preset은 dev managed credential plane의 fresh projection, 고정 stage authorization과 sealed provider lease가 준비된 뒤에만 실행하며 legacy credential fallback을 허용하지 않는다.
- [x] [D13] 배포 qualification의 5-cell direct 진단은 all-success가 아니라 terminal-evidence completeness를 판정한다. fresh `ready=5`, 정확히 5개의 fresh attempt, `unresolved=0`, `running=0`, `interrupted=0`, 모든 slot의 controller/product/harness/process/web-validation terminal evidence와 exhausted browser/CDP infrastructure block 없음이 필요하다. 제품 실패·provider rejection·caller failure 뒤 generated-missing·timeout은 보존할 benchmark 결과이며 암묵 재시도하지 않는다. Edge pre-ingress incompatibility 또는 exhausted browser/CDP infrastructure block만 qualification을 막는다. 이 unscored 진단은 D06/D10의 유일한 새 scored C01-C09 run identity를 소비하지 않는다.
## 문제 / 비목표
- 문제: `[route-02]` 하이브리드 원샷의 실사용 가치와 overhead를 판단하려면 같은 IOP 경계, task fixture와 평가 기준에서 단독 모델·caller agent 조합과 속도·token·품질을 함께 비교해야 한다. 단일 성공 smoke만으로는 모델·agent·coordinator 차이를 설명할 수 없다.
- 비목표:
- `[route-02]` 완료 smoke를 대신하거나 benchmark 점수로 완료 상태를 소급 변경
- 첫 보고서에서 통계적 다회 반복이나 장기/heavy 작업 평가
- framework 설치·cache 성능 비교
- model/provider 가격표를 billing-grade 비용으로 확정
## Source of Truth
| 영역 | 기준 | 메모 |
|------|------|------|
| Roadmap | [Milestone 문서](../../../phase/knowledge-tool-optimization-extension/milestones/iop-one-shot-agent-model-comparison.md) | 9개 비교군과 완료 상태 원장 |
| Pipeline | [bench-01 Milestone](../../../phase/knowledge-tool-optimization-extension/milestones/agent-comparison-benchmark-pipeline.md)의 승인된 manifest/runner/report contract | 실행·측정·보고 구현 원본 |
| Testbed | `../iop-s2` dev IOP runtime | 모든 scored model 호출의 IOP 경유 대상 |
| Fixture | versioned prompt, 이미지 2장과 vanilla workspace checksum | 모든 cell의 동일 입력 기준 |
| Evidence | `agent-test/runs/<run-id>/` | attempt별 timeline, usage, validation, screenshot와 score |
| Report | `agent-test/dev/iop-one-shot-agent-comparison-<date>.md` | 현재 프로젝트의 사람이 읽는 비교 결과 |
| API Contract | [Anthropic-Compatible Messages API](../../../../agent-contract/outer/anthropic-compatible-api.md), [OpenAI-Compatible API](../../../../agent-contract/outer/openai-compatible-api.md), [Gemini-Compatible API](../../../../agent-contract/outer/gemini-compatible-api.md) | caller ingress, stream/terminal과 usage 기준 |
| Config Contract | [Edge Config And Runtime Refresh](../../../../agent-contract/inner/edge-config-runtime-refresh.md) | model route, preset, protocol profile과 credential 경계 |
| User Decision | D01-D10 | 2026-08-06 확정 방향, 추가 사용자 결정 없음 |
## State Machine
| 상태 | 진입 조건 | 다음 상태 | 근거 |
|------|-----------|-----------|------|
| `blocked` | `[route-02]` smoke 또는 `[bench-01]` 완료 전 | `preflighting`, 종료 | active Milestone 상태와 pipeline evidence |
| `preflighting` | 선행 조건 충족, execution-day caller/route/credential 점검 | `ready`, `blocked` | official caller transport, managed projection/lease와 redacted preflight matrix |
| `ready` | fixture와 C01-C09 immutable manifest 확정 | `running`, `cancelled` | manifest/fixture/rubric digest |
| `running` | seed 순서에 따라 각 cell에 사용자 작업 1회 제출 | `validating`, `failed`, `timed_out`, `cancelled` | cell/attempt event timeline |
| `validating` | cell finish/complete 후 idle 확정 | `scoring`, `failed` | workspace, build/render/test evidence |
| `scoring` | C01-C09 결과 identity 제거 완료 | `analyzing`, `failed` | blind mapping과 rubric worksheet |
| `analyzing` | 자동 gate·시간·usage·score 완비 | `reported`, `failed` | comparison table과 limitation notes |
| `reported` | Markdown과 raw evidence 포인터 생성 | 종료 | report path와 digest |
| `failed` | cell 실행·검증·채점·보고 실패 | `analyzing`, 종료 | 보존된 실패 attempt; 누락 없는 matrix |
| `timed_out` | cell timeout | `analyzing`, 종료 | timeout/cancel/cleanup evidence |
| `cancelled` | 명시 중단 | 종료 | 실행된 cell과 미실행 cell 상태 |
State invariant:
- C01-C09는 승인된 run마다 동일 fixture checksum, viewport, rubric version, fresh caller session, setup/cache policy와 repetitions=1을 사용한다. 새 run은 기존 실패 run과 다른 run identity를 가지며 기존 run을 resume/retry하거나 대표 결과에서 삭제하지 않는다.
- execution order는 고정 seed로 생성해 보고서에 남기고 결과에 따라 재정렬하지 않는다.
- preflight와 setup usage/time은 scored measurement에 합산하지 않지만 별도 기록한다.
- 한 cell의 사용자 작업은 한 번 제출하며 사람의 feedback, manual edit, restart가 없다.
- model/tool 호출 횟수는 제약이 아니라 측정 대상이며 finish event 뒤 idle까지가 wall-clock terminal이다.
- 실패 cell도 report matrix에 남고 재실행 결과는 원래 attempt를 대체하지 않는다.
- 배포 qualification의 5-cell direct 진단은 scored C01-C09 run과 별개다. terminal evidence가 완결된 제품 실패·provider rejection·timeout을 acceptance failure로 재해석하지 않고, Edge pre-ingress incompatibility 또는 최대 renderer 재시도 뒤 browser/CDP infrastructure block만 다음 단계 진입을 막는다.
## Interface Contract
- 계약 원문: [Anthropic-Compatible Messages API](../../../../agent-contract/outer/anthropic-compatible-api.md), [OpenAI-Compatible API](../../../../agent-contract/outer/openai-compatible-api.md), [Gemini-Compatible API](../../../../agent-contract/outer/gemini-compatible-api.md), [Edge Config And Runtime Refresh](../../../../agent-contract/inner/edge-config-runtime-refresh.md)
- 입력:
- `fixture`: 동일 이미지 2장, one-page 요구사항, vanilla HTML/CSS/JS initial workspace와 checksum이다.
- `cells`: C01-C09의 caller, IOP route/preset, expected model/stage와 effort binding이다.
- `repetitions=1`, `session_policy=fresh`, `setup_cache_policy`: 초기 scored attempt 수, conversation/resume 격리와 공통 setup/cache 기준이다.
- `environment=dev`, `testbed=../iop-s2`: 실제 IOP runtime 선택이다.
- `completion`: caller별 finish/complete event와 idle 판정 규칙이다.
- `agy`: `modelProvider=gemini`, route별 `GOOGLE_GEMINI_BASE_URL`, `GEMINI_API_KEY=<IOP principal token>`과 official `stream-json` event다. API-key provider가 지원하지 않는 `--effort`는 전달하지 않는다.
- 측정 출력:
- timestamp: submitted, first output, first file write, model/stage start/end, tool start/end, finish, idle의 monotonic 값과 observation source다. overlap과 unavailable을 명시한다.
- usage: call count, input/output/reasoning/cached/total token과 source다.
- validation: requirement, build/serve, desktop/mobile, asset/console, responsive/accessibility 결과다.
- score: rubric version, 항목별 점수/근거와 총점이며 identity mapping과 분리한다.
- 100점 rubric:
- 요구사항 충족 25, 시각 완성도 25, 반응형·접근성 15, 이미지 활용·디테일 10, 동작 안정성 10, 코드 품질 10, 자체 검증 완결성 5.
- 금지:
- IOP를 우회한 model 호출을 scored cell로 인정한다.
- cell마다 prompt, asset, initial workspace나 viewport를 다르게 사용한다.
- unavailable token을 0으로 기록하거나 estimated 값을 provider-reported와 합친다.
- evaluator가 identity를 본 상태에서 점수를 조정하거나 결과를 수동 수정한다.
## Acceptance Scenarios
| ID | Milestone Task | Given | When | Then |
|----|----------------|-------|------|------|
| S01 | `fixture-lock` | 이미지 2장과 one-page benchmark brief | fixture 확정 | prompt/assets/workspace/viewports/rubric의 checksum과 version이 모든 cell에 동일하다. |
| S02 | `route-readiness` | 5-cell direct 진단과 C01-C09 caller·dev IOP | execution-day qualification과 preflight | direct는 fresh `ready=5`, 정확히 5개 attempt, 모든 terminal evidence와 infrastructure block 없음으로 admission되고, 이후 C01-C09 auth/model/preset/effort/stream/finish/idle이 모두 확인되거나 exact blocker로 중단된다. |
| S03 | `matrix-lock` | 선행 gate가 통과한 9개 cell | scored manifest 생성 | repetitions=1, 실행 순서 seed, fresh-session/setup-cache 정책, timeout과 expected binding이 immutable하게 기록된다. |
| S04 | `claude-standalone` | C01 clean workspace | Claude Code 사용자 작업 1회 | IOP→Sonnet 최고 effort 결과와 complete/idle evidence가 생성된다. |
| S05 | `gemini-standalone` | C02-C03 clean workspace | Claude Code와 agy 사용자 작업을 각각 1회 제출 | 두 caller 모두 IOP→Gemini high 결과와 caller별 timing/usage를 남긴다. |
| S06 | `gpt-standalone` | C04-C05 clean workspace | Claude Code와 Codex 사용자 작업을 각각 1회 제출 | 두 caller 모두 IOP→GPT xhigh 결과와 caller별 timing/usage를 남긴다. |
| S07 | `gemini-hybrid` | C06-C07 clean workspace | Claude Code와 agy 사용자 작업을 각각 1회 제출 | IOP Gemini plan→ornith work→Gemini review/repair의 stage evidence와 최종 결과를 남긴다. |
| S08 | `gpt-hybrid` | C08-C09 clean workspace | Claude Code와 Codex 사용자 작업을 각각 1회 제출 | IOP GPT plan→ornith work→GPT review/repair의 stage evidence와 최종 결과를 남긴다. |
| S09 | `objective-validation` | C01-C09 성공·실패 workspace | 자동 웹 검증 | 각 cell의 동일 gate 결과, screenshot과 실패 이유가 누락 없이 생성된다. |
| S10 | `quality-scoring` | identity가 제거된 9개 결과 | Codex rubric 평가 | 항목별 점수/근거와 총점이 자동 gate와 분리되어 기록된다. |
| S11 | `performance-usage` | 모든 attempt timeline/usage | 비교 집계 | 첫 output·첫 write·model/tool/queue/total 시간의 clock/source·overlap, 호출 수와 token/source가 cell·stage별 표가 된다. |
| S12 | `benchmark-report` | S01-S11 evidence | 보고서 생성 | 조건·버전·9개 결과·속도·token·품질·실패·한계와 raw evidence 링크가 Markdown에 남는다. |
| S13 | `agy-iop-compatibility` | official `agy 1.1.12`와 dev Edge | direct·hybrid route별 Gemini base URL로 실제 API-key 호출 | 두 호출 모두 `x-goog-api-key` IOP principal auth, Gemini-native request/tool/SSE, official `stream-json` finish/exit와 config-owned effective binding evidence를 남기고 upstream key 직접 호출이나 합성 event에 의존하지 않는다. |
| S14 | `managed-credential-dev` | dev Control Plane·Edge·Node와 operator-owned security material | managed credential profile로 재기동하고 slot/route를 등록 | CA-signed mTLS·Edge HTTPS·fresh projection·sealed lease가 확인되고 legacy credential source나 cross-route fallback 없이 direct와 marked preset stage가 실행된다. |
## Evidence Map
| Scenario | Required Evidence | `agent-task` 연결 | 완료 Evidence 기대 |
|----------|-------------------|------------------|---------------------------|
| S01 | fixture prompt/assets/workspace/rubric digest | `agent-task/m-iop-one-shot-agent-model-comparison/fixture-lock/` | `fixture-lock` identical-input evidence |
| S02 | redacted direct `ready=5`·5-slot terminal evidence·infrastructure 판정과 C01-C09 preflight matrix | `agent-task/m-iop-one-shot-agent-model-comparison/route-readiness/` | `route-readiness` direct admission과 auth/route/effort/terminal evidence |
| S03 | immutable scored manifest와 order seed | `agent-task/m-iop-one-shot-agent-model-comparison/matrix-lock/` | `matrix-lock` 9-cell/repetitions=1 evidence |
| S04 | C01 event/timing/usage/workspace evidence | `agent-task/m-iop-one-shot-agent-model-comparison/claude-standalone/` | `claude-standalone` one-submission/IOP evidence |
| S05 | C02-C03 caller별 event/timing/usage/workspace evidence | `agent-task/m-iop-one-shot-agent-model-comparison/gemini-standalone/` | `gemini-standalone` two-caller evidence |
| S06 | C04-C05 caller별 event/timing/usage/workspace evidence | `agent-task/m-iop-one-shot-agent-model-comparison/gpt-standalone/` | `gpt-standalone` two-caller evidence |
| S07 | C06-C07 Gemini/ornith stage와 terminal evidence | `agent-task/m-iop-one-shot-agent-model-comparison/gemini-hybrid/` | `gemini-hybrid` two-caller stage evidence |
| S08 | C08-C09 GPT/ornith stage와 terminal evidence | `agent-task/m-iop-one-shot-agent-model-comparison/gpt-hybrid/` | `gpt-hybrid` two-caller stage evidence |
| S09 | build/render/viewport/asset/console/accessibility result와 screenshot | `agent-task/m-iop-one-shot-agent-model-comparison/objective-validation/` | `objective-validation` uniform gate evidence |
| S10 | blind mapping 분리와 Codex rubric worksheet | `agent-task/m-iop-one-shot-agent-model-comparison/quality-scoring/` | `quality-scoring` 100-point evidence |
| S11 | cell/stage별 normalized timeline, calls와 token-source table | `agent-task/m-iop-one-shot-agent-model-comparison/performance-usage/` | `performance-usage` speed/token evidence |
| S12 | `agent-test/dev/` Markdown과 raw run links | `agent-task/m-iop-one-shot-agent-model-comparison/benchmark-report/` | `benchmark-report` complete comparison evidence |
| S13 | official agy request-shape capture, Edge Gemini bridge tests, direct·hybrid live preflight와 sanitized lifecycle/usage | `agent-task/m-iop-one-shot-agent-model-comparison/06+05_comparison_runs/` | `agy-iop-compatibility` official 1.1.12 IOP transport evidence |
| S14 | dev config check, TLS/workload identity, projection generation, slot-route/lease attribution과 post-revoke no-fallback smoke | `agent-task/m-iop-one-shot-agent-model-comparison/06+05_comparison_runs/` | `managed-credential-dev` secure composition and hybrid admission evidence |
공통 완료 검증은 C01-C09 모두가 success/failure/blocked 중 하나의 terminal evidence를 가지고, 성공 결과의 자동 gate·screenshot·blind score와 모든 attempt의 timing/usage source가 보고서에 연결되는지 확인한다. 필수 credential/model이 없으면 raw secret을 요구하거나 기록하지 않고 운영 절차로 등록을 요청한다.
## Cross-repo Dependencies
- 없음. 같은 IOP 프로젝트의 `[route-02]``[bench-01]` 실행 순서는 [전역 마일스톤 실행 순서](../../../priority-queue.md)에서 관리한다.
## Drift Check
- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다.
- [x] Evidence Map이 code-review/complete.log에서 검증 가능하다.
- [x] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
- [x] 사용자 리뷰가 필요한 항목은 없고 확정된 D01-D13을 반영했다.
## 사용자 리뷰 이력
- 2026-08-06: 사용자가 Sonnet/Gemini/GPT 단독과 Gemini/GPT 하이브리드의 9개 IOP 경유 비교군, Claude Code·agy·Codex caller, finish/idle 원샷, 초기 1회, dev `../iop-s2`, 동일 정적 웹 fixture와 시간·token·Codex 품질 평가를 확정했다.
- 2026-08-12: 공식 `agy 1.1.12` API-key provider의 실제 Gemini-native 요청과 `stream-json` event를 확인했고, 사용자의 provider 직접 설정 지시에 따라 upstream key와 IOP principal token을 분리하며 dev managed credential plane까지 구성하는 D11-D12를 기술 보강했다.
- 2026-08-13: 사용자가 terminal outcome과 dispatcher 환경 보완 뒤 다음 벤치까지 계속 실행하도록 승인했다. 이에 기존 실패 run을 보존하고 resume/retry하지 않은 채, 동일 immutable C01-C09 manifest로 repetitions=1인 새 scored run identity를 한 번 생성하는 D06/D10 경계를 확정했다.
- 2026-08-13: 승인된 후속 packet에 따라 direct 배포 qualification을 terminal-evidence admission으로 분리했다. 제품 실패·provider rejection·timeout은 측정 결과로 보존하고, Edge pre-ingress incompatibility와 exhausted browser/CDP infrastructure block만 qualification을 막는 D13을 추가했다. D06/D10의 scored-run uniqueness는 유지한다.
## 작업 컨텍스트
- 표준선: 이 비교는 `[route-02]` 완료 이후의 추가 검증이며 정식 smoke의 일부나 대체 evidence가 아니다.
- 후속 SDD: 없음

View file

@ -0,0 +1,125 @@
evidence_schema=iop.ornith_session_stall_timeout_order.v2
observed_date=2026-08-13
environment=dev-runtime
release=dev-974
qualification_source_sha=2fcc1093c7629ab94b460d083520ffd9c8064815
qualification_source_ref=release/dev-974
qualification_source_clean=true
qualification_origin_dev_match=true
build.edge.sha256=25e097be4aee972cc62821889f8dd697b7d0f8de712973d169449b2271a39096
build.node_darwin_arm64.sha256=c24a3b0e16bc7ab807c00c465a47f9caccd35c5575d264d0c4ad10b9ad422f01
build.node_linux_arm64.sha256=759eb31f8c8752925e54f61fdeb4f9366bd39e37ea50815e79328c3be86ee4e7
build.node_windows_amd64.sha256=f04f33f17be8a4fdc29ef0e4b705953d561478a75f00948d3fdfc13fee8ed572
config.onexplayer-lemonade.response_stall_timeout_ms=120000
config.onexplayer-lemonade.capacity=3
config.rtx5090-lemonade.response_stall_timeout_ms=120000
config.rtx5090-lemonade.capacity=1
config.long_context_threshold_tokens=30000
config.external_caller_boundary_ms=180000
config.edge_request_hard_timeout_ms=3600000
timeout_order_valid=true
restart_required_paths.count=2
restart_required_paths.exact_ornith_only=true
runtime.ports_open=18082,18083,18084,19093,19101
runtime.connected_node_count=4
runtime.ornith_providers_healthy=true
runtime.ornith_recovered_in_flight=0
runtime.ornith_recovered_queued=0
direct.onexplayer-lemonade.http_status=200
direct.onexplayer-lemonade.duration_ms=111232
direct.onexplayer-lemonade.finish_count=1
direct.onexplayer-lemonade.done_count=1
direct.onexplayer-lemonade.error_count=0
direct.onexplayer-lemonade.outcome=normal_terminal
direct.rtx5090-lemonade.http_status=200
direct.rtx5090-lemonade.duration_ms=7710
direct.rtx5090-lemonade.finish_count=1
direct.rtx5090-lemonade.done_count=1
direct.rtx5090-lemonade.error_count=0
direct.rtx5090-lemonade.outcome=normal_terminal
stall_reproduction_status=not_reproduced
managed.script_sha256=bc0dcf5aa8acc6a10942a6d48e4b3248547004df04d0a292562aea2ca465a903
managed.provenance=current-run-manifest
managed.ornith_35b.chat.run_id=1786601559737433000-4ba66d5c48201ad1
managed.ornith_35b.chat.selected_provider=onexplayer-lemonade
managed.ornith_35b.chat.context_class=normal
managed.ornith_35b.chat.request_runes=431
managed.ornith_35b.chat.estimated_input_tokens=133
managed.ornith_35b.chat.eligible_capacity=3
managed.ornith_35b.chat.request_count=4
managed.ornith_35b.chat.http_200_count=4
managed.ornith_35b.chat.finish_count=4
managed.ornith_35b.chat.done_count=4
managed.ornith_35b.chat.error_count=0
managed.ornith_35b.chat.duration_ms_min=35049
managed.ornith_35b.chat.duration_ms_max=60703
managed.ornith_35b.chat.selected_peak_in_flight=3
managed.ornith_35b.chat.selected_max_queued=1
managed.ornith_35b.chat.selected_final_in_flight=0
managed.ornith_35b.chat.selected_final_queued=0
managed.ornith_35b.chat.outcome=pass
managed.ornith_35b.responses.run_id=1786601621300814000-c4d9c6c134fe368b
managed.ornith_35b.responses.selected_provider=onexplayer-lemonade
managed.ornith_35b.responses.context_class=normal
managed.ornith_35b.responses.request_runes=407
managed.ornith_35b.responses.estimated_input_tokens=126
managed.ornith_35b.responses.eligible_capacity=3
managed.ornith_35b.responses.request_count=4
managed.ornith_35b.responses.http_200_count=4
managed.ornith_35b.responses.completed_count=4
managed.ornith_35b.responses.done_count=4
managed.ornith_35b.responses.error_count=0
managed.ornith_35b.responses.duration_ms_min=35451
managed.ornith_35b.responses.duration_ms_max=57073
managed.ornith_35b.responses.selected_peak_in_flight=3
managed.ornith_35b.responses.selected_max_queued=1
managed.ornith_35b.responses.selected_final_in_flight=0
managed.ornith_35b.responses.selected_final_queued=0
managed.ornith_35b.responses.outcome=pass
managed.ornith_fast.chat.run_id=1786601679237576000-53b4cfe86fa3dc40
managed.ornith_fast.chat.selected_provider=rtx5090-lemonade
managed.ornith_fast.chat.context_class=normal
managed.ornith_fast.chat.request_runes=432
managed.ornith_fast.chat.estimated_input_tokens=135
managed.ornith_fast.chat.eligible_capacity=1
managed.ornith_fast.chat.request_count=2
managed.ornith_fast.chat.http_200_count=2
managed.ornith_fast.chat.finish_count=2
managed.ornith_fast.chat.done_count=2
managed.ornith_fast.chat.error_count=0
managed.ornith_fast.chat.duration_ms_min=3402
managed.ornith_fast.chat.duration_ms_max=6602
managed.ornith_fast.chat.selected_peak_in_flight=1
managed.ornith_fast.chat.selected_max_queued=1
managed.ornith_fast.chat.selected_final_in_flight=0
managed.ornith_fast.chat.selected_final_queued=0
managed.ornith_fast.chat.outcome=pass
managed.ornith_fast.responses.run_id=1786601686638286000-825708e93ce3450d
managed.ornith_fast.responses.selected_provider=rtx5090-lemonade
managed.ornith_fast.responses.context_class=normal
managed.ornith_fast.responses.request_runes=408
managed.ornith_fast.responses.estimated_input_tokens=127
managed.ornith_fast.responses.eligible_capacity=1
managed.ornith_fast.responses.request_count=2
managed.ornith_fast.responses.http_200_count=2
managed.ornith_fast.responses.completed_count=2
managed.ornith_fast.responses.done_count=2
managed.ornith_fast.responses.error_count=0
managed.ornith_fast.responses.duration_ms_min=3223
managed.ornith_fast.responses.duration_ms_max=6399
managed.ornith_fast.responses.selected_peak_in_flight=1
managed.ornith_fast.responses.selected_max_queued=1
managed.ornith_fast.responses.selected_final_in_flight=0
managed.ornith_fast.responses.selected_final_queued=0
managed.ornith_fast.responses.outcome=pass
edge.auth.active_control_plane_token_count=1
edge.auth.preflight_http_status=200
edge.auth.preflight_model_count=9
raw_material_in_tracked_evidence=false
release_finish=done
release_tag=dev-974
release_tag_tree=d46c7e3057bafbeedc1ddb10d2619feb3c6eb2db
release_main_sha=fd41adac777f7c7faea0cdd27acbe2817890abf8
release_dev_sha=019500a5d65117bfd115e0c33601ceef3461ee52
release_atomic_push=pass
release_remote_branch_deleted=true

View file

@ -0,0 +1,17 @@
---
spec_doc_type: archive-log
spec_id: testing/agent-comparison-benchmark
status: 폐기됨
source_evidence:
- type: user
path: null
notes: 2026-08-13 전용 하네스를 폐기하고 공식 caller별 수동 개별 검증을 우선하도록 결정
- type: roadmap
path: agent-roadmap/archive/phase/knowledge-tool-optimization-extension/milestones/iop-one-shot-agent-model-comparison.md
notes: 폐기된 측정 Milestone과 경계 분리 근거
---
# 폐기 기록: Agent 비교 벤치마크 파이프라인
전용 manifest runner, 재개·복구 저장소, 자동 웹 검증, 익명 채점과 보고 파이프라인은 2026-08-13 폐기했다.
현재 IOP 호출부는 완료된 제품 기준선으로 유지한다. 구체적 결함이 재현되면 해당 코드와 로드맵 소유 영역에서 국소 처리하며, 전용 benchmark CLI나 전수 안정성 campaign은 현재 구현·완료 표면이 아니다.

View file

@ -27,7 +27,6 @@ AI agent가 작업 전에 읽는 지도이기도 하지만, 사람도 "지금
- 출력 검증 런타임: staged response-start, evidence hold/release, filter arbitration, bounded recovery/rebuild, raw-free observation은 `runtime/stream-evidence-gate`에서 본다.
- 외부 HTTP 입력: OpenAI-compatible 호출, Anthropic-compatible Messages 호출, managed principal route/slot binding, model-driven raw tunnel은 `input/openai-compatible-surface`, A2A JSON-RPC 호출은 `input/a2a-json-rpc-surface`에서 본다.
- 운영 제어: Control Plane, credential HTTPS/host-local bootstrap, mTLS Edge enrollment, projection/lease flow, fleet/edge status, Flutter Client 상태 소비는 `control/control-plane-operations`에서 본다.
- 테스트 도구: Claude Code, agy, Codex의 IOP 경유 비교를 manifest로 실행하고 격리·측정·웹 검증·익명 채점·보고하는 현재 harness는 `testing/agent-comparison-benchmark`에서 본다.
## 스펙 목록
@ -39,7 +38,6 @@ AI agent가 작업 전에 읽는 지도이기도 하지만, 사람도 "지금
| `input/openai-compatible-surface` | 부분 | `/v1/models`, `/v1/chat/completions`, `/v1/responses`, `/v1/messages`, `/v1/messages/count_tokens`, `/anthropic/v1/models`, managed projection/slot routing, OpenAI-compatible auth/metadata/tool handling, Anthropic bearer/`X-Api-Key` auth, provider-pool native/bridge admission, safe slot attribution, marked single-request 내부 stage template과 caller-visible I/O 경계, and OpenAI-only usage metrics를 확인할 때 | `agent-spec/input/openai-compatible-surface.md` | `agent-contract/outer/openai-compatible-api.md`, `agent-contract/outer/anthropic-compatible-api.md`, `apps/edge/internal/openai/chat_handler.go`, `apps/edge/internal/openai/anthropic_handler.go`, `apps/edge/internal/openai/anthropic_bridge.go`, `apps/edge/internal/openai/normalized_sse.go`, `apps/edge/internal/openai/usage_metrics.go` |
| `input/a2a-json-rpc-surface` | 부분 | Edge A2A JSON-RPC, `message/send`, `tasks/get`, `tasks/cancel`, A2A task store와 bearer auth를 확인할 때 | `agent-spec/input/a2a-json-rpc-surface.md` | `agent-contract/outer/a2a-json-rpc-api.md`, `apps/edge/internal/input/a2a/server.go`, `apps/edge/internal/input/a2a/task_store.go` |
| `control/control-plane-operations` | 부분 | credential HTTPS and host-local bootstrap, Control Plane-Edge mTLS projection/lease wire, Client-Control Plane wire, Control Plane HTTP Edge/fleet status view, Flutter Client status consumer를 확인할 때 | `agent-spec/control/control-plane-operations.md` | `agent-contract/inner/control-plane-edge-wire.md`, `agent-contract/inner/client-control-plane-wire.md`, `apps/control-plane/internal/wire/edge_server.go`, `apps/control-plane/internal/credentiallease/service.go` |
| `testing/agent-comparison-benchmark` | 구현됨 | Claude Code, agy, Codex의 IOP 경유 benchmark manifest, preflight, run/resume, 격리, timing/usage, 웹 gate, 익명 채점과 Markdown report를 확인할 때 | `agent-spec/testing/agent-comparison-benchmark.md` | `scripts/agent_comparison_benchmark.py`, `scripts/agent_benchmark/attempts.py`, `scripts/agent_benchmark/scoring.py`, `scripts/agent_benchmark/reporting.py` |
## 작성 규칙

View file

@ -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 해석
@ -74,13 +80,22 @@ source_evidence:
notes: Anthropic native tunnel response relay with header allowlist
- type: code
path: apps/edge/internal/openai/anthropic_bridge.go
notes: Anthropic Messages ↔ Chat Completions bidirectional bridge
notes: Anthropic Messages ↔ Chat Completions request/response bridge
- type: code
path: apps/edge/internal/openai/provider_normalization.go
notes: Caller-neutral provider operation selection, effort fallback, Messages ↔ Responses conversion
- type: code
path: apps/edge/internal/openai/anthropic_stream.go
notes: Chat/Responses provider output을 Anthropic Messages JSON/SSE로 변환
- type: code
path: apps/edge/internal/openai/anthropic_types.go
notes: Anthropic request/response types, header validation, content block decode
- type: test
path: apps/edge/internal/openai/anthropic_bridge_test.go
notes: Claude Code beta/request mapping과 Gemini thought signature 왕복 검증
notes: Messages compatibility mapping, Responses operation selection, effort fallback, Gemini thought signature 왕복 검증
- type: test
path: apps/edge/internal/openai/responses_protocol_profile_test.go
notes: Responses effort nearest-lower mapping과 나머지 reasoning field 보존 검증
- type: code
path: apps/edge/internal/openai/principal.go
notes: Shared principal token hash auth for both OpenAI and Anthropic surfaces
@ -89,7 +104,7 @@ source_evidence:
notes: Shared provider tunnel auth headers and passthrough
- type: code
path: packages/go/config/protocol_profile.go
notes: ConcreteProtocolProfile, ProtocolOperation, ProtocolDriver, capability admission, model mapping
notes: ConcreteProtocolProfile, operation capability admission, model/effort normalization
- type: code
path: apps/edge/internal/openai/run_result.go
notes: RunEvent stream을 OpenAI-compatible result로 수집
@ -189,7 +204,8 @@ Edge가 OpenAI-compatible HTTP 요청을 받아 내부 `adapter + target` 실행
| Anthropic ingress | `POST /v1/messages` and `POST /anthropic/v1/messages` share one handler; the corresponding count-tokens paths share another. `/anthropic/v1/models`, and `/v1/models` with `anthropic-version`, return the Anthropic model-list shape. Wrong methods return `405 invalid_request_error`. |
| 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. |
| Claude Code Chat bridge | Supported Claude Code beta headers are consumed at the bridge, adaptive effort (low/medium/high/xhigh/max) maps to Chat `reasoning_effort`, JSON schema output maps to `response_format`, Anthropic metadata/cache-control annotations are stripped, Gemini tool thought signatures round-trip through opaque tool-use ids, and unsigned private thinking replay is dropped only for generic Chat profiles that cannot represent it. |
| 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. |
@ -203,7 +219,7 @@ Edge가 OpenAI-compatible HTTP 요청을 받아 내부 `adapter + target` 실행
| reasoning observation metric | provider가 reasoning token을 보고하지 않고 reasoning text만 관측되면 관측 횟수와 character count 보조 metric을 emit하고, 별도 estimated-token counter(`iop_openai_reasoning_estimated_tokens_total`)로 `estimation_method="chars_div_4"` 추정을 제공한다. |
| Grafana usage surface | 1차 조회 표면은 Prometheus/Grafana query guide이며 actual `provider_id`·`served_model` 기준 daily/monthly rollup과 `usage_attribution=model_group`으로 승인된 `route_model` query-time rollup, usage origin breakdown, operator-managed cloud price baseline, cloud-equivalent cost, avoided-cost ROI 기준을 문서로 제공한다. Control Plane/Client dashboard와 request-level ledger는 후속 범위다. |
| Responses API | normalized(non-provider) `/v1/responses` supports only non-streaming string input. A provider model-group route relays `/v1/responses` to the selected provider when that candidate declares the Responses operation/capability; this is not exclusive to one driver. |
| Responses provider passthrough | provider-pool model group route와 direct OpenAI-compatible provider route의 `/v1/responses`는 provider raw tunnel을 사용한다. Edge는 `model`만 served target으로 rewrite하고 unknown/Codex field와 `stream:true` raw SSE를 provider로 relay한다. Usage is recorded with endpoint=`responses`, response_mode=`passthrough`, route_model=request alias, and the selected actual provider/served model. Responses는 선택적 기능이다. |
| Responses provider passthrough | provider-pool model group route와 direct OpenAI-compatible provider route의 `/v1/responses`는 provider raw tunnel을 사용한다. Edge는 served `model`과 필요한 operation-normalized `reasoning.effort`만 rewrite하고 unknown/Codex field, 다른 reasoning field와 `stream:true` raw SSE를 보존한다. Usage is recorded with endpoint=`responses`, response_mode=`passthrough`, route_model=request alias, and the selected actual provider/served model. Responses는 선택적 기능이다. |
| strict output | strict output이 켜져 있으면 XML completion contract 기반 instruction 또는 prompt prefix를 추가할 수 있다. |
| tool call 처리 | Chat Completions `tools`는 provider native metadata 복원 또는 text tool-call synthesis/validation 경로를 사용한다. Anthropic Messages `tools`는 Chat bridge를 통해 OpenAI `tools`로 변환되거나, native Anthropic tunnel로 직접 전달된다. |
| cancel 전파 | HTTP caller timeout/cancel이 cancel-worthy error이면 Node `CancelRun`으로 전파한다. |
@ -252,7 +268,7 @@ sequenceDiagram
- `credential_plane.enabled` is the startup-only managed/legacy switch. Managed mode requires TLS on OpenAI ingress, CP-Edge, and Edge-Node hops; config validation rejects legacy principal/provider-auth and static provider credential sources.
- Managed authentication and model resolution use one immutable projection view per request. Trusted principal/route/slot/revision metadata overwrites caller spoofing and remains bound across recovery admission.
- `openai.stream_evidence_gate.enabled` defaults to false and activates configured semantic policy only. Supported OpenAI response/liveness ownership remains in the request runtime in both states; the same config also supplies the 0..3 recovery cap and up-to-16-MiB ingress snapshot bound. Changes remain restart-required.
- A typed stall recovery re-enters provider-pool admission with the failed provider avoided. Exact `available` is the sole health classification that allows same-provider fallback when no alternate exists.
- A typed stall recovery re-enters provider-pool admission with the failed provider avoided. No health classification allows the stalled provider to be selected again for the same request; without an alternate, recovery terminates once.
- `iop_edge_liveness_recovery_eligibility_total` labels are `execution_path`, `provider_health`, `commit_state`, and `eligibility`; `iop_edge_liveness_recovery_results_total` labels are `execution_path`, `provider_health`, and `recovery_result`. All are closed vocabularies and exclude request/attempt/provider/model identifiers and content.
- When `repeat_guard` is configured, Chat accepts plain `content`, `reasoning_content`, `reasoning`, and `reasoning_text` provenance for fingerprinting; Responses accepts its own text/reasoning/function-call item provenance. Signed, encrypted, and unknown values are canonical-only and never sanitation or observation payloads.
- Completed action/result fingerprints provide the only request-history progress boundary. An identical consecutive action/result is no-progress; a changed completed result is progress, while a different action alone is insufficient. No caller product, session metadata, inferred TTL, or cross-request cache participates.
@ -271,6 +287,7 @@ sequenceDiagram
- In legacy mode, `openai.provider_auth` stores only a forwarding rule and reads raw provider material from its request-time header; inbound IOP authorization is never reused. Managed mode rejects that rule and the caller header and uses only the sealed slot lease.
- OpenAI request metadata is bounded caller context. Workspace, runtime, and session ownership are outside this input surface.
- Chat Completions와 Responses request는 caller metadata로 provider raw tunnel과 normalized response shape를 선택하지 않는다. route/provider capability만 실행 경로를 결정한다.
- Provider operation selection never branches on caller/agent identity. It evaluates the selected profile against normalized request requirements. Effort uses `none < low < medium < high < xhigh < max`; an unsupported grade may fall only to the nearest declared lower grade, and no lower grade means fail-closed admission.
- run metadata에는 `openai_model`, `openai_stream`, `strict_output`, `estimated_input_tokens`, `context_class`가 들어갈 수 있다.
- provider tunnel metadata에는 routing context와 관측 후보가 들어갈 수 있으며, provider body에는 합쳐지지 않는다.
- Node complete event metadata의 `openai_tool_calls``openai_text_tool_fallback`은 response tool call 복원에 쓰인다.
@ -326,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`).
@ -354,6 +372,8 @@ sequenceDiagram
- 2026-08-02: Removed IOP-owned workspace and Agent/CLI runtime semantics while preserving bounded metadata, managed projection, and credential lease behavior.
- 2026-08-05: Added Claude Code adaptive-effort/structured-output/cache-control bridge compatibility, stateless Gemini thought-signature tool round trips, and generic Chat replay handling for unsigned private thinking blocks.
- 2026-08-09: Extended `output_config.effort` to accept `low`, `medium`, `high`, `xhigh`, and `max` across Anthropic native and Chat bridge routes without substitution or normalization. Unknown effort values remain `400 invalid_request_error` before provider dispatch. Deterministic Go coverage added for exact bridge mapping, native `max` preservation, and invalid-value rejection. (`apps/edge/internal/openai/anthropic_types.go`, `apps/edge/internal/openai/anthropic_bridge_test.go`, `apps/edge/internal/openai/anthropic_native_test.go`)
- 2026-08-13: Added caller-neutral provider operation normalization for Messages/Responses routes. Tool-bearing adaptive effort can select Responses when Chat cannot preserve the combination, and unsupported effort grades fall only to the nearest declared lower grade (for example `max` to `xhigh`).
- 2026-08-13: Gemini-native path parsing now keeps route IDs strict while accepting the bounded URL-encoded official caller model label `Gemini 3.6 Flash`.
- 2026-08-06: Synchronized always-owned Chat/Responses typed-stall recovery, provider avoidance/fallback admission, and closed-label liveness operational evidence with the current runtime, contracts, and deterministic recovery tests.
- 2026-08-06: Added marked single-request Messages admission through the separate service coordinator capability, one unlabeled runtime ingress counter, buffered sanitized terminal acknowledgement, and deterministic real-POST compatibility evidence.
- 2026-08-06: Added the marked streaming subset with fixed plan/work/review/repair progress, liveness ping, serialized monotonic text blocks, private-wire exclusion, one success/error terminal, joined ticker shutdown, and post-`message_stop` completion acknowledgement.

View file

@ -233,13 +233,13 @@ The shared `packages/go/execution` package contains provider lifecycle, registry
| Work stage | The `ornith-fast` Work runner reads the closed PLAN artifact, projects only the admitted workspace tools, and resumes the same frozen provider route after exactly correlated Node results. It rejects any Work `reasoning_effort`, malformed or multiple tool calls, and empty completion or verification evidence. |
| request-owned cleanup | Node creates and inventories only `.iop/job/<request_id>` internal state, cancels and waits for all active command groups, validates the exact tree without following entries, and removes matching artifacts deepest-first with non-recursive descriptor operations. Symlinks, special files, foreign devices, identity replacements, and unowned entries fail closed. User results and sibling request state are preserved. Concurrent cleanup callers receive one bounded cached typed result. |
| provider raw tunnel | 선택된 provider의 HTTP/SSE를 `ProviderTunnelRequest`/`ProviderTunnelFrame`으로 relay하며 순서와 단일 terminal outcome을 보장한다. |
| response-stall activity contract | 선택된 provider의 response-stall timeout을 normalized/tunnel request에 보존한다. Node는 wire zero를 `300000ms`로 해석하고 invalid raw value를 adapter 호출 전에 거부한다. Runtime event의 terminal type은 payload/usage보다 우선하며 non-terminal usage는 progress다. |
| response-stall activity contract | 선택된 provider의 response-stall timeout을 normalized/tunnel request에 보존한다. Node는 wire zero를 `60000ms`로 해석하고 invalid raw value를 adapter 호출 전에 거부한다. Runtime event의 terminal type은 payload/usage보다 우선하며 non-terminal usage는 progress다. |
| Node stall watchdog | Node가 normalized run과 raw tunnel에 하나의 activity watchdog을 적용한다. progress만 timer를 reset하며, stall은 `response_stalled` terminal 하나와 Node-owned safe metadata를 만들어 normalized `RunEvent`와 raw `ProviderTunnelFrame` wire의 optional typed `ExecutionFailure` 필드에 싣는다. stall claim 뒤에는 bounded close grace fence와 독립 exact-target health probe를 직렬 확장 없이 join한다. close grace 안에 provider return이 확인된 경우만 `Retryable` capability hint를 준다. |
| Node health evidence join | stall terminal에 three-way health evidence를 싣는다: `provider_health` status와 `liveness_classification` normalization이 `available`/`request_stalled`, `unavailable`/`provider_unhealthy`, `unknown`/`health_unknown` 쌍으로 fail-closed된다. probe 성공은 progress reset·fence 변경·retry authority가 아니며 late output은 fenced 상태를 유지한다. |
| health observation sequence | transport Session이 connection-scoped monotonic `health_observation_seq`를 소유한다. 새 connection은 0에서 시작해 첫 finalized observation이 1이며, 같은 connection의 normalized/tunnel observation이 source를 공유해 동시에도 유일 증가값을 받는다. internal/unbound 경로는 key를 생략한다. |
| Edge terminal health handoff | Edge validates authoritative reception node/generation plus the immutable provider/adapter/target lease before applying typed stall evidence. Every validated current bound stall receives `provider_id`, validated health, and `recovery_handoff=confirmed`, while only fresh unavailable evidence lowers a separate runtime overlay; the token never grants replay eligibility. Every valid current terminal still releases its lease exactly once. |
| CAPABILITIES recovery | Node runs the same bounded exact-target `ProbeHealth` and returns stable adapter/target/status plus the next Session sequence. Edge recovers exactly one matching current-generation unavailable provider only from a strictly newer `available` result; malformed, ambiguous, stale, unknown, and unavailable responses are no-ops. |
| recovery candidate preference | `ProviderPoolDispatchRequest` carries `AvoidProviderID` and `AllowAvoidedProviderFallback`. Every admission (initial and queued re-resolution) prefers a runtime-eligible alternate over the avoided provider; only the explicit fallback flag (derived from exact probe-backed `available` evidence) permits re-selecting the avoided provider when no alternate exists. Zero values preserve current selection. This is selection policy only: no retry loop, slot reservation, priority change, persistence, or retry counter. |
| recovery candidate preference | `ProviderPoolDispatchRequest` carries `AvoidProviderID` and `AllowAvoidedProviderFallback`. Every admission (initial and queued re-resolution) requires a runtime-eligible alternate over the avoided provider for liveness recovery. `available` probe evidence describes endpoint health but never permits re-selecting the stalled provider for the same request. Liveness replay is request-locally capped at one, so a replacement-attempt stall terminates. Zero values preserve current selection. This is selection policy only: no retry loop, slot reservation, priority change, persistence, or retry counter. |
| OpenAI typed-stall consumption | Every supported Chat/Responses normalized or tunnel request has one unconditional runtime liveness owner, independent of configured semantic activation. It converts only the Edge-confirmed typed stall handoff into a raw-free StreamGate event, owns pre-commit eligibility, and closes the already fenced old transport before re-admission; Node does not grant replay authority. |
| tunnel-tolerant liveness | Edge와 Node는 30초 heartbeat interval과 45초 response wait를 공통으로 사용해 긴 prompt prefill이나 streaming backpressure 중의 정상 connection을 조기에 끊지 않는다. |
| reconnect/generation fencing | 현재 connection이 종료되면 해당 generation만 fence하고 Node supervisor가 reconnect한다. Heartbeat wait를 넘긴 경우의 close reason은 `heartbeat_timeout`이다. |

View file

@ -50,7 +50,10 @@ source_evidence:
notes: lease state와 candidate pressure 기반 online/offline provider snapshot
- type: code
path: packages/go/config/protocol_profile.go
notes: ConcreteProtocolProfile, ProtocolOperation, ProtocolDriver, overlay validation, alias normalization, capability admission, model mapping
notes: ConcreteProtocolProfile, overlay validation, capability admission, model/operation/effort normalization
- type: test
path: packages/go/config/protocol_profile_test.go
notes: Effort exact/nearest-lower mapping, tools 조합, 상향 매핑 거부 검증
- type: code
path: apps/edge/internal/configrefresh/classify.go
notes: dry-run/apply classification과 changed path report 생성
@ -115,7 +118,7 @@ Edge 설정에서 provider-pool이 어떻게 모델 실행 후보를 고르고,
| usage attribution policy | `models[].usage_attribution``provider|model_group`만 허용하고 생략 시 provider 귀속으로 해석한다. model-group 귀속은 운영자의 명시적 opt-in이다. |
| provider mapping | `models[].providers`는 provider id를 실제 served model name으로 매핑한다. |
| node provider catalog | `nodes[].providers[]`는 Node 아래 resource/provider catalog이며 provider id는 Edge config에서 전역 유일해야 한다. |
| response-stall timeout | `response_stall_timeout_ms`는 provider별 response-stall timeout이다. zero/omitted는 `300000ms`, invalid negative/overflow 값은 validation error이며 selected candidate의 effective 값은 normalized/tunnel request에 보존된다. |
| response-stall timeout | `response_stall_timeout_ms`는 provider별 response-stall timeout이다. zero/omitted는 `60000ms`, invalid negative/overflow 값은 validation error이며 selected candidate의 effective 값은 normalized/tunnel request에 보존된다. |
| config validation | config load가 provider id 참조, served model membership, numeric bounds, long-context budget을 검증한다. |
| provider 후보 필터링 | dispatch는 dispatch-ready connection을 가진 Node의 provider 후보 중 catalog match, enabled, healthy/available, capacity 조건을 만족하는 후보만 사용한다. protocol profile capability(`messages`, `chat`, `responses`, `streaming`, `tool_calling`, `count_tokens`, `models`)는 operation별 admission에 사용된다. |
| provider 전역 capacity/priority dispatch | `node_id + provider_id` lease가 여러 model group의 일반·long in-flight를 합산한다. available 후보 중 낮은 in-flight를 고르고 동률이면 낮은 `priority`와 round-robin을 적용한다. |
@ -124,6 +127,7 @@ Edge 설정에서 provider-pool이 어떻게 모델 실행 후보를 고르고,
| provider snapshot | 일반·long in-flight는 provider lease state, queued 값은 Edge queue에서 해당 provider를 후보로 포함하는 고유 pending request pressure에서 계산한다. offline provider는 catalog identity를 유지하고 effective 수치를 0으로 보고한다. |
| runtime provider health overlay | A confirmed current bound unavailable stall lowers a separate `(node_id, connection_generation, provider_id)` overlay. The provider is excluded from effective admission and its snapshot projects unavailable with zero effective capacity/counters, while configured health remains unchanged. Only a later exact higher-sequence available CAPABILITIES probe recovers it; inconclusive evidence is a no-op. Post-decision metrics/logs expose only closed source, health, decision, and state-change values; they contain no resource identity or raw request/response data. |
| mixed provider execution path | 같은 model group의 OpenAI-compatible provider와 Ollama/native provider를 같은 후보군으로 두며, 선택된 provider capability로 passthrough 또는 normalized 실행 경로를 결정한다. OpenAI-compatible provider는 `openai_chat`, `anthropic_messages`, 또는 `openai_responses` driver로 해석된다. |
| provider operation normalization | `protocol_profiles[].normalization.effort` records operation-scoped provider wire, supported normalized grades, tools compatibility, and explicit token-budget compatibility. Request admission uses these facts rather than caller identity; exact grade misses use only the nearest lower declared grade and never upgrade. |
| long-context admission | estimated input token이 threshold 이상이면 `context_class=long`으로 분류하고, provider long slot이 있으면 일반 capacity slot과 함께 점유한다. |
| config refresh dry-run/apply | loopback admin HTTP `POST /refresh`가 candidate config를 dry-run 또는 apply한다. |
| refresh classification | listener, Edge identity, bootstrap path, adapter structural 변경 등은 restart-required로 분류한다. |
@ -186,9 +190,10 @@ sequenceDiagram
- `long_context_threshold_tokens` 기본 예시는 `100000`이고 0 이하 값은 config load에서 거부된다.
- `credential_plane.enabled=true` requires Edge-Node server TLS, an enabled TLS Control Plane connector, and OpenAI ingress TLS when enabled. The Edge cannot combine managed mode with `openai.bearer_token`, `openai.principal_tokens[]`, `openai.provider_auth`, or static provider credential sources.
- Node managed mode requires Edge transport TLS, `recipient_key_id`/recipient private-key path, issuer key id/public-key path, and a bounded replay cache. All cert/key/keyring values are external file references and credential-plane changes are restart-required.
- `protocol_profiles` is the top-level catalog of custom overlays. A `ProtocolProfileConf` supplies `base`, `driver`, `base_url`, operation paths, `auth`, `capabilities`, `model_mapping`, and `extensions`; `base` inheritance is separate from legacy provider-type normalization.
- `protocol_profiles` is the top-level catalog of custom overlays. A `ProtocolProfileConf` supplies `base`, `driver`, `base_url`, operation paths, `auth`, `capabilities`, `model_mapping`, `normalization`, and `extensions`; `base` inheritance is separate from legacy provider-type normalization.
- `normalization.effort[operation]` must reference a declared operation and a recognized wire. Grade keys are the closed `none|low|medium|high|xhigh|max` order. Runtime mapping takes the exact key or nearest lower key; canonical mapped values cannot exceed the source grade. `with_tools` and `token_budget` describe whether that operation preserves the corresponding semantic combination.
- `nodes[].providers[].profile` selects a catalog entry. Config normalization resolves that selection (or a legacy type alias) into the runtime-only `RuntimeProfile` snapshot; the source YAML remains a selector plus catalog, not a per-model overlay.
- `nodes[].providers[].response_stall_timeout_ms` is validated at config load: zero/omitted resolves to `300000ms`; safe positive values are retained; negative and duration-overflow values are rejected. Its effective value is immutable for the selected provider attempt and survives queue re-resolution for both execution paths.
- `nodes[].providers[].response_stall_timeout_ms` is validated at config load: zero/omitted resolves to `60000ms`; safe positive values are retained; negative and duration-overflow values are rejected. Its effective value is immutable for the selected provider attempt and survives queue re-resolution for both execution paths.
- Profile catalog and provider-selector changes are restart-required. Snapshot immutability describes loaded runtime state and does not make those changes live-applicable.
- `ConcreteProtocolProfile.MapModel(model)`은 provider의 model alias 정규화를 수행한다. provider가 model mapping을 정의하면 IOP external `model` key를 provider served target으로 변환한다.
- `ConcreteProtocolProfile.ResolveOperationURL(op)` returns the complete resolved upstream URL. Absolute operation URLs are returned unchanged, while relative operation paths are joined once to the normalized base URL; the listed `/v1/...` values are operation-path inputs, not return values.
@ -198,7 +203,7 @@ sequenceDiagram
- `filters[].hold_evidence_runes` is bounded `1..65536` and defaults to 500. For `repeat_guard` it controls the Unicode pending/look-behind evidence window, not a time-based release or a cross-request retention period.
- Blocking repeat capability admission is re-resolved for the actual provider/path while the request-start filter policy, history snapshot, recovery ordinals, and temperature candidate order remain generation-stable across provider switches.
- `provider_pool.max_queue`는 0/생략 시 기본값 `16`, `queue_timeout_ms`는 생략 시 `30000`이고 명시적 0은 timeout 없음이다. canonical root key가 없을 때만 서로 같은 legacy provider queue pair를 승격하며 값이 다르면 load를 거부한다.
- `nodes[].providers[].capacity``long_context_capacity`는 provider resource 속성이고 같은 provider를 공유하는 model alias가 합산 점유한다. `total_context_tokens`는 runtime ledger가 아니라 `context_window_tokens * long_context_capacity` 정적 validation 값이다.
- `nodes[].providers[].capacity``long_context_capacity`는 provider resource 속성이고 같은 provider를 공유하는 model alias가 합산 점유한다. 같은 key의 legacy adapter capacity는 transport 상한이므로 provider admission capacity는 그 이하일 수 있고 상한 초과만 거부한다. `total_context_tokens`는 runtime ledger가 아니라 `context_window_tokens * long_context_capacity` 정적 validation 값이다.
- `models[].usage_attribution`은 생략 시 `provider`, 명시값은 `provider|model_group`만 허용한다. 변경은 model catalog policy 변경으로 live apply되며 `models["<id>"].usage_attribution` 경로로 보고한다.
- provider `enabled=false`는 dispatch pool에서 제외하지만 adapter process lifecycle 변경을 의미하지 않는다.
- Runtime health is not a config-refresh field. The overlay never rewrites `nodes[].providers[].health`, is discarded across connection generations, and participates only in effective candidate eligibility and snapshot projection.
@ -258,5 +263,6 @@ sequenceDiagram
- 2026-08-05: Added the separate generation-scoped runtime provider health overlay, effective admission/snapshot exclusion, config-health immutability, and exact higher-sequence CAPABILITIES recovery.
- 2026-08-05: Added post-decision provider-health operational evidence with bounded counters and structured logs, isolated from overlay state and provider identity.
- 2026-08-09: Synchronized the single-request effective-template boundary: relative-only `plan_file`/`review_file` resolution against the `edge.yaml` directory with pre-access absolute rejection, independent per-file default fallback, load-time regular-file/size/UTF-8/grammar rejection, digest-only refresh diff evidence, and admission-time freezing so a refresh reaches only newly admitted requests (`packages/go/config/load.go`, `apps/edge/internal/configrefresh/classify.go`, `apps/edge/internal/openai/single_request_preset_binding.go`).
- 2026-08-13: Added operation-scoped provider effort normalization facts and nearest-lower, never-upgrade mapping semantics to the protocol profile catalog.
- 2026-08-06: Synchronized the fixed single-request policy (`execution_presets[].single_request`) absolute caps, plan→work→review stage shape, opaque `workspace_ref`, live-apply classification, and snapshot-isolation semantics with current code, contract, and classifier implementation.
- 2026-08-06: Required effective positive workspace-operation bounds and clarified that the later Node-private typed config/admission transport is deferred; public/preset/provider surfaces retain no raw workspace roots or command templates.

View file

@ -1,143 +0,0 @@
---
spec_doc_type: spec
spec_id: testing/agent-comparison-benchmark
status: 구현됨
source_evidence:
- type: code
path: scripts/agent_comparison_benchmark.py
notes: validate, preflight, run, resume, status, score, report 공개 CLI
- type: code
path: scripts/agent_benchmark/manifest.py
notes: manifest 정규화, 경로 격리, fixture와 입력 digest 검증
- type: code
path: scripts/agent_benchmark/attempts.py
notes: append-only run/attempt 저장과 preflight, 실행, 재개 lifecycle
- type: code
path: scripts/agent_benchmark/connectivity.py
notes: caller capability, requested/effective binding과 blocker 분류
- type: code
path: scripts/agent_benchmark/measurement.py
notes: source-aware timing과 usage 정규화
- type: code
path: scripts/agent_benchmark/web_validation.py
notes: 정적 웹 산출물과 desktop/mobile 자동 gate 검증
- type: code
path: scripts/agent_benchmark/browser_cdp.py
notes: Chromium/CDP 단일 시도 격리와 닫힌 transient class의 최대 3회 fresh 재시작
- type: code
path: scripts/agent_benchmark/scoring.py
notes: 익명화 입력, fresh evaluator와 scoring attempt 처리
- type: code
path: scripts/agent_benchmark/reporting.py
notes: deterministic Markdown 보고서 생성
- type: test
path: scripts/agent_benchmark/connectivity_integration_test.py
notes: 세 caller의 IOP binding, lifecycle, 격리와 실패 경계 통합 검증
- type: test
path: scripts/agent_benchmark/scoring_test.py
notes: unscored, scoring_failed, 새 scoring attempt와 익명화 검증
- type: test
path: scripts/agent_benchmark/reporting_test.py
notes: all-status, 동점과 raw evidence 포인터 보고 검증
- type: test
path: scripts/agent_benchmark/skill_contract_test.py
notes: project-local skill과 공개 CLI 계약 검증
- type: test
path: scripts/agent_benchmark/browser_cdp_test.py
notes: transient 재시도·소진·비재시도와 process/screenshot 정리 검증
- type: contract
path: agent-contract/outer/openai-compatible-api.md
notes: agy와 Codex가 사용하는 IOP OpenAI-compatible ingress 계약
- type: contract
path: agent-contract/outer/anthropic-compatible-api.md
notes: Claude Code가 사용하는 IOP Anthropic-compatible ingress 계약
- type: contract
path: agent-contract/inner/edge-config-runtime-refresh.md
notes: model, route, execution preset과 protocol profile 설정 계약
- type: roadmap
path: agent-roadmap/archive/phase/knowledge-tool-optimization-extension/milestones/agent-comparison-benchmark-pipeline.md
notes: bench-01 완료 범위와 evidence 집계
- type: sdd
path: agent-roadmap/archive/sdd/knowledge-tool-optimization-extension/agent-comparison-benchmark-pipeline/SDD.md
notes: benchmark lifecycle, 실패 보존, 채점과 secret-safe evidence 결정
---
# 스펙: Agent 비교 벤치마크 파이프라인
## 목적
Claude Code, agy, Codex가 IOP를 경유해 수행하는 동일 과업을 설정만 바꿔 반복 실행하고, 연결 상태부터 실행·검증·채점·보고까지 재현 가능한 evidence로 남기는 현재 benchmark harness를 설명한다.
## 기능 목록
| 기능 | 설명 |
|------|------|
| manifest 검증 | caller, IOP direct/preset route, model, effort, fixture, 반복 횟수, timeout, evaluator와 `agent-test/runs/<output-id>` 경로를 검증하고 canonical digest를 만든다. |
| 연결 preflight | Claude Code, agy, Codex의 binary/config와 IOP endpoint·auth·model·effort·stream binding을 확인하고 `ready`, `registration_required`, `implementation_gap`으로 분류한다. |
| 격리 실행과 재개 | 각 cell/repetition을 동일 checksum의 clean workspace와 fresh caller session에서 실행하며, 제품 결과·harness 정합성·process 종료를 독립 결과로 보존한다. top-level state는 `running`, `completed`, `timed_out`, `cancelled`, `interrupted`의 controller 상태만 나타내며, 실패한 attempt는 덮어쓰지 않는다. |
| 측정과 evidence | 제출, 첫 출력, 첫 파일 쓰기, model/tool/queue, finish/idle 시간을 관측 source와 함께 정규화한다. token은 보고 주체와 미제공 상태를 보존하며 임의 추정값을 authoritative 값과 섞지 않는다. |
| 웹 자동 검증 | product/harness 성공 여부와 무관하게 모든 terminal workspace에서 필수 HTML/CSS/JS와 로컬 이미지, 외부 asset 금지, desktop/mobile render, console/asset 오류, 반응형·접근성 gate와 screenshot을 확인한다. |
| 익명 품질 채점 | 필수 자동 gate를 통과한 결과만 identity를 가린 뒤 manifest에 고정된 fresh evaluator로 100점 rubric을 평가한다. 부적격 결과는 `unscored`, 평가 실패는 `scoring_failed`로 남기며 retry는 새 scoring attempt id를 사용한다. |
| 상태와 보고 | `validate`, `preflight`, `run`, `resume`, `status`, `score`, `report` CLI를 제공하고, controller/product/harness/process/artifact/scoring 축과 동점을 raw evidence 포인터와 함께 deterministic Markdown으로 만든다. |
## 범위
- 포함: benchmark manifest, caller adapter binding, run/attempt 저장, 격리 workspace, source-aware timing/usage, 정적 웹 gate, 익명 채점, Markdown 보고.
- 제외: IOP runtime 자체 구현, provider credential 등록, 실제 9개 비교군 실행과 모델 우열 결론. 해당 실행과 결론은 `[bench-02]`가 소유한다.
## 주요 흐름
```mermaid
flowchart LR
Operator[사용자 또는 project skill] --> CLI[benchmark CLI]
CLI --> Manifest[manifest 검증]
Manifest --> Preflight[caller와 IOP preflight]
Preflight --> Store[append-only run store]
Store --> Attempt[clean workspace와 fresh session attempt]
Attempt --> Evidence[timing, usage와 web evidence]
Evidence --> Score[익명 evaluator scoring]
Score --> Report[deterministic Markdown report]
```
## 계약
- OpenAI-compatible caller ingress는 [OpenAI-Compatible API](../../agent-contract/outer/openai-compatible-api.md)를 따른다.
- Claude Code ingress는 [Anthropic-Compatible Messages API](../../agent-contract/outer/anthropic-compatible-api.md)를 따른다.
- model, direct/preset route와 protocol profile의 기준은 [Edge Config And Runtime Refresh](../../agent-contract/inner/edge-config-runtime-refresh.md)를 따른다.
- 사용자-facing orchestration과 안전한 실행 순서는 [IOP Agent Comparison Benchmark skill](../../agent-ops/skills/project/iop-agent-comparison-benchmark/SKILL.md)이 소유하고, 제품 호출과 evidence 생성은 deterministic Python pipeline이 소유한다.
## 설정/데이터/이벤트
- manifest의 matrix cell은 stable id, caller, IOP route kind, requested/effective model과 effort를 가진다. unsupported alias나 effort는 다른 값으로 대체하지 않고 fail-closed한다.
- run state는 `agent-test/runs/<output-id>/<run-id>/` 아래에 격리되며 manifest digest가 다른 상태를 재개하지 않는다.
- preflight는 scored attempt가 아니며, 실행 중 실패·timeout·cancel과 scoring 실패는 기존 attempt를 수정하지 않고 보존한다.
- caller parser는 raw terminal 문자열 대신 `CallerEvent(finish|idle)`, `CallerTerminal(succeeded|failed)`와 typed metric만 반환한다. Claude result가 마지막 active assistant snapshot을 직접 완성하면 adapter가 typed finish와 idle을 함께 투영하고, assistant가 이미 finish를 냈으면 result는 idle만 투영한다. synthetic API error와 agy ERROR result는 `product=failed`, `harness=passed`가 될 수 있으며 parser malformed는 `product=unknown`, `harness=failed`로 구분한다.
- durable lifecycle/measurement/attempt evidence는 `product`, `harness`, `process` 객체를 그대로 저장한다. `unresolved`은 수집/검증 완결성(모든 슬롯이 웹 검증 증거 보유)이며, `passed`는 전체 gate 성공으로 유지되고 retry/skip를 제어한다. scoring eligibility는 변경없으며, terminal failure는 `unscored` report row로 유지된다. run/resume exit 0은 `unresolved=0`을 요구하며, 독립 실패 축은 stdout에 남고 `score`로 분류된다.
- 배포 qualification은 동일 clean source에서 5-cell direct manifest의 fresh preflight `ready=5`를 확인하고 정확히 5개의 fresh attempt를 한 번씩 실행한다. `unresolved=0`, `running=0`, `interrupted=0`, 모든 slot의 controller/product/harness/process/web-validation terminal evidence, exhausted browser/CDP infrastructure block 없음이 admission 조건이다. product failure, upstream HTTP rejection, caller failure 뒤 generated-missing과 timeout은 측정 결과로 보존하고 암묵 재시도하지 않는다. Edge pre-ingress incompatibility 또는 exhausted browser/CDP infrastructure block만 qualification을 막는다. admission 뒤 fresh C01-C09 preflight `ready=9`까지만 수행하고 hybrid 또는 scored C01-C09 실행은 후속 승인 전에는 할당하지 않는다.
- Chromium/CDP renderer는 source/product validation을 재실행하지 않고, process start·handshake·socket loss의 닫힌 transient class에만 fresh browser/profile로 최대 3회 시도한다. 각 실패 시도는 process group과 부분 screenshot을 정리하며 세 번째 실패는 `artifact=blocked`로 유지한다.
- lifecycle supervisor는 exit watcher와 출력 reader를 join한 뒤 하나의 child return code를 동결해 lifecycle result와 cleanup receipt가 동일한 exit/signal을 갖게 한다. 불일치 evidence는 resume에서 fail-closed한다.
- raw credential과 private endpoint는 tracked manifest, event, log, screenshot과 report에 기록하지 않는다.
- report는 run state의 canonical evidence에서 생성되며 성공하지 않은 결과를 0점으로 변환하거나 동점에 임의 순위를 부여하지 않는다.
## 검증
- `python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'` - 전체 benchmark unit/integration 계약이 통과한다.
- `python3 -m unittest scripts.agent_benchmark.reporting_test scripts.agent_benchmark.skill_contract_test` - 공개 보고 CLI와 project-local skill 계약이 통과한다.
- `python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-manifest.example.json` - 표준 manifest가 유효하다.
- `python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-supported-direct.example.json` - 지원 direct route fixture가 유효하다.
- `python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json` - direct preflight fixture가 유효하다.
## 한계와 주의사항
- example manifest와 fake adapter 테스트 통과는 실제 provider credential, endpoint 또는 preset의 live readiness를 의미하지 않는다. 실제 실행 전 redacted preflight가 필요하다.
- 현재 living spec은 비교 파이프라인의 구현 상태만 다룬다. 9개 scored cell 실행, 비교 결과와 결론은 후속 `[bench-02]` evidence로 관리한다.
- caller나 IOP가 제공하지 않은 usage는 `unavailable`로 남으며, 서로 다른 clock/source의 중첩 구간을 임의로 합산하지 않는다.
- benchmark 결과는 허용된 `agent-test/runs/` 경계 안에만 생성한다.
## 변경 기록
- 2026-08-13: direct 배포 qualification을 all-success에서 terminal-evidence admission으로 분리했다. fresh `ready=5`, 정확히 5개 attempt와 완결 evidence는 요구하되 제품 실패·timeout은 결과로 보존하고, Edge pre-ingress incompatibility 또는 exhausted browser/CDP infrastructure block만 qualification을 막는다. Chromium/CDP 닫힌 transient class에는 최대 3회 fresh renderer 시도를 추가했다.
- 2026-08-13: `unresolved`을 수집/검증 완결성으로 정의하고 `passed`(전체 gate 성공)와 분리했다. run/resume exit 0은 `unresolved=0`(모든 슬롯이 웹 검증 증거 보유)을 요구하며, 독립 실패 축은 stdout에 남고 `score`로 분류된다. 기존 attempt 바이트 변경 없음.
- 2026-08-12: caller terminal을 closed typed observation으로 바꾸고 product/harness/process 결과, failure-inclusive artifact gate, 독립 CLI/report/scoring gate와 direct-first qualification을 구현했다.
- 2026-08-12: official Claude result-direct/API-error 및 agy ERROR terminal을 lifecycle 계약에 맞게 분리하고, timeout cleanup result/receipt가 같은 child exit snapshot을 사용하도록 동기화했다.
- 2026-08-12: `[bench-01]` 종료 감사에서 확인한 421개 benchmark test, manifest/CLI 계약과 구현 evidence를 기준으로 생성했다.

View file

@ -0,0 +1,320 @@
<!-- task=m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission plan=2 tag=REVIEW_API milestone-task=agy-iop-compatibility,route-readiness,objective-validation -->
# Code Review Reference - REVIEW_API
> **[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 root cause, scope, files, and dependency decisions as written. Do not choose another owner, narrow/expand the write boundary, or replace a fix with another verification attempt.
> 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, or classify the next state.
> Finalization (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.
## Overview
date=2026-08-13
task=m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission, plan=2, tag=REVIEW_API
## Archive Evidence Snapshot
- Prior plan: `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/plan_cloud_G10_1.log`.
- Prior review: `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/code_review_cloud_G10_1.log`, verdict `FAIL`, Required R1 only, Suggested/Nit none.
- Resolved authorization: `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/user_review_0.log`; the authorization enables new verification and is not terminal PASS evidence.
- Failed immutable qualification: `run-20260813T020758Z-2920dc067c4e`, `artifact_blocked=1`, `reason=cdp_socket_closed`.
- Retained immutable run: `run-20260812T222805Z-bec48f5fffaa`, 5,489 regular files, digest `d089cd4b3e9bfd4e8ebe3bfa82032a544f0625e0793ad9763b728addf62baffd`.
## For the Review Agent
> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section.
Compare implementation evidence against the plan and immutable run state. Rerun applicable read-only verification and record fresh output. Completion requires one and only one newly authorized direct run, no exhausted browser/CDP block, prior-run immutability, and no release/runtime mutation by this packet.
1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals.
2. Archive `CODE_REVIEW-cloud-G08.md` to `code_review_cloud_G08_2.log` and `PLAN-cloud-G08.md` to `plan_cloud_G08_2.log`.
3. If PASS, write `complete.log` and move this subtask to `agent-task/archive/YYYY/MM/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/`. If WARN/FAIL, materialize the required next state.
4. If PASS, preserve `milestone-task=agy-iop-compatibility,route-readiness,objective-validation` for runtime aggregation without updating the roadmap directly.
---
## Implementation Item Completion
| Item | Status |
|---|---|
| REVIEW_API-4 — Frozen completed release/runtime identity | [x] |
| REVIEW_API-5 — Bounded Chromium/CDP admission | [x] |
| REVIEW_API-6 — One authorized direct qualification | [x] |
| REVIEW_API-7 — Terminal and immutable evidence audit | [x] |
## Implementation Checklist
- [x] [REVIEW_API-4] Prove `dev-974` is finished and freeze one clean release/artifact/runtime/provider identity without mutating shared runtime state.
- [x] [REVIEW_API-5] Pass one bounded Chromium/CDP renderer integration preflight before any new benchmark allocation.
- [x] [REVIEW_API-6] Through `/bin/bash /tmp/iop-bench-13-env`, invoke exactly one fresh direct CLI `preflight` and exactly one fresh direct CLI `run`, with no resume/retry or alternate caller path.
- [x] [REVIEW_API-7] Audit the issued run for exactly five attempts, complete terminal axes, `artifact_blocked=0`, and audit both prior runs for immutability.
- [x] Fill implementation-owned sections in `CODE_REVIEW-cloud-G08.md` with actual commands and output.
## Review-Only Checklist
> **[REVIEW AGENT ONLY]** This checklist is used only by the review agent.
> Implementing agents must not modify or check this section.
- [x] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
- [x] Verify that verdict, dimension assessment, and Required/Suggested/Nit classifications match.
- [x] Run applicable required verification and record fresh output.
- [x] For every Required/Suggested finding, close evidence, root cause, selected fix, ownership, and acceptance commands before follow-up.
- [x] Archive this review to `code_review_cloud_G08_2.log`.
- [x] Archive its plan to `plan_cloud_G08_2.log`.
- [x] Verify generated task artifacts are not ignored.
- [x] If PASS, write `complete.log`, archive the task directory, preserve milestone metadata, and report completion metadata.
- [ ] If WARN/FAIL, write the next filesystem state and do not write `complete.log`.
## Deviations from Plan
- The Control Plane status endpoint now requires HTTPS. The planned read-only status check returned HTTP 400 with `Client sent an HTTP request to an HTTPS server.`; the same exact runner-local endpoint was then queried with `curl -ksS https://127.0.0.1:18001/...`, returning health/readiness HTTP 200 and the redacted Edge status. No certificate, runtime, config, or process state was changed.
- The four binaries retain build metadata for candidate commit `2fcc1093c7629ab94b460d083520ffd9c8064815`, while the finished tag peels to merge commit `fd41adac777f7c7faea0cdd27acbe2817890abf8` and the clean runner is at post-finish `dev` commit `019500a5d65117bfd115e0c33601ceef3461ee52`. All three commits resolve to the identical tree `d46c7e3057bafbeedc1ddb10d2619feb3c6eb2db`, so the planned source/tag/artifact tree invariant is satisfied without rebuilding or altering the release.
## Key Design Decisions
- Treated authoritative remote refs plus Git tree identity as the freeze boundary: `refs/heads/release/dev-974` was absent, `dev-974` existed, and tag, clean checkout, and candidate artifact source shared one tree.
- Kept the authorization count closed: one BrowserRenderer integration test, one direct CLI `preflight`, one direct CLI `run`, and no `resume`, retry flag, alternate caller, or provider invocation.
- Preserved product failures and timeout as terminal benchmark outcomes. Admission was judged only by complete five-slot evidence and `artifact_blocked=0`, per SDD D13/S02.
## Reviewer Checkpoints
- [x] Confirm `dev-974` is fully finished before any benchmark allocation and every observed artifact/runtime identity agrees.
- [x] Confirm this packet did not mutate `dev-974`, shared runtime configuration, credentials, subscriptions, manifests, routes, or old runs.
- [x] Confirm the actual BrowserRenderer integration test passed once before the direct preflight/run.
- [x] Confirm exactly one new direct run was issued, with no resume, retry, second run, or ad-hoc caller/provider invocation.
- [x] Confirm all five slots have controller/product/harness/process/web terminal evidence and `artifact_blocked=0`.
- [x] Confirm product failures/timeouts remain measured outcomes rather than acceptance failures or implicit retry triggers.
- [x] Confirm retained and failed prior run roots are unchanged.
## Verification Results
### REVIEW_API-4 release/runtime freeze
```bash
git ls-remote origin refs/heads/main refs/heads/dev refs/heads/release/dev-974 refs/tags/dev-974 'refs/tags/dev-974^{}'
ssh toki@toki-labs.com '/bin/zsh -lc '\''cd /Users/toki/agent-work/iop-dev && git status --short --branch && git rev-parse HEAD && for f in build/dev-runtime/bin/edge build/dev-runtime/bin/iop-node build/dev-runtime/bin/iop-node-linux-arm64 build/dev-runtime/bin/iop-node-windows-amd64.exe; do go version -m "$f" | sed -n "/vcs.revision/p;/vcs.modified/p"; done'\'''
```
```text
command: git ls-remote origin refs/heads/main refs/heads/dev refs/heads/release/dev-974 refs/tags/dev-974 'refs/tags/dev-974^{}'
exit_code: 0
stdout:
019500a5d65117bfd115e0c33601ceef3461ee52 refs/heads/dev
fd41adac777f7c7faea0cdd27acbe2817890abf8 refs/heads/main
4d2596e3779027ba88457c44fcc9bc91134cf5e9 refs/tags/dev-974
fd41adac777f7c7faea0cdd27acbe2817890abf8 refs/tags/dev-974^{}
stderr: (none)
interpretation: `refs/heads/release/dev-974` is absent and the annotated tag exists.
command: read-only remote checkout, tag, candidate, artifact, process, and listener audit
exit_code: 0
stdout:
- checkout: `## dev...origin/dev`, HEAD `019500a5d65117bfd115e0c33601ceef3461ee52`, tree `d46c7e3057bafbeedc1ddb10d2619feb3c6eb2db`
- peeled tag commit: `fd41adac777f7c7faea0cdd27acbe2817890abf8`, tree `d46c7e3057bafbeedc1ddb10d2619feb3c6eb2db`
- candidate artifact commit: `2fcc1093c7629ab94b460d083520ffd9c8064815`, tree `d46c7e3057bafbeedc1ddb10d2619feb3c6eb2db`
- `build/dev-runtime/bin/edge`: `vcs.revision=2fcc1093c7629ab94b460d083520ffd9c8064815`, `vcs.modified=false`
- `build/dev-runtime/bin/iop-node`: same revision, `vcs.modified=false`
- `build/dev-runtime/bin/iop-node-linux-arm64`: same revision, `vcs.modified=false`
- `build/dev-runtime/bin/iop-node-windows-amd64.exe`: same revision, `vcs.modified=false`
- listeners: `18082=1`, `18083=1`, `18084=1`, `19093=1`, `19101=1`
- live runner processes: Edge PID 26756 and mac Node PID 26895 execute the audited `build/dev-runtime/bin/*` paths; config arguments were redacted.
stderr: (none)
command: curl -ksS https://127.0.0.1:18001/healthz, /readyz, and /edges/edge-toki-labs-dev/status
exit_code: 0
stdout:
- healthz: `ok`, HTTP 200
- readyz: `ready`, HTTP 200
- Nodes: `mac-codex-node`, `gx10-vllm-node`, `onexplayer-lemonade-node`, `rtx5090-lemonade-node`; all `connected=true`
- `mac-gemini-api 1/0/0 healthy`; `mac-mlx-vllm 2/0/0 healthy`; `glm-coding 1/0/0 healthy`; `anthropic-api 1/0/0 healthy`; `openai-api 1/0/0 healthy`
- `gx10-vllm 4/0/0 healthy`; `onexplayer-lemonade 3/0/0 healthy`; `rtx5090-lemonade 1/0/0 healthy`
stderr: (none)
provider tuple order above is `capacity/in_flight/queued`. No release ref, artifact, config, credential, process, or runtime state was mutated.
```
### REVIEW_API-5 Chromium/CDP admission
```bash
python3 -m unittest scripts.agent_benchmark.browser_cdp_test.BrowserIntegrationTest.test_valid_page_emits_complete_two_viewport_observations
```
```text
command: python3 -m unittest scripts.agent_benchmark.browser_cdp_test.BrowserIntegrationTest.test_valid_page_emits_complete_two_viewport_observations
exit_code: 0
stdout:
.
----------------------------------------------------------------------
Ran 1 test in 2.584s
OK
stderr: (none)
command count: 1. This completed before either benchmark CLI command.
```
### REVIEW_API-6 one direct preflight/run
```bash
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py preflight --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py run --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json
```
```text
command: /bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py preflight --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json
exit_code: 0
stdout: ok: preflight run_id=run-20260813T064206Z-07771a1afcbe status=ready ready=5 registration_required=0 implementation_gap=0
stderr: (none)
command: /bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py run --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json
exit_code: 0
stdout: ok: run run_id=run-20260813T064211Z-212a8e1c4b9f executed=5 unresolved=0 completed=4 timed_out=1 cancelled=0 interrupted=0 running=0 product_succeeded=2 product_failed=2 product_unknown=1 harness_passed=4 harness_failed=1 process_exited=4 process_signalled=0 process_timed_out=1 process_cancelled=0 process_not_started=0 artifact_passed=1 artifact_failed=4 artifact_blocked=0 artifact_not_run=0
stderr: (none)
counts: direct preflight=1, direct run=1, resume=0, retry=0, alternate caller/provider invocation=0. Issued qualification run ID: `run-20260813T064211Z-212a8e1c4b9f`.
```
### REVIEW_API-7 exact status and immutability
```bash
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json --run-id <issued-run-id>
```
```text
command: /bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json --run-id run-20260813T064211Z-212a8e1c4b9f
exit_code: 0
stdout: ok: status run_id=run-20260813T064211Z-212a8e1c4b9f unresolved=0 completed=4 timed_out=1 cancelled=0 interrupted=0 running=0 product_succeeded=2 product_failed=2 product_unknown=1 harness_passed=4 harness_failed=1 process_exited=4 process_signalled=0 process_timed_out=1 process_cancelled=0 process_not_started=0 artifact_passed=1 artifact_failed=4 artifact_blocked=0 artifact_not_run=0
stderr: (none)
file audit: exactly 5 `attempt.json` and exactly 5 `web-validation.json` files, one `attempt-000001` for each manifest cell.
terminal summaries:
- `agy-gemini-direct`: controller `completed`; product `failed/caller_error`; harness `passed/success`, ordered terminal and cleanup complete; process `exited/1`; web `failed/generated_missing`.
- `claude-gemini-direct`: controller `completed`; product `succeeded/caller_success`; harness `passed/success`, ordered terminal and cleanup complete; process `exited/0`; browser observed with 2 screenshots, 2 viewports, 11 requests; web `failed/image_evidence_failed` (accessibility also failed).
- `claude-gpt-direct`: controller `completed`; product `failed/caller_error`; harness `passed/success`, ordered terminal and cleanup complete; process `exited/1`; web `failed/generated_missing`.
- `claude-sonnet-direct`: controller `timed_out`; product `unknown/unavailable`; harness `failed/timed_out`, cleanup complete; process `timed_out/143`; web `failed/generated_missing`.
- `codex-gpt-direct`: controller `completed`; product `succeeded/caller_success`; harness `passed/success`, ordered terminal and cleanup complete; process `exited/0`; browser observed with 2 screenshots, 2 viewports, 12 requests; all seven web gates passed and web status is `passed`.
browser/CDP admission: no web record is blocked, aggregate `artifact_blocked=0`, and the previously affected Codex cell has a complete two-viewport browser record. No Gemini rejection observation was present in the closed non-secret run evidence, so no live rejection origin is inferred.
command: failed prior run status and closed CDP record audit
exit_code: 0
stdout:
- `run-20260813T020758Z-2920dc067c4e`: `unresolved=0 completed=3 timed_out=2 cancelled=0 interrupted=0 running=0 product_succeeded=1 product_failed=2 product_unknown=2 harness_passed=3 harness_failed=2 process_exited=3 process_signalled=0 process_timed_out=2 process_cancelled=0 process_not_started=0 artifact_passed=0 artifact_failed=4 artifact_blocked=1 artifact_not_run=0`
- Codex web record remains `status=blocked`, `reason=cdp_socket_closed`, `browser_status=not_observed`, screenshots=0, viewports=0.
- exactly 5 `attempt.json` and 5 `web-validation.json`; regular files=5,497; sorted full-path SHA-256 stream digest=`14711f384e2b577d26a4897c67d88e56b8766a892e735048fa8ad7d55ad444c1`.
stderr: (none)
command: retained prior run regular-file count and sorted full-path SHA-256 stream digest
exit_code: 0
stdout: `run-20260812T222805Z-bec48f5fffaa files=5489 digest=d089cd4b3e9bfd4e8ebe3bfa82032a544f0625e0793ad9763b728addf62baffd`
stderr: (none)
The retained digest exactly matches the frozen baseline. Both prior run trees were read only; no run was resumed, retried, or edited.
```
### Final verification
```bash
git diff --check -- . ':(exclude)agent-task/archive/**'
git status --short --branch
```
```text
command: git diff --check -- . ':(exclude)agent-task/archive/**'
exit_code: 0
stdout: (none)
stderr: (none)
command: git status --short --branch
exit_code: 0
stdout:
## feature/iop-one-shot-agent-model-comparison...origin/feature/iop-one-shot-agent-model-comparison
?? agent-task/m-iop-one-shot-agent-model-comparison/
stderr: (none)
Only the active task evidence tree is untracked; there are no tracked product, benchmark, manifest, runtime, release, credential, or subscription changes.
```
### Reviewer fresh verification
```text
command: /bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json --run-id run-20260813T064211Z-212a8e1c4b9f
exit_code: 0
stdout: ok: status run_id=run-20260813T064211Z-212a8e1c4b9f unresolved=0 completed=4 timed_out=1 cancelled=0 interrupted=0 running=0 product_succeeded=2 product_failed=2 product_unknown=1 harness_passed=4 harness_failed=1 process_exited=4 process_signalled=0 process_timed_out=1 process_cancelled=0 process_not_started=0 artifact_passed=1 artifact_failed=4 artifact_blocked=0 artifact_not_run=0
stderr: (none)
command: python3 -m unittest scripts.agent_benchmark.browser_cdp_test.BrowserIntegrationTest.test_valid_page_emits_complete_two_viewport_observations
exit_code: 0
stdout: Ran 1 test in 2.022s; OK
stderr: (none)
new-run file audit: attempt.json=5, web-validation.json=5. Every slot is terminal; Codex has browser observed, two screenshots, two viewports, and web status passed. The remaining generated-missing, image-evidence, and timeout outcomes are terminal product/artifact results, not infrastructure blocks.
immutable audit: run-20260813T020758Z-2920dc067c4e files=5497 digest=14711f384e2b577d26a4897c67d88e56b8766a892e735048fa8ad7d55ad444c1; run-20260812T222805Z-bec48f5fffaa files=5489 digest=d089cd4b3e9bfd4e8ebe3bfa82032a544f0625e0793ad9763b728addf62baffd.
release/runtime audit: remote release/dev-974 absent; dev-974 peels to fd41adac777f7c7faea0cdd27acbe2817890abf8; clean checkout, tag, and candidate artifacts share tree d46c7e3057bafbeedc1ddb10d2619feb3c6eb2db. Four artifacts report vcs.revision=2fcc1093c7629ab94b460d083520ffd9c8064815 and vcs.modified=false. Listeners 18082/18083/18084/19093/19101 are open; health/readiness return 200; four Nodes are connected; eight provider snapshots are healthy with in_flight=0 and queued=0.
command: git diff --check -- . ':(exclude)agent-task/archive/**'
exit_code: 0
stdout: (none)
stderr: (none)
```
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.
## Section Ownership
| Section | Owner | Note |
|---|---|---|
| Header, Overview, Review Agent instructions | Fixed at stub creation | Implementer must not change finalization protocol. |
| Archive Evidence Snapshot | Fixed at stub creation | Read only the exact linked evidence when needed. |
| Implementation Item Completion | Implementing agent | Change only `[ ]` to `[x]`. |
| Implementation Checklist | Implementing agent | Change only `[ ]` to `[x]`. |
| Review-Only Checklist | Review agent only | Implementer must not modify it. |
| Deviations from Plan, Key Design Decisions | Implementing agent | Replace placeholders with actual content. |
| Reviewer Checkpoints | Fixed at stub creation | Reviewer checks these. |
| Verification Results | Implementing agent, then reviewer | Record actual output; reviewer reruns applicable checks. |
| Code Review Result | Review agent | Appended during official review. |
## Code Review Result
### Overall Verdict
PASS
### Dimension Assessment
| Dimension | Result | Evidence |
|---|---|---|
| Correctness | Pass | The authorized replacement qualification has five terminal slots and `artifact_blocked=0`; product failures and timeout remain independent measured outcomes. |
| Completeness | Pass | REVIEW_API-4 through REVIEW_API-7 are complete, including frozen runtime identity, bounded renderer admission, exactly one replacement run, exact status, and both immutable audits. |
| Test coverage | Pass | The reviewer reran the real Chromium/CDP two-viewport integration test and inspected all five `attempt.json` and `web-validation.json` records. |
| API contract | Pass | The public benchmark CLI produced the required closed status without resume/retry or an alternate caller/provider path. |
| Code quality | Pass | This packet changed only task evidence; `git diff --check` passes and no product or harness source change was introduced. |
| Implementation deviation | Pass | HTTPS status probing and commit-vs-tree identity handling preserve the planned read-only boundary and are justified by the deployed environment. |
| Verification trust | Pass | Fresh reviewer status, renderer, run-file, release/runtime, and digest audits reproduce the implementation evidence. |
| Spec conformance | Pass | SDD D13/S02 is satisfied: fresh `ready=5`, exactly five attempts, complete terminal axes, `unresolved=0`, `running=0`, `interrupted=0`, and no exhausted browser/CDP block. |
### Findings
None.
### Routing Signals
- `review_rework_count=2`
- `evidence_integrity_failure=false`
### Next Step
Archive the PASS pair, write `complete.log`, move the task artifacts to the monthly archive, and emit Milestone completion-event metadata without modifying the roadmap.

View file

@ -0,0 +1,352 @@
<!-- task=m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission plan=0 tag=API milestone-task=agy-iop-compatibility,route-readiness,objective-validation -->
# Code Review Reference - API
> **[IMPLEMENTING AGENT — READ FIRST]** Filling in this file is the mandatory final implementation step. Execute the plan's selected fixes and write boundary exactly. Do not choose a different protocol design, mutate old runs, ask the user, create stop files, archive task artifacts, or write `complete.log`. Final verdict/finalization is review-agent-only.
## Overview
date=2026-08-13
task=m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission, plan=0, tag=API
status=blocked
resume condition: An authorized release owner reconciles and removes the stale remote `release/dev-936` branch without moving or deleting tag `dev-936`; then refetch `origin/dev`, `origin/main`, and the exact feature tip, require no other release branch, merge the feature into clean `dev`, and start the newly computed `dev-<origin/dev commit count>` release.
## Archive Evidence Snapshot
- Satisfied predecessor: `agent-task/archive/2026/08/m-iop-one-shot-agent-model-comparison/13+12_scored_benchmark_result/complete.log`.
- Retained direct result: `agent-test/runs/bench-01-direct-preflight/run-20260812T222805Z-bec48f5fffaa/`; preserve it byte-for-byte.
- Selected fixes are Gemini tool-call ID/tool-result normalization, safe rejection origin, bounded CDP recovery, terminal-evidence admission, clean dev deployment, and one new unscored diagnostic.
## For the Review Agent
Independently inspect every changed source file, rerun deterministic verification, validate source/deployment identity and the exact fresh direct run. Product failures/timeouts are outcomes, while incomplete evidence, pre-ingress incompatibility, or exhausted browser/CDP infrastructure blocking are Required findings. On PASS, append the verdict, archive this pair, write `complete.log` with first-line milestone metadata, move the task directory to the dated archive, and report the completion event. On WARN/FAIL follow the code-review skill; do not invent a worker investigation task.
## Implementation Item Completion
| Item | Status |
|---|---|
| API-1 Gemini tool identity | [x] |
| API-2 Safe rejection origin | [x] |
| API-3 Bounded CDP recovery | [x] |
| API-4 Admission contract | [x] |
| API-5 Publish and deploy | [ ] BLOCKED after feature publish, before dev merge |
| API-6 Direct diagnostic | [ ] NOT RUN because API-5 release prerequisite is blocked |
## Implementation Checklist
- [x] [API-1] Preserve and validate Gemini function-call IDs through request and streaming response conversion, remove the non-standard tool-result field, and add same-name/out-of-order/duplicate/missing-ID regression tests.
- [x] [API-2] Add classification-only Gemini and Anthropic Chat rejection observations with tests proving no request, response, credential, route, prompt, or provider-message content is logged.
- [x] [API-3] Add at most three total fresh Chromium/CDP attempts for closed transient infrastructure errors, with per-attempt cleanup and tests for retry, exhaustion, non-transient no-retry, screenshot cleanup, and process reaping.
- [x] [API-4] Update benchmark skill/spec/SDD/Milestone/dev guide so direct qualification gates on readiness and terminal evidence rather than all-success, while exhausted infrastructure blocks and scored-run uniqueness remain fail-closed.
- [ ] [API-5] Run fresh local tests, commit/push the feature branch, merge its exact tip into clean `dev`, execute the full dev-runtime release/deploy procedure, and prove all Edge/Node binaries and health observations use the same released source.
- [ ] [API-6] Through `/bin/bash /tmp/iop-bench-13-env`, run one fresh direct preflight and one fresh unscored direct run; accept product failures/timeouts as results but require five terminal slots, `unresolved=0`, and no infrastructure-only browser/CDP block.
- [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 section.
- [x] Append one verdict and verified `review_rework_count` / `evidence_integrity_failure` signals.
- [x] Verify dimensions and Required/Suggested/Nit classifications.
- [x] Rerun applicable tests and inspect exact live evidence; repair reviewer-reconstructable evidence gaps.
- [x] For every Required/Suggested finding record evidence, exact root cause, selected fix, files/symbols/tests, and acceptance commands before a follow-up plan.
- [x] Archive plan/review to correctly numbered `.log` files.
- [x] Verify the Agent-Ops `.gitignore` managed block.
- [ ] On PASS write `complete.log`, preserve milestone metadata, move the active directory to the dated archive, and leave no active `.md` files.
- [x] On WARN/FAIL write the exact next filesystem state and do not write `complete.log`.
## Deviations from Plan
API-5 stopped at the mandated release precheck. A fresh fetch confirmed remote `release/dev-936` still exists at `fb23f3e83759373caffbb2ab6d2076218b402311` while completed tag `dev-936` exists and peels to `bc5b326140c809d6d3685ca35d7311e31a180d7c`. Neither ref is an ancestor of the other. Current `origin/dev` is `841511472a62ec20d79eac5f800180d1de34b541` with commit count 962, so the next release candidate would be `dev-962`, not a resumable `dev-936`. The plan and `dev-runtime-deploy` skill classify this other existing release branch as a hard blocker and prohibit automatic deletion/reset. Therefore exact feature tip `28ed27a575f6e3ba473a8c76c78a9e82027525f2` was published, but it was not merged into `dev`; no release branch, tag, binary, process, or runtime state was changed. API-6 was not invoked because the plan prohibits accepting a diagnostic against an unreleased/stale runtime.
## Key Design Decisions
- Gemini explicit call IDs use a closed 128-character token grammar. Pending calls are indexed by ID and name; explicit responses exact-match ID and name in any order, while ID-less responses retain FIFO only for calls whose native request omitted the ID. Duplicate, invalid, mismatched, and missing-required IDs fail closed. Tool-result projection contains only `role`, `tool_call_id`, and `content`.
- Gemini streaming retains a stable provider `delta.tool_calls[].id`, rejects changes/duplicates, and includes it in the native `functionCall`; thought signatures remain on their existing path.
- Rejection observations contain exactly `surface`, `bridge`, `rejection_class`, and `http_status`. Gemini uses closed `pre_ingress`/`provider_http`; Anthropic Chat bridge uses `provider_http`. Tests assert exact field count and secret-marker absence.
- `BrowserRenderer.render` performs input/binary/collision preflight once, then delegates to one-attempt rendering. Only the closed start/handshake/socket-loss set retries, for three total fresh profiles/processes. Existing one-attempt cleanup reaps the process group and removes screenshots; the wrapper defensively removes partial screenshots before retry.
- Direct qualification now admits complete terminal measurement rather than all-success: fresh `ready=5`, exactly five fresh attempts, zero unresolved/running/interrupted, terminal evidence for all axes, and no exhausted browser/CDP block. Product failure/provider rejection/timeout remain results; Edge pre-ingress incompatibility and exhausted browser/CDP infrastructure remain blockers. D06/D10 scored-run uniqueness is unchanged.
## Reviewer Checkpoints
- Explicit Gemini IDs survive response→history→tool-result conversion; ID-less inputs retain deterministic fallback.
- No raw request/provider error material is logged or copied into task evidence.
- CDP retry is exactly a three-attempt maximum for the closed transient set and reaps every failed process.
- Policy sources agree that terminal failure is a result, not an incomplete run.
- Feature, dev merge, release tag/tree, four binaries, and live processes share the recorded source.
- Fresh direct run has exactly five terminal slots and no infrastructure-only artifact block; old runs are unchanged.
## Verification Results
### API-1 / API-2 Edge bridge tests
```text
command: go test -count=1 ./apps/edge/internal/openai -run 'Gemini|Anthropic.*Bridge|Rejection'
exit_code: 0
stdout: ok iop/apps/edge/internal/openai 0.109s
stderr: (none)
command: go test -count=1 ./apps/edge/internal/openai
exit_code: 0
stdout: ok iop/apps/edge/internal/openai 8.653s
stderr: (none)
```
### API-3 Browser tests
```text
command: python3 -m unittest scripts.agent_benchmark.browser_cdp_test scripts.agent_benchmark.web_validation_test
exit_code: 0
stdout: Ran 25 tests in 42.934s / OK
stderr: (none)
```
### API-4 Contract tests
```text
command: python3 -m unittest scripts.agent_benchmark.skill_contract_test
exit_code: 0
stdout: Ran 54 tests in 2.149s / OK
stderr: (none)
command: python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/iop-agent-comparison-benchmark
exit_code: 0
stdout: Skill is valid!
stderr: (none)
command: python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/dev-runtime-deploy
exit_code: 1
stderr: Unexpected key(s) in SKILL.md frontmatter: version. Allowed properties are: allowed-tools, description, license, metadata, name
command: python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json
exit_code: 0
stdout: ok: manifest is valid
stderr: (none)
```
### API-5 Publication and deployment
```text
feature commit: 28ed27a575f6e3ba473a8c76c78a9e82027525f2
feature push: origin/feature/iop-one-shot-agent-model-comparison = 28ed27a575f6e3ba473a8c76c78a9e82027525f2
commit command exit_code: 0
push command exit_code: 0
remote release precheck after fetch:
- origin/dev = 841511472a62ec20d79eac5f800180d1de34b541
- origin/dev commit count = 962 (next candidate version after merge must be recomputed; current baseline is dev-962)
- origin/main = bc5b326140c809d6d3685ca35d7311e31a180d7c
- origin/feature/iop-one-shot-agent-model-comparison = 28ed27a575f6e3ba473a8c76c78a9e82027525f2
- origin/main ancestor of origin/dev: yes
- origin/dev and feature tip ancestry: diverged; merge base = 8f00606c0339e1e9ace5f7c8d019582ba6843513
- existing remote release branch: release/dev-936 = fb23f3e83759373caffbb2ab6d2076218b402311
- existing tag: dev-936; peeled commit = bc5b326140c809d6d3685ca35d7311e31a180d7c
- release/dev-936 and peeled tag ancestry: neither is an ancestor of the other
blocker: existing release/dev-936 is a plan/skill hard stop. No dev merge, release start/resume, build, deployment, process restart, capacity smoke, release finish, or tag/ref mutation was performed.
resume condition: authorized release ownership reconciles and removes only remote release/dev-936 without moving/deleting dev-936; a subsequent fetch sees no release branch, preserves the recorded tag, and passes clean dev/main/feature prechecks before merge and a newly computed release.
```
### API-6 Direct diagnostic
```text
fresh direct preflight command count: 0
fresh direct run command count: 0
reason: API-5 did not deploy the candidate; running against stale runtime is prohibited.
retained old run audit before any new diagnostic:
- path: agent-test/runs/bench-01-direct-preflight/run-20260812T222805Z-bec48f5fffaa/
- regular files: 5489
- aggregate sorted file SHA-256 stream digest: d089cd4b3e9bfd4e8ebe3bfa82032a544f0625e0793ad9763b728addf62baffd
- mutation/resume/retry: none
safe rejection origin live observation: not available because candidate deployment did not occur. Unit tests cover classification-only fields; no provider body was copied into tracked evidence.
```
### Final verification
```text
command: python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
exit_code: 0
stdout: Ran 458 tests in 154.741s / OK
stderr: (none)
command: python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json
exit_code: 0
stdout: ok: manifest is valid
stderr: (none)
command: python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
exit_code: 0
stdout: ok: manifest is valid
stderr: (none)
command: git diff --check -- . ':(exclude)agent-task/archive/**'
exit_code: 0
stdout: (none)
stderr: (none)
final local source status: feature HEAD and origin feature both 28ed27a575f6e3ba473a8c76c78a9e82027525f2; only the active task pair remains untracked for official review/finalization.
```
---
> **[IMPLEMENTING AGENT — BEFORE SAVING]** Fill every implementation-owned section and check every implemented row/item. Leave review-only sections unchanged.
## Section Ownership
| Section | Owner |
|---|---|
| Header, Overview, Archive Snapshot, item names, checkpoints | Fixed at stub creation |
| Implementation checklist statuses, deviations, decisions, initial verification | Implementing agent |
| Review-only checklist, verdict, archive/finalization | Official review agent |
## Reviewer Fresh Verification
```text
command: go test -count=1 ./apps/edge/internal/openai -run 'Gemini|Anthropic.*Bridge|Rejection'
exit_code: 0
stdout: ok iop/apps/edge/internal/openai 0.084s
stderr: (none)
command: go test -count=1 ./apps/edge/internal/openai
exit_code: 0
stdout: ok iop/apps/edge/internal/openai 8.641s
stderr: (none)
command: python3 -m unittest scripts.agent_benchmark.browser_cdp_test scripts.agent_benchmark.web_validation_test
exit_code: 0
stdout: Ran 25 tests in 43.688s / OK
stderr: (none)
command: python3 -m unittest scripts.agent_benchmark.skill_contract_test
exit_code: 0
stdout: Ran 54 tests in 3.304s / OK
stderr: (none)
command: python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
exit_code: 0
stdout: Ran 458 tests in 160.614s / OK
stderr: (none)
command: while IFS= read -r package; do go test -count=1 "$package" || exit; done < <(go list ./apps/control-plane/... ./apps/edge/... ./apps/node/... ./packages/go/... ./scripts/... | sed '/^iop\/packages\/go\/agenttask$/d')
first_exit_code: 1
first_failure: apps/node/internal/workspace TestCommandExecutorCancelKillsProcessGroup read an empty child PID file; three immediate focused reruns passed.
second_exit_code: 0
second_stdout: every listed package passed sequentially, including apps/node/internal/workspace and apps/edge/internal/openai.
command: python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/iop-agent-comparison-benchmark
exit_code: 0
stdout: Skill is valid!
stderr: (none)
command: python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json
exit_code: 0
stdout: ok: manifest is valid
stderr: (none)
command: python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
exit_code: 0
stdout: ok: manifest is valid
stderr: (none)
command: git diff --check -- . ':(exclude)agent-task/archive/**'
exit_code: 0
stdout: (none)
stderr: (none)
retained run audit:
- regular files: 5489
- aggregate sorted file SHA-256 stream digest: d089cd4b3e9bfd4e8ebe3bfa82032a544f0625e0793ad9763b728addf62baffd
- result: unchanged from implementation evidence
```
Focused reviewer reproducers were added temporarily, run, and removed before finalization:
```text
command: go test -count=1 ./apps/edge/internal/openai -run 'ReviewReproducerGemini(InternalDispatchErrorIsProviderHTTP|AuthRejectionIsUnobserved)' -v
exit_code: 0
stdout:
=== RUN TestReviewReproducerGeminiInternalDispatchErrorIsProviderHTTP
--- PASS: TestReviewReproducerGeminiInternalDispatchErrorIsProviderHTTP (0.00s)
=== RUN TestReviewReproducerGeminiAuthRejectionIsUnobserved
--- PASS: TestReviewReproducerGeminiAuthRejectionIsUnobserved (0.00s)
PASS
ok iop/apps/edge/internal/openai 0.058s
stderr: (none)
meaning: the first reproducer proves an Edge-local SubmitRun failure is mislabeled provider_http; the second proves a Gemini authentication rejection emits no pre_ingress observation.
```
External read-only preflight:
```text
runner: toki@toki-labs.com:/Users/toki/agent-work/iop-dev
host: Darwin arm64
checkout: branch=dev, HEAD=fd32abb4b6b15037c24be01821b430a960afd967, dirty=no, behind origin/dev by 3
origin/dev: 841511472a62ec20d79eac5f800180d1de34b541
origin/main: bc5b326140c809d6d3685ca35d7311e31a180d7c
feature: 28ed27a575f6e3ba473a8c76c78a9e82027525f2
authoritative git ls-remote release branches: none
stale runner remote-tracking refs: origin/release/dev-781, origin/release/dev-936
tag dev-936 peeled commit: bc5b326140c809d6d3685ca35d7311e31a180d7c
git-flow: 1.12.3 AVH; master=main; develop=dev; release prefix=release/
Go: /opt/homebrew/bin/go, go1.26.3 darwin/arm64
artifacts: all four declared dev-runtime binaries present
config: build/dev-runtime/edge.yaml present
runtime: Edge PID 81496 listens on 18082, 18083, 18084, and 19093
setup conclusion: the original remote release branch is gone; git fetch --prune must remove stale tracking refs before a clean dev sync and new release.
```
The exact API-5 command in the active plan and `dev-runtime-deploy` skill is not runnable in this checkout:
```text
command: while IFS= read -r package; do go test -count=1 "$package" || exit; done < <(go list ./apps/control-plane/... ./apps/edge/... ./apps/node/... ./cmd/... ./packages/go/... ./scripts/... | sed '/^iop\/packages\/go\/agenttask$/d')
exit_code: 1
stderr: pattern ./cmd/...: lstat ./cmd/: no such file or directory
```
## Code Review Result
### Overall Verdict
FAIL
### Dimension Assessment
| Dimension | Result | Evidence |
|---|---|---|
| Correctness | Fail | Gemini rejection origin is derived from the final internal bridge status, so an Edge-local dispatch failure is reported as `provider_http`; auth rejection is unobserved. |
| Completeness | Fail | API-5 deployment and API-6 fresh direct qualification were not executed. |
| Test coverage | Fail | Existing observation tests omit auth pre-ingress and Edge-local error provenance negatives. |
| API contract | Fail | The closed `pre_ingress` versus `provider_http` operational contract does not reflect the real error owner. |
| Code quality | Pass | The reviewed tool-ID and bounded-CDP implementations contain no debug residue, dead code, or unrelated source changes. |
| Implementation deviation | Fail | The required dev merge/release/deploy/direct-run sequence stopped before its acceptance evidence, and its recorded release blocker is no longer an authoritative remote branch. |
| Verification trust | Pass | Fresh focused and broad checks reproduce the implementation's passing claims; the implementation accurately recorded that API-5/API-6 were not run. |
| Spec conformance | Fail | SDD S02/S13 cannot close without a correctly classified live boundary and fresh five-slot direct evidence. |
### Findings
- **Required R1 — Gemini rejection observations do not identify the actual rejection owner.**
- **Evidence:** The focused reviewer command above passes while asserting both bad states. At `apps/edge/internal/openai/gemini_handler.go:87-90`, every status `>=400` produced by the internal Chat handler is labeled `provider_http`; a normalized `SubmitRun` failure therefore becomes a false provider rejection. At `apps/edge/internal/openai/routes.go:35-45,58-72`, authentication and managed caller-credential rejection return before the Gemini handler and call `writeGeminiError` directly, so those pre-ingress failures emit no observation. `apps/edge/internal/openai/gemini_handler_test.go:321-357` covers malformed body plus a real provider 400 but neither negative provenance variant.
- **Root Cause:** `geminiBridgeResponseWriter.Status()` carries only the final caller-facing status and has no actual provider `RESPONSE_START` provenance. The handler infers ownership from that lossy value, while the shared auth wrapper bypasses `writeGeminiPreIngressError`.
- **Selected Fix:** In `apps/edge/internal/openai/routes.go`, route Gemini authentication and managed caller-provider-credential failures through `Server.writeGeminiPreIngressError`. In `apps/edge/internal/openai/gemini_handler.go`, remove blanket post-handler status classification and construct the bridge with a bounded provider-rejection callback. In `apps/edge/internal/openai/gemini_bridge.go`, implement a private, once-only provider-status observer that emits only for actual status `>=400`. In `apps/edge/internal/openai/stream_gate_release_sink.go`, notify that observer only at raw provider-tunnel response-start/error-response status write points, never for normalized/internal terminal errors. Extend `apps/edge/internal/openai/gemini_handler_test.go` to prove auth and managed pre-ingress classification, actual tunnel 400 classification, Edge-local `SubmitRun` failure non-classification as provider HTTP, exact four-field logging, and secret absence.
- **Disposition:** `direct-fix`.
- **Acceptance Commands:** `go test -count=1 ./apps/edge/internal/openai -run 'Gemini.*Rejection|GeminiIngressRejectsAuthentication'`; `go test -count=1 ./apps/edge/internal/openai`.
- **Required R2 — Required release/deployment and fresh direct qualification are incomplete, and the release procedure contains a deterministic command defect.**
- **Evidence:** API-5 and API-6 remain unchecked with direct command counts `0`. Fresh `git ls-remote --heads` reports no remote release branch, while the runner still holds stale `refs/remotes/origin/release/dev-781` and `dev-936`; the original external blocker has therefore changed into a normal prune/sync prerequisite. Separately, the exact skill/plan Go-test command fails before running tests because this repository has no `./cmd/` directory. No released source identity, four-binary identity, connected-node/capacity smoke, or fresh five-slot terminal evidence exists.
- **Root Cause:** The implementation treated a stale remote-tracking ref as an authoritative remote release branch. `agent-ops/skills/project/dev-runtime-deploy/SKILL.md` fetches without an explicit prune/authoritative `ls-remote` check and lists the nonexistent `./cmd/...` package root, so the mandated procedure cannot reliably advance even after the remote branch disappears.
- **Selected Fix:** Update `agent-ops/skills/project/dev-runtime-deploy/SKILL.md` to remove the unsupported `version` frontmatter key, prune remote refs, distinguish authoritative remote release heads from stale local tracking refs, and remove `./cmd/...` from both sequential test contracts. Add deterministic assertions to `scripts/agent_benchmark/skill_contract_test.py` for valid project-skill frontmatter and the prune/authoritative-ref/package-list wording, then run skill validation. Then commit/push the corrected feature tip, clean-sync the authorized runner, merge that exact tip into `dev`, execute the complete release procedure with four rebuilt binaries, health/identity/4-node/capacity evidence and atomic finish, and only then invoke one fresh direct preflight and run through `/bin/bash /tmp/iop-bench-13-env`. Preserve the retained run byte-for-byte and accept terminal product failures without retry, while requiring five terminal slots, zero unresolved/running/interrupted, and no exhausted browser/CDP infrastructure block.
- **Disposition:** `direct-fix`.
- **Acceptance Commands:** `python3 -m unittest scripts.agent_benchmark.skill_contract_test`; `python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/dev-runtime-deploy`; the corrected sequential Go command without `./cmd/...`; every build/deploy/capacity check in `agent-ops/skills/project/dev-runtime-deploy/SKILL.md`; `/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py preflight --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json`; `/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py run --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json`.
### Routing Signals
- `review_rework_count=1`
- `evidence_integrity_failure=false`
### Next Step
Run the plan skill in `prepare-follow-up` mode with R1 and R2 as closed `direct-fix` findings, archive this pair, and materialize the routed `REVIEW_API` follow-up pair. No user-review gate applies because the declared SSH runner is authorized and the authoritative remote release branch is already absent.

View file

@ -0,0 +1,453 @@
<!-- task=m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission plan=1 tag=REVIEW_API milestone-task=agy-iop-compatibility,route-readiness,objective-validation -->
# Code Review Reference - REVIEW_API
> **[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 root cause, scope, files, and dependency decisions as written. Do not choose another owner, narrow/expand the write boundary, or replace a fix with another verification attempt.
> 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, or classify the next state.
> Finalization (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.
## Overview
date=2026-08-13
task=m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission, plan=1, tag=REVIEW_API
## Archive Evidence Snapshot
- `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/plan_cloud_G09_0.log` and `code_review_cloud_G09_0.log` preserve the first loop. Verdict: `FAIL`; Required R1/R2; Suggested/Nit: none.
- R1 evidence: an Edge-local normalized `SubmitRun` failure is logged as `provider_http`; Gemini auth rejection emits no `pre_ingress` observation. Direct-fix targets are the Gemini/auth bridge, tunnel release provenance hook, and regression tests.
- R2 evidence: API-5/API-6 were not run. Authoritative origin has no release branch, while the runner has stale remote-tracking release refs. The deploy skill contains unsupported `version` frontmatter and its sequential Go command fails on nonexistent `./cmd/...`.
- Published feature evidence starts at `28ed27a575f6e3ba473a8c76c78a9e82027525f2`; the implementing agent must publish a new corrected exact tip before merging it into `dev`.
- The retained run `agent-test/runs/bench-01-direct-preflight/run-20260812T222805Z-bec48f5fffaa/` remains immutable: 5,489 files and aggregate digest `d089cd4b3e9bfd4e8ebe3bfa82032a544f0625e0793ad9763b728addf62baffd`.
## For the Review Agent
> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section.
Compare implementation of each item against source files. Run the applicable verification commands directly and record fresh output in `Verification Results`; implementation-owned output is handoff evidence, not a substitute for reviewer verification. If implementation is present, repair missing or stale verification output instead of failing solely for insufficient recorded evidence. When verification exposes a defect, collect the necessary data, determine the exact root cause, and select one concrete fix before generating the follow-up plan; never delegate investigation or remedy selection to the worker.
Review completion means the following steps are finished:
1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals.
2. Archive `CODE_REVIEW-cloud-G10.md` → `code_review_cloud_G10_1.log` and `PLAN-cloud-G10.md` → `plan_cloud_G10_1.log`.
3. If PASS, write `complete.log` and move active task directory to `agent-task/archive/YYYY/MM/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/`. If WARN/FAIL, fully write the next filesystem state required by the code-review skill.
4. If PASS, preserve `milestone-task=agy-iop-compatibility,route-readiness,objective-validation` in `complete.log` and report it for runtime aggregation. Roadmap state evaluation belongs to `sync-milestone-workstate`.
5. Check applicable `Review-Only Checklist` items at the final `.log` location before reporting.
---
## Implementation Item Completion
| Item | Status |
|------|---------|
| REVIEW_API-1 Rejection provenance | [x] |
| REVIEW_API-2 Deploy contract and release | [x] |
| REVIEW_API-3 Fresh terminal qualification | [ ] |
## Implementation Checklist
- [x] [REVIEW_API-1] Correct Gemini pre-ingress/provider-HTTP provenance and add regression tests for auth, managed credential rejection, actual tunnel error, Edge-local failure, exact fields, and secret absence.
- [x] [REVIEW_API-2] Repair and validate the dev-runtime deployment skill, publish the corrected feature tip, merge that exact tip into clean `dev`, and complete the full release/deploy/identity/connectivity/capacity procedure.
- [ ] [REVIEW_API-3] Through `/bin/bash /tmp/iop-bench-13-env`, run one fresh direct preflight and one fresh unscored five-cell run, record terminal-evidence admission, and prove the retained run is unchanged.
- [x] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
## Review-Only Checklist
> **[REVIEW AGENT ONLY]** This checklist is used only by the review agent.
> Implementing agents must not modify or check this section.
- [x] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
- [x] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match.
- [x] Run applicable required verification and record fresh command/output; repair reviewer-reconstructable evidence gaps instead of forwarding them to another plan.
- [x] For every Required/Suggested finding, record reviewer-collected `Evidence`, exact `Root Cause`, and one `Selected Fix` with affected files/symbols/tests and acceptance commands before creating a follow-up plan.
- [x] Archive active `CODE_REVIEW-*-G??.md` to `code_review_cloud_G10_1.log`.
- [x] Archive active `PLAN-*-G??.md` to `plan_cloud_G10_1.log`.
- [x] Verify that the Agent-Ops managed block in `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores `agent-roadmap/current.md`.
- [ ] If PASS, write `complete.log` based on `agent-ops/skills/common/code-review/templates/complete-log-template.md` and leave no active `.md` files.
- [ ] If PASS, move active task directory to `agent-task/archive/YYYY/MM/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/` and update this checklist at the final archive path.
- [ ] If PASS, preserve and report `milestone-task` metadata for runtime aggregation, without modifying roadmap or directly calling `update-roadmap`.
- [ ] If PASS for split work, remove empty active parent or verify it was kept due to remaining siblings/files.
- [x] If WARN/FAIL, write the next filesystem state matching code-review verdict and do not write `complete.log`.
## Deviations from Plan
- `git fetch --prune origin dev main <feature> --tags`만으로 stale explicit release tracking ref가 지워지지 않는 것을 runner preflight에서 확인했다. 따라서 선택된 skill fix에 `git remote prune origin`을 추가하고 contract test를 보강한 뒤 새 exact feature tip을 다시 게시했다. 이 절차로 `origin/release/dev-781`, `origin/release/dev-936`을 cleanup evidence로 제거했고 authoritative `git ls-remote --heads`는 release head 0개였다.
- local `/tmp`가 noexec라서 빌드된 `/tmp/iop-inventory-query` 실행은 `permission denied`였다. 같은 source의 `go run ./scripts/inventory-query --env dev`로 inventory validation을 수행했다. Go test/build 결과에는 영향이 없었다.
- runner에서 첫 capacity 요청은 dev CA가 curl trust store에 없어서 요청 admission 전에 curl 60으로 종료됐다. 이후 runtime CA를 `SSL_CERT_FILE`로 명시했다. Ornith aggregate 시도는 5/5 HTTP 성공, queue 2, healthy/0/0 복구였지만 RTX provider가 선택되지 않아 peak 3/4였으므로 acceptance evidence로 사용하지 않았다. 문서 기준의 단일 Laguna pool로 두 endpoint를 다시 측정해 각각 peak 4/4, queue 1, failures 0, healthy/0/0을 충족했다.
- mac runner의 기존 active binary path가 backup보다 먼저 새 artifact로 교체된 상태여서 원본 byte-for-byte rollback 파일을 보존하지 못했다. 이전 source `cb18d7bba4f39b159466adfd76b738e6cd655ef5` worktree에서 rollback용 edge/node를 별도 생성해 SHA-256을 남겼지만 sibling `go.work` 빌드 제약 때문에 해당 두 artifact의 `go version -m` VCS revision은 비어 있다. rollback은 필요하지 않았고 candidate identity/health/capacity는 모두 검증됐다.
- REVIEW_API-3의 계획된 direct preflight 1회와 run 1회는 실행했고 5개 slot 모두 terminal evidence를 남겼다. 그러나 `codex-gpt-direct` web-validation이 `blocked/cdp_socket_closed`였다. `BrowserRenderer.render`가 이 transient class를 fresh profile로 최대 3회 시도한 후 마지막 오류를 재전파하므로 이는 exhausted browser/CDP infrastructure block이다. 계획의 재시도 금지에 따라 resume/retry/새 run을 실행하지 않았으며 REVIEW_API-3 admission과 체크박스는 미완료로 남겼다. 재개 조건은 별도 승인된 후속 qualification에서 CDP socket 안정성을 먼저 복구하고 새로운 fresh preflight/run을 할 수 있는 상태다.
## Key Design Decisions
- Gemini auth 및 managed caller-credential 거절은 기존 Gemini pre-ingress writer를 통해 관측한다. endpoint error body는 바꾸지 않았다.
- `provider_http`는 최종 내부 Chat status로 추론하지 않는다. Gemini bridge의 private once-only observer를 raw provider tunnel sink의 실제 provider status commit 지점에만 연결했고, normalized/admission/internal terminal writer에는 연결하지 않았다.
- rejection observation은 `level`, `event`, `origin`, `status` 네 필드만 유지한다. 테스트는 fixture secret과 request/provider body, route, prompt, model, error message가 포함되지 않음을 확인한다.
- 배포 source는 feature tip `884273310301888aa9f200270066956603f8d439`을 두 번째 parent로 포함하는 release commit `003398a149c1433cb21bc8aa2720bde272b87373`으로 고정했다. 네 artifact와 실행 중 node 복사본은 이 release source 및 recorded SHA-256으로 검증했다.
- capacity qualification은 model-group 선택이 명확하고 단일 provider capacity가 4인 `laguna-s:2.1`/`gx10-vllm`을 사용했다. endpoint별 5개 요청으로 saturation, queue, 회복을 독립 관측한 뒤에만 release finish를 실행했다.
- direct result의 product failure, timeout, generated-missing, accessibility failure는 측정 결과로 그대로 보존했다. CDP exhaustion만 admission blocker로 분리했고 old/fresh run 어느 쪽도 재시도하거나 수정하지 않았다.
## Reviewer Checkpoints
- Gemini authentication and managed credential rejection emit only `pre_ingress`; an actual provider-tunnel 4xx emits exactly one `provider_http`; normalized/internal failures never do.
- Rejection observations retain exactly four closed safe fields and contain no request/provider body, credential, route, prompt, model, or error-message data.
- Deploy skill frontmatter validates; initial prune plus authoritative release-head check cannot mistake a stale tracking ref for a remote branch; sequential package roots all exist.
- Feature tip, dev merge, release head/tag tree, four binary module identities, and live processes all name the exact released source.
- Four nodes are connected and both OpenAI-compatible capacity+1 smokes show saturation, queueing, and return to zero before release finish.
- Exactly one fresh five-cell direct preflight/run is invoked through the CLI wrapper; all five slots are terminal, no CDP/browser infrastructure exhaustion remains, and product failures are preserved without retry.
- The retained run remains exactly 5,489 files with digest `d089cd4b3e9bfd4e8ebe3bfa82032a544f0625e0793ad9763b728addf62baffd`.
## Verification Results
Record actual stdout/stderr and exit codes. If output is long, save it outside the repository and record the exact path and command. Do not print secrets.
### REVIEW_API-1
```text
command: go test -count=1 ./apps/edge/internal/openai -run 'Gemini.*Rejection|GeminiIngressRejectsAuthentication'
exit_code: 0
stdout: ok iop/apps/edge/internal/openai 0.045s
stderr: (none)
command: go test -count=1 ./apps/edge/internal/openai
exit_code: 0
stdout: ok iop/apps/edge/internal/openai 8.603s
stderr: (none)
```
### REVIEW_API-2
```text
command: python3 -m unittest scripts.agent_benchmark.skill_contract_test
exit_code: 0
stdout: Ran 55 tests in 2.103s / OK
stderr: (55 progress dots only)
command: python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/dev-runtime-deploy
exit_code: 0
stdout: Skill is valid!
stderr: (none)
command: python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/iop-agent-comparison-benchmark
exit_code: 0
stdout: Skill is valid!
stderr: (none)
command: while IFS= read -r package; do go test -count=1 "$package" || exit; done < <(go list ./apps/control-plane/... ./apps/edge/... ./apps/node/... ./packages/go/... ./scripts/... | sed '/^iop\/packages\/go\/agenttask$/d')
exit_code: 0
stdout: 49 packages passed sequentially; final package `ok iop/scripts/inventory-query 0.053s`; full output `/tmp/iop-g10-final-go-sequential.log`
stderr: (none)
command: git ls-remote --heads origin 'refs/heads/release/*'
exit_code: 0
stdout: (empty before `release/dev-971` start; empty again after atomic finish)
stderr: (none)
feature/dev/release refs and tree:
- published exact feature tip: `884273310301888aa9f200270066956603f8d439` (local and origin equal)
- clean dev feature merge/release source: `003398a149c1433cb21bc8aa2720bde272b87373`; parents `9449aa4ac48fe6672fb743b421a7e5bcb7311760` and exact feature tip `8842733...`
- release tree: `6c3095e497efb44d2da51f48471b0b5cf4f06c9e`; version from post-merge dev count: `dev-971`
- finish refs: origin main `0c1e9e6a3f181eaebe59f888043fadc37385741d`, origin dev `eaf8fbaf2199333dd30082cfd0f0b41cbb801e5b`, peeled tag `dev-971=0c1e9e6...`, tag tree `6c3095e...`; release heads 0
pre-build tests:
- correctly quoted runner sequential command: 49 packages passed, `PRE_BUILD_TESTS=PASS`
- post-build repeat: the same 49 packages passed
- Windows release-built credentiallease test executable passed on OneXPlayer; Windows ownership tests passed and Unix-only test skipped as expected
four artifact paths/SHA-256/go-version-m source:
- `build/dev-runtime/bin/edge`: `e46573e95452d46dba5f20f17583aa15f5a461b6ec269501c2a5129c8a10a9c4`
- `build/dev-runtime/bin/iop-node`: `5b07284870975d20b06c76ec739c34451777610be4cece1f9e7bf3809126fe5c`
- `build/dev-runtime/bin/iop-node-linux-arm64`: `1eae087c99e47fc04cd5cf7b125432e5ae345ab3d43c8ba553291a46502bcfe9`
- `build/dev-runtime/bin/iop-node-windows-amd64.exe`: `dbc39c94fff2f646e03bfc076ba84e0bc3f8a52d9a168acb43f74232fdc41153`
- all four: `vcs.revision=003398a149c1433cb21bc8aa2720bde272b87373`, `vcs.modified=false`
config/refresh checks:
- candidate `config check`: pass
- `config refresh --help`: subcommand/options present
- `config refresh --mode dry-run`: `status=applied`, no changed or restart-required paths
- authenticated `/v1/models`: HTTP 200, 9 models, required `laguna-s:2.1` and `ornith:35b` present
process/listener/runtime identity:
- runner Edge PID 94139; mac node PID 94279; Edge listeners `*:18082,*:18083,*:18084,127.0.0.1:19093`
- GX10 PID 791049, command `./iop-node --config ./node.yaml serve`, Linux artifact hash exact, established 18084 connection 1
- OneXPlayer PID 35640, `Win32_Process.Create`-owned release command, Windows artifact hash exact, established 18084 connection 1
- RTX5090 PID 31332, `Win32_Process.Create`-owned release command, Windows artifact hash exact, established 18084 connection 1; no Startup/Run/Task/Service owner was created
four connected nodes/provider snapshots:
- `mac-codex-node=true`: `mac-gemini-api 1/0/0 healthy`, `mac-mlx-vllm 2/0/0 healthy`, `glm-coding 1/0/0 healthy`, `anthropic-api 1/0/0 healthy`, `openai-api 1/0/0 healthy`
- `gx10-vllm-node=true`: `gx10-vllm 4/0/0 healthy`
- `onexplayer-lemonade-node=true`: `onexplayer-lemonade 3/0/0 healthy`
- `rtx5090-lemonade-node=true`: `rtx5090-lemonade 1/0/0 healthy`
responses capacity+1 evidence:
- `laguna-s:2.1`, requests 5, expected capacity 4, `max_in_flight=4`, `max_queued=1`, `max_by_provider={gx10-vllm:4}`, failures 0, final `gx10-vllm 0/0 healthy`
chat-completions capacity+1 evidence:
- `laguna-s:2.1`, requests 5, expected capacity 4, `max_in_flight=4`, `max_queued=1`, `max_by_provider={gx10-vllm:4}`, failures 0, final `gx10-vllm 0/0 healthy`
release finish/tag/atomic push/cleanup:
- finish preflight preserved origin dev `003398a...`, origin main `bc5b326140c809d6d3685ca35d7311e31a180d7c`, release HEAD `003398a...`, no `dev-971` tag
- `git flow release finish --keepremote -m "Release dev-971" dev-971` succeeded; local tag tree equaled deployed tree
- one atomic push updated main/dev/tag and deleted `release/dev-971`; runner fetch/prune, clean `dev` sync, and local release cleanup succeeded
```
### REVIEW_API-3
```text
Authorization/runtime state: `blocked` — the single permitted fresh run exhausted all 3 fresh CDP browser/profile attempts for `codex-gpt-direct`; the plan and benchmark skill prohibit an implicit retry.
Resume condition: An explicitly authorized follow-up qualification may start after CDP socket stability is restored and can execute one new fresh preflight/run without resuming, retrying, or modifying either retained run.
command: /bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py preflight --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json
exit_code: 0
stdout: ok: preflight run_id=run-20260813T020750Z-69a263acc2c2 status=ready ready=5 registration_required=0 implementation_gap=0
stderr: (none)
command: /bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py run --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json
exit_code: 0
stdout: ok: run run_id=run-20260813T020758Z-2920dc067c4e executed=5 unresolved=0 completed=3 timed_out=2 cancelled=0 interrupted=0 running=0 product_succeeded=1 product_failed=2 product_unknown=2 harness_passed=3 harness_failed=2 process_exited=3 process_signalled=0 process_timed_out=2 process_cancelled=0 process_not_started=0 artifact_passed=0 artifact_failed=4 artifact_blocked=1 artifact_not_run=0
stderr: (none)
preflight command count and fresh observation id:
- direct preflight command count: 1
- fresh observation run id: `run-20260813T020750Z-69a263acc2c2`; `ready=5`
run command count, run id, attempt ids:
- direct run command count: 1; resume/retry count: 0
- run id: `run-20260813T020758Z-2920dc067c4e`
- exactly five `attempt-000001`: `agy-gemini-direct`, `claude-gemini-direct`, `claude-gpt-direct`, `claude-sonnet-direct`, `codex-gpt-direct` (all repetition 0001)
five controller/product/harness/process/web-validation terminals:
- `agy-gemini-direct`: controller completed; product failed/caller_error; harness passed/success/ordered terminal/cleanup complete; process exited 1; web failed/generated_missing
- `claude-gemini-direct`: controller completed; product succeeded/caller_success; harness passed/success/ordered terminal/cleanup complete; process exited 0; web failed/accessibility_failed with Chrome observed, 2 screenshots and 2 viewports
- `claude-gpt-direct`: controller completed; product failed/caller_error; harness passed/success/ordered terminal/cleanup complete; process exited 1; web failed/generated_missing
- `claude-sonnet-direct`: controller timed_out; product unknown/unavailable; harness failed/timed_out/cleanup complete; process timed_out 143; web failed/generated_missing
- `codex-gpt-direct`: controller timed_out; product unknown/unavailable; harness failed/timed_out/cleanup complete; process timed_out signal 15; generated files regular; web blocked/cdp_socket_closed
unresolved/running/interrupted summary:
- `executed=5`, `unresolved=0`, `running=0`, `interrupted=0`; controller `completed=3`, `timed_out=2`; five `attempt.json` and five `web-validation.json` files present
safe rejection origin and CDP/browser result:
- fresh AGY attempt is a caller error; candidate Edge stdout/stderr contained no closed four-field Gemini rejection observation for this attempt, so no live `origin` is inferred or claimed. Unit regression evidence proves auth/managed=`pre_ingress`, raw tunnel 4xx=`provider_http` once, normalized SubmitRun failure=no `provider_http`, and exactly four secret-free fields.
- Codex web validation has `status=blocked`, `reason=cdp_socket_closed`, browser not observed. The renderer's closed transient loop makes at most 3 fresh browser/profile attempts and rethrows the third `CDP socket closed`, so the required no-exhaustion admission gate failed. No retry/resume/new run was invoked.
retained-run file count/digest after new run:
- path `agent-test/runs/bench-01-direct-preflight/run-20260812T222805Z-bec48f5fffaa/`
- regular files 5,489; aggregate sorted full-path file SHA-256 stream digest `d089cd4b3e9bfd4e8ebe3bfa82032a544f0625e0793ad9763b728addf62baffd`; unchanged
```
### Final Verification
```text
command: go test -count=1 ./apps/edge/internal/openai
exit_code: 0
stdout: ok iop/apps/edge/internal/openai 8.603s
stderr: (none)
command: python3 -m unittest scripts.agent_benchmark.skill_contract_test
exit_code: 0
stdout: Ran 55 tests in 2.103s / OK
stderr: (55 progress dots only)
command: python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
exit_code: 0
stdout: Ran 459 tests in 155.914s / OK
stderr: (459 progress dots only)
command: python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/dev-runtime-deploy
exit_code: 0
stdout: Skill is valid!
stderr: (none)
command: python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/iop-agent-comparison-benchmark
exit_code: 0
stdout: Skill is valid!
stderr: (none)
command: python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json
exit_code: 0
stdout: ok: manifest is valid
stderr: (none)
command: python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
exit_code: 0
stdout: ok: manifest is valid
stderr: (none)
command: while IFS= read -r package; do go test -count=1 "$package" || exit; done < <(go list ./apps/control-plane/... ./apps/edge/... ./apps/node/... ./packages/go/... ./scripts/... | sed '/^iop\/packages\/go\/agenttask$/d')
exit_code: 0
stdout: 49 packages passed sequentially; full output `/tmp/iop-g10-final-go-sequential.log`
stderr: (none)
command: git diff --check -- . ':(exclude)agent-task/archive/**'
exit_code: 0
stdout: (none; executed before writing this untracked implementation-evidence file)
stderr: (none)
command: git status --short --branch
exit_code: 0
stdout: `## feature/iop-one-shot-agent-model-comparison...origin/feature/iop-one-shot-agent-model-comparison` and `?? agent-task/m-iop-one-shot-agent-model-comparison/`; source worktree has no tracked modifications, active plan/review evidence remains untracked as required for review handoff
stderr: (none)
```
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.
## Section Ownership
| Section | Owner | Note |
|---------|-------|------|
| Header comment, Overview, Review Agent Instructions | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
| Implementation Item Completion (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
| Implementation Checklist (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[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 placeholder text with actual content |
| Reviewer Checkpoints | Fixed at stub creation | Pre-filled from plan |
| Verification Results (section headings + commands) | Implementing agent, then review agent | Implementing agent records initial output; review agent reruns applicable commands and may fill, replace, or append fresh verified output before verdict. Implementing-agent command changes require a `Deviations from Plan` entry |
| Code Review Result | Review agent appends | Not included in stub |
## Reviewer Fresh Verification
### Deterministic source and contract checks
```text
command: go test -count=1 ./apps/edge/internal/openai -run 'Gemini.*Rejection|GeminiIngressRejectsAuthentication'
exit_code: 0
stdout: ok iop/apps/edge/internal/openai 0.059s
stderr: (none)
command: go test -count=1 ./apps/edge/internal/openai
exit_code: 0
stdout: ok iop/apps/edge/internal/openai 8.865s
stderr: (none)
command: python3 -m unittest scripts.agent_benchmark.skill_contract_test
exit_code: 0
stdout: Ran 55 tests in 3.017s / OK
stderr: (55 progress dots only)
command: python3 -m unittest scripts.agent_benchmark.browser_cdp_test scripts.agent_benchmark.web_validation_test
exit_code: 0
stdout: Ran 25 tests in 42.993s / OK
stderr: (25 progress dots only)
command: python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
exit_code: 0
stdout: Ran 459 tests in 167.011s / OK
stderr: (459 progress dots only)
command: python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/dev-runtime-deploy && python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/iop-agent-comparison-benchmark
exit_code: 0
stdout: Skill is valid! / Skill is valid!
stderr: (none)
command: python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json && python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
exit_code: 0
stdout: ok: manifest is valid / ok: manifest is valid
stderr: (none)
command: while IFS= read -r package; do go test -count=1 "$package" || exit; done < <(go list ./apps/control-plane/... ./apps/edge/... ./apps/node/... ./packages/go/... ./scripts/... | sed '/^iop\/packages\/go\/agenttask$/d')
exit_code: 0
stdout: 49 packages passed sequentially; final package `ok iop/scripts/inventory-query 0.012s`
stderr: (none)
command: git diff --check 28ed27a575f6e3ba473a8c76c78a9e82027525f2..884273310301888aa9f200270066956603f8d439
exit_code: 0
stdout: (none)
stderr: (none)
```
### Release identity and immutable evidence audit
```text
command: git ls-remote --heads origin 'refs/heads/release/*'
exit_code: 0
stdout: (empty at review time before a later independent release was started)
stderr: (none)
command: git ls-remote origin refs/heads/main refs/heads/dev refs/heads/feature/iop-one-shot-agent-model-comparison refs/tags/dev-971 refs/tags/dev-971^{}
exit_code: 0
stdout:
- origin feature = 884273310301888aa9f200270066956603f8d439
- origin main = 0c1e9e6a3f181eaebe59f888043fadc37385741d
- peeled dev-971 tag = 0c1e9e6a3f181eaebe59f888043fadc37385741d
- origin dev at the first reviewer query = eaf8fbaf2199333dd30082cfd0f0b41cbb801e5b
stderr: (none)
command: local ancestry/tree audit for feature 8842733, release source 003398a, main 0c1e9e6, dev eaf8fba, and tag dev-971
exit_code: 0
stdout:
- feature 8842733 is an ancestor of release source 003398a
- release source 003398a is an ancestor of both released main and dev
- release source, released main, released dev, and peeled tag share tree 6c3095e497efb44d2da51f48471b0b5cf4f06c9e
stderr: (none)
command: retained run regular-file count and sorted full-path SHA-256 stream digest
exit_code: 0
stdout: files=5489 / digest=d089cd4b3e9bfd4e8ebe3bfa82032a544f0625e0793ad9763b728addf62baffd
stderr: (none)
note: A later independent deployment had moved the declared runner to clean `release/dev-974` by the final read-only runner audit. Therefore the reviewer did not reinterpret the current live binaries as dev-971 evidence; the immutable dev-971 refs/tree and the implementation-time redacted deployment evidence remain the applicable historical release proof.
```
### Fresh direct qualification admission audit
```text
command: python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json --run-id run-20260813T020758Z-2920dc067c4e
exit_code: 0
stdout: ok: status run_id=run-20260813T020758Z-2920dc067c4e unresolved=0 completed=3 timed_out=2 cancelled=0 interrupted=0 running=0 product_succeeded=1 product_failed=2 product_unknown=2 harness_passed=3 harness_failed=2 process_exited=3 process_signalled=0 process_timed_out=2 process_cancelled=0 process_not_started=0 artifact_passed=0 artifact_failed=4 artifact_blocked=1 artifact_not_run=0
stderr: (none)
artifact: agent-test/runs/bench-01-direct-preflight/run-20260813T020758Z-2920dc067c4e/cells/codex-gpt-direct/repetition-0001/attempt-000001/web-validation.json
verified fields: status=blocked, reason=cdp_socket_closed, browser.status=not_observed, screenshots=0, viewports=0
command: BrowserRenderer.render against the immutable codex-gpt-direct workspace with a separate /tmp output root and the manifest desktop/mobile viewport sizes
exit_code: 0
stdout: render=pass / browser=Chrome/151.0.7922.34 / requests=12 / viewports=2
stderr: (none)
interpretation: Current source and browser can render the same generated workspace, so no deterministic repository defect was reproduced. The immutable qualification run nevertheless exhausted its three permitted fresh Chromium/CDP attempts and remains an infrastructure-blocked admission failure. The plan and benchmark contract prohibit silently retrying or replacing that run.
```
## Code Review Result
### Overall Verdict
FAIL
### Dimension Assessment
| Dimension | Result | Evidence |
|---|---|---|
| Correctness | Pass | The Gemini rejection owner is now attached only to actual provider HTTP commit points; auth and managed caller-credential failures are `pre_ingress`, and normalized Edge-local failure is negative-covered. |
| Completeness | Fail | `REVIEW_API-3` and its implementation checklist remain incomplete because the only permitted fresh qualification contains one exhausted CDP infrastructure block. |
| Test coverage | Pass | Focused Edge tests, the complete OpenAI package, browser/web-validation tests, 459 benchmark tests, and the corrected 49-package sequential suite pass freshly. |
| API contract | Pass | Rejection observations retain the closed four-field safe projection, release refs/tree preserve one exact source identity, and no public error body contract changed. |
| Code quality | Pass | The reviewed changes are bounded to the selected provenance and deployment-contract seams and contain no debug residue or unrelated source mutation. |
| Implementation deviation | Pass | The implementation correctly did not resume or retry the blocked qualification and accurately left `REVIEW_API-3` unchecked. |
| Verification trust | Pass | Fresh deterministic checks reproduce the passing claims, ref/tree evidence corroborates dev-971, and the implementation accurately reports the CDP exhaustion instead of claiming admission. |
| Spec conformance | Fail | SDD D13/S02 requires no exhausted browser/CDP infrastructure block; `artifact_blocked=1` and `reason=cdp_socket_closed` violate that mandatory condition. |
### Findings
- **Required R1 — The fresh five-cell qualification did not satisfy the no-infrastructure-exhaustion admission gate.**
- **Evidence:** The reviewer-run status command for `run-20260813T020758Z-2920dc067c4e` returns `unresolved=0` but also `artifact_blocked=1`. Its immutable `codex-gpt-direct/.../web-validation.json` is `status=blocked`, `reason=cdp_socket_closed`, with no browser, screenshot, or viewport observation. `scripts/agent_benchmark/browser_cdp.py:609-630` permits three fresh attempts for this closed transient class and rethrows the third failure. A reviewer-only render of the same immutable workspace to a separate `/tmp` output root now succeeds with Chrome 151 and both viewports, confirming that the stored run is a transient infrastructure exhaustion rather than an unresolved deterministic source defect. SDD D13/S02 and `REVIEW_API-3` explicitly require no such exhausted block.
- **Root Cause:** During the single authorized qualification run, all three fresh Chromium/profile attempts for the Codex slot lost the CDP socket before a browser observation could be committed. Because run artifacts are immutable and the benchmark contract forbids an implicit retry, later renderer recovery cannot retrofit the missing terminal web evidence into that run.
- **Selected Fix:** Do not change repository source based on this non-reproducing transient. After explicit authorization for one new unscored qualification identity, first confirm the declared Chromium/CDP runner can render through the bounded renderer, then invoke exactly one new direct `preflight` and exactly one new direct `run` through `/bin/bash /tmp/iop-bench-13-env`. Do not `resume`, use `--retry-failed`, modify either existing run, or invoke any caller outside the benchmark CLI. Accept only `ready=5`, exactly five new attempts, `unresolved=0`, `running=0`, `interrupted=0`, five terminal web records, and `artifact_blocked=0`; then recompute the retained-run count/digest.
- **Disposition:** external-execution user-review gate; user authorization is required before allocating the new run identity.
- **Acceptance Commands:** `/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py preflight --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json`; `/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py run --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json`; `python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json --run-id <new-run-id>`; retained-run count/digest audit.
### Routing Signals
- `review_rework_count=2`
- `evidence_integrity_failure=false`
### Next Step
Archive the current pair and write `USER_REVIEW.md` with the `external-execution` gate. Do not create a follow-up plan or `complete.log` until the user explicitly authorizes one new unscored qualification and the resulting evidence resolves this stop state.

View file

@ -0,0 +1,42 @@
<!-- task=m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission plan=2 tag=REVIEW_API milestone-task=agy-iop-compatibility,route-readiness,objective-validation -->
# Complete - m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission
## 완료 일시
2026-08-13
## 요약
두 차례 FAIL과 사용자 승인 정지를 거친 뒤, 승인된 replacement qualification이 SDD D13/S02의 terminal-evidence admission을 충족하여 최종 PASS했다.
## 루프 이력
| Plan | Review | Verdict | 메모 |
|------|--------|---------|------|
| `plan_cloud_G09_0.log` | `code_review_cloud_G09_0.log` | FAIL | Gemini rejection provenance와 dev release/qualification 증거 보완이 필요했다. |
| `plan_cloud_G10_1.log` | `code_review_cloud_G10_1.log` | FAIL | 첫 qualification에서 Chromium/CDP 시도 소진으로 `artifact_blocked=1`이 발생했다. |
| `user_review_0.log` | replacement unscored qualification 승인 | RESOLVED | 기존 실패 run을 보존하고 새 qualification identity 한 건을 허용했다. |
| `plan_cloud_G08_2.log` | `code_review_cloud_G08_2.log` | PASS | 새 run이 5개 terminal slot, `unresolved=0`, `artifact_blocked=0`을 충족했다. |
## 구현/정리 내용
- `dev-974` release/tag/artifact/runtime/provider identity를 read-only로 동결 확인했다.
- bounded Chromium/CDP admission 뒤 direct preflight 1회와 replacement run 1회만 실행했다.
- 새 run의 five-slot terminal evidence와 이전 두 run의 immutable count/digest를 검증했다.
## 최종 검증
- `python3 -m unittest scripts.agent_benchmark.browser_cdp_test.BrowserIntegrationTest.test_valid_page_emits_complete_two_viewport_observations` - PASS; 실제 Chromium/CDP 두 viewport 통합 테스트 1건 통과.
- `/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json --run-id run-20260813T064211Z-212a8e1c4b9f` - PASS; `unresolved=0`, `running=0`, `interrupted=0`, `artifact_blocked=0`.
- exact run file audit - PASS; `attempt.json=5`, `web-validation.json=5`, 모든 slot terminal.
- immutable prior-run audit - PASS; failed run 5,497 files/digest `14711f384e2b577d26a4897c67d88e56b8766a892e735048fa8ad7d55ad444c1`, retained run 5,489 files/digest `d089cd4b3e9bfd4e8ebe3bfa82032a544f0625e0793ad9763b728addf62baffd`.
- `git diff --check -- . ':(exclude)agent-task/archive/**'` - PASS.
## 잔여 Nit
- 없음
## 후속 작업
- 없음

View file

@ -0,0 +1,225 @@
<!-- task=m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission plan=2 tag=REVIEW_API milestone-task=agy-iop-compatibility,route-readiness,objective-validation -->
# Complete the authorized terminal-evidence qualification
## For the Implementing Agent
Execute only the authorized verification packet below. Do not modify product, benchmark, manifest, runtime, release, credential, or subscription configuration; do not finish `dev-974` on behalf of another task. Run every verification command, fill the implementation-owned sections of `CODE_REVIEW-cloud-G08.md` with actual output, keep both active files in place, and report ready for review. If a prerequisite is not met, record the exact blocker and resume condition without allocating a benchmark run or asking the user.
## Background
The prior implementation and deterministic review passed, but the one authorized qualification exhausted all three Chromium/CDP attempts for one cell. The user has now explicitly authorized one replacement unscored qualification identity. An independent `iop-s1` rollout subsequently placed the shared dev runtime on candidate SHA `2fcc1093c7629ab94b460d083520ffd9c8064815`; this packet must wait until that release finishes, then verify and freeze the resulting identity before any caller is invoked.
## Archive Evidence Snapshot
- Prior plan: `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/plan_cloud_G10_1.log`.
- Prior review: `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/code_review_cloud_G10_1.log`, verdict `FAIL`, Required R1 only, Suggested/Nit none.
- Resolved authorization: `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/user_review_0.log`; the authorization enables new verification and is not terminal PASS evidence.
- Failed immutable qualification: `run-20260813T020758Z-2920dc067c4e`, `unresolved=0`, `artifact_blocked=1`, `reason=cdp_socket_closed` for `codex-gpt-direct`.
- Retained immutable run: `agent-test/runs/bench-01-direct-preflight/run-20260812T222805Z-bec48f5fffaa/`, 5,489 regular files and digest `d089cd4b3e9bfd4e8ebe3bfa82032a544f0625e0793ad9763b728addf62baffd`.
## Finding Resolution Map
| Finding | Reviewer evidence | Exact root cause | Selected fix | Mode | Changed precondition | Acceptance commands |
|---|---|---|---|---|---|---|
| Required R1 | The prior fresh run has one immutable `blocked/cdp_socket_closed` record after the renderer's three bounded attempts, while a later isolated render of the same workspace passed. | A transient Chromium/CDP socket loss exhausted the run-owned renderer attempts; immutable evidence cannot be repaired or retried in place. | After the independent `dev-974` release is finished and frozen, pass one bounded renderer preflight, then execute exactly one new direct CLI `preflight` and exactly one new direct CLI `run`; accept only complete terminal evidence with `artifact_blocked=0`. | direct-fix | The user explicitly authorized one new unscored qualification identity, and the renderer must pass before allocation. | Browser integration test; benchmark `preflight`; one benchmark `run`; exact-run `status`; retained-run digest audit. |
## Analysis
### Files Read
- `agent-ops/skills/project/iop-agent-comparison-benchmark/SKILL.md`
- `agent-ops/skills/project/dev-runtime-deploy/SKILL.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/code-review/SKILL.md`
- `agent-ops/skills/common/finalize-task-routing/SKILL.md`
- `agent-test/local/rules.md`
- `agent-test/dev/rules.md`
- `agent-test/inventory-dev.yaml`
- `scripts/agent_benchmark/browser_cdp.py`
- `scripts/agent_benchmark/browser_cdp_test.py`
- `scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/iop-one-shot-agent-model-comparison.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-one-shot-agent-model-comparison/SDD.md`
- the three exact archive evidence paths listed above
### SDD Criteria
- SDD status is approved and unlocked.
- Preserved `milestone-task`: `agy-iop-compatibility,route-readiness,objective-validation`.
- D13/S02 require fresh `ready=5`, exactly five attempts, all controller/product/harness/process/web-validation terminals, `unresolved=0`, `running=0`, `interrupted=0`, and no exhausted browser/CDP infrastructure block.
- Product failure, caller failure, provider rejection, generated-missing, and timeout remain measured terminal outcomes and must not be retried or reinterpreted.
### Verification Context
- Authorized runner/workdir: `/config/workspace/iop-s0`, wrapper `/bin/bash /tmp/iop-bench-13-env`.
- Shared runtime: remote runner `toki@toki-labs.com`, checkout `/Users/toki/agent-work/iop-dev`, Edge ports `18082/18083/18084/19093/19101`.
- Latest read-only observation: checkout, four build artifacts, and live processes use source `2fcc1093c7629ab94b460d083520ffd9c8064815`; all four Nodes are connected and all relevant providers are healthy/idle. The release was not yet finished at plan creation: remote `release/dev-974` existed and tag `dev-974` did not.
- Execution prerequisite: wait for the other task to finish `dev-974`. Require remote tag `dev-974`, no remote `release/dev-974`, the tag tree to equal the deployed source tree, four artifact VCS revisions to equal the frozen source commit, required listeners open, four connected Nodes, and healthy/idle provider snapshots. Do not mutate refs, binaries, config, or processes in this packet.
- Benchmark-only Codex API-key injection remains scoped to the wrapper/manifest path; normal Codex subscription configuration is not modified. Workspaces remain under the validated run root; `../iop-s2` is read-only testbed provenance, not the HTML editing workspace.
- Confidence is high for the deterministic harness and low only for live external outcome quality, which is intentionally measured rather than assumed.
### Test Coverage Gaps
- Deterministic renderer tests prove Chromium/CDP availability but cannot replace a fresh run-owned browser observation.
- Runtime identity and provider health are external temporal state and must be rechecked immediately before allocation.
- No source fix is justified because the same immutable generated workspace later rendered successfully.
### Symbol References
None; this packet changes no source symbols.
### Split Judgment
Keep one compact verification packet. Runtime freeze, bounded renderer admission, one CLI allocation, exact terminal audit, and retained-run immutability form one acceptance transaction; splitting would allow the shared runtime or browser state to change between admission and execution.
### Scope Rationale
- In scope: read-only release/runtime identity checks, one bounded renderer integration test, exactly one direct preflight, exactly one direct run, exact status/evidence audit, retained-run immutability audit, and review evidence.
- Out of scope: product/harness source changes, `dev-974` finish or rollback, `iop-s1` task changes, manifests, credentials, subscriptions, route/model/effort changes, resume/retry, ad-hoc caller/provider invocation, scored C01-C09 execution, and old-run mutation.
### Final Routing
- `evaluation_mode=isolated-reassessment`; all build/review closure fields are true and no capability gap remains.
- Finalizer: `finalize-task-policy.sh pair`.
- Build scores: scope 1, state 2, blast 1, evidence 2, verification 2 = G08; base `local-fit`, `review_rework_count=2`, `evidence_integrity_failure=false`, so route basis `recovery-boundary`, lane `cloud`, catalog `worker/cloud/G08`, filename `PLAN-cloud-G08.md`.
- Review scores: 1/2/1/2/2 = G08; route `official-review`, lane `cloud`, catalog `review/cloud/G08`, filename `CODE_REVIEW-cloud-G08.md`.
- `large_indivisible_context=false`; matched loop-risk signatures are `temporal_state`, `boundary_contract`, and `variant_product` (3).
## Implementation Checklist
- [ ] [REVIEW_API-4] Prove `dev-974` is finished and freeze one clean release/artifact/runtime/provider identity without mutating shared runtime state.
- [ ] [REVIEW_API-5] Pass one bounded Chromium/CDP renderer integration preflight before any new benchmark allocation.
- [ ] [REVIEW_API-6] Through `/bin/bash /tmp/iop-bench-13-env`, invoke exactly one fresh direct CLI `preflight` and exactly one fresh direct CLI `run`, with no resume/retry or alternate caller path.
- [ ] [REVIEW_API-7] Audit the issued run for exactly five attempts, complete terminal axes, `artifact_blocked=0`, and audit both prior runs for immutability.
- [ ] Fill implementation-owned sections in `CODE_REVIEW-cloud-G08.md` with actual commands and output.
### [REVIEW_API-4] Freeze the completed release and live runtime
#### Problem
The shared runtime was changed by another task after the prior qualification. Starting a new run while its release branch is unfinished would make the benchmark source identity unstable.
#### Solution
Perform read-only local/remote ref, tree, artifact, listener, Node, and provider checks. Continue only after `dev-974` is fully finished and its deployed source is one frozen identity. Do not finish, merge, reset, redeploy, restart, or edit the other task.
#### Modified Files and Checklist
- [ ] `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/CODE_REVIEW-cloud-G08.md`: record only redacted ref/tree/artifact/listener/Node/provider identity evidence.
#### Test Strategy
This is a read-only temporal gate. Any missing tag, surviving release branch, identity mismatch, disconnected Node, unhealthy provider, or nonzero in-flight/queue blocks allocation.
#### Verification
```bash
git ls-remote origin refs/heads/main refs/heads/dev refs/heads/release/dev-974 refs/tags/dev-974 'refs/tags/dev-974^{}'
ssh toki@toki-labs.com '/bin/zsh -lc '\''cd /Users/toki/agent-work/iop-dev && git status --short --branch && git rev-parse HEAD && for f in build/dev-runtime/bin/edge build/dev-runtime/bin/iop-node build/dev-runtime/bin/iop-node-linux-arm64 build/dev-runtime/bin/iop-node-windows-amd64.exe; do go version -m "$f" | sed -n "/vcs.revision/p;/vcs.modified/p"; done'\'''
```
Expected: tag exists, remote release branch is absent, checkout is clean, source/tag/artifact trees agree, all required listeners are open, all four Nodes are connected, and providers are healthy with `in_flight=0`, `queued=0`.
### [REVIEW_API-5] Bound Chromium/CDP admission
#### Problem
The prior run exhausted all renderer attempts for one cell even though the same workspace later rendered successfully.
#### Solution
Run the repository's bounded two-viewport BrowserRenderer integration test once immediately before benchmark preflight. It uses a temporary isolated workspace/profile and exercises actual Chromium/CDP startup, navigation, screenshots, image facts, and cleanup.
#### Modified Files and Checklist
- [ ] `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/CODE_REVIEW-cloud-G08.md`: record the exact test output.
#### Test Strategy
No mock is accepted for this gate. Failure stops before benchmark preflight/run; do not loop the test.
#### Verification
```bash
python3 -m unittest scripts.agent_benchmark.browser_cdp_test.BrowserIntegrationTest.test_valid_page_emits_complete_two_viewport_observations
```
Expected: one test passes with no lingering browser process owned by the test.
### [REVIEW_API-6] Allocate one authorized direct qualification
#### Problem
Authorization now exists, but no replacement qualification evidence exists.
#### Solution
After REVIEW_API-4/5 pass, invoke one public CLI preflight and one public CLI run through the protected wrapper. Capture the CLI-issued run ID. Do not run `resume`, `--retry-failed`, another `run`, or any caller/provider command outside the benchmark CLI.
#### Modified Files and Checklist
- [ ] `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/CODE_REVIEW-cloud-G08.md`: record exact stdout/stderr, exit codes, command counts, and issued run ID without secrets.
#### Test Strategy
The live test is outcome-neutral. Product/harness/process/artifact failures remain recorded; only preflight blocking, incomplete terminal evidence, Edge pre-ingress incompatibility, or exhausted browser/CDP infrastructure blocks admission.
#### Verification
```bash
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py preflight --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py run --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json
```
Expected: `ready=5`; exactly one new run ID; five attempt slots; `unresolved=0`, `running=0`, `interrupted=0`, and `artifact_blocked=0`.
### [REVIEW_API-7] Audit terminal and immutable evidence
#### Problem
CLI completion alone does not prove five complete web records or prior-run immutability.
#### Solution
Query the exact new run through the public status command, inspect only closed non-secret evidence fields, and recompute prior-run counts/digests. Do not edit any run tree.
#### Modified Files and Checklist
- [ ] `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/CODE_REVIEW-cloud-G08.md`: record exact status, five terminal summaries, safe rejection origin when present, CDP result, and immutable-run audits.
#### Test Strategy
Require five `attempt.json` and five terminal `web-validation.json` files. A terminal product failure is acceptable; an exhausted browser block is not.
#### Verification
```bash
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json --run-id <issued-run-id>
```
Expected: exactly five terminal slots and `artifact_blocked=0`; `run-20260813T020758Z-2920dc067c4e` remains unchanged; retained run remains 5,489 files with digest `d089cd4b3e9bfd4e8ebe3bfa82032a544f0625e0793ad9763b728addf62baffd`.
## Modified Files Summary
| File | Items |
|---|---|
| `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/CODE_REVIEW-cloud-G08.md` | REVIEW_API-4, REVIEW_API-5, REVIEW_API-6, REVIEW_API-7 |
## Dependencies and Execution Order
1. Wait for the independent `dev-974` release to finish; do not launch the benchmark while its remote release branch exists or its tag is absent.
2. Pass REVIEW_API-4, then REVIEW_API-5.
3. Execute REVIEW_API-6 exactly once.
4. Complete REVIEW_API-7 without mutating any run.
## Final Verification
```bash
python3 -m unittest scripts.agent_benchmark.browser_cdp_test.BrowserIntegrationTest.test_valid_page_emits_complete_two_viewport_observations
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json --run-id <issued-run-id>
git diff --check -- . ':(exclude)agent-task/archive/**'
git status --short --branch
```
Expected: released runtime identity is frozen, browser integration passes, the one authorized run has five complete terminal slots and no infrastructure block, prior runs are immutable, and only task evidence changed. Cached browser output is not accepted.

View file

@ -0,0 +1,245 @@
<!-- task=m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission plan=0 tag=API milestone-task=agy-iop-compatibility,route-readiness,objective-validation -->
# Direct runtime compatibility and terminal-evidence admission
## For the Implementing Agent
Implement the selected fixes exactly, verify them, commit and push the current feature branch, merge that feature ref into clean `dev`, and deploy it with the project `dev-runtime-deploy` procedure. The user explicitly requested dispatcher execution and approved continuous work through the final benchmark; this packet may therefore use the dispatcher as the task-loop owner, but every live benchmark caller must still be invoked only by `scripts/agent_comparison_benchmark.py`. Do not resume or mutate an old run, invoke a caller/provider ad hoc, expose credentials, substitute a model/route/effort, or turn a product failure into a harness pass. Fill every implementation-owned section of the paired review file with actual output and leave finalization to official review.
## Background
The retained direct run `run-20260812T222805Z-bec48f5fffaa` is terminal (`unresolved=0`) but is not all-success: Codex→GPT passed, Claude→Gemini produced files but browser validation lost its CDP socket, agy→Gemini and Claude→GPT received HTTP 400, and Claude→Sonnet timed out after active work. Task 13 correctly stopped under the old 5/5 admission rule, but that rule conflated benchmark outcomes with incomplete measurement and caused repeated pre-benchmark cycles. This packet removes that conflation while repairing two identified measurement/runtime compatibility defects: Gemini tool-call identity is not round-tripped and transient CDP loss has no bounded renderer restart.
## Archive Evidence Snapshot
- `agent-task/archive/2026/08/m-iop-one-shot-agent-model-comparison/13+12_scored_benchmark_result/complete.log` is the satisfied predecessor. It records the one direct run, complete five-slot terminal evidence, and zero C01-C09 allocations.
- The exact retained run above and every older run are immutable evidence. Never resume, retry, edit, delete, or select them as the new diagnostic result.
- Its agy cell reached the Gemini bridge for the initial and continuation requests; the continuation returned 400. Current source synthesizes Chat tool IDs, discards upstream `delta.tool_calls[].id`, and emits a non-standard `tool_name` field on the tool result.
- Its Claude→GPT cell returned a first-turn provider HTTP 400. No raw provider body may be copied into tracked evidence. A sanitized origin/status observation is required so a provider rejection is not misclassified as an Edge ingress warning.
- Its Claude→Gemini artifact is `blocked/cdp_socket_closed`; Codex→GPT later rendered successfully on the same host. This is a transient validation-infrastructure class, not a product-quality result.
## Analysis
### Files Read
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/dev/rules.md`
- `agent-test/dev/testing-smoke.md`
- `agent-ops/skills/project/dev-runtime-deploy/SKILL.md`
- `agent-ops/skills/project/iop-agent-comparison-benchmark/SKILL.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/iop-one-shot-agent-model-comparison.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-one-shot-agent-model-comparison/SDD.md`
- `agent-spec/testing/agent-comparison-benchmark.md`
- `docs/agent-comparison-benchmark-dev-guide.md`
- `apps/edge/internal/openai/gemini_types.go`
- `apps/edge/internal/openai/gemini_handler.go`
- `apps/edge/internal/openai/gemini_bridge.go`
- `apps/edge/internal/openai/gemini_handler_test.go`
- `apps/edge/internal/openai/anthropic_handler.go`
- `apps/edge/internal/openai/anthropic_bridge.go`
- `apps/edge/internal/openai/anthropic_stream.go`
- `apps/edge/internal/openai/anthropic_bridge_test.go`
- `scripts/agent_benchmark/browser_cdp.py`
- `scripts/agent_benchmark/browser_cdp_test.py`
- the predecessor `complete.log` and its directly linked plan/review logs
### Selected Root Causes and Fixes
1. Gemini native continuation loses the provider-issued function-call ID. `geminiFunctionCall`/`geminiFunctionResponse` have no `id`; response streaming ignores OpenAI `delta.tool_calls[].id`; request conversion always synthesizes a positional ID and emits `tool_name` on the OpenAI tool message. Preserve a bounded valid ID end-to-end, match explicit response IDs to the pending same-name call, keep the positional/FIFO path only when the native request omitted IDs, reject duplicates/mismatches, and emit only standard `role`, `tool_call_id`, and `content` for tool-result messages.
2. The browser renderer owns one Chromium process and immediately converts a recoverable start/handshake/socket loss into `artifact=blocked`. Split the current body into one-attempt rendering and a wrapper that performs at most three total fresh browser/profile attempts only for the closed transient classes. Every failed attempt must reap its process group and delete partial screenshots before the next attempt. Source/product validation is never retried.
3. HTTP 400 origin is opaque. Add secret-safe observations for Gemini pre-ingress rejection versus upstream provider rejection and Anthropic→Chat upstream provider rejection. Log only surface/bridge, a closed rejection class, and HTTP status; never log request/response bodies, headers, routes, credentials, model prompts, or provider error messages.
4. Direct admission currently requires product, harness, process, and artifact success 5/5. Replace it with a measurement-completeness gate: fresh preflight `ready=5`; exactly five fresh attempts; `unresolved=0`, `running=0`, `interrupted=0`; every slot has controller/product/harness/process/web-validation terminal evidence; and no exhausted browser/CDP infrastructure block. Product failure, provider rejection, generated-missing after caller failure, and timeout remain measured outcomes and do not trigger another implementation cycle.
### SDD and Contract Criteria
- D06/D10 keep repetitions=1 for a scored C01-C09 run and preserve all failures. This packet is unscored diagnosis/deployment qualification, not a scored run.
- S13 requires official agy Gemini request/tool/SSE compatibility through IOP. The ID-preserving round trip and live direct observation are its acceptance evidence.
- OpenAI/Gemini/Anthropic public error bodies remain sanitized. Added logs are classification-only operational evidence.
- A direct product failure after complete terminal evidence is a benchmark result. Only an Edge pre-ingress incompatibility or exhausted browser/CDP infrastructure block prevents packet acceptance.
### Verification Context
- Current feature baseline is pushed at `634531af`; working tree was clean before this pair was created.
- The managed benchmark wrapper `/tmp/iop-bench-13-env` is invoked via `/bin/bash`, reads the rotated token without sourcing it, and must never be printed.
- The prior deployed runtime is `dev-936` and predates this packet. Validation against it is not acceptance evidence.
- External runner is `toki@toki-labs.com:/Users/toki/agent-work/iop-dev`. Follow the complete `dev-runtime-deploy` clean-sync, sequential-test, four-binary rebuild, restart, health, capacity-smoke, release-finish, and atomic-push contract.
- Before merging, push this packet's feature commit normally. On the runner fetch both refs, require clean `dev`, fast-forward/merge the exact feature tip into `dev` without force, push `dev`, then start the release from the resulting clean `origin/dev`. Existing tags/other release branches/divergence are hard blockers; do not reset or delete them.
- Confidence is high for the Gemini ID/tool-result and admission defects, and high that bounded CDP restart removes the observed one-process transient without masking product failure. Claude→GPT provider acceptance remains a live compatibility outcome; safe status-origin evidence, not speculative request mutation, closes that uncertainty.
### Test Coverage Gaps
- Unit fixtures cannot establish live provider acceptance, process identity, remote binary identity, or CDP stability under the benchmark workload.
- One fresh unscored five-cell diagnostic run after deployment is required. It is not required to be product 5/5; it is required to be evidence-complete and free of a known infrastructure-only block.
### Symbol References
- `geminiFunctionCall`, `geminiFunctionResponse`, `geminiContentToChat`, `geminiBridgeToolState`, `geminiBridgeStream.emitTools`
- `Server.handleGeminiStreamGenerateContent`, `Server.writeAnthropicChatBridgeResponse`
- `BrowserRenderer.render`
### Split Judgment
This packet owns runtime/harness compatibility, policy, deployment, and one unscored direct qualification. The dependent `15+14_scored_benchmark_and_report` owns the only new scored C01-C09 identity, blind scoring, and dated report. Predecessor 13 is satisfied by the exact archive path above; task 15 must wait for this packet's archived `complete.log`.
### Scope Rationale
- Include only the Gemini bridge, sanitized rejection observations, CDP renderer retry, matching tests, direct-admission documents, deployment, and one fresh direct diagnostic.
- Exclude caller adapters, manifests, matrix order, evaluator, score/report implementation, credentials, normal user configs, old run data, and any change intended merely to force every model to succeed.
- Do not implement a new Anthropic→OpenAI Responses bridge in this packet. The observed Claude→GPT 400 is provider-origin until safe evidence proves an Edge ingress defect; speculative protocol replacement would exceed the selected root cause.
### Final Routing
- `evaluation_mode=first-pass`; finalizer `pair` route already selected build `cloud/G09` and review `cloud/G09` with `plan=0`.
- Loop risks are live external state, protocol boundary, deployment blast radius, and variant products. Official review must independently rerun deterministic tests and inspect sanitized live evidence.
## Implementation Checklist
- [ ] [API-1] Preserve and validate Gemini function-call IDs through request and streaming response conversion, remove the non-standard tool-result field, and add same-name/out-of-order/duplicate/missing-ID regression tests.
- [ ] [API-2] Add classification-only Gemini and Anthropic Chat rejection observations with tests proving no request, response, credential, route, prompt, or provider-message content is logged.
- [ ] [API-3] Add at most three total fresh Chromium/CDP attempts for closed transient infrastructure errors, with per-attempt cleanup and tests for retry, exhaustion, non-transient no-retry, screenshot cleanup, and process reaping.
- [ ] [API-4] Update benchmark skill/spec/SDD/Milestone/dev guide so direct qualification gates on readiness and terminal evidence rather than all-success, while exhausted infrastructure blocks and scored-run uniqueness remain fail-closed.
- [ ] [API-5] Run fresh local tests, commit/push the feature branch, merge its exact tip into clean `dev`, execute the full dev-runtime release/deploy procedure, and prove all Edge/Node binaries and health observations use the same released source.
- [ ] [API-6] Through `/bin/bash /tmp/iop-bench-13-env`, run one fresh direct preflight and one fresh unscored direct run; accept product failures/timeouts as results but require five terminal slots, `unresolved=0`, and no infrastructure-only browser/CDP block.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
### [API-1] Gemini tool-call identity
#### Modified Files and Checklist
- [ ] `apps/edge/internal/openai/gemini_types.go`: add optional native IDs to function call/response types.
- [ ] `apps/edge/internal/openai/gemini_handler.go`: validate IDs, preserve explicit IDs, exact-match responses, and emit standard Chat tool messages.
- [ ] `apps/edge/internal/openai/gemini_bridge.go`: retain streamed provider ID and return it in Gemini `functionCall`.
- [ ] `apps/edge/internal/openai/gemini_handler_test.go`: cover explicit-ID round trip, ID-less fallback, duplicate/mismatch rejection, and streamed ID projection.
#### Verification
```bash
go test -count=1 ./apps/edge/internal/openai -run 'Gemini'
```
Expected: every Gemini bridge test passes; explicit IDs survive byte-level JSON projection and ID-less legacy input remains deterministic.
### [API-2] Sanitized rejection origin
#### Modified Files and Checklist
- [ ] `apps/edge/internal/openai/gemini_handler.go`: classify pre-ingress versus provider HTTP rejection without raw payloads.
- [ ] `apps/edge/internal/openai/gemini_handler_test.go`: assert closed fields and absence of secret/body/message markers.
- [ ] `apps/edge/internal/openai/anthropic_stream.go`: observe Chat bridge provider status before sanitized caller projection.
- [ ] `apps/edge/internal/openai/anthropic_bridge_test.go`: assert provider-origin status evidence contains no raw provider body.
#### Verification
```bash
go test -count=1 ./apps/edge/internal/openai -run 'Gemini|Anthropic.*Bridge|Rejection'
```
Expected: logs distinguish ingress/provider classes with status only and contain none of the fixture secrets or messages.
### [API-3] Bounded CDP recovery
#### Modified Files and Checklist
- [ ] `scripts/agent_benchmark/browser_cdp.py`: isolate one render attempt and retry a closed transient set for at most three total fresh processes/profiles.
- [ ] `scripts/agent_benchmark/browser_cdp_test.py`: prove retry bounds, cleanup, and non-transient behavior.
#### Verification
```bash
python3 -m unittest scripts.agent_benchmark.browser_cdp_test
python3 -m unittest scripts.agent_benchmark.web_validation_test
```
Expected: transient first/second failure can recover; third fails closed; non-transient errors run once; no orphan process or partial screenshot remains.
### [API-4] Admission contract
#### Modified Files and Checklist
- [ ] `agent-ops/skills/project/iop-agent-comparison-benchmark/SKILL.md`: replace direct all-success qualification with terminal-evidence admission and retain scored execution prohibitions.
- [ ] `agent-spec/testing/agent-comparison-benchmark.md`: align current implemented lifecycle/admission semantics.
- [ ] `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-one-shot-agent-model-comparison/SDD.md`: record continuing approval, unscored diagnostic boundary, and one scored-run identity.
- [ ] `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/iop-one-shot-agent-model-comparison.md`: replace the obsolete blocker narrative without marking comparison tasks complete.
- [ ] `docs/agent-comparison-benchmark-dev-guide.md`: document exact direct admission, failure-as-result, and infrastructure blocker rules.
- [ ] `scripts/agent_benchmark/skill_contract_test.py`: lock public wording and stop/retry boundaries.
#### Verification
```bash
python3 -m unittest scripts.agent_benchmark.skill_contract_test
python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json
```
Expected: policy sources agree; direct product failure is not an endless fix gate; incomplete evidence and implicit retry remain prohibited.
### [API-5] Source publication and dev deployment
#### Modified Files and Checklist
- [ ] `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/CODE_REVIEW-cloud-G09.md`: record exact feature/dev/release refs, tests, four artifact hashes, process/listener/health/capacity results, and any stop condition without secrets.
#### Verification
```bash
python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
while IFS= read -r package; do go test -count=1 "$package"; done < <(go list ./apps/control-plane/... ./apps/edge/... ./apps/node/... ./cmd/... ./packages/go/... ./scripts/... | sed '/^iop\/packages\/go\/agenttask$/d')
git diff --check -- . ':(exclude)agent-task/archive/**'
```
Expected: all tests pass from clean published source; release procedure reports a single source identity for Edge/mac/Linux/Windows binaries and 4/4 connected Nodes with healthy providers.
### [API-6] Fresh direct diagnostic
#### Modified Files and Checklist
- [ ] `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/CODE_REVIEW-cloud-G09.md`: record command counts, fresh IDs, terminal summaries, safe rejection origin, CDP outcome, and immutable-old-run audit.
#### Verification
```bash
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py preflight --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py run --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json
```
Expected: `ready=5`; one distinct run with five attempts and web validations; `unresolved=0`, `running=0`, `interrupted=0`; no exhausted `cdp_*`/`browser_*` infrastructure block. Product/process/artifact failures remain visible and are not retried.
## Modified Files Summary
| File | Items |
|---|---|
| `apps/edge/internal/openai/gemini_types.go` | API-1 |
| `apps/edge/internal/openai/gemini_handler.go` | API-1, API-2 |
| `apps/edge/internal/openai/gemini_bridge.go` | API-1 |
| `apps/edge/internal/openai/gemini_handler_test.go` | API-1, API-2 |
| `apps/edge/internal/openai/anthropic_stream.go` | API-2 |
| `apps/edge/internal/openai/anthropic_bridge_test.go` | API-2 |
| `scripts/agent_benchmark/browser_cdp.py` | API-3 |
| `scripts/agent_benchmark/browser_cdp_test.py` | API-3 |
| `agent-ops/skills/project/iop-agent-comparison-benchmark/SKILL.md` | API-4 |
| `agent-spec/testing/agent-comparison-benchmark.md` | API-4 |
| `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-one-shot-agent-model-comparison/SDD.md` | API-4 |
| `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/iop-one-shot-agent-model-comparison.md` | API-4 |
| `docs/agent-comparison-benchmark-dev-guide.md` | API-4 |
| `scripts/agent_benchmark/skill_contract_test.py` | API-4 |
| `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/CODE_REVIEW-cloud-G09.md` | API-5, API-6 |
## Dependencies and Execution Order
- Predecessor 13 is satisfied by `agent-task/archive/2026/08/m-iop-one-shot-agent-model-comparison/13+12_scored_benchmark_result/complete.log`.
- Execute API-1 and API-2, then API-3, API-4, API-5, and API-6. Do not run the live diagnostic against an unreleased or stale binary.
- Packet `15+14_scored_benchmark_and_report` must remain dependency-waiting until this packet has an archived PASS `complete.log`.
## Final Verification
```bash
go test -count=1 ./apps/edge/internal/openai
python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json
python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
git diff --check -- . ':(exclude)agent-task/archive/**'
git status --short --branch
```
Expected: deterministic tests and manifests pass, implementation files are committed/pushed and deployed from one released source, the only uncommitted task mutation is implementation evidence/finalization state, and the fresh direct diagnostic satisfies terminal-evidence admission without hiding product failures. After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.

View file

@ -0,0 +1,260 @@
<!-- task=m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission plan=1 tag=REVIEW_API milestone-task=agy-iop-compatibility,route-readiness,objective-validation -->
# Rejection provenance and terminal-qualified dev release follow-up
## For the Implementing Agent
Implement the selected fixes exactly, run every listed verification, and fill the implementation-owned sections of `CODE_REVIEW-cloud-G10.md` with actual stdout/stderr and live evidence. Keep the active pair in place and report ready for review; final verdict, archive names, `complete.log`, and task moves are review-agent-only. If blocked, record only the exact blocker, attempted commands/output, and resume condition. Do not ask the user, invoke user-input tools, create stop files, classify the next state, mutate old benchmark runs, or invoke a live caller/provider outside `scripts/agent_comparison_benchmark.py`.
## Background
The first review found that Gemini rejection origin is inferred from the final internal Chat status rather than actual provider provenance: Edge-local dispatch failures become `provider_http`, while authentication failures are not observed. It also found that the release blocker has disappeared from authoritative remote refs, but the deployment skill retains stale-ref ambiguity, an invalid project-skill frontmatter key, and a nonexistent Go package root. This follow-up fixes those closed defects, releases one exact corrected source, and runs the still-required fresh five-cell direct qualification.
## Archive Evidence Snapshot
- `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/plan_cloud_G09_0.log` and `code_review_cloud_G09_0.log` preserve the first loop. Verdict: `FAIL`; Required R1/R2; Suggested/Nit: none.
- R1 evidence: an Edge-local normalized `SubmitRun` failure is logged as `provider_http`; Gemini auth rejection emits no `pre_ingress` observation. Direct-fix targets are the Gemini/auth bridge, tunnel release provenance hook, and regression tests.
- R2 evidence: API-5/API-6 were not run. Authoritative origin has no release branch, while the runner has stale remote-tracking release refs. The deploy skill contains unsupported `version` frontmatter and its sequential Go command fails on nonexistent `./cmd/...`.
- Published feature evidence starts at `28ed27a575f6e3ba473a8c76c78a9e82027525f2`; the implementing agent must publish a new corrected exact tip before merging it into `dev`.
- The retained run `agent-test/runs/bench-01-direct-preflight/run-20260812T222805Z-bec48f5fffaa/` remains immutable: 5,489 files and aggregate digest `d089cd4b3e9bfd4e8ebe3bfa82032a544f0625e0793ad9763b728addf62baffd`.
## Finding Resolution Map
| Finding | Reviewer evidence and root cause | Selected fix | Mode | Changed precondition | Acceptance commands |
|---|---|---|---|---|---|
| R1 | `gemini_handler.go:87-90` labels every internal Chat `>=400` as provider HTTP; `routes.go:35-45,58-72` bypasses Gemini pre-ingress observation. Final bridge status has no actual provider `RESPONSE_START` provenance. | Route Gemini auth/managed-credential failures through the pre-ingress writer; remove blanket status inference; pass a once-only callback into the Gemini writer; notify it only where the raw tunnel sink writes an actual provider error status; add owner-negative and safe-field tests. | `direct-fix` | Actual provider status and Edge-local terminal status become distinguishable at the response-writer boundary. | `go test -count=1 ./apps/edge/internal/openai -run 'Gemini.*Rejection|GeminiIngressRejectsAuthentication'`; `go test -count=1 ./apps/edge/internal/openai` |
| R2 | API-5/API-6 have zero live commands. `git ls-remote` shows no remote release head, but the runner retains stale tracking refs. `dev-runtime-deploy/SKILL.md:3,70,95` has invalid frontmatter, no initial prune/authoritative-head rule, and nonexistent `./cmd/...`. | Correct the project skill and deterministic contract tests; publish/merge the corrected feature tip; perform the complete release/deploy/capacity procedure; then run one CLI-owned fresh five-cell direct preflight/run and preserve all terminal outcomes. | `direct-fix` | Stale tracking refs are pruned and cannot masquerade as remote heads; the mandated sequential test command becomes runnable; the deployed runtime contains R1. | `python3 -m unittest scripts.agent_benchmark.skill_contract_test`; both skill validators; corrected sequential Go tests; complete dev-runtime release evidence; fresh direct `preflight` and `run` via `/bin/bash /tmp/iop-bench-13-env` |
## Analysis
### Files Read
- `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/plan_cloud_G09_0.log`
- `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/code_review_cloud_G09_0.log`
- `apps/edge/internal/openai/gemini_handler.go`
- `apps/edge/internal/openai/gemini_bridge.go`
- `apps/edge/internal/openai/gemini_handler_test.go`
- `apps/edge/internal/openai/routes.go`
- `apps/edge/internal/openai/stream_gate_release_sink.go`
- `apps/edge/internal/openai/stream_gate_tunnel_codec.go`
- `apps/edge/internal/openai/stream_gate_pipeline_test.go`
- `apps/edge/internal/openai/server_test_support_test.go`
- `apps/edge/internal/openai/identity_metering_test.go`
- `apps/edge/internal/openai/anthropic_stream.go`
- `agent-ops/skills/project/dev-runtime-deploy/SKILL.md`
- `agent-ops/skills/project/iop-agent-comparison-benchmark/SKILL.md`
- `scripts/agent_benchmark/skill_contract_test.py`
- `agent-spec/testing/agent-comparison-benchmark.md`
- `agent-contract/outer/gemini-compatible-api.md`
- `agent-contract/outer/openai-compatible-api.md`
- `agent-contract/outer/anthropic-compatible-api.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/iop-one-shot-agent-model-comparison.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-one-shot-agent-model-comparison/SDD.md`
- `agent-test/dev/rules.md`
- `agent-test/dev/edge-smoke.md`
- `agent-test/dev/testing-smoke.md`
### SDD Criteria
- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-one-shot-agent-model-comparison/SDD.md`; status `[승인됨]`; lock `해제`.
- Preserved `milestone-task`: `agy-iop-compatibility,route-readiness,objective-validation`.
- S02 requires a fresh `ready=5`, five terminal direct slots without infrastructure exhaustion, then route/auth/effort/terminal evidence. S09 requires uniform web-validation evidence. S13 requires official agy Gemini auth, request/tool/SSE, and live lifecycle evidence.
- Evidence Map rows S02, S09, and S13 therefore drive the provenance regression tests, released-source identity checks, five-slot direct run, web-validation terminal checks, and retained-run immutability audit below.
### Verification Context
- Handoff: the first review log supplies actual focused/broad outputs, R1/R2 diagnosis, selected fixes, and exclusions. Current source and remote refs were revalidated without changing external state.
- Local evidence: focused Edge tests pass; Python benchmark suite passes 458 tests; the corrected package list passes sequentially after one independently reproducible workspace-test flake passed three focused reruns and a complete rerun.
- Current authoritative refs at planning: `origin/dev=841511472a62ec20d79eac5f800180d1de34b541`, `origin/main=bc5b326140c809d6d3685ca35d7311e31a180d7c`, feature=`28ed27a575f6e3ba473a8c76c78a9e82027525f2`; no authoritative remote `release/*` head; tag `dev-936` peels to `origin/main`.
- External Verification Preflight: runner `toki@toki-labs.com`, repo `/Users/toki/agent-work/iop-dev`, Darwin arm64, clean local `dev` at `fd32abb...` and behind current origin/dev by three. Stale tracking refs are `origin/release/dev-781` and `origin/release/dev-936`; use `git fetch --prune` before judging release state. Git-flow is AVH 1.12.3 with `main`/`dev`/`release/`; Go is `/opt/homebrew/bin/go`, version 1.26.3. All four binary paths and `build/dev-runtime/edge.yaml` exist; the prior Edge process listens on 18082/18083/18084/19093 but is stale and is not acceptance evidence.
- Setup: after publishing the corrected feature tip, prune and clean-sync the runner, prove authoritative remote release heads are empty, merge the exact tip into `dev`, push it, recompute `dev-<count>` from the post-merge dev commit, and then follow the release procedure. Do not move/delete `dev-936`.
- Constraint: `/tmp/iop-bench-13-env` is invoked only through `/bin/bash`; its token material is never printed. Old runs are not resumed, retried, edited, or selected as the new result.
- Gaps: only released-runtime identity, provider capacity, and fresh caller terminal evidence remain external. The declared runner and direct node routes are authorized, so no user-review gate applies.
- Confidence: high. Focused reproducers prove R1, `ls-remote` proves the stale-ref condition, and the package/validator failures are deterministic.
### Test Coverage Gaps
- Gemini malformed-body and real provider-400 logging are covered; auth/managed pre-ingress and Edge-local error non-provider provenance are missing and must be added.
- The deploy skill has no assertion covering frontmatter validity, initial prune/authoritative release head, or package roots; extend the existing project contract test.
- Unit tests cannot prove deployed binary identity, four-node connectivity/capacity, official caller behavior, or CDP stability; the release and one fresh direct diagnostic remain required.
### Symbol References
- `geminiBridgeResponseWriter.Status`: only `handleGeminiStreamGenerateContent` uses it; remove both the method and its caller-side inference.
- `newGeminiBridgeResponseWriter`: only `handleGeminiStreamGenerateContent` calls it; update that call with the provider-rejection callback.
- New private provider-status observer is implemented by `geminiBridgeResponseWriter` and consumed only by `openAITunnelReleaseSink`; no public API changes.
### Split Judgment
Keep one plan. R1 must be in the exact feature source merged and released by R2, and S02/S13 acceptance requires the live diagnostic against that released identity. Splitting would allow no useful intermediate PASS and would sever the source→release→evidence invariant. Predecessor 13 remains satisfied by the exact archive evidence already recorded in the prior loop.
### Scope Rationale
- Include only Gemini rejection provenance, the tunnel observer seam, related tests, the project deploy skill/contract test, source publication, dev release, and one fresh unscored direct qualification.
- Exclude Gemini tool-call ID logic, Anthropic observation logic, browser retry implementation, benchmark scoring/reporting, caller adapters, manifests, credentials, common Agent-Ops files, old run contents, and any change intended only to force product success.
- Do not change the endpoint error bodies or add provider/request/model identifiers to logs.
### Final Routing
- `evaluation_mode=isolated-reassessment`; finalizer=`finalize-task-policy.sh`, mode=`pair`; status=`routed`.
- Build closures: scope/context/verification/evidence/ownership/decision all `true`; capability gap none; scores `2/2/2/2/2` => G10; base/route basis=`grade-boundary`; lane=`cloud`; catalog=`worker/cloud/G10`; filename=`PLAN-cloud-G10.md`.
- Review closures: all `true`; scores `2/2/2/2/2` => G10; basis=`official-review`; lane=`cloud`; catalog=`review/cloud/G10`; filename=`CODE_REVIEW-cloud-G10.md`.
- `large_indivisible_context=false`; positive loop risks=`temporal_state,boundary_contract,structured_interpretation,variant_product` (4); risk boundary matched but does not replace grade basis.
- Recovery signals: `review_rework_count=1`, `evidence_integrity_failure=false`; recovery boundary not matched.
## Implementation Checklist
- [ ] [REVIEW_API-1] Correct Gemini pre-ingress/provider-HTTP provenance and add regression tests for auth, managed credential rejection, actual tunnel error, Edge-local failure, exact fields, and secret absence.
- [ ] [REVIEW_API-2] Repair and validate the dev-runtime deployment skill, publish the corrected feature tip, merge that exact tip into clean `dev`, and complete the full release/deploy/identity/connectivity/capacity procedure.
- [ ] [REVIEW_API-3] Through `/bin/bash /tmp/iop-bench-13-env`, run one fresh direct preflight and one fresh unscored five-cell run, record terminal-evidence admission, and prove the retained run is unchanged.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
### [REVIEW_API-1] Bind rejection class to actual owner
#### Problem
`apps/edge/internal/openai/gemini_handler.go:86-90` currently does this after all internal Chat paths:
```go
bridge := newGeminiBridgeResponseWriter(w, callerModel)
s.handleChatCompletions(bridge, internal)
if bridge.Status() >= http.StatusBadRequest {
s.observeGeminiRejection(geminiRejectionProviderHTTP, bridge.Status())
}
```
This cannot distinguish raw provider `RESPONSE_START` from Edge admission/dispatch/runtime errors. `apps/edge/internal/openai/routes.go:58-72` also writes Gemini auth-layer errors without calling `writeGeminiPreIngressError`.
#### Solution
Replace status inference with an explicit, private provider-status signal:
```go
bridge := newGeminiBridgeResponseWriter(w, callerModel, func(status int) {
s.observeGeminiRejection(geminiRejectionProviderHTTP, status)
})
s.handleChatCompletions(bridge, internal)
bridge.Finish()
```
Add a once-only method on `geminiBridgeResponseWriter` that invokes the callback only for `status >= 400`. In `openAITunnelReleaseSink.CommitResponseStart` and its staged provider error-response write path, notify a private response-writer observer immediately before writing an actual provider status. Never notify from normalized sinks, recovery/admission errors, compatibility errors, or generic terminal writers. Route Gemini branches in `writeAuthenticationFailure` and `writeCallerProviderCredentialRejection` through `writeGeminiPreIngressError`.
#### Modified Files and Checklist
- [ ] `apps/edge/internal/openai/routes.go`: observe Gemini auth and managed caller-credential rejection as `pre_ingress`.
- [ ] `apps/edge/internal/openai/gemini_handler.go`: remove final-status inference and wire the bounded provider callback.
- [ ] `apps/edge/internal/openai/gemini_bridge.go`: remove `Status`, add once-only actual-provider status observation.
- [ ] `apps/edge/internal/openai/stream_gate_release_sink.go`: notify only at actual raw provider status commit points.
- [ ] `apps/edge/internal/openai/gemini_handler_test.go`: add all positive/negative provenance and log-safety assertions.
#### Test Strategy
Write regression tests in `gemini_handler_test.go`. Assert missing/invalid auth and managed caller credential are `pre_ingress`; a provider-tunnel 400 is exactly one `provider_http`; normalized `SubmitRun` failure does not produce `provider_http`; every observation has exactly four safe fields and omits fixture secrets.
#### Verification
```bash
go test -count=1 ./apps/edge/internal/openai -run 'Gemini.*Rejection|GeminiIngressRejectsAuthentication'
go test -count=1 ./apps/edge/internal/openai
```
Expected: all tests pass; only actual provider HTTP status creates `provider_http`.
### [REVIEW_API-2] Repair the deploy contract and release one exact source
#### Problem
`agent-ops/skills/project/dev-runtime-deploy/SKILL.md:3` uses an unsupported `version` frontmatter key, line 70 initially fetches without prune/authoritative-head distinction, and lines 92-97 include nonexistent `./cmd/...`. API-5 has no merge, release, binary, process, node, or capacity evidence.
#### Solution
Remove the `version` key. Require `git fetch --prune origin dev main <feature> --tags`, use `git ls-remote --heads origin 'refs/heads/release/*'` as the authoritative release-head check, and describe stale local tracking refs as cleanup evidence rather than remote blockers. Remove `./cmd/...` from the pre/post-build sequential package list. Add exact assertions in the existing benchmark skill contract test, then validate both project skills.
Commit only the selected source/skill/test changes plus implementation evidence as appropriate, push the exact feature tip normally, and record its SHA. On the runner, prune, require clean state and no authoritative release head, clean-sync local `main`/`dev`, merge the exact recorded feature SHA into `dev` without force, push `dev`, recompute its commit count, and execute all dev-runtime-deploy steps. Record pre/post sequential tests, four `-trimpath` builds with SHA-256 and `go version -m` source, config/refresh checks, process/listener identity, four connected Nodes, provider snapshots, both endpoint capacity+1 saturation/queue/recovery, finish preflight, tag tree, atomic push, and cleanup.
#### Modified Files and Checklist
- [ ] `agent-ops/skills/project/dev-runtime-deploy/SKILL.md`: fix frontmatter, authoritative prune/ref rules, and package roots.
- [ ] `scripts/agent_benchmark/skill_contract_test.py`: lock the corrected skill contract.
- [ ] `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/CODE_REVIEW-cloud-G10.md`: record exact publication, runner, release, artifact, runtime, and capacity output.
#### Test Strategy
Extend `BenchmarkSkillContractTest` with deterministic tracked-text assertions; no new test file. Re-run the corrected sequential command locally and on the release source. Live deployment evidence is mandatory and cannot be replaced by unit tests.
#### Verification
```bash
python3 -m unittest scripts.agent_benchmark.skill_contract_test
python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/dev-runtime-deploy
python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/iop-agent-comparison-benchmark
while IFS= read -r package; do go test -count=1 "$package" || exit; done < <(go list ./apps/control-plane/... ./apps/edge/... ./apps/node/... ./packages/go/... ./scripts/... | sed '/^iop\/packages\/go\/agenttask$/d')
git ls-remote --heads origin 'refs/heads/release/*'
```
Expected: validators and tests pass; authoritative release-head output is empty before the new release; the full `dev-runtime-deploy` procedure finishes with one source identity and healthy capacity evidence.
### [REVIEW_API-3] Run the released five-cell terminal qualification
#### Problem
API-6 command counts are zero. SDD S02/S09/S13 still lack fresh released-runtime evidence; the retained old run is evidence-complete but cannot qualify this corrected source.
#### Solution
After REVIEW_API-2 finishes, invoke exactly one fresh direct preflight and one fresh direct run through the benchmark CLI and managed wrapper. Require `ready=5`, exactly five attempts, `unresolved=0`, `running=0`, `interrupted=0`, controller/product/harness/process/web-validation terminals for every slot, and no exhausted browser/CDP infrastructure block. Preserve product failure/provider rejection/generated-missing/timeout as outcomes and do not retry. Recompute the retained-run file count/digest afterward.
#### Modified Files and Checklist
- [ ] `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/CODE_REVIEW-cloud-G10.md`: record command counts, fresh IDs, five terminal summaries, safe rejection origin, CDP result, and retained-run audit.
#### Test Strategy
No harness source change is planned. The required test is the one fresh CLI-owned live diagnostic against the released source; an outcome may fail, but evidence completeness and infrastructure admission must pass.
#### Verification
```bash
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py preflight --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py run --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json
```
Expected: fresh `ready=5`; one new run with five terminal slots and no exhausted browser/CDP block; product failures remain visible and un-retried.
## Modified Files Summary
| File | Items |
|---|---|
| `apps/edge/internal/openai/routes.go` | REVIEW_API-1 |
| `apps/edge/internal/openai/gemini_handler.go` | REVIEW_API-1 |
| `apps/edge/internal/openai/gemini_bridge.go` | REVIEW_API-1 |
| `apps/edge/internal/openai/stream_gate_release_sink.go` | REVIEW_API-1 |
| `apps/edge/internal/openai/gemini_handler_test.go` | REVIEW_API-1 |
| `agent-ops/skills/project/dev-runtime-deploy/SKILL.md` | REVIEW_API-2 |
| `scripts/agent_benchmark/skill_contract_test.py` | REVIEW_API-2 |
| `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/CODE_REVIEW-cloud-G10.md` | REVIEW_API-2, REVIEW_API-3 |
## Dependencies and Execution Order
1. Implement and verify REVIEW_API-1.
2. Repair/validate the deploy skill, commit and publish the resulting exact feature tip, then complete REVIEW_API-2 against that tip.
3. Run REVIEW_API-3 only after the released runtime identity is proven. Do not run against the stale process.
## Final Verification
```bash
go test -count=1 ./apps/edge/internal/openai
python3 -m unittest scripts.agent_benchmark.skill_contract_test
python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/dev-runtime-deploy
python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/iop-agent-comparison-benchmark
python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json
python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
while IFS= read -r package; do go test -count=1 "$package" || exit; done < <(go list ./apps/control-plane/... ./apps/edge/... ./apps/node/... ./packages/go/... ./scripts/... | sed '/^iop\/packages\/go\/agenttask$/d')
git diff --check -- . ':(exclude)agent-task/archive/**'
git status --short --branch
```
Then execute every step and checklist in `agent-ops/skills/project/dev-runtime-deploy/SKILL.md`, followed by the two REVIEW_API-3 commands. Cached test output is not accepted; use `-count=1`. Expected: all deterministic checks pass, feature/dev/release/artifact/runtime identities agree, all required deploy/capacity gates pass before finish, the direct run is terminal-evidence complete, and the retained run digest is unchanged.
After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.

View file

@ -0,0 +1,54 @@
# User Review Required - m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission
## Requested At
2026-08-13
## Status
RESOLVED_FOR_REPLAN
## Reason
- Type: external-execution
- Target: authorized benchmark runner `/config/workspace/iop-s0` with `/bin/bash /tmp/iop-bench-13-env`, Chromium/CDP, and the dev IOP endpoint bound by `scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json`
- Current review number: 2
- Final verdict: FAIL
- Summary: The only permitted fresh five-cell qualification exhausted all three Chromium/CDP attempts for `codex-gpt-direct`. The immutable run cannot be repaired or implicitly retried, and allocating one new unscored qualification identity requires explicit user authorization.
## Loop History
| Plan | Review | Verdict | Note |
|------|--------|---------|------|
| `plan_cloud_G09_0.log` | `code_review_cloud_G09_0.log` | FAIL | Gemini rejection provenance was lossy, and release/deployment plus fresh qualification had not run. |
| `plan_cloud_G10_1.log` | `code_review_cloud_G10_1.log` | FAIL | Provenance, deployment contract, deterministic tests, and dev-971 release evidence pass, but the fresh qualification has `artifact_blocked=1` from exhausted `cdp_socket_closed`. |
## Blocking Evidence
- Problem: SDD D13/S02 and `REVIEW_API-3` require no exhausted browser/CDP infrastructure block, but `run-20260813T020758Z-2920dc067c4e` contains one immutable blocked web-validation record.
- Current archived plan: `plan_cloud_G10_1.log`
- Current archived review: `code_review_cloud_G10_1.log`
- Verification command: `python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json --run-id run-20260813T020758Z-2920dc067c4e`
- Actual output: `unresolved=0`, `running=0`, `interrupted=0`, `artifact_blocked=1`; `codex-gpt-direct/.../web-validation.json` has `status=blocked`, `reason=cdp_socket_closed`, no browser observation, no screenshots, and no viewports.
- Blocking rationale: A reviewer-only render of the same immutable workspace to a separate `/tmp` output root now succeeds with Chrome 151 and both viewports, so no deterministic repository defect was reproduced. The old run remains immutable, the benchmark contract prohibits an implicit retry, and the skill requires explicit authorization before a new execution identity is allocated.
## Required User Action
- [x] Explicitly authorize exactly one new unscored five-cell direct qualification cycle: one fresh CLI `preflight` and one fresh CLI `run`, without `resume`, `--retry-failed`, old-run mutation, or ad-hoc caller/provider invocation.
## Resolution
- 2026-08-13: 사용자가 작업 종료까지 필요한 승인을 부여했고 이후에도 계속 작업하도록 지시했다. 이 승인은 새 unscored direct qualification identity 한 건을 허용하지만, 검증 완료를 뜻하지 않으므로 새 PLAN/CODE_REVIEW pair로 재개한다.
## Resume Condition
- Authorization is recorded, the declared runner first passes a bounded Chromium/CDP render preflight, and one new run identity is produced through `/bin/bash /tmp/iop-bench-13-env` with `ready=5`, exactly five new attempts, `unresolved=0`, `running=0`, `interrupted=0`, five terminal web-validation records, and `artifact_blocked=0`; the retained run must remain 5,489 files with digest `d089cd4b3e9bfd4e8ebe3bfa82032a544f0625e0793ad9763b728addf62baffd`.
## Next Execution Hint
- Resolve this review stop for `agent-task/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/USER_REVIEW.md`. After explicit authorization, route through the benchmark CLI only: `/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py preflight --manifest scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json`, then exactly one matching `run` command.
## Closure Rules
- If the recorded user action and evidence resolve this stop as complete/PASS, update `USER_REVIEW.md` to the resolved state, write `complete.log` from `agent-ops/skills/common/code-review/templates/complete-log-template.md`, and move the task directory to the archive.
- If new implementation is required, the `plan` skill archives `USER_REVIEW.md` as `user_review_N.log` before writing a new `PLAN-*-G??.md` / `CODE_REVIEW-*-G??.md` pair.

View file

@ -0,0 +1,112 @@
<!-- task=m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report plan=0 tag=TEST milestone-task=claude-standalone,gemini-standalone,gpt-standalone,gemini-hybrid,gpt-hybrid,objective-validation,quality-scoring,performance-usage,benchmark-report -->
# Code Review Reference - TEST
> **[IMPLEMENTING AGENT — READ FIRST]** Execute the deterministic plan exactly and fill every implementation-owned section. Do not change source/manifests, run callers outside the CLI, resume/retry caller attempts, allocate a second scored run, ask the user, create stop files, archive artifacts, or write `complete.log`. Final verdict/finalization is review-agent-only.
## Overview
date=2026-08-13
task=m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report, plan=0, tag=TEST
## Archive Evidence Snapshot
- Dependency-wait for `agent-task/archive/2026/08/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/complete.log`.
- Preserve all existing direct and C01-C09 run roots. This packet owns one distinct new C01-C09 run only.
- Scoring retry is evaluator-only, explicit, append-only, and limited to three total score invocations.
## For the Review Agent
Reconstruct command counts and identities from durable state. Verify one scored run, nine terminal slots, no caller retry, eligible-only blind scoring, deterministic reports, and isolation. Failed/timed-out cells are valid outcomes. On PASS append verdict, archive pair, write `complete.log` with milestone metadata, and move the task directory. On WARN/FAIL follow the code-review skill with a reviewer-selected fix.
## Implementation Item Completion
| Item | Status |
|---|---|
| TEST-1 Final admission | [ ] |
| TEST-2 Single execution | [ ] |
| TEST-3 Status/scoring | [ ] |
| TEST-4 Reports/audit | [ ] |
| TEST-5 Final integrity | [ ] |
## Implementation Checklist
- [ ] [TEST-1] Verify predecessor 14 PASS, exact released runtime/source identity, immutable manifests, protected-file modes, caller versions, 4/4 Nodes, provider health, and `ready=9` without exposing secrets.
- [ ] [TEST-2] Invoke exactly one fresh C01-C09 `run`, capture its issued ID, and require nine complete terminal slots with `unresolved=0`; preserve every product/harness/process/artifact outcome without resume or caller retry.
- [ ] [TEST-3] Query status for that exact run and score eligible artifacts blindly; use `--retry-scoring-failed` only when required and at most three total score invocations, preserving every score attempt.
- [ ] [TEST-4] Generate the deterministic run-owned report, audit identities/counts/timing/usage/links/isolation, and publish `agent-test/dev/iop-one-shot-agent-comparison-2026-08-13.md` with prior-run relationship, failures, limitations, and raw evidence pointers.
- [ ] [TEST-5] Rerun deterministic verification and prove old runs, testbed, normal caller subscriptions, credentials, manifests, and released runtime were not mutated by benchmark execution.
- [ ] 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 section.
- [ ] Append verdict and verified routing signals.
- [ ] Verify every dimension and finding classification.
- [ ] Rerun deterministic commands and reconstruct dynamic command/run/score/report counts.
- [ ] Record evidence, exact root cause, selected fix, files/tests, and acceptance commands for every Required/Suggested finding before follow-up.
- [ ] Archive plan/review to correctly numbered logs and verify `.gitignore` managed block.
- [ ] On PASS write `complete.log`, preserve/report milestone metadata, move the task directory, and leave no active task files.
- [ ] On WARN/FAIL write the required next filesystem state and do not write `complete.log`.
## Deviations from Plan
_Implementing agent: replace with actual deviations or `None`._
## Key Design Decisions
_Implementing agent: record actual execution decisions within the fixed policy._
## Reviewer Checkpoints
- Predecessor 14 archive PASS predates every new C01-C09 attempt.
- Preflight count is one, scored run count is one, and run ID is absent from all older roots.
- Every matrix slot has exactly one caller attempt and terminal web validation; no resume/`--retry-failed` exists.
- Score command count is 1-3 only; retries correspond solely to prior `scoring_failed` and have fresh score IDs.
- Reports use the exact run ID, retain failed/unscored rows, and do not invent unavailable metrics or arbitrary ranks.
- Exact protected-value scan returns zero hits and old-run/testbed/subscription/runtime identities are unchanged.
## Verification Results
### TEST-1 Admission
```text
_Implementing agent: exact commands, bounded output, identities, and exit codes._
```
### TEST-2 C01-C09 run
```text
_Implementing agent: exact single run output, ID, terminal axes, and exit._
```
### TEST-3 Status and scoring
```text
_Implementing agent: exact status/score commands, score IDs/counts, output, and exits._
```
### TEST-4 Report and evidence audit
```text
_Implementing agent: exact report output/path and bounded audit results._
```
### TEST-5 Final integrity
```text
_Implementing agent: fresh tests, immutable/isolation audit, and exits._
```
---
> **[IMPLEMENTING AGENT — BEFORE SAVING]** Fill every implementation-owned section and check every completed item. Leave review-only sections unchanged.
## Section Ownership
| Section | Owner |
|---|---|
| Header, Overview, Archive Snapshot, item names, checkpoints | Fixed at stub creation |
| Implementation checklist statuses, deviations, decisions, initial verification | Implementing agent |
| Review-only checklist, verdict, archive/finalization | Official review agent |

View file

@ -0,0 +1,239 @@
<!-- task=m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report plan=1 tag=TEST milestone-task=claude-standalone,gemini-standalone,gpt-standalone,gemini-hybrid,gpt-hybrid,objective-validation,quality-scoring,performance-usage,benchmark-report -->
# Code Review Reference - TEST
> **[IMPLEMENTING AGENT — READ FIRST]** Implement the compact fixture and same-caller scoring fix, verify them, then execute only the one changed-manifest live run authorized by the plan. Fill every implementation-owned section. Do not resume/retry caller attempts, allocate another run, mutate old evidence, ask the user, create stop files, archive artifacts, or write `complete.log`; official review owns finalization.
## Overview
date=2026-08-13
task=m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report, plan=1, tag=TEST
## Archive Evidence Snapshot
- Superseded pair: `plan_cloud_G09_0.log`, `code_review_cloud_G09_0.log`.
- Diagnostic-only old run: `agent-test/runs/bench-02/run-20260813T071816Z-755d136c3e2b`; preserve it and never use it as the final report source.
- Predecessor 14 PASS: `agent-task/archive/2026/08/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/complete.log`.
## For the Review Agent
Verify that the implementation task is genuinely compact and capped at 180 seconds per sequential cell, the full matrix is unchanged, and same-caller evaluator-owned Codex state no longer triggers a false producer leak without weakening cell/path/producer-only identity rejection. Reconstruct the exact new run/score/report counts from durable state. On PASS append verdict, archive pair, write `complete.log` with milestone metadata, and move the task directory. On WARN/FAIL follow the code-review skill with one evidence-backed fix packet.
## Implementation Item Completion
| Item | Status |
|---|---|
| TEST-1 Micro fixture/manifest | [x] |
| TEST-2 Same-caller scoring fix | [x] |
| TEST-3 Deterministic regression | [x] |
| TEST-4 One new live run | [x] |
| TEST-5 Scoring/report | [ ] |
## Implementation Checklist
- [x] [TEST-1] Replace the old seven-section prompt/reference with the compact one-card task, set the execution ceiling to 180 seconds, update fixture version/checksum and exact manifest regression assertions, and prove the full C01-C09 matrix is unchanged.
- [x] [TEST-2] Fix same-caller blind identity classification in `_identity_values` and add focused tests proving evaluator-owned Codex state is allowed only when the producer caller is also Codex while cell/path/producer-only identities still fail closed.
- [x] [TEST-3] Run focused and full deterministic verification, validate the changed manifest, and confirm no test invokes real providers.
- [x] [TEST-4] Run secret-safe live preflight and then exactly one fresh changed-manifest C01-C09 `run`; capture the new run ID and require nine terminal slots with `unresolved=0` without caller resume/retry.
- [ ] [TEST-5] Score the exact new run, use scoring-only retry solely after durable `scoring_failed` and within the three-invocation ceiling, generate the deterministic run report, publish the dated report, and audit identities/counts/links/isolation.
- [x] Fill implementation-owned sections below with exact evidence.
## Review-Only Checklist
> **[REVIEW AGENT ONLY]** Implementing agents must not modify this section.
- [x] Append verdict and verified routing signals.
- [x] Verify every dimension and finding classification.
- [x] Rerun deterministic commands and reconstruct dynamic command/run/score/report counts.
- [x] Confirm shared-caller exemption is limited to the evaluator's own caller and all producer-specific identity guards remain closed.
- [x] Confirm prior runs/testbed/normal subscriptions/protected values were not mutated or exposed.
- [x] Archive plan/review to correctly numbered logs and verify `.gitignore` managed block.
- [ ] On PASS write `complete.log`, preserve/report milestone metadata, move the task directory, and leave no active task files.
- [x] On WARN/FAIL write the required next filesystem state and do not write `complete.log`.
## Deviations from Plan
- The shared prompt/reference change invalidated the three tracked example manifests that reuse those inputs. Their fixture version/checksum were synchronized in `agent-comparison-benchmark-manifest.example.json`, `agent-comparison-benchmark-supported-direct.example.json`, and `agent-comparison-benchmark-direct-preflight.example.json`; their timeout and matrices were not changed.
- TEST-5 did not complete. Three official score invocations all failed closed with exit 69 before publishing a score result. No durable `scoring_failed` existed, so `--retry-scoring-failed` was never used. The deterministic report consequently returned exit 69 and the dated report was not fabricated.
## Key Design Decisions
- The fixture now requires only one compact product card: a small product header, one hero, one primary CTA, both visible local images, and a keyboard-accessible status-detail toggle whose content remains readable without JavaScript. The seven-section landing-page burden was removed.
- `_identity_values` always retains the opaque cell id, attempt path, and producer-only route/model/effort identities. It omits the producer caller exact token only when `cell.caller == manifest.evaluator.caller`; this permits evaluator-owned `.codex`/Codex state without weakening other identity guards.
- Manifest allocation was deferred until focused/full deterministic checks and manifest validation passed. After live `ready=9`, exactly one new run was issued. No caller resume/retry, route/model/effort substitution, run-state edit, second run, or evidence deletion occurred.
- After run allocation, prompt/reference/manifest/scoring source was not changed. All seven failed artifact rows were retained as `unscored`; no zero or selected-success replacement was introduced.
- Spec update not needed: the benchmark lifecycle and public CLI contract remain as documented; this packet changes the benchmark input size and fixes a narrow evaluator-shared identity classification.
## Reviewer Checkpoints
- Prompt requires only one compact product card, one CTA, two images, and one tiny enhancement; it does not retain the seven-section landing scope.
- `timeout.run_seconds` is exactly 180 and the report calls it a per-cell ceiling, not total nine-cell wall time.
- C01-C09 callers/routes/models/efforts/repetitions and evaluator/rubric remain unchanged.
- A producer caller equal to evaluator caller is excluded only from shared exact caller tokens; cell id, attempt path, and producer-only model/route/effort leaks still fail.
- One new run ID follows the changed manifest and has exactly one attempt per slot, nine terminal web records, no caller retry/resume, and no second new run.
- Score results are durable and report rows retain all failed/unscored outcomes without zero fabrication.
## Verification Results
status=BLOCKED — scoring finalization did not publish a durable result within the plan-authorized three score invocations.
### TEST-1 Micro fixture/manifest
```text
Changed fixture:
- version=product-card-v2
- fixture checksum=sha256:fb16198fd4c3576f880f047ed7de54dddc160b0f70c2ba55b435cf078c61828e
- run_seconds=180; idle/quiet/cleanup unchanged at 30/10/5
- viewports remain desktop_1080 1920x1080 and mobile_375 375x812
- repetitions=1, seed=bench-02-c01-c09-v1, evaluator/rubric and exact C01-C09 matrix unchanged
python3 -m unittest scripts.agent_benchmark.manifest_test scripts.agent_benchmark.scoring_test
exit 0; Ran 132 tests in 6.533s; OK
python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
exit 0; ok: manifest is valid
Validated manifest digest for the issued run:
sha256:38e48ef35beaa6ecc1aee0460df443e0ccf101a89aa6b91ce1f4b198aa84022a
```
### TEST-2 Same-caller scoring fix
```text
Focused command:
python3 -m unittest scripts.agent_benchmark.manifest_test scripts.agent_benchmark.scoring_test
exit 0; Ran 132 tests in 6.533s; OK
Regression coverage:
- Codex-produced artifact plus Codex evaluator-owned `.codex/state.txt` containing `Codex evaluator-owned state`: scored, no false leak.
- same-caller `_identity_values.exact_tokens`: only opaque `cell-sentinel`; shared caller `codex` excluded.
- opaque cell id, resolved attempt path, and producer-only `source-route`: still detected.
- existing non-shared `agy` caller integration, delimited/binary caller and cell ids, producer route/model, invalid filesystem bytes, and evaluator-visible tree scans: all retained and passing.
```
### TEST-3 Deterministic regression
```text
python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
exit 0; Ran 460 tests in 152.445s; OK
python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
exit 0; ok: manifest is valid
git diff --check -- . ':(exclude)agent-task/archive/**'
exit 0; no output
The deterministic suite ran without the benchmark environment wrapper. Its execution/scoring paths use fake adapters and the repository provider-deny boundary; no real provider command was invoked by these tests.
```
### TEST-4 One new live run
```text
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py preflight --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
exit 0 in 6.536s
ok: preflight run_id=run-20260813T081317Z-cfa895b54397 status=ready ready=9 registration_required=0 implementation_gap=0
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py run --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
exit 0
ok: run run_id=run-20260813T081326Z-4e1ac5152c6c executed=9 unresolved=0 completed=8 timed_out=1 cancelled=0 interrupted=0 running=0 product_succeeded=3 product_failed=4 product_unknown=2 harness_passed=7 harness_failed=2 process_exited=8 process_signalled=0 process_timed_out=1 process_cancelled=0 process_not_started=0 artifact_passed=2 artifact_failed=7 artifact_blocked=0 artifact_not_run=0
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c
exit 0 in 0.497s; counts exactly match the run summary.
Bounded durable-state audit:
- attempt.json=9, web-validation.json=9, attempt-measurement.json=9
- C01-C09 each have exactly attempt-000001
- score-eligible artifacts: C05 and C09; seven other rows retained as unscored
- no resume, caller retry, second changed-manifest run, or retry attempt
```
### TEST-5 Scoring/report
```text
Score invocation 1:
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py score --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c
exit 69 after about 32s; stderr: error: benchmark scoring is unavailable run_id=run-20260813T081326Z-4e1ac5152c6c
Score invocations 2 and 3 used the same official command without a retry flag.
Both exited 69 in 3.278s and 3.357s with the same sanitized stderr.
No fourth invocation was made. `--retry-scoring-failed` was never used because no durable result.json/status=scoring_failed was published.
Durable scoring audit after invocation 3:
- unscored.json=7: C01, C02, C03, C04, C06, C07, C08; their exact independent gate reasons are retained.
- C05: score-000001 allocation/input/runner exist; result.json is absent.
- C09: eligible but no score allocation was reached.
- score result.json=0, run-owned report=0.
- Read-only validation of C05 allocation, frozen input digest, runner, identity-visible tree, strict worksheet, cleanup receipt, and lifecycle all passed. The fail-closed boundary is after completed evaluator evidence and before durable result publication; the CLI intentionally suppresses the internal exception.
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py report --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c
exit 69; stderr: error: benchmark report is unavailable
Resume condition: Official review creates or approves a follow-up source fix for interrupted scoring finalization and explicitly authorizes a further score invocation for the preserved run.
BLOCKED follow-up details:
- Official review must create/approve a follow-up source fix for interrupted live scoring finalization and explicitly authorize any further score invocation beyond this packet's exhausted three-invocation ceiling.
- The follow-up must preserve this exact run and C05 score-000001 bytes, close the interrupted result append-only, retry only after a durable scoring_failed result, score C05/C09 without caller retry, then generate the deterministic run report and dated report.
- Until then, no final C01-C09 comparison or quality ranking is publishable. `agent-test/dev/iop-one-shot-agent-comparison-2026-08-13.md` is intentionally absent rather than fabricated.
```
---
> **[IMPLEMENTING AGENT — BEFORE SAVING]** Fill every implementation-owned section and check every completed item. Leave review-only sections unchanged.
## Section Ownership
| Section | Owner |
|---|---|
| Header, overview, archive snapshot, item names, checkpoints | Fixed at stub creation |
| Implementation statuses, deviations, decisions, verification | Implementing agent |
| Review-only checklist, verdict, archive/finalization | Official review agent |
## Reviewer Fresh Verification
```text
python3 -m unittest scripts.agent_benchmark.manifest_test scripts.agent_benchmark.scoring_test
exit 0; Ran 132 tests in 6.347s; OK
python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
exit 0; Ran 460 tests in 150.648s; OK
python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
exit 0; ok: manifest is valid
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c
exit 0; unresolved=0, completed=8, timed_out=1, artifact_passed=2, artifact_failed=7; all other counts match the implementation record.
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py report --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c
exit 69; error: benchmark report is unavailable
Durable-state audit:
- attempt.json=9, web-validation.json=9, attempt-measurement.json=9
- unscored.json=7, score allocation=1, score result.json=0, run report=0
- C05 score-000001 has valid allocation/input/runner, successful lifecycle, cleanup receipt, and a valid 94-point worksheet.
- `_identity_values`, frozen input, evaluator-visible identity scan, worksheet, runner, cleanup, lifecycle, and post-tree validation all pass on the retained C05 evidence; `_result_status` is None only because result.json is absent.
Focused reproduction against the retained blind tree:
`_wait_post_cleanup_quiet(blind_root, lifecycle_validator=...)`
=> ScoringError: evaluator lifecycle publication is incomplete after 2.591s
The blind tree contains 5,378 files, primarily evaluator-owned Codex session/plugin/cache state.
```
## Code Review Result
- **Overall Verdict**: FAIL
- **Dimension Assessment**:
- Correctness: Fail — a successful evaluator lifecycle and worksheet cannot reach durable score publication.
- Completeness: Fail — TEST-5, the run-owned report, and the dated report are absent.
- Test Coverage: Fail — current recovery tests use small blind trees and do not cover a large mutable evaluator session tree.
- API Contract: Fail — the public `score`/`report` lifecycle remains unavailable for the preserved terminal run, contrary to S10/S12.
- Code Quality: Pass — the compact fixture and same-caller identity change are focused and readable.
- Implementation Deviation: Fail — the declared scoring/report deliverable is incomplete, although the deviation was accurately recorded and no evidence was fabricated.
- Verification Trust: Pass — fresh deterministic checks and durable-state reconstruction agree with the implementation record.
- Spec Conformance: Fail — S04-S09 execution evidence exists, but S10 quality scoring and S12 report evidence are not complete.
- **Findings**:
- **Required R1 — Bound lifecycle quiescence to evaluator publication state and finish the preserved run.**
- **Evidence**: `scripts/agent_benchmark/scoring.py:848-905` starts a two-second deadline and recursively snapshots all of `blind_root`; `scripts/agent_benchmark/scoring.py:944-952` uses that scan even when lifecycle and cleanup evidence already exist. The retained C05 blind tree has 5,378 files. A reviewer-run call reproduces `ScoringError: evaluator lifecycle publication is incomplete` after 2.591s, while allocation, input, runner, cleanup, lifecycle, worksheet (94), identity scan, and post-tree checks independently pass. Public report remains exit 69 with zero score results.
- **Root Cause**: `_wait_post_cleanup_quiet` treats evaluator session/cache state as lifecycle-publication state. Traversing the large Codex session/plugin tree consumes the whole deadline before a stable interval can be observed, so `_recover_runner` raises before `_score_one` or `_complete_interrupted` can append `result.json`.
- **Selected Fix**: In `scripts/agent_benchmark/scoring.py`, scope the quiet snapshot to the evaluator-owned `output/` publication surface while preserving strict lifecycle journal/result validation, cleanup-receipt binding, stable-digest revalidation, and socket/alias cleanup. In `scripts/agent_benchmark/scoring_test.py`, add a deterministic recovery regression that continuously mutates `session/` while a stable published `output/` lifecycle is recovered; require recovery to succeed, retain the existing output-mutation and missing-publication failures, and verify no prior bytes are rewritten. After deterministic tests pass, use the preserved run only: one normal `score` invocation may close C05 score-000001 append-only and score C09; only if C05 becomes durable `scoring_failed`, use one `--retry-scoring-failed` invocation for C05. Then require `scored=2`, `unscored=7`, `scoring_failed=0`, generate the run report and dated report, and do not run/resume callers or allocate another run.
- **Routing Signals**: `review_rework_count=1`, `evidence_integrity_failure=false`
- **Next Step**: Create the mandatory routed follow-up pair from Required R1; do not write `complete.log` or update the Milestone.

View file

@ -0,0 +1,216 @@
<!-- task=m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report plan=2 tag=REVIEW_TEST milestone-task=claude-standalone,gemini-standalone,gpt-standalone,gemini-hybrid,gpt-hybrid,objective-validation,quality-scoring,performance-usage,benchmark-report -->
# Code Review Reference - REVIEW_TEST
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> Complete the checklist, fill every implementation-owned section, keep active files in place, and report ready for review.
> Execute Required R1 exactly as planned. Do not choose another owner or remedy, ask the user, create stop files, archive logs, write `complete.log`, run/resume callers, or allocate another run.
## Overview
date=2026-08-13
task=m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report, plan=2, tag=REVIEW_TEST
## Archive Evidence Snapshot
- Failed review: `code_review_cloud_G10_1.log`; verdict `FAIL`, Required R1 only, Suggested/Nit none.
- Closed plan: `plan_cloud_G10_1.log`; TEST-1 through TEST-4 passed, TEST-5 incomplete.
- Preserved run: `agent-test/runs/bench-02/run-20260813T081326Z-4e1ac5152c6c`; nine terminal attempts, seven unscored rows, C05 successful evaluator/94-point worksheet, zero score results and reports.
- Selected fix: bound lifecycle quiet observation to evaluator output publication, raise its fail-closed maximum deadline from 2 seconds to 300 seconds, add session-churn regression, then finish this run through the official score/report CLI only.
## For the Review Agent
Verify the output-scoped recovery fix preserves strict lifecycle/receipt/digest cleanup, the new regression proves session churn cannot exhaust publication wait, and all 460+ deterministic tests pass. Reconstruct the preserved run's exact C05/C09 scoring and seven unscored rows, verify no caller attempt/new run/state rewrite, and inspect both reports. Finalization remains code-review-only.
## Implementation Item Completion
| Item | Status |
|---|---|
| REVIEW_TEST-1 Bound lifecycle quiescence | [x] |
| REVIEW_TEST-2 Session-churn regression | [x] |
| REVIEW_TEST-3 Deterministic gate | [x] |
| REVIEW_TEST-4 Preserved scoring closure | [ ] Blocked: C09 retry retained `evaluator_output_leak` |
| REVIEW_TEST-5 Report publication | [ ] Not run after scoring blocker |
## Implementation Checklist
- [x] [REVIEW_TEST-1] Scope lifecycle quiescence to evaluator output publication, set the maximum wait to 300 seconds, and preserve lifecycle/receipt/digest and cleanup fail-closed checks.
- [x] [REVIEW_TEST-2] Add a deterministic large/mutating-session recovery regression and keep delayed, changed, and missing output cases passing.
- [x] [REVIEW_TEST-3] Run focused/full deterministic verification and manifest validation before any live score invocation.
- [ ] [REVIEW_TEST-4] On the preserved run only, invoke normal score once; use one retry flag only after durable `scoring_failed`; require C05/C09 scored, seven retained unscored rows, no blocked/failure result, and no caller/run allocation.
- [ ] [REVIEW_TEST-5] Generate the run-owned report, publish `agent-test/dev/iop-one-shot-agent-comparison-2026-08-13.md`, and audit exact run links, failures, three-minute per-cell/sequential limitation, metrics, scores, and append-only isolation.
- [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 section.
- [ ] Append one verdict and verified routing signals.
- [ ] Verify every dimension and finding classification.
- [ ] Run required verification and reconstruct dynamic score/report/run counts.
- [ ] Verify Required R1 resolution map, exact fix, and acceptance commands.
- [ ] Archive active pair to correctly numbered logs and verify `.gitignore` managed block.
- [ ] On PASS write `complete.log`, report milestone metadata, move the task directory, and leave no active files.
- [ ] On WARN/FAIL write the required next filesystem state and do not write `complete.log`.
## Deviations from Plan
The planned normal score command durably closed C05 `score-000001` as
`scoring_failed/interrupted` and C09 `score-000001` as
`scoring_failed/evaluator_output_leak`. The conditionally authorized single
`--retry-scoring-failed` invocation then scored C05 as 93 points in
`score-000002`, but C09 `score-000002` again failed with
`evaluator_output_leak`. Per the plan and benchmark skill stop conditions, no
third score attempt, status/report command, manual state edit, caller retry,
or new run was performed. REVIEW_TEST-4 and REVIEW_TEST-5 therefore remain
incomplete.
Resume condition: a reviewed follow-up must diagnose and resolve the repeated
C09 `evaluator_output_leak` without weakening identity isolation, pass the
deterministic gate, and explicitly authorize a new scoring retry. Only after
the preserved run reaches `scored=2 unscored=7 scoring_failed=0 blocked=0`
may report generation and dated publication proceed.
Read-only diagnosis after the stopped retry narrowed the blocker further. Both
C09 blind workspaces contain valid worksheets (94 and 95 points), and the
producer identity is absent from the anonymous `input/` and worksheet content.
The post-evaluator whole-tree scan instead finds producer-only tokens in fresh
Codex evaluator-owned `session/.codex` state: `gpt-5.6-terra` occurs in the
bundled OpenAI model reference and the generic effort token `high` occurs in
plugin/cache files. This affects C09 because its GPT hybrid producer binding
differs from the GPT Luna evaluator binding. C05 uses the same caller and an
evaluator-shared direct GPT Luna binding, so those evaluator-owned strings are
not classified as producer-only for C05.
The follow-up therefore needs a provenance-aware boundary between anonymous
producer evidence and evaluator-owned runtime/session state. It must retain
secret scrubbing and reject producer identity in anonymous inputs, evaluator
prompts, worksheets, and other publication evidence; merely deleting tokens,
dropping identity checks, or exempting all session state is not an acceptable
repair. This diagnosis does not authorize that repair or another score attempt
under the current plan.
## Key Design Decisions
- `_POST_CLEANUP_TIMEOUT_SECONDS` is 300 seconds; the existing 0.2-second
quiet interval and 0.01-second poll remain unchanged.
- All three `_recover_runner` quiet waits observe `blind_root / "output"`.
`_wait_post_cleanup_quiet` consequently resolves lifecycle journal/result
relative to that publication root. Lifecycle binding, receipt validation,
final digest comparison, socket cleanup, alias cleanup, input freeze,
identity scan, and post-tree validation were not weakened.
- The regression creates 2,048 evaluator session files and continuously
rewrites another session file while recovery validates stable output. It
asserts recovery completes inside a patched 0.4-second deadline and that
lifecycle journal/result and cleanup receipt bytes are unchanged.
## Reviewer Checkpoints
- `_wait_post_cleanup_quiet` observes only the evaluator output publication surface; session/cache churn cannot consume its deadline.
- `_POST_CLEANUP_TIMEOUT_SECONDS` is 300 seconds; stable output still returns after the short quiet interval, while incomplete output fails closed at the deadline.
- Lifecycle journal/result, receipt, final digest, socket/alias cleanup, input freeze, identity scan, and post-tree validation remain strict.
- Existing delayed publication succeeds; changed output and missing publication still fail closed.
- Deterministic tests pass before score continuation.
- C05 score-000001 is closed append-only; C09 is scored; seven prior unscored rows remain unchanged.
- Final state is `scored=2 unscored=7 scoring_failed=0 blocked=0`, with exactly nine caller attempts and one run identity.
- Both reports point only to `run-20260813T081326Z-4e1ac5152c6c` and state per-cell versus sequential timing limitations.
## Verification Results
### REVIEW_TEST-1/2 Focused fix and regression
```text
$ python3 -m unittest scripts.agent_benchmark.scoring_test
...................
----------------------------------------------------------------------
Ran 19 tests in 5.322s
OK
exit 0
```
### REVIEW_TEST-3 Deterministic gate
```text
$ python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
............................................................................................................................................................................................................................................................................................................................................................................................................................................................................
----------------------------------------------------------------------
Ran 460 tests in 152.724s
OK
exit 0
$ python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
ok: manifest is valid
exit 0
$ git diff --check -- . ':(exclude)agent-task/archive/**'
(no output)
exit 0
```
### REVIEW_TEST-4 Preserved scoring closure
```text
$ /bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py score --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c
error: benchmark scoring failed run_id=run-20260813T081326Z-4e1ac5152c6c scored=0 unscored=7 scoring_failed=2 blocked=0
exit 69
Durable result audit after the normal command:
c05-codex-gpt-direct score-000001 status=scoring_failed reason=interrupted
c09-codex-gpt-hybrid score-000001 status=scoring_failed reason=evaluator_output_leak
$ /bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py score --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c --retry-scoring-failed
error: benchmark scoring failed run_id=run-20260813T081326Z-4e1ac5152c6c scored=1 unscored=7 scoring_failed=1 blocked=0
exit 69
Final durable result audit:
c05-codex-gpt-direct score-000001 status=scoring_failed reason=interrupted
c05-codex-gpt-direct score-000002 status=scored worksheet.total=93
c09-codex-gpt-hybrid score-000001 status=scoring_failed reason=evaluator_output_leak
c09-codex-gpt-hybrid score-000002 status=scoring_failed reason=evaluator_output_leak
Before scoring: RUN_DIRS=25 ATTEMPT_DIRS=9 SCORE_RESULT_FILES=0
After retry: RUN_DIRS=25 ATTEMPT_DIRS=9 SCORE_DIRS=4 SCORE_RESULT_FILES=4
No status command was run after the terminal scoring failure because the
benchmark skill requires stopping after `scoring_failed`. No caller attempt or
run identity was allocated.
Read-only retained-tree diagnosis (no official CLI call and no state write):
- both C09 `output/worksheet.json` files are valid and total 94/95;
- `gpt-5.6-terra` matches evaluator-owned
`session/.codex/skills/.system/openai-docs/references/latest-model.md` in
both blind trees;
- the producer effort token `high` matches evaluator-owned plugin/cache files,
including `session/.codex/.tmp/plugins/.git/index`;
- reapplying the current whole-tree identity scanner to the retained C09 blind
roots reproduces `evaluator-visible evidence leaks execution identity`.
```
### REVIEW_TEST-5 Reports and isolation
```text
Not run. The preserved run remained
`scored=1 unscored=7 scoring_failed=1 blocked=0`, so deterministic report
projection and dated publication were prohibited.
report.md exists: no
agent-test/dev/iop-one-shot-agent-comparison-2026-08-13.md exists: no
Resume only after C09 is durably scored and the closed score summary is
`scored=2 unscored=7 scoring_failed=0 blocked=0`.
```
---
> **[IMPLEMENTING AGENT — BEFORE SAVING]** Fill every implementation-owned section and check every completed item. Leave review-only sections unchanged.
## Section Ownership
| Section | Owner |
|---|---|
| Header, overview, archive snapshot, item names, checkpoints | Fixed at stub creation |
| Implementation statuses, deviations, decisions, verification | Implementing agent |
| Review-only checklist, verdict, archive/finalization | Official review agent |

View file

@ -0,0 +1,257 @@
<!-- task=m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report plan=3 tag=REVIEW_TEST milestone-task=claude-standalone,gemini-standalone,gpt-standalone,gemini-hybrid,gpt-hybrid,objective-validation,quality-scoring,performance-usage,benchmark-report -->
# Code Review Reference - REVIEW_TEST
> **[IMPLEMENTING AGENT — READ FIRST]** Implement only the provenance-aware identity boundary in the paired plan, run deterministic gates before the single authorized C09 retry, fill every implementation-owned section, and leave verdict/archive/completion to official review.
## Overview
date=2026-08-13
task=m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report, plan=3, tag=REVIEW_TEST
## Evidence Snapshot
- Previous pair: `plan_cloud_G10_2.log`, `code_review_cloud_G10_2.log`.
- Preserved run: `agent-test/runs/bench-02/run-20260813T081326Z-4e1ac5152c6c`.
- Starting state: C05 scored 93, C09 failed identity isolation twice despite valid 94/95 worksheets, seven unscored rows, no report.
- Confirmed false positive: C09 producer model/effort strings occur in fresh evaluator-owned `session/.codex` documentation/cache, not anonymous input or worksheet content.
## Implementation Item Completion
| Item | Status |
|---|---|
| REVIEW_TEST-1 Provenance-aware scan | [x] |
| REVIEW_TEST-2 Regression coverage | [x] |
| REVIEW_TEST-3 Deterministic gate | [x] |
| REVIEW_TEST-4 C09 scoring closure | [x] |
| REVIEW_TEST-5 Report publication | [x] |
## Implementation Checklist
- [x] [REVIEW_TEST-1] Exclude only fresh evaluator-owned session state from producer identity scanning; retain input/output/prompt/path checks.
- [x] [REVIEW_TEST-2] Prove evaluator session tokens pass and the same tokens in producer evidence/output fail.
- [x] [REVIEW_TEST-3] Pass focused/full tests, manifest validation, and diff hygiene before live score.
- [x] [REVIEW_TEST-4] Use the one authorized C09 scoring retry and audit final two-scored/seven-unscored state without another run or caller attempt.
- [x] [REVIEW_TEST-5] Generate run-owned and dated reports with contained links and timing limitations.
- [x] Fill implementation-owned evidence below.
## Review-Only Checklist
> **[REVIEW AGENT ONLY]** Implementing agents must not modify this section.
- [x] Append verdict and verified routing signals.
- [x] Re-run deterministic verification and reconstruct score/run/report counts.
- [x] Confirm session exemption is provenance-limited and input/output isolation remains fail-closed.
- [x] Verify no caller retry, new run, evidence rewrite, or protected-value exposure.
- [x] On PASS archive the pair, write `complete.log`, move the task directory, and leave no active task files.
- [ ] On WARN/FAIL create the required follow-up state and do not write `complete.log`.
## Deviations from Plan
None.
## Key Design Decisions
- Producer identity scanning now visits exactly the frozen anonymous `input/`
and evaluator publication `output/` roots. The controller-created fresh
evaluator `session/` is not producer provenance and is the only excluded
root.
- No identity token, model, effort, `.codex` path, or output-content exemption
was added. Prompt scanning, input materialization/freeze/digest checks,
output path/content and worksheet scans, cell/attempt/binding checks remain
fail-closed.
- Session state remains inside adapter secret scrubbing and the final whole
blind-tree digest. Lifecycle/receipt/digest validation, invalid file-type
handling, cleanup, and post-tree binding are unchanged.
## Verification Results
### REVIEW_TEST-1/2 Focused fix
```text
$ python3 -m unittest scripts.agent_benchmark.scoring_test
...................
----------------------------------------------------------------------
Ran 19 tests in 5.322s
OK
exit 0
```
`test_evaluator_session_identity_tokens_are_accepted_but_anonymous_evidence_rejects_them`
proves that producer model/effort strings written by the evaluator under
`session/` are accepted. The same values in anonymous input, an output path,
output content, or worksheet evidence fail closed; input leakage prevents the
evaluator invocation and output leakage records `evaluator_output_leak`.
### REVIEW_TEST-3 Deterministic gate
All gates below passed before the authorized scoring retry:
```text
$ python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
............................................................................................................................................................................................................................................................................................................................................................................................................................................................................
----------------------------------------------------------------------
Ran 460 tests in 152.724s
OK
exit 0
$ python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
ok: manifest is valid
exit 0
$ git diff --check -- . ':(exclude)agent-task/archive/**'
(no output)
exit 0
```
The deterministic tests did not invoke a live caller or provider.
After report publication, the final deterministic rerun also passed:
```text
$ python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
----------------------------------------------------------------------
Ran 460 tests in 159.711s
OK
exit 0
$ python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
ok: manifest is valid
exit 0
$ git diff --check -- . ':(exclude)agent-task/archive/**'
(no output)
exit 0
```
### REVIEW_TEST-4 Preserved scoring closure
Exactly one newly authorized scoring retry was invoked; no normal score call,
producer `run`/`resume`, caller retry, or new run was invoked in this plan:
```text
$ /bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py score --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c --retry-scoring-failed
ok: score run_id=run-20260813T081326Z-4e1ac5152c6c scored=2 unscored=7 scoring_failed=0 blocked=0
exit 0
$ /bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c
ok: status run_id=run-20260813T081326Z-4e1ac5152c6c unresolved=0 completed=8 timed_out=1 cancelled=0 interrupted=0 running=0 product_succeeded=3 product_failed=4 product_unknown=2 harness_passed=7 harness_failed=2 process_exited=8 process_signalled=0 process_timed_out=1 process_cancelled=0 process_not_started=0 artifact_passed=2 artifact_failed=7 artifact_blocked=0 artifact_not_run=0
exit 0
```
Append-only audit:
- output root run identities: 25 before and after;
- preserved producer attempts: 9 before and after, one per C01-C09 slot;
- pre-retry score directories/results: 4; post-retry: 5;
- C05 terminal score remains `score-000002`, total 93, result digest
`sha256:1bee5f8266078e37dde687d0252b804516d1e7a8285bfdf3a99a11468766c0d5`;
- the only new allocation/result is C09 `score-000003`, total 93, result
digest `sha256:9302104fa76d6592e77e901bcce40f3d5188990cf1b4dbbab54e8dd43735a7c8`;
- prior C09 `score-000001` and `score-000002` failure result digests remain
`sha256:e98fdc6ef72ee34ef8cc09f3e3027a2c530da4d8b2e26f08cd53fcab94f985c3`
and
`sha256:cfd605c5af7ac34d26ff02c8f2ec86fd3ffe5d7d2be8e92907d409280c8fbf1f`;
- producer evidence remains 10,959 files with the same pre/post digest
`sha256:1a68004d9a56818ceaaa8aadca1203f7370728d1223ae178d76ecedf0dffbcf1`;
this producer-only audit excludes append-only scoring/blind/scoring-preflight
state and the derived `report.md`.
### REVIEW_TEST-5 Reports and isolation
```text
$ /bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py report --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c
ok: report run_id=run-20260813T081326Z-4e1ac5152c6c path=agent-test/runs/bench-02/run-20260813T081326Z-4e1ac5152c6c/report.md
exit 0
```
- The deterministic report and
`agent-test/dev/iop-one-shot-agent-comparison-2026-08-13.md` both project
the same preserved run and all nine rows.
- Both record C05/C09 as scored 93 and retain seven unscored rows without
assigning zero or rank.
- The dated report lists source-aware per-cell timing and caller-reported
usage, leaves unavailable metrics unavailable, and states that 180 seconds
is a per-cell ceiling while the nine cells execute sequentially.
- Every dated-report raw link resolves under
`agent-test/runs/bench-02/run-20260813T081326Z-4e1ac5152c6c/`; no link points
to a different run, external path, credential, or evaluator identity map.
- Link/count audit passed with 43 contained existing links, nine result rows,
two scored rows, and seven unscored rows.
### Reviewer Fresh Verification
The official reviewer independently reran every safe required route. The
single authorized live scoring retry had already been consumed successfully,
so it was not invoked again.
```text
$ python3 -m unittest scripts.agent_benchmark.scoring_test
...................
----------------------------------------------------------------------
Ran 19 tests in 5.986s
OK
exit 0
$ python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
----------------------------------------------------------------------
Ran 460 tests in 156.203s
OK
exit 0
$ python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
ok: manifest is valid
exit 0
$ /bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c
ok: status run_id=run-20260813T081326Z-4e1ac5152c6c unresolved=0 completed=8 timed_out=1 cancelled=0 interrupted=0 running=0 product_succeeded=3 product_failed=4 product_unknown=2 harness_passed=7 harness_failed=2 process_exited=8 process_signalled=0 process_timed_out=1 process_cancelled=0 process_not_started=0 artifact_passed=2 artifact_failed=7 artifact_blocked=0 artifact_not_run=0
exit 0
$ /bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py report --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c
ok: report run_id=run-20260813T081326Z-4e1ac5152c6c path=agent-test/runs/bench-02/run-20260813T081326Z-4e1ac5152c6c/report.md
exit 0
$ git diff --check -- . ':(exclude)agent-task/archive/**'
(no output)
exit 0
```
Reviewer reconstruction confirmed nine producer attempts, 25 pre-existing
run identities before and after review, five append-only score results, C05
and C09 terminal totals of 93, seven terminal unscored rows, and 43/43 dated
report links resolving inside the preserved run. The producer projection has
10,959 regular-file/symlink records under the recorded boundary. C05
`score-000002` and C09 `score-000001..000003` result SHA-256 values match the
implementation record. No caller, provider, score retry, resume, or new run
was invoked by the reviewer.
---
## Code Review Result
- **Overall Verdict**: PASS
- **Dimension Assessment**:
- Correctness: Pass — producer identity scanning remains fail-closed for anonymous input and evaluator output while excluding only controller-created evaluator session provenance.
- Completeness: Pass — every planned code, test, preserved scoring, and report deliverable is present and independently reconstructable.
- Test Coverage: Pass — the focused regression covers session acceptance and input/output rejection, and all 460 benchmark tests pass.
- API Contract: Pass — public validate/status/report commands succeed on the preserved run and append-only score semantics remain intact.
- Code Quality: Pass — the change is localized, documented at the provenance boundary, and contains no debug or unfinished code.
- Implementation Deviation: Pass — no unplanned behavioral deviation or unrelated current-packet change was found.
- Verification Trust: Pass — fresh reviewer commands, score-result digests, counts, and contained links agree with the implementation record.
- Spec Conformance: Pass — S04-S12 evidence is represented by the nine terminal attempts, uniform validation, two blind scores, source-aware timing/usage, and the dated report.
- **Findings**: None
- **Routing Signals**: `review_rework_count=1`, `evidence_integrity_failure=false`
- **Next Step**: Archive the PASS pair, write `complete.log`, move the split task directory, and emit Milestone completion-event metadata without modifying the roadmap.
## Section Ownership
| Section | Owner |
|---|---|
| Header, item names, review checklist | Fixed at stub creation |
| Implementation statuses, deviations, decisions, verification | Implementing agent |
| Verdict, archive, completion | Official review agent |

View file

@ -0,0 +1,44 @@
<!-- task=m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report plan=3 tag=REVIEW_TEST milestone-task=claude-standalone,gemini-standalone,gpt-standalone,gemini-hybrid,gpt-hybrid,objective-validation,quality-scoring,performance-usage,benchmark-report -->
# Complete - m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report
## 완료 일시
2026-08-13
## 요약
4개 Plan/Review 루프를 거쳐 보존 C01-C09 run의 provenance-aware 익명 채점과 날짜별 비교 보고서를 완료했으며 최종 판정은 PASS다.
## 루프 이력
| Plan | Review | Verdict | 메모 |
|------|--------|---------|------|
| `plan_cloud_G09_0.log` | `code_review_cloud_G09_0.log` | 대체됨 | 초기 큰 fixture 실행은 사용자 요청으로 compact fixture 계획에 대체되었고 진단 evidence만 보존했다. |
| `plan_cloud_G10_1.log` | `code_review_cloud_G10_1.log` | FAIL | evaluator session 규모가 lifecycle publication quiet wait를 소진하는 Required R1을 확인했다. |
| `plan_cloud_G10_2.log` | `code_review_cloud_G10_2.log` | 후속 필요 | lifecycle recovery는 복구했으나 C09 evaluator-owned session의 producer token false positive를 확인했다. |
| `plan_cloud_G10_3.log` | `code_review_cloud_G10_3.log` | PASS | provenance-aware identity boundary, 19/19 집중 테스트, 460/460 전체 테스트, 2 scored/7 unscored 및 보고서를 검증했다. |
## 구현/정리 내용
- producer identity 검사를 frozen anonymous `input/`과 evaluator publication `output/`에 한정하고 fresh evaluator-owned `session/`은 provenance 검사에서 분리했다.
- input/output identity leakage, prompt/path/cell/attempt binding, secret scrubbing, lifecycle/receipt/digest, input freeze와 append-only score 경계를 유지했다.
- 보존 run `run-20260813T081326Z-4e1ac5152c6c`을 C05/C09 각 93점, 나머지 7개 unscored로 닫고 run-owned 및 `agent-test/dev/` 날짜별 보고서를 생성했다.
## 최종 검증
- `python3 -m unittest scripts.agent_benchmark.scoring_test` - PASS; 19 tests.
- `python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'` - PASS; 460 tests.
- `python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json` - PASS; manifest valid.
- `/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c` - PASS; unresolved=0, attempts=9, artifact_passed=2, artifact_failed=7.
- `/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py report --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c` - PASS; deterministic run report regenerated.
- `git diff --check -- . ':(exclude)agent-task/archive/**'` - PASS; no output.
- report/count/digest audit - PASS; 9 result rows, 2 scored, 7 unscored, 43/43 contained links, 25 run identities, and recorded score-result digests matched.
## 잔여 Nit
- 없음
## 후속 작업
- 없음

View file

@ -0,0 +1,184 @@
<!-- task=m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report plan=0 tag=TEST milestone-task=claude-standalone,gemini-standalone,gpt-standalone,gemini-hybrid,gpt-hybrid,objective-validation,quality-scoring,performance-usage,benchmark-report -->
# Execute, score, and publish the final C01-C09 benchmark
## For the Implementing Agent
Wait for predecessor 14 to archive PASS, then execute this packet through the deterministic benchmark CLI. The user approved uninterrupted work through final benchmark completion and explicitly requested dispatcher task-loop execution. This authorizes exactly one new scored C01-C09 `run` identity with repetitions=1. It does not authorize caller-attempt retry, `resume`, `--retry-failed`, a second scored run, ad-hoc caller/provider/evaluator calls, model/route/effort substitution, manual run-state edits, deletion of evidence, or success-only selection. Scoring-only failures may use the public `--retry-scoring-failed` path for at most three total score invocations per affected run; every score attempt remains append-only. Fill the paired review evidence and leave finalization to official review.
## Background
Task 13 preserved a terminal direct run but never allocated C01-C09 because the former gate demanded all four direct axes 5/5. Task 14 repairs measurement/runtime compatibility, deploys the exact source, and replaces that gate with terminal-evidence admission. This packet consumes that qualified environment once, preserves all nine outcomes whether successful, failed, or timed out, blindly scores only eligible artifacts, and publishes the dated comparison report.
## Archive Evidence Snapshot
- Required predecessor: `agent-task/archive/2026/08/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/complete.log`. It does not yet exist at plan creation; the dispatcher must keep this packet dependency-waiting until it does.
- Predecessor 13: `agent-task/archive/2026/08/m-iop-one-shot-agent-model-comparison/13+12_scored_benchmark_result/complete.log` and its exact direct run remain immutable historical evidence.
- All existing `agent-test/runs/bench-02/run-*` roots are prior attempts. The result for this packet must be a distinct run ID issued by its one `run` command.
- `/tmp/iop-bench-13-env` is the approved benchmark-only environment wrapper and must be invoked with `/bin/bash`; never print its environment or protected files.
## Analysis
### Files Read
- `agent-ops/skills/project/iop-agent-comparison-benchmark/SKILL.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/iop-one-shot-agent-model-comparison.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-one-shot-agent-model-comparison/SDD.md`
- `agent-spec/testing/agent-comparison-benchmark.md`
- `agent-test/local/rules.md`
- `agent-test/dev/rules.md`
- `docs/agent-comparison-benchmark-dev-guide.md`
- `scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json`
- `scripts/agent_comparison_benchmark.py`
- `scripts/agent_benchmark/reporting.py`
- predecessor 13's directly linked completion/plan/review evidence
### SDD Criteria
- D06: one new run ID, repetitions=1, clean workspace and fresh caller session for every cell.
- D10: retain all failure/timeout evidence and never replace it with another run. A score retry is a separate evaluator attempt, not a caller rerun.
- S04-S11: the same run ID must connect lifecycle, workspace, web validation, timing/usage, blind score status, and report rows.
- S12: publish a dated Markdown report with conditions, versions, failures, limitations, and contained raw-evidence links.
### Verification Context
- Live admission requires predecessor 14's exact released source, `ready=5` direct qualification, terminal five-slot evidence, no exhausted browser/CDP infrastructure block, 4/4 connected Nodes, and healthy/available provider snapshots.
- C01-C09 preflight must return `ready=9` before attempt allocation. Preflight is not a scored attempt.
- The immutable manifest checksum, order seed, timeout, evaluator, rubric, fixture, route/model/effort, and repetitions must not change.
- The one `run` command succeeds operationally when all nine slots have complete terminal evidence (`unresolved=0`), even if product/harness/process/artifact axes contain failures. Those rows become `unscored` where ineligible.
- Score invocation 1 is normal. Only an explicit `scoring_failed` result permits invocations 2 and 3 with `--retry-scoring-failed`; stop after the third unresolved scoring failure for official review. Never retry an `unscored` product result.
- Confidence is high in state/report determinism after task 12/14; live model quality and timing are deliberately unknown benchmark outputs.
### Test Coverage Gaps
- Deterministic tests cannot replace nine live caller observations or fresh blind evaluation.
- The reviewer must validate the run tree structure, contained pointers, anonymous score inputs, and exact report projection, not merely trust CLI exit codes.
### Symbol References
None; this packet is execution/evidence publication and must not change benchmark implementation.
### Split Judgment
This task is indivisible after admission: one run ID must flow through status, scoring, report, and dated publication. It is split from packet 14 because source deployment/qualification must complete before the single scored allocation. Directory dependency `15+14` is authoritative.
### Scope Rationale
- Include only deterministic tests/preflight, one C01-C09 run, status, bounded scoring retry, run-owned report, evidence audit, and dated publication.
- Exclude product/runtime/harness source, manifests, fixtures, rubric, route configuration, credentials, normal caller configuration, old runs, and any second scored cycle.
- The dispatcher schedules/monitors because the user explicitly requested it; it is not a caller continuation path. All caller and evaluator work remains CLI-owned.
### Final Routing
- `evaluation_mode=first-pass`; finalizer `pair` route already selected build `cloud/G09` and review `cloud/G09` with `plan=0`.
- Loop risks are temporal external state, expensive single-run identity, variant products, and scoring availability. Official review must reconstruct counts from durable evidence.
## Implementation Checklist
- [ ] [TEST-1] Verify predecessor 14 PASS, exact released runtime/source identity, immutable manifests, protected-file modes, caller versions, 4/4 Nodes, provider health, and `ready=9` without exposing secrets.
- [ ] [TEST-2] Invoke exactly one fresh C01-C09 `run`, capture its issued ID, and require nine complete terminal slots with `unresolved=0`; preserve every product/harness/process/artifact outcome without resume or caller retry.
- [ ] [TEST-3] Query status for that exact run and score eligible artifacts blindly; use `--retry-scoring-failed` only when required and at most three total score invocations, preserving every score attempt.
- [ ] [TEST-4] Generate the deterministic run-owned report, audit identities/counts/timing/usage/links/isolation, and publish `agent-test/dev/iop-one-shot-agent-comparison-2026-08-13.md` with prior-run relationship, failures, limitations, and raw evidence pointers.
- [ ] [TEST-5] Rerun deterministic verification and prove old runs, testbed, normal caller subscriptions, credentials, manifests, and released runtime were not mutated by benchmark execution.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
### [TEST-1] Final admission
#### Modified Files and Checklist
- [ ] `agent-task/m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/CODE_REVIEW-cloud-G09.md`: record predecessor/source/runtime/preflight identities and secret-safe output.
#### Verification
```bash
python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py preflight --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
```
Expected: deterministic tests pass, manifest is unchanged/valid, runtime identity matches predecessor 14, and preflight reports `ready=9`.
### [TEST-2] Single scored execution
#### Modified Files and Checklist
- [ ] `agent-task/m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/CODE_REVIEW-cloud-G09.md`: record the single command, exit, issued ID, and all terminal axes.
#### Verification
```bash
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py run --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
```
Expected: exactly one new run ID, nine attempt slots and web validations, `unresolved=0`, `running=0`, `interrupted=0`. Independent failures remain retained results and do not allocate another run.
### [TEST-3] Status and blind scoring
#### Modified Files and Checklist
- [ ] `agent-task/m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/CODE_REVIEW-cloud-G09.md`: record exact dynamic commands, exits, score IDs/counts, eligibility, and retry count.
#### Verification
```bash
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id <issued-run-id>
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py score --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id <issued-run-id>
```
On explicit `scoring_failed` only, repeat `score` with `--retry-scoring-failed` no more than twice. Expected: every cell is closed as `scored` or `unscored`, `scoring_failed=0`, and no identity leaks into blind evaluator inputs.
### [TEST-4] Reports and evidence audit
#### Modified Files and Checklist
- [ ] `agent-test/dev/iop-one-shot-agent-comparison-2026-08-13.md`: publish exact conditions/outcomes/metrics/scores/failures/limitations and contained raw links for the issued run.
- [ ] `agent-task/m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/CODE_REVIEW-cloud-G09.md`: record report command/output, run-tree audit, and protected-value hit count only.
#### Verification
```bash
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py report --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id <issued-run-id>
```
Expected: `agent-test/runs/bench-02/<issued-run-id>/report.md` is deterministic; the dated report cites that same ID, distinguishes unscored from zero, labels unavailable metrics, and includes prior-failure relationship/limitations.
### [TEST-5] Final integrity
#### Modified Files and Checklist
- [ ] `agent-task/m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/CODE_REVIEW-cloud-G09.md`: record fresh test output and immutable/isolation audit.
#### Verification
```bash
python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
git diff --check -- . ':(exclude)agent-task/archive/**'
git status --short --branch
```
Expected: deterministic checks pass; only the dated report and active task evidence are intentional post-release changes; old runs/testbed/subscription/credentials/manifests/runtime are unchanged.
## Modified Files Summary
| File | Items |
|---|---|
| `agent-test/dev/iop-one-shot-agent-comparison-2026-08-13.md` | TEST-4 |
| `agent-task/m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/CODE_REVIEW-cloud-G09.md` | TEST-1, TEST-2, TEST-3, TEST-4, TEST-5 |
## Dependencies and Execution Order
- Wait for `agent-task/archive/2026/08/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/complete.log`; the active predecessor does not satisfy the dependency.
- Execute TEST-1 → TEST-2 → TEST-3 → TEST-4 → TEST-5. No later step may change the issued run ID.
## Final Verification
```bash
python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id <issued-run-id>
git diff --check -- . ':(exclude)agent-task/archive/**'
git status --short --branch
```
Expected: exact run has nine terminal slots and closed scoring states, both reports point to it, no second run/caller retry exists, and isolation audits pass. After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.

View file

@ -0,0 +1,191 @@
<!-- task=m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report plan=1 tag=TEST milestone-task=claude-standalone,gemini-standalone,gpt-standalone,gemini-hybrid,gpt-hybrid,objective-validation,quality-scoring,performance-usage,benchmark-report -->
# Rebase the final benchmark on a three-minute micro implementation
## For the Implementing Agent
Replace the oversized landing-page fixture with a compact implementation task sized for roughly three minutes per execution cell, fix the confirmed same-caller blind-scoring false positive, and then produce the final C01-C09 result through the deterministic CLI. Fill every implementation-owned section in the paired review and leave finalization to official review. If blocked, record only exact evidence and the resume condition; do not ask the user, create control-plane stop files, archive the pair, or write `complete.log`.
The user explicitly replaced plan 0 while it was running. This plan authorizes exactly one new `run` identity after the changed manifest validates and preflight is `ready=9`. Do not resume or retry caller attempts, allocate a second run, substitute caller/route/model/effort, edit run state, delete prior evidence, or select only successful results. A durable `scoring_failed` result alone may use `--retry-scoring-failed`, with at most three total score invocations for the affected run.
## Background
Plan 0's fixture asked every cell to build seven page sections, two-image composition, navigation, responsive behavior, accessibility, and JavaScript under a 300-second execution limit. The harness executes the nine C01-C09 slots sequentially, so that definition was not a three-minute benchmark and produced a 20-minute-class run.
The interrupted worker preserved `run-20260813T071816Z-755d136c3e2b` with nine terminal slots and no caller retry. One eligible artifact reached a valid evaluator worksheet with total 92, but the controller could not publish a durable score result. Read-only diagnosis proved 5,370 identity-scan hits when the producer and evaluator were both Codex and zero hits when the evaluator-shared caller token `codex` was excluded. The scan correctly exempts shared evaluator route/model/effort already, but not the shared caller name. Plan 0 is diagnostic evidence only and must not be reported as the final comparison.
## Archive Evidence Snapshot
- Superseded pair: `plan_cloud_G09_0.log` and `code_review_cloud_G09_0.log` in this task directory. It authorized and consumed one old-manifest run before the user changed scope.
- Preserved diagnostic run: `agent-test/runs/bench-02/run-20260813T071816Z-755d136c3e2b`. It is immutable and must not be scored, resumed, reported, deleted, or presented as the final result.
- Required predecessor PASS: `agent-task/archive/2026/08/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/complete.log`.
- `/tmp/iop-bench-13-env` remains the approved benchmark-only environment wrapper and must be invoked with `/bin/bash`; never print its environment or protected values.
## Analysis
### Files Read
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-ops/skills/project/iop-agent-comparison-benchmark/SKILL.md`
- `agent-test/local/rules.md`
- `agent-test/local/testing-smoke.md`
- `scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json`
- `scripts/fixtures/agent-comparison-benchmark/prompt.md`
- `scripts/fixtures/agent-comparison-benchmark/reference.txt`
- `scripts/agent_benchmark/manifest.py`
- `scripts/agent_benchmark/manifest_test.py`
- `scripts/agent_benchmark/scoring.py`
- `scripts/agent_benchmark/scoring_test.py`
### Acceptance and Design
- Keep the complete C01-C09 matrix, repetitions=1, fresh sessions, isolated workspaces, two viewports, two local images, evaluator, rubric, and append-only evidence policy.
- Reduce only the implementation workload: one compact responsive product card composed of a small header, one hero area, one CTA, both visible local images, and one tiny progressive-enhancement toggle. Keep exactly `index.html`, `styles.css`, and `script.js`; remove the old feature/workflow/testimonial/pricing/footer-section burden.
- Set `timeout.run_seconds=180`. This is a per-cell execution ceiling. Because `run_slots` is intentionally sequential, do not claim the complete nine-cell wall clock is three minutes and do not add parallel scheduling in this packet.
- Change fixture version and checksum-bound reference content so new runs cannot be opened with the superseded manifest digest. Preserve the existing fixture directory and assets instead of creating a new benchmark tree.
- Treat a producer caller token as evaluator-shared when `cell.caller == manifest.evaluator.caller`, just as shared evaluator route/model/effort tokens are already exempt. The opaque cell id, producer-only routes/models/efforts, and attempt path remain prohibited everywhere in evaluator-visible evidence.
- Add a focused regression proving `.codex`/Codex evaluator-owned state is accepted for a Codex-produced artifact while the opaque producer cell id still fails closed.
### SDD Criteria
- D06/D10: every new slot has one fresh session and one attempt; old and failed evidence is retained without retry.
- S04-S11: the same new run ID connects lifecycle, workspace, web gates, timing/usage, blind score, and report rows.
- S12: publish a dated report with conditions, failures, limitations, and contained raw links; state the sequential nine-cell timing limitation.
### Test Coverage
- `manifest_test.py` locks the tracked manifest's timeout, fixture version/content mapping, checksum, viewports, matrix, and deterministic order.
- `scoring_test.py` already covers producer caller/cell leaks, evaluator-shared route/model bindings, raw filenames, append-only scoring retries, and strict worksheet publication. Add the missing same-caller case.
- The full `scripts/agent_benchmark` suite covers workspace materialization, browser/CDP gates, adapters, lifecycle, measurement, reporting, and CLI integration.
### Symbol References
- `_identity_values` is consumed by blind path/input/prompt and evaluator-visible tree scans inside `scoring.py`; its exact-token change must preserve all non-shared caller behavior.
- The tracked manifest constants are asserted directly in `ManifestValidationTest.test_iop_one_shot_manifest_locks_benchmark_readiness`.
### Split Judgment
Keep one atomic packet. Fixture duration, manifest digest/tests, scoring identity semantics, and the one final live run must agree before any result is publishable; splitting would allow an invalid intermediate benchmark contract or another wasted live run.
### Routing Signals
- `evaluation_mode=isolated-reassessment`
- Closures are true for build and review; `evidence_integrity_failure=true` because plan 0 produced a worksheet without a durable terminal score result.
- Positive loop risks: `temporal_state`, `boundary_contract`, `variant_product`; `large_indivisible_context=false`, `review_rework_count=0`.
- Finalizer output: build `cloud/G10`, review `cloud/G10`, files `PLAN-cloud-G10.md` and `CODE_REVIEW-cloud-G10.md`.
## Implementation Checklist
- [ ] [TEST-1] Replace the old seven-section prompt/reference with the compact one-card task, set the execution ceiling to 180 seconds, update fixture version/checksum and exact manifest regression assertions, and prove the full C01-C09 matrix is unchanged.
- [ ] [TEST-2] Fix same-caller blind identity classification in `_identity_values` and add focused tests proving evaluator-owned Codex state is allowed only when the producer caller is also Codex while cell/path/producer-only identities still fail closed.
- [ ] [TEST-3] Run focused and full deterministic verification, validate the changed manifest, and confirm no test invokes real providers.
- [ ] [TEST-4] Run secret-safe live preflight and then exactly one fresh changed-manifest C01-C09 `run`; capture the new run ID and require nine terminal slots with `unresolved=0` without caller resume/retry.
- [ ] [TEST-5] Score the exact new run, use scoring-only retry solely after durable `scoring_failed` and within the three-invocation ceiling, generate the deterministic run report, publish the dated report, and audit identities/counts/links/isolation.
- [ ] Fill implementation-owned sections in `CODE_REVIEW-cloud-G10.md` with exact commands, exits, bounded outputs, changed files, new run/score/report identities, omissions, and remaining risks.
### [TEST-1] Three-minute micro fixture
#### Modified Files
- `scripts/fixtures/agent-comparison-benchmark/prompt.md`
- `scripts/fixtures/agent-comparison-benchmark/reference.txt`
- `scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json`
- `scripts/agent_benchmark/manifest_test.py`
#### Verification
```bash
python3 -m unittest scripts.agent_benchmark.manifest_test
python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
```
Expected: manifest is valid, `run_seconds=180`, fixture version/content/checksum match, and exact C01-C09 cells/routes remain unchanged.
### [TEST-2] Same-caller blind scoring
#### Modified Files
- `scripts/agent_benchmark/scoring.py`
- `scripts/agent_benchmark/scoring_test.py`
#### Verification
```bash
python3 -m unittest scripts.agent_benchmark.scoring_test
```
Expected: shared Codex caller state does not create a false leak; non-shared callers, cell IDs, producer-only bindings, paths, and binary/raw filename cases still fail closed.
### [TEST-3] Deterministic regression
#### Modified Files
- `agent-task/m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/CODE_REVIEW-cloud-G10.md`
#### Verification
```bash
python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
git diff --check -- . ':(exclude)agent-task/archive/**'
```
Expected: all deterministic tests pass without external model/provider invocation and the worktree patch is clean.
### [TEST-4] One new live run
#### Verification
```bash
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py preflight --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py run --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id <new-run-id>
```
Expected: preflight `ready=9`; exactly one new run ID; nine retained terminal slots; `unresolved=0`, `running=0`, `interrupted=0`. Product/artifact failures remain valid measured outcomes.
### [TEST-5] Scoring and report
#### Modified Files
- `agent-test/dev/iop-one-shot-agent-comparison-2026-08-13.md`
- `agent-task/m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/CODE_REVIEW-cloud-G10.md`
#### Verification
```bash
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py score --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id <new-run-id>
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py report --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id <new-run-id>
```
Expected: every slot is `scored` or `unscored`, `scoring_failed=0`, report links stay inside the new run, failed rows are retained, and the dated report explicitly distinguishes the three-minute per-cell task ceiling from total sequential matrix wall time.
## Modified Files Summary
| File | Items |
|---|---|
| `scripts/fixtures/agent-comparison-benchmark/prompt.md` | TEST-1 |
| `scripts/fixtures/agent-comparison-benchmark/reference.txt` | TEST-1 |
| `scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json` | TEST-1 |
| `scripts/agent_benchmark/manifest_test.py` | TEST-1 |
| `scripts/agent_benchmark/scoring.py` | TEST-2 |
| `scripts/agent_benchmark/scoring_test.py` | TEST-2 |
| `agent-test/dev/iop-one-shot-agent-comparison-2026-08-13.md` | TEST-5 |
| `agent-task/m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/CODE_REVIEW-cloud-G10.md` | TEST-1..5 |
## Dependencies and Execution Order
- Predecessor 14 PASS is already archived at the exact path above.
- Execute TEST-1 → TEST-2 → TEST-3 before any live allocation, then TEST-4 → TEST-5 using only the new run ID.
- Do not change prompt/reference/manifest/scoring source after allocating the new run. Any pre-run failure is fixed and revalidated; any post-allocation caller failure is retained without a second run.
## Final Verification
```bash
python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id <new-run-id>
git diff --check -- . ':(exclude)agent-task/archive/**'
git status --short --branch
```
Expected: deterministic checks pass; the new manifest is immutable and valid; the exact new run is terminal with closed scoring/report evidence; the diagnostic run and testbed remain unchanged.

View file

@ -0,0 +1,282 @@
<!-- task=m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report plan=2 tag=REVIEW_TEST milestone-task=claude-standalone,gemini-standalone,gpt-standalone,gemini-hybrid,gpt-hybrid,objective-validation,quality-scoring,performance-usage,benchmark-report -->
# Bound scoring quiescence and finish the preserved benchmark report
## For the Implementing Agent
Implement Required R1 exactly as selected below, run every deterministic check before any live score command, then close scoring and reporting on the preserved run only. Fill all implementation-owned sections in `CODE_REVIEW-cloud-G10.md`, keep both active files in place, and report ready for review. If blocked, record exact commands, output, and the resume condition only; do not ask the user, create stop files, classify next state, archive logs, write `complete.log`, run/resume callers, or allocate another run.
## Background
The compact fixture and same-caller identity change pass all 460 deterministic benchmark tests, and run `run-20260813T081326Z-4e1ac5152c6c` has nine terminal slots. C05 also has a successful evaluator lifecycle and valid 94-point worksheet, but scoring finalization scans 5,378 evaluator-visible files under a two-second deadline and exits before appending `result.json`. Fix that bounded publication wait, preserve every existing byte, finish C05/C09 scoring, and publish the deterministic and dated reports.
## Archive Evidence Snapshot
- Failed review: `code_review_cloud_G10_1.log`; verdict `FAIL`, Required R1 only, Suggested/Nit none.
- Closed plan: `plan_cloud_G10_1.log`; TEST-1 through TEST-4 passed, TEST-5 scoring/report remained incomplete.
- Earlier diagnostic pair: `plan_cloud_G09_0.log`, `code_review_cloud_G09_0.log`.
- Preserved final run: `agent-test/runs/bench-02/run-20260813T081326Z-4e1ac5152c6c`; nine terminal attempts, seven `unscored.json`, C05 `score-000001` allocation/input/runner/success lifecycle/94-point worksheet, zero score results, zero reports.
- Predecessor 14 PASS: `agent-task/archive/2026/08/m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/complete.log`.
## Finding Resolution Map
| Finding | Reviewer evidence | Root cause | Selected fix | Mode | Changed precondition | Acceptance commands |
|---|---|---|---|---|---|---|
| Required R1 | `_wait_post_cleanup_quiet` on the retained 5,378-file blind tree raises `evaluator lifecycle publication is incomplete` after 2.591s while allocation, input, runner, receipt, lifecycle, worksheet, identity scan, and post-tree checks pass. | `scripts/agent_benchmark/scoring.py:848-905` snapshots all of `blind_root`; evaluator session/plugin/cache traversal consumes the fixed deadline before `_recover_runner` can return and append a score result. | Scope the quiet snapshot to the evaluator `output/` publication surface, raise the bounded maximum wait from 2 seconds to 300 seconds, retain strict lifecycle/receipt/stable-digest checks and cleanup, add a large/mutating-session recovery regression, then finish only the preserved run and publish both reports. | `direct-fix` | The recovery oracle no longer depends on unrelated evaluator session tree size or churn; normal stable output returns immediately after the quiet interval, while incomplete publication remains fail-closed after at most 300 seconds. | `python3 -m unittest scripts.agent_benchmark.scoring_test`; full benchmark suite; manifest validation; preserved-run score/status/report commands; final artifact audit. |
## Analysis
### Files Read
- `scripts/agent_benchmark/scoring.py`
- `scripts/agent_benchmark/scoring_test.py`
- `scripts/agent_benchmark/live_iop.py`
- `scripts/agent_benchmark/reporting.py`
- `scripts/agent_comparison_benchmark.py`
- `scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json`
- `agent-spec/testing/agent-comparison-benchmark.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/iop-one-shot-agent-model-comparison.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-one-shot-agent-model-comparison/SDD.md`
- `code_review_cloud_G10_1.log`
- `plan_cloud_G10_1.log`
### SDD Criteria
- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-one-shot-agent-model-comparison/SDD.md`, `[승인됨]`, 잠금 해제.
- Milestone tasks: `claude-standalone`, `gemini-standalone`, `gpt-standalone`, `gemini-hybrid`, `gpt-hybrid`, `objective-validation`, `quality-scoring`, `performance-usage`, `benchmark-report`.
- Target scenarios/evidence: S04-S09 retain the exact nine terminal execution and web evidence; S10 requires blind Codex rubric results without identity leakage; S11 requires run-bound timing/usage; S12 requires the dated report and contained raw links.
- The checklist therefore forbids caller rerun and run replacement, requires append-only closure of existing C05 scoring before C09 allocation, and makes successful score/report projection the final oracle.
### Verification Context
- Handoff source: `code_review_cloud_G10_1.log` with reviewer-run 132-test and 460-test passes, manifest validation, exact status, report exit 69, and read-only durable-state diagnostics.
- Local rules: deterministic unit/integration tests run without provider environment; live benchmark operations use `/bin/bash /tmp/iop-bench-13-env` without printing protected values.
- External verification preflight: current repo `/config/workspace/iop-s0`, branch `feature/iop-one-shot-agent-model-comparison`, preserved dirty implementation patch, Python 3.12.3 through the wrapper, exact manifest/run identity fixed above. The wrapper is a Bash script and is invoked through `/bin/bash`; executable mode is not required.
- Constraints: no caller `run`/`resume`, no new run, no state edit, no deletion, no route/model/effort substitution. Score invocation 1 must be normal so `_complete_interrupted` can append the existing C05 result; one `--retry-scoring-failed` invocation is allowed only if that invocation durably publishes C05 `scoring_failed`.
- Confidence: high. The failure reproduces from retained bytes and the selected fix has a deterministic regression boundary.
### Test Coverage Gaps
- Existing `test_receipt_only_recovery_waits_for_lifecycle_quiescence` covers delayed, changed, and missing lifecycle output on small trees, but not large or continuously changing evaluator session state.
- Add one regression under `scripts/agent_benchmark/scoring_test.py` that churns `session/` while stable lifecycle output is recovered; it must fail before the fix and pass after it.
- Existing full suite covers scoring retry, append-only evidence, input mutation, identity leakage, report projection, and live adapter boundaries.
### Symbol References
- No public or removed symbol.
- `_wait_post_cleanup_quiet` callers: `scripts/agent_benchmark/scoring.py:944`, `:962`, `:997`. Preserve the helper signature unless an internal publication-root parameter is required; update all three call sites and tests together.
### Split Judgment
Keep one packet. Recovery semantics, retained score closure, and report publication form one append-only invariant: a source fix is not independently complete until the preserved run projects a terminal score/report, while live scoring must not run before the deterministic fix passes.
### Scope Rationale
- Include only `scoring.py`, its regression test, the dated report, and active review evidence.
- Exclude fixture/manifest/scoring identity semantics already verified, caller adapters, runtime deployment, credentials, old runs, existing run bytes, and roadmap files.
- Do not modify `agent-test/runs/**`; official CLI operations alone may append evidence under the preserved run.
### Final Routing
- `evaluation_mode=isolated-reassessment`; `finalizer=finalize-task-policy.sh`, mode `pair`.
- Closures: build/review scope, context, verification, evidence, ownership, and decisions all true; no capability gap.
- Build scores `2+2+2+2+2=G10`, `grade-boundary`, `cloud`, `PLAN-cloud-G10.md`.
- Review scores `2+2+2+2+2=G10`, `official-review`, `cloud`, `CODE_REVIEW-cloud-G10.md`.
- `large_indivisible_context=false`; positive risks: `temporal_state`, `boundary_contract`, `variant_product` (3).
- `review_rework_count=1`, `evidence_integrity_failure=false`; no recovery-boundary override.
## Implementation Checklist
- [ ] [REVIEW_TEST-1] Scope lifecycle quiescence to evaluator output publication, set the bounded maximum wait to 300 seconds, and preserve lifecycle/receipt/digest and cleanup fail-closed checks.
- [ ] [REVIEW_TEST-2] Add a deterministic large/mutating-session recovery regression and keep delayed, changed, and missing output cases passing.
- [ ] [REVIEW_TEST-3] Run focused/full deterministic verification and manifest validation before any live score invocation.
- [ ] [REVIEW_TEST-4] On the preserved run only, invoke normal score once; use one retry flag only after durable `scoring_failed`; require C05/C09 scored, seven retained unscored rows, no blocked/failure result, and no caller/run allocation.
- [ ] [REVIEW_TEST-5] Generate the run-owned report, publish `agent-test/dev/iop-one-shot-agent-comparison-2026-08-13.md`, and audit exact run links, failures, three-minute per-cell/sequential limitation, metrics, scores, and append-only isolation.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
### [REVIEW_TEST-1] Bound lifecycle publication quiescence
#### Problem
`scripts/agent_benchmark/scoring.py:848-905` recursively snapshots the entire blind tree on every poll. `scripts/agent_benchmark/scoring.py:944-952` passes `blind_root`, so a 5,378-file Codex session tree consumes the two-second publication deadline despite already stable `output/lifecycle-journal.jsonl`, `output/lifecycle-result.json`, and cleanup receipt.
#### Solution
Before (`scripts/agent_benchmark/scoring.py:944-952`):
```python
stable_lifecycle = _wait_post_cleanup_quiet(
blind_root,
lifecycle_validator=lambda: _validate_lifecycle_binding(...),
)
```
After:
```python
stable_lifecycle = _wait_post_cleanup_quiet(
blind_root / "output",
lifecycle_validator=lambda: _validate_lifecycle_binding(...),
)
```
Apply the same publication-root rule at all three recovery call sites. Do not weaken `_validate_lifecycle_binding`, cleanup receipt validation, final digest comparison, socket cleanup, alias cleanup, input freezing, identity scanning, or post-tree digest validation.
Set `_POST_CLEANUP_TIMEOUT_SECONDS = 300.0`. This is a maximum deadline, not a mandatory delay: stable publication still returns after the existing quiet interval. If publication never becomes valid or stable, recovery must fail closed when the 300-second deadline expires.
#### Modified Files and Checklist
- [ ] `scripts/agent_benchmark/scoring.py`: use bounded evaluator output publication roots at all recovery waits and set the maximum publication deadline to 300 seconds.
#### Test Strategy
Regression required in REVIEW_TEST-2; no new public API test is needed.
#### Verification
```bash
python3 -m unittest scripts.agent_benchmark.scoring_test
```
Expected: all scoring tests pass without a provider invocation.
### [REVIEW_TEST-2] Lock large-session recovery behavior
#### Problem
`scripts/agent_benchmark/scoring_test.py:1227-1622` validates lifecycle publication and recovery only with small `session/` trees, so unrelated session churn can regress the output publication boundary undetected.
#### Solution
Extend `test_receipt_only_recovery_waits_for_lifecycle_quiescence` or add a focused sibling test. Create a stable, valid output lifecycle/receipt and a bounded background writer that repeatedly changes files under `session/`; call `_recover_runner`, require it to return valid lifecycle/receipt digests within the patched timeout, stop/join the writer, and prove existing evidence bytes remain unchanged. Retain the existing test cases that reject changed lifecycle output and missing publication.
#### Modified Files and Checklist
- [ ] `scripts/agent_benchmark/scoring_test.py`: add deterministic session-churn recovery regression and append-only assertions.
#### Test Strategy
Write the regression above. Use local files/threading only; no real provider, network, or benchmark CLI call.
#### Verification
```bash
python3 -m unittest scripts.agent_benchmark.scoring_test
```
Expected: the new regression and all existing scoring tests pass.
### [REVIEW_TEST-3] Deterministic gate before live continuation
#### Problem
The preserved run is expensive append-only state. A live score continuation before validating the fix could leave another interrupted score allocation.
#### Solution
Run focused and full tests, manifest validation, and diff hygiene first. If any fails, stop without score invocation and record the failure.
#### Modified Files and Checklist
- [ ] `CODE_REVIEW-cloud-G10.md`: record exact commands, exits, and bounded output.
#### Test Strategy
Use fresh unittest execution; unittest does not reuse cached results.
#### Verification
```bash
python3 -m unittest scripts.agent_benchmark.scoring_test
python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
git diff --check -- . ':(exclude)agent-task/archive/**'
```
Expected: all commands exit 0, 460 or more benchmark tests pass, and no external provider is invoked by tests.
### [REVIEW_TEST-4] Close preserved scoring append-only
#### Problem
C05 `score-000001` lacks `result.json`, C09 has no allocation, and seven ineligible rows are correctly retained as unscored.
#### Solution
After REVIEW_TEST-3 passes, run the normal public score command exactly once. It must recover and append the existing C05 result without allocating `score-000002`, then score C09. If and only if it durably publishes C05 as `scoring_failed`, run one explicit `--retry-scoring-failed`; otherwise do not use the flag. Never run/resume callers or create a new run. Require final `scored=2 unscored=7 scoring_failed=0 blocked=0`.
#### Modified Files and Checklist
- [ ] `CODE_REVIEW-cloud-G10.md`: record exact score commands, exits, counts, C05/C09 score ids, and append-only audit.
#### Test Strategy
Use the official public CLI only; no ad-hoc evaluator or manual state write.
#### Verification
```bash
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py score --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c
```
Expected: score exits 0 with `scored=2 unscored=7 scoring_failed=0 blocked=0`; status retains nine one-attempt terminal slots and `unresolved=0`.
### [REVIEW_TEST-5] Publish and audit reports
#### Problem
No run-owned `report.md` or dated comparison report exists, so S12 and the Milestone report tasks are incomplete.
#### Solution
Generate the deterministic report through the public CLI. Publish the exact run projection to `agent-test/dev/iop-one-shot-agent-comparison-2026-08-13.md`, including conditions/version, all nine rows, two scores and seven unscored reasons, timing/usage sources, failures, unavailable values, the 180-second per-cell versus sequential total limitation, previous diagnostic-run relationship, and contained raw links. Do not invent zeros or ranks for unscored rows.
#### Modified Files and Checklist
- [ ] `agent-test/dev/iop-one-shot-agent-comparison-2026-08-13.md`: publish the final dated comparison.
- [ ] `CODE_REVIEW-cloud-G10.md`: record report output/path and contained-link/isolation audit.
#### Test Strategy
Use deterministic report generation and bounded path/count audits; no new report unit test is needed because production reporting behavior is unchanged.
#### Verification
```bash
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py report --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c
test -f agent-test/runs/bench-02/run-20260813T081326Z-4e1ac5152c6c/report.md
test -f agent-test/dev/iop-one-shot-agent-comparison-2026-08-13.md
rg --sort path -n 'run-20260813T081326Z-4e1ac5152c6c|per-cell|sequential|unscored|scored' agent-test/dev/iop-one-shot-agent-comparison-2026-08-13.md
```
Expected: report exits 0, both files exist, and the dated report uses only the preserved run's contained evidence.
## Modified Files Summary
| File | Items |
|---|---|
| `scripts/agent_benchmark/scoring.py` | REVIEW_TEST-1 |
| `scripts/agent_benchmark/scoring_test.py` | REVIEW_TEST-2 |
| `agent-test/dev/iop-one-shot-agent-comparison-2026-08-13.md` | REVIEW_TEST-5 |
| `agent-task/m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/CODE_REVIEW-cloud-G10.md` | REVIEW_TEST-3..5 |
## Dependencies and Execution Order
Execute REVIEW_TEST-1 → REVIEW_TEST-2 → REVIEW_TEST-3. Only after all deterministic gates pass may REVIEW_TEST-4 invoke scoring; only after scoring closes may REVIEW_TEST-5 publish reports.
## Final Verification
```bash
python3 -m unittest scripts.agent_benchmark.scoring_test
python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py report --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c
git diff --check -- . ':(exclude)agent-task/archive/**'
git status --short --branch
```
Expected: deterministic tests and validation pass; status remains nine terminal one-attempt slots; scoring is two scored/seven unscored with no failure/block; run-owned and dated reports exist; no second run or caller retry exists; all changes stay within the declared write boundary.
After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.

View file

@ -0,0 +1,104 @@
<!-- task=m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report plan=3 tag=REVIEW_TEST milestone-task=claude-standalone,gemini-standalone,gpt-standalone,gemini-hybrid,gpt-hybrid,objective-validation,quality-scoring,performance-usage,benchmark-report -->
# Separate evaluator-owned session state from anonymous scoring evidence
## For the Implementing Agent
Fix only the confirmed C09 false positive, run every deterministic gate before live scoring, then continue the preserved run through the official CLI. Do not run or resume producer callers, allocate another run, edit/delete prior evidence, weaken input/prompt/output identity checks, or perform more than one newly authorized `--retry-scoring-failed` invocation. Fill the paired review and leave finalization to official review.
## Background
Plan 2 fixed lifecycle recovery by observing only evaluator `output/` and setting the maximum publication wait to 300 seconds. All 460 deterministic tests passed. The preserved run then durably reached `scored=1 unscored=7 scoring_failed=1`: C05 scored 93, while C09 twice produced valid 94/95 worksheets but failed `evaluator_output_leak`.
Read-only diagnosis reproduced the scanner failure. C09 producer-only tokens `gpt-5.6-terra` and `high` are absent from anonymous input and worksheet content but occur in the fresh Codex evaluator's own `session/.codex` model documentation and plugin cache. The session is created empty by the controller and populated only by the evaluator. Scanning it as producer evidence is therefore a provenance error.
## Evidence Snapshot
- Prior pair: `plan_cloud_G10_2.log`, `code_review_cloud_G10_2.log` in this task directory.
- Preserved run only: `agent-test/runs/bench-02/run-20260813T081326Z-4e1ac5152c6c`.
- Current durable state: C05 `score-000002` scored 93; C09 `score-000001` and `score-000002` are `scoring_failed/evaluator_output_leak`; seven rows remain unscored; nine producer attempts and one run identity remain fixed.
- C09 retained worksheets are valid totals 94 and 95 but are not publishable scores.
## Required Fix
Apply producer-identity scanning to the provenance-bearing anonymous `input/` and evaluator publication `output/` trees, not to the fresh evaluator-owned `session/` tree. Keep all of these boundaries strict:
- input materialization, path, prompt, and frozen-input identity checks;
- output filenames and contents, including worksheet and lifecycle publication;
- secret scrubbing across input/session/output;
- invalid file-type handling, lifecycle/receipt/digest checks, and post-tree binding;
- cell ID, attempt path, and producer-only route/model/effort checks on producer evidence.
Do not globally exempt `gpt-5.6-terra`, `high`, `.codex`, or arbitrary output content. The allowance is provenance-based and limited to controller-created fresh evaluator session state.
## Implementation Checklist
- [x] [REVIEW_TEST-1] Refactor the post-evaluator identity scan to exclude only evaluator-owned `session/` while retaining input and output scans.
- [x] [REVIEW_TEST-2] Add regression coverage proving producer tokens in evaluator session state are accepted and the same tokens in input/output still fail closed.
- [x] [REVIEW_TEST-3] Run focused and full deterministic tests, manifest validation, and diff hygiene before live scoring.
- [x] [REVIEW_TEST-4] Invoke exactly one newly authorized `--retry-scoring-failed` on the preserved run; require C09 scored, C05 unchanged, and final `scored=2 unscored=7 scoring_failed=0 blocked=0`.
- [x] [REVIEW_TEST-5] Generate the run-owned report and publish `agent-test/dev/iop-one-shot-agent-comparison-2026-08-13.md`, preserving all nine outcomes and the per-cell/sequential timing limitation.
- [x] Fill all implementation-owned sections in `CODE_REVIEW-cloud-G10.md` with exact commands, exits, score IDs, counts, and isolation audit.
### REVIEW_TEST-1/2 — Provenance-aware identity boundary
#### Modified Files
- `scripts/agent_benchmark/scoring.py`
- `scripts/agent_benchmark/scoring_test.py`
#### Verification
```bash
python3 -m unittest scripts.agent_benchmark.scoring_test
```
Expected: evaluator-created session state containing C09 producer model/effort strings is accepted; producer identities in anonymous input, prompt, output path/content, worksheet, cell id, attempt path, route, model, and effort remain rejected.
### REVIEW_TEST-3 — Deterministic gate
```bash
python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
git diff --check -- . ':(exclude)agent-task/archive/**'
```
Expected: all checks pass without live provider invocation. On any failure, stop before scoring.
### REVIEW_TEST-4 — One preserved-run retry
```bash
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py score --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c --retry-scoring-failed
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c
```
Expected: only C09 receives the next append-only score allocation; final counts are exactly two scored and seven unscored, with no scoring failure, producer retry, or new run.
### REVIEW_TEST-5 — Reports
```bash
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py report --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c
```
Publish the deterministic projection to the dated report without inventing values or ranks for unscored rows. All raw links must remain inside the preserved run.
## Modified Files Summary
| File | Items |
|---|---|
| `scripts/agent_benchmark/scoring.py` | REVIEW_TEST-1 |
| `scripts/agent_benchmark/scoring_test.py` | REVIEW_TEST-2 |
| `agent-test/dev/iop-one-shot-agent-comparison-2026-08-13.md` | REVIEW_TEST-5 |
| `agent-task/m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/CODE_REVIEW-cloud-G10.md` | REVIEW_TEST-1..5 |
## Final Verification
```bash
python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
python3 scripts/agent_comparison_benchmark.py validate --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
/bin/bash /tmp/iop-bench-13-env python3 scripts/agent_comparison_benchmark.py status --manifest scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json --run-id run-20260813T081326Z-4e1ac5152c6c
git diff --check -- . ':(exclude)agent-task/archive/**'
git status --short --branch
```
Expected: two scored/seven unscored, report files present, nine original producer attempts, one run identity, no evidence rewrite, and no active scoring failure.

View file

@ -0,0 +1,42 @@
# 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 08:48:56 KST | START | m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/PLAN-cloud-G09.md | 0 | worker | 0 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T084856+0900__m-iop-one-shot-agent-model-comparison__14__13_runtime_compatibility_and_admission__p0__worker__a00/locator.json |
| 2 | 26-08-13 09:17:53 KST | FINISH | m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/PLAN-cloud-G09.md | 0 | worker | 0 | codex/gpt-5.6-sol xhigh | failed:generic-error:0 | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T084856+0900__m-iop-one-shot-agent-model-comparison__14__13_runtime_compatibility_and_admission__p0__worker__a00/locator.json |
| 3 | 26-08-13 09:17:55 KST | START | m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/PLAN-cloud-G09.md | 0 | worker | 1 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T091755+0900__m-iop-one-shot-agent-model-comparison__14__13_runtime_compatibility_and_admission__p0__worker__a01/locator.json |
| 4 | 26-08-13 09:24:09 KST | FINISH | m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/PLAN-cloud-G09.md | 0 | worker | 1 | codex/gpt-5.6-sol xhigh | succeeded:0 | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T091755+0900__m-iop-one-shot-agent-model-comparison__14__13_runtime_compatibility_and_admission__p0__worker__a01/locator.json |
| 5 | 26-08-13 09:24:10 KST | START | m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/CODE_REVIEW-cloud-G09.md | 0 | review | 0 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T092410+0900__m-iop-one-shot-agent-model-comparison__14__13_runtime_compatibility_and_admission__p0__review__a00/locator.json |
| 6 | 26-08-13 10:18:29 KST | FINISH | m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/CODE_REVIEW-cloud-G09.md | 0 | review | 0 | codex/gpt-5.6-sol xhigh | succeeded:0 | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T092410+0900__m-iop-one-shot-agent-model-comparison__14__13_runtime_compatibility_and_admission__p0__review__a00/locator.json |
| 7 | 26-08-13 10:18:29 KST | START | m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/PLAN-cloud-G10.md | 1 | worker | 0 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T101829+0900__m-iop-one-shot-agent-model-comparison__14__13_runtime_compatibility_and_admission__p1__worker__a00/locator.json |
| 8 | 26-08-13 11:35:08 KST | FINISH | m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/PLAN-cloud-G10.md | 1 | worker | 0 | codex/gpt-5.6-sol xhigh | failed:generic-error:0 | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T101829+0900__m-iop-one-shot-agent-model-comparison__14__13_runtime_compatibility_and_admission__p1__worker__a00/locator.json |
| 9 | 26-08-13 11:35:10 KST | START | m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/PLAN-cloud-G10.md | 1 | worker | 1 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T113510+0900__m-iop-one-shot-agent-model-comparison__14__13_runtime_compatibility_and_admission__p1__worker__a01/locator.json |
| 10 | 26-08-13 11:38:52 KST | FINISH | m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/PLAN-cloud-G10.md | 1 | worker | 1 | codex/gpt-5.6-sol xhigh | succeeded:0 | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T113510+0900__m-iop-one-shot-agent-model-comparison__14__13_runtime_compatibility_and_admission__p1__worker__a01/locator.json |
| 11 | 26-08-13 11:38:53 KST | START | m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/CODE_REVIEW-cloud-G10.md | 1 | review | 0 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T113852+0900__m-iop-one-shot-agent-model-comparison__14__13_runtime_compatibility_and_admission__p1__review__a00/locator.json |
| 12 | 26-08-13 11:55:04 KST | FINISH | m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/CODE_REVIEW-cloud-G10.md | 1 | review | 0 | codex/gpt-5.6-sol xhigh | succeeded:0 | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T113852+0900__m-iop-one-shot-agent-model-comparison__14__13_runtime_compatibility_and_admission__p1__review__a00/locator.json |
| 13 | 26-08-13 15:39:00 KST | START | m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/PLAN-cloud-G08.md | 2 | worker | 0 | codex/gpt-5.6-sol high | running | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T153900+0900__m-iop-one-shot-agent-model-comparison__14__13_runtime_compatibility_and_admission__p2__worker__a00/locator.json |
| 14 | 26-08-13 15:58:33 KST | FINISH | m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/PLAN-cloud-G08.md | 2 | worker | 0 | codex/gpt-5.6-sol high | succeeded:0 | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T153900+0900__m-iop-one-shot-agent-model-comparison__14__13_runtime_compatibility_and_admission__p2__worker__a00/locator.json |
| 15 | 26-08-13 15:58:34 KST | START | m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/CODE_REVIEW-cloud-G08.md | 2 | review | 0 | codex/gpt-5.6-sol high | running | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T155834+0900__m-iop-one-shot-agent-model-comparison__14__13_runtime_compatibility_and_admission__p2__review__a00/locator.json |
| 16 | 26-08-13 16:08:14 KST | FINISH | m-iop-one-shot-agent-model-comparison/14+13_runtime_compatibility_and_admission/CODE_REVIEW-cloud-G08.md | 2 | review | 0 | codex/gpt-5.6-sol high | succeeded:0 | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T155834+0900__m-iop-one-shot-agent-model-comparison__14__13_runtime_compatibility_and_admission__p2__review__a00/locator.json |
| 17 | 26-08-13 16:08:15 KST | START | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/PLAN-cloud-G09.md | 0 | worker | 0 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T160815+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p0__worker__a00/locator.json |
| 18 | 26-08-13 16:50:09 KST | FINISH | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/PLAN-cloud-G09.md | 0 | worker | 0 | codex/gpt-5.6-sol xhigh | failed:cancelled | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T160815+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p0__worker__a00/locator.json |
| 19 | 26-08-13 17:04:59 KST | START | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/PLAN-cloud-G10.md | 1 | worker | 0 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T170459+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p1__worker__a00/locator.json |
| 20 | 26-08-13 17:40:28 KST | FINISH | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/PLAN-cloud-G10.md | 1 | worker | 0 | codex/gpt-5.6-sol xhigh | failed:generic-error:0 | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T170459+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p1__worker__a00/locator.json |
| 21 | 26-08-13 17:40:30 KST | START | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/PLAN-cloud-G10.md | 1 | worker | 1 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T174030+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p1__worker__a01/locator.json |
| 22 | 26-08-13 17:45:12 KST | FINISH | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/PLAN-cloud-G10.md | 1 | worker | 1 | codex/gpt-5.6-sol xhigh | succeeded:0 | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T174030+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p1__worker__a01/locator.json |
| 23 | 26-08-13 17:45:12 KST | START | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/CODE_REVIEW-cloud-G10.md | 1 | review | 0 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T174512+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p1__review__a00/locator.json |
| 24 | 26-08-13 18:07:35 KST | FINISH | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/CODE_REVIEW-cloud-G10.md | 1 | review | 0 | codex/gpt-5.6-sol xhigh | succeeded:0 | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T174512+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p1__review__a00/locator.json |
| 25 | 26-08-13 18:07:35 KST | START | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/PLAN-cloud-G10.md | 2 | worker | 0 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T180735+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p2__worker__a00/locator.json |
| 26 | 26-08-13 18:26:25 KST | FINISH | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/PLAN-cloud-G10.md | 2 | worker | 0 | codex/gpt-5.6-sol xhigh | failed:generic-error:0 | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T180735+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p2__worker__a00/locator.json |
| 27 | 26-08-13 18:26:27 KST | START | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/PLAN-cloud-G10.md | 2 | worker | 1 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T182627+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p2__worker__a01/locator.json |
| 28 | 26-08-13 18:28:59 KST | FINISH | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/PLAN-cloud-G10.md | 2 | worker | 1 | codex/gpt-5.6-sol xhigh | failed:generic-error:0 | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T182627+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p2__worker__a01/locator.json |
| 29 | 26-08-13 18:29:03 KST | START | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/PLAN-cloud-G10.md | 2 | worker | 2 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T182903+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p2__worker__a02/locator.json |
| 30 | 26-08-13 18:36:43 KST | FINISH | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/PLAN-cloud-G10.md | 2 | worker | 2 | codex/gpt-5.6-sol xhigh | failed:generic-error:0 | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T182903+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p2__worker__a02/locator.json |
| 31 | 26-08-13 18:40:08 KST | START | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/PLAN-cloud-G10.md | 3 | worker | 0 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T184008+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p3__worker__a00/locator.json |
| 32 | 26-08-13 19:07:31 KST | FINISH | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/PLAN-cloud-G10.md | 3 | worker | 0 | codex/gpt-5.6-sol xhigh | failed:session-stall:0 | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T184008+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p3__worker__a00/locator.json |
| 33 | 26-08-13 19:07:34 KST | START | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/PLAN-cloud-G10.md | 3 | worker | 1 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T190733+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p3__worker__a01/locator.json |
| 34 | 26-08-13 19:23:41 KST | FINISH | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/PLAN-cloud-G10.md | 3 | worker | 1 | codex/gpt-5.6-sol xhigh | succeeded:0 | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T190733+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p3__worker__a01/locator.json |
| 35 | 26-08-13 19:23:42 KST | START | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/CODE_REVIEW-cloud-G10.md | 3 | review | 0 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T192342+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p3__review__a00/locator.json |
| 36 | 26-08-13 19:39:04 KST | FINISH | m-iop-one-shot-agent-model-comparison/15+14_scored_benchmark_and_report/CODE_REVIEW-cloud-G10.md | 3 | review | 0 | codex/gpt-5.6-sol xhigh | succeeded:0 | /config/workspace/iop-s0/.git/agent-task-dispatcher/runs/20260813T192342+0900__m-iop-one-shot-agent-model-comparison__15__14_scored_benchmark_and_report__p3__review__a00/locator.json |

View file

@ -0,0 +1,249 @@
<!-- task=m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness plan=0 tag=TEST milestone-task=repeat-guard,ops-evidence -->
# 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 root cause, scope, files, and dependency decisions as written. Do not choose another owner, narrow/expand the write boundary, or replace a fix with another verification attempt.
> 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, or classify the next state.
> Finalization (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.
## Overview
date=2026-08-13
task=m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness, plan=0, tag=TEST
## Archive Evidence Snapshot
- Dependency evidence: `agent-task/archive/2026/07/m-openai-compatible-output-validation-filters/03_repeat_guard_s07_remote_evidence/complete.log` is `PASS` and satisfies predecessor index `03` for this `04+03` packet.
- That completion proves the previous repeat-guard remote evidence and single-terminal validation passed for milestone task `repeat-guard`.
- It does not prove the Ornith logical-finish-without-`[DONE]`/EOF/`END` stall shape or the dev timeout-order rollout; those remain owned by this packet.
## For the Review Agent
> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section.
Compare implementation of each item against source files. Run the applicable verification commands directly and record fresh output in `Verification Results`; implementation-owned output is handoff evidence, not a substitute for reviewer verification. If implementation is present, repair missing or stale verification output instead of failing solely for insufficient recorded evidence. When verification exposes a defect, collect the necessary data, determine the exact root cause, and select one concrete fix before generating the follow-up plan; never delegate investigation or remedy selection to the worker.
Review completion means the following steps are finished:
1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals.
2. Archive `CODE_REVIEW-{review_lane}-{review_grade}.md` → `code_review_{review_lane}_{review_grade}_{review_log_number}.log` and `PLAN-{build_lane}-{build_grade}.md` → `plan_{build_lane}_{build_grade}_{plan_log_number}.log`.
3. If PASS, write `complete.log` and move active task directory to `agent-task/archive/YYYY/MM/{task_name}/`. If WARN/FAIL, fully write the next filesystem state required by the code-review skill.
4. If PASS and task group is `m-<milestone-slug>`, preserve the first-line `milestone-task` metadata in `complete.log` and report it for the runtime aggregation event. Roadmap state evaluation belongs to `sync-milestone-workstate`.
5. Check applicable `Review-Only Checklist` items at the final `.log` location before reporting.
---
## Implementation Item Completion
| Item | Status |
|------|---------|
| TEST-1 — Logical finish followed by transport stall | [x] |
| TEST-2 — Deterministic Lemonade adapter-to-Edge smoke | [ ] |
| TEST-3 — Dev Ornith timeout order and sanitized evidence | [ ] |
## Implementation Checklist
- [x] [TEST-1] Add exact Node and Edge logical-finish-without-transport-terminal regressions while preserving the existing terminal contract.
- [ ] [TEST-2] Extend the fake Lemonade end-to-end smoke with a bounded hang-after-finish fixture and sanitized single-terminal assertions.
- [ ] [TEST-3] Align both dev Ornith provider response-stall timeouts, perform a same-ref rollout, update the runbook, and capture sanitized live evidence.
- [x] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
## Review-Only Checklist
> **[REVIEW AGENT ONLY]** This checklist is used only by the review agent.
> Implementing agents must not modify or check this section.
- [x] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
- [x] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match.
- [x] Run applicable required verification and record fresh command/output; repair reviewer-reconstructable evidence gaps instead of forwarding them to another plan.
- [x] For every Required/Suggested finding, record reviewer-collected `Evidence`, exact `Root Cause`, and one `Selected Fix` with affected files/symbols/tests and acceptance commands before creating a follow-up plan.
- [x] Archive active `CODE_REVIEW-*-G??.md` to `code_review_{review_lane}_{review_grade}_{review_log_number}.log`.
- [x] Archive active `PLAN-*-G??.md` to `plan_{build_lane}_{build_grade}_{plan_log_number}.log`.
- [x] Verify that the Agent-Ops managed block in `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores `agent-roadmap/current.md`.
- [ ] If PASS, write `complete.log` based on `agent-ops/skills/common/code-review/templates/complete-log-template.md` and leave no active `.md` files.
- [ ] If PASS, move active task directory `agent-task/{task_name}/` to `agent-task/archive/YYYY/MM/{task_name}/` and update this checklist at the final archive path.
- [ ] If PASS and task group is `m-<milestone-slug>`, preserve and report `milestone-task` metadata for runtime aggregation, without modifying roadmap or directly calling `update-roadmap`.
- [ ] If PASS for split work, remove empty active parent `agent-task/{task_group}/` or verify it was kept due to remaining siblings/files.
- [x] If WARN/FAIL, write the next filesystem state matching code-review verdict and do not write `complete.log`.
## Deviations from Plan
- TEST-1 completed as planned. TEST-2's deterministic adapter-to-Edge fixture reproduced an actual existing runtime defect instead of passing as a missing-regression addition. Per the plan's explicit stop condition, no product runtime fix, TEST-3 documentation/config change, remote clean-sync/rebuild/deploy/restart, or live Ornith evidence collection was attempted.
- Full local and race qualification was not run after the TEST-2 blocker. Focused TEST-1 regressions, script syntax, and `git diff --check` were rerun so the completed bounded test work remains reviewable without presenting the packet as qualified.
- The planned inventory query binary could not execute from `/tmp` because that mount is non-executable (`Permission denied`). The same read-only inventory query was rebuilt under `.tmp/task-g09/` and confirmed the two intended Ornith providers; this did not change repository state or the TEST-3 stop decision.
## Key Design Decisions
- Kept `finish_reason` as provider wire/protocol state. The new Node test drives response-start plus a logical-finish BODY and proves only watchdog expiry creates the typed transport failure; late usage and `END` remain fenced.
- Added the Edge logical-finish matrix with a held pre-commit branch and an already-open post-commit branch. It asserts one alternate replay before commit, no replay after commit, hidden pending finish wire, no raw failure leakage, and exactly one sanitized terminal on the endpoint-aware path.
- Strengthened the normal Chat codec control with usage on the logical finish frame and an assertion that no Core terminal exists before `[DONE]`/physical finish.
- The fake Lemonade fixture uses request-context cancellation instead of an unbounded sleep, a 200 ms test-only provider stall timeout, count-based terminal assertions, post-cancel health control, and explicit fake-process reap. Its observed failure is kept as the regression/blocker rather than widening this test packet into an unreviewed production change.
- Exact blocker/root cause: with `openai.stream_evidence_gate.enabled` omitted/false, `buildOpenAITunnelStreamGateRuntime` selects `newOpenAITunnelEventSource` rather than `newOpenAITunnelEndpointEventSource` (`apps/edge/internal/openai/stream_gate_runtime.go`). Node correctly emits a typed `response_stalled` after 200 ms, but the tunnel sink's codec endpoint remains unbound. After the already-released logical finish opens the response, `openAITunnelReleaseSink.CommitTerminal` only writes the sanitized SSE error plus `[DONE]` when `endpointIsChat()` is true (`apps/edge/internal/openai/stream_gate_release_sink.go`), so it returns with no client terminal. Resume condition: a separately reviewed runtime fix must preserve endpoint-aware terminal rendering in semantic-disabled supported Chat traffic, after which this packet can rerun TEST-2 and only then proceed to TEST-3.
## Reviewer Checkpoints
- [ ] Confirm `finish_reason` remains wire/protocol state and does not create a Core terminal before `[DONE]` or tunnel `END`.
- [ ] Confirm Node expiry emits exactly one typed `response_stalled` and fences late usage/`END`.
- [ ] Confirm Edge pre-commit recovery replays once and exposes no pending/raw bytes; post-commit recovery emits one sanitized error plus `[DONE]` without replay.
- [ ] Confirm the fake Lemonade fixture is bounded, cancels cleanly, and retains the exact normal finish/usage/`[DONE]` control.
- [ ] Confirm both current Ornith provider entries use `response_stall_timeout_ms: 120000` with no route/capacity change and the Edge request timeout remains longer than the caller boundary.
- [ ] Confirm remote source/build/runtime identities match one clean latest `origin/dev` ref after full rebuild/restart.
- [ ] Confirm tracked evidence is sanitized and raw prompt/output exists only in ignored artifacts.
- [ ] Confirm only files in the plan's `Modified Files Summary` changed.
## Verification Results
### TEST-1 focused regressions
```bash
go test -count=1 ./apps/node/internal/node -run '^TestTunnelWatchdogFinishFrameWithoutEndStallsOnce$'
go test -count=1 ./apps/edge/internal/openai -run '^(TestOpenAIStallAfterLogicalFinishMatrix|TestOpenAITunnelCodecTerminalWire)$'
```
Fresh resumed-worker result on 2026-08-13 (exit 0 for each):
```text
ok iop/apps/node/internal/node 0.055s
ok iop/apps/edge/internal/openai 0.037s
```
Additional local checks (exit 0):
```text
bash -n scripts/e2e-openai-lemonade.sh
git diff --check
```
### TEST-2 deterministic end-to-end smoke
```bash
IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh
```
status=BLOCKED — the deterministic TEST-2 fixture exposed the existing semantic-disabled Chat terminal-rendering source defect described below.
Blocked, reproduced again by the resumed worker after the prior two runs (fresh exit 1). Sanitized fresh output:
```text
[openai-lemonade] hang fixture terminal counts prefix=1 finish=1 run_error=0 done=0
```
The prior ignored run artifacts confirmed, without copying raw bodies into tracked evidence, that Node observed `execution_path=provider_tunnel`, `attempt_fence=confirmed`, `idle_duration_ms=200`, and returned `provider response stalled`. The fake provider also observed request-context cancellation. The fresh count-only replay again showed that the client received neither the expected sanitized `run_error` nor `[DONE]`, matching the semantic-disabled endpoint-binding root cause recorded above.
Resume condition: Complete a separately reviewed runtime fix that preserves endpoint-aware Chat terminal rendering when semantic validation is disabled, then rerun TEST-2 successfully before starting TEST-3.
### TEST-3 remote rollout and live evidence
```bash
ssh toki@toki-labs.com '/bin/zsh -lc '\''cd /Users/toki/agent-work/iop-dev && git status --short --branch && git rev-parse HEAD && git rev-list --left-right --count HEAD...origin/dev && go version'\'''
```
Not run. TEST-2 exposed the actual source defect above, so the plan's stop condition prohibited starting the TEST-3 remote rollout. No dev config, process, binary, port, tracked runbook, or evidence log was changed. Resume only after the reviewed runtime fix makes TEST-2 pass locally.
### Full local qualification
```bash
go test -count=1 ./packages/go/execution ./packages/go/config ./apps/node/internal/node ./apps/edge/internal/openai
go test -race -count=1 ./apps/node/internal/node ./apps/edge/internal/openai
git diff --check
git status --short --branch
```
Not run as a completion qualification because TEST-2 is blocked by the source defect. `git diff --check` did pass. Intentional tracked changes at handoff:
```text
M apps/edge/internal/openai/stream_gate_pipeline_test.go
M apps/edge/internal/openai/stream_gate_stall_recovery_test.go
M apps/node/internal/node/liveness_watchdog_test.go
M scripts/e2e-openai-lemonade.sh
?? agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/
```
No TEST-3 documentation, dev runtime config, or tracked remote-evidence file was created. Active PLAN and CODE_REVIEW files remain in place as required.
### Reviewer verification (fresh, 2026-08-13)
The reviewer reran the applicable local commands from the active plan rather than relying on the implementation handoff. Focused regressions, script syntax, package qualification, race qualification, and diff hygiene all passed:
```text
$ go test -count=1 ./apps/node/internal/node -run '^TestTunnelWatchdogFinishFrameWithoutEndStallsOnce$'
ok iop/apps/node/internal/node 0.057s
$ go test -count=1 ./apps/edge/internal/openai -run '^(TestOpenAIStallAfterLogicalFinishMatrix|TestOpenAITunnelCodecTerminalWire)$'
ok iop/apps/edge/internal/openai 0.054s
$ bash -n scripts/e2e-openai-lemonade.sh
# exit 0, no output
$ go test -count=1 ./packages/go/execution ./packages/go/config ./apps/node/internal/node ./apps/edge/internal/openai
ok iop/packages/go/execution 0.035s
ok iop/packages/go/config 0.197s
ok iop/apps/node/internal/node 1.044s
ok iop/apps/edge/internal/openai 8.575s
$ go test -race -count=1 ./apps/node/internal/node ./apps/edge/internal/openai
ok iop/apps/node/internal/node 3.733s
ok iop/apps/edge/internal/openai 13.410s
$ git diff --check
# exit 0, no output
```
The required deterministic adapter-to-Edge smoke still failed and reproduced the implementation handoff exactly:
```text
$ IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh
[openai-lemonade] hang fixture terminal counts prefix=1 finish=1 run_error=0 done=0
# exit 1
```
The reviewer also completed a read-only declared-runner preflight. `/Users/toki/agent-work/iop-dev` is clean on `release/dev-971` at `003398a149c1433cb21bc8aa2720bde272b87373`, equal to `origin/dev` with divergence `0 0`; Go is `1.26.3 darwin/arm64`, git-flow is `1.12.3`, the Edge/Node binaries under `build/dev-runtime/bin/` report source `003398a149c1`, and ports `18082`, `18083`, `18084`, `19093`, and `19101` are listening. This proves the TEST-3 route is available but does not satisfy its rollout/evidence acceptance criteria.
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.
## Section Ownership
| Section | Owner | Note |
|---------|-------|------|
| Header comment, Overview, Review Agent Instructions | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
| Implementation Item Completion (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
| Implementation Checklist (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[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 placeholder text with actual content |
| Reviewer Checkpoints | Fixed at stub creation | Pre-filled from plan |
| Verification Results (section headings + commands) | Implementing agent, then review agent | Implementing agent records initial output; review agent reruns applicable commands and may fill, replace, or append fresh verified output before verdict. Implementing-agent command changes require a `Deviations from Plan` entry |
| Code Review Result | Review agent appends | Not included in stub |
## Code Review Result
- **Overall Verdict:** FAIL
- **Dimension Assessment:**
- Correctness: Fail — the deterministic semantic-disabled Chat tunnel path opens the stream but emits no sanitized terminal after the typed stall.
- Completeness: Fail — TEST-2 does not pass and TEST-3 rollout, runbook, and tracked evidence were not completed.
- Test coverage: Fail — the new semantic-enabled logical-finish matrix passes, but it does not cover the semantic-disabled product path that the fake smoke exercises; streaming Responses has the same endpoint/sink split without an exact post-commit regression.
- API contract: Fail — the always-owned runtime does not preserve the required single sanitized terminal when semantic filtering is disabled.
- Code quality: Pass — the added test/script changes are bounded and contain no unrelated product changes or debug residue.
- Implementation deviation: Pass — implementation obeyed the explicit stop condition instead of widening the original test-only plan into an unreviewed runtime change.
- Verification trust: Pass — fresh reviewer execution reproduced the recorded failure, and the implementation did not claim TEST-2/TEST-3 completion.
- Spec conformance: Fail — the `repeat-guard,ops-evidence` SDD evidence contribution cannot close while terminal liveness and dev operational evidence remain incomplete.
- **Findings:**
- **Required R1 — Semantic-disabled provider-tunnel stalls can leave an already-open OpenAI-compatible stream unterminated.**
- **Evidence:** Fresh `IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh` exits 1 with `prefix=1 finish=1 run_error=0 done=0`. In `apps/edge/internal/openai/stream_gate_runtime.go`, both Chat pool tunnel construction and generic tunnel initial/recovery construction choose `newOpenAITunnelEventSource` when semantic filtering is disabled, so `openAITunnelCodecState.endpoint` remains unbound. `openAITunnelReleaseSink.CommitTerminal` emits a post-commit Chat `run_error` plus `[DONE]` only when `endpointIsChat()` is true. `apps/edge/internal/openai/responses_stream_gate.go` also selects the endpoint-unaware source and generic composite tunnel sink for semantic-disabled streaming Responses. This contradicts `agent-contract/outer/openai-compatible-api.md:120-126` and `agent-contract/inner/execution-runtime.md:52`.
- **Root Cause:** Endpoint framing was incorrectly coupled to semantic-filter activation. Disabling configured semantic filters bypasses the endpoint codec and, for streaming Responses, its endpoint-native terminal sink, even though the private typed-stall registration and request-local StreamGate remain active. Once a prefix is committed, Core commits an error terminal but the selected raw sink lacks the endpoint identity/renderer needed to serialize it.
- **Selected Fix:** In `apps/edge/internal/openai/stream_gate_runtime.go`, always construct `newOpenAITunnelEndpointEventSource` for supported Chat/Responses tunnel attempts (initial and recovery), independent of `semanticEnabled`; keep that flag limited to semantic policy and compatibility decisions. For direct streaming Responses, select the existing `newOpenAIResponsesPoolReleaseSink` and its codec state instead of the generic raw tunnel sink. In `apps/edge/internal/openai/responses_stream_gate.go`, likewise always use the Responses endpoint source and use `newOpenAIResponsesPoolReleaseSink` for streaming provider-pool Responses regardless of semantic-filter activation. This reuses the existing endpoint-native sink that observes raw sequence state, preserves successful provider wire byte-for-byte, and writes one sanitized Responses error plus `[DONE]` after commit. Extend `apps/edge/internal/openai/stream_gate_stall_recovery_test.go` with semantic-disabled Chat and Responses pre/post-commit typed-stall cases, including direct Responses, and retain the existing disabled-semantic byte-order compatibility controls. Acceptance requires the focused tests, full package tests, race tests, and fake Lemonade smoke to exit 0 with exact one-terminal/no-leakage assertions.
- **Required R2 — The required dev timeout-order rollout and durable operational evidence are absent.**
- **Evidence:** TEST-3 remains unchecked, `docs/edge-local-dev-guide.md` has no Ornith response-stall ownership section, and `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/evidence/2026-08-13-ornith-session-stall-timeout-order.log` does not exist. Fresh read-only preflight proves the declared dev runner is reachable, clean, synchronized at `003398a149c1`, built from that same source, and has all required ports listening; the omission is therefore the planned TEST-2 stop condition, not an unavailable execution route.
- **Root Cause:** The original plan correctly prohibited TEST-3 after TEST-2 exposed a product defect. Consequently no dev config/restart, direct/Edge Ornith smoke, runbook update, or sanitized evidence could be accepted in this loop.
- **Selected Fix:** After R1 and all local qualification commands pass, execute the existing TEST-3 procedure exactly: set `response_stall_timeout_ms: 120000` only for `onexplayer-lemonade` and `rtx5090-lemonade`, preserve routing/capacity, keep the Edge request timeout above the approximately 180-second caller boundary, perform the full `dev-runtime-deploy` clean same-ref config-check/rebuild/deploy/restart/identity procedure, run the direct-provider and Edge approximately 41k-token controls for both providers, update `docs/edge-local-dev-guide.md`, and write the exact sanitized evidence log above. Record `not_reproduced` when appropriate; never copy prompt/output/token/credential material into tracked artifacts. Acceptance requires same-ref identity, healthy declared ports/nodes, the timeout inequality, one terminal per control, and the tracked raw-free evidence file.
- **Routing Signals:** `review_rework_count=1`, `evidence_integrity_failure=false`
- **Next Step:** Archive this FAIL pair and materialize the prepared `REVIEW_TEST` follow-up PLAN/CODE_REVIEW pair containing direct fixes R1 and R2; do not write `complete.log` or update the roadmap.

View file

@ -0,0 +1,383 @@
<!-- task=m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness plan=1 tag=REVIEW_TEST milestone-task=repeat-guard,ops-evidence -->
# Code Review Reference - REVIEW_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 root cause, scope, files, and dependency decisions as written. Do not choose another owner, narrow/expand the write boundary, or replace a fix with another verification attempt.
> 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, or classify the next state.
> Finalization (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.
## Overview
date=2026-08-13
task=m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness, plan=1, tag=REVIEW_TEST
## Archive Evidence Snapshot
- Prior plan: `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/plan_cloud_G09_0.log`.
- Prior review: `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/code_review_cloud_G09_0.log`, verdict `FAIL`, `Required R1` semantic-disabled Chat/Responses tunnel terminal ownership and `Required R2` unfinished dev timeout-order evidence; no Suggested or Nit findings.
- Fresh reviewer evidence: focused Node/Edge tests, full package tests, race tests, script syntax, and `git diff --check` passed; `IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh` failed with `prefix=1 finish=1 run_error=0 done=0`.
- Dev preflight: `/Users/toki/agent-work/iop-dev` was clean on `release/dev-971` at `003398a149c1433cb21bc8aa2720bde272b87373`, equal to `origin/dev`; Edge/Node binaries reported source `003398a149c1`, and ports `18082`, `18083`, `18084`, `19093`, `19101` were listening.
- Roadmap carryover: this packet still contributes only `repeat-guard,ops-evidence`; it does not complete or update the Milestone directly.
## For the Review Agent
> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section.
Compare implementation of each item against source files. Run the applicable verification commands directly and record fresh output in `Verification Results`; implementation-owned output is handoff evidence, not a substitute for reviewer verification. If implementation is present, repair missing or stale verification output instead of failing solely for insufficient recorded evidence. When verification exposes a defect, collect the necessary data, determine the exact root cause, and select one concrete fix before generating the follow-up plan; never delegate investigation or remedy selection to the worker.
Review completion means the following steps are finished:
1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals.
2. Archive `CODE_REVIEW-cloud-G10.md` → `code_review_cloud_G10_1.log` and `PLAN-cloud-G10.md` → `plan_cloud_G10_1.log`.
3. If PASS, write `complete.log` and move active task directory to `agent-task/archive/YYYY/MM/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/`. If WARN/FAIL, fully write the next filesystem state required by the code-review skill.
4. If PASS and task group is `m-openai-compatible-output-validation-filters`, preserve the first-line `milestone-task` metadata in `complete.log` and report it for the runtime aggregation event. Roadmap state evaluation belongs to `sync-milestone-workstate`.
5. Check applicable `Review-Only Checklist` items at the final `.log` location before reporting.
---
## Implementation Item Completion
| Item | Status |
|------|---------|
| REVIEW_TEST-1 — Endpoint-aware semantic-disabled terminal ownership | [x] |
| REVIEW_TEST-2 — Deterministic adapter composition and local qualification | [x] |
| REVIEW_TEST-3 — Dev Ornith timeout order and sanitized evidence | [x] |
## Implementation Checklist
- [x] [REVIEW_TEST-1] Restore endpoint-aware semantic-disabled Chat/Responses tunnel terminal behavior and add exact pre/post-commit regressions.
- [x] [REVIEW_TEST-2] Rerun the bounded fake Lemonade adapter-to-Edge smoke plus focused, full, race, syntax, and diff qualification.
- [x] [REVIEW_TEST-3] After local qualification, apply the two-provider dev timeout order, perform same-ref rollout/health checks, update the runbook, and capture sanitized Ornith evidence.
- [x] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
## Review-Only Checklist
> **[REVIEW AGENT ONLY]** This checklist is used only by the review agent.
> Implementing agents must not modify or check this section.
- [x] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
- [x] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match.
- [x] Run applicable required verification and record fresh command/output; repair reviewer-reconstructable evidence gaps instead of forwarding them to another plan.
- [x] For every Required/Suggested finding, record reviewer-collected `Evidence`, exact `Root Cause`, and one `Selected Fix` with affected files/symbols/tests and acceptance commands before creating a follow-up plan.
- [x] Archive active `CODE_REVIEW-*-G??.md` to `code_review_cloud_G10_1.log`.
- [x] Archive active `PLAN-*-G??.md` to `plan_cloud_G10_1.log`.
- [x] Verify that the Agent-Ops managed block in `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores `agent-roadmap/current.md`.
- [ ] If PASS, write `complete.log` based on `agent-ops/skills/common/code-review/templates/complete-log-template.md` and leave no active `.md` files.
- [ ] If PASS, move active task directory `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/` to `agent-task/archive/YYYY/MM/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/` and update this checklist at the final archive path.
- [ ] If PASS and task group is `m-openai-compatible-output-validation-filters`, preserve and report `milestone-task` metadata for runtime aggregation, without modifying roadmap or directly calling `update-roadmap`.
- [ ] If PASS for split work, remove empty active parent `agent-task/m-openai-compatible-output-validation-filters/` or verify it was kept due to remaining siblings/files.
- [x] If WARN/FAIL, write the next filesystem state matching code-review verdict and do not write `complete.log`.
## Deviations from Plan
- REVIEW_TEST-1/2 구현과 runbook 갱신은 현재 source commit `2fcc1093c7629ab94b460d083520ffd9c8064815`에 이미 포함되어 있어 선택된 구현을 재작성하지 않고 source/blame와 fresh 검증으로 확인했다.
- 원격 순차 테스트의 첫 zsh wrapper는 테스트 실행 전에 parse error가 났고, 같은 package 목록을 ignored `remote-sequential-tests.zsh`로 실행해 build 전/후 각각 `packages=49 failed=0`을 확인했다.
- build 완료 뒤 artifact summary의 `awk '$1'`가 zsh positional parameter로 해석되어 summary만 실패했다. artifact 자체는 완성돼 있었고 shell-safe identity 명령으로 네 hash/source를 재확인했다.
- Windows Node 재기동 wrapper는 `Win32_Process.Create`가 반환한 `cmd.exe` PID와 자식 `iop-node.exe` PID를 동일하다고 가정해 실패처럼 보였다. 실제 process/query, installed hash, 상태 snapshot으로 OneXPlayer/RTX5090 Node가 같은 candidate로 실행 중임을 확인했다.
- Windows ACL smoke 첫 호출은 PowerShell의 dotted `-test` flag 전달이 분리되어 테스트 실행 전에 실패했다. `cmd.exe /c` 인용으로 재실행해 두 Windows host에서 요구 테스트를 통과시켰다.
- 첫 Edge Chat smoke는 잘못된 `/v1/chat-completions` 경로로 404였다. 올바른 `/v1/chat/completions`로 수정해 재시도했으나 Chat/Responses 모두 인증 admission 전 401이었다.
- 실행 중 Control Plane의 활성 token digest와 runner/local 보호 SOPS 및 Pi auth/model token scalar를 원문 노출 없이 비교했으나 일치 0개였다. runner의 shell/agent history와 ignored run 후보에서도 일치 0개였다. 새 token 발급/회전은 계획된 runtime rollout보다 넓은 인증 상태 변경이므로 수행하지 않았다. resume condition은 활성 digest와 일치하는 보호된 raw token 제공 또는 token rotation 명시 승인이다.
- 2026-08-13 재개 진단에서 보호 SOPS의 기존 token은 static `openai-principal-tokens.yaml` active entry와 digest가 일치했지만 `/v1/models`는 계속 401이었다. Managed mode는 static mapping이 아니라 fresh Control Plane projection만 인증 source로 사용하며, Control Plane durable store의 active digest 1건과 보호 SOPS 전체의 일치 건수는 다시 0건이었다. 따라서 blocker를 static token 부재가 아니라 active Control Plane projected digest와 일치하는 보호된 plaintext 부재로 좁혔다.
- 승인된 protected wrapper의 활성 token을 명령행·로그·파일에 남기지 않고 stdin으로 1회 주입해 인증 preflight(`/v1/models` HTTP 200, model count 9)를 통과시켰다. 그 뒤 direct control은 반복하지 않고 Edge Chat/Responses capacity+1만 1회 실행했다.
- 인증 후 capacity smoke는 Chat/Responses 각 5건 모두 180초 caller boundary에서 `curl rc=28`, terminal 0건으로 실패했다. Chat 2건은 HTTP 200 스트림을 열고 약 289301 KB를 받았지만 끝나지 않았고, 나머지 8건은 header 전 대기였다. 두 endpoint 모두 OneXPlayer peak `in_flight=2`, `queued=3`를 관측했고 최종 `0/0`, healthy/available로 회복했다.
- 필수 Edge capacity smoke가 실제 runtime terminal 수렴 실패로 미완료여서 `dev-runtime-deploy` stop condition에 따라 release finish/tag/atomic push를 실행하지 않고 `release/dev-974`를 유지했다.
## Key Design Decisions
- semantic filter enablement와 endpoint codec/terminal sink 선택을 분리하고, Chat/Responses의 성공 wire는 그대로 유지하면서 typed stall만 endpoint-native 단일 terminal로 수렴시켰다.
- dev rollout은 `release/dev-974`의 단일 SHA에서 Edge, macOS/Linux ARM64/Windows AMD64 Node를 모두 rebuild/restart했다. Ornith capacity/priority/routes는 변경하지 않고 두 provider의 `response_stall_timeout_ms`만 `120000`으로 적용했다.
- timeout ownership은 Node `120000 ms` < 외부 caller 약 `180000 ms` < Edge hard timeout `3600000 ms` 순서로 유지했다.
- tracked 증빙에는 source/build/config identity, monotonic duration, terminal count, outcome과 blocker만 기록하고 raw prompt/output/token/credential은 ignored run 경로에만 두었다.
## Reviewer Checkpoints
- [ ] Confirm endpoint codec binding is independent of semantic filter enablement on every selected Chat/Responses initial and recovery tunnel path.
- [ ] Confirm semantic-disabled successful Chat/Responses status, headers, JSON/SSE wire order, usage, and single provider terminal remain byte-identical.
- [ ] Confirm pre-commit typed stalls replay at most once under the existing guards and post-commit Chat/Responses emit exactly one sanitized endpoint-native terminal with no raw provider data.
- [ ] Confirm direct streaming Responses and provider-pool streaming Responses both close after an already-committed typed stall.
- [ ] Confirm `finish_reason`/`response.completed` stays protocol wire state until `[DONE]` or physical `END`.
- [ ] Confirm the fake Lemonade fixture remains bounded, receives one `run_error` and one `[DONE]`, cancels/reaps cleanly, and passes a post-cancel control.
- [ ] Confirm both current Ornith provider entries use `response_stall_timeout_ms: 120000` with no route/capacity change and Edge timeout remains above the caller boundary.
- [ ] Confirm dev source/build/runtime identities match one clean release ref, required ports/nodes/providers are healthy, and tracked evidence is sanitized.
- [ ] Confirm only files in `Modified Files Summary` changed.
## Verification Results
Record actual stdout/stderr for every command. If output is too long, save it under an ignored task-specific run directory and record the exact command/path; never reconstruct output.
### REVIEW_TEST-1 focused terminal regressions
```bash
go test -count=1 ./apps/edge/internal/openai -run '^(TestOpenAIStallAfterLogicalFinishMatrix|TestOpenAISemanticGateDisabledStallTerminalMatrix|TestOpenAISemanticGateDisabledCompatibility|TestOpenAITunnelCodecTerminalWire)$'
```
출력(exit 0):
```text
ok iop/apps/edge/internal/openai 0.077s
```
### REVIEW_TEST-2 deterministic fake composition
```bash
bash -n scripts/e2e-openai-lemonade.sh
go test -count=1 ./apps/node/internal/node -run '^TestTunnelWatchdogFinishFrameWithoutEndStallsOnce$'
IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh
```
모두 exit 0. 실제 출력:
```text
bash -n: no output
ok iop/apps/node/internal/node 0.047s
[openai-lemonade] OpenAI-compatible Lemonade serving test PASSED (mode=fake).
```
### REVIEW_TEST-3 dev preflight, rollout, and live evidence
```bash
ssh toki@toki-labs.com '/bin/zsh -lc '\''cd /Users/toki/agent-work/iop-dev && git status --short --branch && git rev-parse HEAD && git rev-parse origin/dev && git rev-list --left-right --count HEAD...origin/dev && go version && git flow version | head -n 1'\'''
```
Record the exact sanitized inventory, clean-sync/release, sequential tests, build hashes/source ids, config check/dry-run, deploy/restart, port/node/provider snapshot, direct/Edge smoke, capacity, and release-finish output required by `dev-runtime-deploy`. Raw prompt/output/token/credential artifacts stay under ignored `agent-test/runs/**`.
상세 sanitized 로그는 `.local/task-runs/stream-terminal-liveness/`에 보존했다. raw HTTP body는 runner의 ignored `agent-test/runs/stream-terminal-liveness-dev-974/`에만 있다.
Preflight/release identity:
```text
branch=release/dev-974
head=origin/dev=origin/release/dev-974=2fcc1093c7629ab94b460d083520ffd9c8064815
divergence=0 0; shallow=false
origin/main=0c1e9e6a3f181eaebe59f888043fadc37385741d
go=go1.26.3 darwin/arm64; git-flow=1.12.3 AVH
remote dev-974 tag count=0
```
Config/rebuild/test:
```text
config check: OK
dry-run: restart_required; exact paths=onexplayer-lemonade.response_stall_timeout_ms,rtx5090-lemonade.response_stall_timeout_ms; 300000 -> 120000
pre-build sequential tests: packages=49 failed=0
post-build sequential tests: packages=49 failed=0
edge sha256=25e097be4aee972cc62821889f8dd697b7d0f8de712973d169449b2271a39096
node darwin/arm64 sha256=c24a3b0e16bc7ab807c00c465a47f9caccd35c5575d264d0c4ad10b9ad422f01
node linux/arm64 sha256=759eb31f8c8752925e54f61fdeb4f9366bd39e37ea50815e79328c3be86ee4e7
node windows/amd64 sha256=f04f33f17be8a4fdc29ef0e4b705953d561478a75f00948d3fdfc13fee8ed572
all artifact vcs.revision=2fcc1093c7629ab94b460d083520ffd9c8064815
Windows ACL smoke: required tests PASS on OneXPlayer and RTX5090
```
Deployment/health/config identity:
```text
Edge + mac-codex + GX10 + OneXPlayer + RTX5090 restarted from the same candidate
ports 18082,18083,18084,19093,19101=open
connected node count=4
onexplayer-lemonade capacity=3 priority=2 timeout=120000 health=healthy status=available in_flight=0 queued=0
rtx5090-lemonade capacity=1 priority=0 timeout=120000 health=healthy status=available in_flight=0 queued=0
Edge request hard timeout=3600000 (> caller boundary 180000)
post-restart refresh dry-run: applied, no changes
RTX manual toggle/profile/listener/Edge connection=true; IOP Startup/Run/Task/Service entries not created
```
약 41k-token direct controls:
```text
onexplayer-lemonade: http=200 duration_ms=111232 finish=1 done=1 error=0 outcome=normal_terminal
rtx5090-lemonade: http=200 duration_ms=7710 finish=1 done=1 error=0 outcome=normal_terminal
stall_reproduction_status=not_reproduced
```
Blocker와 stop condition:
```text
protected active-token preflight: /v1/models http=200 model_count=9
Chat capacity+1: requests=5, caller timeouts=5, terminal=0, http=200,200,000,000,000
Responses capacity+1: requests=5, caller timeouts=5, terminal=0, http=000,000,000,000,000
both endpoints: onexplayer peak in_flight=2 queued=3; final in_flight=0 queued=0 healthy/available
resume_condition=fix or replan admission/terminal convergence, then repeat one bounded capacity smoke
capacity smoke=failed; release finish/tag/atomic push=not run; release/dev-974 retained
```
REVIEW_TEST-3의 구현 작업과 stop condition 처리는 완료됐다. 배포, same-ref identity, 두 provider 직접 control, timeout ordering, runbook과 raw-free evidence는 완료됐다. 이후 활성 token을 안전하게 stdin으로 주입해 인증 blocker를 제거하고 필수 capacity smoke를 실제 실행했지만, capacity+1 장문 요청 10건 모두 caller boundary 전에 endpoint terminal을 내지 못했다. 따라서 acceptance의 capacity smoke는 미충족이며, 이를 성공 전제로 하는 release finish/tag/atomic push도 `dev-runtime-deploy` stop condition에 따라 실행하지 않았다. 구현 체크는 이 실패를 성공으로 간주하지 않고, 계획된 배포·검증·중단·증거 기록까지 수행했음을 뜻한다.
2026-08-13 최신 read-only 재확인:
```text
release/dev-974 head=origin/dev=origin/release/dev-974=2fcc1093c7629ab94b460d083520ffd9c8064815
remote checkout clean; divergence=0 0; remote tag count=0
ports 18082,18083,18084,19093,19101=open; Edge health http=200
connected node count=4
onexplayer-lemonade capacity=3 health=healthy status=available in_flight=0 queued=0
rtx5090-lemonade capacity=1 health=healthy status=available in_flight=0 queued=0
protected static token digest match=true; managed /v1/models http=401
Control Plane active token count=1; active digest vs protected SOPS match count=0
```
위 read-only 재확인의 401은 stale SOPS 후보에 대한 과거 진단이다. 이후 protected active-token preflight와 실제 capacity smoke 결과가 최신 판정이며, 인증 blocker는 해소됐다. 현재 blocker는 180초 안에 terminal로 수렴하지 않는 capacity+1 장문 runtime이다.
Tracked raw-free evidence: `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/evidence/2026-08-13-ornith-session-stall-timeout-order.log`.
### Full local qualification
```bash
go test -count=1 ./packages/go/execution ./packages/go/config ./apps/node/internal/node ./apps/edge/internal/openai
go test -race -count=1 ./apps/node/internal/node ./apps/edge/internal/openai
git diff --check
git status --short --branch
```
모두 exit 0. 실제 출력:
```text
ok iop/packages/go/execution 0.010s
ok iop/packages/go/config 0.164s
ok iop/apps/node/internal/node 0.984s
ok iop/apps/edge/internal/openai 8.471s
ok iop/apps/node/internal/node 3.605s # race
ok iop/apps/edge/internal/openai 12.919s # race
git diff --check: no output
git status: existing untracked .gocache/, .local/, active agent-task plus this evidence file; no unrelated tracked modification
```
2026-08-13 resume fresh rerun도 모두 exit 0이며 전체 stdout/stderr는 ignored `.local/task-runs/stream-terminal-liveness/resume-a03-local-verification.log`에 보존했다:
```text
focused Edge terminal regressions: ok 0.068s
Node watchdog regression: ok 0.041s
bash syntax: no output
fake Lemonade: PASSED (mode=fake)
selected full packages: execution 0.006s, config 0.206s, node 0.988s, edge 8.425s
race: node 3.644s, edge 12.664s
git diff --check: no output
```
2026-08-13 최종 worker 재검증도 모두 exit 0이며 전체 stdout/stderr는 ignored `.local/task-runs/stream-terminal-liveness/resume-a05-final-verification.log`에 보존했다:
```text
Node watchdog regression: ok 0.032s
focused Edge terminal regressions: ok 0.057s
bash syntax: no output
fake Lemonade: PASSED (mode=fake)
selected full packages: execution 0.014s, config 0.217s, node 1.078s, edge 8.513s
race: node 3.693s, edge 13.139s
git diff --check: no output
```
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.
## Section Ownership
| Section | Owner | Note |
|---------|-------|------|
| Header comment, Overview, Review Agent Instructions | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
| Implementation Item Completion (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
| Implementation Checklist (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[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 placeholder text with actual content |
| Reviewer Checkpoints | Fixed at stub creation | Pre-filled from plan |
| Verification Results (section headings + commands) | Implementing agent, then review agent | Implementing agent records initial output; review agent reruns applicable commands and may fill, replace, or append fresh verified output before verdict. Implementing-agent command changes require a `Deviations from Plan` entry |
| Code Review Result | Review agent appends | Not included in stub |
## Reviewer Fresh Verification — 2026-08-13
### Focused and full local qualification
```text
$ go test -count=1 ./apps/node/internal/node -run '^TestTunnelWatchdogFinishFrameWithoutEndStallsOnce$'
ok iop/apps/node/internal/node 0.076s
$ go test -count=1 ./apps/edge/internal/openai -run '^(TestOpenAIStallAfterLogicalFinishMatrix|TestOpenAISemanticGateDisabledStallTerminalMatrix|TestOpenAISemanticGateDisabledCompatibility|TestOpenAITunnelCodecTerminalWire|TestOpenAIDirectResponsesSemanticDisabledStallTerminal)$'
ok iop/apps/edge/internal/openai 0.049s
$ bash -n scripts/e2e-openai-lemonade.sh
(no output)
$ IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh
[openai-lemonade] OpenAI-compatible Lemonade serving test PASSED (mode=fake).
$ go test -count=1 ./packages/go/execution ./packages/go/config ./apps/node/internal/node ./apps/edge/internal/openai
ok iop/packages/go/execution 0.009s
ok iop/packages/go/config 0.193s
ok iop/apps/node/internal/node 1.038s
ok iop/apps/edge/internal/openai 8.457s
$ go test -race -count=1 ./apps/node/internal/node ./apps/edge/internal/openai
ok iop/apps/node/internal/node 3.688s
ok iop/apps/edge/internal/openai 12.751s
$ git diff --check
(no output)
```
Full stdout/stderr is retained outside the repository at `/tmp/iop-review-stream-terminal.uzTeHn/focused.log` and `/tmp/iop-review-full.SjSkTi/full.log`.
### Dev route, policy, and artifact provenance
The first two read-only SSH probes failed before reading state because of shell quoting and missing remote PyYAML. The corrected Ruby YAML plus read-only SQLite probe exited 0 and printed only sanitized configuration/route facts:
```text
branch=release/dev-974
head=2fcc1093c7629ab94b460d083520ffd9c8064815
origin_dev=2fcc1093c7629ab94b460d083520ffd9c8064815
dirty_count=0
model=ornith:35b default_max_tokens=32768 min_max_tokens=16384 providers=onexplayer-lemonade,rtx5090-lemonade
model=ornith-fast default_max_tokens=32768 min_max_tokens=16384 providers=rtx5090-lemonade
long_context_threshold_tokens=30000
provider=onexplayer-lemonade capacity=3 long_context_capacity=2 priority=2 response_stall_timeout_ms=120000
provider=rtx5090-lemonade capacity=1 long_context_capacity=1 priority=0 response_stall_timeout_ms=120000
active_route_alias=ornith-fast resource_selector=rtx5090-lemonade
active_route_alias=ornith:35b resource_selector=onexplayer-lemonade
```
The live wrapper used `"evidence " * 41000`, which is about 369,000 runes and exceeds the configured 30,000-token long-context threshold under `runes/4 + runes/16`. It also sent five requests through the managed `ornith:35b` route even though `apps/edge/internal/openai/principal_routes.go:291-305` restricts that route to OneXPlayer. The observed OneXPlayer `in_flight=2`, `queued=3` is therefore the expected long-context-slot result, not evidence that aggregate normal capacity 4 was exercised. Chat `max_tokens=900` is raised to the model's `min_max_tokens=16384` by `apps/edge/internal/openai/chat_policy.go:60-80`; the two HTTP 200 streams were still producing data at the caller deadline, so Node's idle-stall watchdog had no reason to fire.
The remote raw directory also contains mixed-generation files. Current timeout stderr files have a newer mtime than eight 59-byte SSE bodies left by the earlier 401 run, while `safe/summary.json` counted those stale bodies as the current `http=000` cases. The fixed run root therefore does not prove one-run body provenance. Sanitized output is retained at `/tmp/iop-review-dev-route-preflight.log` and `/tmp/iop-review-dev-artifact-preflight.log`; no token or credential value was read or printed.
## Code Review Result
- **Overall Verdict:** FAIL
### Dimension Assessment
| Dimension | Result | Assessment |
|---|---|---|
| Correctness | Pass | Endpoint-aware semantic-disabled Chat/Responses terminal ownership is implemented and passes focused, composition, full, and race verification. |
| Completeness | Fail | The required dev Chat/Responses capacity smoke did not succeed, so release finish/tag/atomic push correctly remain unrun. |
| Test coverage | Fail | Deterministic runtime regressions pass, but the required route-realistic dev capacity/terminal qualification has not produced passing evidence. |
| API contract | Pass | Successful endpoint-native wire compatibility and one sanitized typed-stall terminal remain covered. |
| Code quality | Pass | No debug/dead-code/TODO or diff-check issue was found in the runtime fix. |
| Implementation deviation | Fail | The live smoke combined a long-context input, a caller output request rewritten to 16,384 tokens, and an aggregate-capacity assumption contradicted by the active managed route selector. |
| Verification trust | Fail | A fixed artifact directory mixed prior 401 response bodies with the current timeout run, and the recorded `approx_input_tokens=41000` does not match the repository estimator. |
| Spec conformance | Fail | SDD S07/ops-evidence still lacks a successful capacity+1, terminal-complete, raw-free dev result and the release completion it gates. |
### Findings
- **Required R1 — The dev capacity smoke targets capacity that the authenticated route cannot reach.**
- **Evidence:** Reviewer read-only preflight shows `ornith:35b -> resource_selector=onexplayer-lemonade`, while the smoke sends five long-context requests and expects OneXPlayer 3 + RTX5090 1. `principal_routes.go:291-305` excludes every provider other than the resolved selector. The configured long slots are OneXPlayer 2 and RTX5090 1, and the observed selected route peak is exactly OneXPlayer `2/3` in-flight/queued. `chat_policy.go:60-80` also raises Chat 900 to 16,384 output tokens, while the two open streams continued producing bytes through 180 seconds.
- **Root Cause:** The project deployment/test contract and task-local wrapper treat a catalog model group's physical providers as one caller-reachable pool without first intersecting them with the active managed principal route. The same wrapper also conflates a 41k-token liveness control with the shorter normal-capacity smoke and assumes the caller deadline is an idle-stall oracle.
- **Selected Fix:** Update `agent-ops/skills/project/dev-runtime-deploy/SKILL.md` and `agent-test/dev/edge-smoke.md` so managed capacity is derived from the authenticated route's eligible provider selector; add `scripts/e2e-openai-managed-capacity-smoke.sh` that runs Chat and Responses separately for one route/provider with `eligible capacity + 1`, a short input that requests a 7001200-token structured answer, bounded thinking, exact endpoint terminal validation, selected-provider peak/queue/final recovery, and no runtime route/capacity mutation. Document this distinction in `docs/edge-local-dev-guide.md`. Rerun `ornith:35b` against OneXPlayer and `ornith-fast` against RTX5090 using their actual projected aliases, then finish the retained release only after both endpoint matrices pass.
- **Disposition:** direct-fix.
- **Acceptance:** `bash -n scripts/e2e-openai-managed-capacity-smoke.sh`; `./scripts/e2e-openai-managed-capacity-smoke.sh --self-test`; `python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/dev-runtime-deploy`; route-aware dev Chat/Responses runs show every request HTTP 200 with exactly one success terminal, selected-provider peak equal to eligible capacity, queue at least 1, non-selected provider excluded from the claimed total, and final selected counters 0/0.
- **Required R2 — The live evidence directory does not provide single-run provenance.**
- **Evidence:** Eight `edge-*.sse` files are 59-byte bodies with older mtimes from the prior 401 attempt, whereas current `*.stderr` files have the later timeout mtime. The latest summary reports those bytes under current `rc=28/http=000`. It also labels the `"evidence " * 41000` payload as about 41k input tokens although the repository estimator classifies roughly 369,000 runes above 100k estimated tokens.
- **Root Cause:** The wrapper reuses `agent-test/runs/stream-terminal-liveness-dev-974`, does not create an immutable unique run directory, and summarizes any pre-existing response file when curl times out before opening a new response body. Request-shape metadata is a handwritten label instead of being derived from the actual request using the repository estimator contract.
- **Selected Fix:** Make the new managed-capacity smoke create a mode-0700 unique run directory for every invocation, remove/refuse pre-existing per-case targets before curl, bind every summary row to the current run id and request/result mtimes, derive rune/estimated-token/context-class metadata from the emitted request, and fail closed on missing/current-run-mismatched bodies. Update the tracked evidence log only from the new sanitized summary; retain raw bodies exclusively under the unique ignored run directory.
- **Disposition:** direct-fix.
- **Acceptance:** the script self-test rejects a stale-body fixture and proves unique-run isolation; the dev summaries contain one run id, current request/result provenance, computed input estimate/context class, no raw/secret fields, and no stale 59-byte body attribution; `git diff --check` passes.
### Routing Signals
- `review_rework_count=2`
- `evidence_integrity_failure=true`
### Next Step
Invoke the plan skill in `prepare-follow-up` mode with Required R1/R2 exactly as closed above, archive this pair, and materialize the freshly routed `REVIEW_REVIEW_TEST` follow-up pair. Do not write `complete.log` or update the Milestone.

View file

@ -0,0 +1,384 @@
<!-- task=m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness plan=2 tag=REVIEW_REVIEW_TEST milestone-task=repeat-guard,ops-evidence -->
# Code Review Reference - REVIEW_REVIEW_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 root cause, scope, files, and dependency decisions as written. Do not choose another owner, narrow/expand the write boundary, or replace a fix with another verification attempt.
> 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, or classify the next state.
> Finalization (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.
## Overview
date=2026-08-13
task=m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness, plan=2, tag=REVIEW_REVIEW_TEST
## Archive Evidence Snapshot
- Prior plan: `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/plan_cloud_G10_1.log`.
- Prior review: `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/code_review_cloud_G10_1.log`, verdict `FAIL`, with `Required R1` for route-ineligible aggregate capacity and `Required R2` for mixed-generation artifact provenance; no Suggested or Nit findings.
- Fresh reviewer evidence: local Node/Edge focused, fake Lemonade, selected full-package, race, syntax, and diff checks passed. Read-only dev preflight showed `ornith:35b -> onexplayer-lemonade`, `ornith-fast -> rtx5090-lemonade`, normal capacities `3/1`, long-context capacities `2/1`, and a 30,000-token long-context threshold.
- Failure evidence: the five-request `ornith:35b` run used a roughly 369,000-rune input, was classified long by the repository estimator, reached OneXPlayer `in_flight=2, queued=3`, and never exercised RTX5090. Eight response bodies summarized as current results were older 59-byte artifacts from a prior 401 attempt.
- Roadmap carryover: this packet still contributes only `repeat-guard,ops-evidence`; PASS is contribution evidence and does not directly complete or update the Milestone.
## For the Review Agent
> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section.
Compare implementation of each item against source files. Run the applicable verification commands directly and record fresh output in `Verification Results`; implementation-owned output is handoff evidence, not a substitute for reviewer verification. If implementation is present, repair missing or stale verification output instead of failing solely for insufficient recorded evidence. When verification exposes a defect, collect the necessary data, determine the exact root cause, and select one concrete fix before generating the follow-up plan; never delegate investigation or remedy selection to the worker.
Review completion means the following steps are finished:
1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals.
2. Archive `CODE_REVIEW-cloud-G10.md` → `code_review_cloud_G10_2.log` and `PLAN-cloud-G10.md` → `plan_cloud_G10_2.log`.
3. If PASS, write `complete.log` and move active task directory to `agent-task/archive/YYYY/MM/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/`. If WARN/FAIL, fully write the next filesystem state required by the code-review skill.
4. If PASS and task group is `m-openai-compatible-output-validation-filters`, preserve the first-line `milestone-task` metadata in `complete.log` and report it for the runtime aggregation event. Roadmap state evaluation belongs to `sync-milestone-workstate`.
5. Check applicable `Review-Only Checklist` items at the final `.log` location before reporting.
---
## Implementation Item Completion
| Item | Status |
|---|---|
| REVIEW_REVIEW_TEST-1 — Managed route-qualified capacity contract | [x] |
| REVIEW_REVIEW_TEST-2 — Deterministic managed-capacity and provenance oracle | [x] |
| REVIEW_REVIEW_TEST-3 — Route-realistic dev evidence and retained release gate | [x] |
## Implementation Checklist
- [x] [REVIEW_REVIEW_TEST-1] Correct managed-capacity ownership in the project deployment skill, dev Edge smoke profile, and local guide.
- [x] [REVIEW_REVIEW_TEST-2] Add the route-aware Chat/Responses capacity smoke with deterministic route, terminal, provenance, and stale-artifact self-tests.
- [x] [REVIEW_REVIEW_TEST-3] Run both projected Ornith route matrices, replace disputed evidence from the sanitized current-run summaries, and finish the retained release only if every gate passes.
- [x] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
## Review-Only Checklist
> **[REVIEW AGENT ONLY]** This checklist is used only by the review agent.
> Implementing agents must not modify or check this section.
- [x] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
- [x] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match.
- [x] Run applicable required verification and record fresh command/output; repair reviewer-reconstructable evidence gaps instead of forwarding them to another plan.
- [ ] For every Required/Suggested finding, record reviewer-collected `Evidence`, exact `Root Cause`, and one `Selected Fix` with affected files/symbols/tests and acceptance commands before creating a follow-up plan.
- [x] Archive active `CODE_REVIEW-*-G??.md` to `code_review_cloud_G10_2.log`.
- [x] Archive active `PLAN-*-G??.md` to `plan_cloud_G10_2.log`.
- [x] Verify that the Agent-Ops managed block in `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores `agent-roadmap/current.md`.
- [x] If PASS, write `complete.log` based on `agent-ops/skills/common/code-review/templates/complete-log-template.md` and leave no active `.md` files.
- [x] If PASS, move active task directory `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/` to `agent-task/archive/YYYY/MM/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/` and update this checklist at the final archive path.
- [x] If PASS and task group is `m-openai-compatible-output-validation-filters`, preserve and report `milestone-task` metadata for runtime aggregation, without modifying roadmap or directly calling `update-roadmap`.
- [x] If PASS for split work, remove empty active parent `agent-task/m-openai-compatible-output-validation-filters/` or verify it was kept due to remaining siblings/files.
- [ ] If WARN/FAIL, write the next filesystem state matching code-review verdict and do not write `complete.log`.
## Deviations from Plan
- No implementation scope deviation was made. The previously unavailable active principal token was supplied through the already approved protected wrapper and stdin handoff; it was not printed or stored in tracked evidence.
- The first passing live matrix exposed a macOS cross-process monotonic-clock epoch incompatibility in the informational duration field. The runner was corrected to use the shared wall-clock epoch, its local and remote self-tests and SHA identity were re-proved, and all four cases were rerun. The terminal, capacity, queue, recovery, and provenance gates were unchanged.
- `shellcheck` is not installed in the local environment, so the selected `bash -n`, local self-test, and remote macOS Bash 3.2 self-test were used for the shell surface. `shellcheck` was not a plan acceptance command.
## Key Design Decisions
- Each live invocation owns one authenticated route alias and one endpoint. Route resolution requires exactly one active alias and joins `resource_selector`, profile, and upstream model to exactly one healthy connected provider snapshot.
- Capacity comes from only the selected provider and the computed request class: `capacity` for `normal`, `long_context_capacity` for `long`. The normal live path rejects a computed long request before dispatch.
- Every invocation creates a unique mode-0700 ignored run directory and binds request/result/status files to an immutable run id, manifest, digest, and creation boundary. Terminal parsing consumes only manifest-owned response files and fails on stale, missing, or foreign-run paths.
- The sanitized summary uses a fixed allowlist of run id, script hash, route alias, selected provider, endpoint, computed request shape/class, terminal counts, durations, selected peak/queue/final counters, provenance, and outcome. Raw routes, token/header values, route/slot ids, prompts, requests, response bodies, and model output remain in ignored run material.
- The disputed v1 tracked evidence was replaced by a raw-free v2 summary only after the corrected runner produced four passing current-run summaries. Release finish and atomic push ran only after the final retained-release ref gate passed.
## Reviewer Checkpoints
- [x] Confirm managed capacity is derived from the authenticated route selector and only the selected provider's capacity for the computed context class.
- [x] Confirm current projected aliases map exactly to `ornith:35b/onexplayer-lemonade` and `ornith-fast/rtx5090-lemonade` without route or capacity mutation.
- [x] Confirm the request fixture is short, computed as `normal`, requests a 700-1200-token structured answer, and bounds provider-native thinking without relying on the caller max-token field as the duration oracle.
- [x] Confirm Chat and Responses run separately with selected eligible capacity+1, exact endpoint success terminals, selected peak equal to capacity, queue at least one, and selected final counters `0/0`.
- [x] Confirm non-selected providers are excluded from the claimed capacity total.
- [x] Confirm every invocation creates a distinct mode-0700 run directory and every summary row is bound to its current run manifest, request/result path, and mtime.
- [x] Confirm the self-test rejects route mismatch, aggregate-capacity claims, duplicate/missing terminals, stale/missing/foreign-run bodies, and non-current provenance.
- [x] Confirm sanitized summaries and tracked evidence contain no token/header, route slot/id, prompt, response body, output, or credential material.
- [x] Confirm the disputed 41k-token label, terminal-zero capacity claim, and fixed raw-artifact path were not retained as successful evidence.
- [x] Confirm the retained release was finished only if every local/live/ref gate passed and no partial refs were pushed on failure.
- [x] Confirm only files in `Modified Files Summary` changed.
## Verification Results
Record actual stdout/stderr for every command. If output is too long, save it under an ignored task-specific run directory and record the exact command/path; never reconstruct output.
### REVIEW_REVIEW_TEST-1 managed route-qualified contract
```bash
python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/dev-runtime-deploy
rg --sort path -n 'managed|resource_selector|eligible capacity|e2e-openai-managed-capacity-smoke' agent-ops/skills/project/dev-runtime-deploy/SKILL.md agent-test/dev/edge-smoke.md docs/edge-local-dev-guide.md
```
Exit `0`:
Exact output: `.local/task-runs/stream-terminal-liveness/plan2-contract.log` (`sha256=6c249186293aac6795b1402dd6dec1f88f9709d9ee9d1b1ed85f005029dd6a66`).
```text
Skill is valid!
agent-ops/skills/project/dev-runtime-deploy/SKILL.md:146: In managed mode, authenticate GET /v1/credentials/routes ... intersect its resource_selector, profile, and upstream model ...
agent-ops/skills/project/dev-runtime-deploy/SKILL.md:147: Run scripts/e2e-openai-managed-capacity-smoke.sh ... selected provider's eligible capacity plus one ... never add a provider excluded by that route.
agent-ops/skills/project/dev-runtime-deploy/SKILL.md:149: Require HTTP 200 ... exactly one endpoint-native success terminal and one [DONE] ... final counters 0/0.
agent-test/dev/edge-smoke.md:153: dev-runtime managed capacity smoke는 scripts/e2e-openai-managed-capacity-smoke.sh를 사용한다 ... ornith:35b는 onexplayer-lemonade=3, ornith-fast는 rtx5090-lemonade=1 ...
agent-test/dev/edge-smoke.md:154: emitted request JSON ... runes/4 + runes/16 ... normal qualification은 capacity ... long_context_capacity ...
agent-test/dev/edge-smoke.md:157: HTTP 200 ... endpoint-native success terminal 정확히 1개, [DONE] 정확히 1개 ...
agent-test/dev/edge-smoke.md:158: mode 0700 unique directory ... stale/missing/foreign-run body는 실패한다.
docs/edge-local-dev-guide.md:170: Managed mode의 capacity는 ... 모든 provider의 합이 아닙니다 ... resource_selector ...
docs/edge-local-dev-guide.md:172: scripts/e2e-openai-managed-capacity-smoke.sh ... capacity + 1 ...
docs/edge-local-dev-guide.md:174: terminal ... peak ... queue ... final in_flight=0/queued=0 ... fail-closed ...
```
### REVIEW_REVIEW_TEST-2 deterministic smoke and provenance self-test
```bash
bash -n scripts/e2e-openai-managed-capacity-smoke.sh
./scripts/e2e-openai-managed-capacity-smoke.sh --self-test
```
Both commands exited `0`. Self-test output:
Exact output: `.local/task-runs/stream-terminal-liveness/plan2-self-test.log` (`sha256=a1e6efa43684683bc9b0299a20418e6e1e273d4a02e9c2034b5d49de572e06af`).
```text
[managed-capacity-smoke] self-test unique_run_directories=true mode=0700
[managed-capacity-smoke] self-test rejected=route-selector-mismatch
[managed-capacity-smoke] self-test rejected=aggregate-capacity-claim
[managed-capacity-smoke] self-test route_exclusion=true normal_capacity=3 long_capacity=2
[managed-capacity-smoke] self-test rejected=duplicate-terminal
[managed-capacity-smoke] self-test rejected=missing-terminal
[managed-capacity-smoke] self-test exact_chat_and_responses_terminals=true
[managed-capacity-smoke] self-test rejected=stale-body
[managed-capacity-smoke] self-test rejected=missing-body
[managed-capacity-smoke] self-test rejected=foreign-run-body
[managed-capacity-smoke] self-test current_manifest_acceptance=true stale_missing_foreign_rejected=true
[managed-capacity-smoke] SELF_TEST_PASS
```
The corrected copied script passed under remote macOS Bash 3.2. Its local and remote SHA-256 both equal:
```text
bc0dcf5aa8acc6a10942a6d48e4b3248547004df04d0a292562aea2ca465a903
```
### REVIEW_REVIEW_TEST-3 dev route matrices and retained release
```bash
ssh toki@toki-labs.com '/bin/zsh -lc '\''cd /Users/toki/agent-work/iop-dev && git status --short --branch && git rev-parse HEAD && git rev-parse origin/dev && git rev-parse origin/release/dev-974 && git rev-list --left-right --count HEAD...origin/dev && git tag -l dev-974 && go version && git flow version | head -n 1'\'''
```
Run the new smoke through the authorized protected-token wrapper for `ornith:35b/onexplayer-lemonade` and `ornith-fast/rtx5090-lemonade`, each with Chat and Responses. Record exact sanitized summaries and release finish/tag/atomic-push output or the exact blocker and retained-release state.
Retained-release preflight exited `0`:
```text
## release/dev-974...origin/release/dev-974
HEAD=2fcc1093c7629ab94b460d083520ffd9c8064815
origin/dev=2fcc1093c7629ab94b460d083520ffd9c8064815
origin/release/dev-974=2fcc1093c7629ab94b460d083520ffd9c8064815
HEAD...origin/dev=0 0
local dev-974 tag count=0
remote dev-974 tag count=0
go version go1.26.3 darwin/arm64
git-flow 1.12.3 (AVH Edition)
```
The approved local `/tmp/iop-bench-13-env` wrapper supplied the existing active principal token over SSH protected stdin to the remote runner without printing it; the wrapper is intentionally not copied to the remote host. Corrected-run sanitized summaries:
```text
ornith:35b chat: run_id=1786601559737433000-4ba66d5c48201ad1 provider=onexplayer-lemonade class=normal estimate=133 capacity=3 requests=4 HTTP200=4 finish=4 done=4 errors=0 duration_ms=35049..60703 peak=3 max_queued=1 final=0/0 provenance=current-run-manifest outcome=pass
ornith:35b responses: run_id=1786601621300814000-c4d9c6c134fe368b provider=onexplayer-lemonade class=normal estimate=126 capacity=3 requests=4 HTTP200=4 completed=4 done=4 errors=0 duration_ms=35451..57073 peak=3 max_queued=1 final=0/0 provenance=current-run-manifest outcome=pass
ornith-fast chat: run_id=1786601679237576000-53b4cfe86fa3dc40 provider=rtx5090-lemonade class=normal estimate=135 capacity=1 requests=2 HTTP200=2 finish=2 done=2 errors=0 duration_ms=3402..6602 peak=1 max_queued=1 final=0/0 provenance=current-run-manifest outcome=pass
ornith-fast responses: run_id=1786601686638286000-825708e93ce3450d provider=rtx5090-lemonade class=normal estimate=127 capacity=1 requests=2 HTTP200=2 completed=2 done=2 errors=0 duration_ms=3223..6399 peak=1 max_queued=1 final=0/0 provenance=current-run-manifest outcome=pass
script_sha256=bc0dcf5aa8acc6a10942a6d48e4b3248547004df04d0a292562aea2ca465a903
```
The v2 tracked evidence redaction check exited `0` and rejected token/header, raw artifact, prompt/body, route-id, and slot-id fields. Its SHA-256 is `965407296852ee25a3ccc5e2069e21faff8386da92e911dd80682d24476a12e7`.
Final retained-release gate and finish:
```text
candidate=2fcc1093c7629ab94b460d083520ffd9c8064815
candidate_tree=d46c7e3057bafbeedc1ddb10d2619feb3c6eb2db
pre_finish origin/dev=2fcc1093c7629ab94b460d083520ffd9c8064815 origin/main=0c1e9e6a3f181eaebe59f888043fadc37385741d tag_count=0 release_remote_count=1 tracked_status=clean
git flow release finish --keepremote -m "Release dev-974" dev-974: PASS
git push --atomic origin main dev dev-974 :release/dev-974: PASS
final main=origin/main=fd41adac777f7c7faea0cdd27acbe2817890abf8
final dev=origin/dev=019500a5d65117bfd115e0c33601ceef3461ee52
final tag_object=4d2596e3779027ba88457c44fcc9bc91134cf5e9 tag_tree=d46c7e3057bafbeedc1ddb10d2619feb3c6eb2db
final local_release_count=0 remote_release_count=0 tracked_status=clean
```
### Final local qualification
```bash
bash -n scripts/e2e-openai-managed-capacity-smoke.sh
./scripts/e2e-openai-managed-capacity-smoke.sh --self-test
python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/dev-runtime-deploy
go test -count=1 ./apps/node/internal/node -run '^TestTunnelWatchdogFinishFrameWithoutEndStallsOnce$'
go test -count=1 ./apps/edge/internal/openai -run '^(TestOpenAIStallAfterLogicalFinishMatrix|TestOpenAISemanticGateDisabledStallTerminalMatrix|TestOpenAISemanticGateDisabledCompatibility|TestOpenAITunnelCodecTerminalWire|TestOpenAIDirectResponsesSemanticDisabledStallTerminal)$'
IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh
git diff --check
git status --short --branch
```
All executable local qualification commands exited `0`. Exact ignored output paths and SHA-256:
```text
.local/task-runs/stream-terminal-liveness/plan2-self-test.log a1e6efa43684683bc9b0299a20418e6e1e273d4a02e9c2034b5d49de572e06af
.local/task-runs/stream-terminal-liveness/plan2-contract.log 6c249186293aac6795b1402dd6dec1f88f9709d9ee9d1b1ed85f005029dd6a66
.local/task-runs/stream-terminal-liveness/plan2-node.log d55dbf369fb2761e401bf0f0ac53d6f10093f5e081cb88657c86a07c4c20106c
.local/task-runs/stream-terminal-liveness/plan2-edge.log 8dfa7d08ab348b15aae58561ac98f688af78fb68c0e230aae46f70e206f3ad68
.local/task-runs/stream-terminal-liveness/plan2-lemonade.log 4bfc218d83fb5cd90819978c9bfee79d35d8178cdf2c9c62df40718aec514a80
.local/task-runs/stream-terminal-liveness/plan2-diff-check.log e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
.local/task-runs/stream-terminal-liveness/plan2-status.log 1d6c83b3b8470ad2f6d386802f20957c2c5d520d5020e5d206d4edfd4fdc863c
```
Short output:
```text
bash -n: PASS
self-test: SELF_TEST_PASS
skill validation: Skill is valid!
ok iop/apps/node/internal/node 0.141s
ok iop/apps/edge/internal/openai 0.148s
[openai-lemonade] OpenAI-compatible Lemonade serving test PASSED (mode=fake).
git diff --check: PASS
## feature/openai-compatible-stream-terminal-liveness...origin/feature/openai-compatible-stream-terminal-liveness [ahead 12]
M agent-ops/skills/project/dev-runtime-deploy/SKILL.md
M agent-test/dev/edge-smoke.md
M docs/edge-local-dev-guide.md
?? agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/evidence/2026-08-13-ornith-session-stall-timeout-order.log
?? agent-task/m-openai-compatible-output-validation-filters/
?? scripts/e2e-openai-managed-capacity-smoke.sh
```
`.gocache/` and `.local/` are pre-existing ignored workspace material and were not added to the plan write set. The evidence path is now the v2 sanitized current-run summary produced after all four live cases passed.
### Resumed worker verification
The resumed worker reran the deterministic gates before the authorized live handoff became available:
```bash
bash -n scripts/e2e-openai-managed-capacity-smoke.sh
./scripts/e2e-openai-managed-capacity-smoke.sh --self-test
python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/dev-runtime-deploy
go test -count=1 ./apps/node/internal/node -run '^TestTunnelWatchdogFinishFrameWithoutEndStallsOnce$'
go test -count=1 ./apps/edge/internal/openai -run '^(TestOpenAIStallAfterLogicalFinishMatrix|TestOpenAISemanticGateDisabledStallTerminalMatrix|TestOpenAISemanticGateDisabledCompatibility|TestOpenAITunnelCodecTerminalWire|TestOpenAIDirectResponsesSemanticDisabledStallTerminal)$'
IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh
git diff --check
```
All corrected commands exited `0`:
```text
[managed-capacity-smoke] SELF_TEST_PASS
Skill is valid!
ok iop/apps/node/internal/node
ok iop/apps/edge/internal/openai
[openai-lemonade] OpenAI-compatible Lemonade serving test PASSED (mode=fake).
git diff --check: PASS
```
The implementation was subsequently resumed through the approved protected wrapper; the successful live and release evidence above supersedes that temporary external-blocker handoff.
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.
## Section Ownership
| Section | Owner | Note |
|---|---|---|
| Header comment, Overview, Review Agent Instructions | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
| Implementation Item Completion (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
| Implementation Checklist (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[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 placeholder text with actual content |
| Reviewer Checkpoints | Fixed at stub creation | Pre-filled from plan |
| Verification Results (section headings + commands) | Implementing agent, then review agent | Implementing agent records initial output; review agent reruns applicable commands and may fill, replace, or append fresh verified output before verdict. Implementing-agent command changes require a `Deviations from Plan` entry |
| Code Review Result | Review agent appends | Not included in stub |
## Reviewer Fresh Verification — 2026-08-13
### Local acceptance rerun
The reviewer reran the plan's executable local gates. Full stdout/stderr was captured at `/tmp/iop-code-review-plan2.LIOA8I/reviewer-local.log` with SHA-256 `38abbfc9eada6b0f2ea760896d41e94b37a2b9f83cac7f7b34d1dd2b7aaa5a82`.
```text
$ bash -n scripts/e2e-openai-managed-capacity-smoke.sh
PASS
$ ./scripts/e2e-openai-managed-capacity-smoke.sh --self-test
[managed-capacity-smoke] self-test rejected=route-selector-mismatch
[managed-capacity-smoke] self-test rejected=aggregate-capacity-claim
[managed-capacity-smoke] self-test exact_chat_and_responses_terminals=true
[managed-capacity-smoke] self-test rejected=stale-body
[managed-capacity-smoke] self-test rejected=missing-body
[managed-capacity-smoke] self-test rejected=foreign-run-body
[managed-capacity-smoke] SELF_TEST_PASS
$ python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/dev-runtime-deploy
Skill is valid!
$ go test -count=1 ./apps/node/internal/node -run '^TestTunnelWatchdogFinishFrameWithoutEndStallsOnce$'
ok iop/apps/node/internal/node 0.059s
$ go test -count=1 ./apps/edge/internal/openai -run '^(TestOpenAIStallAfterLogicalFinishMatrix|TestOpenAISemanticGateDisabledStallTerminalMatrix|TestOpenAISemanticGateDisabledCompatibility|TestOpenAITunnelCodecTerminalWire|TestOpenAIDirectResponsesSemanticDisabledStallTerminal)$'
ok iop/apps/edge/internal/openai 0.044s
$ IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh
[openai-lemonade] OpenAI-compatible Lemonade serving test PASSED (mode=fake).
$ git diff --check
PASS
```
### Remote current-run and release validation
The reviewer independently opened the four ignored remote run manifests and sanitized summaries without printing request/response bodies or credential material. For every case, the route alias resolved to one active route; `resource_selector`, non-empty profile, upstream model, connected provider snapshot, current-run owner, mtime, request/result digest, endpoint terminal, eligible capacity, peak, queue, and final recovery all matched.
```text
run=1786601559737433000-4ba66d5c48201ad1 endpoint=chat provider=onexplayer-lemonade profile=present upstream_match=true provenance=digest+mtime+owner terminal=pass capacity=3 peak=3 queue=1 final=0/0
run=1786601621300814000-c4d9c6c134fe368b endpoint=responses provider=onexplayer-lemonade profile=present upstream_match=true provenance=digest+mtime+owner terminal=pass capacity=3 peak=3 queue=1 final=0/0
run=1786601679237576000-53b4cfe86fa3dc40 endpoint=chat provider=rtx5090-lemonade profile=present upstream_match=true provenance=digest+mtime+owner terminal=pass capacity=1 peak=1 queue=1 final=0/0
run=1786601686638286000-825708e93ce3450d endpoint=responses provider=rtx5090-lemonade profile=present upstream_match=true provenance=digest+mtime+owner terminal=pass capacity=1 peak=1 queue=1 final=0/0
REMOTE_MANIFEST_VALIDATION_PASS
```
The copied remote runner and local source both hash to `bc0dcf5aa8acc6a10942a6d48e4b3248547004df04d0a292562aea2ca465a903`. Remote `dev` is clean at `019500a5d65117bfd115e0c33601ceef3461ee52`, equal to `origin/dev`; `origin/main` is `fd41adac777f7c7faea0cdd27acbe2817890abf8`; `dev-974^{tree}` is the candidate tree `d46c7e3057bafbeedc1ddb10d2619feb3c6eb2db`; the remote tag exists exactly once; `release/dev-974` no longer exists; `config check` passes; ports `18082,18083,18084,19093,19101` are open.
### Evidence projection check and reviewer repair
The reviewer parsed the tracked v2 evidence as unique key/value rows and asserted all four route/endpoint counts, terminal counts, selected capacities, peaks, queue evidence, final recovery, script hash, raw-material exclusion marker, release finish, atomic push, and release-branch deletion. The check passed. Two non-behavioral Nits were repaired directly: the implementation had translated the existing `dev-runtime-deploy` frontmatter description and `목적` heading/body into English, and one procedure line implied that the normal-only managed script accepted long requests. The unrelated language drift was restored, and the procedure now fails the normal gate on a long classification and points to the separate long-context smoke.
## Code Review Result
- **Overall Verdict:** PASS
### Dimension Assessment
| Dimension | Result | Assessment |
|---|---|---|
| Correctness | Pass | Managed capacity is computed from one authenticated route selector and its selected provider; all four live route/endpoint cases satisfy exact capacity, queue, terminal, and recovery gates. |
| Completeness | Pass | The route-qualified contract, deterministic oracle, v2 evidence replacement, retained release finish, tag, atomic push, and release cleanup are complete. |
| Test coverage | Pass | Self-test negatives cover route mismatch, aggregate capacity, terminal duplication/absence, and stale/missing/foreign provenance; focused Node/Edge and fake Lemonade regressions pass. |
| API contract | Pass | Chat and Responses preserve their endpoint-native success terminal plus one `[DONE]`, while managed route/profile/upstream binding remains authenticated and caller-neutral. |
| Code quality | Pass | Shell syntax, skill validation, diff check, bounded secret cleanup, unique 0700 run ownership, digest/mtime checks, and allowlisted summaries pass. |
| Implementation deviation | Pass | The macOS elapsed-clock correction preserved the selected gates; the only unrelated language drift was directly repaired as a Nit. |
| Verification trust | Pass | Reviewer-local reruns and independent remote manifest/digest/ref validation agree with the recorded results; no contradictory evidence remains. |
| Spec conformance | Pass | The contribution evidence satisfies the selected S03/S04/S07 terminal and raw-free operational gates for `repeat-guard,ops-evidence`; Milestone completion remains deferred to runtime aggregation. |
### Findings
- Required: None.
- Suggested: None.
- Nit (repaired): Restored the pre-existing Korean frontmatter description and `목적` text in `agent-ops/skills/project/dev-runtime-deploy/SKILL.md`; no behavior or acceptance criterion changed.
- Nit (repaired): Aligned the deployment procedure with the implemented normal-only managed smoke and directed long requests to the separate long-context admission smoke.
### Routing Signals
- `review_rework_count=2`
- `evidence_integrity_failure=false`
### Next Step
Archive the active plan/review pair, write `complete.log`, move this split task under the current month archive, and emit the `m-openai-compatible-output-validation-filters` completion metadata for runtime aggregation. Do not modify the roadmap directly.

View file

@ -0,0 +1,48 @@
<!-- task=m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness plan=2 tag=REVIEW_REVIEW_TEST milestone-task=repeat-guard,ops-evidence -->
# Complete - m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness
## 완료 일시
2026-08-13
## 요약
3회의 plan/review 루프 끝에 managed route-qualified capacity smoke, current-run provenance, 네 개 Ornith live matrix, raw-free v2 evidence, `dev-974` retained release를 모두 검증하고 최종 PASS로 종료했다.
## 루프 이력
| Plan | Review | Verdict | 메모 |
|------|--------|---------|------|
| `plan_cloud_G09_0.log` | `code_review_cloud_G09_0.log` | FAIL | semantic-disabled Chat/Responses tunnel terminal 소유권과 dev timeout-order evidence 미충족 |
| `plan_cloud_G10_1.log` | `code_review_cloud_G10_1.log` | FAIL | managed route가 접근할 수 없는 aggregate capacity와 mixed-generation 아티팩트 provenance 발견 |
| `plan_cloud_G10_2.log` | `code_review_cloud_G10_2.log` | PASS | route-qualified 네 개 live case, manifest/digest/mtime 소유권, raw-free evidence, release finish/tag/atomic push 검증 |
## 구현/정리 내용
- Managed principal route의 `resource_selector`, profile, upstream model을 하나의 healthy provider snapshot과 교차해 eligible capacity를 계산하는 Chat/Responses smoke를 추가했다.
- 각 invocation을 고유한 mode-0700 run directory, immutable run id, manifest, request/result digest, dispatch mtime, result owner에 묶고 stale/missing/foreign-run body를 fail-closed로 처리했다.
- `ornith:35b`/OneXPlayer와 `ornith-fast`/RTX5090의 Chat/Responses 네 케이스를 분리 실행해 HTTP 200, exact terminal, peak=capacity, queue>=1, final 0/0을 확인했다.
- 분쟁된 v1 evidence를 current-run sanitized v2 evidence로 교체하고 `dev-974` release finish, tag, atomic push, remote release branch 삭제를 완료했다.
- 리뷰 중 발견한 `dev-runtime-deploy` frontmatter/목적의 불필요한 영문 번역과 normal-only 스크립트의 long-context 처리 오해 가능성을 문서 Nit로 복구했다.
## 최종 검증
- `bash -n scripts/e2e-openai-managed-capacity-smoke.sh` - PASS.
- `./scripts/e2e-openai-managed-capacity-smoke.sh --self-test` - PASS; route/aggregate/terminal/provenance negative fixture가 모두 reject됨.
- `python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/dev-runtime-deploy` - PASS; `Skill is valid!`.
- `go test -count=1 ./apps/node/internal/node -run '^TestTunnelWatchdogFinishFrameWithoutEndStallsOnce$'` - PASS; `ok iop/apps/node/internal/node 0.059s`.
- `go test -count=1 ./apps/edge/internal/openai -run '^(TestOpenAIStallAfterLogicalFinishMatrix|TestOpenAISemanticGateDisabledStallTerminalMatrix|TestOpenAISemanticGateDisabledCompatibility|TestOpenAITunnelCodecTerminalWire|TestOpenAIDirectResponsesSemanticDisabledStallTerminal)$'` - PASS; `ok iop/apps/edge/internal/openai 0.044s`.
- `IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh` - PASS.
- 원격 네 run manifest/digest/mtime/owner/route/profile/upstream/terminal/capacity 독립 검증 - PASS; `REMOTE_MANIFEST_VALIDATION_PASS`.
- 원격 ref/runtime 검증 - PASS; clean `dev=origin/dev`, `dev-974` tag 1개, remote release branch 0개, `config check` PASS, 포트 `18082,18083,18084,19093,19101` open.
- tracked v2 evidence schema/redaction/matrix/release assertion - PASS.
- `git diff --check` - PASS.
## 잔여 Nit
- 없음
## 후속 작업
- 없음

View file

@ -0,0 +1,245 @@
<!-- task=m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness plan=0 tag=TEST milestone-task=repeat-guard,ops-evidence -->
# OpenAI-compatible stream terminal liveness and timeout ownership
## For the Implementing Agent
Implement the deterministic terminal-liveness regressions and the dev timeout-order rollout exactly as written. Preserve the existing protocol contract: an OpenAI-compatible `finish_reason` is releasable wire state, while `[DONE]`, provider EOF, or tunnel `END` remains the transport terminal. Do not synthesize a Core terminal from a normal `finish_reason`, change routing, or add another terminal grace mechanism. Run every verification command, fill the implementation-owned sections of `CODE_REVIEW-cloud-G09.md` with actual output, keep both active files in place, and report ready for review. If blocked, record only the exact blocker, attempted command/output, and resume condition in the review evidence; do not ask the user, call user-input tools, create control-plane stop files, classify the next state, archive logs, or write `complete.log`.
## Background
`/config/workspace/iop/HANDOFF-ORNITH-SESSION-STALL.md` records a real Ornith request in which Pi was terminated after roughly 180 seconds of silence with exit 143. The request had already produced provider content but no observed Node tunnel `END`, provider terminal, or Core terminal. The current IOP defaults allow a Node response stall to wait 300 seconds and an Edge request to wait 3600 seconds, so the external 180-second caller can terminate before IOP owns the failure and recovery boundary.
The current implementation already has typed `response_stalled` watchdog handling, Edge pre-commit recovery, post-commit sanitized termination, exact replay gates, and single-terminal fencing. The gap is narrower: the actual logical-finish-without-transport-terminal shape is not represented by a deterministic Node/Edge end-to-end regression, and the dev Ornith provider timeout order is not aligned with the 180-second caller boundary.
The active milestone `openai-compatible-output-validation-filters` already owns repeat-guard idle behavior, tunnel/codec terminal integrity, Ornith live smoke, and operational evidence. This task therefore remains a split subtask of existing milestone `[output-01]`; it does not create or promote a second milestone.
## Archive Evidence Snapshot
- Dependency evidence: `agent-task/archive/2026/07/m-openai-compatible-output-validation-filters/03_repeat_guard_s07_remote_evidence/complete.log` is `PASS` and satisfies predecessor index `03` for this `04+03` packet.
- That completion proves the previous repeat-guard remote evidence and single-terminal validation passed for milestone task `repeat-guard`.
- It does not prove the Ornith logical-finish-without-`[DONE]`/EOF/`END` stall shape or the dev timeout-order rollout; those remain owned by this packet.
## Dependencies and Execution Order
1. `03_repeat_guard_s07_remote_evidence` is complete at the exact archive path above; do not reread sibling archive tasks.
2. Complete TEST-1 and TEST-2 locally before changing or restarting any dev runtime.
3. TEST-3 may start only after local regressions pass and the remote workspace is clean, synchronized to the selected `origin/dev` release ref, and rebuilt from that same ref.
4. The deterministic tests are mandatory even if the real external stall cannot be reproduced. A live `not_reproduced` result is evidence, not a substitute for TEST-1 or TEST-2.
## Analysis
### Files Read
- `apps/node/internal/adapters/openai_compat/provider_tunnel.go`
- `apps/node/internal/node/liveness_watchdog_test.go`
- `apps/edge/internal/openai/stream_gate_tunnel_codec.go`
- `apps/edge/internal/openai/stream_gate_pipeline_test.go`
- `apps/edge/internal/openai/stream_gate_stall_recovery_test.go`
- `packages/go/execution/liveness.go`
- `scripts/e2e-openai-lemonade.sh`
- `docs/edge-local-dev-guide.md`
- `agent-spec/index.md`
- `agent-spec/openai/stream-evidence-gate.md`
- `agent-spec/runtime/edge-node-execution.md`
- `agent-spec/runtime/provider-pool-config-refresh.md`
- `agent-spec/openai/openai-compatible-surface.md`
- `agent-contract/index.md`
- `agent-contract/outer/openai-api.md`
- `agent-contract/inner/edge-node-runtime-wire.md`
- `agent-contract/inner/execution-runtime.md`
- `agent-contract/inner/edge-config-runtime-refresh.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md`
- `agent-test/local/rules.md`
- `agent-test/dev/rules.md`
- applicable local/dev Edge, Node, platform-common, and testing profiles
- applicable Edge, Node, platform-common, and testing domain rules
- `agent-ops/skills/project/dev-runtime-deploy/SKILL.md`
- `/config/workspace/iop/HANDOFF-ORNITH-SESSION-STALL.md`
- `agent-task/archive/2026/07/m-openai-compatible-output-validation-filters/03_repeat_guard_s07_remote_evidence/complete.log`
### SDD and Contract Criteria
- SDD status is approved and unlocked. This packet maps to milestone tasks `repeat-guard,ops-evidence` and preserves the approved evidence and single-terminal gates.
- Supported Chat/Responses traffic keeps StreamGate as the liveness owner even when semantic validation filters are disabled.
- `finish_reason=length` managed continuation is an approved semantic case, but normal `finish_reason=stop` is not a transport terminal and must not be promoted to one by this task.
- The typed `response_stalled` error, Node watchdog, Edge validated recovery, exact replay boundary, post-commit sanitized error, and terminal fence are existing contract behavior. This task adds missing shape coverage and operational timeout ordering; it does not redesign those contracts.
- Current inventory intentionally routes `ornith:35b` to `onexplayer-lemonade` and `rtx5090-lemonade`. No provider catalog, route, or capacity change is in scope.
### Verification Context
- Branch: `feature/openai-compatible-stream-terminal-liveness`, created with Git Flow from synchronized `dev` and published to `origin/feature/openai-compatible-stream-terminal-liveness` at base `841511472a62ec20d79eac5f800180d1de34b541` before this plan was written.
- Fresh local preflight passed for the focused Node watchdog tests, Edge terminal/stall tests, and `git diff --check` with Go `1.26.2 linux/arm64`.
- The current Node adapter relays `BODY` until provider EOF and then emits `END`; the watchdog counts response-start/body/usage as progress and treats `END` or error as terminal.
- The current Edge codec stages `finish_reason` as wire data and creates a Core terminal only for `[DONE]` or tunnel `END`. Existing tests cover the general stall-recovery matrix but not the exact finish-frame-followed-by-missing-terminal shape.
- The fake Lemonade smoke covers normal `finish_reason`/usage/`[DONE]`; it has no deterministic hang-after-finish fixture.
- Confidence is high that the 300-second IOP response-stall default loses ownership to the 180-second caller. Confidence is moderate that this was the sole real-world trigger because no provider EOF/Node `END` trace was captured; TEST-1/2 close the deterministic boundary while TEST-3 collects sanitized live evidence.
#### External Verification Preflight
- Runner/workdir: `ssh toki@toki-labs.com`, `/Users/toki/agent-work/iop-dev`, using login zsh so `/opt/homebrew/bin/git-flow` and the declared dev toolchain are available.
- Read-only inspection found the checkout on `dev`, clean but three commits behind `origin/dev`; the deployed Edge binary source identity did not match the checkout. Do not reuse that checkout or binary.
- Before rollout, fetch and cleanly synchronize to the selected latest `origin/dev` release ref, record branch/HEAD/dirty/ancestry state, and follow the project `dev-runtime-deploy` skill for a full same-ref rebuild, config check, deploy, restart, and source/build identity proof.
- Confirm ports `18082`, `18083`, `18084`, `19093`, and `19101`, connected Nodes, expected provider snapshots, and the exact deployed Edge/Node build identities before live smoke.
- Current `build/dev-runtime/edge.yaml` omits `response_stall_timeout_ms`, so the 300-second default is active. The rollout must explicitly set `120000` for both dev Ornith Lemonade provider entries and preserve an Edge request timeout longer than that value.
- Record only sanitized timing/outcome evidence in tracked files. Raw prompts, provider output, tokens, credentials, and full request/response bodies stay in ignored run artifacts and must never enter the plan, review file, evidence log, or terminal transcript.
### Test Coverage Gaps
- Node: no exact regression proves that a body containing a logical finish but lacking provider EOF/tunnel `END` expires once as typed `response_stalled` and fences late terminal frames.
- Edge: no exact matrix drives that shape through both pre-commit replay eligibility and post-commit sanitized termination while asserting no raw leakage and one Core terminal.
- Adapter-to-runtime smoke: no deterministic fake provider flushes a final content/finish frame and then remains open until cancellation.
- Operations: no tracked guide/evidence establishes `response_stall_timeout_ms < caller kill timeout < edge request timeout` for the current Ornith dev provider pool.
### Symbol References
- `apps/node/internal/adapters/openai_compat/provider_tunnel.go`: provider body relay and EOF-to-`END` emission; observe through tests, do not change terminal semantics.
- `packages/go/execution/liveness.go`: typed liveness vocabulary and terminal/progress classification; reuse without adding a new error type.
- `apps/edge/internal/openai/stream_gate_tunnel_codec.go`: `finish_reason`, `[DONE]`, usage, `END`, and single-terminal handling; preserve implementation and strengthen assertions.
- `apps/edge/internal/openai/stream_gate_stall_recovery_test.go`: pre/post-commit recovery matrix owner.
- `apps/edge/internal/openai/stream_gate_pipeline_test.go`: exact wire order and logical-vs-transport terminal assertions.
- `scripts/e2e-openai-lemonade.sh`: deterministic adapter → Node watchdog → Edge runtime fixture owner.
### Split Judgment
Keep one bounded test-and-operations packet. The local regression proves the exact event shape, and the timeout rollout proves the same liveness owner wins before the external caller. Splitting them would leave either an unqualified operational change or a regression that does not resolve the observed timeout order. No product-source or contract migration is necessary.
### Scope Rationale
- In scope: Node/Edge tests, fake Lemonade end-to-end fixture, dev timeout-order documentation, same-ref dev rollout, and sanitized evidence.
- Out of scope: provider routing/capacity changes, new fallback policy, Core/outer API schema changes, synthesizing terminal state from `finish_reason`, production deployment, Chronos/Pi timeout modification, raw prompt/output retention, and edits to common Agent-Ops rules or skills.
- If TEST-1 or TEST-2 reveals an actual source defect rather than a missing regression, stop and record the blocker. Do not expand this test plan into an unreviewed runtime fix.
### Final Routing
- `evaluation_mode=first-pass`; build and review closures are true with no capability gap.
- Finalizer: `finalize-task-policy.sh pair`.
- Build: `grade-boundary/cloud/G09`, catalog `worker/cloud/G09`, filename `PLAN-cloud-G09.md`.
- Review: `official-review/cloud/G09`, catalog `review/cloud/G09`, filename `CODE_REVIEW-cloud-G09.md`.
- Grade scores: build `2/2/1/2/2`, review `2/2/1/2/2` for scope/state/blast/evidence/verification.
- `large_indivisible_context=false`; positive loop risks are `temporal_state`, `concurrent_consistency`, `boundary_contract`, `structured_interpretation`, and `variant_product` (5).
- Recovery signals: `review_rework_count=0`, `evidence_integrity_failure=false`.
## Implementation Checklist
- [ ] [TEST-1] Add exact Node and Edge logical-finish-without-transport-terminal regressions while preserving the existing terminal contract.
- [ ] [TEST-2] Extend the fake Lemonade end-to-end smoke with a bounded hang-after-finish fixture and sanitized single-terminal assertions.
- [ ] [TEST-3] Align both dev Ornith provider response-stall timeouts, perform a same-ref rollout, update the runbook, and capture sanitized live evidence.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
### [TEST-1] Logical finish followed by transport stall
**Problem:** Current unit coverage proves general stall recovery and normal terminal ordering but does not reproduce the observed sequence: response body and logical `finish_reason`, followed by silence with no `[DONE]`, provider EOF, tunnel `END`, or Core terminal.
**Solution:** Add a Node watchdog regression named `TestTunnelWatchdogFinishFrameWithoutEndStallsOnce`. Feed response-start and a body frame containing a logical finish, withhold `END`, expire the watchdog, and assert exactly one typed `response_stalled`; late usage/`END` must be fenced and must not create a second terminal. Add an Edge table regression named `TestOpenAIStallAfterLogicalFinishMatrix` with:
- pre-commit/under-threshold repeat-guard state: pending finish wire remains hidden, one eligible alternate replay occurs, and only the recovered output plus one terminal is exposed;
- post-commit state: no replay occurs, the committed stream emits exactly one sanitized SSE error followed by `[DONE]`, pending state is cleaned up, and no provider/raw diagnostic bytes leak;
- success control: content/finish, usage, `[DONE]`, and physical `END` preserve exact byte order and produce one Core terminal;
- contract assertion: a finish frame alone does not create a Core terminal before `[DONE]` or `END`.
Do not modify the watchdog, codec, adapter, StreamGate, or liveness source unless a follow-up reviewed fix is created.
**Modified Files and Checklist:**
- [ ] `apps/node/internal/node/liveness_watchdog_test.go`: add the finish-without-`END` expiry/fence regression.
- [ ] `apps/edge/internal/openai/stream_gate_stall_recovery_test.go`: add pre/post-commit exact-shape recovery cases.
- [ ] `apps/edge/internal/openai/stream_gate_pipeline_test.go`: add logical-vs-transport terminal and exact success-order assertions.
- [ ] `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/CODE_REVIEW-cloud-G09.md`: record fresh focused/full/race outputs.
**Test Strategy:** Use fake clocks/channels and existing test helpers only. Assert typed codes, replay count, byte order, pending cleanup, raw-data absence, and exact terminal counts; do not use wall-clock sleeps or network access.
**Verification:**
```bash
go test -count=1 ./apps/node/internal/node -run '^TestTunnelWatchdogFinishFrameWithoutEndStallsOnce$'
go test -count=1 ./apps/edge/internal/openai -run '^(TestOpenAIStallAfterLogicalFinishMatrix|TestOpenAITunnelCodecTerminalWire)$'
```
Expected: both commands exit 0; the missing transport terminal becomes one typed stall, the Edge branch matches commit state, and normal terminal order remains unchanged.
### [TEST-2] Deterministic Lemonade adapter-to-Edge smoke
**Problem:** The existing fake Lemonade smoke always completes normally, so it cannot prove the real adapter/watchdog/Edge composition for a provider that flushes a logical finish and then leaves the response open.
**Solution:** Add a bounded fixture request recognized only by the fake provider. It writes content plus `finish_reason`, flushes, omits `[DONE]` and EOF, and blocks until request-context cancellation. Generate a test-only provider config with a short `response_stall_timeout_ms` such as 200 ms. Drive the request through the actual OpenAI-compatible provider adapter, Node watchdog, and Edge runtime; permit only the expected typed stall in the script's failure scan and continue rejecting every other error marker.
Assert one sanitized client terminal, no duplicate error/`[DONE]`, no raw provider diagnostic leakage, prompt handling after cancellation, and clean process teardown. Retain the existing normal content/finish/usage/`[DONE]` control and exact ordering assertions.
**Modified Files and Checklist:**
- [ ] `scripts/e2e-openai-lemonade.sh`: add the bounded hang-after-finish fixture, test-only timeout, expected-error allowlist, exact assertions, and cleanup.
- [ ] `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/CODE_REVIEW-cloud-G09.md`: record the fake-mode output without request or response bodies.
**Test Strategy:** Run fake mode locally; use deterministic synchronization and the request context rather than an unbounded sleep. The script must fail on duplicate terminals, timeout-owner mismatch, unexpected error types, leaked raw diagnostics, or a surviving fake-provider process.
**Verification:**
```bash
IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh
```
Expected: exit 0; the normal control is unchanged and the stall fixture ends once through IOP's typed/sanitized path.
### [TEST-3] Dev Ornith timeout order and sanitized evidence
**Problem:** Both current Ornith Lemonade provider entries inherit a 300-second Node response-stall timeout, while the observed caller is killed after about 180 seconds. The stale remote checkout/binary also prevents trustworthy live attribution.
**Solution:** Document and apply this dev-only ordering for both `onexplayer-lemonade` and `rtx5090-lemonade` Ornith provider entries:
```text
response_stall_timeout_ms = 120000
caller kill timeout = approximately 180000
edge request timeout > 180000
```
Follow `dev-runtime-deploy` from a clean latest `origin/dev` ref using login zsh. Run config check/dry-run, require the expected restart-required classification, rebuild/deploy/restart all applicable same-ref binaries, and re-prove source/build/runtime identities before smoke. Do not change route selection.
Run a direct-provider control and an Edge request with the same approximately 41k-token input shape and a caller boundary equivalent to the observed 180 seconds. For each provider selected by the current inventory, capture sanitized monotonic timings for response start, last body, logical finish, `[DONE]`/EOF/`END`/Core terminal when observable, timeout owner, recovery decision, and final client outcome. A post-commit stall must emit IOP's explicit sanitized terminal before 180 seconds; an eligible pre-commit stall must perform one exact replay. A normal success must preserve finish/usage/`[DONE]` order and one terminal. If the real provider stall is not reproduced, record `not_reproduced` with the successful control and timeout configuration; do not manufacture a failure or weaken TEST-1/2.
Write only a raw-free summary to the tracked evidence log. Keep the request, raw output, tokens, provider payloads, and any sensitive runtime details under an ignored run-artifact directory.
**Modified Files and Checklist:**
- [ ] `docs/edge-local-dev-guide.md`: document response-stall ownership, safe timeout ordering, config validation/restart requirement, and raw-evidence handling.
- [ ] `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/evidence/2026-08-13-ornith-session-stall-timeout-order.log`: record sanitized source/build/config identities, provider/result matrix, monotonic timing summary, terminal counts, and reproduction status.
- [ ] `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/CODE_REVIEW-cloud-G09.md`: record rollout commands/results and the exact tracked evidence path without secrets.
**Test Strategy:** Treat dev as external integration evidence after deterministic local qualification. Validate both configuration and runtime identity before requests, use the current provider mapping without substitution, and retain a successful normal-stream control beside any stall result.
**Verification:**
```bash
ssh toki@toki-labs.com '/bin/zsh -lc '\''cd /Users/toki/agent-work/iop-dev && git status --short --branch && git rev-parse HEAD && git rev-list --left-right --count HEAD...origin/dev && go version'\'''
```
Then execute the exact config-check, build, deploy, restart, health, direct-provider, and Edge smoke commands required by `agent-ops/skills/project/dev-runtime-deploy/SKILL.md`, recording sanitized output in the review file and evidence log.
Expected: the remote checkout and deployed artifacts share one clean release ref; both Ornith providers report 120000 ms response-stall ownership; health is restored; normal controls have one terminal; any stall is owned by IOP before the caller's 180-second boundary.
## Final Verification
```bash
go test -count=1 ./apps/node/internal/node -run '^TestTunnelWatchdogFinishFrameWithoutEndStallsOnce$'
go test -count=1 ./apps/edge/internal/openai -run '^(TestOpenAIStallAfterLogicalFinishMatrix|TestOpenAITunnelCodecTerminalWire)$'
IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh
go test -count=1 ./packages/go/execution ./packages/go/config ./apps/node/internal/node ./apps/edge/internal/openai
go test -race -count=1 ./apps/node/internal/node ./apps/edge/internal/openai
git diff --check
git status --short --branch
```
Expected: all tests and smoke commands exit 0, race tests report no race, diff check is silent, and only the declared plan/review/implementation/evidence changes are present. TEST-3 additionally requires the same-ref remote identity, health, timeout-order, single-terminal, and sanitized-evidence criteria above.
## Modified Files Summary
| File | Items |
|---|---|
| `apps/node/internal/node/liveness_watchdog_test.go` | TEST-1 |
| `apps/edge/internal/openai/stream_gate_stall_recovery_test.go` | TEST-1 |
| `apps/edge/internal/openai/stream_gate_pipeline_test.go` | TEST-1 |
| `scripts/e2e-openai-lemonade.sh` | TEST-2 |
| `docs/edge-local-dev-guide.md` | TEST-3 |
| `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/evidence/2026-08-13-ornith-session-stall-timeout-order.log` | TEST-3 |
| `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/CODE_REVIEW-cloud-G09.md` | TEST-1, TEST-2, TEST-3 |

View file

@ -0,0 +1,321 @@
<!-- task=m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness plan=1 tag=REVIEW_TEST milestone-task=repeat-guard,ops-evidence -->
# Restore semantic-disabled stream terminals and finish timeout evidence
## For the Implementing Agent
Implement the reviewer-selected R1 runtime fix and R2 rollout exactly as written. Do not investigate another cause, choose another sink/source design, narrow the Chat/Responses matrix, or change provider routing/capacity. Run every verification command, fill the implementation-owned sections of `CODE_REVIEW-cloud-G10.md` with actual output, keep both active files in place, and report ready for review. If blocked, record only the exact blocker, attempted commands/output, and resume condition in implementation-owned evidence fields; do not ask the user, call user-input tools, create control-plane stop files, classify the next state, archive logs, or write `complete.log`.
## Background
The first review reproduced the fake Lemonade logical-finish stall with one visible prefix and finish frame but no `run_error` or `[DONE]`. The request-local StreamGate and typed stall remain active when semantic filters are disabled, but tunnel endpoint framing and terminal serialization were incorrectly gated by the semantic switch. The original TEST-3 dev evidence was correctly stopped after that defect surfaced and must resume only after the selected runtime fix passes locally.
## Archive Evidence Snapshot
- Prior plan: `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/plan_cloud_G09_0.log`.
- Prior review: `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/code_review_cloud_G09_0.log`, verdict `FAIL`, `Required R1` semantic-disabled Chat/Responses tunnel terminal ownership and `Required R2` unfinished dev timeout-order evidence; no Suggested or Nit findings.
- Fresh reviewer evidence: focused Node/Edge tests, full package tests, race tests, script syntax, and `git diff --check` passed; `IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh` failed with `prefix=1 finish=1 run_error=0 done=0`.
- Dev preflight: `/Users/toki/agent-work/iop-dev` was clean on `release/dev-971` at `003398a149c1433cb21bc8aa2720bde272b87373`, equal to `origin/dev`; Edge/Node binaries reported source `003398a149c1`, and ports `18082`, `18083`, `18084`, `19093`, `19101` were listening.
- Roadmap carryover: this packet still contributes only `repeat-guard,ops-evidence`; it does not complete or update the Milestone directly.
## Finding Resolution Map
| Finding | Reviewer evidence | Exact root cause | Selected fix | Mode | Changed precondition | Acceptance commands |
|---|---|---|---|---|---|---|
| Required R1 | Fake Lemonade smoke exits 1 with `prefix=1 finish=1 run_error=0 done=0`; semantic-disabled branches at `stream_gate_runtime.go:1241-1246,1836-1841,1867-1872` and `responses_stream_gate.go:1180-1185,1279-1286` bypass endpoint framing/terminal sinks. | Endpoint framing was coupled to semantic-filter activation. The Core commits a typed stall terminal, but an already-open raw sink lacks endpoint identity or a Responses renderer. | Always use endpoint event sources for supported tunnels; select the existing Responses pool sink/codec state for direct and provider-pool streaming Responses independent of semantic filters; add semantic-disabled Chat/Responses pre/post-commit and direct Responses regressions while preserving exact success wire. | direct-fix | The fake smoke and new regressions now exercise the fixed endpoint-aware product path instead of the unchanged broken branch. | `go test -count=1 ./apps/edge/internal/openai -run '^(TestOpenAIStallAfterLogicalFinishMatrix|TestOpenAISemanticGateDisabledStallTerminalMatrix|TestOpenAISemanticGateDisabledCompatibility|TestOpenAITunnelCodecTerminalWire)$'`; `IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh`; full/race commands below. |
| Required R2 | TEST-3 was not run and its guide/evidence files are absent; read-only dev preflight proves the declared runner and current services are reachable. | The prior plan's correct stop condition prohibited rollout after TEST-2 exposed R1. | Only after R1 local qualification passes, apply `response_stall_timeout_ms: 120000` to the two Ornith providers, execute the project dev-runtime same-ref validation/rebuild/deploy/restart path without route/capacity changes, run direct and Edge controls, update the guide, and write the raw-free evidence log. | direct-fix | R1 makes local TEST-2 pass, removing the stop condition before external rollout/evidence. | Remote preflight/config/deploy/health/identity commands from `dev-runtime-deploy`; direct/Edge Ornith controls; tracked evidence checks and final commands below. |
## Dependencies and Execution Order
1. Archived predecessor `03_repeat_guard_s07_remote_evidence` remains satisfied by the prior packet evidence; do not search sibling archives.
2. Complete REVIEW_TEST-1, then pass every REVIEW_TEST-2 local/fake/full/race check before changing dev runtime state.
3. Start REVIEW_TEST-3 only after the local stop condition is cleared. Follow `dev-runtime-deploy`; do not deploy an arbitrary dirty or non-`dev` source ref. If its clean-source/release preconditions are not satisfied, record the exact state and resume condition rather than weakening same-ref identity.
## Analysis
### Files Read
- `apps/edge/internal/openai/stream_gate_runtime.go`
- `apps/edge/internal/openai/responses_stream_gate.go`
- `apps/edge/internal/openai/stream_gate_release_sink.go`
- `apps/edge/internal/openai/stream_gate_tunnel_codec.go`
- `apps/edge/internal/openai/provider_tunnel.go`
- `apps/edge/internal/openai/stream_gate_stall_recovery_test.go`
- `apps/edge/internal/openai/stream_gate_pipeline_test.go`
- `apps/node/internal/node/liveness_watchdog_test.go`
- `scripts/e2e-openai-lemonade.sh`
- `docs/edge-local-dev-guide.md`
- `agent-spec/runtime/stream-evidence-gate.md`
- `agent-spec/runtime/edge-node-execution.md`
- `agent-spec/runtime/provider-pool-config-refresh.md`
- `agent-spec/input/openai-compatible-surface.md`
- `agent-contract/outer/openai-compatible-api.md`
- `agent-contract/inner/execution-runtime.md`
- `agent-contract/inner/edge-node-runtime-wire.md`
- `agent-contract/inner/edge-config-runtime-refresh.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-test/local/node-smoke.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-test/local/testing-smoke.md`
- `agent-test/dev/rules.md`
- `agent-test/dev/edge-smoke.md`
- `agent-test/dev/node-smoke.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/node/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-ops/skills/project/dev-runtime-deploy/SKILL.md`
- `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/plan_cloud_G09_0.log`
- `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/code_review_cloud_G09_0.log`
### SDD Criteria
- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md`; status approved, lock released.
- Milestone contribution: `repeat-guard,ops-evidence`.
- Acceptance scenarios: S03/S04 require stream-open recovery/safe-stop to converge on one endpoint terminal; S07 requires deterministic generic smoke plus sanitized Ornith dev evidence and permits `not_reproduced` only beside deterministic coverage.
- Evidence Map: S03/S04 drive the semantic-disabled pre/post-commit single-terminal regressions and retained `[DONE]`/side-effect boundaries. S07 drives the fake adapter smoke, two-provider dev timeout ordering, raw-free guide/evidence artifact, and live result matrix. These rows define REVIEW_TEST-1 through REVIEW_TEST-3 and the final verification.
### Verification Context
- Handoff source: the archived G09 review contains implementation output plus fresh reviewer reruns. Repository-native source/contract reads independently confirm the same root cause and selected fix.
- Exact local failure: `IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh` exits 1 with `run_error=0 done=0`; focused/full/race tests otherwise pass.
- Contract criteria: `agent-contract/outer/openai-compatible-api.md:120-126` and `agent-contract/inner/execution-runtime.md:52` require one always-owned runtime, semantic-disabled native compatibility, private typed-stall ownership, and one sanitized terminal.
- Constraints: no synthesized Core terminal from `finish_reason`; no route/capacity change; no raw prompt/output/tool/auth in tracked artifacts; no common Agent-Ops edits; config timeout change remains restart-required.
- Confidence: high. The deterministic smoke, source branches, codec binding, and sink predicates form one directly observed causal chain.
#### External Verification Preflight
- Runner/workdir: `ssh toki@toki-labs.com`, `/Users/toki/agent-work/iop-dev`, login zsh.
- Observed state: clean `release/dev-971`, HEAD/origin-dev `003398a149c1433cb21bc8aa2720bde272b87373`, divergence `0 0`, Go `1.26.3 darwin/arm64`, git-flow `1.12.3`.
- Artifacts: `build/dev-runtime/bin/edge` and `build/dev-runtime/bin/iop-node` report module source `003398a149c1`; config is `build/dev-runtime/edge.yaml`.
- Runtime: ports `18082`, `18083`, `18084`, `19093`, and `19101` listen. Re-prove branch/ref/artifact/process identity immediately before deployment; this snapshot is evidence, not permission to reuse a stale candidate.
- Setup/rebuild: follow `agent-ops/skills/project/dev-runtime-deploy/SKILL.md` from clean synchronized `dev`, reuse only the matching release state allowed by that skill, run sequential full tests, build all four binaries from one `DEPLOY_SHA`, require expected `restart_required`, restart Edge then Nodes, and verify connected provider snapshots before smoke.
### Test Coverage Gaps
- Current `TestOpenAIStallAfterLogicalFinishMatrix` sets semantic filtering true only; add exact false cases.
- Current semantic-disabled compatibility tests prove successful byte order but not typed-stall pre/post-commit termination.
- Streaming Responses semantic-disabled provider-pool and direct tunnel post-commit failure serialization need explicit regressions.
- The fake Lemonade composition already supplies the exact Chat regression and must become green without weakening its assertions.
- Dev timeout ordering, same-ref rollout identity, and raw-free Ornith outcome evidence remain absent.
### Symbol References
- No symbol rename/removal is selected.
- Change sites: `newOpenAIChatAttemptEventSourceFactory`, `buildOpenAITunnelStreamGateRuntime`, `runOpenAITunnelStreamGate`, `buildOpenAIResponsesStreamGateRuntimeFromAttempt`, and `runOpenAIResponsesStreamGateAttempt`.
- Existing callers remain unchanged: direct Chat/Responses tunnel paths in `provider_tunnel.go`, provider-pool Chat in `stream_gate_runtime.go`, and provider-pool Responses in `responses_stream_gate.go`.
### Split Judgment
Keep one follow-up packet. Endpoint-source selection and endpoint-native terminal rendering form one correctness invariant across direct/pool and Chat/Responses variants; local tests and fake smoke must close before the dev timeout rollout can safely supply the required operational evidence. Splitting R2 now would recreate the stop-condition gap and cannot independently PASS.
### Scope Rationale
- In scope: the five selected OpenAI runtime/test/script files, the two inherited completed regression files, the dev guide, exact evidence log, and active review evidence.
- Out of scope: Node watchdog/adapter behavior changes, new liveness error types, Core filter/recovery algorithms, public/wire/config schema changes, provider routing/capacity, production deployment, caller timeout changes, and common Agent-Ops files.
- `stream_gate_release_sink.go` and `stream_gate_tunnel_codec.go` are read-only: the existing Responses pool sink already preserves raw success wire, tracks its endpoint-native sequence state, and serializes the required error terminal.
- Existing TEST-1 modifications in `liveness_watchdog_test.go` and `stream_gate_pipeline_test.go` are retained and reverified, not redesigned.
### Final Routing
- `evaluation_mode=isolated-reassessment`; all build/review scope, context, verification, evidence, ownership, and decision closures are true; no capability gap.
- Finalizer: `finalize-task-policy.sh pair`.
- Build: scores `2/2/2/2/2`, grade `G10`, route basis `grade-boundary`, lane `cloud`, catalog `worker/cloud/G10`, filename `PLAN-cloud-G10.md`.
- Review: scores `2/2/2/2/2`, route `official-review/cloud/G10`, catalog `review/cloud/G10`, filename `CODE_REVIEW-cloud-G10.md`.
- `large_indivisible_context=false`; positive loop risks are `temporal_state`, `concurrent_consistency`, `boundary_contract`, `structured_interpretation`, and `variant_product` (5).
- Recovery signals: `review_rework_count=1`, `evidence_integrity_failure=false`.
## Implementation Checklist
- [x] [REVIEW_TEST-1] Restore endpoint-aware semantic-disabled Chat/Responses tunnel terminal behavior and add exact pre/post-commit regressions.
- [x] [REVIEW_TEST-2] Rerun the bounded fake Lemonade adapter-to-Edge smoke plus focused, full, race, syntax, and diff qualification.
- [x] [REVIEW_TEST-3] After local qualification, apply the two-provider dev timeout order, perform same-ref rollout/health checks, update the runbook, and capture sanitized Ornith evidence.
- [x] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
### [REVIEW_TEST-1] Endpoint-aware semantic-disabled terminal ownership
**Problem:** `apps/edge/internal/openai/stream_gate_runtime.go:1241-1246,1836-1841,1867-1872` and `apps/edge/internal/openai/responses_stream_gate.go:1180-1185` select an endpoint-unaware source when semantic filtering is disabled. Generic direct Responses and `apps/edge/internal/openai/responses_stream_gate.go:1279-1286` also select a raw tunnel sink instead of the existing Responses-native streaming sink. The private typed stall therefore reaches Core but can leave the client stream unterminated.
**Solution:** Decouple endpoint parsing/rendering from semantic-filter policy. Always bind the known supported endpoint codec, retain exact raw wire on success, and give both Chat and Responses a single sanitized post-commit terminal.
Before (`stream_gate_runtime.go:1836-1841`):
```go
var src *openAITunnelEventSource
if semanticEnabled {
src = newOpenAITunnelEndpointEventSource(transport.tunnel.Stream(), transport.tunnel.WaitTimeout(), rewriter, assembler, req.endpoint, state)
} else {
src = newOpenAITunnelEventSource(transport.tunnel.Stream(), transport.tunnel.WaitTimeout(), rewriter, assembler, state)
}
```
After:
```go
src := newOpenAITunnelEndpointEventSource(
transport.tunnel.Stream(), transport.tunnel.WaitTimeout(),
rewriter, assembler, req.endpoint, state,
)
```
Apply the same unconditional endpoint-source construction at the Chat pool attempt factory, generic tunnel initial attempt, and Responses attempt factory. Do not remove `semanticEnabled`; keep it only where semantic registry, compatibility messages, and graceful-close behavior genuinely differ.
Before (`responses_stream_gate.go:1279-1286`):
```go
if dc.responsesRequestContext.envelope.Stream {
if s.streamGateSemanticEnabled() {
sink = newOpenAIResponsesPoolReleaseSink(w, holder, selector)
} else {
flusher, _ := w.(http.Flusher)
sink = newOpenAICompositeReleaseSink(selector, normalized, newOpenAITunnelReleaseSink(w, flusher))
}
}
```
After:
```go
if dc.responsesRequestContext.envelope.Stream {
sink = newOpenAIResponsesPoolReleaseSink(w, holder, selector)
}
```
Before (`stream_gate_runtime.go:1949-1956`):
```go
var sink *openAITunnelReleaseSink
if req.stream {
sink = newOpenAITunnelReleaseSink(w, flusher)
} else {
sink = newOpenAIBufferedTunnelReleaseSink(w, flusher, req.requestModel)
}
```
After: use `openAIStreamGateSink` and select `newOpenAIResponsesPoolReleaseSink` with a tunnel selector when `req.stream && req.endpoint == openAIRebuildEndpointResponses`; keep the existing tunnel sinks for Chat and non-stream requests. In `buildOpenAITunnelStreamGateRuntime`, resolve the Responses pool codec via `openAIResponsesTunnelCodecStateForSink`, set its usage holder once, and bind every initial/recovery attempt from the actual transport dispatch before events flow. This existing sink owns raw Responses sequence observation, successful raw terminal release, and one sanitized error plus `[DONE]`. Do not add a second error renderer, replay staged provider terminal wire on failure, expose raw failure data, or change pre-commit provider-error/status behavior.
**Modified Files and Checklist:**
- [x] `apps/edge/internal/openai/stream_gate_runtime.go`: make known endpoint codec construction independent of semantic filters for Chat/generic tunnel initial and recovery attempts.
- [x] `apps/edge/internal/openai/responses_stream_gate.go`: make Responses tunnel sources endpoint-aware and streaming pool sink endpoint-native regardless of semantic filters.
- [x] `apps/edge/internal/openai/stream_gate_stall_recovery_test.go`: add `TestOpenAISemanticGateDisabledStallTerminalMatrix` covering Chat/Responses pool pre/post-commit ownership, no replay after commit, raw leakage absence, and exact terminal counts.
- [x] `apps/edge/internal/openai/stream_gate_pipeline_test.go`: preserve inherited codec assertions and add the direct Responses runtime terminal case beside the existing generic tunnel lifecycle fixtures.
**Test Strategy:** Add deterministic scripted tunnel frames and typed `confirmedStallFailure`; no wall-clock sleep or network. Pre-commit cases assert one allowed replay and hidden rejected wire. Post-commit Chat asserts one `run_error` and `[DONE]`; Responses asserts one `type:error`, one `[DONE]`, no `response.completed`, no raw provider message/metadata, and one transport close. Retain `TestOpenAISemanticGateDisabledCompatibility` byte-for-byte Chat/Responses success assertions as the compatibility oracle.
**Verification:**
```bash
go test -count=1 ./apps/edge/internal/openai -run '^(TestOpenAIStallAfterLogicalFinishMatrix|TestOpenAISemanticGateDisabledStallTerminalMatrix|TestOpenAISemanticGateDisabledCompatibility|TestOpenAITunnelCodecTerminalWire)$'
```
Expected: exit 0; semantic true/false supported paths converge on one safe terminal, successful disabled-semantic tunnel bytes remain exact, and `finish_reason` alone never becomes a Core terminal.
### [REVIEW_TEST-2] Deterministic adapter composition and local qualification
**Problem:** `scripts/e2e-openai-lemonade.sh:399-405` correctly observes one prefix/finish but zero error/done on the current source. The existing TEST-1 regressions pass because their logical-finish matrix enables semantic filtering.
**Solution:** Keep the bounded fixture, 200 ms Node timeout, request-context cancellation, raw-leak guard, post-cancel health control, and exact count assertions unchanged in meaning. After REVIEW_TEST-1, rerun it through the actual fake provider → adapter → Node watchdog → Edge runtime. Only make script changes if required to keep the already-selected deterministic oracle stable; do not allowlist missing terminals or relax counts.
Before (`scripts/e2e-openai-lemonade.sh:399-405`):
```bash
HANG_ERROR_COUNT=$(grep -c '"type":"run_error"' "$HANG_OUT" || true)
HANG_DONE_COUNT=$(grep -c '^data: \[DONE\]$' "$HANG_OUT" || true)
if [ "$HANG_PREFIX_COUNT" -ne 1 ] || [ "$HANG_FINISH_COUNT" -ne 1 ] || [ "$HANG_ERROR_COUNT" -ne 1 ] || [ "$HANG_DONE_COUNT" -ne 1 ]; then
echo "[openai-lemonade] hang fixture terminal counts prefix=$HANG_PREFIX_COUNT finish=$HANG_FINISH_COUNT run_error=$HANG_ERROR_COUNT done=$HANG_DONE_COUNT"
exit 1
fi
```
After: retain the same exact `1/1/1/1` contract and make no assertion downgrade. The runtime fix must make it pass.
**Modified Files and Checklist:**
- [x] `scripts/e2e-openai-lemonade.sh`: retain the exact bounded semantic-disabled Chat oracle; adjust only deterministic fixture mechanics if required by the selected runtime fix.
- [x] `apps/node/internal/node/liveness_watchdog_test.go`: preserve and rerun the completed finish-without-END fence regression.
- [x] `apps/edge/internal/openai/stream_gate_pipeline_test.go`: preserve and rerun the completed logical-vs-transport terminal/byte-order regression alongside REVIEW_TEST-1's direct Responses case.
**Test Strategy:** The fake smoke is the product composition regression. Focused unit tests isolate endpoint/source/sink behavior; full and race suites catch shared state, codec queue, terminal fencing, and compatibility regressions. Fresh `-count=1` output is mandatory; cached results are not acceptable.
**Verification:**
```bash
bash -n scripts/e2e-openai-lemonade.sh
go test -count=1 ./apps/node/internal/node -run '^TestTunnelWatchdogFinishFrameWithoutEndStallsOnce$'
go test -count=1 ./apps/edge/internal/openai -run '^(TestOpenAIStallAfterLogicalFinishMatrix|TestOpenAISemanticGateDisabledStallTerminalMatrix|TestOpenAISemanticGateDisabledCompatibility|TestOpenAITunnelCodecTerminalWire)$'
IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh
go test -count=1 ./packages/go/execution ./packages/go/config ./apps/node/internal/node ./apps/edge/internal/openai
go test -race -count=1 ./apps/node/internal/node ./apps/edge/internal/openai
git diff --check
```
Expected: every command exits 0; the fake fixture reports no count mismatch, cancels and reaps cleanly, normal post-cancel traffic succeeds, package/race tests pass, and diff check is silent.
### [REVIEW_TEST-3] Dev Ornith timeout order and sanitized evidence
**Problem:** The declared dev config still needs an explicit 120-second Node response-stall boundary for both Ornith providers, and neither the guide nor the 2026-08-13 raw-free evidence artifact exists. The external caller boundary is approximately 180 seconds, so the default 300 seconds does not preserve IOP timeout ownership.
**Solution:** Only after REVIEW_TEST-2 passes, follow `dev-runtime-deploy` from clean synchronized `dev`. Change the ignored runtime config values only for `onexplayer-lemonade` and `rtx5090-lemonade`, preserve their current capacity/priority/routes, and ensure the Edge request hard timeout remains above 180 seconds. `response_stall_timeout_ms` is restart-required: run config check and dry-run, perform the full same-ref rebuild/restart and identity/health proof, then execute direct-provider and Edge approximately 41k-token controls for both providers. Record only sanitized timings/outcomes and `not_reproduced` when applicable.
Before (`docs/edge-local-dev-guide.md:70-110`): the provider-operation section documents model/provider baselines but not response-stall timeout ownership.
After: add one concise subsection specifying:
```text
Node provider response_stall_timeout_ms = 120000
external caller boundary ≈ 180000
Edge request hard timeout > 180000
```
Document that the config change requires restart, same-ref identity must be re-proved, and tracked evidence excludes prompt/output/token/credential material.
**Modified Files and Checklist:**
- [x] `docs/edge-local-dev-guide.md`: document timeout ownership, restart validation, same-ref proof, and raw-evidence policy.
- [x] `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/evidence/2026-08-13-ornith-session-stall-timeout-order.log`: record sanitized source/build/config identities, two-provider direct/Edge result matrix, monotonic timing summary, terminal counts, timeout owner/recovery decision, and reproduction status.
- [x] `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/CODE_REVIEW-cloud-G10.md`: record exact sanitized rollout and smoke stdout/stderr or ignored artifact paths.
**Test Strategy:** This is required dev integration evidence, not a substitute for deterministic tests. Apply the current inventory mapping (`onexplayer-lemonade` capacity 3, `rtx5090-lemonade` capacity 1) without substitution. Preserve raw request/response material only under ignored `agent-test/runs/**`; tracked output contains safe refs, timeout values, monotonic durations, counts, and outcome labels only.
**Verification:**
```bash
ssh toki@toki-labs.com '/bin/zsh -lc '\''cd /Users/toki/agent-work/iop-dev && git status --short --branch && git rev-parse HEAD && git rev-parse origin/dev && git rev-list --left-right --count HEAD...origin/dev && go version && git flow version | head -n 1'\'''
```
Then execute the exact inventory, clean-sync/release, sequential full-test, four-binary rebuild, `config check`, `config refresh --help`, expected `config refresh --mode dry-run` restart-required, Edge/Node deployment/restart, port/node/provider snapshot, direct-provider, Edge Chat/Responses, capacity, and release-finish commands required by `agent-ops/skills/project/dev-runtime-deploy/SKILL.md`. Do not finish or push the release unless every skill-mandated test/capacity smoke succeeds. Record sanitized actual output in the review and evidence file.
Expected: one clean selected release source owns all deployed binaries; both Ornith provider entries report `120000`; Edge timeout is above 180 seconds; ports/nodes/provider snapshots recover; every normal control terminates once; a reproduced stall is owned by IOP before the caller boundary or is recorded as `not_reproduced`; tracked evidence contains no raw or secret material.
## Final Verification
```bash
go test -count=1 ./apps/node/internal/node -run '^TestTunnelWatchdogFinishFrameWithoutEndStallsOnce$'
go test -count=1 ./apps/edge/internal/openai -run '^(TestOpenAIStallAfterLogicalFinishMatrix|TestOpenAISemanticGateDisabledStallTerminalMatrix|TestOpenAISemanticGateDisabledCompatibility|TestOpenAITunnelCodecTerminalWire)$'
IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh
go test -count=1 ./packages/go/execution ./packages/go/config ./apps/node/internal/node ./apps/edge/internal/openai
go test -race -count=1 ./apps/node/internal/node ./apps/edge/internal/openai
git diff --check
git status --short --branch
```
Expected: all local commands exit 0 with fresh output; only files listed below and the active task artifacts are changed. REVIEW_TEST-3 additionally satisfies the same-ref deployment, expected restart, health, timeout inequality, exact terminal count, and raw-free evidence criteria above.
After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
## Modified Files Summary
| File | Items |
|---|---|
| `apps/edge/internal/openai/stream_gate_runtime.go` | REVIEW_TEST-1 |
| `apps/edge/internal/openai/responses_stream_gate.go` | REVIEW_TEST-1 |
| `apps/edge/internal/openai/stream_gate_stall_recovery_test.go` | REVIEW_TEST-1 |
| `apps/node/internal/node/liveness_watchdog_test.go` | REVIEW_TEST-2 |
| `apps/edge/internal/openai/stream_gate_pipeline_test.go` | REVIEW_TEST-1, REVIEW_TEST-2 |
| `scripts/e2e-openai-lemonade.sh` | REVIEW_TEST-2 |
| `docs/edge-local-dev-guide.md` | REVIEW_TEST-3 |
| `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/evidence/2026-08-13-ornith-session-stall-timeout-order.log` | REVIEW_TEST-3 |
| `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/CODE_REVIEW-cloud-G10.md` | REVIEW_TEST-1, REVIEW_TEST-2, REVIEW_TEST-3 |

View file

@ -0,0 +1,241 @@
<!-- task=m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness plan=2 tag=REVIEW_REVIEW_TEST milestone-task=repeat-guard,ops-evidence -->
# Route-aware managed capacity smoke and single-run evidence
## For the Implementing Agent
Implement the reviewer-selected R1/R2 fixes exactly as written. Keep product routing, provider capacities, timeout values, and OpenAI-compatible runtime behavior unchanged. Add the route-aware smoke and its deterministic self-test, update the project deployment/test contracts and guide, then run the authorized dev verification against the retained release candidate. Run every verification command, fill implementation-owned sections of `CODE_REVIEW-cloud-G10.md` with actual output, keep both active files in place, and report ready for review. If blocked, record only the exact blocker, attempted command/output, and resume condition in the review evidence; do not ask the user, call user-input tools, create control-plane stop files, classify the next state, archive logs, or write `complete.log`.
## Background
The second review proved the runtime terminal fix locally, but the dev capacity qualification measured an aggregate catalog capacity that the authenticated managed route could not reach and mixed prior response bodies into the current run summary. A route-qualified smoke must derive eligible capacity from the authenticated route selector, keep normal-capacity and long-context checks distinct, and bind every summarized artifact to one unique invocation. The retained release may finish only after both projected Ornith aliases pass Chat and Responses with exact terminal and recovery evidence.
## Archive Evidence Snapshot
- Prior plan: `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/plan_cloud_G10_1.log`.
- Prior review: `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/code_review_cloud_G10_1.log`, verdict `FAIL`, with `Required R1` for route-ineligible aggregate capacity and `Required R2` for mixed-generation artifact provenance; no Suggested or Nit findings.
- Fresh reviewer evidence: local Node/Edge focused, fake Lemonade, selected full-package, race, syntax, and diff checks passed. Read-only dev preflight showed `ornith:35b -> onexplayer-lemonade`, `ornith-fast -> rtx5090-lemonade`, normal capacities `3/1`, long-context capacities `2/1`, and a 30,000-token long-context threshold.
- Failure evidence: the five-request `ornith:35b` run used a roughly 369,000-rune input, was classified long by the repository estimator, reached OneXPlayer `in_flight=2, queued=3`, and never exercised RTX5090. Eight response bodies summarized as current results were older 59-byte artifacts from a prior 401 attempt.
- Roadmap carryover: this packet still contributes only `repeat-guard,ops-evidence`; PASS is contribution evidence and does not directly complete or update the Milestone.
## Finding Resolution Map
| Finding | Reviewer evidence | Root cause | Selected fix | Mode | Changed precondition | Acceptance commands |
|---|---|---|---|---|---|---|
| Required R1 | The active managed routes resolve `ornith:35b` only to `onexplayer-lemonade` and `ornith-fast` only to `rtx5090-lemonade`; `principal_routes.go:291-305` excludes non-selected providers. The prior long request reached OneXPlayer's two long slots, while Chat policy raised the caller's 900-token cap to the catalog minimum. | Deployment/test guidance treated a catalog model group's physical providers as one caller-reachable pool without intersecting the authenticated route selector, and reused a long liveness request as a normal-capacity oracle. | Make the project deployment skill and dev Edge profile derive managed capacity from the authenticated route selector and selected provider snapshot. Add one smoke that runs Chat and Responses separately with a short 700-1200-token structured-answer request, bounded thinking, `eligible capacity + 1`, exact endpoint terminal checks, selected-provider peak/queue/final recovery, and no route/capacity mutation. Document the distinction and run `ornith:35b`/OneXPlayer and `ornith-fast`/RTX5090 separately. | direct-fix | Each live case is now bound to one authenticated route, one selected provider, the actual normal context class, and only that provider's eligible capacity. | `bash -n scripts/e2e-openai-managed-capacity-smoke.sh`; `./scripts/e2e-openai-managed-capacity-smoke.sh --self-test`; `python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/dev-runtime-deploy`; both live route matrices return HTTP 200, exact success terminals, peak equal to selected eligible capacity, queue at least one, and final selected counters `0/0`. |
| Required R2 | The fixed raw directory retained older 59-byte SSE bodies while newer curl stderr represented the current timeouts; the summary paired them. The handwritten `approx_input_tokens=41000` label contradicted the actual emitted payload and repository estimator. | The task-local wrapper reused one directory, summarized paths outside a current-run manifest, and used a handwritten request-size label. | Give every invocation a mode-0700 unique run directory and run id. Create fresh per-case paths only inside it, bind request/result mtimes and the summary row to the run manifest, derive rune count, `runes/4 + runes/16` estimate, and context class from the emitted request, and fail closed on missing or mismatched current-run bodies. Generate the tracked evidence only from the new sanitized summary. | direct-fix | A stale body, wrong run id, non-current mtime, missing result, handwritten estimate, or unsafe summary field now fails before evidence is accepted. | The self-test rejects a stale-body fixture and proves two invocations use distinct 0700 directories; live summaries carry one run id with computed request shape and current result provenance; `git diff --check` passes. |
## Analysis
### Files Read
- `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/plan_cloud_G10_1.log`
- `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/code_review_cloud_G10_1.log`
- `agent-ops/skills/project/dev-runtime-deploy/SKILL.md`
- `agent-test/dev/rules.md`
- `agent-test/dev/edge-smoke.md`
- `agent-test/dev/testing-smoke.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `scripts/e2e-provider-capacity-smoke.sh`
- `scripts/e2e-long-context-admission-smoke.sh`
- `apps/control-plane/cmd/control-plane/credential_http_handlers.go`
- `apps/control-plane/internal/credentialops/service.go`
- `apps/edge/internal/openai/principal_routes.go`
- `apps/edge/internal/openai/chat_policy.go`
- `apps/edge/internal/openai/input_estimator.go`
- `apps/edge/internal/openai/responses_types.go`
- `apps/edge/internal/openai/chat_handler.go`
- `apps/edge/internal/openai/responses_handler.go`
- `docs/edge-local-dev-guide.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/evidence/2026-08-13-ornith-session-stall-timeout-order.log`
### SDD Criteria
- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md`, status `[승인됨]`, lock `해제`.
- Milestone contribution ids remain exactly `repeat-guard,ops-evidence`.
- Acceptance Scenario S07 and its Evidence Map row require caller-neutral dev `ornith:35b` capacity+1 streaming evidence, a repeat fingerprint/decision or `not_reproduced`, raw request/output/auth exclusion, and a generic deterministic fixture that the live result cannot replace.
- This follow-up does not change S07's product behavior. It repairs the live oracle so route eligibility, normal versus long admission, exact terminal counts, and raw-free one-run provenance are trustworthy before the retained release and task evidence can close.
### Verification Context
- Handoff source: the archived G10 review contains fresh local verification plus a read-only dev route/config/artifact preflight. Repository source confirms the route predicate, output-token floor, input estimator, and credential route list shape used by the selected fix.
- Existing local behavior: focused terminal regressions, the fake Lemonade composition, selected full package tests, and race tests pass at source `2fcc1093c7629ab94b460d083520ffd9c8064815`.
- Current dev candidate: `/Users/toki/agent-work/iop-dev`, clean `release/dev-974`, `HEAD=origin/dev=origin/release/dev-974=2fcc1093c7629ab94b460d083520ffd9c8064815`, no `dev-974` tag, same-ref Edge/Node artifacts, ports `18082,18083,18084,19093,19101`, four connected Nodes, and healthy Ornith providers recovered to `0/0`.
- Route source: authenticated `GET /v1/credentials/routes` returns secret-blind route DTOs including alias and `resource_selector`; the smoke must use the same protected principal token as OpenAI ingress, never print it, and require one active exact alias whose selector equals the requested provider. If the credential HTTPS route is not directly reachable from the smoke host, an authorized protected wrapper may capture that same authenticated JSON into the unique run directory before the smoke, but an unscoped catalog/config guess is invalid.
- Request shape: build the actual Chat and Responses JSON in the unique run directory, count its Unicode runes, compute `runes/4 + runes/16`, and classify it against the runtime threshold. The selected capacity comes from `capacity` only when this computed class is `normal`; a long request must use `long_context_capacity` and must never satisfy this normal-capacity packet.
- Constraints: Bash must remain compatible with the remote macOS Bash 3.2 baseline; token/header values, prompt text, response bodies, route slot identifiers, and credential material stay out of stdout, tracked evidence, and task files. Raw request/response files remain only under ignored `agent-test/runs/**`.
- Confidence: high. The previous failure is explained by exact route, admission, estimator, policy, and file-mtime evidence, and the selected self-test can reproduce both oracle defects without network access.
#### External Verification Preflight
- Runner/workdir: `ssh toki@toki-labs.com`, `/Users/toki/agent-work/iop-dev`, login zsh for the declared toolchain; the new script may be copied to a mode-0700 ignored runner path with its SHA-256 recorded because the runtime candidate itself remains `dev-974`.
- Re-prove before live traffic: branch/HEAD/origin divergence/dirty state; retained release branch/tag state; `go version`; `git flow version`; script SHA-256; Edge/Node build revisions and hashes; ports; four connected Nodes; route list response; model discovery; provider capacities/long capacities/health; `long_context_threshold_tokens`; and final initial `in_flight=0, queued=0`.
- Authorized secret route: use the already approved protected active-token wrapper or stdin handoff. The token may be exported only in the child process environment and must be unset after the command. Do not print commands with expanded headers.
- Live cases: run Chat then Responses for `ornith:35b` with expected selector `onexplayer-lemonade` and separately for `ornith-fast` with `rtx5090-lemonade`. Every case must be normal context, send selected capacity+1 concurrent requests, poll status at 50-100ms, validate every HTTP/SSE result, and wait for selected-provider recovery.
- Release action: if all four endpoint/route cases pass and the retained release/source/build/origin preconditions remain unchanged, resume the existing `dev-runtime-deploy` finish/tag/atomic-push step. If any case or precondition fails, retain the release branch and do not finish or push.
### Test Coverage Gaps
- No current script validates managed route selector eligibility before calculating capacity.
- No deterministic self-test rejects catalog aggregate capacity for a selector-pinned route.
- No current live smoke enforces per-invocation unique evidence directories and current-run request/result provenance.
- No current self-test rejects a stale response body with a current curl result.
- No accepted dev matrix separately proves Chat and Responses terminal completion for both current Ornith projected aliases.
### Symbol References
- No production symbol is renamed or removed.
- New script functions should own route resolution, provider snapshot normalization, request-shape calculation, unique-run allocation, endpoint request creation, SSE terminal parsing, peak polling, final recovery, sanitized summary emission, and self-test fixtures. Do not add a product API or import the smoke into runtime code.
### Split Judgment
Keep one follow-up packet. The project skill/profile corrections are unsafe without the executable route/provenance oracle, and the tracked evidence/release decision is invalid until that same oracle passes live. Splitting documentation, script, and rollout would permit an intermediate state that repeats either R1 or R2 and cannot independently PASS.
### Scope Rationale
- In scope: `dev-runtime-deploy`, dev Edge smoke criteria, one new managed-capacity smoke and self-test, the local guide, the exact existing sanitized evidence log, live validation of the retained release, and active review evidence.
- Out of scope: `apps/**` and `packages/**` production behavior, public/inner contract schema, provider catalog or route mutation, capacity/timeout changes, new credentials, Control Plane database mutation, new Agent-Ops common rules/skills, long-context repeat reproduction redesign, and production deployment.
- Existing `scripts/e2e-provider-capacity-smoke.sh` and `scripts/e2e-long-context-admission-smoke.sh` are read-only pattern sources. Do not retrofit the local shared-provider fixture or the separate long-context scenario into this managed-route smoke.
### Final Routing
- `evaluation_mode=isolated-reassessment`; build/review scope, context, verification, evidence, ownership, and decision closures are true; no capability gap.
- Finalizer: `finalize-task-policy.sh pair`.
- Build: scores `2/2/2/2/2`, grade `G10`, base/final route basis `grade-boundary`, lane `cloud`, catalog `worker/cloud/G10`, filename `PLAN-cloud-G10.md`.
- Review: scores `2/2/2/2/2`, route `official-review/cloud/G10`, catalog `review/cloud/G10`, filename `CODE_REVIEW-cloud-G10.md`.
- `large_indivisible_context=false`; positive loop risks are `temporal_state`, `concurrent_consistency`, `boundary_contract`, `structured_interpretation`, and `variant_product` (5).
- Recovery signals: `review_rework_count=2`, `evidence_integrity_failure=true`; both boundaries match, while the already-cloud grade basis remains `grade-boundary`.
## Implementation Checklist
- [ ] [REVIEW_REVIEW_TEST-1] Correct managed-capacity ownership in the project deployment skill, dev Edge smoke profile, and local guide.
- [ ] [REVIEW_REVIEW_TEST-2] Add the route-aware Chat/Responses capacity smoke with deterministic route, terminal, provenance, and stale-artifact self-tests.
- [ ] [REVIEW_REVIEW_TEST-3] Run both projected Ornith route matrices, replace disputed evidence from the sanitized current-run summaries, and finish the retained release only if every gate passes.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
### [REVIEW_REVIEW_TEST-1] Managed route-qualified capacity contract
**Problem:** `agent-ops/skills/project/dev-runtime-deploy/SKILL.md:143-150` and `agent-test/dev/edge-smoke.md:151-156` calculate capacity from all providers in a model catalog group. In managed mode, `apps/edge/internal/openai/principal_routes.go:291-305` constrains dispatch to the authenticated route selector, so the aggregate can include unreachable resources.
**Solution:** Replace model-group aggregate guidance with one explicit rule: resolve the authenticated route alias first, intersect its `resource_selector`, profile, and upstream model with current provider snapshots, classify the emitted request as normal or long, and use only the selected eligible provider's matching capacity. Require `scripts/e2e-openai-managed-capacity-smoke.sh` for managed Chat/Responses deployment qualification. Keep unprojected/legacy pools on their existing pool-capacity rule. Update the guide to distinguish short normal-capacity verification from the independent long-context/repeat smoke.
Before (`agent-ops/skills/project/dev-runtime-deploy/SKILL.md:143-150`):
```markdown
- endpoint별로 선택한 model group의 총 provider capacity + 1개 요청을 동시에 보낸다.
- 요청 실행 중 ... 대상 provider들의 in_flight 합이 총 capacity에 도달하고 queued 합이 1 이상 ...
```
After:
```markdown
- Managed mode resolves the authenticated route alias and resource selector before capacity calculation.
- Run selected-provider eligible capacity + 1 for one endpoint and route at a time; never add a provider excluded by that route.
- Compute request context class from the emitted request and use capacity or long_context_capacity accordingly.
```
**Modified Files and Checklist:**
- [ ] `agent-ops/skills/project/dev-runtime-deploy/SKILL.md`: define managed route projection, selected-provider capacity, script invocation, and fail-closed release gate.
- [ ] `agent-test/dev/edge-smoke.md`: replace the Ornith aggregate baseline with exact projected alias/provider cases and separate normal/long acceptance.
- [ ] `docs/edge-local-dev-guide.md`: document route-aware capacity ownership, unique-run evidence, terminal criteria, and raw-material boundary.
**Test Strategy:** Validate the project skill structure and use the new script self-test as the executable oracle for the documented mapping. Do not add product Go tests because no runtime behavior changes.
**Verification:**
```bash
python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/dev-runtime-deploy
rg --sort path -n 'managed|resource_selector|eligible capacity|e2e-openai-managed-capacity-smoke' agent-ops/skills/project/dev-runtime-deploy/SKILL.md agent-test/dev/edge-smoke.md docs/edge-local-dev-guide.md
```
Expected: validation exits 0; the three documents consistently require route-qualified capacity and do not claim `ornith:35b` can aggregate OneXPlayer and RTX5090 under the current projected selectors.
### [REVIEW_REVIEW_TEST-2] Deterministic managed-capacity and provenance oracle
**Problem:** No tracked smoke owns the selected route/provider eligibility or exact Chat/Responses terminal checks. The prior task-local wrapper reused one output directory, so current curl metadata could be paired with a stale response body.
**Solution:** Add `scripts/e2e-openai-managed-capacity-smoke.sh` with live and `--self-test` modes. Live mode must:
1. create a `mktemp -d` child under ignored `agent-test/runs/**`, set and verify mode `0700`, assign one immutable run id, and refuse caller-selected existing run directories;
2. obtain the secret-blind authenticated route list with the protected principal token, require one exact alias/selector match, and join it to one healthy current provider snapshot;
3. build the actual short Chat/Responses request bodies inside the run directory, compute Unicode runes, `runes/4 + runes/16`, and context class, require `normal`, and choose the selected provider's normal `capacity`;
4. launch `capacity + 1` streaming requests for one endpoint/route, poll status at 50-100ms, and record selected-provider peak/queue plus final recovery without adding non-selected capacity;
5. bind every case to a current-run manifest entry and fresh request/result/curl-status paths, reject missing files or mtimes before request creation, and parse only manifest-owned results;
6. require every Chat result to have HTTP 200, one finish terminal, one `[DONE]`, and no error; require every Responses result to have HTTP 200, one `response.completed`, one `[DONE]`, and no error;
7. write a sanitized summary with run id, script hash, route alias, selected provider, endpoint, request count, computed request shape/class, durations, terminal counts, peak/queue/final counters, and outcome only. Never copy raw request/response, token/header, slot/route id, prompt, or output into the summary.
`--self-test` must use local fixtures only. It must prove route selector exclusion, normal/long capacity choice, Chat and Responses exact terminals, duplicate/missing terminal rejection, two distinct 0700 run directories, current-run manifest/mtime acceptance, and stale/missing/foreign-run body rejection. Keep macOS Bash 3.2 compatibility and use Python 3 only for deterministic JSON/rune/provenance parsing where shell parsing would be unsafe.
**Modified Files and Checklist:**
- [ ] `scripts/e2e-openai-managed-capacity-smoke.sh`: implement the live driver, safe summary, and complete deterministic self-test.
**Test Strategy:** `bash -n` checks the shell surface. `--self-test` is the required regression and must execute all positive and negative fixtures without network, credentials, or repository writes outside a temporary directory.
**Verification:**
```bash
bash -n scripts/e2e-openai-managed-capacity-smoke.sh
./scripts/e2e-openai-managed-capacity-smoke.sh --self-test
```
Expected: both commands exit 0; self-test output reports every route/terminal/provenance negative rejected, unique mode-0700 directories, and no surviving repository artifact.
### [REVIEW_REVIEW_TEST-3] Route-realistic dev evidence and retained release gate
**Problem:** `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/evidence/2026-08-13-ornith-session-stall-timeout-order.log:29-69` contains a handwritten 41k-token label, aggregate-capacity interpretation, terminal-zero results, and a fixed raw directory that the reviewer proved contained mixed generations. Those rows cannot qualify S07 or the retained release.
**Solution:** After local gates pass, re-prove the retained release and runtime identities. Execute the new smoke with the authorized protected token for these exact matrices, sequentially:
- `ornith:35b`, selector/provider `onexplayer-lemonade`, Chat then Responses, request count `3 + 1`;
- `ornith-fast`, selector/provider `rtx5090-lemonade`, Chat then Responses, request count `1 + 1`.
Use the script's short normal request and current-run summaries; do not reuse the 369k-rune liveness payload. Require every request to complete before the bounded caller deadline with exact endpoint terminals, selected peak equal to capacity, queue at least one, and selected final counters `0/0`. Replace the disputed request-shape/capacity/artifact rows in the tracked evidence with a v2 sanitized summary derived only from these successful run summaries. Preserve previously proven source/build/timeout/direct-control facts only when their meaning remains accurate, and keep `stall_reproduction_status=not_reproduced` distinct from the normal-capacity result. If all gates and retained-release ref checks pass, execute the already-defined release finish/tag/atomic push; otherwise record the exact blocker and leave the release unfinished.
**Modified Files and Checklist:**
- [ ] `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/evidence/2026-08-13-ornith-session-stall-timeout-order.log`: replace disputed rows with one-run, route-qualified, computed-shape, terminal-complete v2 evidence.
- [ ] `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/CODE_REVIEW-cloud-G10.md`: record actual local/live/release output or exact ignored sanitized artifact paths.
**Test Strategy:** Treat the four live cases as mandatory integration evidence and keep the deterministic self-test as the provenance oracle. Do not declare `not_reproduced`, counter recovery alone, or release retention as a PASS substitute.
**Verification:**
```bash
ssh toki@toki-labs.com '/bin/zsh -lc '\''cd /Users/toki/agent-work/iop-dev && git status --short --branch && git rev-parse HEAD && git rev-parse origin/dev && git rev-parse origin/release/dev-974 && git rev-list --left-right --count HEAD...origin/dev && git tag -l dev-974 && go version && git flow version | head -n 1'\'''
```
Then run the new smoke through the authorized protected-token wrapper for the exact `ornith:35b/onexplayer-lemonade` and `ornith-fast/rtx5090-lemonade` Chat/Responses matrices. Save raw bodies only in the script-created ignored run directories and save sanitized summaries in the implementation-owned review evidence. Execute the retained release finish/tag/atomic-push sequence only after all four matrices and final preconditions pass.
Expected: source/build/runtime identity remains one retained candidate; all four route/endpoint cases pass with exact terminals, selected-provider peak/queue/final recovery and no aggregate misattribution; tracked evidence is raw-free and current-run bound; release finish is either fully atomic and verified or not run with one exact blocker.
## Final Verification
```bash
bash -n scripts/e2e-openai-managed-capacity-smoke.sh
./scripts/e2e-openai-managed-capacity-smoke.sh --self-test
python3 /config/.codex/skills/.system/skill-creator/scripts/quick_validate.py agent-ops/skills/project/dev-runtime-deploy
go test -count=1 ./apps/node/internal/node -run '^TestTunnelWatchdogFinishFrameWithoutEndStallsOnce$'
go test -count=1 ./apps/edge/internal/openai -run '^(TestOpenAIStallAfterLogicalFinishMatrix|TestOpenAISemanticGateDisabledStallTerminalMatrix|TestOpenAISemanticGateDisabledCompatibility|TestOpenAITunnelCodecTerminalWire|TestOpenAIDirectResponsesSemanticDisabledStallTerminal)$'
IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh
git diff --check
git status --short --branch
```
Expected: every local command exits 0 with fresh output; only the files below and active task artifacts change. External verification additionally passes the four exact route/endpoint cases, tracked evidence provenance/redaction checks, retained-release ref gate, and atomic finish or preserves one exact blocker without partial refs.
After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
## Modified Files Summary
| File | Items |
|---|---|
| `agent-ops/skills/project/dev-runtime-deploy/SKILL.md` | REVIEW_REVIEW_TEST-1 |
| `agent-test/dev/edge-smoke.md` | REVIEW_REVIEW_TEST-1 |
| `docs/edge-local-dev-guide.md` | REVIEW_REVIEW_TEST-1 |
| `scripts/e2e-openai-managed-capacity-smoke.sh` | REVIEW_REVIEW_TEST-2 |
| `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/evidence/2026-08-13-ornith-session-stall-timeout-order.log` | REVIEW_REVIEW_TEST-3 |
| `agent-task/m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/CODE_REVIEW-cloud-G10.md` | REVIEW_REVIEW_TEST-1, REVIEW_REVIEW_TEST-2, REVIEW_REVIEW_TEST-3 |

View file

@ -0,0 +1,40 @@
# 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 09:51:41 KST | START | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G09.md | 0 | worker | 0 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T095141+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p0__worker__a00/locator.json |
| 2 | 26-08-13 10:16:28 KST | FINISH | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G09.md | 0 | worker | 0 | codex/gpt-5.6-sol xhigh | failed:generic-error:0 | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T095141+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p0__worker__a00/locator.json |
| 3 | 26-08-13 10:16:31 KST | START | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G09.md | 0 | worker | 1 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T101630+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p0__worker__a01/locator.json |
| 4 | 26-08-13 10:25:11 KST | FINISH | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G09.md | 0 | worker | 1 | codex/gpt-5.6-sol xhigh | failed:generic-error:0 | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T101630+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p0__worker__a01/locator.json |
| 5 | 26-08-13 10:25:15 KST | START | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G09.md | 0 | worker | 2 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T102515+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p0__worker__a02/locator.json |
| 6 | 26-08-13 10:31:25 KST | FINISH | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G09.md | 0 | worker | 2 | codex/gpt-5.6-sol xhigh | succeeded:0 | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T102515+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p0__worker__a02/locator.json |
| 7 | 26-08-13 10:31:25 KST | START | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/CODE_REVIEW-cloud-G09.md | 0 | review | 0 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T103125+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p0__review__a00/locator.json |
| 8 | 26-08-13 11:17:48 KST | FINISH | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/CODE_REVIEW-cloud-G09.md | 0 | review | 0 | codex/gpt-5.6-sol xhigh | succeeded:0 | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T103125+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p0__review__a00/locator.json |
| 9 | 26-08-13 11:18:12 KST | START | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G10.md | 1 | worker | 0 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T111812+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p1__worker__a00/locator.json |
| 10 | 26-08-13 11:52:01 KST | FINISH | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G10.md | 1 | worker | 0 | codex/gpt-5.6-sol xhigh | failed:cancelled | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T111812+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p1__worker__a00/locator.json |
| 11 | 26-08-13 11:52:30 KST | START | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G10.md | 1 | worker | 1 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T115230+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p1__worker__a01/locator.json |
| 12 | 26-08-13 11:56:51 KST | FINISH | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G10.md | 1 | worker | 1 | codex/gpt-5.6-sol xhigh | failed:cancelled | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T115230+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p1__worker__a01/locator.json |
| 13 | 26-08-13 11:57:14 KST | START | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G10.md | 1 | worker | 2 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T115714+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p1__worker__a02/locator.json |
| 14 | 26-08-13 12:17:18 KST | START | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G10.md | 1 | worker | 3 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T121718+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p1__worker__a03/locator.json |
| 15 | 26-08-13 13:04:05 KST | FINISH | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G10.md | 1 | worker | 3 | codex/gpt-5.6-sol xhigh | failed:generic-error:0 | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T121718+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p1__worker__a03/locator.json |
| 16 | 26-08-13 13:04:07 KST | START | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G10.md | 1 | worker | 4 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T130407+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p1__worker__a04/locator.json |
| 17 | 26-08-13 13:09:32 KST | FINISH | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G10.md | 1 | worker | 4 | codex/gpt-5.6-sol xhigh | failed:generic-error:0 | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T130407+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p1__worker__a04/locator.json |
| 18 | 26-08-13 13:09:36 KST | START | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G10.md | 1 | worker | 5 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T130936+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p1__worker__a05/locator.json |
| 19 | 26-08-13 13:24:50 KST | FINISH | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G10.md | 1 | worker | 5 | codex/gpt-5.6-sol xhigh | succeeded:0 | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T130936+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p1__worker__a05/locator.json |
| 20 | 26-08-13 13:24:50 KST | START | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/CODE_REVIEW-cloud-G10.md | 1 | review | 0 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T132450+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p1__review__a00/locator.json |
| 21 | 26-08-13 14:01:04 KST | FINISH | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/CODE_REVIEW-cloud-G10.md | 1 | review | 0 | codex/gpt-5.6-sol xhigh | failed:session-stall:0 | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T132450+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p1__review__a00/locator.json |
| 22 | 26-08-13 14:01:06 KST | START | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/CODE_REVIEW-cloud-G10.md | 0 | review | 1 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T140106+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p0__review__a01/locator.json |
| 23 | 26-08-13 14:17:35 KST | FINISH | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/CODE_REVIEW-cloud-G10.md | 0 | review | 1 | codex/gpt-5.6-sol xhigh | failed:session-stall:0 | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T140106+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p0__review__a01/locator.json |
| 24 | 26-08-13 14:17:39 KST | START | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/CODE_REVIEW-cloud-G10.md | 0 | review | 2 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T141739+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p0__review__a02/locator.json |
| 25 | 26-08-13 14:31:33 KST | FINISH | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/CODE_REVIEW-cloud-G10.md | 0 | review | 2 | codex/gpt-5.6-sol xhigh | succeeded:0 | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T141739+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p0__review__a02/locator.json |
| 26 | 26-08-13 14:32:00 KST | START | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G10.md | 2 | worker | 0 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T143200+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p2__worker__a00/locator.json |
| 27 | 26-08-13 14:44:54 KST | FINISH | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G10.md | 2 | worker | 0 | codex/gpt-5.6-sol xhigh | failed:session-stall:0 | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T143200+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p2__worker__a00/locator.json |
| 28 | 26-08-13 14:44:56 KST | START | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G10.md | 2 | worker | 1 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T144456+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p2__worker__a01/locator.json |
| 29 | 26-08-13 15:10:07 KST | FINISH | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G10.md | 2 | worker | 1 | codex/gpt-5.6-sol xhigh | failed:generic-error:0 | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T144456+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p2__worker__a01/locator.json |
| 30 | 26-08-13 15:10:11 KST | START | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G10.md | 2 | worker | 2 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T151011+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p2__worker__a02/locator.json |
| 31 | 26-08-13 15:20:32 KST | FINISH | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G10.md | 2 | worker | 2 | codex/gpt-5.6-sol xhigh | succeeded:0 | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T151011+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p2__worker__a02/locator.json |
| 32 | 26-08-13 15:20:33 KST | START | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/CODE_REVIEW-cloud-G10.md | 2 | review | 0 | codex/gpt-5.6-sol xhigh | running | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T152033+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p2__review__a00/locator.json |
| 33 | 26-08-13 15:37:51 KST | FINISH | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/CODE_REVIEW-cloud-G10.md | 2 | review | 0 | codex/gpt-5.6-sol xhigh | succeeded:0 | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T152033+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p2__review__a00/locator.json |
| 34 | 26-08-13 15:37:51 KST | FINISH | m-openai-compatible-output-validation-filters/04+03_stream_terminal_liveness/PLAN-cloud-G10.md | 1 | worker | 2 | codex/gpt-5.6-sol xhigh | reconciled:verified-complete-archive | /config/workspace/iop-s1/.git/agent-task-dispatcher/runs/20260813T115714+0900__m-openai-compatible-output-validation-filters__04__03_stream_terminal_liveness__p1__worker__a02/locator.json |

View file

@ -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 |

View file

@ -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
- 없음
## 후속 작업
- 없음

View file

@ -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`.

View file

@ -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 |

View file

@ -3,7 +3,7 @@ test_env: dev
test_profile: edge-smoke
domain: edge
verification_type: smoke
last_rule_updated_at: 2026-08-06
last_rule_updated_at: 2026-08-13
---
# edge-smoke dev 테스트
@ -89,10 +89,10 @@ Claude Anthropic-compatible 단일 요청 Agent 실행을 검증할 때는 Claud
- 접속 기준: 현재 작업 호스트에서 직접 SSH
- provider endpoint: `http://192.168.0.59:13305/v1`
- served model: `Ornith-1.0-35B-GGUF-llamacpp-tp1-Q5_K_M`
- capacity baseline: `3`
- capacity baseline: `1` (agent 장문 요청의 provider 독점 실행 기준)
- priority baseline: `2`
- load baseline: checkpoint `LordNeel/Ornith-1.0-35B-GGUF-llamacpp-tp1:Q5_K_M`, backend `vulkan`, ctx size `524288`, `llamacpp_args="--spec-type none -cb -fa on -b 4096 -ub 1024 --kv-unified --temp 0.6 --top-p 0.95 --top-k 20"`, `save_options=true`
- long-context admission baseline: `total_context_tokens=524288`, `long_context_capacity=2` (`-np` 고정 분할을 제거하고 `--kv-unified`를 사용한다. `/slots`는 auto slots 4개와 slot `n_ctx=262144`를 보고한다)
- load baseline: checkpoint `LordNeel/Ornith-1.0-35B-GGUF-llamacpp-tp1:Q5_K_M`, backend `vulkan`, ctx size `262144`, `llamacpp_args="--spec-type none -np 1 -cb -fa on -b 4096 -ub 1024 --kv-unified --temp 0.6 --top-p 0.95 --top-k 20"`, `save_options=true`
- long-context admission baseline: `total_context_tokens=262144`, `long_context_capacity=1` (IOP의 provider 직렬 admission과 llama-server의 `-np 1`을 일치시켜 agent turn별 prefix cache가 서로 다른 slot에 분산되지 않게 한다. `/slots`는 slot 1개와 `n_ctx=262144`를 보고한다)
- workspace: `C:/Users/r0bin/iop-field`
- RTX5090 Lemonade node: `rtx5090-lemonade-node` / `rtx5090-lemonade`
- SSH/user: `ssh iop-dev-rtx5090`
@ -150,10 +150,12 @@ GX10 Laguna 공식 vLLM baseline은 `temperature=0.7`, `top_p=0.95`, model `gene
- raw text tool-call boundary smoke: Pi/Cline형 `tools[]` 요청에서 응답 body와 SSE delta 어디에도 `<tool_call>`, `{{`, `<|mask_end|>` 원문이 성공 content로 남지 않는다. 요청 `tools[]`에 있는 valid raw text tool-call은 `message.tool_calls`(또는 stream `delta.tool_calls`)와 `finish_reason: "tool_calls"`로 정규화되고, unknown tool hallucination이나 malformed 블록은 성공 content가 아니라 non-stream `tool_validation_error`(HTTP 502) 또는 SSE `tool_validation_error` 이벤트로 끝난다. non-stream/strict buffered stream은 bounded retry 후 차단을 확인한다. 세부 payload와 계약은 `docs/edge-local-dev-guide.md`의 Raw text tool-call boundary smoke와 `agent-contract/outer/openai-compatible-api.md`를 따른다.
- raw boundary smoke evidence는 tracked 문서가 아니라 ignored run 위치(`agent-test/runs/**`)나 code-review output path에 저장하고, 저장물에도 token 원문을 남기지 않는다.
- provider-pool dispatch는 `in_flight >= capacity`인 provider를 후보에서 제외하고, 남은 후보 중 가장 낮은 `in_flight` 레벨을 먼저 선택한다. 같은 `in_flight` 레벨 안에서는 낮은 priority 값과 rotation으로 분산한다.
- dev-runtime capacity smoke는 model group별로 분리한다. Laguna `laguna-s:2.1``gx10-vllm=4`이므로 5개 동시 요청에서 `in_flight=4`, `queued>=1`; Ornith `ornith:35b``onexplayer-lemonade=3` + `rtx5090-lemonade=1`이므로 5개 요청에서 `in_flight=4`, `queued>=1`; Qwen `qwen3.6:35b``mac-mlx-vllm=2`이므로 3개 요청에서 `in_flight=2`, `queued>=1`을 기준으로 한다. `ornith:35b``ornith-fast`는 provider-owned shared capacity 구현 전까지 model group별 capacity를 독립 집계하므로 같은 smoke에서 두 alias를 섞지 않는다.
- dev-runtime managed capacity smoke는 `scripts/e2e-openai-managed-capacity-smoke.sh`를 사용한다. 같은 principal token으로 authenticated route alias를 먼저 조회하고, exact active route의 `resource_selector`, profile, upstream model과 일치하는 healthy provider snapshot 하나만 eligible pool로 본다. `ornith:35b``onexplayer-lemonade=1`, `ornith-fast``rtx5090-lemonade=1`로 각각 분리하며, 현재 projected route에서 제외된 두 provider의 capacity를 더하지 않는다. Chat/Responses를 alias별로 따로 실행해 각각 2개 요청에서 selected `in_flight=1`, `queued>=1`을 확인한다.
- managed smoke는 실제 emitted request JSON의 Unicode rune 수와 `runes/4 + runes/16` estimate를 계산하고 `long_context_threshold_tokens`와 대조한다. normal qualification은 `capacity`, 별도 long-context 시나리오는 `long_context_capacity`를 사용한다. 장문 반복/liveness payload로 normal capacity를 증명하거나 normal 요청으로 long slot을 증명하지 않는다. unprojected/legacy Laguna `laguna-s:2.1`과 Qwen `qwen3.6:35b`는 기존 model-group capacity `4`/`2` 기준을 유지한다.
- capacity smoke 완료 후 대상 provider의 `in_flight=0`, `queued=0` 회복을 확인한다.
- long-context admission 시나리오(normal 10-way, mixed long/normal, all-long-slot-full)와 최종 회복 근거는 `agent-test/dev/long-context-admission-smoke.md``scripts/e2e-long-context-admission-smoke.sh`를 사용한다.
- `/v1/responses``/v1/chat/completions`는 각각 700~1200 token 수준의 구조화된 응답을 유도하고 50~100ms 간격으로 status를 polling한다. 각 요청의 HTTP 200, target provider별 capacity 미초과, 합산 peak와 queue, 최종 0 회복을 별도 증거로 남긴다.
- `/v1/responses``/v1/chat/completions`는 각각 짧은 입력에서 700~1200 token 수준의 구조화된 응답을 유도하고 provider-native thinking을 제한하며 50~100ms 간격으로 status를 polling한다. 각 요청의 HTTP 200, endpoint-native success terminal 정확히 1개, `[DONE]` 정확히 1개, selected provider의 eligible capacity 미초과, exact peak와 queue, 최종 0 회복을 별도 증거로 남긴다.
- managed smoke invocation마다 ignored `agent-test/runs/**` 아래 mode `0700` unique directory와 immutable run id를 생성한다. manifest가 소유하고 현재 run/dispatch 이후 생성된 request/result/status만 판정하며 stale/missing/foreign-run body는 실패한다. tracked/review evidence에는 sanitized summary의 run id, script hash, alias, selected provider, endpoint, computed shape/class, terminal count, peak/queue/final counter, outcome만 허용하고 token/header, route/slot id, prompt, response body와 출력은 기록하지 않는다.
- Qwen provider-pool smoke는 thinking/reasoning 텍스트가 포함될 수 있다. 추론 출력 자체를 실패로 보지 말고 HTTP 성공, model alias, final marker 포함 여부, provider node log/run count 증가로 판정한다. 응답 전체가 특정 token과 정확히 같은지 비교하는 strict exact-match는 이 profile의 기본 판정으로 쓰지 않는다.
- Qwen provider를 agent/tool-call 용도로 검증할 때는 일반 chat smoke와 별도로 forced tool call, auto tool call, streaming `delta.tool_calls`, multi-turn tool result 후 최종 답변을 확인한다. raw native marker나 reasoning text가 assistant content로 새면 해당 model/runtime의 parser/template profile 미확정으로 보고한다.
- Qwen runtime에는 Qwen 전용 parser/template 검증값만 사용한다. dev-corp Gemma 계열의 `tool_call_parser=gemma4`, `reasoning_parser=gemma4`, Gemma4 chat template/profile을 Qwen provider에 복사하지 않는다.

View file

@ -0,0 +1,43 @@
# IOP 벤치 경로 최소 HTML 스모크
## 목적
벤치 대상 9개 caller/model/route 조합에 같은 최소 `index.html` 생성 요청을 한 번씩 직접 보내 호출 경로만 빠르게 확인한다. 전용 runner, manifest, 자동 retry, browser gate와 품질 채점은 사용하지 않는다.
## 고정 요청
외부 asset과 JavaScript 없이 단일 `index.html`을 구현한다. 문서에는 `<!doctype html>`, `<title>IOP Route Smoke</title>`, `<h1>IOP_ROUTE_SMOKE_OK</h1>`이 정확히 한 번씩 있어야 한다. direct 경로는 빈 caller workspace의 파일을 확인한다. Edge-owned execution preset은 비공개 workspace가 완료 전에 정리되는 제품 계약을 따르므로 caller 로컬 파일이 아니라 terminal `decision.output`의 완료·marker를 확인한다.
## 사전 확인 — 2026-08-13
| 항목 | 결과 |
|---|---|
| Claude Code | 실제 원격 실행기 2.1.177 확인 |
| agy | 1.1.12 확인 |
| Codex | 실제 원격 실행기 0.146.0 확인 |
| managed CA | live Edge CA bundle 확인 |
| principal token | 원격 SOPS의 기존 두 token 사용; 별도 benchmark token 미사용 |
| public `/v1/models` | 두 기존 token 모두 HTTP 200, route 9개 확인 |
| ambient CA override | 호출 전후 unset 확인 |
## 경로 결과
| 경로 | 최초 1회 결과 | 재검증 | 분류 |
|---|---|---|---|
| Claude Code → Claude direct | 통과 | 없음 | 기존 성공 |
| Claude Code → Gemini direct | 통과 | 없음 | 기존 성공 |
| agy → Gemini direct | 1초, caller login 요구 | `modelProvider=gemini` 보정 뒤 8초, HTTP 400 | IOP Gemini path가 공식 URL-encoded model label을 거부 |
| Claude Code → GPT direct | provider HTTP 400 | 새 normalization 배포 대기 | IOP가 tools+effort를 Chat으로 보낸 결함 |
| Codex → GPT direct | 30초, `turn.failed`, 파일 없음 | 공식 설정대로 임시 `CODEX_HOME`, Responses 전용 provider, `CODEX_CA_CERTIFICATE`에 CA bundle을 사용해 10초 통과 | 측정 환경 결함: 첫 호출은 CA bundle 대신 Edge leaf 인증서를 사용 |
| Claude Code → Gemini execution preset | 184초, caller terminal success, caller workspace 파일 없음 | 판정 정정: caller 파일 부재는 정상, 120초 초과는 실패 | 측정 판정 결함과 preset 지연을 분리; terminal marker는 최초 결과에서 미수집 |
| agy → Gemini execution preset | 미실행 | direct parser 수정 배포 대기 | 선행 결함 |
| Claude Code → GPT execution preset | 미실행 | normalization 배포 대기 | 선행 결함 |
| Codex → GPT execution preset | 16초, `turn.completed`, terminal marker 1회 | 없음 | 통과 |
추가 API 분리에서는 동일 principal의 최소 `/v1/responses`가 HTTP 200이었다. Codex direct도 사용자 설정과 로그인 상태를 배제한 임시 `CODEX_HOME`, Responses 전용 custom provider, 원격 SOPS의 기존 token, command-scoped managed CA bundle으로 통과했다. Gemini-native 최소 요청은 canonical caller model id에서 HTTP 200, 공식 표시 label `Gemini 3.6 Flash`에서 HTTP 400으로 갈려 path parser 결함을 재현했다.
## 재개 조건
원격에 남은 미완료 release head를 운영 절차로 먼저 정리한 뒤 병합된 `dev`를 새 release로 배포한다. 그 뒤 변경된 원인에 연결된 agy direct와 Claude Code GPT direct만 1회 재검증하고, 선행 결함이 해소된 미실행 행을 각 1회 수행한다.
성공한 경로는 반복하지 않는다. 실패한 경로는 원인이 변경된 경우에만 해당 경로를 1회 재검증한다.

View file

@ -33,11 +33,11 @@ last_rule_updated_at: 2026-07-24
- Control Plane status URL: `http://127.0.0.1:18001/edges/edge-toki-labs-dev/status` (runner-local; 다른 host에서는 `IOP_LONG_SMOKE_STATUS_SSH`로 ssh curl).
- 현재 모델 그룹별 capacity baseline:
- `laguna-s:2.1`: `4` (`gx10-vllm=4`)
- `ornith:35b`: `4` (`onexplayer-lemonade=3`, `rtx5090-vllm=1`)
- `ornith:35b`: `2` (`onexplayer-lemonade=1`, `rtx5090-lemonade=1`)
- `qwen3.6:35b`: `2` (`mac-mlx-vllm=2`)
- 현재 모델 그룹별 long slot baseline:
- `laguna-s:2.1`: `1` (`gx10-vllm=1`)
- `ornith:35b`: `3` (`onexplayer-lemonade=2`, `rtx5090-vllm=1`)
- `ornith:35b`: `2` (`onexplayer-lemonade=1`, `rtx5090-lemonade=1`)
- `qwen3.6:35b`: `1` (`mac-mlx-vllm=1`)
- `scripts/e2e-long-context-admission-smoke.sh`는 아직 이전 `qwen3.6:35b` 공용 풀(`gx10-vllm`, `onexplayer-lemonade`, `mac-mlx-vllm`)과 총 capacity `9`를 전제로 한다. 현재 dev에서 이 스크립트의 `normal-10`/`mixed`/`all-long-slot-full` 결과를 최신 Laguna-S 모델 그룹 evidence로 사용하지 않는다.
- credential: bearer token은 `IOP_LONG_SMOKE_TOKEN` 환경 변수로만 주입하고 명령/로그/tracked 파일에 원문을 남기지 않는다.

View file

@ -74,10 +74,10 @@ dev-runtime의 실제 4-node 연결을 점검할 때는 원격 runner `ssh toki@
- 접속 기준: 현재 작업 호스트에서 직접 SSH
- provider endpoint: `http://192.168.0.59:13305/v1`
- served model: `Ornith-1.0-35B-GGUF-llamacpp-tp1-Q5_K_M`
- capacity baseline: `3`
- capacity baseline: `1` (agent 장문 요청의 provider 독점 실행 기준)
- priority baseline: `2`
- load baseline: checkpoint `LordNeel/Ornith-1.0-35B-GGUF-llamacpp-tp1:Q5_K_M`, backend `vulkan`, ctx size `524288`, `llamacpp_args="--spec-type none -cb -fa on -b 4096 -ub 1024 --kv-unified --temp 0.6 --top-p 0.95 --top-k 20"`, `save_options=true`
- long-context admission baseline: `total_context_tokens=524288`, `long_context_capacity=2` (`-np` 고정 분할을 제거하고 `--kv-unified`를 사용한다. `/slots`는 auto slots 4개와 slot `n_ctx=262144`를 보고한다)
- load baseline: checkpoint `LordNeel/Ornith-1.0-35B-GGUF-llamacpp-tp1:Q5_K_M`, backend `vulkan`, ctx size `262144`, `llamacpp_args="--spec-type none -np 1 -cb -fa on -b 4096 -ub 1024 --kv-unified --temp 0.6 --top-p 0.95 --top-k 20"`, `save_options=true`
- long-context admission baseline: `total_context_tokens=262144`, `long_context_capacity=1` (IOP의 provider 직렬 admission과 llama-server의 `-np 1`을 일치시켜 agent turn별 prefix cache가 서로 다른 slot에 분산되지 않게 한다. `/slots`는 slot 1개와 `n_ctx=262144`를 보고한다)
- workspace: `C:/Users/r0bin/iop-field`
- RTX5090 Lemonade node: `rtx5090-lemonade-node` / `rtx5090-lemonade`
- SSH/user: `ssh iop-dev-rtx5090`

View file

@ -1,6 +1,6 @@
inventory_id: inventory-agent
schema_version: 1
last_updated_at: "2026-08-05"
last_updated_at: "2026-08-13"
scope:
type: shared_agent_host_profiles
@ -12,6 +12,13 @@ policy:
provider_runtime_source_of_truth: environment_inventory
agent_provider_compatibility_observations: allowed
node_reference_policy: reference_only
defect_resolution:
applies_to: all_agents
workaround_paths: forbidden
extension_based_workaround: forbidden
hook_wrapper_or_client_side_behavior_override: forbidden
required_resolution: fix_the_owning_agent_runtime_provider_or_transport_layer
validation_path: canonical_path_only
dispatcher_scope:
included_agents:

View file

@ -2,7 +2,7 @@ inventory_id: inventory-dev
common_inventory: agent-test/inventory.yaml
test_env: dev
profile: dev-runtime-provider-pool
last_updated_at: "2026-08-10"
last_updated_at: "2026-08-13"
source:
remote_runner:
@ -70,7 +70,7 @@ model:
aliases:
"claude-sonnet-5":
observed_at: "2026-08-10"
status: active_edge_model_group_benchmark_target_observed
status: active_edge_model_group_observed
display_name: Claude Sonnet 5
capacity_total: 1
providers:
@ -90,7 +90,7 @@ model:
provider_snapshot: healthy_idle
"gpt-5.6-luna":
observed_at: "2026-08-10"
status: active_edge_model_group_benchmark_target_observed
status: active_edge_model_group_observed
display_name: GPT-5.6 Luna
capacity_total: 1
providers:
@ -241,7 +241,7 @@ model:
"ornith:35b":
status: active_edge_model_group
display_name: Ornith 1.0 35B
capacity_total: 4
capacity_total: 2
providers:
- id: onexplayer-lemonade
served_model: Ornith-1.0-35B-GGUF-llamacpp-tp1-Q5_K_M
@ -289,7 +289,7 @@ model:
- onexplayer-lemonade-node
- rtx5090-lemonade-node
known_risk: ornith-fast and ornith:35b each admit against an independent model-group capacity counter, so one simultaneous request through each alias can target the same RTX5090 provider until provider-owned shared capacity is implemented.
provider_capacity_total: 4
provider_capacity_total: 2
provider_capacity_status: verified_with_edge_routing_and_provider_metrics_2026_07_24
context_window: 262144
default_max_tokens: 65536
@ -1177,14 +1177,15 @@ nodes:
iop_capacity_guard: 3
long_context_capacity: 2
validation_required_after_load: true
validation_basis: Same Lemonade llama.cpp backend and current Ornith Q5 runtime confirmed --kv-unified with omitted -np prevents fixed ctx_size/parallel partitioning.
capacity: 3
validation_basis: This inactive Qwen profile requires separate validation before re-enable; do not derive its parallelism from the current single-slot Ornith profile.
capacity: 1
priority: 2
# Long-context admission policy (maps to edge.yaml nodes[].providers[]).
# Ornith Q5 direct runtime keeps ctx_size=524288 and removes fixed -np
# partitioning by using llama.cpp unified KV. Do NOT raise ctx_size above 524288.
total_context_tokens: 524288
long_context_capacity: 2
# IOP admits one Ornith request at a time, so llama.cpp also uses one slot.
# This keeps agent-turn prefix cache on the same slot instead of scattering
# it across auto slots. Do NOT raise ctx_size above 262144.
total_context_tokens: 262144
long_context_capacity: 1
load:
endpoint: http://192.168.0.59:13305/v1/load
model_name: Ornith-1.0-35B-GGUF-llamacpp-tp1-Q5_K_M
@ -1196,13 +1197,13 @@ nodes:
gguf_file_size_bytes: 24729130848
cache_snapshot_path: C:/Users/r0bin/.cache/huggingface/hub/models--LordNeel--Ornith-1.0-35B-GGUF-llamacpp-tp1/snapshots/c50d5d4407f70e43208dee836c66bb8a05c1be91
backend: vulkan
ctx_size: 524288
llamacpp_args: "--spec-type none -cb -fa on -b 4096 -ub 1024 --kv-unified --temp 0.6 --top-p 0.95 --top-k 20"
observed_process_args: "--ctx-size 524288 --port 8001 --jinja --context-shift --keep 16 --reasoning-format auto --no-webui --no-mmap -ngl 99 --kv-unified --spec-type none --temp 0.6 --top-k 20 --top-p 0.95 -b 4096 -cb -fa on -ub 1024"
observed_total_slots: 4
observed_total_ctx_size: 524288
ctx_size: 262144
llamacpp_args: "--spec-type none -np 1 -cb -fa on -b 4096 -ub 1024 --kv-unified --temp 0.6 --top-p 0.95 --top-k 20"
observed_process_args: "--ctx-size 262144 --port 8001 --jinja --context-shift --keep 16 --reasoning-format auto --no-webui --no-mmap -ngl 99 --kv-unified --spec-type none --temp 0.6 --top-k 20 --top-p 0.95 -b 4096 -cb -fa on -np 1 -ub 1024"
observed_total_slots: 1
observed_total_ctx_size: 262144
observed_slot_n_ctx: 262144
context_per_slot_note: -np is intentionally omitted; llama.cpp auto slots with --kv-unified avoid the prior fixed 524288/3 partition and report 262144-token slot windows backed by shared KV.
context_per_slot_note: -np 1 matches the IOP provider capacity and preserves one reusable prefix-cache lineage across agent turns.
residency:
observed_at: "2026-07-12"
process_resident_policy: keep llama-server loaded until explicit lemonade unload or service/process restart
@ -1262,15 +1263,15 @@ nodes:
recoverable_backup: C:/Users/r0bin/iop-field/IOP-OnexNode.pre-manual-lemonade-20260725T234232Z.xml
manual_remote_llm_toggle:
script: C:/Users/r0bin/iop-field/remote-llm-toggle.ps1
script_sha256: cd04150b460da9d3157e9431f0b25f2d80e63f957a7a14448feb6925b3ada11f
script_sha256: d1729c7928978d1d547f8ed20f86ef25a75cc4e0228857e332c0530541abd9c7
log: C:/Users/r0bin/iop-field/onex-remote-llm-toggle.log
default_action: toggle_by_complete_stack_readiness
process_start: Win32_Process.Create
readiness_requires:
- LemonadeServer.exe running and health status ok
- exact Ornith model with Vulkan ctx_size 524288 profile
- exact Ornith model with Vulkan ctx_size 262144 and -np 1 profile
- public listener 0.0.0.0:13305
- four llama slots with n_ctx 262144
- one llama slot with n_ctx 262144
- iop-node.exe running and established Edge TCP connection to port 18084
up_sequence:
- start LemonadeServer.exe
@ -1291,14 +1292,14 @@ nodes:
icon: C:/Users/r0bin/AppData/Roaming/Microsoft/Installer/{221D1879-DDCE-4E14-AC7C-ACA9F084FE21}/LemonadeIcon,0
boot_autostart: false
validation:
observed_at: "2026-07-26"
observed_at: "2026-08-13"
default_toggle_down: passed
default_toggle_up: passed
final_state: ready
listener_public: passed
model_profile_valid: passed
edge_connected: passed
slots: 4x262144
slots: 1x262144
- id: rtx5090-lemonade-node
alias: rtx5090-lemonade
role: lemonade-provider

View file

@ -42,7 +42,7 @@ nodes:
raw string
want bool
}{
{name: "positive is restart required", raw: "60000", want: true},
{name: "positive is restart required", raw: "45000", want: true},
{name: "explicit zero matches omitted", raw: "0"},
} {
t.Run(tc.name, func(t *testing.T) {

View file

@ -63,6 +63,13 @@ func prepareAnthropicChatBridge(body []byte, target string, profile config.Concr
if err != nil {
return nil, req, err
}
plan, err := selectProviderOperation(profile, config.OperationMessages, anthropicProviderRequirements(req))
if err != nil {
return nil, req, err
}
if plan.Operation != config.OperationChatCompletions || (plan.Effort != "" && plan.EffortWire != config.ProtocolEffortWireOpenAIChat && plan.EffortWire != config.ProtocolEffortWireGeminiChat) {
return nil, req, fmt.Errorf("selected profile does not support the Chat bridge request controls")
}
if req.TopK != nil {
return nil, req, fmt.Errorf("top_k is not supported by the Chat bridge")
}
@ -101,10 +108,10 @@ func prepareAnthropicChatBridge(body []byte, target string, profile config.Concr
"stream": req.Stream,
}
maxTokensField := "max_tokens"
if profile.ID == "openai" {
if mapping, ok := profile.EffortMapping(config.OperationChatCompletions); ok && mapping.Wire == config.ProtocolEffortWireOpenAIChat {
// OpenAI's current Chat completion models use the completion-specific
// field. Other OpenAI-compatible profiles retain their native legacy
// spelling instead of inheriting an OpenAI-only request contract.
// field. Profiles on other Chat-compatible wires retain their native
// legacy spelling instead of inheriting this request contract.
maxTokensField = "max_completion_tokens"
}
chat[maxTokensField] = *req.MaxTokens
@ -154,8 +161,8 @@ func prepareAnthropicChatBridge(body []byte, target string, profile config.Concr
chat["thinking_token_budget"] = req.Thinking.BudgetTokens
}
if req.OutputConfig != nil {
if req.OutputConfig.Effort != "" {
chat["reasoning_effort"] = req.OutputConfig.Effort
if plan.Effort != "" {
chat["reasoning_effort"] = plan.Effort
}
if req.OutputConfig.Format != nil {
var schema map[string]any

View file

@ -197,16 +197,17 @@ func TestAnthropicChatBridgeDropsUnsignedThinkingReplayForGenericProfile(t *test
func TestAnthropicChatBridgeRejectsUnsupportedBeforeWire(t *testing.T) {
for _, tc := range []struct {
name string
body string
beta string
name string
body string
beta string
errorType string
}{
{name: "top k", body: `{"model":"claude-route","max_tokens":16,"top_k":4,"messages":[{"role":"user","content":"hello"}]}`},
{name: "unknown block", body: `{"model":"claude-route","max_tokens":16,"messages":[{"role":"user","content":[{"type":"search_result","content":"unknown"}]}]}`},
{name: "unknown field", body: `{"model":"claude-route","max_tokens":16,"vendor_extension":true,"messages":[{"role":"user","content":"hello"}]}`},
{name: "context management scalar", body: `{"model":"claude-route","max_tokens":16,"context_management":"compact","messages":[{"role":"user","content":"hello"}]}`, beta: "context-management-2025-06-27"},
{name: "context management array", body: `{"model":"claude-route","max_tokens":16,"context_management":[],"messages":[{"role":"user","content":"hello"}]}`, beta: "context-management-2025-06-27"},
{name: "thinking capability", body: `{"model":"claude-route","max_tokens":16,"thinking":{"type":"enabled","budget_tokens":8},"messages":[{"role":"user","content":"hello"}]}`},
{name: "thinking capability", body: `{"model":"claude-route","max_tokens":16,"thinking":{"type":"enabled","budget_tokens":8},"messages":[{"role":"user","content":"hello"}]}`, errorType: "not_supported_error"},
{name: "tool strict", body: `{"model":"claude-route","max_tokens":16,"messages":[{"role":"user","content":"hello"}],"tools":[{"name":"Read","input_schema":{"type":"object"},"strict":true}]}`, beta: "advanced-tool-use-2025-11-20"},
{name: "tool eager input streaming", body: `{"model":"claude-route","max_tokens":16,"messages":[{"role":"user","content":"hello"}],"tools":[{"name":"Read","input_schema":{"type":"object"},"eager_input_streaming":true}]}`, beta: "advanced-tool-use-2025-11-20"},
{name: "thinking display value", body: `{"model":"claude-route","max_tokens":16,"thinking":{"type":"adaptive","display":"raw"},"messages":[{"role":"user","content":"hello"}]}`},
@ -228,7 +229,11 @@ func TestAnthropicChatBridgeRejectsUnsupportedBeforeWire(t *testing.T) {
req.Header.Set(anthropicBetaHeader, tc.beta)
}
w := serveAnthropicHTTPRequest(srv, req)
if w.Code != http.StatusBadRequest || !strings.Contains(w.Body.String(), `"type":"invalid_request_error"`) {
errorType := tc.errorType
if errorType == "" {
errorType = "invalid_request_error"
}
if w.Code != http.StatusBadRequest || !strings.Contains(w.Body.String(), `"type":"`+errorType+`"`) {
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
}
if got := len(fake.tunnelReqsSnapshot()); got != 0 {
@ -341,6 +346,94 @@ func TestAnthropicChatBridgeEffortExactTokenPreservation(t *testing.T) {
}
}
func TestAnthropicToolsAndEffortSelectResponsesWithLowerFallback(t *testing.T) {
candidate := anthropicTestCandidate(t, "openai")
candidate.ActualModel = "served-responses"
responsesMapping := candidate.ProtocolProfile.Normalization.Effort[string(config.OperationResponses)]
delete(responsesMapping.Levels, "max")
candidate.ProtocolProfile.Normalization.Effort[string(config.OperationResponses)] = responsesMapping
providerResponse := []byte(`{"id":"resp_effort","model":"served-responses","status":"completed","output":[{"type":"function_call","id":"fc_1","call_id":"call_1","name":"write_file","arguments":"{\"path\":\"index.html\"}"}],"usage":{"input_tokens":11,"output_tokens":3,"input_tokens_details":{"cached_tokens":2}}}`)
fake := &providerFakeRunService{
poolDispatchPath: string(edgeservice.ProviderPoolPathTunnel),
poolSelectedCandidate: candidate,
tunnelFrames: anthropicTunnelFrames(http.StatusOK, "application/json", providerResponse),
}
srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
srv.SetModelCatalog([]config.ModelCatalogEntry{{ID: "gpt-route", Providers: map[string]string{"openai": "served-responses"}}})
body := `{"model":"gpt-route","max_tokens":256,"output_config":{"effort":"max"},"tools":[{"name":"write_file","description":"write a file","input_schema":{"type":"object","properties":{"path":{"type":"string"}},"required":["path"]}}],"messages":[{"role":"user","content":"create index.html"}]}`
w := serveAnthropicRequest(srv, "/v1/messages", body)
if w.Code != http.StatusOK {
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
}
requests := fake.tunnelReqsSnapshot()
if len(requests) != 1 {
t.Fatalf("tunnel requests=%d, want 1", len(requests))
}
if requests[0].Operation != string(config.OperationResponses) || requests[0].Path != "/v1/responses" {
t.Fatalf("operation/path=%q/%q, want responses//v1/responses", requests[0].Operation, requests[0].Path)
}
var upstream map[string]any
if err := json.Unmarshal(fake.tunnelBodiesSnapshot()[0], &upstream); err != nil {
t.Fatal(err)
}
reasoning, _ := upstream["reasoning"].(map[string]any)
if reasoning["effort"] != "xhigh" {
t.Fatalf("reasoning effort=%v, want xhigh fallback", reasoning["effort"])
}
if _, hasChatMessages := upstream["messages"]; hasChatMessages {
t.Fatalf("Responses bridge emitted Chat messages: %+v", upstream)
}
if _, hasResponsesInput := upstream["input"]; !hasResponsesInput {
t.Fatalf("Responses bridge omitted input: %+v", upstream)
}
var response anthropicMessageResponse
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
t.Fatal(err)
}
if len(response.Content) != 1 || response.Content[0]["type"] != "tool_use" || response.Content[0]["name"] != "write_file" {
t.Fatalf("Anthropic tool response=%+v", response.Content)
}
if response.StopReason == nil || *response.StopReason != "tool_use" {
t.Fatalf("stop_reason=%v, want tool_use", response.StopReason)
}
}
func TestAnthropicStreamingToolsAndEffortUseResponsesBridge(t *testing.T) {
candidate := anthropicTestCandidate(t, "openai")
candidate.ActualModel = "served-responses"
providerResponse := []byte("event: response.completed\n" +
`data: {"type":"response.completed","response":{"id":"resp_stream","model":"served-responses","status":"completed","output":[{"type":"function_call","id":"fc_1","call_id":"call_1","name":"write_file","arguments":"{\"path\":\"index.html\"}"}],"usage":{"input_tokens":11,"output_tokens":3}}}` + "\n\n" +
"data: [DONE]\n\n")
fake := &providerFakeRunService{
poolDispatchPath: string(edgeservice.ProviderPoolPathTunnel),
poolSelectedCandidate: candidate,
tunnelFrames: anthropicTunnelFrames(http.StatusOK, "text/event-stream", providerResponse),
}
srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
srv.SetModelCatalog([]config.ModelCatalogEntry{{ID: "gpt-route", Providers: map[string]string{"openai": "served-responses"}}})
body := `{"model":"gpt-route","max_tokens":256,"stream":true,"output_config":{"effort":"high"},"tools":[{"name":"write_file","input_schema":{"type":"object"}}],"messages":[{"role":"user","content":"create index.html"}]}`
w := serveAnthropicRequest(srv, "/v1/messages", body)
if w.Code != http.StatusOK || w.Header().Get("Content-Type") != "text/event-stream" {
t.Fatalf("status=%d content-type=%q body=%s", w.Code, w.Header().Get("Content-Type"), w.Body.String())
}
requests := fake.tunnelReqsSnapshot()
if len(requests) != 1 || requests[0].Operation != string(config.OperationResponses) || !requests[0].Stream {
t.Fatalf("Responses stream dispatch mismatch: %+v", requests)
}
output := w.Body.String()
for _, want := range []string{"event: message_start", `"type":"tool_use"`, `"name":"write_file"`, "event: message_stop"} {
if !strings.Contains(output, want) {
t.Fatalf("Anthropic stream missing %q: %s", want, output)
}
}
if strings.Contains(output, "response.completed") || strings.Count(output, "event: message_stop") != 1 {
t.Fatalf("provider event leaked or terminal count mismatched: %s", output)
}
}
func TestAnthropicChatBridgeEffortRejectsInvalidValue(t *testing.T) {
for _, effort := range []string{"HIGH", "XHigh", "maxx", "xhighx", "h"} {
t.Run(effort, func(t *testing.T) {

View file

@ -247,8 +247,12 @@ func (s *Server) handleAnthropicMessages(w http.ResponseWriter, r *http.Request)
return
}
needsTools := anthropicRequestNeedsTools(body)
poolReq, presetIngress, err := s.anthropicPoolRequest(r, dispatch, envelope, body, config.OperationMessages, needsTools)
requirements, err := decodeAnthropicProviderRequirements(body)
if err != nil {
s.writeAnthropicPreIngressError(w, http.StatusBadRequest, "invalid_request_error", err.Error(), anthropicPreIngressInvalidEnvelope)
return
}
poolReq, presetIngress, err := s.anthropicPoolRequest(r, dispatch, envelope, body, config.OperationMessages, requirements)
if err != nil {
writeAnthropicError(w, http.StatusBadRequest, "invalid_request_error", err.Error())
return
@ -311,15 +315,28 @@ func (s *Server) handleAnthropicMessages(w http.ResponseWriter, r *http.Request)
}
defer result.Tunnel.Close()
switch result.DispatchInfo.ProfileDriver {
case string(config.ProtocolDriverAnthropicMessages):
profileOperation := result.DispatchInfo.ProfileOperation
if profileOperation == "" {
switch result.DispatchInfo.ProfileDriver {
case string(config.ProtocolDriverAnthropicMessages):
profileOperation = string(config.OperationMessages)
case string(config.ProtocolDriverOpenAIChat):
profileOperation = string(config.OperationChatCompletions)
case string(config.ProtocolDriverOpenAIResponses):
profileOperation = string(config.OperationResponses)
}
}
switch profileOperation {
case string(config.OperationMessages):
publicModelID := ""
if dispatch.IsPreset {
publicModelID = dispatch.ExternalModelID
}
s.writeAnthropicNativeTunnelResponse(w, r, result.Tunnel, publicModelID)
case string(config.ProtocolDriverOpenAIChat):
case string(config.OperationChatCompletions):
s.writeAnthropicChatBridgeResponse(w, r, result.Tunnel, envelope)
case string(config.OperationResponses):
s.writeAnthropicResponsesBridgeResponse(w, r, result.Tunnel, envelope)
default:
writeAnthropicError(w, http.StatusBadGateway, "api_error", "selected provider returned an unsupported protocol driver")
}
@ -547,7 +564,7 @@ func (s *Server) handleAnthropicCountTokens(w http.ResponseWriter, r *http.Reque
return
}
poolReq, _, err := s.anthropicPoolRequest(r, dispatch, envelope, body, config.OperationCountTokens, false)
poolReq, _, err := s.anthropicPoolRequest(r, dispatch, envelope, body, config.OperationCountTokens, providerRequestRequirements{})
if err != nil {
writeAnthropicError(w, http.StatusBadRequest, "invalid_request_error", err.Error())
return
@ -572,7 +589,7 @@ func (s *Server) anthropicPoolRequest(
envelope anthropicRequestEnvelope,
body []byte,
operation config.ProtocolOperation,
needsTools bool,
requirements providerRequestRequirements,
) (edgeservice.ProviderPoolDispatchRequest, presetIngressResult, error) {
metadata := principalMetadata(r.Context())
if metadata == nil {
@ -593,9 +610,9 @@ func (s *Server) anthropicPoolRequest(
}
// Resume-selector and ordinary continuations both construct the same
// trusted selector request; only local eligibility bypasses the pool.
return s.buildAnthropicPoolRequest(r, dispatch, envelope, body, operation, needsTools, metadata, presetIngress)
return s.buildAnthropicPoolRequest(r, dispatch, envelope, body, operation, requirements, metadata, presetIngress)
}
return s.buildAnthropicPoolRequest(r, dispatch, envelope, body, operation, needsTools, metadata, presetIngressResult{})
return s.buildAnthropicPoolRequest(r, dispatch, envelope, body, operation, requirements, metadata, presetIngressResult{})
}
func (s *Server) buildAnthropicPoolRequest(
@ -604,7 +621,7 @@ func (s *Server) buildAnthropicPoolRequest(
envelope anthropicRequestEnvelope,
body []byte,
operation config.ProtocolOperation,
needsTools bool,
requirements providerRequestRequirements,
metadata map[string]string,
presetIngress presetIngressResult,
) (edgeservice.ProviderPoolDispatchRequest, presetIngressResult, error) {
@ -634,7 +651,7 @@ func (s *Server) buildAnthropicPoolRequest(
EstimatedInputTokens: estimate, ContextClass: contextClass, ProviderPool: true,
},
}
poolReq.AcceptCandidate = anthropicCandidatePredicate(operation, envelope.Stream, needsTools)
poolReq.AcceptCandidate = anthropicCandidatePredicate(operation, requirements)
if dispatch.Managed {
poolReq.AcceptCandidate = composeCandidatePredicates(poolReq.AcceptCandidate, dispatch.CandidatePredicate())
}
@ -643,18 +660,22 @@ func (s *Server) buildAnthropicPoolRequest(
return tunnelReq, newAnthropicClientError("not_supported_error", fmt.Errorf("selected provider has no concrete protocol profile"))
}
profile := selected.ProtocolProfile.Clone()
plan, err := selectProviderOperation(profile, operation, requirements)
if err != nil {
return tunnelReq, newAnthropicClientError("not_supported_error", err)
}
headers, err := s.anthropicUpstreamHeaders(r, profile, profile.Driver == config.ProtocolDriverAnthropicMessages)
if err != nil {
return tunnelReq, newAnthropicClientError("invalid_request_error", err)
}
tunnelReq.Headers = headers
switch profile.Driver {
case config.ProtocolDriverAnthropicMessages:
tunnelReq.Operation = string(operation)
switch plan.Operation {
case config.OperationMessages, config.OperationCountTokens:
tunnelReq.Operation = string(plan.Operation)
tunnelReq.BuildBody = func(target string) ([]byte, error) {
return rewriteResponsesModel(body, target)
}
case config.ProtocolDriverOpenAIChat:
case config.OperationChatCompletions:
if operation != config.OperationMessages {
return tunnelReq, newAnthropicClientError("not_supported_error", fmt.Errorf("selected Chat profile has no native count-tokens operation"))
}
@ -668,6 +689,21 @@ func (s *Server) buildAnthropicPoolRequest(
tunnelReq.Operation = string(config.OperationChatCompletions)
tunnelReq.Body = bridged
tunnelReq.BuildBody = nil
case config.OperationResponses:
if operation != config.OperationMessages {
return tunnelReq, newAnthropicClientError("not_supported_error", fmt.Errorf("selected Responses profile has no native count-tokens operation"))
}
if err := validateAnthropicHeaders(r); err != nil {
return tunnelReq, newAnthropicClientError("invalid_request_error", err)
}
bridged, _, err := prepareAnthropicResponsesBridge(body, selected.ActualModel, profile, plan)
if err != nil {
return tunnelReq, newAnthropicClientError("invalid_request_error", err)
}
tunnelReq.Operation = string(config.OperationResponses)
tunnelReq.Path = "/v1/responses"
tunnelReq.Body = bridged
tunnelReq.BuildBody = nil
default:
return tunnelReq, newAnthropicClientError("not_supported_error", fmt.Errorf("unsupported protocol driver %q", profile.Driver))
}
@ -676,28 +712,19 @@ func (s *Server) buildAnthropicPoolRequest(
return poolReq, presetIngress, nil
}
func anthropicCandidatePredicate(operation config.ProtocolOperation, stream, needsTools bool) edgeservice.ProviderPoolCandidatePredicate {
func anthropicCandidatePredicate(operation config.ProtocolOperation, requirements providerRequestRequirements) edgeservice.ProviderPoolCandidatePredicate {
return func(candidate edgeservice.ProviderPoolCandidate) bool {
profile := candidate.ProtocolProfile
if profile == nil || candidate.ExecutionPath != string(edgeservice.ProviderPoolPathTunnel) {
return false
}
if stream && !profile.HasCapability("streaming") {
return false
}
if needsTools && !profile.HasCapability("tool_calling") {
return false
}
switch operation {
case config.OperationCountTokens:
return profile.Driver == config.ProtocolDriverAnthropicMessages &&
profile.HasCapability("count_tokens") && profileHasOperation(*profile, config.OperationCountTokens)
case config.OperationMessages:
if profile.Driver == config.ProtocolDriverAnthropicMessages {
return profile.HasCapability("messages") && profileHasOperation(*profile, config.OperationMessages)
}
return profile.Driver == config.ProtocolDriverOpenAIChat &&
profile.HasCapability("chat") && profileHasOperation(*profile, config.OperationChatCompletions)
_, err := selectProviderOperation(*profile, operation, requirements)
return err == nil
default:
return false
}

View file

@ -1203,3 +1203,140 @@ func (s *Server) writeAnthropicChatBridgeResponse(w http.ResponseWriter, r *http
}
}
}
// writeAnthropicResponsesBridgeResponse translates an OpenAI Responses
// provider result back to the Messages surface. Streaming input is currently
// buffered until the Responses terminal so the caller still receives one
// valid Anthropic SSE lifecycle without exposing provider-specific events.
func (s *Server) writeAnthropicResponsesBridgeResponse(w http.ResponseWriter, r *http.Request, handle edgeservice.ProviderTunnelResult, envelope anthropicRequestEnvelope) {
frames := handle.Stream().Frames
if frames == nil {
writeAnthropicError(w, http.StatusBadGateway, "api_error", "provider tunnel is unavailable")
return
}
timer := time.NewTimer(handle.WaitTimeout())
defer timer.Stop()
status := http.StatusOK
headers := make(map[string]string)
var body []byte
for {
select {
case <-r.Context().Done():
s.cancelRunOnHTTPGiveUp(handle.Dispatch(), r.Context().Err())
return
case <-timer.C:
s.cancelRunOnHTTPGiveUp(handle.Dispatch(), errRunTimedOut)
writeAnthropicError(w, http.StatusBadGateway, "api_error", "provider response timed out")
return
case frame, ok := <-frames:
if !ok {
writeAnthropicError(w, http.StatusBadGateway, "api_error", "provider tunnel closed before a response")
return
}
switch frame.GetKind() {
case iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START:
status = int(frame.GetStatusCode())
if status == 0 {
status = http.StatusOK
}
for key, value := range frame.GetHeaders() {
headers[key] = value
}
case iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY:
body = append(body, frame.GetBody()...)
case iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_ERROR:
writeAnthropicError(w, http.StatusBadGateway, "api_error", "provider tunnel failed")
return
case iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END:
copyAnthropicResponseHeaders(w.Header(), headers)
w.Header().Del("Content-Length")
if status >= http.StatusBadRequest {
writeAnthropicError(w, status, "api_error", "upstream provider rejected the request")
return
}
responseBody := body
if envelope.Stream {
var terminal struct {
Type string `json:"type"`
Response json.RawMessage `json:"response"`
}
for _, event := range splitOpenAIResponsesSSE(body) {
if json.Unmarshal(event, &terminal) == nil && terminal.Type == "response.completed" && len(terminal.Response) > 0 {
responseBody = terminal.Response
}
}
}
converted, err := convertResponsesResponseToAnthropic(responseBody, envelope.Model)
if err != nil {
writeAnthropicError(w, http.StatusBadGateway, "api_error", "upstream response could not be translated")
return
}
if !envelope.Stream {
writeJSON(w, http.StatusOK, converted)
return
}
writeBufferedAnthropicMessageStream(w, converted)
return
}
}
}
}
func splitOpenAIResponsesSSE(body []byte) [][]byte {
var payloads [][]byte
normalized := bytes.ReplaceAll(body, []byte("\r\n"), []byte("\n"))
for _, event := range bytes.Split(normalized, []byte("\n\n")) {
var data [][]byte
for _, line := range bytes.Split(event, []byte("\n")) {
line = bytes.TrimSpace(line)
if bytes.HasPrefix(line, []byte("data:")) {
part := bytes.TrimSpace(bytes.TrimPrefix(line, []byte("data:")))
if !bytes.Equal(part, []byte("[DONE]")) {
data = append(data, part)
}
}
}
if len(data) > 0 {
payloads = append(payloads, bytes.Join(data, []byte("\n")))
}
}
return payloads
}
func writeBufferedAnthropicMessageStream(w http.ResponseWriter, message anthropicMessageResponse) {
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.WriteHeader(http.StatusOK)
start := message
start.Content = []map[string]any{}
start.StopReason = nil
start.Usage.OutputTokens = 0
_ = writeAnthropicSSEEvent(w, "message_start", map[string]any{"type": "message_start", "message": start})
for index, block := range message.Content {
blockType, _ := block["type"].(string)
opening := map[string]any{"type": blockType}
var delta map[string]any
switch blockType {
case "text":
opening["text"] = ""
delta = map[string]any{"type": "text_delta", "text": block["text"]}
case "thinking":
opening["thinking"], opening["signature"] = "", ""
delta = map[string]any{"type": "thinking_delta", "thinking": block["thinking"]}
case "tool_use":
opening["id"], opening["name"], opening["input"] = block["id"], block["name"], map[string]any{}
encodedInput, _ := json.Marshal(block["input"])
delta = map[string]any{"type": "input_json_delta", "partial_json": string(encodedInput)}
default:
continue
}
_ = writeAnthropicSSEEvent(w, "content_block_start", map[string]any{"type": "content_block_start", "index": index, "content_block": opening})
_ = writeAnthropicSSEEvent(w, "content_block_delta", map[string]any{"type": "content_block_delta", "index": index, "delta": delta})
_ = writeAnthropicSSEEvent(w, "content_block_stop", map[string]any{"type": "content_block_stop", "index": index})
}
_ = writeAnthropicSSEEvent(w, "message_delta", map[string]any{
"type": "message_delta", "delta": map[string]any{"stop_reason": message.StopReason, "stop_sequence": nil},
"usage": message.Usage,
})
_ = writeAnthropicSSEEvent(w, "message_stop", map[string]any{"type": "message_stop"})
}

View file

@ -15,8 +15,8 @@ func validateThinkControl(req *chatCompletionRequest) error {
if eff == "" {
return fmt.Errorf("reasoning_effort cannot be empty when present")
}
if eff != "none" && eff != "low" && eff != "medium" && eff != "high" {
return fmt.Errorf("reasoning_effort must be one of none, low, medium, or high")
if eff != "none" && eff != "low" && eff != "medium" && eff != "high" && eff != "xhigh" && eff != "max" {
return fmt.Errorf("reasoning_effort must be one of none, low, medium, high, xhigh, or max")
}
}
if req.Think != nil && !*req.Think {

View file

@ -13,6 +13,7 @@ import (
)
var geminiPathToken = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]*$`)
var geminiCallerModelLabel = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9 ._-]{0,127}$`)
var geminiToolCallID = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$`)
const geminiRejectionLogMessage = "edge_gemini_rejection"
@ -104,7 +105,7 @@ func parseGeminiStreamPath(r *http.Request) (string, string, error) {
}
routeID := parts[0]
callerModel := strings.TrimSuffix(parts[3], suffix)
if !geminiPathToken.MatchString(routeID) || !geminiPathToken.MatchString(callerModel) {
if !geminiPathToken.MatchString(routeID) || !geminiCallerModelLabel.MatchString(callerModel) || strings.TrimSpace(callerModel) != callerModel {
return "", "", fmt.Errorf("invalid path token")
}
query := r.URL.Query()
@ -468,7 +469,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 +482,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, "")

View file

@ -59,6 +59,31 @@ func TestGeminiIngressAuthenticatesAndStreamsThroughChatRoute(t *testing.T) {
}
}
func TestGeminiStreamPathAcceptsOfficialCallerModelLabel(t *testing.T) {
req := httptest.NewRequest(
http.MethodPost,
"/gemini/gemini-direct/v1beta/models/Gemini%203.6%20Flash:streamGenerateContent?alt=sse",
nil,
)
routeID, callerModel, err := parseGeminiStreamPath(req)
if err != nil {
t.Fatalf("parseGeminiStreamPath: %v", err)
}
if routeID != "gemini-direct" || callerModel != "Gemini 3.6 Flash" {
t.Fatalf("path = %q/%q", routeID, callerModel)
}
for _, path := range []string{
"/gemini/gemini-direct/v1beta/models/%20Gemini:streamGenerateContent?alt=sse",
"/gemini/gemini-direct/v1beta/models/Gemini%20:streamGenerateContent?alt=sse",
} {
bad := httptest.NewRequest(http.MethodPost, path, nil)
if _, _, err := parseGeminiStreamPath(bad); err == nil {
t.Fatalf("expected invalid caller model path: %s", path)
}
}
}
func TestGeminiIngressRejectsAuthenticationAndShapeBeforeDispatch(t *testing.T) {
base := `{"contents":[{"role":"user","parts":[{"text":"hi"}]}]}`
for _, tc := range []struct {
@ -185,6 +210,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

View file

@ -110,6 +110,7 @@ func presetSelectorAdmission(
NodeID: selected.NodeID,
ExecutionPath: selected.ExecutionPath,
ProfileDriver: selected.ProfileDriver,
ProfileOperation: selected.ProfileOperation,
ProfileCapabilities: append([]string(nil), selected.ProfileCapabilities...),
}
expectedGroup := presetSelectorModelGroupKey(dispatch, dispatch.ExternalModelID)
@ -118,7 +119,7 @@ func presetSelectorAdmission(
strings.TrimSpace(selected.ProviderID) != "" &&
strings.TrimSpace(selected.ModelGroupKey) == strings.TrimSpace(expectedGroup) &&
strings.TrimSpace(selected.ExecutionPath) == string(result.Path)
gate.CapabilitySatisfied = selectedPresetCapability(protocol, selected.ProfileDriver, selected.ProfileCapabilities)
gate.CapabilitySatisfied = selectedPresetCapability(protocol, selected.ProfileOperation, selected.ProfileDriver, selected.ProfileCapabilities)
return selected, gate, nil
}
@ -169,10 +170,21 @@ func (s *Server) runLivePresetSelectorResult(
}
}
func selectedPresetCapability(protocol, driver string, capabilities []string) bool {
func selectedPresetCapability(protocol, operation, driver string, capabilities []string) bool {
required := "chat"
if protocol == "anthropic" && driver == string(config.ProtocolDriverAnthropicMessages) {
required = "messages"
if protocol == "anthropic" {
switch operation {
case string(config.OperationMessages):
required = "messages"
case string(config.OperationResponses):
required = "responses"
case string(config.OperationChatCompletions):
required = "chat"
default:
if driver == string(config.ProtocolDriverAnthropicMessages) {
required = "messages"
}
}
}
for _, capability := range capabilities {
if strings.TrimSpace(capability) == required {
@ -440,7 +452,7 @@ func collectPresetTunnelResult(ctx context.Context, handle edgeservice.ProviderT
if status < http.StatusOK || status >= http.StatusMultipleChoices {
return normalizedStageOutput{}, fmt.Errorf("preset selector provider returned HTTP %d", status)
}
stage, err := decodePresetTunnelBody(body.Bytes(), contentType, protocol, selected.ProfileDriver)
stage, err := decodePresetTunnelBody(body.Bytes(), contentType, protocol, selected.ProfileOperation, selected.ProfileDriver)
if err != nil {
return normalizedStageOutput{}, err
}
@ -453,7 +465,7 @@ func collectPresetTunnelResult(ctx context.Context, handle edgeservice.ProviderT
TotalTokens: int(sideUsage.GetInputTokens() + sideUsage.GetOutputTokens()),
ReasoningTokens: int(sideUsage.GetReasoningTokens()), CachedInputTokens: int(sideUsage.GetCachedInputTokens()),
}
if protocol == "anthropic" && selected.ProfileDriver == string(config.ProtocolDriverAnthropicMessages) {
if protocol == "anthropic" && selected.ProfileOperation == string(config.OperationMessages) {
stage.Usage, _ = json.Marshal(anthropicUsage{
InputTokens: int(sideUsage.GetInputTokens()), OutputTokens: int(sideUsage.GetOutputTokens()),
CacheReadInputTokens: int(sideUsage.GetCachedInputTokens()),
@ -494,14 +506,17 @@ func unixSeconds(timestamp int64) int64 {
return timestamp
}
func decodePresetTunnelBody(body []byte, contentType, protocol, driver string) (normalizedStageOutput, error) {
func decodePresetTunnelBody(body []byte, contentType, protocol, operation, driver string) (normalizedStageOutput, error) {
streaming := strings.Contains(strings.ToLower(contentType), "text/event-stream") || bytes.Contains(body, []byte("data:"))
if protocol == "anthropic" && driver == string(config.ProtocolDriverAnthropicMessages) {
if protocol == "anthropic" && (operation == string(config.OperationMessages) || (operation == "" && driver == string(config.ProtocolDriverAnthropicMessages))) {
if streaming {
return decodeAnthropicPresetSSE(body)
}
return decodeAnthropicPresetJSON(body)
}
if protocol == "anthropic" && operation == string(config.OperationResponses) {
return decodeResponsesPresetBody(body, streaming)
}
var stage normalizedStageOutput
var err error
if streaming {
@ -519,6 +534,30 @@ func decodePresetTunnelBody(body []byte, contentType, protocol, driver string) (
return stage, nil
}
func decodeResponsesPresetBody(body []byte, streaming bool) (normalizedStageOutput, error) {
responseBody := body
if streaming {
for _, payload := range splitOpenAIResponsesSSE(body) {
var event struct {
Type string `json:"type"`
Response json.RawMessage `json:"response"`
}
if json.Unmarshal(payload, &event) == nil && event.Type == "response.completed" && len(event.Response) > 0 {
responseBody = event.Response
}
}
}
converted, err := convertResponsesResponseToAnthropic(responseBody, "")
if err != nil {
return normalizedStageOutput{}, err
}
raw, err := json.Marshal(converted)
if err != nil {
return normalizedStageOutput{}, err
}
return decodeAnthropicPresetJSON(raw)
}
func decodeOpenAIPresetJSON(body []byte) (normalizedStageOutput, error) {
var response struct {
ID string `json:"id"`
@ -1593,7 +1632,8 @@ func stageCorrelation(stageID string, output normalizedStageOutput, dispatch edg
// protocol. The tunnel decode and the HTTP-turn stage source both select their
// decoder from this single fact rather than the caller endpoint.
func hotPathStageWireProtocol(dispatch edgeservice.RunDispatch) string {
if dispatch.ProfileDriver == string(config.ProtocolDriverAnthropicMessages) {
if dispatch.ProfileOperation == string(config.OperationMessages) ||
(dispatch.ProfileOperation == "" && dispatch.ProfileDriver == string(config.ProtocolDriverAnthropicMessages)) {
return "anthropic"
}
return "openai"

View file

@ -105,6 +105,7 @@ type hotPathSelectorGate struct {
NodeID string
ExecutionPath string
ProfileDriver string
ProfileOperation string
ProfileCapabilities []string
Healthy bool
CapabilitySatisfied bool

View file

@ -0,0 +1,457 @@
package openai
import (
"encoding/json"
"fmt"
"strings"
edgeservice "iop/apps/edge/internal/service"
"iop/packages/go/config"
)
// providerRequestRequirements is the caller-neutral request shape used for
// provider operation selection. Agent or SDK identity is intentionally absent.
type providerRequestRequirements struct {
Effort string
HasTools bool
HasTokenBudget bool
Stream bool
StructuredOutput bool
}
type providerOperationPlan struct {
Operation config.ProtocolOperation
Effort string
EffortWire string
}
type openAIResponsesBridgeResponse struct {
ID string `json:"id"`
Model string `json:"model"`
Status string `json:"status"`
Output []struct {
Type string `json:"type"`
ID string `json:"id"`
CallID string `json:"call_id"`
Name string `json:"name"`
Arguments string `json:"arguments"`
Content []struct {
Type string `json:"type"`
Text string `json:"text"`
} `json:"content"`
Summary []struct {
Type string `json:"type"`
Text string `json:"text"`
} `json:"summary"`
} `json:"output"`
Usage struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
InputDetails struct {
CachedTokens int `json:"cached_tokens"`
} `json:"input_tokens_details"`
} `json:"usage"`
IncompleteDetails struct {
Reason string `json:"reason"`
} `json:"incomplete_details"`
}
func anthropicProviderRequirements(req anthropicMessageRequest) providerRequestRequirements {
requirements := providerRequestRequirements{
HasTools: len(req.Tools) > 0,
Stream: req.Stream,
HasTokenBudget: req.Thinking != nil && req.Thinking.Type == "enabled",
StructuredOutput: req.OutputConfig != nil && req.OutputConfig.Format != nil,
}
if req.OutputConfig != nil {
requirements.Effort = strings.TrimSpace(req.OutputConfig.Effort)
}
return requirements
}
func decodeAnthropicProviderRequirements(body []byte) (providerRequestRequirements, error) {
var request struct {
Stream bool `json:"stream"`
Tools []json.RawMessage `json:"tools"`
Thinking *anthropicThinkingConfig `json:"thinking"`
OutputConfig *anthropicOutputConfig `json:"output_config"`
}
if err := json.Unmarshal(body, &request); err != nil {
return providerRequestRequirements{}, fmt.Errorf("decode Messages request")
}
return anthropicProviderRequirements(anthropicMessageRequest{
Stream: request.Stream, Tools: make([]anthropicTool, len(request.Tools)),
Thinking: request.Thinking, OutputConfig: request.OutputConfig,
}), nil
}
func decodeResponsesProviderRequirements(body []byte) (providerRequestRequirements, error) {
var request struct {
Tools []json.RawMessage `json:"tools"`
Reasoning *struct {
Effort string `json:"effort"`
} `json:"reasoning"`
}
if err := json.Unmarshal(body, &request); err != nil {
return providerRequestRequirements{}, fmt.Errorf("decode Responses request")
}
requirements := providerRequestRequirements{HasTools: len(request.Tools) > 0}
if request.Reasoning != nil {
requirements.Effort = strings.TrimSpace(request.Reasoning.Effort)
}
return requirements, nil
}
func responsesCandidatePredicate(requirements providerRequestRequirements) edgeservice.ProviderPoolCandidatePredicate {
return func(candidate edgeservice.ProviderPoolCandidate) bool {
// A nil profile is the legacy tunnel contract: operation resolution is
// deferred to the existing dispatch path, which must remain compatible.
if candidate.ProtocolProfile == nil {
return true
}
if candidate.ExecutionPath != string(edgeservice.ProviderPoolPathTunnel) {
return requirements.Effort == "" && !requirements.HasTools && !requirements.HasTokenBudget
}
_, err := selectProviderOperation(*candidate.ProtocolProfile, config.OperationResponses, requirements)
return err == nil
}
}
func rewriteResponsesProviderControls(body []byte, target string, plan providerOperationPlan) ([]byte, error) {
patches := make([]topLevelJSONPatch, 0, 2)
if strings.TrimSpace(target) != "" {
modelJSON, err := json.Marshal(target)
if err != nil {
return nil, err
}
patches = append(patches, topLevelJSONPatch{name: "model", value: modelJSON})
}
if plan.Effort != "" {
var root map[string]json.RawMessage
if err := json.Unmarshal(body, &root); err != nil {
return nil, fmt.Errorf("decode Responses request")
}
var reasoning map[string]any
if raw := root["reasoning"]; len(raw) > 0 && string(raw) != "null" {
if err := json.Unmarshal(raw, &reasoning); err != nil {
return nil, fmt.Errorf("reasoning must be an object")
}
}
if reasoning == nil {
reasoning = make(map[string]any)
}
reasoning["effort"] = plan.Effort
reasoningJSON, err := json.Marshal(reasoning)
if err != nil {
return nil, err
}
patches = append(patches, topLevelJSONPatch{name: "reasoning", value: reasoningJSON})
}
if len(patches) == 0 {
return body, nil
}
patchPlan, err := planTopLevelJSONPatches(body, patches)
if err != nil {
return nil, err
}
return patchPlan.apply(), nil
}
// selectProviderOperation chooses an operation solely from normalized request
// requirements and the selected provider profile. The order prefers the
// closest wire surface, but only an operation that preserves every declared
// requirement is eligible.
func selectProviderOperation(profile config.ConcreteProtocolProfile, ingress config.ProtocolOperation, requirements providerRequestRequirements) (providerOperationPlan, error) {
operations := []config.ProtocolOperation{ingress}
if ingress == config.OperationMessages {
switch profile.Driver {
case config.ProtocolDriverAnthropicMessages:
operations = []config.ProtocolOperation{config.OperationMessages}
case config.ProtocolDriverOpenAIChat:
operations = []config.ProtocolOperation{config.OperationChatCompletions, config.OperationResponses}
case config.ProtocolDriverOpenAIResponses:
operations = []config.ProtocolOperation{config.OperationResponses}
default:
operations = nil
}
}
for _, operation := range operations {
if _, ok := profile.Operations[string(operation)]; !ok {
continue
}
if required := operationRequiredCapability(operation); required != "" && !profile.HasCapability(required) {
continue
}
if requirements.Stream && !profile.HasCapability("streaming") {
continue
}
if requirements.HasTools && !profile.HasCapability("tool_calling") {
continue
}
plan := providerOperationPlan{Operation: operation}
mapping, hasMapping := profile.EffortMapping(operation)
supportsTokenBudget := hasMapping && mapping.TokenBudget
if operation == config.OperationChatCompletions && profileSupportsAnthropicThinking(profile) {
supportsTokenBudget = true
}
if requirements.HasTokenBudget && !supportsTokenBudget {
continue
}
if requirements.Effort != "" {
mapped, ok := profile.MapReasoningEffort(operation, requirements.Effort, requirements.HasTools)
if !ok {
continue
}
plan.Effort = mapped
plan.EffortWire = mapping.Wire
}
return plan, nil
}
return providerOperationPlan{}, fmt.Errorf("protocol profile %q cannot preserve the requested operation, tools, and reasoning controls", profile.ID)
}
func operationRequiredCapability(operation config.ProtocolOperation) string {
switch operation {
case config.OperationMessages:
return "messages"
case config.OperationChatCompletions:
return "chat"
case config.OperationResponses:
return "responses"
case config.OperationCountTokens:
return "count_tokens"
default:
return ""
}
}
func prepareAnthropicResponsesBridge(body []byte, target string, profile config.ConcreteProtocolProfile, plan providerOperationPlan) ([]byte, anthropicMessageRequest, error) {
req, err := decodeAnthropicMessageRequest(body, true)
if err != nil {
return nil, req, err
}
if plan.Operation != config.OperationResponses || (plan.Effort != "" && plan.EffortWire != config.ProtocolEffortWireOpenAIResponses) {
return nil, req, fmt.Errorf("selected operation has incompatible Responses effort normalization")
}
if req.TopK != nil {
return nil, req, fmt.Errorf("top_k is not supported by the Responses bridge")
}
if req.Thinking != nil && req.Thinking.Type == "enabled" {
return nil, req, fmt.Errorf("selected Responses profile does not support an explicit thinking token budget")
}
input := make([]map[string]any, 0, len(req.Messages))
for index, message := range req.Messages {
blocks, err := decodeAnthropicContent(message.Content)
if err != nil {
return nil, req, fmt.Errorf("messages[%d].content: %w", index, err)
}
converted, err := anthropicMessageToResponses(message.Role, blocks)
if err != nil {
return nil, req, fmt.Errorf("messages[%d]: %w", index, err)
}
input = append(input, converted...)
}
responses := map[string]any{
"model": target,
"input": input,
"max_output_tokens": *req.MaxTokens,
"stream": req.Stream,
}
if system, err := decodeAnthropicSystem(req.System); err != nil {
return nil, req, err
} else if len(system) > 0 {
parts := make([]string, 0, len(system))
for _, block := range system {
parts = append(parts, block.Text)
}
responses["instructions"] = strings.Join(parts, "\n")
}
if plan.Effort != "" {
responses["reasoning"] = map[string]any{"effort": plan.Effort}
}
if req.Temperature != nil {
responses["temperature"] = *req.Temperature
}
if req.TopP != nil {
responses["top_p"] = *req.TopP
}
if len(req.StopSequences) > 0 {
return nil, req, fmt.Errorf("stop_sequences is not supported by the Responses bridge")
}
if len(req.Tools) > 0 {
tools := make([]map[string]any, 0, len(req.Tools))
for _, tool := range req.Tools {
var schema map[string]any
if err := json.Unmarshal(tool.InputSchema, &schema); err != nil {
return nil, req, fmt.Errorf("tool %q input_schema is invalid", tool.Name)
}
converted := map[string]any{"type": "function", "name": tool.Name, "parameters": schema}
if tool.Description != "" {
converted["description"] = tool.Description
}
tools = append(tools, converted)
}
responses["tools"] = tools
}
if req.ToolChoice != nil {
choice, parallel := anthropicToolChoiceToResponses(*req.ToolChoice)
responses["tool_choice"] = choice
if parallel != nil {
responses["parallel_tool_calls"] = *parallel
}
}
if req.OutputConfig != nil && req.OutputConfig.Format != nil {
var schema map[string]any
if err := json.Unmarshal(req.OutputConfig.Format.Schema, &schema); err != nil {
return nil, req, fmt.Errorf("decode output_config.format.schema: %w", err)
}
responses["text"] = map[string]any{"format": map[string]any{
"type": "json_schema", "name": "response", "strict": true, "schema": schema,
}}
}
encoded, err := json.Marshal(responses)
if err != nil {
return nil, req, fmt.Errorf("encode Responses bridge request: %w", err)
}
return encoded, req, nil
}
func anthropicMessageToResponses(role string, blocks []anthropicContentBlock) ([]map[string]any, error) {
if role == "assistant" {
out := make([]map[string]any, 0, len(blocks))
for _, block := range blocks {
switch block.Type {
case "text":
out = append(out, map[string]any{"type": "message", "role": "assistant", "content": []map[string]any{{"type": "output_text", "text": block.Text}}})
case "thinking":
if block.Signature != "" {
return nil, fmt.Errorf("signed thinking blocks cannot be represented by the Responses bridge")
}
case "tool_use":
callID, _, _ := decodeAnthropicBridgeToolID(block.ID)
out = append(out, map[string]any{"type": "function_call", "call_id": callID, "name": block.Name, "arguments": string(block.Input)})
default:
return nil, fmt.Errorf("content block %q is invalid for an assistant message", block.Type)
}
}
return out, nil
}
out := make([]map[string]any, 0, len(blocks))
content := make([]map[string]any, 0, len(blocks))
flushContent := func() {
if len(content) > 0 {
out = append(out, map[string]any{"type": "message", "role": "user", "content": content})
content = nil
}
}
for _, block := range blocks {
switch block.Type {
case "text":
content = append(content, map[string]any{"type": "input_text", "text": block.Text})
case "image":
imageURL := block.Source.URL
if block.Source.Type == "base64" {
imageURL = "data:" + block.Source.MediaType + ";base64," + block.Source.Data
}
content = append(content, map[string]any{"type": "input_image", "image_url": imageURL})
case "tool_result":
flushContent()
result, err := anthropicToolResultText(block.Content)
if err != nil {
return nil, err
}
if block.IsError {
result = "Error: " + result
}
callID, _, _ := decodeAnthropicBridgeToolID(block.ToolUseID)
out = append(out, map[string]any{"type": "function_call_output", "call_id": callID, "output": result})
default:
return nil, fmt.Errorf("content block %q is invalid for a user message", block.Type)
}
}
flushContent()
if len(out) == 0 {
return nil, fmt.Errorf("user message content is empty")
}
return out, nil
}
func anthropicToolChoiceToResponses(choice anthropicToolChoice) (any, *bool) {
parallel := !choice.DisableParallelToolUse
switch choice.Type {
case "auto":
return "auto", &parallel
case "any":
return "required", &parallel
case "none":
return "none", &parallel
default:
return map[string]any{"type": "function", "name": choice.Name}, &parallel
}
}
func convertResponsesResponseToAnthropic(body []byte, requestModel string) (anthropicMessageResponse, error) {
var response openAIResponsesBridgeResponse
if err := json.Unmarshal(body, &response); err != nil {
return anthropicMessageResponse{}, fmt.Errorf("decode Responses response: %w", err)
}
if strings.TrimSpace(response.ID) == "" {
return anthropicMessageResponse{}, fmt.Errorf("Responses response has no id")
}
content := make([]map[string]any, 0, len(response.Output))
hasTools := false
for _, item := range response.Output {
switch item.Type {
case "message":
for _, part := range item.Content {
if part.Type == "output_text" && part.Text != "" {
content = append(content, map[string]any{"type": "text", "text": part.Text})
}
}
case "reasoning":
for _, part := range item.Summary {
if part.Text != "" {
content = append(content, map[string]any{"type": "thinking", "thinking": part.Text, "signature": ""})
}
}
case "function_call":
if item.CallID == "" || item.Name == "" || !json.Valid([]byte(item.Arguments)) {
return anthropicMessageResponse{}, fmt.Errorf("Responses function call has invalid call_id, name, or arguments")
}
var input any
if err := json.Unmarshal([]byte(item.Arguments), &input); err != nil {
return anthropicMessageResponse{}, fmt.Errorf("decode Responses function arguments: %w", err)
}
content = append(content, map[string]any{
"type": "tool_use", "id": encodeAnthropicBridgeToolID(item.CallID, openAIChatToolExtraContent{}),
"name": item.Name, "input": input,
})
hasTools = true
}
}
stopReason := "end_turn"
if hasTools {
stopReason = "tool_use"
} else if response.Status == "incomplete" && response.IncompleteDetails.Reason == "max_output_tokens" {
stopReason = "max_tokens"
}
model := requestModel
if model == "" {
model = response.Model
}
return anthropicMessageResponse{
ID: response.ID, Type: "message", Role: "assistant", Model: model, Content: content,
StopReason: &stopReason,
Usage: anthropicUsage{
InputTokens: response.Usage.InputTokens, OutputTokens: response.Usage.OutputTokens,
CacheReadInputTokens: response.Usage.InputDetails.CachedTokens,
},
}, nil
}

View file

@ -247,6 +247,9 @@ func (s *providerFakeRunService) SubmitProviderPool(_ context.Context, req edges
if selectedProvider := strings.TrimSpace(s.poolSelectedCandidate.ProviderID); selectedProvider != "" {
tunnelReq.ProviderID = selectedProvider
}
if selectedTarget := strings.TrimSpace(s.poolSelectedCandidate.ActualModel); selectedTarget != "" {
tunnelReq.Target = selectedTarget
}
if req.PrepareProtocolTunnel != nil {
tunnelReqPrepared, prepErr := req.PrepareProtocolTunnel(tunnelReq, s.poolSelectedCandidate)
if prepErr != nil {
@ -291,6 +294,7 @@ func (s *providerFakeRunService) SubmitProviderPool(_ context.Context, req edges
if selected := s.poolSelectedCandidate; selected.ProtocolProfile != nil {
disp.ProfileID = selected.ProfileID
disp.ProfileDriver = selected.ProfileDriver
disp.ProfileOperation = req.Tunnel.Operation
disp.ProfileCapabilities = append([]string(nil), selected.ProfileCapabilities...)
if selected.ProviderID != "" {
disp.ProviderID = selected.ProviderID

View file

@ -320,6 +320,12 @@ func (s *Server) handleResponsesProviderPool(w http.ResponseWriter, requestCtx *
}
estimate := requestCtx.estimate
contextClass := requestCtx.contextClass
requirements, err := decodeResponsesProviderRequirements(rawBody)
if err != nil {
requestCtx.finishUsageRequest(usageStatusError, responseModePassthrough)
writeError(w, http.StatusBadRequest, "invalid_request_error", err.Error())
return
}
env := requestCtx.envelope
runMeta := cloneMetadata(requestCtx.callerMetadata)
@ -367,6 +373,7 @@ func (s *Server) handleResponsesProviderPool(w http.ResponseWriter, requestCtx *
Run: baseRun,
Tunnel: baseTunnel,
}
poolReq.AcceptCandidate = responsesCandidatePredicate(requirements)
if s.streamGateSemanticEnabled() {
fctx, err := s.openAIResponsesOutputFilterContext(requestCtx)
@ -381,7 +388,7 @@ func (s *Server) handleResponsesProviderPool(w http.ResponseWriter, requestCtx *
writeError(w, http.StatusInternalServerError, "run_error", "stream gate runtime unavailable")
return
}
poolReq.AcceptCandidate = predicate
poolReq.AcceptCandidate = composeCandidatePredicates(poolReq.AcceptCandidate, predicate)
}
if requestCtx.route.Managed {
poolReq.AcceptCandidate = composeCandidatePredicates(poolReq.AcceptCandidate, requestCtx.route.CandidatePredicate())
@ -399,11 +406,29 @@ func (s *Server) handleResponsesProviderPool(w http.ResponseWriter, requestCtx *
tunnelReq.Headers = headers
return tunnelReq, nil
}
poolReq.PrepareProtocolTunnel = s.protocolTunnelPreparer(r, config.OperationResponses)
basePreparer := s.protocolTunnelPreparer(r, config.OperationResponses)
poolReq.PrepareProtocolTunnel = func(tunnelReq edgeservice.SubmitProviderTunnelRequest, selected edgeservice.ProviderPoolCandidate) (edgeservice.SubmitProviderTunnelRequest, error) {
prepared, err := basePreparer(tunnelReq, selected)
if err != nil {
return tunnelReq, err
}
if selected.ProtocolProfile == nil {
return prepared, nil
}
plan, err := selectProviderOperation(*selected.ProtocolProfile, config.OperationResponses, requirements)
if err != nil {
return tunnelReq, err
}
prepared.BuildBody = func(target string) ([]byte, error) {
return rewriteResponsesProviderControls(rawBody, target, plan)
}
return prepared, nil
}
// Tunnel branch: rewrite only the model field, preserve all other fields
// (tools, max_output_tokens, custom fields). Stream/background gating is
// skipped: the provider itself enforces those constraints (SDD S04).
// Tunnel branch rewrites the model and, for concrete profiles, the mapped
// reasoning effort. All other fields (tools, max_output_tokens, custom
// fields) are preserved. Stream/background gating is skipped: the provider
// itself enforces those constraints (SDD S04).
bodyBuilder := newOpenAIProviderBodyBuilder(func(target string) (*openAIRebuiltLease, error) {
return rewriteResponsesModelFromIngress(requestCtx.ingress, target)
})

View file

@ -126,6 +126,55 @@ func TestResponsesProtocolProfileOperationPassthroughNonStream(t *testing.T) {
}
}
func TestResponsesProtocolProfileEffortFallsBackToNearestLowerGrade(t *testing.T) {
profile, err := config.ResolveProtocolProfile("openai", "", config.BuiltInProtocolProfileCatalog())
if err != nil {
t.Fatalf("ResolveProtocolProfile: %v", err)
}
mapping := profile.Normalization.Effort[string(config.OperationResponses)]
delete(mapping.Levels, "max")
profile.Normalization.Effort[string(config.OperationResponses)] = mapping
fake := &providerFakeRunService{
tunnelFrames: staticProviderTunnelFrames(`{"id":"resp-effort","object":"response","output":[]}`),
poolSelectedCandidate: edgeservice.ProviderPoolCandidate{
ActualModel: "gpt-served",
ProviderID: "prov-openai-effort",
ExecutionPath: string(edgeservice.ProviderPoolPathTunnel),
ProfileID: profile.ID,
ProfileDriver: string(profile.Driver),
ProfileCapabilities: append([]string(nil), profile.Capabilities...),
ProtocolProfile: &profile,
},
}
srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
srv.SetModelCatalog([]config.ModelCatalogEntry{{ID: "responses-effort", Providers: map[string]string{"prov-openai-effort": "gpt-served"}}})
req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{
"model":"responses-effort",
"input":"use the tool",
"reasoning":{"effort":"max","summary":"auto"},
"tools":[{"type":"function","name":"lookup","parameters":{"type":"object"}}]
}`))
w := httptest.NewRecorder()
srv.handleResponses(w, req)
if w.Code != http.StatusOK {
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
}
var upstream map[string]any
if err := json.Unmarshal(fake.tunnelBodiesSnapshot()[0], &upstream); err != nil {
t.Fatal(err)
}
reasoning, _ := upstream["reasoning"].(map[string]any)
if reasoning["effort"] != "xhigh" || reasoning["summary"] != "auto" {
t.Fatalf("reasoning=%v, want effort fallback with preserved summary", reasoning)
}
if upstream["model"] != "gpt-served" {
t.Fatalf("model=%v, want served target", upstream["model"])
}
}
// TestResponsesProtocolProfileOperationPassthroughStream verifies that the
// responses profile operation admits stream=true passthrough and that the
// provider tunnel carries the stream flag to the upstream provider.

View file

@ -235,7 +235,7 @@ func TestOpenAIAttemptDispatcherStalledProvider(t *testing.T) {
t.Fatalf("dispatch recovery: %v", err)
}
defer binding.Controller().AbortAttempt(context.Background())
if service.lastPool.AvoidProviderID != "provider.stalled" || !service.lastPool.AllowAvoidedProviderFallback {
if service.lastPool.AvoidProviderID != "provider.stalled" || service.lastPool.AllowAvoidedProviderFallback {
t.Fatalf("recovery pool hints = %#v", service.lastPool)
}
}

View file

@ -759,7 +759,7 @@ func (f *openAIStallRecoveryFilter) Evaluate(_ context.Context, fctx streamgate.
var intent *streamgate.RecoveryIntent
if descriptor == "response_stalled_confirmed" {
unsafe := fctx.CommitState() != streamgate.CommitStateTransportUncommitted || fctx.HasToolSideEffect() || f.requestRef == "" || batchHasToolEvidence(batch)
if unsafe {
if unsafe || !f.state.claimRecovery() {
descriptor = "response_stalled_ineligible"
} else {
directive, err := streamgate.NewRecoveryDirectiveExact(f.requestRef)

View file

@ -45,6 +45,20 @@ type openAIStallRecoveryState struct {
providerID string
health string
confirmedForClose bool
recoveryClaimed bool
}
func (s *openAIStallRecoveryState) claimRecovery() bool {
if s == nil {
return false
}
s.mu.Lock()
defer s.mu.Unlock()
if s.recoveryClaimed {
return false
}
s.recoveryClaimed = true
return true
}
func (s *openAIStallRecoveryState) arm(attemptID, providerID, health string) {
@ -81,7 +95,10 @@ func (s *openAIStallRecoveryState) consumeAdmission() (providerID string, allowF
if !s.confirmedForClose || s.providerID == "" {
return "", false, false
}
providerID, allowFallback = s.providerID, s.health == "available"
// Health-probe availability proves only that the endpoint is reachable. It
// never makes the exact request that just stalled safe to replay on the same
// provider.
providerID, allowFallback = s.providerID, false
s.attemptID, s.providerID, s.health = "", "", ""
s.confirmedForClose = false
return providerID, allowFallback, true

View file

@ -106,7 +106,7 @@ func TestOpenAIStallRecoveryFilter(t *testing.T) {
t.Fatal("confirmed state was not armed")
}
provider, fallback, ok := state.consumeAdmission()
if !ok || provider != "provider-a" || fallback != (health == "available") {
if !ok || provider != "provider-a" || fallback {
t.Fatalf("admission hint = %q/%t/%t", provider, fallback, ok)
}
})
@ -140,6 +140,34 @@ func TestOpenAIStallRecoveryIneligibleAfterCommitOrTool(t *testing.T) {
}
}
func TestOpenAIStallRecoveryIsBoundedToOneReplay(t *testing.T) {
state := &openAIStallRecoveryState{}
filter, err := newOpenAIStallRecoveryFilter("openai.ingress.1", state)
if err != nil {
t.Fatal(err)
}
event, err := newOpenAIProviderErrorEventFromFailure(confirmedStallFailure("available"), streamGateErrorRunFailed)
if err != nil {
t.Fatal(err)
}
ctx := stallFilterContext(t, streamgate.CommitStateTransportUncommitted, false)
batch := stallBatch(t, event, streamgate.CommitStateTransportUncommitted)
first, err := filter.Evaluate(context.Background(), ctx, batch)
if err != nil {
t.Fatal(err)
}
second, err := filter.Evaluate(context.Background(), ctx, batch)
if err != nil {
t.Fatal(err)
}
if first.Kind() != streamgate.FilterDecisionKindViolation || first.RecoveryIntent() == nil {
t.Fatalf("first stall decision = %#v", first)
}
if second.Kind() != streamgate.FilterDecisionKindPass || second.RecoveryIntent() != nil {
t.Fatalf("second stall decision = %#v", second)
}
}
func stallMatrixSuccessAttempt(endpoint, path string, stream bool, runID, provider, marker string) scriptedPoolAttempt {
attempt := scriptedPoolAttempt{path: path, runID: runID, provider: provider, target: "served-" + provider}
if path == string(edgeservice.ProviderPoolPathNormalized) {
@ -304,7 +332,7 @@ func TestOpenAIStallAfterLogicalFinishMatrix(t *testing.T) {
t.Fatalf("raw failure data leaked: %q", body)
}
requests := stallPoolRequests(service)
if len(requests) != 2 || requests[1].AvoidProviderID != "provider-a" || !requests[1].AllowAvoidedProviderFallback {
if len(requests) != 2 || requests[1].AvoidProviderID != "provider-a" || requests[1].AllowAvoidedProviderFallback {
t.Fatalf("pre-commit replay requests=%+v", requests)
}
if strings.Count(body, recovered) != 1 {
@ -557,7 +585,7 @@ func TestOpenAIStallRecoveryMatrix(t *testing.T) {
})
}
t.Run("same-provider fallback requires available evidence", func(t *testing.T) {
t.Run("stall replay never grants same-provider fallback", func(t *testing.T) {
path := string(edgeservice.ProviderPoolPathNormalized)
service := newScriptedPoolRunService(
stallMatrixFailureAttempt(path, "available-a", "provider-a", "available"),
@ -565,7 +593,7 @@ func TestOpenAIStallRecoveryMatrix(t *testing.T) {
)
w := runStallMatrixHandler(t, stallMatrixServer(service, false, 1), openAIRebuildEndpointChat, false, nil)
requests := stallPoolRequests(service)
if w.Code != http.StatusOK || len(requests) != 2 || requests[1].AvoidProviderID != "provider-a" || !requests[1].AllowAvoidedProviderFallback {
if w.Code != http.StatusOK || len(requests) != 2 || requests[1].AvoidProviderID != "provider-a" || requests[1].AllowAvoidedProviderFallback {
t.Fatalf("available fallback response=%d/%q requests=%+v", w.Code, w.Body.String(), requests)
}
})

View file

@ -95,9 +95,9 @@ func (e *ProviderPoolOperationUnsupportedError) Unwrap() error {
// alternate provider over the avoided one. The avoided provider is only
// retained when no alternate exists AND AllowAvoidedProviderFallback is
// true AND the provider is still runtime eligible — the explicit fallback
// permission is the only way to re-select the avoided provider, and it is
// always derived from exact probe-backed available evidence by the caller
// (never from current overlay state).
// permission is the only way to re-select the avoided provider. Liveness
// recovery never grants that permission: a health probe proves endpoint
// availability, not safety of replaying the request that just stalled.
//
// Zero values (empty AvoidProviderID, false AllowAvoidedProviderFallback)
// preserve the current candidate selection behavior.
@ -392,6 +392,7 @@ func (s *Service) dispatchProviderPoolTunnel(
disp.ExecutionPath = string(selected.executionPath)
disp.QueueReason = queueReason
disp.ProfileID, disp.ProfileDriver = profileFacts(selected.profile)
disp.ProfileOperation = tunnelReq.Operation
if selected.profile != nil {
disp.ProfileCapabilities = append([]string(nil), selected.profile.Capabilities...)
}

View file

@ -63,6 +63,7 @@ type RunDispatch struct {
ExecutionPath string // non-empty for provider-pool dispatches
ProfileID string
ProfileDriver string
ProfileOperation string
ProfileCapabilities []string
CredentialSlotRef string
CredentialRevision uint64

View file

@ -166,8 +166,8 @@ openai:
stream_evidence_gate:
enabled: false
environment: dev # dev | dev-corp; request-start selector snapshot
max_request_fault_recovery: 3
max_strategy_fault_recovery: 3
max_request_fault_recovery: 0
max_strategy_fault_recovery: 0
max_ingress_snapshot_bytes: 16777216
# filters are disabled by omission. Each policy has one unique filter kind:
# repeat_guard (request-local history plus Unicode rolling/current-stream
@ -417,7 +417,7 @@ nodes:
health: "healthy"
capacity: 1
priority: 50
# response_stall_timeout_ms: 300000 # omitted → uses documented default
# response_stall_timeout_ms: 60000 # omitted → uses documented default
# Seulgivibe OpenAI-compatible provider examples. Keep endpoint values
# illustrative and provide user tokens per request via openai.provider_auth.
# - id: "seulgivibe-claude"

View file

@ -1,583 +0,0 @@
# Agent Comparison Benchmark Dev Guide
이 문서는 IOP one-shot agent/model comparison benchmark의 현재 구현, dev 환경 구성, caller 연결 방식, managed credential 경계, 실행 절차와 장애 대응 기준을 한곳에 모은 운영 가이드다.
raw token, provider API key, private key, slot alias, lease id와 개인 endpoint는 이 문서에 기록하지 않는다. 실제 host, checkout, Node/provider endpoint와 최신 process 상태는 아래 source of truth에서 확인한다.
- benchmark manifest: `scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json`
- public CLI: `scripts/agent_comparison_benchmark.py`
- dev environment: `agent-test/dev/rules.md`
- machine-readable dev inventory: `agent-test/inventory-dev.yaml`
- Edge/Node verification: `agent-test/dev/edge-smoke.md`, `agent-test/dev/node-smoke.md`
- API contracts: `agent-contract/outer/anthropic-compatible-api.md`, `agent-contract/outer/openai-compatible-api.md`, `agent-contract/outer/gemini-compatible-api.md`
- benchmark SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-one-shot-agent-model-comparison/SDD.md`
이 문서는 위 계약과 manifest를 설명할 뿐 덮어쓰지 않는다. 값이 다르면 manifest, contract, inventory, environment rule 순서로 최신 상태를 확인한다.
## 1. 현재 상태
2026-08-12 기준 상태는 다음과 같다.
| 영역 | 상태 |
|---|---|
| Gemini-native Edge ingress | 구현 완료. route-qualified `streamGenerateContent`, `x-goog-api-key` IOP principal auth, request/tool/SSE 변환을 지원한다. |
| official agy adapter | 구현 완료. `agy 1.1.12`, Gemini API-key provider, real `init/step_update/result` JSONL을 사용한다. |
| managed dev credential runtime | 구성 완료. Control Plane projection, credential slot/route, sealed lease, Edge HTTPS, CP↔Edge/Edge↔Node mTLS를 사용한다. |
| caller readiness | 최근 완료된 readiness evidence에서 C01-C09 `ready=9`를 확인했다. 실제 실행일에는 fresh preflight가 다시 필요하다. |
| controller recovery | control socket symlink와 caller launch 전 interruption 회귀 수정 및 테스트가 완료됐다. |
| Codex production JSONL | `cache_write_input_tokens` 수용과 config-owned effective binding 정합화가 완료됐다. |
| deterministic tests | focused 41 tests, 전체 benchmark 429 tests가 통과했다. 숫자는 당시 snapshot이며 현재 suite 결과는 fresh 실행으로 판단한다. |
| C01-C09 scored execution | 아직 완료되지 않았다. 과거 incomplete/failed run은 append-only evidence로 보존하며 정상 결과로 간주하지 않는다. |
| blind scoring/report | 유효한 9-cell execution이 생긴 뒤 수행하는 후속 단계다. |
과거 실행 실패는 새 run을 정당화하는 완료 evidence가 아니다. 이전 run tree를 수정하거나 old run을 암묵적으로 `resume`/retry하지 않는다. 새 scored execution은 그 실행을 명시적으로 소유하는 현재 plan과 사용자 권한이 있을 때만 한 번 수행한다. 이 문서 자체는 지속적 실행 승인을 부여하지 않는다.
## 2. 시스템 구성
```text
Benchmark runner
├─ Claude Code ── Anthropic Messages ───────────────┐
├─ agy ───────── Gemini streamGenerateContent ─────┤
└─ Codex ─────── OpenAI Responses ─────────────────┤
v
IOP Edge HTTPS
┌───────────────────────────┴───────────────────────────┐
│ │
direct model route execution preset
│ selector/plan/work/review/repair
└───────────────────────────┬───────────────────────────┘
v
managed credential lease
Control Plane projection + Node sealed lease
v
Node-owned provider
```
핵심 보안 경계는 다음과 같다.
- benchmark caller는 하나의 IOP principal token으로 Edge만 인증한다.
- caller token은 upstream provider credential이 아니다.
- provider credential은 Control Plane에 등록된 slot과 Node 대상 sealed lease에서만 온다.
- Edge는 provider credential을 복호화하지 않고 caller가 보낸 token을 upstream auth로 재사용하지 않는다.
- managed mode는 legacy static principal/provider credential과 혼용하지 않는다.
- config observation은 secret이 아닌 route/model/stage binding의 독립 증거다.
## 3. 환경 프로필
### 3.1 Benchmark runner
현재 검증된 runner class는 Linux/AArch64다. 명령은 repository root에서 실행한다.
필수 command:
```bash
command -v python3
command -v git
command -v claude
command -v agy
command -v codex
```
2026-08-12 확인 snapshot:
| Tool | 확인된 버전 | 정책 |
|---|---|---|
| Claude Code | `2.1.228` | 고정 버전으로 추정하지 않고 매 execution preflight에서 `--version`/`--help`를 확인한다. |
| agy | `1.1.12` | adapter가 이 버전을 명시적으로 gate한다. 다른 버전은 재검증 전 fail closed한다. |
| Codex CLI | `0.147.0` | 고정 버전으로 추정하지 않고 매 execution preflight에서 `--version``exec --help`를 확인한다. |
### 3.2 Testbed
- path: `../iop-s2`
- 현재 확인 branch: `dev`
- 현재 확인 상태: clean
- benchmark는 testbed를 read-only provenance로 취급한다.
- caller별 workspace와 session은 run tree 아래에 새로 만들며 서로 공유하지 않는다.
- testbed를 benchmark 결과로 수정하거나 결과 파일을 다시 복사하지 않는다.
fixture checksum과 source file 목록은 manifest가 고정한다. testbed HEAD와 clean 상태는 실행일에 다시 확인한다.
### 3.3 Dev runtime
현재 검증된 runtime class는 macOS/ARM64 remote dev runner다. exact SSH target과 checkout은 `agent-test/dev/rules.md``agent-test/inventory-dev.yaml`을 따른다.
benchmark 관련 runtime 역할:
| Port | 역할 |
|---:|---|
| `18082` | Edge artifact/bootstrap HTTP |
| `18083` | managed Edge public HTTPS; Anthropic/OpenAI/Gemini caller ingress |
| `18084` | native dev-runtime Edge↔Node TCP |
| `19093` | Edge admin/config refresh |
| `19101` | Edge metrics |
2026-08-12 read-only 확인에서 위 listener와 managed Edge process는 모두 active였고 remote checkout은 clean release 상태였다. exact commit, binary checksum, process id와 endpoint는 실행 evidence에만 기록하고 이 가이드에 고정하지 않는다.
최근 완료된 live readiness evidence는 4 connected Nodes와 8 healthy/available providers를 확인했다. 최신 Node/provider 세부와 접속 위치는 반드시 `agent-test/inventory-dev.yaml`에서 다시 확인한다.
이 benchmark는 compose dev stack의 Edge-Node TCP `19003`이 아니라 native dev-runtime provider pool의 `18084`를 사용한다. compose와 native profile은 포트, process와 판정 evidence가 서로 다르므로 한 실행에서 섞지 않는다. 현재 inventory가 가리키는 배포 산출물은 다음과 같다.
| Artifact | Path |
|---|---|
| native Edge config | `build/dev-runtime/edge.yaml` |
| Edge binary | `build/dev-runtime/bin/edge` |
| macOS Node binary | `build/dev-runtime/bin/iop-node` |
| Linux ARM64 Node binary | `build/dev-runtime/bin/iop-node-linux-arm64` |
| Windows AMD64 Node binary | `build/dev-runtime/bin/iop-node-windows-amd64.exe` |
모든 Edge/Node binary는 scored execution 전에 동일 source ref로 rebuild·redeploy·restart한다. `build/dev-runtime/**`의 runtime config와 untracked credential material은 원격 runner가 소유하며 tracked 문서나 testbed로 복사하지 않는다.
## 4. 보호 파일과 credential 역할
benchmark runner의 `token/` 아래에는 다음 파일이 준비돼 있다. 파일 존재와 mode만 확인하며 내용을 출력하지 않는다.
| Path | 역할 | 실행 시 사용 |
|---|---|---|
| `token/.iop-bench` | benchmark용 IOP principal token | preflight/run/score caller가 Edge를 인증할 때 사용 |
| `token/iop-dev-ca.pem` | managed dev Edge HTTPS CA certificate | `SSL_CERT_FILE`, `NODE_EXTRA_CA_CERTS`로 전달 |
| `token/.claude` | Claude provider credential의 초기 provisioning source | provider slot 등록 시에만 사용; benchmark caller에 전달하지 않음 |
| `token/.gemini` | Gemini provider credential의 초기 provisioning source | provider slot 등록 시에만 사용; benchmark caller에 전달하지 않음 |
| `token/.gpt` | GPT provider credential의 초기 provisioning source | provider slot 등록 시에만 사용; benchmark caller에 전달하지 않음 |
현재 secret source 파일은 `0600`, CA certificate는 `0644`로 확인됐다. CA certificate는 public trust material이지만 private key는 아니다.
안전 확인:
```bash
for benchmark_secret_file in token/.iop-bench token/.claude token/.gemini token/.gpt; do
test -f "$benchmark_secret_file"
test "$(stat -c '%a' "$benchmark_secret_file")" = 600
done
test -f token/iop-dev-ca.pem
```
macOS에서 동일 검사를 수행할 때는 BSD `stat` 문법을 사용한다. 어떤 경우에도 `cat`, `echo`, shell tracing(`set -x`)으로 secret 내용을 출력하지 않는다.
## 5. Provider와 managed credential 설정
현재 dev 구성은 다음 절차로 만들어졌다.
1. Control Plane credential plane용 CA, workload certificate, at-rest keyring과 lease issuer/recipient key를 operator-owned untracked 경로에 생성했다.
2. Control Plane, Edge와 각 Node에 role/name-bound mTLS identity를 배치했다.
3. Edge public ingress를 HTTPS로 구성하고 benchmark runner에 CA certificate만 전달했다.
4. benchmark principal을 bootstrap하고 one-time token을 `token/.iop-bench`에 저장했다.
5. `token/.claude`, `token/.gemini`, `token/.gpt`의 raw provider key를 credential HTTPS request body로 직접 등록했다. command argument, YAML, tracked docs나 task evidence에는 넣지 않았다.
6. credential slot과 public route를 별도로 생성하고 principal projection에 direct route와 hybrid preset stage authorization을 연결했다.
7. Control Plane → Edge → Nodes 순서로 bounded restart하고 fresh projection, sealed lease, route revision과 no-fallback 동작을 검증했다.
재구성이 필요하면 `docs/edge-local-dev-guide.md`의 “Managed credential plane and TLS startup”과 “Safe slot lifecycle”을 따른다. 실제 slot id, alias, revision과 lease id는 운영 상태이므로 이 문서에 복사하지 않는다.
## 6. Caller별 연결 방식
| Caller | Edge surface | Child 설정 | 중요한 제한 |
|---|---|---|---|
| Claude Code | Anthropic-compatible Messages | `ANTHROPIC_BASE_URL`, `ANTHROPIC_API_KEY`; `--bare --print --verbose --output-format stream-json --no-session-persistence --permission-mode dontAsk --tools Read,Write,Edit --allowedTools Read,Write,Edit` | task는 stdin으로 한 번 제출한다. network/shell 도구 없이 격리 workspace 파일 작업만 허용한다. model/effort는 manifest 값을 그대로 전달한다. |
| agy | Gemini-native `streamGenerateContent` | fresh session `HOME`, `GOOGLE_GEMINI_BASE_URL`, `GEMINI_API_KEY`; `--sandbox --output-format stream-json --model ... --print ...` | `agy 1.1.12`만 승인된다. API-key provider에 `--effort`를 전달하지 않으며 ambient user config를 읽지 않는다. |
| Codex | OpenAI-compatible Responses | fresh session `HOME`, isolated `iop_benchmark` provider override, `IOP_BENCHMARK_API_KEY`; `exec --sandbox workspace-write --json --ephemeral --ignore-user-config --strict-config` | user config를 읽지 않고 격리 workspace에만 쓸 수 있다. current adapter effort는 `xhigh`; caller binding event가 없으면 admitted config binding을 사용하고, 보고된 mismatch는 거부한다. |
세 caller child에는 필요한 `PATH`와 CA 변수만 allowlist로 전달한다. parent의 caller/provider 설정이나 unrelated secret은 상속하지 않는다.
agy의 public base는 adapter가 cell별로 다음처럼 route-qualified 한다.
```text
<edge-origin>/gemini/<route-id>
```
`GEMINI_BASE_URL`, `AGY_PROVIDER`, `AGY_OPENAI_BASE_URL`, `AGY_OPENAI_API_KEY`는 이 benchmark transport가 아니다.
official agy planner가 Gemini `generationConfig.responseMimeType``responseSchema` 또는 `responseJsonSchema`를 보내면 Edge는 이를 기존 Chat `response_format`으로 변환한다. function declaration은 official SDK가 사용하는 `parameters``parametersJsonSchema` 표기 중 정확히 하나를 허용한다. 동의어 필드가 동시에 있거나 schema가 JSON object가 아니면 provider dispatch 전에 거부한다. 구조가 유효한 agy `result.status=ERROR`는 stream parser 오류로 바꾸지 않고 caller process 실패로 기록한다.
## 7. Benchmark 고정 설정
| 설정 | 값 |
|---|---|
| pipeline version | `2` |
| environment | `dev` |
| execution seed | `bench-02-c01-c09-v1` |
| repetitions | `1` |
| session policy | `fresh` |
| setup cache policy | `isolated` |
| run timeout | 300 seconds |
| idle timeout | 30 seconds |
| quiet window | 10 seconds |
| cleanup grace | 5 seconds |
| desktop viewport | `1920x1080` |
| mobile viewport | `375x812` |
| output root | `agent-test/runs/bench-02` |
| rubric | `one-shot-agent-comparison-v1` |
execution seed로 결정되는 현재 slot 순서는 다음과 같다. 표의 C 번호 순서와 실제 실행 순서는 다르다.
1. C02 Claude→Gemini direct
2. C05 Codex→GPT direct
3. C03 agy→Gemini direct
4. C06 Claude→Gemini hybrid
5. C08 Claude→GPT hybrid
6. C09 Codex→GPT hybrid
7. C01 Claude→Sonnet direct
8. C07 agy→Gemini hybrid
9. C04 Claude→GPT direct
## 8. C01-C09 matrix와 route binding
| Cell | Caller | Route kind/id | Requested model/effort | Effective stage binding |
|---|---|---|---|---|
| C01 | Claude | direct / `claude-sonnet-5` | `claude-sonnet-5` / `max` | request=`claude-sonnet-5` max |
| C02 | Claude | direct / `gemini-3.6-flash` | `gemini-3.6-flash` / `high` | request=`gemini-3.6-flash` high |
| C03 | agy | direct / `gemini-3.6-flash` | `gemini-3.6-flash` / `high` | request=`gemini-3.6-flash` high |
| C04 | Claude | direct / `gpt-5.6-luna` | `gpt-5.6-luna` / `xhigh` | request=`gpt-5.6-luna` xhigh |
| C05 | Codex | direct / `gpt-5.6-luna` | `gpt-5.6-luna` / `xhigh` | request=`gpt-5.6-luna` xhigh |
| C06 | Claude | preset / `gemini-hybrid` | `gemini-hybrid` / `high` | selector/plan/review/repair=`gemini-3.6-flash` high; work=`ornith-fast` |
| C07 | agy | preset / `gemini-hybrid` | `gemini-hybrid` / `high` | selector/plan/review/repair=`gemini-3.6-flash` high; work=`ornith-fast` |
| C08 | Claude | preset / `gpt-hybrid` | `gpt-hybrid` / `xhigh` | selector/plan/review/repair=`gpt-5.6-terra` high; work=`ornith-fast` |
| C09 | Codex | preset / `gpt-hybrid` | `gpt-hybrid` / `xhigh` | selector/plan/review/repair=`gpt-5.6-terra` high; work=`ornith-fast` |
hybrid preset은 caller가 stage를 따로 호출하는 구조가 아니다. 하나의 caller request 안에서 Edge가 selector/plan/work/review/repair를 소유한다.
## 9. Fixture와 결과 조건
공통 task는 fictional product “Lumen Atlas”의 responsive one-page landing page다.
- prompt: `scripts/fixtures/agent-comparison-benchmark/prompt.md`
- copy: `scripts/fixtures/agent-comparison-benchmark/reference.txt`
- images: `aurora-grid.svg`, `orbit-rings.svg`
- 생성 파일: workspace root의 `index.html`, `styles.css`, `script.js` 정확히 세 개
- 외부 asset, framework, package manager, build tool, analytics와 network dependency 금지
- desktop/mobile responsive, semantic HTML, focus/contrast/accessibility 요구
- 각 attempt는 fresh workspace/session에서 task를 한 번만 제출
fixture checksum은 manifest의 값이 유일한 기준이다. prompt나 asset을 변경하면 기존 run과 비교하지 말고 manifest/version/checksum을 함께 갱신하는 별도 작업으로 처리한다.
## 10. Process environment 준비
다음은 value를 출력하지 않는 process-local 예시다. `<edge-host>`를 문서에 실제 값으로 치환하지 말고 실행 환경에서만 주입한다.
Public live registry가 소비하는 environment contract는 다음과 같다.
| Variable | 값/의미 | Durable evidence |
|---|---|---|
| `IOP_BENCH_CLAUDE_BASE_URL` | managed Edge HTTPS origin | raw 값 금지; endpoint digest만 허용 |
| `IOP_BENCH_AGY_BASE_URL` | managed Edge HTTPS origin; adapter가 `/gemini/<route-id>`를 추가 | raw 값 금지; endpoint digest만 허용 |
| `IOP_BENCH_CODEX_BASE_URL` | managed Edge OpenAI-compatible `/v1` base | raw 값 금지; endpoint digest만 허용 |
| `IOP_BENCH_CLAUDE_SECRET_ENV` | Claude가 사용할 secret-bearing variable 이름 | variable 이름만 허용 |
| `IOP_BENCH_AGY_SECRET_ENV` | agy가 사용할 secret-bearing variable 이름 | variable 이름만 허용 |
| `IOP_BENCH_CODEX_SECRET_ENV` | Codex가 사용할 secret-bearing variable 이름 | variable 이름만 허용 |
| `IOP_BENCH_SHARED_TOKEN` | 이 가이드 예시의 secret-bearing variable | 값 기록 금지 |
| `SSL_CERT_FILE` | dev Edge HTTPS CA certificate path | repository-relative file reference만 허용 |
| `NODE_EXTRA_CA_CERTS` | Node.js caller용 동일 CA certificate path | repository-relative file reference만 허용 |
| `IOP_BENCH_CONFIG_OBSERVATION_ENV` | config JSON을 보유한 variable 이름 | variable 이름만 허용 |
| `BENCH_CONFIG` | schema v1 route/model/stage observation JSON | secret은 없지만 runtime과 일치하는 canonical digest만 evidence에 기록 |
| `PATH` | caller binary resolution | resolved executable path/version만 preflight에서 확인 |
Child adapter가 내부적으로 만드는 값은 caller별로 격리된다.
- Claude: `ANTHROPIC_BASE_URL`, `ANTHROPIC_API_KEY`, restricted `Read,Write,Edit`, nonessential traffic/autoupdater disable flags와 CA variables
- agy: fresh session `HOME`, `GOOGLE_GEMINI_BASE_URL`, `GEMINI_API_KEY`, `LANG=C.UTF-8`, `LC_ALL=C.UTF-8`, `TZ=UTC`와 CA variables
- Codex: fresh session `HOME`, `IOP_BENCHMARK_API_KEY`, strict ephemeral provider override와 CA variables
이 child variable은 사용자가 별도로 준비할 값이 아니다. live registry가 위 public contract에서 파생하며, parent의 같은 이름 값을 그대로 신뢰하거나 상속하지 않는다.
```bash
set -euo pipefail
benchmark_edge_origin="${BENCHMARK_EDGE_ORIGIN:?set BENCHMARK_EDGE_ORIGIN to the managed dev Edge HTTPS origin}"
read -r IOP_BENCH_SHARED_TOKEN < token/.iop-bench
export IOP_BENCH_SHARED_TOKEN
export IOP_BENCH_CLAUDE_BASE_URL="$benchmark_edge_origin"
export IOP_BENCH_AGY_BASE_URL="$benchmark_edge_origin"
export IOP_BENCH_CODEX_BASE_URL="$benchmark_edge_origin/v1"
export IOP_BENCH_CLAUDE_SECRET_ENV=IOP_BENCH_SHARED_TOKEN
export IOP_BENCH_AGY_SECRET_ENV=IOP_BENCH_SHARED_TOKEN
export IOP_BENCH_CODEX_SECRET_ENV=IOP_BENCH_SHARED_TOKEN
export SSL_CERT_FILE="$PWD/token/iop-dev-ca.pem"
export NODE_EXTRA_CA_CERTS="$PWD/token/iop-dev-ca.pem"
export IOP_BENCH_CONFIG_OBSERVATION_ENV=BENCH_CONFIG
BENCH_CONFIG="$(python3 - <<'PY'
import json
routes = [
{"route_kind":"direct","route_id":"claude-sonnet-5","model":"claude-sonnet-5","bindings":[{"stage":"request","model":"claude-sonnet-5","effort":"max"}]},
{"route_kind":"direct","route_id":"gemini-3.6-flash","model":"gemini-3.6-flash","bindings":[{"stage":"request","model":"gemini-3.6-flash","effort":"high"}]},
{"route_kind":"direct","route_id":"gpt-5.6-luna","model":"gpt-5.6-luna","bindings":[{"stage":"request","model":"gpt-5.6-luna","effort":"xhigh"}]},
{"route_kind":"execution_preset","route_id":"gemini-hybrid","model":"gemini-hybrid","bindings":[{"stage":"selector","model":"gemini-3.6-flash","effort":"high"},{"stage":"plan","model":"gemini-3.6-flash","effort":"high"},{"stage":"work","model":"ornith-fast","effort":None},{"stage":"review","model":"gemini-3.6-flash","effort":"high"},{"stage":"repair","model":"gemini-3.6-flash","effort":"high"}]},
{"route_kind":"execution_preset","route_id":"gpt-hybrid","model":"gpt-hybrid","bindings":[{"stage":"selector","model":"gpt-5.6-terra","effort":"high"},{"stage":"plan","model":"gpt-5.6-terra","effort":"high"},{"stage":"work","model":"ornith-fast","effort":None},{"stage":"review","model":"gpt-5.6-terra","effort":"high"},{"stage":"repair","model":"gpt-5.6-terra","effort":"high"}]},
]
print(json.dumps({"schema_version":"1","routes":routes}, separators=(",",":")))
PY
)"
export BENCH_CONFIG
```
중요한 의미:
- `IOP_BENCH_*_SECRET_ENV`의 값은 secret이 아니라 실제 secret을 보유한 environment variable의 이름이다.
- 세 caller는 같은 IOP principal을 사용하지만 서로 다른 protocol base를 받는다.
- `BENCH_CONFIG`는 runtime에서 독립적으로 확인한 route/stage snapshot이어야 한다. manifest를 보고 임의 합성한 값을 live readiness evidence로 사용하면 안 된다.
- endpoint와 config는 evidence에 raw 값 대신 digest identity로만 남는다.
작업 후에는 같은 shell에서 다음 변수를 제거한다.
```bash
unset IOP_BENCH_SHARED_TOKEN
unset IOP_BENCH_CLAUDE_BASE_URL IOP_BENCH_AGY_BASE_URL IOP_BENCH_CODEX_BASE_URL
unset IOP_BENCH_CLAUDE_SECRET_ENV IOP_BENCH_AGY_SECRET_ENV IOP_BENCH_CODEX_SECRET_ENV
unset IOP_BENCH_CONFIG_OBSERVATION_ENV BENCH_CONFIG
unset SSL_CERT_FILE NODE_EXTRA_CA_CERTS
unset BENCHMARK_EDGE_ORIGIN benchmark_edge_origin
```
## 11. 실행 절차
manifest path는 모든 명령에서 동일하게 사용한다.
```bash
benchmark_manifest=scripts/fixtures/agent-comparison-benchmark-iop-one-shot.json
```
### 11.1 Source와 deterministic verification
```bash
python3 -m unittest scripts.agent_benchmark.codex_iop_test scripts.agent_benchmark.connectivity_integration_test
python3 -m unittest discover -s scripts/agent_benchmark -p '*_test.py'
python3 scripts/agent_comparison_benchmark.py validate --manifest "$benchmark_manifest"
git diff --check
```
필요하면 변경 범위에 따라 Go tests와 managed credential qualification도 실행한다.
```bash
go test -count=1 ./...
credential_smoke_parent="$(mktemp -d /tmp/iop-benchmark-credential.XXXXXX)"
TMPDIR="$credential_smoke_parent" make test-credential-slot-smoke
rmdir "$credential_smoke_parent"
```
### 11.2 Direct-first qualification
동일 clean source ref를 모든 runtime binary에 배포하고 4/4 Node와 provider health를 확인한 다음, 먼저 기존 5-cell direct manifest를 사용한다.
```bash
direct_manifest="scripts/fixtures/agent-comparison-benchmark-direct-preflight.example.json"
python3 scripts/agent_comparison_benchmark.py preflight --manifest "$direct_manifest"
python3 scripts/agent_comparison_benchmark.py run --manifest "$direct_manifest"
```
direct canary는 unscored 진단이다. fresh preflight가 `ready=5`여야 하고, 정확히 5개의 fresh attempt를 한 번씩 할당하며, `unresolved=0`, `running=0`, `interrupted=0`과 함께 각 slot의 controller/product/harness/process/web-validation terminal evidence가 있어야 한다. 최대 3회 Chromium/CDP 재시도 뒤에도 `browser_*` 또는 `cdp_*` infrastructure block이 남거나 Edge pre-ingress incompatibility가 확인되면 qualification을 중단한다.
`product=failed`, upstream HTTP rejection, caller failure 뒤 `generated_missing`, timeout은 terminal evidence가 완결되면 측정 결과다. 이를 성공으로 바꾸거나 암묵 재시도하지 않으며, 그 자체로 다음 구현 cycle을 만들지 않는다. scoring eligibility는 각 독립 gate로 별도 판정한다.
### 11.3 Public nine-cell preflight
10절의 환경을 같은 shell에 준비한 뒤 실행한다.
```bash
python3 scripts/agent_comparison_benchmark.py preflight --manifest "$benchmark_manifest"
```
성공 조건:
```text
status=ready ready=9 registration_required=0 implementation_gap=0
```
preflight는 다음을 함께 확인한다.
- caller binary/version/help
- Edge model catalog
- principal auth와 endpoint compatibility
- direct/preset route 존재
- requested model/effort
- exact stage binding과 order
- official agy transport capability
`registration_required` 또는 `implementation_gap`이면 즉시 중단한다. alias, model, effort, route나 caller를 대체하지 않는다. preflight-only run root는 evidence이므로 삭제하지 않는다.
terminal-evidence direct canary admission 뒤 fresh one-shot preflight가 `ready=9`인지 확인하고 멈춘다. 이 qualification 단계에서는 hybrid canary나 C01-C09 `run`을 호출하지 않는다.
### 11.4 Scored execution
fresh preflight와 명시적 실행 권한이 있는 현재 plan에서만 다음 명령을 한 번 호출한다.
```bash
python3 scripts/agent_comparison_benchmark.py run --manifest "$benchmark_manifest"
```
- direct CLI stdout/stderr와 exit code를 보존한다.
- CLI가 출력한 canonical `run-...` id만 이후 `status`, `score`, `report`에 사용한다.
- command가 nonzero여도 같은 plan에서 `run`을 다시 호출하지 않는다.
- CLI가 run id를 출력하지 않으면 임의 id나 성공 pointer를 만들지 않는다.
- caller나 provider를 CLI 밖에서 별도로 호출해 scored result를 보충하지 않는다.
### 11.5 Status
```bash
python3 scripts/agent_comparison_benchmark.py status \
--manifest "$benchmark_manifest" \
--run-id "$benchmark_run_id"
```
현재 comparison execution 완료 조건:
- controller terminal 수 `completed + timed_out + cancelled + interrupted = 9`
- `running = 0`
- `interrupted = 0`
- 각 cell/repetition에 retained terminal attempt가 존재
- 모든 최신 attempt에 controller/product/harness/process/web-validation terminal evidence가 있고 `unresolved=0`
`completed`는 controller 종료만 뜻하며 product 성공을 뜻하지 않는다. product/harness/process/artifact 실패는 scoring eligibility와 최종 비교에서 각각 별도로 표시되고, terminal 실패를 성공 결과로 바꾸거나 암묵 재시도하지 않는다.
### 11.6 Blind scoring
유효한 execution run에 대해서만 수행한다.
```bash
python3 scripts/agent_comparison_benchmark.py score \
--manifest "$benchmark_manifest" \
--run-id "$benchmark_run_id"
```
evaluator는 Codex→`gpt-5.6-luna` xhigh direct route다. identity가 제거된 blind workspace만 보며 source cell identity mapping은 blind tree 밖에 유지한다.
product, harness, acceptable process 또는 artifact gate 실패는 각각의 reason을 가진 `unscored`이고 0점으로 바꾸지 않는다. `scoring_failed`도 명시적 `--retry-scoring-failed` 권한 없이 재시도하지 않는다.
### 11.7 Report
```bash
python3 scripts/agent_comparison_benchmark.py report \
--manifest "$benchmark_manifest" \
--run-id "$benchmark_run_id"
```
report는 run root의 immutable evidence를 읽어 idempotent `report.md`를 만든다. 기존 report 내용과 새 projection이 다르면 덮어쓰지 않고 실패한다.
## 12. 수집 evidence
각 attempt는 다음 범주의 evidence를 가진다.
| 범주 | 내용 |
|---|---|
| lifecycle | typed caller terminal, submission, first output, finish, idle, quiet와 독립 product/harness/process 결과 |
| timeline | submitted, first output, first workspace write observation/mtime, total duration |
| usage | input/output/reasoning/cache read/cache write/total tokens, model/tool calls와 duration |
| workspace | fresh session identity, fixture checksum, testbed provenance, generated file tree |
| web validation | product/harness 결과와 무관하게 모든 terminal workspace에서 생성되는 generated files, static safety, images, network, console, responsive, accessibility |
| screenshots | desktop `1920x1080`, mobile `375x812` |
| scoring | eligibility, blind allocation, rubric worksheet, score status |
caller가 보고하지 않은 metric은 0으로 만들지 않고 `unavailable`과 reason/source를 보존한다.
Codex current usage mapping:
| Caller JSONL field | Canonical metric |
|---|---|
| `input_tokens` | `input_tokens` |
| `cached_input_tokens` | `cached_input_tokens` |
| `cache_write_input_tokens` | `cache_write_tokens` |
| `output_tokens` | `output_tokens` |
| `reasoning_output_tokens` | `reasoning_tokens` |
| `total_tokens` | `total_tokens` |
누락된 `total_tokens`는 하위 category 합으로 재구성하지 않는다.
## 13. 자동 gate와 100점 rubric
automatic web gates는 scoring eligibility만 결정하고 점수에 포함되지 않는다.
| Gate |
|---|
| generated files |
| static safety |
| local images |
| no external network dependency |
| console safety |
| responsive layout |
| accessibility |
quality rubric:
| Category | Max |
|---|---:|
| requirements fidelity | 25 |
| visual completeness | 25 |
| responsive accessibility | 15 |
| image/detail usage | 10 |
| behavior stability | 10 |
| code quality | 10 |
| self verification | 5 |
| Total | 100 |
## 14. 실패 처리와 재개 원칙
| 상황 | 조치 |
|---|---|
| manifest invalid | source/manifest를 수정하고 validate부터 다시 시작한다. run을 만들지 않는다. |
| preflight not ready | blocker를 해결하고 fresh preflight한다. attempt를 할당하지 않는다. |
| caller launch 전 interruption | retained evidence를 보존한다. run tree를 직접 수정하지 않는다. |
| lifecycle/parser failure | exact retained output으로 source 원인을 수정하고 deterministic regression을 추가한다. |
| completed이지만 product/harness/process/artifact gate 실패 또는 timed_out/cancelled | 각 축 evidence로 보존한다. 암묵 retry하지 않는다. |
| state가 `running`이지만 process가 없음 | manual JSON 수정/삭제/reconcile을 하지 않는다. reviewer evidence로 남기고 승인된 새 plan에서만 다음 상태를 결정한다. |
| scoring_failed | 0점 처리하지 않는다. 명시적 retry 권한 없이는 중단한다. |
| report unavailable | run evidence를 수정하거나 report를 수작업 생성하지 않는다. |
현재 milestone의 원칙은 old incomplete/failed run을 `resume --retry-failed`하지 않고 distinct fresh run을 만드는 것이다. 일반 CLI가 `resume`을 지원한다는 사실이 현재 benchmark에서 사용 권한을 뜻하지 않는다.
## 15. Secret-safe 기록 규칙
다음 값은 tracked docs, task review, run metadata, log, metric label과 command argument에 남기지 않는다.
- IOP principal token
- raw provider credential
- private key, at-rest key, issuer/recipient private key
- slot alias와 lease id
- credential-bearing URL
- raw prompt/response, tool input/output
- caller/provider session content
허용되는 내용:
- secret file의 상대 path와 존재/mode
- route/model/stage 이름
- safe credential slot reference/revision
- hashed endpoint/config/spec identity
- redacted lifecycle 상태, duration과 usage count
- run id와 attempt identity가 필요한 controller evidence
incident evidence를 보존하기 전에 repository와 run output에서 secret 원문이 없는지 확인한다. 의심되는 artifact는 내용을 복사하지 말고 path와 redaction failure만 보고한다.
## 16. 실행 전 체크리스트
- [ ] 현재 manifest validation 통과
- [ ] focused/full deterministic tests fresh PASS
- [ ] benchmark runner와 `../iop-s2` provenance 확인
- [ ] Claude/agy/Codex command와 current version/help 확인
- [ ] `token/.iop-bench`, CA와 provider provisioning source 존재/mode 확인; 내용 출력 없음
- [ ] dev runtime source/build identity, process와 listener 확인
- [ ] managed projection, provider slot/route와 no-legacy-fallback 확인
- [ ] config observation이 runtime route/stage와 정확히 일치
- [ ] direct qualification `ready=5`, fresh attempt 5개, `unresolved=0`, `running=0`, `interrupted=0`, terminal evidence 5개와 infrastructure block 없음
- [ ] public preflight `ready=9`
- [ ] 현재 plan이 exactly one scored run을 소유하고 사용자 권한이 명확함
- [ ] old run resume/retry/state edit 계획 없음
- [ ] run 이후 status, scoring, report의 run id 전달 경로 준비
## 17. 관련 구현
- controller/state: `scripts/agent_benchmark/attempts.py`
- manifest: `scripts/agent_benchmark/manifest.py`
- lifecycle: `scripts/agent_benchmark/lifecycle.py`
- workspace isolation: `scripts/agent_benchmark/workspace.py`
- live routing/admission: `scripts/agent_benchmark/live_iop.py`
- Claude adapter: `scripts/agent_benchmark/claude_iop.py`
- agy adapter: `scripts/agent_benchmark/agy_iop.py`
- Codex adapter: `scripts/agent_benchmark/codex_iop.py`
- measurement: `scripts/agent_benchmark/measurement.py`
- browser/web gate: `scripts/agent_benchmark/web_validation.py`
- blind scoring: `scripts/agent_benchmark/scoring.py`
- rubric: `scripts/agent_benchmark/rubric.py`
- report: `scripts/agent_benchmark/reporting.py`

View file

@ -77,11 +77,14 @@ nodes:
dev-runtime에서 긴 응답의 정지 판정은 아래 순서를 유지한다.
```text
Node provider response_stall_timeout_ms = 120000
external caller boundary ≈ 180000
Edge request hard timeout > 180000
Node provider response_stall_timeout_ms = 60000
Node close/probe join ceiling = 5000
Agent dispatcher silence safety = 70000
Edge request hard timeout > 70000
```
dev-runtime은 `max_request_fault_recovery: 0``max_strategy_fault_recovery: 0`을 명시하고 Pi agent retry도 비활성화한다. 따라서 provider 무진행은 Node가 60초에 한 번만 종료하며 Pi·Edge·Dispatcher가 같은 요청을 다시 중첩 실행하지 않는다.
`response_stall_timeout_ms` 변경은 restart-required다. `config check``config refresh --mode dry-run`에서 이를 확인한 뒤 Edge와 Node를 같은 source ref로 rebuild/restart하고, 각 binary의 build identity와 실행 중인 process identity를 다시 대조한다. tracked 검증 근거에는 source/build/config 식별자, 단조 시간, terminal 개수와 결과 분류만 남기며 prompt, output, token, credential 원문은 기록하지 않는다.
확인:
@ -165,6 +168,16 @@ http://<edge-host>:18081/v1
- `think=false` 또는 `reasoning_effort=none`은 hide-only 옵션이 아니라 thinking disable 요청이다. Provider-native field가 있는 경우 해당 field를 우선 사용해 passthrough 보존을 검증한다.
- Provider-pool passthrough 파라미터의 세부 계약과 금지/허용 범위는 `agent-contract/outer/openai-compatible-api.md`를 기준으로 한다.
### Managed route-qualified capacity smoke
Managed mode의 capacity는 전역 model catalog나 같은 upstream model을 제공하는 모든 provider의 합이 아닙니다. 먼저 OpenAI ingress와 같은 principal token으로 active route alias를 조회하고, route의 `resource_selector`, profile, upstream model과 일치하는 현재 healthy provider snapshot만 eligible capacity로 계산합니다. 현재 dev projection에서는 `ornith:35b``onexplayer-lemonade`, `ornith-fast``rtx5090-lemonade`에 각각 고정되므로 두 capacity를 합치거나 alias를 한 batch에 섞지 않습니다.
정상 capacity 검증은 `scripts/e2e-openai-managed-capacity-smoke.sh`로 Chat과 Responses를 한 endpoint·route씩 실행합니다. 스크립트는 실제 emitted JSON의 Unicode rune 수와 `runes/4 + runes/16` estimate를 계산해 `normal`임을 확인하고, selected provider의 `capacity + 1`만 전송합니다. 별도 long-context/repeat smoke는 `long_context_capacity`를 사용하며 normal-capacity 완료 근거를 대체하지 않습니다.
성공 조건은 모든 요청 HTTP 200, Chat의 finish terminal과 Responses의 `response.completed` 각각 정확히 1개, stream별 `[DONE]` 정확히 1개, selected provider peak가 eligible capacity와 같고 queue가 1 이상인 상태, 최종 `in_flight=0`/`queued=0`입니다. route mismatch, context class mismatch, non-selected capacity 포함, terminal 누락/중복, status 관측 누락은 fail-closed입니다.
각 invocation은 ignored `agent-test/runs/**` 아래 mode `0700` unique directory를 생성하고 current-run manifest가 소유한 request/result/status만 판정합니다. Raw route DTO, token/header, route/slot id, prompt, request/response body와 모델 출력은 해당 디렉터리 밖으로 복사하지 않습니다. tracked evidence와 code review에는 allowlist된 sanitized summary의 run id, script hash, route alias, selected provider, endpoint, computed request shape/class, terminal count, peak/queue/final counter와 outcome만 남깁니다.
예시 (dev-corp `gemma4:26b` provider-pool non-stream 측정, think 생략):
아래 `18081` 포트는 local Edge 예시다. dev-corp public smoke에서는 base URL을 `https://digitalplatform.iop.ai.kr/v1`로 바꾼다. Direct Edge listener `http://digitalplatform.iop.ai.kr:18086/v1`도 동작하지만 사용자-facing 기본값은 포트 없는 public URL이다.
@ -270,25 +283,26 @@ The deterministic Messages qualification succeeds alongside Chat: the Control Pl
공식 `agy` 1.1.12 API-key provider는 upstream Gemini key가 아니라 관리형 IOP principal token을 사용한다. dev operator가 이미 발급한 token과 CA 파일을 보호된 `token/` 아래에 둔 경우 값을 명령행에 직접 쓰지 않고 다음처럼 읽는다.
```bash
read -r IOP_BENCH_TOKEN < token/.iop-bench
export GEMINI_API_KEY="$IOP_BENCH_TOKEN"
export SSL_CERT_FILE="$PWD/token/iop-dev-ca.pem"
export NODE_EXTRA_CA_CERTS="$PWD/token/iop-dev-ca.pem"
read -r IOP_AGY_SMOKE_TOKEN < token/.iop-principal
GEMINI_API_KEY="$IOP_AGY_SMOKE_TOKEN" \
SSL_CERT_FILE="$PWD/token/iop-dev-ca.pem" \
NODE_EXTRA_CA_CERTS="$PWD/token/iop-dev-ca.pem" \
GOOGLE_GEMINI_BASE_URL="https://<edge-host>:<https-port>/gemini/<direct-route-id>" \
agy --sandbox --output-format stream-json --model 'Gemini 3.6 Flash' \
--print 'Reply only with OK. Do not use tools or modify files.'
GEMINI_API_KEY="$IOP_AGY_SMOKE_TOKEN" \
SSL_CERT_FILE="$PWD/token/iop-dev-ca.pem" \
NODE_EXTRA_CA_CERTS="$PWD/token/iop-dev-ca.pem" \
GOOGLE_GEMINI_BASE_URL="https://<edge-host>:<https-port>/gemini/<hybrid-preset-id>" \
agy --sandbox --output-format stream-json --model 'Gemini 3.6 Flash' \
--print 'Inspect README.md and report only its first Markdown heading. Do not modify files.'
unset IOP_BENCH_TOKEN GEMINI_API_KEY SSL_CERT_FILE NODE_EXTRA_CA_CERTS
unset IOP_AGY_SMOKE_TOKEN
```
`--effort`는 API-key provider 호출에 넣지 않는다. direct와 hybrid 모두 JSONL의 마지막 record가 `event=result`, 중첩 `result.status=SUCCESS` 한 건이어야 한다. hybrid는 plan/work/review가 포함되므로 direct보다 오래 걸릴 수 있으며, caller timeout을 이유로 같은 scored attempt를 재실행하지 않는다. Gemini ingress와 공식 event 구조의 상세 계약은 `agent-contract/outer/gemini-compatible-api.md`를 기준으로 한다.
benchmark 전체 preflight에서는 Claude/agy/Codex에 같은 IOP principal을 secret environment reference로 연결하고 `IOP_BENCH_CONFIG_OBSERVATION_ENV`가 가리키는 operator-owned route/binding observation을 함께 제공한다. 사설 CA 환경은 각 isolated caller child에 `SSL_CERT_FILE``NODE_EXTRA_CA_CERTS`로 전달된다. preflight가 모든 cell을 `ready`로 판정하기 전에는 scored `run`을 시작하지 않는다.
`SSL_CERT_FILE``NODE_EXTRA_CA_CERTS`는 위 caller process에만 적용한다. Codex/IDE 시작 환경이나 ambient shell에 export하면 ChatGPT WebSocket 같은 공개 TLS 연결까지 사설 CA override를 사용하므로 금지한다. `--effort`는 API-key provider 호출에 넣지 않는다. direct와 hybrid 모두 JSONL의 마지막 record가 `event=result`, 중첩 `result.status=SUCCESS` 한 건이어야 한다. hybrid는 plan/work/review가 포함되므로 direct보다 오래 걸릴 수 있다. 한 경로가 실패하면 다른 경로를 묶어 재실행하지 않고 해당 IOP ingress, route binding, preset stage 또는 caller terminal을 분리해 확인한다. Gemini ingress와 공식 event 구조의 상세 계약은 `agent-contract/outer/gemini-compatible-api.md`를 기준으로 한다.
### Incident redaction check

View file

@ -101,6 +101,37 @@ type ProtocolAuthConf struct {
Scheme string `mapstructure:"scheme" yaml:"scheme,omitempty"`
}
// ProtocolEffortMappingConf declares how one provider operation represents
// IOP's normalized reasoning-effort levels. Levels may map multiple IOP grades
// to one provider grade when the provider exposes a smaller scale.
type ProtocolEffortMappingConf struct {
Levels map[string]string `mapstructure:"levels" yaml:"levels,omitempty"`
WithTools bool `mapstructure:"with_tools" yaml:"with_tools,omitempty"`
TokenBudget bool `mapstructure:"token_budget" yaml:"token_budget,omitempty"`
Wire string `mapstructure:"wire" yaml:"wire,omitempty"`
}
const (
ProtocolEffortWireOpenAIChat = "openai_chat"
ProtocolEffortWireOpenAIResponses = "openai_responses"
ProtocolEffortWireAnthropicMessage = "anthropic_messages"
ProtocolEffortWireGeminiChat = "gemini_openai_chat"
)
var validProtocolEffortWires = map[string]struct{}{
ProtocolEffortWireOpenAIChat: {},
ProtocolEffortWireOpenAIResponses: {},
ProtocolEffortWireAnthropicMessage: {},
ProtocolEffortWireGeminiChat: {},
}
// ProtocolNormalizationConf contains provider-wire normalization facts. It is
// intentionally operation-scoped: a provider may support reasoning with tools
// on Responses while rejecting the same semantic request on Chat Completions.
type ProtocolNormalizationConf struct {
Effort map[string]ProtocolEffortMappingConf `mapstructure:"effort" yaml:"effort,omitempty"`
}
// ProtocolProfileConf is the overlayable configuration of a protocol profile.
// It is the source of truth for endpoint, operation paths, auth, and
// capabilities before concrete resolution.
@ -128,6 +159,32 @@ type ProtocolProfileConf struct {
// Extensions holds restricted profile-specific options that cannot be
// expressed in the typed fields above.
Extensions map[string]any `mapstructure:"extensions" yaml:"extensions,omitempty"`
// Normalization maps IOP semantic request controls to provider operations.
Normalization ProtocolNormalizationConf `mapstructure:"normalization" yaml:"normalization,omitempty"`
}
var identityReasoningEffortLevels = map[string]string{
"none": "none", "low": "low", "medium": "medium",
"high": "high", "xhigh": "xhigh", "max": "max",
}
var reasoningEffortOrder = []string{"none", "low", "medium", "high", "xhigh", "max"}
func reasoningEffortGradeIndex(level string) int {
for index, candidate := range reasoningEffortOrder {
if candidate == level {
return index
}
}
return -1
}
func identityEffortMapping(wire string, withTools, tokenBudget bool) ProtocolEffortMappingConf {
levels := make(map[string]string, len(identityReasoningEffortLevels))
for level, providerLevel := range identityReasoningEffortLevels {
levels[level] = providerLevel
}
return ProtocolEffortMappingConf{Levels: levels, WithTools: withTools, TokenBudget: tokenBudget, Wire: wire}
}
// ConcreteProtocolProfile is the immutable, resolved snapshot of a protocol
@ -162,6 +219,10 @@ var builtInProtocolProfiles = map[string]ProtocolProfileConf{
},
Auth: ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"},
Capabilities: []string{"models", "chat", "streaming", "tool_calling", "responses"},
Normalization: ProtocolNormalizationConf{Effort: map[string]ProtocolEffortMappingConf{
string(OperationChatCompletions): identityEffortMapping(ProtocolEffortWireOpenAIChat, false, false),
string(OperationResponses): identityEffortMapping(ProtocolEffortWireOpenAIResponses, true, false),
}},
},
"gemini": {
Driver: ProtocolDriverOpenAIChat,
@ -172,6 +233,9 @@ var builtInProtocolProfiles = map[string]ProtocolProfileConf{
},
Auth: ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"},
Capabilities: []string{"models", "chat", "streaming", "tool_calling"},
Normalization: ProtocolNormalizationConf{Effort: map[string]ProtocolEffortMappingConf{
string(OperationChatCompletions): identityEffortMapping(ProtocolEffortWireGeminiChat, true, true),
}},
},
"anthropic": {
Driver: ProtocolDriverAnthropicMessages,
@ -182,6 +246,9 @@ var builtInProtocolProfiles = map[string]ProtocolProfileConf{
},
Auth: ProtocolAuthConf{Header: "x-api-key"},
Capabilities: []string{"messages", "streaming", "tool_calling", "count_tokens"},
Normalization: ProtocolNormalizationConf{Effort: map[string]ProtocolEffortMappingConf{
string(OperationMessages): identityEffortMapping(ProtocolEffortWireAnthropicMessage, true, true),
}},
},
"glm": {
Driver: ProtocolDriverOpenAIChat,
@ -386,6 +453,27 @@ func deepCopyProfileConf(src ProtocolProfileConf) ProtocolProfileConf {
if src.Extensions != nil {
dst.Extensions = deepCopyExtensions(src.Extensions)
}
if src.Normalization.Effort != nil {
dst.Normalization.Effort = make(map[string]ProtocolEffortMappingConf, len(src.Normalization.Effort))
for operation, mapping := range src.Normalization.Effort {
dst.Normalization.Effort[operation] = cloneEffortMapping(mapping)
}
}
return dst
}
func cloneEffortMapping(src ProtocolEffortMappingConf) ProtocolEffortMappingConf {
dst := ProtocolEffortMappingConf{
WithTools: src.WithTools,
TokenBudget: src.TokenBudget,
Wire: src.Wire,
}
if src.Levels != nil {
dst.Levels = make(map[string]string, len(src.Levels))
for level, providerLevel := range src.Levels {
dst.Levels[level] = providerLevel
}
}
return dst
}
@ -533,6 +621,14 @@ func mergeProfileOverlay(base, overlay ProtocolProfileConf) (ProtocolProfileConf
merged.Extensions[k] = v
}
}
if len(overlay.Normalization.Effort) > 0 {
if merged.Normalization.Effort == nil {
merged.Normalization.Effort = make(map[string]ProtocolEffortMappingConf)
}
for operation, mapping := range overlay.Normalization.Effort {
merged.Normalization.Effort[operation] = cloneEffortMapping(mapping)
}
}
return merged, nil
}
@ -583,6 +679,32 @@ func validateConcreteProfile(id string, p ProtocolProfileConf) error {
}
}
}
for operation, mapping := range p.Normalization.Effort {
if _, ok := p.Operations[operation]; !ok {
return fmt.Errorf("profile %q: effort normalization operation %q is not declared", id, operation)
}
if len(mapping.Levels) == 0 {
return fmt.Errorf("profile %q: effort normalization operation %q has no levels", id, operation)
}
if _, ok := validProtocolEffortWires[mapping.Wire]; !ok {
return fmt.Errorf("profile %q: effort normalization operation %q has invalid wire %q", id, operation, mapping.Wire)
}
for level, providerLevel := range mapping.Levels {
levelIndex := reasoningEffortGradeIndex(level)
if levelIndex < 0 {
return fmt.Errorf("profile %q: effort normalization level %q is not recognized", id, level)
}
providerLevel = strings.TrimSpace(providerLevel)
if providerLevel == "" {
return fmt.Errorf("profile %q: effort normalization level %q has an empty provider value", id, level)
}
// Canonical provider grades may collapse downward, but an explicit
// mapping must never silently upgrade the caller's requested grade.
if providerIndex := reasoningEffortGradeIndex(providerLevel); providerIndex > levelIndex {
return fmt.Errorf("profile %q: effort normalization level %q upgrades to %q", id, level, providerLevel)
}
}
}
if p.Auth.Header == "" {
return fmt.Errorf("profile %q: auth.header must not be empty", id)
}
@ -704,6 +826,37 @@ func (p ConcreteProtocolProfile) MapModel(model string) string {
return model
}
// MapReasoningEffort maps one normalized IOP effort grade to the provider
// value for an operation. If the exact grade is unsupported, it selects the
// nearest explicitly supported lower grade. It never upgrades effort and
// rejects unsupported operation/tool combinations.
func (p ConcreteProtocolProfile) MapReasoningEffort(operation ProtocolOperation, level string, hasTools bool) (string, bool) {
mapping, ok := p.Normalization.Effort[string(operation)]
if !ok || (hasTools && !mapping.WithTools) {
return "", false
}
requestedIndex := reasoningEffortGradeIndex(level)
if requestedIndex < 0 {
return "", false
}
for index := requestedIndex; index >= 0; index-- {
mapped := strings.TrimSpace(mapping.Levels[reasoningEffortOrder[index]])
if mapped != "" {
return mapped, true
}
}
return "", false
}
// EffortMapping returns an operation-scoped copy of the normalization facts.
func (p ConcreteProtocolProfile) EffortMapping(operation ProtocolOperation) (ProtocolEffortMappingConf, bool) {
mapping, ok := p.Normalization.Effort[string(operation)]
if !ok {
return ProtocolEffortMappingConf{}, false
}
return cloneEffortMapping(mapping), true
}
// Clone returns a deep copy of the concrete profile.
func (p ConcreteProtocolProfile) Clone() ConcreteProtocolProfile {
return ConcreteProtocolProfile{

Some files were not shown because too many files have changed in this diff Show more