Repository navigation
fix(search): fall back to episode vector when no fact in scope - #509
Merged
Merged
Conversation
method="vector" over episodes recalls through atomic_fact (MaxSim) and returned an empty list with HTTP 200 whenever no fact in scope pointed at an episode: extraction disabled, not caught up, or failing. Every episode carries its own vector that was never consulted, so vector search went silently dead while keyword kept working. Fall back to ANN over the episode vector in that case, with radius applied as on the other dense routes, and log a warning. When any fact is in scope the MaxSim path is unchanged. The unit test that pinned the empty result is replaced: returning nothing was the defect reported in #502, not a requirement. Refs: #502 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fallback fires in ordinary states too (a new user, a session whose facts are not extracted yet), so a per-request warning would be noise. Log at info with the number of hits instead. Make test_search_radius_filters_low_similarity the end-to-end pin for #502: it seeds episodes only, so it now asserts that vector search finds them. Before the fallback it could not fail (0 <= 0). Correct comments that still said VECTOR never reads the episode vector, and the docstring that said MaxSim pools by memcell (it pools by parent episode). Refs: #502 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs: #502 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kendrick-Song
approved these changes
Oct 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #502.
method="vector"over episodes recalls throughatomic_fact: ANN over facts, max-pool by parent episode, fetch the episodes (SearchManager._maxsim_atomic_recall). When no fact in scope pointed at an episode (extraction disabled, not caught up, or failing), it returned an empty list with HTTP 200 and no log line, while every episode carries its own vector that was never consulted. Keyword kept working, so the store looked half-alive rather than broken.This PR makes that one exit fall back to ANN over the episode's own vector, with
radiusapplied as on the other dense routes, and logsvector_recall_fact_pool_emptyat info with the hit count. Info rather than warning: it also fires in ordinary states, such as a new user or a session whose facts are not extracted yet. The fallback is the same code as thevector_strategy = "episode"branch that existed before 1.1.1.When any fact is in scope, the MaxSim path is unchanged.
Out of scope, on purpose:
vectoruntil extraction catches up.[]. This is pre-existing and not reachable through normal writes.not vectorexit still returns[]. That belongs with Undocumented 1024-dim embedding floor fails asynchronously while /health still reports embed: true #434./healthreason. The issue suggested adding a/healthreason for an empty fact table. That would touch files Undocumented 1024-dim embedding floor fails asynchronously while /health still reports embed: true #434 is changing, so it is left for a follow-up.Area
Verification
Symptom before and after the fix, on the same store and query. The store was built in an isolated root the way the issue describes: episodes written with
EpisodeWriter, theneveros cascade rebuild --yes, with a real embedding provider (Qwen3-Embedding-4B). Requests went throughcreate_app()viaTestClient, with the lifespan running.Mutation checks:
slow/live_llm, so CI deselects it): removing the fallback turns it red onassert n_loose > 0. It seeds only episodes, so before this PR it could not fail (0 <= 0). It is now the end-to-end pin for method=vector over episodes never reads the episode vector column — it depends entirely on atomic_fact #502.The full
make ciwas not run locally; CI runs it.Checklist
main..envfiles, dependency folders, or generated output.Notes for Reviewers
test_vector_returns_empty_when_no_factspinned the empty result, which is the defect method=vector over episodes never reads the episode vector column — it depends entirely on atomic_fact #502 reports. It is replaced bytest_vector_falls_back_to_episode_vector_when_no_facts, which also pinsradiuson the fallback.filters.parent_idmeans different things in the two paths. Onatomic_factit is the episodeentry_id; onepisodeit is the memcell id. With the fallback, aparent_idfilter onvectormatches episodes by memcell when no fact is in scope.keywordandhybridalready apply the same filter against the episode table this way.docs/api.mdalready describesvectoras "Dense vector ANN only" and never mentionsatomic_fact.hybriddrops the top dense hit.Pet adoptionis first by vector (0.696) but is absent from hybrid's top 5. This looks like the hierarchy ranking whenepisode_to_factsis empty, which is a separate question from method=vector over episodes never reads the episode vector column — it depends entirely on atomic_fact #502.By submitting this pull request, I agree that my contribution is licensed under
the Apache License 2.0.
🤖 Generated with Claude Code