feat(experiments): shadow mode — observe-only candidate fan-out (Step 5.7b) - #145
Merged
officialCodeWork merged 2 commits intoJun 7, 2026
Merged
Conversation
… 5.7b) Run a candidate retriever alongside the served control path on a sampled fraction of live queries — observe-only — and feed both variants' outcome (mean retrieval score) into the 5.7a ABExperimentTracker, so the dashboard compares them on real traffic before anyone serves the candidate. - ShadowRunner (rag_gateway.experiments): scheduled as a FastAPI BackgroundTask so the served response is never delayed; degrade-open; deterministic request_id-hash sampling; skipped on a retrieval-cache hit. - Candidate is any SupportsRoute; the config build makes a RetrievalRouter differing only in shadow_candidate RRF weights; production injects one via build_app(shadow_runner=...). Same read_chunk PDP, event-only observability. - cfg.experiments gains shadow_enabled / shadow_sample_rate / shadow_experiment / shadow_candidate; doubly opt-in (enabled AND shadow_enabled). ragctl shadow. - 27 tests; ruff / mypy --strict (292) / RAG001 / log + policy gates green; rag.schema regenerated (no dist/schemas or openapi change). Docs: ADR-0032 (5.7b update), reference/experiments.md, architecture/ab-shadow-mode.md. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Step 5.7b — shadow mode: on a sampled fraction of live
/v1/queryretrievals, run a candidate retriever alongside the served control path observe-only (never affecting the response) and feed both variants' outcome into the 5.7aABExperimentTracker, soGET /v1/status/experimentscompares them on real traffic before anyone serves the candidate.This is the consolidated, final 5.7b — correct to merge. It is the functional superset of the two parallel implementations and supersedes the WIP on
build/phase-5/step-5.7b-shadow-mode(20c1f58, no PR). Based onmainafter the TRACKER restructure (#144), so it merges clean (origin/mainis an ancestor — no rebase, no conflicts).Design — hot-path safe (the Phase-4 reliability invariant, applied to A/B)
BackgroundTask, after the response is sent.experiment.shadow_failed) and dropped; a crashing candidate still returns 200.sha256(request_id) < shadow_sample_rate, stable across processes; skipped on a retrieval-cache hit (no fresh retrieval to compare).read_chunkPDP — the candidate routes throughHybridRetriever, so ACLs are enforced; no PolicyEngine coverage-linter entry needed. Event-only observability (no per-call span).Key choices (best of both parallel implementations)
ShadowRunner(rag_gateway.experiments) — isolated + unit-testable, and keepsABExperimentTrackera pure sample holder per ADR-0032 §3 (sampling lives in the gateway, not in observability).cfg.experiments.shadow_candidateRRF weights build a real secondRetrievalRouter, so shadow works end-to-end fromrag.yaml; production can also inject one viabuild_app(shadow_runner=…).cfg.experimentsgainsshadow_enabled/shadow_sample_rate/shadow_experiment/shadow_candidate; doubly opt-in (enabledANDshadow_enabled) and observe-only, so turning it on cannot change a served response.ragctl shadowsmoke command (drives the full flow against in-process stubs, creds-free).Verification — all gates green
ruff · mypy --strict (292 files) · RAG001 · log-schema/event-registry · policy-coverage · schema-drift · openapi-drift (unchanged —
BackgroundTasksis not a body/query param) · gateway / config / observability / ragctl / contract / retrieval / core suites · 27 new tests.Docs
ADR-0032 (5.7b landed) ·
architecture/ab-shadow-mode.md·reference/experiments.md·docs/README.md· TRACKER (5.7b ✅, Next action → 5.7c).Test plan
enabled+shadow_enabledmain🤖 Generated with Claude Code