Sub-issue of #137. Depends on #151 (inference ladder scaffolding).
Goal
Eliminate the configuration trap where setting QVAC_LOCAL_URL and QVAC_SERVER_URL appears to work but silently has no effect on most of the system.
The problem, verbatim from the code
services/ai/app/core/config.py documents it:
study_service.py, chat_service.py, document_service.py, rag/query_rewriter.py, rag/compressor.py, workers/pipeline.py, and the /health check in main.py each still read QVAC_SERVICE_URL directly rather than through these settings — they will NOT pick up a diverging QVAC_LOCAL_URL/QVAC_SERVER_URL value until migrated onto settings too.
Seven modules bypass the router. Until they are migrated, the inference ladder is decorative: an operator can configure a separate local and server endpoint, see no error, and have most traffic ignore the configuration.
This is the worst kind of bug — it fails silently and looks like success.
Call sites to migrate
Tasks
Verification
Acceptance criteria
- No module outside
config.py reads QVAC_SERVICE_URL directly
- Diverging local/server URLs demonstrably change routing for every task type
- Startup logs make the resolved configuration visible
- A regression guard prevents reintroduction
Why this is the first ladder task
Rungs 0 and 1 are optimisations. This is a correctness fix: without it, the ladder's configuration is a lie.
Sub-issue of #137. Depends on #151 (inference ladder scaffolding).
Goal
Eliminate the configuration trap where setting
QVAC_LOCAL_URLandQVAC_SERVER_URLappears to work but silently has no effect on most of the system.The problem, verbatim from the code
services/ai/app/core/config.pydocuments it:Seven modules bypass the router. Until they are migrated, the inference ladder is decorative: an operator can configure a separate local and server endpoint, see no error, and have most traffic ignore the configuration.
This is the worst kind of bug — it fails silently and looks like success.
Call sites to migrate
services/ai/app/services/study_service.py—_qvac_clientat module levelservices/ai/app/services/chat_service.pyservices/ai/app/services/document_service.pyservices/ai/app/rag/query_rewriter.pyservices/ai/app/rag/compressor.pyservices/ai/app/workers/pipeline.pyservices/ai/app/main.py— the/healthcheckTasks
settings/qvac_routertask_typeso routing is meaningfulhttpx.AsyncClientat import time, which captures the URL before configuration can change it. Move to lazy construction where neededconfig.pyonce completeQVAC_SERVICE_URLreads remain outsideconfig.pyVerification
QVAC_LOCAL_URLandQVAC_SERVER_URLto different endpoints and assert each task type reaches the expected oneQVAC_SERVICE_URLdirectly, so this cannot regressAcceptance criteria
config.pyreadsQVAC_SERVICE_URLdirectlyWhy this is the first ladder task
Rungs 0 and 1 are optimisations. This is a correctness fix: without it, the ladder's configuration is a lie.