Context
The 2026-06-08 Gemini cost incident showed that Agent OS cheap synthesis surfaces must not default to remote paid calls just because a local process has GEMINI_API_KEY in its environment. Memory Core summary paths at least route through aiConfig.modelProvider; Knowledge Base ask synthesis does not.
V-B-A evidence:
ai/services/knowledge-base/SearchService.mjs:54 reads process.env.GEMINI_API_KEY directly.
ai/services/knowledge-base/SearchService.mjs:56 constructs new GoogleGenerativeAI(apiKey) directly.
ai/services/knowledge-base/SearchService.mjs:57 creates the model from aiConfig.modelName only, not a provider selector.
ai/services/knowledge-base/SearchService.mjs:174 throws GEMINI_API_KEY is required for RAG features. when the direct Gemini model is absent.
ai/services/knowledge-base/SearchService.mjs:235 calls this.model.generateContent(prompt) for synthesis.
ai/services/memory-core/SessionService.mjs:37 already has a provider-routed buildChatModel() precedent for Gemini, OpenAI-compatible, and Ollama chat paths.
ai/mcp/server/knowledge-base/config.template.mjs:305 still carries a KB-local modelName leaf for Gemini defaults, but no provider selector equivalent to Memory Core.
Live latest-open sweep: checked latest 60 open issues at 2026-06-08T07:57:30Z via live GitHub and GitHub Workflow MCP. Existing adjacent tickets are #12737 (return degraded refs when synthesis fails), #12719 (candidate weighting), #12715 (lexical-rescue I/O), #12450 (Memory Core query_summaries), and #12456 (AiConfig SSOT parent). Targeted live search for ask knowledge base provider configurable local capable returned no equivalent open issue.
Semantic duplicate sweep: used non-synthesis query_documents(type='ticket') instead of ask_knowledge_base because ask synthesis is the incident surface. It surfaced older KB/provider issues (#10097, #10103, #9893/#9900) but no open provider-routing leaf for KB ask synthesis.
The Problem
ask_knowledge_base is the default Neo knowledge tool, but its synthesis leg is still Gemini-only. That creates three problems:
- Local harnesses can spend remote Gemini tokens for every ask synthesis when a key is inherited.
NEO_MODEL_PROVIDER=openAiCompatible or ollama does not make KB ask synthesis local, even though Memory Core summary paths can route locally.
- Removing the Gemini key degrades ask into an exception path instead of local synthesis or a controlled degraded response.
#12737 covers preserving ranked references when synthesis fails. This ticket covers the upstream routing problem: KB ask synthesis should be provider-configurable and local-capable by construction.
The Architectural Reality
- Owning service:
ai/services/knowledge-base/SearchService.mjs.
- Current KB config:
ai/mcp/server/knowledge-base/config.template.mjs owns KB-local knobs and currently only exposes modelName for Gemini synthesis.
- Existing provider precedent:
ai/services/memory-core/SessionService.mjs exports buildChatModel() and wraps OpenAI-compatible/Ollama providers into the same generateContent envelope used by downstream summary code.
- ADR 0019 boundary: provider selection should read resolved AiConfig leaves at use sites and avoid parallel aliases unless a successor decision explicitly changes the SSOT.
The Fix
Route KB ask synthesis through a provider selector that supports local OpenAI-compatible/Ollama chat providers as well as explicit Gemini.
Expected implementation shape:
- Add or reuse a provider selector for KB ask synthesis that defaults local for local harnesses and supports explicit remote Gemini opt-in.
- Avoid duplicating Memory Core provider-wrapper logic if the existing
buildChatModel() can be safely shared without creating an import-boundary violation.
- Preserve #12737's degraded-reference behavior as a sibling contract: provider unavailability after retrieval should not erase ranked references.
- Update focused KB SearchService tests to prove provider dispatch without live Gemini.
- Keep retrieval/ranking behavior unchanged.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
SearchService.ask() synthesis provider |
SearchService.mjs + ADR 0019 provider-routing discipline |
Synthesis uses configured chat provider, with local-capable routing and explicit Gemini opt-in |
If no synthesis provider is available, return/compose the #12737 degraded refs path |
JSDoc on the widened provider contract |
Unit tests stub local provider and Gemini absence |
| Knowledge Base config template |
ai/mcp/server/knowledge-base/config.template.mjs |
KB ask synthesis has a provider selector or reads a Tier-1 provider leaf rather than Gemini-only modelName |
Existing modelName remains only for explicit Gemini mode |
Config JSDoc |
Config import test / focused spec |
MCP ask_knowledge_base behavior |
Tool contract: synthesized answer plus references |
Local deployments can use ask_knowledge_base without a Gemini key |
Degraded refs returned when synthesis cannot run |
PR body and any OpenAPI schema updates if needed |
SearchService unit coverage |
| Cost-safety boundary |
#12740 incident epic |
Presence of GEMINI_API_KEY alone does not force remote KB ask synthesis |
Remote providers require explicit provider opt-in |
PR evidence |
Test with key set but local provider selected |
Decision Record impact
aligned-with ADR 0019. If implementation finds KB needs a distinct kbSynthesisProvider leaf instead of the shared chat provider, that divergence must be justified against ADR 0019 in the PR before adding a second provider axis.
Acceptance Criteria
Out of Scope
- Query candidate weighting (#12719).
- Lexical-rescue I/O cost (#12715).
- Degraded-reference fallback itself (#12737), except as the fallback contract this ticket composes with.
- Memory Core mini-summary provider defaults; those are a sibling #12740 leaf.
- Removing Gemini support.
Avoided Traps
- Treating
GEMINI_API_KEY absence as the only safety mechanism. Keys can reappear in inherited harness environments.
- Copying provider-wrapper logic into KB without checking the existing
buildChatModel() precedent.
- Bundling ranking changes into the provider-routing PR.
Related
Parent: #12740
Related: #12737, #12719, #12715, #12456
Origin Session ID: e8f07ef9-ef7e-4815-8ff4-7abe13720621
Retrieval Hint: "SearchService ask provider routed local capable GEMINI_API_KEY direct GoogleGenerativeAI"
Context
The 2026-06-08 Gemini cost incident showed that Agent OS cheap synthesis surfaces must not default to remote paid calls just because a local process has
GEMINI_API_KEYin its environment. Memory Core summary paths at least route throughaiConfig.modelProvider; Knowledge Base ask synthesis does not.V-B-A evidence:
ai/services/knowledge-base/SearchService.mjs:54readsprocess.env.GEMINI_API_KEYdirectly.ai/services/knowledge-base/SearchService.mjs:56constructsnew GoogleGenerativeAI(apiKey)directly.ai/services/knowledge-base/SearchService.mjs:57creates the model fromaiConfig.modelNameonly, not a provider selector.ai/services/knowledge-base/SearchService.mjs:174throwsGEMINI_API_KEY is required for RAG features.when the direct Gemini model is absent.ai/services/knowledge-base/SearchService.mjs:235callsthis.model.generateContent(prompt)for synthesis.ai/services/memory-core/SessionService.mjs:37already has a provider-routedbuildChatModel()precedent for Gemini, OpenAI-compatible, and Ollama chat paths.ai/mcp/server/knowledge-base/config.template.mjs:305still carries a KB-localmodelNameleaf for Gemini defaults, but no provider selector equivalent to Memory Core.Live latest-open sweep: checked latest 60 open issues at 2026-06-08T07:57:30Z via live GitHub and GitHub Workflow MCP. Existing adjacent tickets are #12737 (return degraded refs when synthesis fails), #12719 (candidate weighting), #12715 (lexical-rescue I/O), #12450 (Memory Core
query_summaries), and #12456 (AiConfig SSOT parent). Targeted live search forask knowledge base provider configurable local capablereturned no equivalent open issue.Semantic duplicate sweep: used non-synthesis
query_documents(type='ticket')instead ofask_knowledge_basebecause ask synthesis is the incident surface. It surfaced older KB/provider issues (#10097, #10103, #9893/#9900) but no open provider-routing leaf for KB ask synthesis.The Problem
ask_knowledge_baseis the default Neo knowledge tool, but its synthesis leg is still Gemini-only. That creates three problems:NEO_MODEL_PROVIDER=openAiCompatibleorollamadoes not make KB ask synthesis local, even though Memory Core summary paths can route locally.#12737 covers preserving ranked references when synthesis fails. This ticket covers the upstream routing problem: KB ask synthesis should be provider-configurable and local-capable by construction.
The Architectural Reality
ai/services/knowledge-base/SearchService.mjs.ai/mcp/server/knowledge-base/config.template.mjsowns KB-local knobs and currently only exposesmodelNamefor Gemini synthesis.ai/services/memory-core/SessionService.mjsexportsbuildChatModel()and wraps OpenAI-compatible/Ollama providers into the samegenerateContentenvelope used by downstream summary code.The Fix
Route KB ask synthesis through a provider selector that supports local OpenAI-compatible/Ollama chat providers as well as explicit Gemini.
Expected implementation shape:
buildChatModel()can be safely shared without creating an import-boundary violation.Contract Ledger Matrix
SearchService.ask()synthesis providerSearchService.mjs+ ADR 0019 provider-routing disciplineai/mcp/server/knowledge-base/config.template.mjsmodelNamemodelNameremains only for explicit Gemini modeask_knowledge_basebehaviorask_knowledge_basewithout a Gemini keyGEMINI_API_KEYalone does not force remote KB ask synthesisDecision Record impact
aligned-with ADR 0019. If implementation finds KB needs a distinct
kbSynthesisProviderleaf instead of the shared chat provider, that divergence must be justified against ADR 0019 in the PR before adding a second provider axis.Acceptance Criteria
SearchService.ask()no longer constructs Gemini directly as its only synthesis model.GEMINI_API_KEY.GEMINI_API_KEYpresence alone does not override a configured local synthesis provider.Out of Scope
Avoided Traps
GEMINI_API_KEYabsence as the only safety mechanism. Keys can reappear in inherited harness environments.buildChatModel()precedent.Related
Parent: #12740 Related: #12737, #12719, #12715, #12456
Origin Session ID: e8f07ef9-ef7e-4815-8ff4-7abe13720621 Retrieval Hint: "SearchService ask provider routed local capable GEMINI_API_KEY direct GoogleGenerativeAI"