docs(roadmap): Node Provider 실행 Liveness 관측과 안전 복구 마일스톤을 추가한다

PHASE와 priority-queue에 새 마일스톤을 등록하고,
해당 마일스톤의 정의서와 SDD를 추가한다.
This commit is contained in:
toki 2026-07-30 04:50:31 +09:00
parent 0b21e0d512
commit 8deddc97eb
4 changed files with 250 additions and 19 deletions

View file

@ -54,6 +54,10 @@ Phase를 가로지르는 실제 다음 작업 선택은 [전역 마일스톤 실
- 경로: [provider-load-metrics-queue-dashboard](milestones/provider-load-metrics-queue-dashboard.md)
- 요약: provider별 capacity 사용률, in-flight, queue 적체, queue wait를 Prometheus time series와 Grafana dashboard로 노출해 시간대별 live 부하 분석을 가능하게 한다.
- [계획] Node Provider 실행 Liveness 관측과 안전 복구
- 경로: [node-provider-execution-liveness-recovery](milestones/node-provider-execution-liveness-recovery.md)
- 요약: Node가 provider-originated 진행 신호의 5분 무응답을 request stall로 판정하고 provider health와 local attempt fence를 별도 확정하며, ingress recovery owner가 미커밋 요청만 기존 공통 budget 안에서 재실행한다.
- [스케치] 요청 실행 로그와 Usage Ledger 기반
- 경로: [request-execution-log-usage-ledger-foundation](milestones/request-execution-log-usage-ledger-foundation.md)
- 요약: 사용자 요청 하나의 device/provider/model 선택, queue/dispatch/start/first-token/end 시간, token breakdown, status/error를 구조화된 실행 로그와 usage ledger로 남기는 로그 시스템 개편 후보를 스케치한다.

View file

@ -0,0 +1,99 @@
# Milestone: Node Provider 실행 Liveness 관측과 안전 복구
## 위치
- Roadmap: [ROADMAP.md](../../../ROADMAP.md)
- Phase: [PHASE.md](../PHASE.md)
## 목표
Node가 자신이 실행하는 normalized run과 provider raw tunnel의 provider-originated 진행 신호를 request 단위로 관측하고, 기본 5분 동안 의미 있는 진행이 없으면 request stall로 확정한다.
Node는 원 요청의 liveness와 provider 전체 health를 분리해 직접 점검하고 stalled attempt를 취소·fence하며, Edge의 ingress recovery owner는 전달받은 typed 결과와 response commit 상태를 기준으로 안전한 요청만 기존 공통 budget 안에서 bounded 재실행한다.
## 상태
[계획]
## 승격 조건
- 없음
## 구현 잠금
- 상태: 해제
- SDD: 필요
- SDD 문서: [SDD.md](../../../sdd/operational-observability-provider-management/node-provider-execution-liveness-recovery/SDD.md)
- SDD 사유: request liveness 상태 머신, provider health probe, timeout/config, typed failure, Edge-Node wire, cancel fencing과 bounded retry 계약을 함께 변경한다.
- 잠금 해제 조건: 아래 체크리스트
- [x] SDD 잠금이 해제되어 있다.
- [x] SDD 사용자 리뷰가 없거나 승인/해결되었다.
- [x] Acceptance Scenario가 Milestone 기능 Task와 연결되어 있다.
- [x] Evidence Map이 완료 시 `Roadmap Completion`과 최종 검증 evidence로 검증 가능하게 연결되어 있다.
- 결정 필요: 없음
## 범위
- Node의 `RunRequest``ProviderTunnelRequest` 실행을 감싸는 provider-neutral liveness observer와 per-attempt no-progress clock
- `delta`, `reasoning_delta`, provider response header/body/usage처럼 provider가 실제로 낸 진행 신호와 Node/Edge heartbeat·process/socket 생존 신호의 분리
- provider-first `nodes[].providers[].response_stall_timeout_ms` 설정. 생략/`0`은 `300000`, 양수는 provider별 override, 음수는 config 오류이며 request 전체 timeout·queue timeout과 CLI `response_idle_timeout_ms`를 대체하지 않는다.
- timeout 진입 시 target-aware `ProviderProber`를 이용한 bounded health probe, `request_stalled`, `provider_unhealthy`, `health_unknown` 분류와 Edge connection generation에 묶인 monotonic observation sequence를 가진 fresh successful probe 기반 health 회복
- stalled attempt의 cancel, exactly-once terminal, late event drop와 run/attempt emission-authority fencing. fence는 remote provider의 내부 종료를 추정하는 값이 아니라 Node가 old attempt의 출력 권한을 철회하고 로컬 transport/execution close를 완료했는지를 나타낸다.
- Edge의 immutable dispatch-provider binding과 provider runtime health overlay. config health를 직접 덮어쓰지 않고 stale/mismatched probe evidence를 거부한다.
- response commit/replay owner가 있는 ingress에서만 기존 request-local recovery coordinator와 공통 fault budget을 이용하는 bounded 재실행. OpenAI-compatible 경로는 StreamGate commit boundary/recovery coordinator를 재사용하고 별도 liveness retry loop나 전용 기본 횟수를 만들지 않는다.
- recovery owner가 없는 normalized run/raw tunnel surface의 typed terminal fallback
- Node의 request/provider 관측 metric·structured log와 Edge의 commit/recovery 결정 log를 분리한 low-cardinality 운영 증거 및 기존 `ProviderSnapshot` health projection
## 기능
### Epic: [liveness-observer] Node 실행 Liveness Observer
Node가 provider 실행에 가장 가까운 위치에서 진행 증거와 무응답 시간을 판정하고 health probe 결과를 별도 축으로 분류하는 capability를 묶는다.
- [ ] [activity-contract] normalized `RuntimeEvent`와 raw `ProviderTunnelFrame`의 provider-originated activity를 하나의 진행 계약으로 정규화하고 provider-level `response_stall_timeout_ms`의 기본 5분 no-progress clock을 적용한다. 더 이른 request hard deadline은 기존 failure로 유지하며 구현과 함께 Agent Runtime·Edge Config/Refresh 계약을 갱신한다. 검증: config default/override/negative validation과 fake clock 기반 run/tunnel 테스트에서 text·reasoning·response start/body/usage가 clock을 갱신하고 Node/Edge heartbeat, socket/process 생존, 빈 frame은 갱신하지 않으며 hard deadline을 stall로 재분류하지 않는다.
- [ ] [stall-watchdog] no-progress threshold에 도달한 attempt를 단 한 번 `response_stalled`로 전환하고 cancel·exactly-once terminal·late-event fencing을 Node pipeline에서 수행한다. `attempt_fence=confirmed`는 old attempt의 Node emission authority와 로컬 transport/execution ownership이 닫혔음을 뜻하고, `unconfirmed`이면 자동 재실행을 금지한다. 검증: threshold 경계, timer/event/cancel race, close success/failure와 terminal 이후 late delta/frame에서 terminal과 fence 결과가 정확히 한 번 확정된다.
- [ ] [health-classification] stalled request와 독립된 bounded target-aware provider probe를 실행해 `available`, `unavailable`, `unknown`을 각각 request-stalled/provider-unhealthy/health-unknown으로 분류한다. Node는 adapter/target과 connection-scoped monotonic observation sequence를 내고, Edge는 수신 connection generation 및 immutable dispatch의 provider identity와 일치하는 fresh evidence만 runtime health overlay에 적용한다. 검증: probe 성공·target 없음·network error·unsupported prober·stale connection/sequence·identity mismatch·unhealthy 후 recovery fixture가 원 요청의 내부 추론 상태를 추정하지 않고 기대 분류와 복구 전이를 낸다.
### Epic: [recovery-handoff] Edge 복구 Handoff와 Attempt Fencing
Node가 확정한 stall evidence를 Edge가 안전한 재실행 또는 terminal 결과로 수렴시키는 capability를 묶는다.
- [ ] [failure-handoff] normalized run과 raw tunnel이 같은 stable `response_stalled` failure code, provider health 분류, idle duration, attempt identity, fence 결과와 observation sequence를 전달하고 구현과 함께 Agent Runtime·Edge-Node Runtime Wire 계약을 갱신한다. `Failure.retryable`은 confirmed local fence에 대한 capability hint일 뿐 재실행 승인이 아니며, Node terminal에는 Node가 알 수 없는 `recovery_eligible`을 싣지 않는다. Edge는 immutable dispatch binding을 검증하고 old attempt lease를 정확히 한 번 정리한다. 검증: Edge-Node wire round-trip과 normalized/tunnel lifecycle 테스트에서 secret/raw output 없이 동일 분류가 보존되고 provider identity mismatch가 health projection을 바꾸지 않는다.
- [ ] [bounded-retry] OpenAI-compatible host가 typed stall을 기존 StreamGate recovery intent/cause로 변환하고, `transport_uncommitted`, caller cancel, tool/비가역 side effect, confirmed attempt fence와 공유 request-level recovery budget을 함께 평가해 새 run/attempt identity로 재실행한다. stalled provider는 해당 recovery cycle에서 우선 제외하고, 대체 후보가 없으며 probe가 `available`일 때만 같은 provider 후보를 허용한다. 별도 liveness retry counter를 만들지 않고 recovery owner가 없는 surface, post-commit, unconfirmed fence와 budget 소진은 terminal로 끝낸다. 검증: healthy request stall, unhealthy provider failover, unknown probe, same-provider-only, no-recovery-owner, post-commit, unconfirmed fence와 shared-budget exhaustion fixture에서 중복 dispatch/terminal이 없다.
### Epic: [liveness-operations] Liveness 운영 증거
request stall과 provider health를 운영자가 서로 다른 원인 축으로 확인할 수 있는 관측 capability를 묶는다.
- [ ] [ops-evidence] Node는 stall count/duration, fence와 probe result를, Edge recovery owner는 commit state, eligibility와 recovery result를 bounded label metric/structured log로 남긴다. provider-unhealthy와 fresh provider recovery는 기존 provider health projection의 runtime overlay에 반영한다. 검증: deterministic run/tunnel smoke에서 request-stalled-but-provider-available, provider-unhealthy, stale evidence rejection과 recovered가 구분되고 request/session/raw prompt/response가 metric label이나 일반 로그에 포함되지 않는다.
## 완료 리뷰
- 상태: 없음
- 요청일: 없음
- 완료 근거: 계획 Milestone이며 기능 Task가 아직 충족되지 않았다.
- 검토 항목: 모든 기능 Task 검증, SDD Evidence Map, exactly-once terminal/lease release와 bounded retry evidence를 확인한다.
- 리뷰 코멘트: 없음
## 범위 제외
- `agent-task` Python dispatcher나 Node를 거치지 않는 직접 Pi/provider 호출의 감시·재시작
- standalone `iop-agent` 또는 개별 agent가 자체 watchdog을 소유하는 구조
- provider가 별도 reasoning/progress event를 내지 않을 때 내부에서 실제 추론 중인지 추정하는 기능
- 반복, tool-call syntax, schema, 출력 품질 같은 content filter 판정
- queue wait timeout, request 전체 hard timeout, provider capacity/routing score의 의미 변경
- CLI profile의 `response_idle_timeout_ms` completion heuristic 재해석
- StreamGate와 별개인 Edge/Node liveness retry coordinator 또는 전용 retry budget
- 외부 응답 또는 비가역 side effect가 이미 커밋된 attempt의 blind replay
- provider runtime launch/restart, credential/login, 모델 다운로드와 lifecycle 자동화
## 작업 컨텍스트
- 관련 경로: `apps/node/internal/node`, `packages/go/agentruntime`, `packages/go/config`, `apps/edge/internal/service`, `apps/edge/internal/openai`, `packages/go/streamgate`, `proto/iop/runtime.proto`
- 표준선(선택): liveness timer, local attempt fence와 probe orchestration은 Node가 소유한다. 공통 runtime은 provider-neutral activity/failure/probe 계약만 제공한다. Edge service는 provider lease·admission·routing을 소유하고 ingress별 recovery host가 response commit·replay eligibility를 소유하며 Control Plane과 agent는 실행 감시자가 아니다.
- 표준선(선택): reasoning 여부는 provider가 `reasoning_delta` 또는 동등한 명시 progress를 낸 경우에만 관측 가능하다. socket/process/heartbeat가 살아 있다는 사실이나 독립 health probe 성공을 원 요청의 추론 진행 증거로 사용하지 않는다.
- 표준선(선택): timeout 진입은 monotonic하다. threshold 뒤 도착한 old attempt event는 새 progress로 되살리지 않고 attempt generation으로 drop한다.
- 표준선(선택): OpenAI-compatible 자동 재실행은 [OpenAI-compatible 출력 검증 필터](../../knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md)가 채택하는 StreamGate commit boundary와 request-local recovery coordinator를 재사용하고 공통 fault budget을 소비한다. 이 Milestone은 별도 기본 재시도 횟수를 추가하지 않는다.
- 큐 배치: 사용자 지정 순서가 없어 전역 실행 순서 끝에 추가한다.
- 선행 작업: 없음
- 후속 작업: [요청 실행 로그와 Usage Ledger 기반](request-execution-log-usage-ledger-foundation.md), [Provider 부하 메트릭과 Live Queue Dashboard](provider-load-metrics-queue-dashboard.md)
- 확인 필요: 없음

View file

@ -13,57 +13,57 @@
3. [OpenAI-compatible 출력 검증 필터](phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md)
OpenAI-compatible single-stream 반복과 incoming request history에 누적된 assistant 반복, JSON contract 검증/repair 경로를 안정화한다.
4. [OpenAI-compatible Incomplete Tool Call Syntax Gate](phase/knowledge-tool-optimization-extension/milestones/openai-compatible-incomplete-tool-call-syntax-gate.md)
4. [Provider-Device-Model Qualification 리포트와 Lifecycle 관리](phase/operational-observability-provider-management/milestones/provider-device-model-qualification-report.md)
provider/device/model별 compatibility, performance, quality, lifecycle 리포트 경계를 정리한다.
5. [OpenAI-compatible Incomplete Tool Call Syntax Gate](phase/knowledge-tool-optimization-extension/milestones/openai-compatible-incomplete-tool-call-syntax-gate.md)
terminal provider 응답의 incomplete tool-call syntax를 deterministic하게 판정한다.
5. [OpenAI-compatible Runtime Output Integrity Filter](phase/knowledge-tool-optimization-extension/milestones/openai-compatible-runtime-output-integrity-filter.md)
6. [OpenAI-compatible Runtime Output Integrity Filter](phase/knowledge-tool-optimization-extension/milestones/openai-compatible-runtime-output-integrity-filter.md)
terminal output invariant와 공통 filter/retry pipeline을 정의한다.
6. [LLM 판별 기반 Missing Tool Call 재시도 Gate](phase/knowledge-tool-optimization-extension/milestones/llm-judged-missing-tool-call-retry-gate.md)
7. [LLM 판별 기반 Missing Tool Call 재시도 Gate](phase/knowledge-tool-optimization-extension/milestones/llm-judged-missing-tool-call-retry-gate.md)
tool 사용 의도 누락 케이스를 LLM judge와 buffered retry 후보로 검토한다.
7. [Tool Call 판정 모델 Gate 리뷰](phase/knowledge-tool-optimization-extension/milestones/tool-call-validator-model-gate-review.md)
8. [Tool Call 판정 모델 Gate 리뷰](phase/knowledge-tool-optimization-extension/milestones/tool-call-validator-model-gate-review.md)
schema만으로 어려운 tool-call 후보에 validator 모델을 쓸지 검토한다.
8. [Pi CLI Provider Integration](phase/automation-runtime-bridge/milestones/pi-cli-provider-integration.md)
9. [Pi CLI Provider Integration](phase/automation-runtime-bridge/milestones/pi-cli-provider-integration.md)
Pi를 Node CLI provider 실행 후보에 추가하고 OpenAI-compatible route smoke로 안정화한다.
9. [CLI Agent Group Grade Routing](phase/automation-runtime-bridge/milestones/cli-agent-group-grade-routing.md)
10. [CLI Agent Group Grade Routing](phase/automation-runtime-bridge/milestones/cli-agent-group-grade-routing.md)
lane/grade 파일명과 `metadata.agent_group.task_file` 기반 CLI agent group 라우팅 계약을 정리한다.
10. [에이전트 작업 루프 오케스트레이션 MVP](phase/automation-runtime-bridge/milestones/agent-workflow-loop-orchestration-mvp.md)
11. [에이전트 작업 루프 오케스트레이션 MVP](phase/automation-runtime-bridge/milestones/agent-workflow-loop-orchestration-mvp.md)
일반 사용자 요청을 direct/Plan/Milestone으로 분류하고, 사용자 agent의 tool call로 만든 작업 파일을 IOP가 읽어 다음 실행·리뷰·완료 단계까지 연결한다.
11. [Provider 사용량 알림과 운영 표면](phase/automation-runtime-bridge/milestones/provider-usage-notification-operations-surface.md)
12. [Provider 사용량 알림과 운영 표면](phase/automation-runtime-bridge/milestones/provider-usage-notification-operations-surface.md)
공통 runtime의 quota/status/failure event를 소비해 macOS·Desktop·후속 외부 채널에 전달하는 알림과 이력 표면을 스케치한다.
12. [단계 호출과 검증 최적화 MVP](phase/knowledge-tool-optimization-extension/milestones/knowledge-tool-validation-optimization.md)
13. [단계 호출과 검증 최적화 MVP](phase/knowledge-tool-optimization-extension/milestones/knowledge-tool-validation-optimization.md)
planner/generator/verifier 단계 호출과 runtime schema 검증 실행 모드를 스케치한다.
13. [원격 코딩/유지보수 작업 환경](phase/automation-runtime-bridge/milestones/remote-workspace-operations-environment.md)
14. [원격 코딩/유지보수 작업 환경](phase/automation-runtime-bridge/milestones/remote-workspace-operations-environment.md)
workspace-bound execution 기반 원격 코딩/유지보수 운영 경계를 스케치한다.
14. [Personal Local Edge 패키징과 배포 모드 프로파일](phase/personal-edge-packaging-deployment/milestones/personal-local-edge-deployment-profiles.md)
15. [Personal Local Edge 패키징과 배포 모드 프로파일](phase/personal-edge-packaging-deployment/milestones/personal-local-edge-deployment-profiles.md)
personal/server/fleet 배포 모드와 capability gate 경계를 스케치한다.
15. [요청 실행 로그와 Usage Ledger 기반](phase/operational-observability-provider-management/milestones/request-execution-log-usage-ledger-foundation.md)
16. [요청 실행 로그와 Usage Ledger 기반](phase/operational-observability-provider-management/milestones/request-execution-log-usage-ledger-foundation.md)
요청별 provider/model 선택, timing, token, status/error를 구조화된 ledger로 남기는 기반을 스케치한다.
16. [Update Plane 안정 프로토콜](phase/update-plane-self-update-foundation/milestones/update-plane-stable-protocol.md)
17. [Update Plane 안정 프로토콜](phase/update-plane-self-update-foundation/milestones/update-plane-stable-protocol.md)
hello/status, manifest, command, event, recovery 최소 계약을 스케치한다.
17. [Host-local Manager 기반 자체 업데이트](phase/update-plane-self-update-foundation/milestones/host-local-manager-self-update.md)
18. [Host-local Manager 기반 자체 업데이트](phase/update-plane-self-update-foundation/milestones/host-local-manager-self-update.md)
manager/updater의 release staging, 검증, restart, rollback 실행 모델을 정리한다.
18. [Edge/Node 롤아웃과 복구 정책](phase/update-plane-self-update-foundation/milestones/edge-node-rollout-recovery-policy.md)
19. [Edge/Node 롤아웃과 복구 정책](phase/update-plane-self-update-foundation/milestones/edge-node-rollout-recovery-policy.md)
Edge/Node rolling update, 실패/재연결/rollback 보고 정책을 스케치한다.
19. [Provider Runtime 설정과 모델 획득 오케스트레이션](phase/operational-observability-provider-management/milestones/provider-runtime-model-acquisition-orchestration.md)
20. [Provider Runtime 설정과 모델 획득 오케스트레이션](phase/operational-observability-provider-management/milestones/provider-runtime-model-acquisition-orchestration.md)
provider runtime launch/profile, model download/cache/verification 경계를 스케치한다.
20. [Provider-Device-Model Qualification 리포트와 Lifecycle 관리](phase/operational-observability-provider-management/milestones/provider-device-model-qualification-report.md)
provider/device/model별 compatibility, performance, quality, lifecycle 리포트 경계를 정리한다.
21. [Provider 입력 컨텍스트 선택과 축소](phase/knowledge-tool-optimization-extension/milestones/request-context-assembly-optimization.md)
provider dispatch 전에 무관한 과거 요청-답변 단위를 제거하고, 유지한 답변·tool/search 결과 안에서도 필요한 문단·코드 블록·구간만 남기는 입력 context 최적화를 스케치한다.
@ -84,3 +84,6 @@
27. [IOP Hot Path One-shot 실행 경로](phase/knowledge-tool-optimization-extension/milestones/iop-hot-path-one-shot-execution.md)
외부 `model=iop` 요청을 Gemini 3.6 Flash와 RTX 5090 `ornith-fast`의 bounded one-shot 경로로 처리해 최대 속도와 실사용 품질의 균형을 맞춘다.
28. [Node Provider 실행 Liveness 관측과 안전 복구](phase/operational-observability-provider-management/milestones/node-provider-execution-liveness-recovery.md)
Node가 5분간 provider 진행이 없는 request를 health와 분리 판정하고 local attempt를 fence한 뒤 기존 recovery owner가 안전한 요청만 공통 budget 안에서 재실행한다.

View file

@ -0,0 +1,125 @@
# SDD: Node Provider 실행 Liveness 관측과 안전 복구
## 위치
- Milestone: [Milestone 문서](../../../phase/operational-observability-provider-management/milestones/node-provider-execution-liveness-recovery.md)
- Phase: [PHASE.md](../../../phase/operational-observability-provider-management/PHASE.md)
## 상태
[승인됨]
## SDD 잠금
- 상태: 해제
- 사용자 리뷰: 없음
- 잠금 항목: 없음
## 문제 / 비목표
- 문제: 현재 Node 실행 경로에는 provider request가 terminal 없이 멈췄을 때 request liveness를 판정하고 provider 전체 health를 별도 점검한 뒤 old attempt를 fence하여 Edge 복구로 넘기는 공통 pipeline이 없다. CLI persistent idle은 일부 profile에서 `idle-timeout`을 정상 complete로 취급하고, Node heartbeat·process/socket 생존과 독립 provider probe 성공만으로는 원 요청이 실제 추론 중인지 알 수 없다.
- 비목표:
- Node를 거치지 않는 Python dispatcher, 직접 Pi/provider 호출과 standalone `iop-agent` 감시
- content 반복, tool-call/schema/품질 검증과 queue wait 정책 변경
- provider가 progress event를 내지 않을 때 내부 reasoning 상태 추정
- provider runtime restart, credential/login 또는 model lifecycle 자동화
## Source of Truth
| 영역 | 기준 | 메모 |
|------|------|------|
| Roadmap | [Milestone 문서](../../../phase/operational-observability-provider-management/milestones/node-provider-execution-liveness-recovery.md) | 목표, 기능 Task와 완료 범위 |
| Code | `apps/node/internal/node/run_handler.go`, `runtime_sink.go`, `tunnel_handler.go` | per-run/tunnel observer, cancel과 terminal fencing owner |
| Code | `packages/go/agentruntime/types.go`, `failure.go` | provider-neutral activity, `ProviderProber`와 typed failure 계약 |
| Code | `apps/edge/internal/service` | immutable dispatch-provider binding, provider lease·admission·routing owner |
| Code | `packages/go/streamgate`, `apps/edge/internal/openai` | OpenAI response commit, request-local recovery budget, attempt abort/rebuild/dispatch owner |
| Config | `packages/go/config`, `configs/edge.yaml` | provider-first liveness timeout과 Node payload source of truth |
| Contract | [Agent Runtime 계약](../../../../agent-contract/inner/agent-runtime.md) | provider run/event/probe/failure 의미 |
| Contract | [Edge-Node Runtime Wire 계약](../../../../agent-contract/inner/edge-node-runtime-wire.md) | normalized run/tunnel terminal과 cancel ordering |
| Contract | [Edge Config/Refresh 계약](../../../../agent-contract/inner/edge-config-runtime-refresh.md) | provider liveness 설정과 generation isolation |
| User Decision | 2026-07-29 사용자 대화 | Node 관측 pipeline이 감시를 소유하고, 5분 이상 응답이 없으면 health 분류 후 안전한 요청을 재실행한다. |
## State Machine
| 상태 | 진입 조건 | 다음 상태 | 근거 |
|------|-----------|-----------|------|
| request/observing | Node가 run/tunnel attempt를 실행하고 start clock을 시작함 | request/observing / request/stall-detected / request/terminal | provider-originated activity, monotonic clock, terminal |
| request/observing | non-empty text/reasoning/response-start/body/usage 또는 명시 provider progress가 도착함 | request/observing | Node liveness observer가 last-progress를 갱신 |
| request/stall-detected | last-progress 이후 configured threshold가 지나고 terminal이 없음 | request/fencing + provider/probing | Node watchdog이 stall을 단 한 번 확정하고 cancel/close와 bounded probe를 시작 |
| request/fencing | Node가 old attempt의 emission authority를 철회하고 local transport/execution ownership을 닫음 | request/stalled (`attempt_fence=confirmed`) | late event를 drop할 수 있는 local generation fence |
| request/fencing | bounded cancel/close가 실패하거나 완료 여부를 확정할 수 없음 | request/terminal (`attempt_fence=unconfirmed`) | 중복 output/dispatch 위험 때문에 자동 재실행 금지 |
| provider/probing | target-aware probe가 `available`을 반환함 | provider/available | provider 전체는 available이지만 원 request stall은 유지 |
| provider/probing | probe가 `unavailable` 또는 명시 target unavailable을 반환함 | provider/unavailable | Node provider health evidence |
| provider/probing | prober 미지원, timeout 또는 판정 불가 오류 | provider/unknown | provider health를 추정하지 않는 fallback |
| provider/unavailable | current Edge connection에서 같은 provider/adapter/target의 이후 probe가 더 큰 observation sequence로 `available`을 반환함 | provider/available | stale connection/sequence 또는 mismatched evidence가 아닌 fresh recovery evidence |
| request/stalled | ingress recovery owner가 uncommitted·uncanceled·side-effect-safe·budget-available로 판정함 | request/redispatched | host-owned commit boundary와 recovery coordinator |
| request/redispatched | recovery owner가 새 run/attempt identity를 발급함 | request/observing | 새 Node execution generation |
| request/stalled | recovery owner 없음, post-commit, cancel, side effect, budget 소진 또는 provider 후보 없음 | request/terminal | typed `response_stalled` terminal |
| request/terminal | complete/error/cancelled 또는 liveness terminal이 exactly once 확정됨 | 없음 | terminal emitter와 old generation fence |
## Interface Contract
- 계약 원문: [Agent Runtime 계약](../../../../agent-contract/inner/agent-runtime.md), [Edge-Node Runtime Wire 계약](../../../../agent-contract/inner/edge-node-runtime-wire.md), [Edge Config/Refresh 계약](../../../../agent-contract/inner/edge-config-runtime-refresh.md)
- 입력:
- `nodes[].providers[].response_stall_timeout_ms`: 생략/`0`이면 `300000`, 양수이면 provider별 override, 음수이면 config 오류다. provider-first config가 Node adapter/runtime observation config로 전달되며 provider config가 없는 legacy adapter route도 기본 `300000`을 사용한다. 변경은 다른 provider-first execution field와 같이 `restart_required`로 분류한다.
- timeout precedence: request hard deadline이 no-progress threshold보다 먼저 끝나면 기존 deadline failure를 유지한다. `response_stall_timeout_ms`는 queue timeout, request 전체 timeout과 CLI profile의 `response_idle_timeout_ms` completion heuristic을 대체하지 않는다.
- normalized activity: non-empty `delta`, `reasoning_delta`, 명시 provider progress와 terminal event. `start`는 clock 시작점이지 반복 progress heartbeat가 아니다.
- tunnel activity: provider response start/header, non-empty body, usage와 terminal frame. 빈 frame, Node/Edge heartbeat, socket/process 생존은 progress가 아니다.
- provider probe: stalled attempt의 adapter/target에 대한 bounded `ProviderProber` 결과. probe는 원 request와 별도 context에서 실행한다.
- recovery eligibility: ingress recovery owner가 소유한 response commit, caller cancel, shared request-level fault budget, tool/비가역 side effect, provider-pool candidate와 request idempotency 상태. Node는 이 값을 계산하지 않는다.
- 출력:
- common failure: stable `response_stalled` failure code. 기존 `Failure.retryable``attempt_fence=confirmed`일 때만 true가 될 수 있는 capability hint이며 response commit, side effect와 budget을 포함한 재실행 승인은 ingress recovery owner가 별도로 판정한다.
- wire terminal: normalized run은 exactly-once `RunEvent{type=error}`, tunnel은 exactly-once `ProviderTunnelFrame{kind=ERROR}`로 수렴한다.
- safe Node metadata: `failure_code=response_stalled`, `provider_health=available|unavailable|unknown`, `idle_duration_ms`, `run_id`, `attempt_id`, `attempt_fence=confirmed|unconfirmed`, adapter/target identity와 `health_observation_seq`; raw provider body, reasoning, prompt, credential과 Edge-owned `recovery_eligible`은 넣지 않는다.
- provider identity: Node의 `health_observation_seq`는 connection 안에서만 단조 증가한다. Edge는 wire에 내부 generation을 노출하지 않고 evidence를 수신한 registry connection generation에 묶은 뒤, immutable `RunDispatch``(node_id, provider_id, adapter, target)`과 대조한다. stale connection/sequence 또는 identity mismatch evidence는 health projection에 적용하지 않는다.
- provider projection: config health는 immutable config snapshot으로 유지하고 runtime health overlay를 `(node_id, connection_generation, provider_id)`에 별도 관리한다. `unavailable` probe만 bound provider candidate를 runtime unhealthy로 낮추고, 이후 bounded status probe가 낸 current connection의 같은 provider/adapter/target `available` evidence와 더 큰 observation sequence가 있어야 다시 활성화한다. request-stalled/available과 health-unknown은 provider 전체 장애로 승격하지 않는다.
- recovery: OpenAI-compatible host는 typed stall을 기존 StreamGate recovery cause/intent로 변환하고 `transport_uncommitted`에서만 기존 request-local coordinator의 공유 fault budget을 소비해 새 `run_id`와 attempt identity를 발급한다. 별도 liveness retry counter는 없다. recovery owner가 없는 surface는 typed terminal로 끝난다.
- 금지:
- Node/Edge heartbeat, TCP 연결, process 생존이나 독립 probe 성공을 원 request의 추론 진행 증거로 사용하지 않는다.
- 현재 CLI persistent `idle-timeout` complete를 liveness failure로 재해석하거나 새 watchdog을 provider별 구현에 복제하지 않는다.
- Node, Edge와 agent가 동시에 retry loop를 소유하지 않는다. Node는 detect/probe/cancel/local fence, Edge service는 lease/admission/routing, ingress recovery host는 commit/eligibility/retry를 소유한다.
- 외부 응답 또는 비가역 side effect commit 뒤 blind replay하지 않는다.
- old attempt를 terminal 뒤 되살리거나 lease를 두 번 반환하지 않는다.
## Acceptance Scenarios
| ID | Milestone Task | Given | When | Then |
|----|----------------|-------|------|------|
| S01 | `activity-contract` | normalized run과 raw tunnel이 provider default/override 설정으로 실행 중임 | provider text/reasoning/response-start/body, Node heartbeat와 더 이른 hard deadline이 각각 발생함 | provider-originated activity만 last-progress를 갱신하고 기본 5분/positive override가 적용되며 heartbeat/process/socket은 무시되고 hard deadline은 stall로 재분류되지 않는다. |
| S02 | `stall-watchdog` | terminal 없이 configured threshold 동안 provider progress가 없음 | watchdog, 늦은 provider event와 cancel/close success 또는 failure가 경쟁함 | stall/terminal과 local attempt fence가 한 번만 확정되고 confirmed일 때 old event가 drop되며 unconfirmed일 때 자동 재실행이 금지된다. |
| S03 | `health-classification` | request stall이 확정됨 | target probe가 available/unavailable/unsupported 또는 timeout을 반환하고 stale connection/sequence, identity mismatch 및 fresh recovery evidence가 도착함 | request health와 provider health가 분리되고 current connection의 bound identity와 더 큰 observation sequence만 unhealthy를 회복하며 probe 성공을 원 request progress로 기록하지 않는다. |
| S04 | `failure-handoff` | normalized run과 tunnel이 각각 stall됨 | Node가 typed terminal을 Edge로 전달함 | 두 path가 같은 failure/health/fence 의미를 보존하고 Node metadata에 recovery eligibility가 없으며 identity mismatch는 health를 바꾸지 않고 old attempt lease가 정확히 한 번 정리된다. |
| S05 | `bounded-retry` | OpenAI 미커밋 request, post-commit request, unconfirmed fence와 recovery owner가 없는 request가 각각 stall됨 | ingress host가 recovery를 평가함 | confirmed·미커밋·side-effect-safe request만 StreamGate 공유 fault budget 안에서 새 run identity로 재실행되고 나머지는 terminal로 끝난다. |
| S06 | `ops-evidence` | provider-available request stall, provider-unhealthy, stale health evidence와 후속 recovery가 발생함 | Node/Edge metric·log와 provider snapshot을 조회함 | request liveness, fence/probe와 Edge commit/recovery 결정이 분리되고 stale evidence가 거부되며 high-cardinality/raw content가 노출되지 않는다. |
## Evidence Map
| Scenario | Required Evidence | `agent-task` 연결 | 완료 Evidence 기대 |
|----------|-------------------|------------------|---------------------------|
| S01 | config validation과 fake clock 기반 normalized/tunnel activity/deadline table test | `agent-task/m-node-provider-execution-liveness-recovery/...` | `activity-contract` Task id, default/override/negative, activity reset와 deadline precedence assertion |
| S02 | threshold·timer/event·cancel/close race와 exactly-once terminal/fence test | `agent-task/m-node-provider-execution-liveness-recovery/...` | `stall-watchdog` Task id, confirmed/unconfirmed fixture와 late-event fence assertion |
| S03 | available/unavailable/unsupported/timeout, stale connection/sequence, identity mismatch와 fresh recovery prober fixture | `agent-task/m-node-provider-execution-liveness-recovery/...` | `health-classification` Task id, request/provider 분리, binding validation과 fresh observation recovery assertion |
| S04 | RunEvent/ProviderTunnelFrame wire round-trip와 queue lifecycle test | `agent-task/m-node-provider-execution-liveness-recovery/...` | `failure-handoff` Task id, stable code/fence metadata, no recovery eligibility와 release-once assertion |
| S05 | StreamGate commit-boundary/shared-budget, provider-pool failover와 no-owner terminal test | `agent-task/m-node-provider-execution-liveness-recovery/...` | `bounded-retry` Task id, recovery-owner gating, new run identity와 bounded dispatch count assertion |
| S06 | Node/Edge metric label guard, structured log capture와 provider snapshot overlay recovery test | `agent-task/m-node-provider-execution-liveness-recovery/...` | `ops-evidence` Task id, liveness/fence/health/commit/recovery 축과 raw-free evidence |
## Cross-repo Dependencies
- 없음
## Drift Check
- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다.
- [x] Evidence Map이 code-review/complete.log에서 검증 가능하다.
- [x] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
- [x] 사용자 리뷰가 필요한 항목은 `USER_REVIEW.md`에만 남겼다.
## 사용자 리뷰 이력
- 2026-07-29: 사용자가 agent가 아니라 IOP 내부 Node 관측 pipeline이 감시를 소유하고, 5분 no-response 뒤 provider health를 분리 판정해 재요청하는 방향을 승인했다.
## 작업 컨텍스트
- 표준선: Node는 execution-local liveness, local attempt fence와 probe evidence를 소유한다. Edge service는 provider lease·candidate eligibility를, ingress recovery host는 response commit·bounded retry를 소유한다. Control Plane은 projection을 소비할 수 있지만 canonical 실행 상태나 watchdog을 소유하지 않는다.
- 재사용 기준: OpenAI-compatible 경로는 [OpenAI-compatible 출력 검증 필터 SDD](../../knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md)의 StreamGate commit/recovery 경계를 사용한다. liveness failure는 Node 관측 결과를 소비하는 recovery cause/intent이며 별도 output content filter나 retry coordinator가 아니다.
- 후속 SDD: [요청 실행 로그와 Usage Ledger 기반 SDD](../request-execution-log-usage-ledger-foundation/SDD.md)