LearnNewsExamplesServices
Frontmatter
id12831
titleask_knowledge_base swallows degraded references on synthesis timeout
stateClosed
labels
bugaiarchitectureperformancemodel-experience
assigneesneo-opus-grace
createdAtJun 10, 2026, 11:01 AM
updatedAtJun 10, 2026, 2:36 PM
githubUrlhttps://github.com/neomjs/neo/issues/12831
authorneo-opus-grace
commentsCount1
parentIssue12740
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 10, 2026, 2:36 PM

ask_knowledge_base swallows degraded references on synthesis timeout

Closed v13.0.0/archive-v13-0-0-chunk-17 bugaiarchitectureperformancemodel-experience
neo-opus-grace
neo-opus-grace commented on Jun 10, 2026, 11:01 AM

Context

ask_knowledge_base currently returns an opaque Tool Error: synthesis_failed. Message: No message provided. for every query (verified live this session, 2× — including a 3-doc query). KB retrieval is healthy (neo-knowledge-base, 27,540 docs); the failure is the LLM synthesis step. Two stacked defects make the tool unusable.

The Problem

Defect 1 — synthesis times out at 30s. Live KB log: [SearchService] Synthesis failed after retrieval; returning degraded references: [OpenAiCompatible] ask_knowledge_base synthesis timed out after 30000ms (host=http://127.0.0.1:1234, model=gemma-4-31b-it). askSynthesisTimeoutMs is leaf(30000, …). A recent cluster already established that local gemma-4-31b latency exceeds prior budgets — #12805 raised the miniSummary timeout above measured local-model latency, #12722 aborts hung miniSummary provider calls, #12802 watchdog-bounds the backfill. The ask path has a larger prompt (up to 5 full documents) than a miniSummary yet keeps a 30s budget, so it times out on legitimate local synthesis. Independent of embedder contention — the timeout reproduces; it's budget-vs-latency, the same family as #12805.

Defect 2 — graceful degradation defeated at the MCP boundary. On timeout, SearchService.#createDegradedSynthesisResponse deliberately returns {answer, references, degraded:true, error:'synthesis_failed', reason} so the caller can "use the references directly while the synthesis provider recovers." But BaseServer.formatToolResult treats any object with an error key as a hard error (isError = 'error' in result), emits Tool Error: ${result.error}. Message: ${result.message || 'No message provided.'}, and discards answer, references, and reason (the degraded path sets reason, not message). The references it fetched are thrown away and the real cause is hidden — the degradation delivers nothing.

The Architectural Reality

  • ai/mcp/server/knowledge-base/config.template.mjs:150askSynthesisTimeoutMs: leaf(30000, 'NEO_KB_ASK_SYNTHESIS_TIMEOUT_MS', 'number').
  • ai/services/knowledge-base/SearchService.mjs:165#createDegradedSynthesisResponse returns the error:'synthesis_failed' + reason shape; :282 the timeout try/catch.
  • ai/mcp/server/BaseServer.mjs:370isError = 'error' in result; the error envelope reads result.message, never reason/answer/references.
  • Precedent: #12805 (miniSummary timeout right-size), #12722 (abort hung calls), #12759 (ask synthesis provider-routing via buildChatModel).

The Fix

  1. Right-size the timeout. Measure real gemma-4-31b synthesis latency for a representative 5-doc ask prompt (clean embedder window), then raise askSynthesisTimeoutMs to a measured-safe budget and/or add an abort-guard mirroring #12805/#12722. Trimming context size is a secondary lever.
  2. Deliver the degraded response. Surface references + a bounded reason on synthesis timeout/failure instead of an opaque error. Primary (low-blast): drop the top-level error key from the degraded response so BaseServer delivers it as content with degraded:true + reason (keep a machine-detectable degradedCode). Secondary (optional, broader): teach BaseServer.formatToolResult to attach structuredContent + surface reason even on error envelopes so future degraded-with-data responses aren't silently flattened.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
ask_knowledge_base result SearchService.mjs:165 #createDegradedSynthesisResponse On synth timeout/failure return {answer, references, degraded:true, reason, degradedCode} as content (no error envelope) references usable; degraded:true machine-detectable ai/mcp/server/knowledge-base/openapi.yaml live log timeout + opaque No message provided output
askSynthesisTimeoutMs config.template.mjs:150 Raise to measured-safe budget (+ optional abort-guard) env NEO_KB_ASK_SYNTHESIS_TIMEOUT_MS leaf JSDoc 2× live 30000ms timeouts on gemma-4-31b

Decision Record impact

none (no ADR asserted). Embodies the same "no silent fallback that erases the failure signal" principle as #12450.

Acceptance Criteria

  • On a synthesis timeout/failure, ask_knowledge_base returns its ranked references + a bounded reason (degraded marker), NOT an opaque Tool Error: synthesis_failed. Message: No message provided.
  • askSynthesisTimeoutMs raised to a measured-safe budget for a 5-doc gemma-4-31b prompt (cite the measured latency); abort-guard if synthesis hangs.
  • Unit test: a degraded/timed-out synthesis yields a content response carrying references + a degraded marker (not an isError envelope).
  • A successful synthesis is unaffected (regression guard).

Out of Scope

  • The KB chat-path contention/priority lane (#12748) — this is timeout-budget + result-shape, not scheduling.
  • Memory-core corrupted memories (#12830).
  • Switching the synthesis model/provider.

Avoided Traps

  • "The ask tool is broken / the collection is corrupt." Falsified: KB healthy (27,540 docs); retrieval works; only synthesis times out.
  • "It's embedder contention." The timeout reproduces; it's budget-vs-latency (the #12805 family), independent of any concurrent embedding run.
  • Fixing only the timeout. Even with a correct budget, synthesis can still legitimately fail/slow; the degraded-references delivery must work so ask stays useful.
  • Broad BaseServer rewrite as the primary fix. The surgical SearchService-side change (drop the error key) is low-blast; the BaseServer structuredContent-on-error enhancement is a separate optional hardening.

Related

  • #12740 — [Epic] Agent OS local-first AI provider defaults and cost-safety (parent)
  • #12748 — interactive/batch priority lane for local chat-model requests (sibling; contention, not this)
  • #12805 / #12722 / #12759 — local-model timeout right-size / abort-hung / ask provider-routing precedent
  • #12450 — query_summaries empty (same "no silent fallback" principle)

Origin Session ID: 2feb15b9-1948-4553-9679-1419ed7eecf1

Handoff Retrieval Hints:

  • Retrieval Hint: "ask_knowledge_base synthesis timeout 30000ms gemma degraded references swallowed BaseServer error envelope"
  • Log signature: [SearchService] Synthesis failed after retrieval; returning degraded references: [OpenAiCompatible] ask_knowledge_base synthesis timed out after 30000ms
  • Code anchors: SearchService.mjs:165, BaseServer.mjs:370, config.template.mjs:150.

Authored by Claude Opus 4.8 as @neo-opus-grace.

tobiu referenced in commit e23ad5f - "fix(knowledge-base): deliver degraded references on ask synthesis failure (#12831) (#12832) on Jun 10, 2026, 2:36 PM
tobiu closed this issue on Jun 10, 2026, 2:36 PM