Context
The 2026-06-08 €50+ Gemini cost incident (~1,200 req/min) root-caused to the memory miniSummary backfill calling remote gemini-3.5-flash instead of a local fast model. This is the actionable fix for the root cause identified in #12713 ("mini-summaries should be cheap"). Operator directive: "mini-summaries → configurable remote/local, default local. No remote call to summarize 5k chars → a tweet."
Incident timeline (orchestrator.log): 06:43:24Z Starting memory miniSummary backfill (pending-memory-minisummary:50) looping back-to-back; after the operator's gemini-2.5→3.5-flash switch made the calls succeed (they were failing 00:00–02:00), the backfill drained the pending backlog over the billed remote API → 429-retry storm → €50+.
The Problem
Summarization (session + mini-summary) is cheap by design — compress a ~5k-char memory to a tweet-sized summary — and belongs on a local model (gemma4). It currently resolves to remote Gemini Flash by default, which is (a) needlessly expensive, (b) network/quota-failure-prone (the #12713 hang AND this cost storm), and (c) a per-item cost multiplier across a backlog.
Root cause: aiConfig.modelProvider defaults to 'gemini' and is shared between the chat/agent path and the summarization path, so summarization inherits the remote default whenever NEO_MODEL_PROVIDER is unset.
The Architectural Reality
ai/config.template.mjs:136 — modelProvider: leaf('gemini', 'NEO_MODEL_PROVIDER', 'string') — default 'gemini', shared by chat + summarization.
ai/services/memory-core/SessionService.mjs:241 (session summary) + ai/services/memory-core/MemoryService.mjs:713 (mini-summary) — resolve aiConfig.modelProvider → buildChatModel(...) → Gemini client on 'gemini'.
- Local providers already exist:
graphProvider / embeddingProvider default 'openAiCompatible' (local), localModels.ollama.model = 'gemma4:31b'. The local summarization path is available; only the default/routing is wrong.
- ADR 0019 (AiConfig reactive-Provider SSOT) owns the config; this is a declarative-leaf change, read at the use site.
The Fix
Decouple the summarization provider from the chat/agent modelProvider, defaulting summarization to local; remote is explicit opt-in:
- Introduce a summarization-scoped provider resolution (e.g. a
summaryProvider leaf, or a documented derivation) that defaults to a local provider (ollama / openAiCompatible → gemma4), independent of the chat/agent provider.
SessionService + MemoryService summary paths read the summarization provider (not the shared chat modelProvider).
- Remote (Gemini) summarization requires an explicit opt-in (env or config), never the silent default.
- Declarative + by-construction (ADR 0019) — no runtime singleton mutation.
Contract Ledger
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
| summarization provider (new/derived leaf) |
ADR 0019 AiConfig SSOT |
default LOCAL (gemma4 via ollama/openAiCompatible); remote = explicit opt-in |
local provider; if local unavailable, fail loud (NOT silent remote) |
learn/agentos/AiConfigModel.md + ADR 0019 |
this incident (orchestrator.log 06:43:24Z); config.template.mjs:136 |
SessionService / MemoryService summary calls |
the summarization-provider leaf |
read summarization provider, not chat modelProvider |
— |
service JSDoc |
SessionService.mjs:241, MemoryService.mjs:713 |
Decision Record impact
aligned-with ADR 0019 — a declarative config-leaf addition/derivation within the reactive Provider SSOT (default local, read at use site). No mutation / aliasing.
Acceptance Criteria
Out of Scope
- Backfill loop-safety (rate-limit / no-back-to-back / bounded 429-retry) — separate ticket (the runaway amplifier).
- Live-remote-AI test isolation (QA/Librarian
skip(!KEY) inversion) — separate (Refs #12435).
- KB-
ask provider configurability — gpt's #12737.
- API key spend-cap / rotation — operator / console.
Avoided Traps
- Just rate-limiting the remote calls — treats the symptom; the root is the wrong (remote) default for cheap summarization.
- Changing the SHARED
modelProvider default to local without decoupling — could silently downgrade chat/agent quality; summarization needs its own scoped default.
- Snapshot/restore test isolation —
restore() is itself a B4 singleton mutation (ADR 0019, the #12660 Drop); isolate by construction.
Related
- #12713 — root-cause origin ("mini-summaries should be cheap"); this ticket is its fix. Recommend #12722 (abort-guard)
Refs (not Resolve) #12713, with #12713's "reliably cheap" AC carried here.
- #12456 — AiConfig-SSOT antipattern epic (ADR 0019). #12435 — test-isolation (B4). #12737 — KB-
ask provider (gpt, sibling path). #12712 / #12722 — symptom guards (timeout / abort).
- €50 Gemini cost incident, 2026-06-08.
Live latest-open sweep: checked latest 25 open issues at 2026-06-08T07:54Z; closest is #12713 (root-cause investigation — this is its fix, Refs not dup) + gpt's #12737 (KB-ask, distinct path). No equivalent fix ticket exists.
Origin Session ID: 1cf2ad9f-28af-4242-9069-fd14c04e1b62
Retrieval Hint: query_raw_memories("Gemini cost incident miniSummary backfill modelProvider default local") or commit-range anchor on the orchestrator.log 06:43:24Z backfill loop.
Authored by Claude Opus 4.8 (Claude Code), @neo-opus-grace.
Context
The 2026-06-08 €50+ Gemini cost incident (~1,200 req/min) root-caused to the memory miniSummary backfill calling remote
gemini-3.5-flashinstead of a local fast model. This is the actionable fix for the root cause identified in #12713 ("mini-summaries should be cheap"). Operator directive: "mini-summaries → configurable remote/local, default local. No remote call to summarize 5k chars → a tweet."Incident timeline (
orchestrator.log):06:43:24Z Starting memory miniSummary backfill (pending-memory-minisummary:50)looping back-to-back; after the operator'sgemini-2.5→3.5-flashswitch made the calls succeed (they were failing 00:00–02:00), the backfill drained the pending backlog over the billed remote API → 429-retry storm → €50+.The Problem
Summarization (session + mini-summary) is cheap by design — compress a ~5k-char memory to a tweet-sized summary — and belongs on a local model (gemma4). It currently resolves to remote Gemini Flash by default, which is (a) needlessly expensive, (b) network/quota-failure-prone (the #12713 hang AND this cost storm), and (c) a per-item cost multiplier across a backlog.
Root cause:
aiConfig.modelProviderdefaults to'gemini'and is shared between the chat/agent path and the summarization path, so summarization inherits the remote default wheneverNEO_MODEL_PROVIDERis unset.The Architectural Reality
ai/config.template.mjs:136—modelProvider: leaf('gemini', 'NEO_MODEL_PROVIDER', 'string')— default'gemini', shared by chat + summarization.ai/services/memory-core/SessionService.mjs:241(session summary) +ai/services/memory-core/MemoryService.mjs:713(mini-summary) — resolveaiConfig.modelProvider→buildChatModel(...)→ Gemini client on'gemini'.graphProvider/embeddingProviderdefault'openAiCompatible'(local),localModels.ollama.model = 'gemma4:31b'. The local summarization path is available; only the default/routing is wrong.The Fix
Decouple the summarization provider from the chat/agent
modelProvider, defaulting summarization to local; remote is explicit opt-in:summaryProviderleaf, or a documented derivation) that defaults to a local provider (ollama/openAiCompatible→ gemma4), independent of the chat/agent provider.SessionService+MemoryServicesummary paths read the summarization provider (not the shared chatmodelProvider).Contract Ledger
learn/agentos/AiConfigModel.md+ ADR 0019orchestrator.log06:43:24Z);config.template.mjs:136SessionService/MemoryServicesummary callsmodelProviderSessionService.mjs:241,MemoryService.mjs:713Decision Record impact
aligned-with ADR 0019— a declarative config-leaf addition/derivation within the reactive Provider SSOT (default local, read at use site). No mutation / aliasing.Acceptance Criteria
NEO_MODEL_PROVIDERunset, session + mini-summary resolve to a local provider (gemma4) — verified by a by-construction config test (ADR 0019, not singleton mutation).modelProvider(agents' provider choice unaffected, or the shared-default change is explicitly justified).Out of Scope
skip(!KEY)inversion) — separate (Refs #12435).askprovider configurability — gpt's #12737.Avoided Traps
modelProviderdefault to local without decoupling — could silently downgrade chat/agent quality; summarization needs its own scoped default.restore()is itself a B4 singleton mutation (ADR 0019, the #12660 Drop); isolate by construction.Related
Refs(not Resolve) #12713, with #12713's "reliably cheap" AC carried here.askprovider (gpt, sibling path). #12712 / #12722 — symptom guards (timeout / abort).Live latest-open sweep: checked latest 25 open issues at 2026-06-08T07:54Z; closest is #12713 (root-cause investigation — this is its fix, Refs not dup) + gpt's #12737 (KB-ask, distinct path). No equivalent fix ticket exists.
Origin Session ID: 1cf2ad9f-28af-4242-9069-fd14c04e1b62
Retrieval Hint:
query_raw_memories("Gemini cost incident miniSummary backfill modelProvider default local")or commit-range anchor on theorchestrator.log06:43:24Z backfill loop.Authored by Claude Opus 4.8 (Claude Code), @neo-opus-grace.