# Milestone: [judge-01] LLM 판별 기반 Missing Tool Call 재시도 Gate

## 위치

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

## 목표

Pi/dev-corp 같은 tool-bearing OpenAI-compatible 요청에서 provider가 tool 사용 의도를 reasoning했지만 실제 tool call 없이 `stop` 또는 빈 응답으로 종료하는 케이스를 어떻게 다룰지 정책으로 정의한다.
runtime-only 패턴 검출만으로는 최종 답변과 missing tool-call을 안정적으로 구분할 수 없으므로, [Stream Evidence Gate Core](stream-evidence-gate-core.md)의 release barrier 위에서 LLM judge와 buffered retry 후보를 재검토한다.
이 스케치는 "언제 멈추지 말아야 하는가"와 "언제 원응답을 그대로 종료로 인정해야 하는가"를 닫는 기준이 정해질 때까지 구현 계획과 코드 구현을 시작하지 않는다.

## 상태

[스케치]

## 승격 조건

- [ ] LLM judge를 호출할 입력 조건을 확정한다. 예: `tools[]` 존재, `tool_choice != none`, native tool call 없음, text tool-call 후보 없음, terminal finish 도달, downstream으로 아직 bytes를 내보내지 않은 buffered 상태. judge는 이 조건을 만족한 attempt의 eligible terminal epoch에서 최대 1회만 호출하고 text/reasoning delta나 rolling evidence batch마다 호출하지 않는다.
- [ ] judge 판정 라벨과 action을 확정한다. 예: `final_ok`, `missing_tool_call`, `indeterminate` 각각에 대해 원응답 통과, 내부 재요청, 오류 반환, 관측 로그만 남김 중 무엇을 할지 정의한다.
- [ ] "멈추어선 안 되는 조건"과 "정상 종료로 인정할 조건"을 사용자-visible 답변, 빈 content, reasoning-only stop, malformed tool-call 실패 이후 응답으로 나누어 정의한다.
- [ ] streaming에서 어느 지점까지 buffer하고, buffer 한도/latency 한도 초과 시 fail-open 또는 fail-closed 중 어떤 정책을 적용할지 결정한다.
- [ ] judge가 반환할 typed `RecoveryIntent`와 corrective prompt directive를 확정한다. 최대 횟수, recursive same-plan 금지, tool side-effect와 strategy별 commit eligibility, request rebuild/dispatch는 공통 RecoveryPlan Coordinator에 위임한다. missing-tool retry가 response replacement라면 `transport_uncommitted` terminal gate 안에서만 허용한다.
- [ ] request 전체 `max_judge_invocations_total`, 호출별 hard deadline, judge timeout, invalid JSON, provider 오류, judge와 deterministic observation 충돌 같은 불확실 케이스의 fallback 정책을 확정한다. judge invocation cap은 Core의 최초 실행 제외 기본값/절대 상한 3회 `max_recovery_attempts_total`과 별도로 request 시작 시 고정하고 어느 쪽이든 먼저 소진되면 추가 judge/recovery를 금지한다.
- [ ] OpenAI-compatible API/stream/retry 계약 변경으로 승격할 때 SDD 필요 여부와 후속 구현 Milestone 분리 방식을 결정한다.

## 구현 잠금

- 상태: 잠금
- SDD: 불필요
- SDD 문서: 없음
- SDD 사유: 현재 Milestone은 정책 스케치이며, buffered stream/API/retry 계약 구현으로 승격할 때 SDD 필요 여부를 재판정한다.
- 잠금 해제 조건: 아래 체크리스트
  - [ ] 승격 조건의 종료/재시도 정책 질문이 해소되어 있다.
  - [ ] judge/retry가 적용되는 요청 경계와 제외 경계가 문서화되어 있다.
  - [ ] 구현 Milestone으로 넘길 기능 Task와 SDD gate 필요 여부가 정리되어 있다.
- 결정 필요: 아래 체크리스트
  - [ ] LLM judge를 blocking gate로 둘지 advisory signal로만 둘지 결정한다.
  - [ ] 현재 모델/provider 제약 안에서 judge 호출을 구성할지, 별도 judge route가 준비될 때까지 구현을 잠글지 결정한다.
  - [ ] `indeterminate` 판정을 원응답 통과, 사용자-visible 오류, 내부 retry 중 어느 쪽으로 처리할지 결정한다.
  - [ ] streaming UX를 위해 buffered validation을 허용할 요청 범위와 latency budget을 결정한다.
  - [ ] retry 실패를 Pi/agent client가 구분할 수 있게 어떤 error/metadata/로그로 노출할지 결정한다.
  - [ ] 이 gate를 dev-corp/Pi smoke 전용 opt-in으로 시작할지, model group/provider policy로 확장할지 결정한다.

## 범위

- OpenAI-compatible `/v1/chat/completions` provider route 중 `tools[]` 또는 tool-capable client 요청
- provider 응답이 terminal 상태로 끝났지만 native tool call이 없고, content가 비었거나 reasoning에 tool 사용 의도가 남은 케이스
- LLM judge 입력/출력 계약, 판정 라벨, timeout/오류/fallback 정책 후보
- downstream으로 첫 content/SSE를 보내기 전 buffer하는 validation path와 내부 재요청 후보
- model, IOP, Pi 중 어느 레이어의 종료인지 사후 관측할 수 있는 로그/metadata 후보

## 기능

### Epic: [missing-tool-gate] Missing Tool Call Gate Policy

tool이 필요한지 runtime만으로 확정할 수 없는 종료 응답의 케이스 분류, LLM judge 계약, terminal buffering 경계를 묶는다.

- [ ] [case-taxonomy] 정상 최종 답변, reasoning-only stop, 빈 content stop, malformed tool-call 이후 무툴 종료, 반복 출력 중단 이후 응답을 구분하는 케이스 표가 작성되어 있다.
- [ ] [judge-contract] `missing_tool_call_judge`가 [Stream Evidence Gate Core](stream-evidence-gate-core.md)의 `Filter` interface를 구현하도록 judge 입력 필드, eligible terminal trigger, attempt당 1회/request 전체 invocation cap, hard deadline, strict JSON 출력 라벨, confidence 사용 여부, `FilterDecision`/typed RecoveryIntent와 invalid output fail policy가 정리되어 있다. judge filter는 request mutation이나 retry submit을 소유하지 않는다.
- [ ] [stream-buffer] [Stream Evidence Gate Core](stream-evidence-gate-core.md)의 hold/terminal/commit mechanics를 소비해 missing tool-call judge가 전체 terminal 결과를 요구하는 explicit `terminal_gate`와 `max_buffer_runes`를 선언하도록 확정한다. text/reasoning delta와 rolling evidence batch에서는 judge를 호출하지 않고 terminal 전에는 `deferred_by_requirement`로 남긴다. blocking/observe-only failure policy와 hard-limit overflow를 정하고 Core tail/commit을 중복하지 않는다.

### Epic: [missing-tool-recovery] Missing Tool Call Recovery

judge 결과를 공통 recovery budget과 운영 관측으로 연결하는 복구·승격 산출물을 묶는다.

- [ ] [retry-loop] `missing_tool_call_judge`는 corrective prompt 의미를 typed directive로 가진 RecoveryIntent만 반환하고, 내부 재요청 횟수, request 전체 `max_judge_invocations_total`, recursive judge guard, tool side-effect 중복 방지, request rebuild/dispatch, 최종 실패는 [Stream Evidence Gate Core](stream-evidence-gate-core.md)의 strategy budget과 최초 실행 제외 기본값/절대 상한 3회 request 전체 recovery hard cap 안에서 공통 RecoveryPlan Coordinator가 처리하도록 정리되어 있다.
- [ ] [ops-signal] `missing_tool_call_judge` stable filter id, judge outcome, retry 여부, 최종 종료 원인과 sanitized evidence를 [Stream Evidence Gate Core](stream-evidence-gate-core.md)의 `FilterObservation` timeline으로 model/provider/run correlation에 연결할 관측 필드 후보가 정리되어 있다. judge input·원응답·tool args/result은 남기지 않는다.
- [ ] [promotion-plan] 정책 확정 후 `[계획]` 구현 Milestone과 SDD gate 필요 여부가 분리되어 있다.

## 완료 리뷰

- 상태: 없음
- 요청일: 없음
- 완료 근거: 스케치 Milestone이며 종료/재시도 정책과 구현 경계가 아직 확정되지 않았다.
- 검토 항목: 사용자 리뷰 통과, 승격 조건 충족, 후속 구현 Milestone 및 SDD gate 분리 여부
- agent-ui 상태 반영: 해당 없음
- 리뷰 코멘트: 없음

## 범위 제외

- 즉시 코드 구현 또는 dev-corp runtime 배포
- 모델 교체, provider 옵션 변경, Pi 설정 강제 변경
- runtime-only 문자열 패턴 검출만으로 tool 필요 여부를 최종 확정하는 방식
- 이미 downstream으로 보낸 SSE/content를 되돌리거나 덮어쓰는 방식
- LLM judge가 tool call을 합성하거나 tool을 대신 실행하는 방식
- text/reasoning delta 또는 rolling evidence batch마다 LLM judge를 호출하는 방식과 invocation/deadline hard limit이 없는 judge fan-out
- 무제한 재시도, recursive judge loop, 모든 답변을 tool-call 필요 응답으로 간주하는 정책

## 작업 컨텍스트

- 관련 경로: `apps/edge/internal/openai`, `apps/edge/internal/service`, `apps/node/internal/adapters/openai_compat`, [openai-compatible-api.md](../../../../agent-contract/outer/openai-compatible-api.md)
- 표준선(선택): runtime은 tool call 부재와 일부 malformed pattern은 확인할 수 있지만, 모든 최종 답변이 tool을 필요로 하는지 여부는 deterministic하게 판정하지 않는다.
- 표준선(선택): LLM judge가 필요하더라도 downstream bytes는 되돌리지 않는다. missing tool-call 부재 판정은 terminal에서만 확정하므로 [Stream Evidence Gate Core](stream-evidence-gate-core.md)의 bounded `terminal_gate`를 명시한다. terminal 전 deterministic preflight는 호출 후보를 좁히는 데만 쓰고 LLM judge를 실행하지 않으며, terminal gate의 hard bound와 blocking/observe failure policy를 이 Milestone에서 결정한다.
- 표준선(선택): `missing_tool_call_judge`는 precondition을 만족한 attempt의 terminal epoch에서 최대 1회만 평가한다. terminal 전 delta/rolling batch에는 실행하지 않으며 request 전체 invocation cap과 호출별 hard deadline이 확정되기 전에는 구현 Milestone으로 승격하지 않는다.
- 표준선(선택): judge filter는 직접 retry하지 않는다. 공통 Coordinator가 bounded strategy budget, 최초 실행 제외 기본값/절대 상한 3회의 request 전체 recovery hard cap과 same-plan recursion guard를 보장할 때만 구현 Milestone으로 승격한다.
- 표준선(선택): 이 consumer는 stable filter id `missing_tool_call_judge`, judge 의미 판정과 typed intent, sanitized judge outcome만 소유하고 stable id와 judge outcome을 Core `FilterObservation`에 제공한다. recovery가 승인되면 Core `RecoveryPlan`과 strategy/request-total cap, bounded ingress snapshot을 사용하며 raw stream buffer, request snapshot/rebuild, retry loop, 공개 오류 사슬 직렬화를 재구현하지 않는다. judge/preparer/rebuild 실패는 sanitized `FailureCauseChain`으로 전달하고 endpoint host가 외부 오류 하나만 직렬화한다.
- 선행 작업: [Stream Evidence Gate Core](stream-evidence-gate-core.md), [OpenAI-compatible 출력 검증 필터](openai-compatible-output-validation-filters.md)
- 후속 작업: 정책 확정 후 별도 implementation Milestone, [Tool Call 판정 모델 Gate 리뷰](tool-call-validator-model-gate-review.md)
- 확인 필요: `구현 잠금 > 결정 필요` 항목
