Skip to content

Dead semantic tier is reported but never escalated — silent keyword-only degradation destroys recall (field report for #226 item 3) #240

Description

@edheltzel

Summary

A dead semantic tier (Ollama down → embeddings = 0 rows) degrades memory_hybrid_search to keyword-only. The degradation is reported at every layer — and is still invisible to the human, because nothing escalates it. The result is a memory system that confidently answers "No results found" for memories it holds, indefinitely, with no alarm.

This was found in the wild on a live install. It cost real trust in Recall.

Evidence (measured 2026-07-15, live install)

Check Value
embeddings table 0 rows
vec_embeddings_rowids 0 rows
Ollama binary installed (/opt/homebrew/bin/ollama)
Ollama service DOWN (11434 refused)
recall doctor verdict [WARN] — same severity as two false alarms

User-visible failure:

$ recall search "do you remember when we decided how to handle graphs"
No results found.

$ recall search "CodeGraph"
Found 20 result(s)

The matching record (decision #2892) exists and is important enough to be injected into L1 at every SessionStart. The memory was never lost — the retrieval path to it was dead.

Traced path: memory_search → 0 keyword hits → shouldFallbackToHybrid (src/lib/search-fallback.ts:44) → hybrid → no embeddings → empty → "No results found" (search-fallback.ts:93).

Root cause: this is an escalation failure, not a silence failure

The system tells the truth at every layer:

  • CLI: Error: Embedding service not available at http://localhost:11434 / Falling back to keyword-only search...
  • MCP: Found 1 results (keyword-only: embeddings unavailable; semantic backend: none)

The signal reaches the agent on every single search. The agent reads it, answers from degraded results, and never tells the human. The information dies in the agent's context. No prose in a guide fixes this — the guides already say "memory first" in four places.

Contributing defects

1. recall doctor has no FAIL tier, so the real signal is buried in noise. Three same-severity WARNs; two are false alarms:

  • "CLAUDECODE env var set (will block extraction)" — factually wrong. Every spawn path already clears it: RecallExtract.ts:1034, extract-model.ts:129/:226, RecallInSession.ts:94, RecallClearExtract.ts:190. It flags the parent env, which is irrelevant.
  • "messages stale 2.8d" — near-vestigial; structured extraction (decisions/LoA) flows daily.

A dead semantic tier and a bogus env warning cannot share a severity level.

2. The hybrid benchmark is an invalid instrument. The 23.5ms p50 @100k SLO (#217, #146) passes because the vec index is empty — it is fast because it searches nothing. A green benchmark over 0 rows is how this stayed invisible. The benchmark must fail on an empty index.

3. Negative relevance scores on the fallback path. src/lib/search-fallback.ts:63 and src/mcp-server.ts:531 do (r.score * 100).toFixed(1), but FTS5 bm25() returns negative scores (more negative = better). Renders as -1121.0%. This is on the fallback path — i.e. it only shows up while already degraded, compounding the trust loss.

4. Semantic dedup is silently non-functional in this state. dedup.ts:52 (DEFAULT_SEMANTIC_THRESHOLD = 0.95) reads embeddings, which are empty — and has no automatic caller.

Relationship to #226

#226 item 3 predicted this exact failure, verbatim:

recall doctor has no vec checks — extension loaded, index row count vs embeddings count, index format version. A silently-degraded vec tier is currently invisible outside stderr logs.

It sits unstarted in Phase 3 Todo. The failure mode was correctly diagnosed and filed, then deprioritized, then hit in production. This issue is the field report that should pull #226 item 3 forward.

Proposed fix

Detection

Escalation (the actual gap)

  • A degraded tier must reach the human, not just the agent's context. Options: prefix the MCP response with an unmissable banner; have SessionStart surface a health line when a tier is down; make memory_hybrid_search return a top-level degraded: true the guide requires escalating
  • Add an escalation rule to FOR_CLAUDE.md: if a tier reports degradation, surface it to the user that turn — never answer from degraded results silently

Instrument integrity

  • Make the hybrid benchmark fail (not pass) on an empty vec index
  • Fix -1121.0% — normalize BM25 or drop the % unit

Recovery UX

  • When embeddings are empty/backend down, point at the fix in-product: ollama serverecall embed backfillrecall embed stats

Acceptance criteria

  1. With Ollama stopped, recall doctor exits non-zero with a FAIL naming the dead semantic tier — and no CLAUDECODE warning.
  2. With embeddings = 0, the hybrid benchmark fails.
  3. With the tier degraded, an agent using memory_hybrid_search receives a signal it cannot answer past without surfacing to the user.
  4. No search output renders a negative percentage.

Notes

  • Diagnosed via 3-agent RedTeam ParallelAnalysis. Full plan: .agents/atlas/plans/2026-07-15-recall-confidence-diagnosis-and-remediation.md (local, gitignored). Recall learning #720.
  • Capture was never at fault — decisions 3004, LoA 910, learnings 717, breadcrumbs 4244, same-day writes. Retrieval was the whole defect.

Refs #226, #217, #146.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageMaintainer needs to evaluate this issuerisk:hightype:bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions