Repository navigation
Description
Context
An audit of the RAG/embedding pipeline (see docs/CONFIG_AUDIT_2026-09-04.md, Part 1) found that several operationally-relevant retrieval/chunking knobs are hardcoded Python constants instead of following the repo's established SystemSettings pattern (env-default + nullable-DB-override, resolved per-call via a resolve_*_config function, exposed on the Settings UI with a tooltip, audit-logged on change) already used for retention, guardrails, tracing, and rate-limiting.
Unlike llm_provider/embedding_provider/embedding_model (already fully configurable via system_settings_service.py), nothing about how retrieval works is currently tunable without a code change and redeploy.
Gaps found
- Similarity/relevance threshold — missing entirely, not just hardcoded.
EmbeddingRepository.search_similar(backend/app/repositories/embedding_repository.py:17-40, duplicated inmcp-servers/doc-search/app/repositories/embedding_repository.py:53-80) doesORDER BY distance LIMIT top_kwith noWHERE distance < Xcutoff. A query with zero relevant documents in the corpus still returns the k least-bad chunks — there is no way today to have the system say "nothing relevant was found." - Max results (top_k) —
mcp-servers/doc-search/app/server.py:147,mcp-servers/doc-search/app/search.py:169,backend/app/mcp_client/doc_search_client.py:76all default to5, duplicated three times, with no server-side ceiling — the LLM can request anytop_kit wants via the tool call. - Chunk size —
backend/app/services/chunking_service.py:23-24,MIN_CHUNK_TOKENS=500/MAX_CHUNK_TOKENS=1000, module constants. - Chunk overlap ratio —
backend/app/services/chunking_service.py:30,CHUNK_OVERLAP_RATIO = 0.125, module constant. - Excerpt truncation cap —
mcp-servers/doc-search/app/search.py:24,MAX_EXCERPT_CHARS = 2000, module constant. Governs how much of a retrieved chunk's text reaches the LLM per source. - Two duplicated/inline candidate-pool multipliers (
mcp-servers/doc-search/app/repositories/embedding_repository.py:23_CANDIDATE_MULTIPLIER = 4, and an un-named inlinepayload.top_k * 4atbackend/app/api/embeddings.py:72) are lower-priority internal implementation details, not admin-facing — at minimum, the inline one should become a named constant even if not made configurable.
Why bundle these together
These knobs are reviewed and tuned together in practice (a change to chunk size interacts with excerpt cap and top_k; a similarity threshold interacts with top_k), and they share one Settings UI section and one design conversation (schema fields, retroactivity caveats). Splitting them into separate issues would fragment that conversation.
Scope
- Add nullable
SystemSettingscolumns:rag_similarity_threshold(float,None= no cutoff — preserves today's behavior),rag_max_results(int, becomes doc-search's actual enforced ceiling, not just a suggested default),rag_chunk_size(int),rag_chunk_overlap_ratio(float),rag_max_excerpt_chars(int). Followdocs/DATABASE_MODELS.md's shape and the existingretention_hours/guardrail_*nullable-override precedent exactly. - New
resolve_rag_configfollowingdocs/BACKEND_SERVICES.mdand the existingresolve_guardrail_config/resolve_retention_configshape, resolved per-call (not at startup — unliketracing_enabled, none of these require a process restart to take effect). - Wire
rag_similarity_thresholdintoEmbeddingRepository.search_similarin bothbackend/andmcp-servers/doc-search/as aWHERE distance < threshold(or equivalent) predicate. - Wire
rag_max_resultsinto doc-search'ssearch_knowledge_base(app/server.py) as the actual enforcedLIMIT, not just a caller-suppliable default. - Wire
rag_chunk_size/rag_chunk_overlap_ratiointochunking_service.py, replacing the module constants. - Wire
rag_max_excerpt_charsinto doc-search'ssearch.py, replacingMAX_EXCERPT_CHARS. - Settings UI: new "Retrieval & Chunking" section on the Settings screen, each field with a tooltip, following the existing
info-tooltip.tsxpattern from the Settings Help Text work. - Audit-log every change (
"rag_config.config_update"), following the existing before/after-diff pattern ("retention.update","guardrail.config_update"). - Document the retroactivity caveat explicitly in the Settings UI copy and in
docs/SECURITY.md(or a newdocs/RAG_CONFIGURATION.md, matchingdocs/RETRIEVAL_IMPROVEMENT_PROMPTS.md's existing shape): changing chunk size/overlap only affects newly-(re)indexed documents, not documents already chunked and stored — unlike the LLM/embedding provider switch, which applies to the very next call. Consider whether existing documents should show a "needs re-chunking" indicator (can be scoped out to a follow-up if so).
Non-goals
- No reranking (already tracked separately — see
docs/RETRIEVAL_IMPROVEMENT_PROMPTS.md's "Prompt 4," not yet a numbered issue). - No change to the RRF fusion constant (
RRF_K = 60) or hybrid-search candidate multiplier — low-value, rarely-tuned literature-standard defaults; out of scope here. - No prompt-text changes (the
search_knowledge_basetool description / chat agent system prompt are a separate, differently-shaped gap — free text, not a scalar field — and need their own design, not bundled here).
Definition of Done
- Five new nullable
SystemSettingscolumns + Alembic migration. -
resolve_rag_configservice function, unit-tested against[None, 0, boundary]per field perAGENTS.md's retention-field-semantics pattern where applicable. - All five knobs wired into their actual enforcement points (not just resolved and ignored).
- Settings UI section with tooltips; component-tested.
- Audit logging on change.
- Retroactivity caveat documented in both UI copy and a docs file.
- RBAC call-out per
AGENTS.mdTDD Discipline step 0: this is an admin feature, gated byrequire_admin, same asSettings. - Standard merge gate from
AGENTS.md's Definition of Done applies in full (tests, lint, e2e for the new UI, docs).
Activity
Make RAG retrieval/chunking tuning knobs admin-configurable (issue #68)…