Persist bounded model-group routing observations - #911
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthrough옵트인 SQLite 라우팅 관찰 저장소가 컨텍스트 키와 완료 시각을 저장하고 공유합니다. CLI와 오케스트레이터가 저장소를 구성하고 라우팅 경로에 연결합니다. Responses의 빈 Changes라우팅 관찰 지속성
요청 경로 동작 수정
프로젝트 기록 갱신
Estimated code review effort: 4 (Complex) | ~60 minutes Merge Risk: 🟡 Moderate · up to The opt-in durable-routing path can turn successful requests into failures during storage errors, lose routing observations when provider identity changes, and expose raw exception details in service logs. The PR should not merge until these bounded correctness, availability, and observability risks are fixed or explicitly accepted. Sequence Diagram(s)sequenceDiagram
participant CLI
participant TaskOrchestrator
participant ModelGroupRouter
participant SqliteRoutingObservationStore
participant SQLite
CLI->>TaskOrchestrator: 시간 창과 state_db 전달
TaskOrchestrator->>SqliteRoutingObservationStore: 저장소 초기화
TaskOrchestrator->>ModelGroupRouter: transport 및 quality 라우터 연결
ModelGroupRouter->>SqliteRoutingObservationStore: context_key와 observed_at을 포함한 관찰값 append
SqliteRoutingObservationStore->>SQLite: 관찰값 저장 및 공유 최대 보존 창 기준 정리
TaskOrchestrator->>ModelGroupRouter: refresh()
ModelGroupRouter->>SqliteRoutingObservationStore: active_contexts로 조회
SqliteRoutingObservationStore->>SQLite: 현재 컨텍스트 관찰값 조회
SQLite-->>SqliteRoutingObservationStore: 관찰값 반환
SqliteRoutingObservationStore-->>ModelGroupRouter: 관찰값 목록 반환
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 47.75% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 111 functions across 11 files. (9 skipped: 8 unsupported, 1 too large.)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Reusable conflict resolver stopped fail-closed. Executable or structured-data conflicts require semantic resolution at exact head |
|
Reusable conflict resolver stopped fail-closed. Exact PR head: Executable or structured-data conflicts require semantic resolution:
|
|
Routine staleness triage: this is the PR matching the org's known stale-base marker — its recorded I deliberately did not attempt a merge/push here: this session's shared environment shows clear signs of another in-progress process already mid-resolution on this exact branch (an existing local checkout with unpushed commits including a Flagging for visibility in case that other effort stalls or is abandoned: as of this comment the PR branch on GitHub is still unchanged (head Generated by Claude Code |
|
Re-verified this PR fresh (head still
Root-caused both original CI failures from job logs anyway, since that's independent of whether a merge proceeds:
Both are review-infrastructure gaps (consistent with the #868/#857/#906/#912-cycle pattern already flagged in this thread), not code defects this PR needs to fix. No action taken beyond this diagnosis — leaving the merge and any check re-run to whoever completes the #971 reconciliation, per your hold. Generated by Claude Code |
|
Scheduled review-feedback autofix for this PR head.
|
Resolves 8 conflicted files on this 5-day-old branch (CHANGELOG.md, __main__.py, model_group.py, orchestrator.py, provider_errors.py, docs/library_research.md, docs/product-technical-gap-baseline.md, tests/test_embeddings_model_pool_http_honesty.py). The two docs/CHANGELOG conflicts were the usual "both sides append a distinct dated entry" shape (kept both). The code conflicts needed real feature-merging, not a pick-one-side resolution, since main had independently evolved the exact same call sites this branch touches: - model_group.py's observe_success/_apply_success_locked: kept this branch's persist_observation/_apply_success_locked architecture (already used by 5+ other call sites org-wide) but merged in main's independently-added optional-latency support (latency_seconds: float | None, for successes with no honest single-attempt timing, e.g. shared Batch API calls) and its clamped-is-not-None guard -- which required widening _apply_success_locked's own latency param to Optional and adding the matching guard there too, since its original unconditional ewma update would have crashed on None. - orchestrator.py's _orchestrated_provider_completion synthesis/repair retry loop: took main's current while-loop structure (EffortProfileError handling, schema-repair-once logic -- this branch predates that whole refactor) but restored this branch's durable-persistence context-key tracking (synthesis_context_key/repair_context_key, _record_group_failure(_for_agent) wrapper calls) at each of the 4 failure-recording call sites, and fixed one condition (candidate.group_name and not (request_too_large OR capability_mismatch)) this branch had dropped relative to main. - _invoke's return shape: main independently widened it to a 4-tuple (output, served_id, served_model, usage); this branch's competing widening was a named _InvocationResult dataclass (output, served_id, usage, observation_context_key) with a 3-item __iter__ for tuple-unpack compat. Merged: added served_model as _InvocationResult's 3rd field, extended __iter__ to yield it, and fixed both _InvocationResult(...) construction sites plus the __invoke race-path usage extraction (was reading the stale outcome.value[2] as usage; main's caller-side code already used outcome.value[3] a few lines above it -- confirmed via call()'s own `-> tuple[str, str, str, ...]` return annotation that index 2 is now served_model, not usage). - provider_errors.py: kept this branch's _sanitize_provider_message_text extraction (reused at a second call site) but folded in main's independently-added _SAFE_SCHEMA_DIAGNOSTIC check, which the extraction had been missing. Two rounds of full-suite failures beyond the marked conflicts, both fixed: 1. route_once had a leftover `getattr(invocation, ...)` reference to a variable a different call site's resolution had removed -- caused a NameError cascading into ~448 failures across every test whose fixture transitively calls route_once. Fixed by capturing the _InvocationResult object instead of discarding it after unpacking. 2. Two unrelated post-fix failures: an ADR-0039 number collision (this branch's own "time-windowed-routing-observations" ADR vs. an independently-landed main ADR of the same number) -- renumbered to 0042 (next free slot before the 0124 block), fixed 3 cross-references in CHANGELOG.md/library_research.md/product-technical-gap-baseline.md. And a genuinely unrelated main-side token_counting.py rewrite (old heuristic word-count estimator replaced with a strict exact-model-allowlist policy that fails closed for unknown model names) broke this branch's own test_embedding_attempts_keep_their_original_routing_context, which never got the ExactSyntheticCounter workaround every sibling test in the same file already uses for its "mock-planner" fixture model name -- applied the same pattern. Full suite: 3445 passed, 1 skipped. provider_errors.py 100% coverage, model_group.py 98% (pre-existing gaps in unrelated validation branches, confirmed my own added guard is fully covered), interrogate 100%. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…only CodeRabbit flagged that the correction paragraph mischaracterized contextual-orchestrator#911 as "EWMA-based candidate-exclusion data." Per .github#1804 (the source PR#1043 already cites), #911's routing-observation EWMA data only reorders candidates by score — it does not exclude or skip a saturated one. Landing #911 alone would not solve the serial-failover stall. Verified directly via the GitHub API that #911 is still open, unmerged, and mergeable_state=blocked, so CodeRabbit's "already merged" premise for this same finding is false and was not acted on. The underlying re-ranking-vs-exclusion mischaracterization was real on its own terms and is fixed here: a separate, not-yet-built durable exclusion/skip mechanism (consuming #911's ranking data once #911 lands) is what's actually still required, not #911 by itself. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BV96rXhqoR3tYZ9AeAVur4
|
Found a concrete connection to this PR while investigating a separate, long-standing gateway-stall issue ( This PR's new The mechanism that is consulted regardless of grouping is the circuit breaker ( Not asking for scope creep on this PR — just flagging a concrete, low-risk follow-up once this lands: route |
|
Scheduled review-feedback autofix for this PR head.
|
|
Scheduled review-feedback autofix for this PR head.
|
|
Scheduled review-feedback autofix for this PR head.
|
Assisted-by: OpenAI Codex Signed-off-by: Seongho Bae <me@seonghobae.me>
…to codex/pr911-repair
The correction above defers option (a) to a "not-yet-built durable candidate-exclusion/skip mechanism". Reading TaskOrchestrator at pin 414f229 shows a per-agent breaker already exists and still would not exclude a stalled candidate, for two independent reasons: 1. The tool-bearing passthrough re-raises a bare TimeoutError as 500 internal_error before _record_failure (orchestrator.py:8048) runs, so the breaker never counts the failure. Measured: 0 of 21, 0 of 48, 0 of 63 and 0 of 65 passthrough timeouts recorded as circuit_failure, against 9/14 and 10/15 on the no-tools _invoke route-walk. That is #1082's scope. 2. _circuit_open clears state["failures"] to 0.0 once circuit_reset_seconds (30.0) have elapsed since opened_at (orchestrator.py:8036-8038). Against the ~90s attempts these stalls take, a route is re-admitted after 30s and needs three fresh failures to be excluded again. Also records that #911 remains unmerged as of this amendment. The conclusion is unchanged: a fixed wall-clock deadline on the candidate/retry loop is still barred by product-goal-directive section 8. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BV96rXhqoR3tYZ9AeAVur4
_record_success (orchestrator.py:8073-8077 at pin 414f229) pops the agent's circuit state rather than decrementing it, so with a threshold of 3 any single success zeroes the accumulated failure count. A route that alternates failure and success -- what an overloaded provider does -- never reaches the threshold and is never excluded, however long each failure runs. Recorded as a property rather than one log's reading: the same sequence appeared on three separate pull requests on 2026-09-06, each on the single ready route the post-.github#1957 preflight had found. Also corrects how far #911 is from landing. It is not merely unmerged: its base is codex/nim-evidence-successor, which is #1068 (open), itself based on codex/stacked-security-successor. "Once #911 lands" requires that whole stack. The conclusion is unchanged -- a fixed wall-clock deadline on the candidate/retry loop is still barred by product-goal-directive section 8. The count of missing pieces in the not-yet-built exclusion mechanism goes from two to three. Gate: 3392 passed, 3 failed, 2 skipped. All three failures are pre-existing and reproduce identically on origin/main (missing fast_mlsirm, missing OTLP HTTP exporter, and test_spend_analytics asserting mixed vs tokenizer); this change is documentation only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BV96rXhqoR3tYZ9AeAVur4
|
Exact-head RCA/lifecycle correction (2026-09-07): preserved the routing-observation delta and converted |
Summary
routing_observationsstore for transport and quality ledgers.Validation
uv run pytest -q— 3452 passed, 2 skipped on exact heade80949188f0afa86052f10f5a9b627da1ee1ef0bruff check --select E9,F63,F7,F82on changed Python filessemgrep --config p/pythonon changed runtime files — 0 findingspython -m compileall -q contextual_orchestratorgit diff --checkStacked on PR #1068 exact head
fa5446294ae7ae69f1c2958aa1ab6c071fa760bc; the PR head contains the latestmainand was pushed by fast-forward without force.Summary by CodeRabbit
새로운 기능
--routing-observation-window-seconds및--state-db옵션으로 설정할 수 있습니다.버그 수정
seed및top_logprobs가 불필요한 비스트리밍 처리를 유발하지 않습니다.