# Milestone: [output-01] OpenAI-compatible 출력 검증 필터

## 위치

- Roadmap: [ROADMAP.md](../../../ROADMAP.md)
- Phase: [PHASE.md](../PHASE.md)

## 목표

OpenAI-compatible Chat Completions와 Responses provider 경로에서 모델 출력/행동 이상을 caller 종류와 무관하게 request history와 provider response stream으로 감지하고, 사용자 경험을 해치지 않는 방식으로 관찰/보정/중단/재시도/검증한다.
반복 루프는 단일 stream content 반복, request history에 누적된 assistant-only anchor 반복, 동일 tool/action 반복을 모두 포함한다. 단일 stream content 반복은 streaming passthrough를 유지한 채 upstream만 교체해 continuation repair로 이어 쓴다. assistant history anchor는 endpoint codec이 보존한 표준 role/channel provenance를 기준으로 탐지하고, caller가 reasoning history를 재전송하지 않거나 conversation identity가 없으면 존재하지 않는 cross-request state를 추론하지 않는다. D01은 반복된 plain reasoning만 sanitize/live dedupe하고 no-progress·tool 미release·side-effect 비해당일 때 원문 safe prefix를 보존한 단계 복구를 허용하도록 확정됐다. D05는 언어 판별·번역 모델 호출을 제거하고 고정 영어 지시문으로 복구 요청을 직접 조립하도록 확정됐다. tool/action 반복은 `tool name + normalized args` fingerprint와 완료된 이전 tool result의 동일/no-progress 신호를 기준으로 감지해 side-effect 안전성이 없으면 repair 대신 안전 중단한다. `metadata.scheme` JSON 출력 계약은 검증 전 downstream content streaming을 막는 `contract_schema` 경로로 처리한다.
실제 repeat/schema/provider-error 의미 필터 구현에 앞서 deterministic diagnostic filter로 pass, observe-only violation, blocking violation과 단일 recovery를 실제 codec/Core/Arbiter/ReleaseSink 경로에서 한 명령으로 관측하는 smoke를 선행 gate로 둔다.
rolling evidence threshold를 통과한 tail은 검증 안전성을 낮추지 않으면서 provider가 해당 window를 생성한 시간에 맞춰 균일하게 전달한다. 시간은 filter 승인 전 release eligibility로 사용하지 않고, 승인된 출력의 delivery scheduling에만 사용한다.

## 상태

[계획]

## 승격 조건

- 없음

## 구현 잠금

- 상태: 해제
- SDD: 필요
- SDD 문서: [SDD.md](../../../sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md)
- SDD 사유: OpenAI-compatible metadata schema, streaming retry/abort 상태 전이, provider 응답 검증 계약이 바뀌는 Milestone이다.
- 잠금 해제 조건:
  - [x] SDD 잠금이 해제되어 있다
  - [x] SDD 사용자 리뷰가 없거나 승인/해결되었다
  - [x] Acceptance Scenario가 Milestone 기능 Task와 연결되어 있다
  - [x] Evidence Map이 완료 시 `Roadmap Completion`과 최종 검증 evidence로 검증 가능하게 연결되어 있다
- 결정 필요:
  - [x] [D01] assistant history anchor의 preflight history sanitation, live reasoning dedupe, no-progress repair와 온도 단계 복구 정책
  - [x] [D02] `/v1/chat/completions`와 `/v1/responses`를 공통 필터 코어와 endpoint별 codec으로 함께 구현하는 범위
  - [x] [D03] assembled output/reasoning 원문 로그의 설정 기반 기록·중단 정책
  - [x] [D04] 기존 Tool Call Runtime 검증 재시도와 공유하는 request-local exact-replay 최대 3회 및 commit 경계 정책
  - [x] [D05] 언어 판별·번역 모델 호출 없이 사용하는 고정 영어 반복 복구 지시문
  - [x] [D06] 오류 사건의 중복 집계와 LLM 기반 소스 분석·수정 제안·승인·변경 요청·병합·배포를 별도 범용 플랫폼으로 분리하는 책임 경계
  - [x] [D07] provider output-cap `length`를 작은 attempt cap의 managed continuation으로 처리하고, 원본 요청·assistant prefix를 보존한 context-window 기반 논리 trajectory를 fault recovery 최대 3회와 분리하는 정책
  - [x] [D08] filter 승인을 받은 pending tail만 수집 시간 기준으로 비동기 pacing하고, 시간은 release eligibility로 사용하지 않는 정책

## 범위

- OpenAI-compatible `/v1/chat/completions`와 `/v1/responses` provider route의 출력 검증 필터 모듈과 response path 선택
- caller/agent 이름이 아닌 OpenAI-compatible role, message/input item, response delta/item, tool contract capability를 endpoint codec이 normalized event로 바꿔 사용하는 caller-neutral 판정 경계
- 출력 검증 filter별 enable/disable 정책을 environment(`dev`, `dev-corp`), model group/model/provider, 기능 단위로 평가하는 config/registry 계층
- [Stream Evidence Gate Core](../../../archive/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md)가 제공하는 response-start 포함 normalized event, rolling look-behind, bounded terminal/fragment gate, transport commit과 recovery mechanics를 OpenAI Chat Completions와 Responses consumer가 함께 채택한다. 이 Milestone은 endpoint별 raw codec, lossless `RequestRebuilder`, Edge `AttemptDispatcher`/`ReleaseSink` adapter와 반복·schema·provider 오류의 의미 판정/typed `RecoveryIntent`만 소유한다. release/terminal/recovery arbitration과 budget은 Core가 소유하며 공통 gate를 재구현하지 않는다. provider의 attempt별 출력 상한 도달은 외부 `length` 실패가 아니라, 모델 context window 안에서 작은 attempt 상한으로 계속 생성하는 managed continuation 후보로 판정한다.
- provider terminal reason `length`를 `managed_length_continuation` 후보로 판정한다. IOP는 managed profile에서 provider의 작은 attempt별 `max_tokens`만 사용하고, 외부 caller에는 중간 `length`/`[DONE]`을 노출하지 않는다. Core가 이미 release한 content/think/reasoning safe prefix와 cursor를 보존한 뒤 endpoint별 Rebuilder가 원본 요청과 channel별 assistant prefix를 다음 attempt로 조립한다. attempt 상한과 재구성된 request prompt의 실제 token 수, reserve, caller가 명시했다면 남은 논리 output cap이 모두 허용할 때만 이어 간다. provider attempt cap은 내부 운영값이고 caller output cap은 논리 요청 전체에 한 번만 적용한다. assistant prefix는 rebuilt prompt에 이미 포함되므로 별도의 누적 output과 이중 계상하지 않는다. context 여유·논리 trajectory 예산이 소진되거나 완성된 tool call이 생기면 최종 terminal을 한 번만 전달한다. 이 정책은 Core의 오류 recovery 3회 상한과 분리된 context-window 기반 trajectory budget을 소비하며, 의미 요약·문장 경계 절단·별도 모델 호출은 사용하지 않는다.
- 반복 출력 루프 감지용 single-stream rolling inspector, incoming request-history 기반 assistant anchor 및 tool/action fingerprint inspector, bounded text/tool-call fragment hold/release 판정, upstream abort, continuation repair. repair는 반복 전까지 사용자에게 전달된 원문을 보존하고 반복 구간만 제외하며, 사용자 지정 온도가 없을 때 `[0.2, 0.4, 0.6]` 순서로 시도하고 배열 소진 시 종료한다. 의미 요약·임의 절단과 side-effect 구간 자동 복구는 금지한다. all-complete Arbiter가 단일 plan을 고르고 current attempt ownership이 끝나면 endpoint별 Rebuilder가 반복 전 content와 think/reasoning 원문을 channel별로 구분해 고정 영어 지시문과 직접 조립한다. 사용자 요청·message, 언어 판별·번역·별도 모델 호출은 포함하지 않으며 문맥 한도를 넘으면 자동 복구하지 않는다.
- rolling window의 첫 eligible text/reasoning delta부터 evidence threshold 또는 terminal 도달까지 monotonic 수집 시간 `T`를 측정하고, all-complete filter pass 뒤 immutable `approved` queue로 옮긴 tail의 승인된 rune 수를 `T` 동안 frame tick별 누적 quota로 균일하게 전달한다. 다음 window의 provider 수집·검증은 이전 approved window 전달과 겹쳐 실행하고, bounded queue와 backpressure로 메모리를 제한한다. `pending -> approved -> delivered` 경계를 분리하며 recovery는 unapproved tail만 폐기·교체할 수 있다. structural/tool fragment는 임의 분할하지 않고 endpoint event/channel 순서를 보존하며, terminal은 approved queue drain 뒤 한 번만 전달하고 cancel·sink failure는 timer와 provider ownership을 정리한다.
- `code`와 `message`만 가진 `filters[]`에 매칭된 provider 오류가 Core의 downstream commit 전에 끝난 경우, 완료된 [Tool Call Runtime 검증 재시도 MVP](../../../archive/phase/knowledge-tool-optimization-extension/milestones/tool-call-runtime-validation-retry.md)의 request-local raw request snapshot·bounded exact replay 경로를 확장한다. provider 오류와 Tool Call Runtime 검증은 최초 실행을 제외하고 합쳐 최대 3회 재실행하며, commit 뒤 오류는 남은 500-rune tail과 무관하게 안전 종료한다.
- `metadata.scheme` JSON schema 계약 수신, 마지막 user message prompt append, hard bound가 있는 `terminal_gate` validation, schema 위반 시 bounded retry
- `passthrough`, `passthrough_guarded`, `contract_schema` 내부 response path 구분과 실행 로그/관측 기준. 이 이름들은 caller가 지정하는 공개 request field가 아니라 IOP 내부 경로/로그 기준이다.
- provider-pool의 raw tunnel/normalized provider execution 경로는 유지하되 두 path의 endpoint codec이 같은 Core event 계약으로 수렴하고, standard inference/provider response validation은 활성 [OpenAI-compatible API 계약](../../../../agent-contract/outer/openai-compatible-api.md)을 따르는 책임 경계
- local/dev에서만 명시적으로 활성화되는 deterministic diagnostic filter와 provider stream fixture. mock은 판정만 제어하고 Chat/Responses codec, Stream Evidence Gate Core, all-complete Arbiter, Recovery Coordinator, 실제 ReleaseSink와 raw-free `FilterObservation` sink는 production 구현을 그대로 사용한다.

## 기능

### Epic: [stream-gate-observability] Observable Stream Gate Diagnostic

실제 의미 필터 구현 전에 공통 stream pipeline의 지연 평가·release·recovery를 운영자가 반복 관측할 수 있는 선행 smoke를 묶는다.

- [x] [observable-core-smoke] 한 명령으로 실행 가능한 local/dev diagnostic smoke가 deterministic provider stream과 diagnostic `Filter` mock을 실제 Chat/Responses codec, Stream Evidence Gate Core, all-complete Arbiter, Recovery Coordinator, `ReleaseSink`, raw-free `FilterObservation` sink에 연결한다. mock은 `pass`, `observe_only` violation, pre-release `blocking` violation 뒤 recovery 성공의 판정만 제어하고, 외부 caller request로 활성화할 수 없으며 production 기본 Registry에는 등록하지 않는다. 검증: 모든 provider batch가 평가 전 stage되고, pass와 observe-only는 `response_staged -> filter_evaluation_started -> filter_evaluated -> arbitration_decided -> release_committed -> terminal_committed`, blocking은 `response_staged -> filter_evaluation_started -> filter_evaluated -> arbitration_decided -> recovery_plan_selected -> recovery_attempt_aborted -> recovery_rebuilt -> recovery_dispatched` 뒤 새 attempt의 stage/evaluate/release/terminal 순서를 구조화된 결과로 보여 준다. stable correlation, attempt 전환, 출력·terminal 중복 부재와 raw prompt/output/tool args/result/auth 미기록까지 확인한 뒤에만 `repeat-guard`, `schema-contract`, `provider-error-retry` 의미 필터 구현을 시작한다.

### Epic: [output-filter] Output Filter Runtime Foundation

OpenAI-compatible 출력 필터의 계약, 공통 pipeline, endpoint codec, Stream Evidence Gate 채택과 적용 정책 기반을 묶는다.

- [x] [contract-doc] OpenAI-compatible 계약 문서와 구현 타입이 `/v1/chat/completions`와 `/v1/responses`의 `metadata.scheme`, 내부 `contract_schema`/`passthrough_guarded`/provider-error-retry path, provider-pool tunnel/normalized RunEvent 실행 경계, caller-neutral 반복 guard 입력, bounded raw-canonical request snapshot과 conversation identity 부재 시 degraded behavior를 설명한다. initial ingress overflow는 HTTP 413 `invalid_request_error`, internal rebuild overflow는 commit state에 맞는 recovery terminal error로 구분한다. raw tunnel provider를 normalized 실행으로 강제하지 않지만 두 path는 codec 뒤 같은 Core event/recovery 계약을 사용한다. 구현 시 [openai-compatible-api.md](../../../../agent-contract/outer/openai-compatible-api.md)와 [edge-config-runtime-refresh.md](../../../../agent-contract/inner/edge-config-runtime-refresh.md)를 코드/Go handler·config type 테스트와 함께 갱신하고 caller 제품명을 runtime 조건으로 사용하지 않는다.
- [x] [filter-pipeline] repeat/schema/provider-error filter가 Core Go `Filter` interface로 등록된다. trigger-ready filter는 동일 immutable batch에서 병렬 평가되고 terminal trigger 미충족 schema는 blocking deferred, error event가 없는 provider-error는 nonblocking not-applicable outcome으로 complete set에 포함된다. repeat는 rolling, schema는 bounded terminal, provider-error는 hold 없는 event subscription을 선언하고 enforcement/failure mode는 Registry가 소유한다. 검증: `go test ./apps/edge/internal/openai -count=1`에서 evaluated/deferred/not-applicable 구분, no deferred-pass, all-complete/failure policy와 direct-submit 금지가 통과한다.
- [x] [stream-gate-adoption] Chat Completions와 Responses codec이 response status/header/opening event까지 normalized event로 만들고, Edge adapter가 Core `AttemptDispatcher`/`AttemptController`/`ReleaseSink`를 구현한다. 두 handler의 unbounded `io.ReadAll` 앞에 host pre-read limit를 적용하고 limit+1 overflow를 413으로 구분한다. SnapshotBuilder/Rebuilder는 typed view 추가 직후와 rebuild 임시 output 할당 전후의 current peak retained bytes를 다시 확인하고 종료/cancel/overflow에서 object를 release한다. provider response-start와 normalized live SSE role chunk는 첫 safe release 전 commit/flush하지 않으며 hop-by-hop/변환 전 `Content-Length`는 전달하지 않는다. Core가 rolling/terminal/fragment hold, all-complete, idle no-release와 single action을 소유한다. 검증: Chat/Responses raw-body limit-1/limit/limit+1, exact-limit body+typed-view overflow, rebuild 전후 peak/no full-read overflow/release, tunnel/normalized 두 path의 eager header/role 없음, header allowlist/content-length 제거, staged 500 retry, backpressure, path switch와 single terminal fixture가 통과한다.
- [x] [responses-codec] `/v1/responses`의 input item history와 response-start/text/reasoning/function-call/terminal event를 normalized event와 repair input으로 변환하고, Core의 기본값/절대 상한 16 MiB `max_ingress_snapshot_bytes` 안에서 raw body 하나를 canonical source로 unknown caller item/field를 보존하는 bounded lossless Responses `RequestRebuilder`를 제공한다. raw parser와 serializer는 Chat과 분리하고 concrete model/auth rewrite는 dispatcher admission에 둔다. 검증: Responses raw body round-trip/unknown field, stream item split, staged opening event, reasoning/encrypted reasoning 보존, function-call, terminal/error, path-switch recovery가 Chat과 같은 semantic decision/plan을 내고 endpoint shape를 유지하며 retained/rebuild limit 초과는 no-dispatch로 끝난다.
- [x] [filter-policy] filter enable/disable, `blocking|observe_only`, hold mode/bound를 environment(`dev`, `dev-corp`)와 model group/model/provider/protocol capability별로 평가한다. request 시작 시 config generation과 required capability를 고정해 schema 같은 필수 filter를 지원하지 않는 provider 후보는 admission 전에 제외하고, actual target별 active set은 attempt마다 같은 snapshot으로 다시 resolve한다. 검증: Chat/Responses와 qwen/gemma/ornith fixture에서 policy precedence, mid-request reload 격리, provider 전환 re-resolution, required no-candidate 400, disabled/duplicate filter 미평가와 caller-neutral 분기가 통과한다.

### Epic: [approved-tail-delivery] Approved Tail Delivery Pacing

filter가 승인한 rolling tail의 전달 속도를 provider 수집 cadence에 맞추되 검증·복구 경계와 upstream 수집을 분리한다.

- [ ] [approved-tail-pacing] Core와 Edge release adapter가 rolling epoch를 `pending`, immutable `approved`, `delivered`로 분리한다. 첫 eligible text/reasoning delta부터 threshold 또는 terminal까지의 monotonic 수집 시간 `T`와 approved rune 수를 queue item에 고정하고, 비동기 scheduler가 endpoint-safe text/reasoning delta를 Unicode rune 경계에서 frame tick별 누적 quota로 나눠 `T` 동안 전달한다. filter 평가 시간과 provider prefill은 `T`에서 제외하며 시간 경과만으로 pending을 승인하거나 idle fail-closed를 우회하지 않는다. 이전 approved window를 전달하는 동안 다음 provider window를 계속 수집·평가하고, bounded approved queue가 찰 때만 upstream backpressure를 적용한다. recovery는 unapproved tail만 폐기·교체하고 approved payload는 순서·내용을 바꾸지 않으며, structural/tool fragment는 원자적으로 유지한다. terminal은 queue drain 뒤 한 번만 전달하고 cancel·deadline·partial sink failure에서 timer, queue, provider ownership을 정리한다. 검증: fake monotonic clock으로 200/500/1000-rune pass와 short terminal tail을 실행해 각 window의 전달 시간이 `T`의 ±10%이고 전체 rune/event/channel 순서와 UTF-8이 동일하며 `collect T + deliver T`의 직렬 지연 없이 다음 수집이 겹치는지 확인한다. blocking/recovery, idle, bounded queue/backpressure, tool fragment, cancel과 sink failure fixture에서 승인 전 방출·중복 terminal·timer leak이 없어야 한다. Chat Completions/Responses의 tunnel·normalized 경로를 모두 검증하고, dev `ornith:35b` direct OneX 대비 Edge smoke에서 2ms 미만 gap 비율과 threshold burst가 감소하며 raw 출력은 ignored `agent-test/runs/**`에만 보관한다. 같은 변경에서 [Stream Evidence Gate 구현 스펙](../../../../agent-spec/runtime/stream-evidence-gate.md), [OpenAI-compatible API 계약](../../../../agent-contract/outer/openai-compatible-api.md), 설정 field를 추가하거나 바꾸는 경우 [Edge config 계약](../../../../agent-contract/inner/edge-config-runtime-refresh.md)을 현재 동작·기본값·관측 지표와 함께 갱신한다.

### Epic: [output-filter-recovery] Output Filter Recovery and Evidence

반복·provider 오류·schema 위반의 감지·복구 안전 경계와 운영 관측·회귀 evidence를 묶는다.

- [x] [resume-notice-builder] D01 continuation repair가 선택되고 all-complete Arbiter가 plan 하나를 고른 뒤 current attempt ownership이 끝나면 endpoint별 Rebuilder가 복구 요청을 직접 조립한다. 반복 구간을 제외한 모델의 content와 think/reasoning 원문을 channel별로 구분하고 고정 영어 지시문 `The previous model output was stopped after a repetition loop was detected. Continue from the provided content and reasoning output without repeating already generated text.`를 더한다. 사용자 요청·message는 넣지 않고, 모델 출력은 의미 요약·임의 절단·재작성하지 않는다. 두 channel 원문 전체가 문맥 한도를 넘으면 자동 복구하지 않는다. 언어 판별·번역·로컬 모델 호출·`RecoveryPlanPreparer`·번역용 설정은 구현하지 않으며, recovery budget은 실제 outbound recovery dispatch에서만 소비한다. 검증: 정확한 고정 문구, content/think channel provenance, 사용자 요청·message 부재, no-summary/no-truncation/no-rewrite, 문맥 한도 초과 no-dispatch, translator/local-model/preparer 미호출, Chat Completions와 Responses의 endpoint별 shape 보존 fixture가 통과한다.
- [x] [repeat-guard] content 반복은 단일 provider stream의 rolling window로 감지한다. assistant history anchor는 현재 incoming `messages`의 `role=user|assistant`, `content`, `reasoning_content`, `reasoning`, `reasoning_text`를 raw Chat Completions payload에서 role/channel별로 분리해 user 입력에는 없고 assistant history에 N회 누적된 plain-text fingerprint를 provider dispatch 전에 감지한다. 이 request-history 판정은 Pi session이나 특정 caller SDK에 의존하지 않는다. 명시적 conversation identity 계약이 없는 요청에는 stable lineage를 추정하거나 caller 간 TTL state를 공유하지 않으며, caller가 reasoning history를 재전송하지 않으면 current request/stream에서 관찰 가능한 범위로 낮춘다. history sanitation과 live reasoning dedupe는 [D01](../../../sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md)의 승인 범위에서만 수행하고, assistant final `content`, tool call, signed/encrypted/unknown reasoning field는 조용히 변경하지 않는다. progress는 current response가 아니라 incoming history에서 완료된 이전 tool call/result/error만으로 판정하며 서로 다른 action 자체를 progress로 단정하지 않는다. current provider content는 `rolling_window` pending에 기본 500 Unicode rune의 증거가 쌓이거나 terminal event가 올 때까지 보류한 뒤 safe prefix만 release한다. Core는 committed look-behind와 release cursor를 유지해 stream-open 뒤 반복도 감지하며, continuation recovery에서는 이미 보낸 prefix를 보존하고 새 attempt의 response-start/role/prefix 중복을 억제한다. 시간 경과만으로 release하지 않고 evidence 미충족 idle은 terminal error다. 현재 provider tool call delta는 `fragment_gate`로 완성 전 최소 fragment만 hold하며 이미 downstream으로 tool call이 나갔거나 side effect 가능성이 있으면 자동 repair하지 않는다. 검증: generic raw HTTP/OpenAI SDK fixture가 single-stream 반복, assistant-history anchor, reasoning alias, reasoning-history 미전송, conversation identity 부재, 200/500-rune rolling/look-behind, idle no-release, progress/no-progress, D01 원문 보존·반복 구간 제외·`[0.2, 0.4, 0.6]` 온도 후보, stream-open continuation, duplicate opening/prefix 금지, `[DONE]` 단일 종료와 tool side-effect 경계를 확인한다. fixture에는 UTF-8 multi-byte 경계에서 쪼개진 긴 한국어 문단 6개가 다시 반복되는 stream을 포함한다. dev에서는 `ornith:35b`에 `stream=true` 긴 한국어 최종 출력 요청을 model group 총 capacity+1 동시 요청으로 최소 3회 실행하고 raw SSE/한국어 출력을 ignored `agent-test/runs/**`에만 저장한다. 실제 반복이 관측되면 upstream abort, safe prefix continuation 또는 안전 중단을 확인하고 미재현이면 `not_reproduced`로 남기되 결정론적 fixture를 대체하지 않는다. 재개 안내문은 [D05](../../../sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md)의 고정 영어 지시문만 사용하며 2026-07-16 Pi/Ornith evidence는 generic fixture 입력 사례로만 쓴다.
- [ ] [provider-error-retry] provider tunnel 오류는 `filters[]`의 각 원소가 가진 `code`와 `message` 두 필드만으로 판정한다. `code` exact-match와 `message` 포함-match를 모두 만족하면 `provider_error_filter`가 `exact_replay` RecoveryIntent와 sanitized reason을 반환한다. 초기 원소는 `{ code: 500, message: "Failed to parse input at pos" }`이며 유사 오류는 같은 두 필드를 가진 원소를 배열에 추가한다. filter는 snapshot, counter, body, provider selection, submit을 소유하지 않는다. Core는 response-start/status/header/body를 staged evidence로 평가하고 `transport_uncommitted`에서만 D04의 최초 실행 제외 공통 최대 3회 exact replace-attempt를 허용하되, exact/continuation/schema를 합산한 최초 실행 제외 기본값/절대 상한 3회의 request 전체 `max_recovery_attempts_total`을 우선 적용한다. current attempt abort 뒤 bounded lossless Rebuilder/dispatcher로 cycle당 새 admission 하나를 실행하며 provider 선택은 기존 pool 정책에 맡긴다. 검증: response-start 뒤 알려진 parser error/두 번째 원소, commit 전 buffered chunk, tool-validation 동시/연속 violation, original status/header 미노출, final response-start 단일 노출, 0/1/3회 policy와 4회 이상 config rejection, shared exact/전체 cap 교차 소진·stream-open/cancel/filter mismatch 안전 종료가 통과한다.
- [ ] [schema-contract] `metadata.scheme`이 있으면 `stream=true` 요청이어도 content channel을 explicit `terminal_gate`로 보류하고 JSON parse/schema validation을 수행한다. request별 `max_buffer_runes` hard bound를 필수로 두며 overflow는 partial release 없이 terminal error다. 실패 filter는 schema와 validation summary의 typed `schema_repair` intent만 반환하고 Core가 `transport_uncommitted`에서 bounded lossless Rebuilder로 새 attempt를 만든다. schema strategy budget이 남아도 request 전체 recovery cap 또는 ingress snapshot limit이 소진되면 새 attempt를 만들지 않는다. 검증: valid JSON, invalid-then-common-recovery, retry exhausted, request 전체 cap 소진, hard-limit/snapshot overflow, multimodal/unknown user field rebuild, eager header/content 없음이 통과한다.
- [ ] [ops-evidence] 출력 필터 결과가 [Stream Evidence Gate Core](../../../archive/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md)의 `FilterObservation` timeline과 요청 실행 로그/smoke에서 같은 correlation으로 원인 축을 구분할 수 있게 남고, 실제 incident는 raw prompt/tool args/result를 제외한 별도 sanitized evidence log로 generic 회귀 fixture에 연결된다. 이 consumer는 `repeat_guard`, `assistant_history_anchor`, `provider_error_filter` 등 stable filter/rule id와 fingerprint·count·offset만 Core observation에 제공한다. assembled output/reasoning 원문 기록은 설정 기본값 `on`으로 시작하되, `off` 전환 뒤의 요청에서는 원문을 쓰지 않고 비원문 운영 정보만 남긴다. 검증: generic raw HTTP/OpenAI SDK smoke를 필수 기준으로 실행하고, Pi TUI는 선택적 caller field smoke로 추가한다. role/channel provenance, reasoning history 미전송, provider 전환, 반복 fragment 관찰/보정/중단, pending tail의 configured evidence-rune threshold·evidence/terminal/idle-error release-or-close reason, provider-error-retry의 filter index/공통 exact-replay 사유·1~3회 shared attempt·commit 상태·기존 pool이 다시 선택한 provider/재사용 snapshot 여부 또는 schema validation 결과가 model/provider/IOP/protocol 축과 함께 관찰되며, 한국어 장문 dev smoke는 model/provider, attempt 수, repeat fingerprint/offset, guard 결정, `not_reproduced` 여부를 sanitized evidence로 남긴다. 사용자 요청 원문·tool args/result·인증 정보는 `on` 상태에서도 Core observation 또는 일반 로그에 기록하지 않고, 요청별 raw SSE와 출력은 단기 ignored `agent-test/runs/**`에만 두며 tracked 문서에는 복제하지 않는다.

### Epic: [managed-length] Managed Provider Length Continuation

큰 provider output cap이 admission·queue·동시성에 미치는 부작용을 attempt 단위 상한과 context-window 기반 논리 trajectory로 분리한다.

- [ ] [length-continuation] Chat Completions와 Responses codec이 provider `finish_reason=length` 또는 동등 terminal reason을 `provider_length_gate`에 전달한다. blocking filter는 terminal을 Core gate에서 보류해 외부 중간 `length`/`[DONE]`을 억제하고, complete tool call·side effect·cancel이 없으며 managed profile의 남은 context window와 logical trajectory 예산이 있을 때만 `managed_continuation` intent를 반환한다. Core는 같은 downstream stream의 released prefix/cursor를 보존하고 attempt ownership 종료 뒤 endpoint별 Rebuilder가 원본 요청, channel별 content/think/reasoning assistant prefix, provider가 지원하면 assistant prefill/continue-final-message를 우선 사용해 다음 작은 attempt를 조립한다. 그 기능이 없으면 endpoint-private 고정 continuation directive만 더하며 caller의 새 user message로 만들지 않는다. request별 provider attempt 상한은 작게 유지하고, Rebuilder가 original request와 assistant prefix를 조립한 뒤 측정한 `rebuilt_prompt_tokens`로 남은 attempt 상한을 `min(policy attempt cap, remaining_caller_logical_cap(if set), context_window - rebuilt_prompt_tokens - reserve)`로 계산한다. reasoning/content를 요약·문장 경계 절단·재작성하지 않는다. 미완성 tool fragment는 release하지 않은 채 endpoint Rebuilder가 해당 fragment를 assistant continuation prefix로 lossless하게 직렬화할 수 있을 때만 continuation으로 완성하고, 그 capability가 없거나 직렬화가 실패하면 최종 오류로 끝낸다. raw fragment는 observation/log에 넣지 않는다. 검증: 16K attempt cap 아래 2회 이상 `length`가 난 뒤 하나의 Chat/Responses stream으로 safe prefix가 중복 없이 이어지고, 중간 terminal 억제·assistant prefix 보존·logical cap exhaustion final terminal·complete/fragment tool-call 경계·fault recovery 3회 cap과 trajectory budget 분리가 확인된다.

## 완료 리뷰

- 상태: 없음
- 요청일: 없음
- 완료 근거: 기능 Task가 아직 충족되지 않았다.
- 검토 항목:
  - [ ] `complete.log`의 `Roadmap Completion`이 각 기능 Task id를 기록한다.
  - [ ] 최종 검증 출력이 SDD Evidence Map과 일치한다.
  - [ ] 모든 기능 Task 구현과 1차 검증이 끝난 뒤 비동기 collection/delivery 분리, filter 승인 경계, rune/event 순서, queue/backpressure, terminal/cancel/timer cleanup, Chat/Responses 양쪽 codec과 문서·계약 drift를 다시 검토한다.
  - [ ] 재검토에서 발견된 actionable issue를 수정하고 영향 테스트와 관련 전체 검증을 다시 실행한 뒤, 추가 actionable issue가 없을 때까지 review-fix-retest를 반복한다. 최종 `complete.log`에는 review findings, 수정 내역, 재검증 evidence와 잔여 위험을 기록한다.
- [ ] generic raw HTTP/OpenAI SDK 기준 staged response-start, single-stream 반복 continuation, assistant history anchor, provider error/Tool Call validation의 최초 실행 제외 공통 최대 3회 exact budget, 모든 strategy를 합산한 request 전체 recovery cap, bounded ingress snapshot/schema terminal gate, same action과 provider/path switch가 확인된다. 반복 검증에는 multi-byte 한국어 장문, 200/500-rune rolling/look-behind, idle no-release, stream-open continuation과 dev `ornith:35b` 다회 smoke가 포함된다.
- [ ] approved tail pacing은 filter 승인 전 release 조건을 바꾸지 않고 window별 수집 시간과 전달 시간을 맞추며, 다음 window 수집과 비동기 전달의 중첩, bounded queue/backpressure, 정확한 rune/event 순서, 단일 terminal/cancel cleanup을 deterministic clock과 dev `ornith:35b` 비교 evidence로 확인한다.
- [ ] managed `length` continuation은 작은 provider attempt cap으로 중간 terminal을 숨기고 context-window 기반 trajectory를 같은 stream에 연결하며, fault recovery cap·tool boundary·최종 usage/terminal 관측을 보존한다.
- agent-ui 상태 반영: 해당 없음
- 리뷰 코멘트: 없음

## 범위 제외

- raw tunnel provider를 normalized RunEvent 실행 경로로 강제 변환하거나 두 path의 raw parser를 합치는 작업
- OpenAI-compatible standard inference/provider response 계약 밖의 agent·terminal·workspace protocol 도입
- Pi session JSONL, Pi SDK 내부 message type, Pi local tool invocation을 IOP 반복 guard의 runtime 입력이나 필수 의존성으로 사용하는 방식
- 명시적 conversation identity 없이 caller/model을 조합한 hash를 대화 식별자로 간주하거나 caller 간 TTL 반복 state를 공유하는 방식
- 반복루프 감지를 위해 전체 응답을 buffer하거나, filter 승인 전에 경과 시간을 이유로 pending 출력을 release하는 방식. approved tail pacing은 all-complete pass 뒤의 bounded rolling window에만 적용한다.
- cross-request 반복 증거에 raw user prompt, raw tool args/result, 전체 reasoning/content를 저장하는 방식
- 진행 신호가 있는 서로 다른 tool/action을 assistant history anchor 반복만으로 차단하는 방식
- assistant final `content`를 history anchor fingerprint만으로 조용히 삭제하는 방식
- schema 계약이 있는 요청에서 검증 전 content delta를 사용자에게 먼저 노출하는 방식
- validator 모델을 이용한 자연어/tool-call 판정 gate
- diagnostic filter를 production 기본 Registry에 등록하거나 OpenAI-compatible public request field로 선택·활성화하는 기능
- 누적 요청 컨텍스트 최적화, 장기 RAG, advisor/Context Hook, workflow routing 정책
- 오류 사건의 cross-request 저장·중복 count, 연결 저장소 분석, 범용 수정 제안, 프로젝트별 마일스톤/작업 문서 생성, 사용자 승인, 격리 수정·테스트·독립 검토, 변경 요청·병합·배포·재발 관찰을 수행하는 별도 플랫폼 구현

## 작업 컨텍스트

- 표준선(선택): 첫 구현 단위는 `observable-core-smoke`다. diagnostic mock은 filter 판정만 결정론적으로 바꾸고 실제 codec/Core/Arbiter/recovery/ReleaseSink/observation 경로는 대체하지 않는다. pass·observe-only·blocking recovery의 구조화된 timeline과 출력/terminal 단일성·raw-free invariant가 관측되기 전에는 실제 의미 필터 구현으로 넘어가지 않는다.
- 관련 경로: `packages/go/streamgate`, `apps/edge/internal/openai`, `apps/edge/internal/service`, `apps/node/internal/adapters/openai_compat`, `apps/node/internal/runtime`, `packages/go/config`, [openai-compatible-api.md](../../../../agent-contract/outer/openai-compatible-api.md), [Stream Evidence Gate Core](../../../archive/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md)
- 표준선(선택): `provider_length_gate`는 provider가 한 attempt의 output cap에 도달한 terminal reason만 caller-neutral하게 판정한다. managed profile에서는 그 terminal을 외부 오류로 전달하지 않고 Core의 stream-open continuation을 요청한다. provider attempt cap은 작은 운영 단위로 두고, IOP의 논리 trajectory는 original request와 assistant prefix를 조립한 뒤 측정한 rebuilt prompt와 reserve를 뺀 context window 여유, 그리고 caller가 명시한 논리 output cap까지만 확장한다. provider attempt cap은 이와 별개인 내부 운영 단위다. provider가 assistant prefill을 지원하면 우선 사용하고, 지원하지 않으면 고정 internal continuation directive로 같은 assistant prefix를 재구성한다. 이는 일반 오류 재시도와 다른 progress continuation이므로 Core의 fault recovery 3회 cap을 소비하지 않으며, context 여유·취소·완성 tool call·side effect·미완성 fragment 실패에서는 final terminal로 수렴한다. context 여유 또는 caller logical cap 소진의 경우에만 endpoint가 지원하는 logical `length` terminal과 단일 `[DONE]`을 한 번 전달하며, attempt 중간 `length`는 전달하지 않는다.
- 표준선(선택): 반복루프 필터는 `rolling_window` passthrough consumer이며 이미 흘린 정상 prefix를 버리지 않는다. 반복 감지 시 continuation directive/온도 후보/반복 span만 반환하고, Core가 committed look-behind/release cursor를 보존해 current attempt abort, 고정 영어 지시문을 포함한 endpoint별 rebuild와 cycle별 single re-admission을 수행한다. 새 attempt의 response-start/role과 이미 보낸 prefix는 downstream에 중복하지 않는다.
- 표준선(선택): 기본 streaming filter는 provider 출력 전체를 buffer하지 않는다. Core가 response-start staging, rolling pending/look-behind, active filters single-flight evaluation과 all-complete Arbiter를 소유한다. schema처럼 전체 결과가 필요한 명시적 `terminal_gate`만 hard bound 안에서 content를 terminal까지 보류하며, 시간 경과는 어느 mode에서도 release 조건이 아니다.
- 표준선(선택): rolling release는 `pending -> approved -> delivered`를 분리한다. 첫 eligible delta부터 threshold/terminal까지의 monotonic 수집 시간만 delivery pacing 기준으로 고정하고 prefill·filter 평가 시간은 제외한다. all-complete pass 전에는 scheduler에 넣지 않으며, 승인 뒤에는 synchronous whole-batch release나 `ReleaseSafe` 내부 sleep 대신 bounded 비동기 queue와 frame tick 누적 rune quota를 사용한다. 이전 approved window 전달과 다음 provider window 수집을 겹치고 queue full에서만 backpressure를 건다. structural/tool event는 원자성과 순서를 보존하고 terminal은 drain 뒤 한 번만 전달하며 cancel·sink failure는 timer와 provider ownership을 함께 정리한다.
- 표준선(선택): provider 오류 filter는 `filters[] = [{ code, message }]`만 사용해 `exact_replay` intent를 반환한다. Core는 staged response-start를 포함해 `transport_uncommitted`인지 판정하고 Tool Call validation과 최초 실행 제외 request-local 최대 3회를 공유한다. exact/continuation/schema는 Core의 request 전체 `max_recovery_attempts_total` 안에서만 실행되고 cap 소진을 다른 strategy로 우회하지 않는다. stream-open 뒤에는 exact replay하지 않으며 current attempt abort가 끝난 뒤에만 기존 provider-pool admission을 다시 거친다. provider 선택은 pool 정책에 맡기고 filter는 retry loop/budget/snapshot/rebuild/submit을 소유하지 않는다.
- 표준선(선택): Chat/Responses ingress snapshot과 repair 결과는 Core의 기본값/절대 상한 16 MiB `max_ingress_snapshot_bytes` 안에서 OpenAI JSON raw body 하나를 canonical source로 보존한다. handler는 body를 읽기 전에 limit를 적용하고 typed view/rebuild 임시 allocation까지 retained bytes에 계상한다. limit 초과는 provider dispatch와 recovery budget 소비 전에 fail-closed하고 raw request를 filter/관측 로그에 남기지 않는다.
- 표준선(선택): 반복루프 필터는 텍스트 n-gram/문단 반복뿐 아니라 tool/action 반복도 본다. action fingerprint는 `tool name + normalized args`를 안정적으로 정규화해 만들고, 로그/metric label에는 raw args나 secret 가능 문자열을 넣지 않으며 fingerprint hash, action 종류, 반복 횟수만 남긴다.
- 표준선(선택): 한국어 장문 출력은 결정론적 single-stream 반복 fixture로 고정할 회귀 축이다. 2026-07-22~23 dev `ornith:35b` smoke에서는 반복이 확인되지 않았고 downstream release 전 llama-server HTTP 500 `Failed to parse input at pos`만 관측됐다. 구현 검증은 UTF-8 multi-byte chunk 경계와 반복 문단을 가진 결정론적 fixture를 필수로 두고, dev `ornith:35b`에서는 최소 3회 capacity+1 다회 stream smoke로 실제 재발을 관찰한다. live smoke 원문은 ignored run artifact에만 보관하고, 반복이 제한 횟수 안에 나오지 않으면 `not_reproduced`로 기록해 결정론적 guard 판정을 대체하지 않는다.
- 표준선(선택): request-history anchor guard의 source of truth는 현재 incoming Chat Completions `messages`다. user와 assistant provenance를 분리하고 user 입력에 같은 fingerprint가 있으면 assistant-only로 단정하지 않는다. caller가 assistant reasoning을 history에 재전송하지 않으면 해당 channel의 과거 반복을 만들어내지 않으며, 명시 conversation identity가 없는 MVP에서는 별도 TTL lineage를 추정하지 않는다.
- 표준선(선택): progress 판정은 validator 모델 없이 incoming history에 이미 완료된 tool/action fingerprint와 result/error hash, terminal 상태만 사용한다. 현재 response의 tool result는 아직 존재하지 않으므로 progress 근거로 사용하지 않는다. history sanitation, live reasoning dedupe, no-progress repair의 활성 조합은 D01 결정 후 filter policy로 고정한다.
- 표준선(선택): incident 회귀 증거는 [2026-07-16 Pi/Ornith cross-request history anchor](../../../sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/evidence/2026-07-16-pi-ornith-cross-request-history-anchor.log)처럼 SDD와 함께 보존한다. exact anchor는 비민감 예시로 명시 승인된 경우만 남긴다. 원문 기록과 별개인 일반 운영 로그에는 fingerprint hash, role/channel, repeat/progress/decision만 기록한다.
- 표준선(선택): tool/action 반복은 단일 stream 안에서만 판단하지 않는다. 현재 incoming `messages` 안의 assistant tool call과 완료된 tool result, 현재 provider tool call delta, consecutive 동일 fingerprint, 동일/no-progress tool result, threshold를 함께 만족해야 한다. 명시적 conversation identity가 추가되기 전에는 request 외 TTL/session state를 보충 근거로 사용하지 않는다. action guard가 활성화된 tool call delta는 full fingerprint 판정 전까지 해당 tool call fragment만 hold하며 전체 content stream을 buffer하지 않는다. `git status`, `ls`, polling처럼 정상적으로 반복될 수 있는 read-only action은 model/provider/tool별 threshold/allow policy로 조정하고, dev-corp 초기 적용은 observe-only evidence를 먼저 남긴 뒤 guard mode로 승격한다.
- 표준선(선택): IOP 필터는 caller-neutral OpenAI-compatible contract만 소비한다. Pi는 incident evidence와 선택적 field smoke 중 하나이며, Codex나 다른 SDK/curl caller와 동일한 protocol fixture를 통과해야 한다. caller별 보조 guard는 이 Milestone의 IOP 구현과 분리한다.
- 표준선(선택): schema 출력 계약은 `metadata.scheme` 하나로 표현하고 wrapper/options를 추가하지 않는다. 계약이 있으면 streaming 요청보다 contract validation을 우선하며, 검증 전 content delta를 흘리지 않는다.
- 표준선(선택): `metadata.scheme`은 JSON schema로 간주하며 IOP가 마지막 user message에 출력 계약 블록을 append해 provider로 전달한다.
- 표준선(선택): 이번 적용 범위는 `/v1/chat/completions`와 `/v1/responses`다. 두 endpoint codec은 response-start를 포함한 raw chunk/item parsing과 lossless `RequestRebuilder`를, Edge는 `AttemptDispatcher`/`AttemptController`/`ReleaseSink`를 제공한다. Core는 hold/evaluation/arbitration/commit/recovery를 소유하며 concrete provider/model/auth는 기존 admission이 계산한다. Claude/agent-family codec은 이번 소비 Milestone 범위에 포함하지 않는다.
- 구현 접점: `chat_handler.go`/`responses_handler.go`의 unbounded request `io.ReadAll`을 host pre-read limiter로 감싸고, `writeProviderTunnelResponse`의 response-start flush와 `streamChatCompletion`의 opening role write를 `ReleaseSink` staging으로 옮긴다. `buffered_sse.go`/`completeChatCompletion`의 기존 Tool Call validation retry는 공통 Coordinator로 이관하며 기존 loop와 새 loop를 동시에 활성화하지 않는다.
- 표준선(선택): 출력 검증 filter는 Core의 Go `Filter` interface와 shared helper를 구현한다. 모든 filter는 동일 immutable `FilterContext`/`EvidenceBatch`를 받고, 모델/환경/provider별 enablement와 병렬 실행/all-complete barrier는 Core Registry/Coordinator가 일관되게 관리한다.
- 표준선(선택): optional online filter가 비활성화된 모델은 pure passthrough로 처리할 수 있지만, caller가 `metadata.scheme`처럼 필수 계약을 요청했는데 해당 filter가 비활성화된 모델은 silent passthrough가 아니라 unsupported/400으로 거부한다.
- 표준선(선택): raw tunnel provider를 normalized provider execution 경로로 강제 전환하지 않는다. 기존 provider-pool이 선택한 tunnel/normalized path를 유지하고, 각 path adapter가 provider output을 같은 Core normalized event로 변환해 활성 [OpenAI-compatible API 계약](../../../../agent-contract/outer/openai-compatible-api.md)의 response validation 경계에 전달한다. Chronos가 소유하는 agent·terminal·workspace response protocol은 IOP 범위에 두지 않는다.
- 표준선(선택): 이 Milestone은 별도 오류 수정 플랫폼이 소비할 수 있는 raw-free terminal code/cause/`FilterObservation`을 내보내는 경계까지만 소유한다. 사건 지문·중복 집계·소스/커밋 연결·LLM 분석·수정 제안·프로젝트 작업 문서·사용자 승인·변경 요청·병합·배포·재발 확인은 별도 브랜치 대화에서 범용 프로젝트로 구체화한다.
- 표준선(선택): 이 consumer는 `repeat_guard`, `assistant_history_anchor`, `provider_error_filter`, `contract_schema`, `provider_length_gate` stable filter/rule id와 의미 판정·typed intent, endpoint codec/Rebuilder/host adapter만 소유하고 stable id와 sanitized fingerprint/count/offset을 Core `FilterObservation`에 제공한다. Core `RecoveryPlan`과 strategy/request-total cap, bounded ingress snapshot을 사용하고 raw stream buffer, 공통 request snapshot/rebuild, retry loop, 공개 오류 사슬 직렬화를 중복 구현하지 않는다. filter/prepare/rebuild 실패는 sanitized `FailureCauseChain`으로 전달하고 Chat/Responses host가 endpoint별 외부 오류 하나만 직렬화한다.
- 큐 배치: [에이전트 작업성 중심 저장소 구조 리팩터링](../../../archive/phase/automation-runtime-bridge/milestones/agent-readable-repository-refactor.md) 뒤, [OpenAI-compatible Incomplete Tool Call Syntax Gate](openai-compatible-incomplete-tool-call-syntax-gate.md) 앞
- 선행 작업: [Stream Evidence Gate Core](../../../archive/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md)
- 완료 기반: [OpenAI-compatible Tool Call Boundary Hardening](../../../archive/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-tool-call-boundary-hardening.md), [OpenAI-compatible Raw Tunnel 기반](../../../archive/phase/routing-policy-model-orchestration/milestones/openai-compatible-raw-tunnel-sideband-passthrough.md)
- 후속 작업: 단계 호출과 검증 최적화 MVP, Tool Call 판정 모델 Gate 리뷰
- 확인 필요: 없음
