Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/brainlayer/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ def think(
include_audit: bool = False,
agent_id: str | None = None,
consumer_scope: Any | None = None,
warm_rrf: bool = False,
) -> ThinkResult:
"""Given current task context, retrieve relevant past knowledge.

Expand Down Expand Up @@ -212,6 +213,7 @@ def think(
include_audit=include_audit,
agent_id=agent_id,
consumer_scope=consumer_scope,
warm_rrf=warm_rrf,
)

if not results["documents"][0]:
Expand Down Expand Up @@ -248,6 +250,7 @@ def recall(
include_audit: bool = False,
agent_id: str | None = None,
consumer_scope: Any | None = None,
warm_rrf: bool = False,
) -> RecallResult:
"""Proactive smart retrieval based on file or topic.

Expand Down Expand Up @@ -290,6 +293,7 @@ def recall(
include_audit=include_audit,
agent_id=agent_id,
consumer_scope=consumer_scope,
warm_rrf=warm_rrf,
)
for doc, meta in zip(search_results["documents"][0], search_results["metadatas"][0]):
result.related_chunks.append(
Expand All @@ -314,6 +318,7 @@ def recall(
include_audit=include_audit,
agent_id=agent_id,
consumer_scope=consumer_scope,
warm_rrf=warm_rrf,
)
for doc, meta in zip(search_results["documents"][0], search_results["metadatas"][0]):
result.related_chunks.append(
Expand Down
2 changes: 2 additions & 0 deletions src/brainlayer/mcp/search_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2456,6 +2456,7 @@ def _embed(text: str) -> list[float]:
include_audit=include_audit,
agent_id=agent_id,
consumer_scope=consumer_scope,
warm_rrf=True,
),
)
result = _filter_think_result_for_consumer_scope(result, normalized_project, consumer_scope)
Expand Down Expand Up @@ -2506,6 +2507,7 @@ def _embed(text: str) -> list[float]:
include_audit=include_audit,
agent_id=agent_id,
consumer_scope=consumer_scope,
warm_rrf=True,
),
)
result = _filter_recall_result_for_consumer_scope(result, normalized_project, consumer_scope)
Expand Down
17 changes: 14 additions & 3 deletions src/brainlayer/search_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ def _hybrid_cache_key(
include_operational: bool = False,
content_class_filter: Optional[str] = None,
recency_rerank: bool = False,
recency_decay: bool = False,
importance_rerank: bool = False,
warm_rrf: bool = False,
rrf_vector_alpha: float = RRF_VECTOR_ALPHA,
Expand Down Expand Up @@ -367,6 +368,7 @@ def _hybrid_cache_key(
include_operational,
normalize_content_class(content_class_filter) if content_class_filter else None,
recency_rerank,
recency_decay,
importance_rerank,
warm_rrf,
rrf_vector_alpha,
Expand Down Expand Up @@ -1801,7 +1803,7 @@ def hybrid_search(
Cache is module-level LRU (128 entries) with defensive copy-on-read.
"""

recency_rerank = recency_rerank or _env_flag_enabled("BRAINLAYER_SCORE_RECENCY_DECAY")
recency_decay = recency_rerank or _env_flag_enabled("BRAINLAYER_SCORE_RECENCY_DECAY")
importance_rerank = importance_rerank or _env_flag_enabled("BRAINLAYER_SCORE_IMPORTANCE_BOOST")
rrf_vector_alpha = _rrf_vector_alpha() if warm_rrf else RRF_VECTOR_ALPHA
project_filter = _effective_project_filter(project_filter, consumer_scope)
Expand Down Expand Up @@ -1837,6 +1839,7 @@ def hybrid_search(
include_operational,
content_class_filter,
recency_rerank,
recency_decay,
importance_rerank,
warm_rrf,
rrf_vector_alpha,
Expand Down Expand Up @@ -2320,6 +2323,14 @@ def _ingest_keyword_rows(rows: list[tuple], ranks: dict[str, int]) -> None:
if not warm_rrf:
all_chunk_ids |= set(trigram_ranks.keys())

# A degraded single-leg search must retain that leg's rank signal even
# when the configured fusion weight assigns it zero weight.
fusion_alpha = rrf_vector_alpha
if warm_rrf and not semantic_by_id:
fusion_alpha = 0.0
elif warm_rrf and not fts_ranks:
fusion_alpha = 1.0

scored = []
match_features_by_id: dict[str, dict[str, bool]] = {}
for cid in all_chunk_ids:
Expand All @@ -2331,7 +2342,7 @@ def _ingest_keyword_rows(rows: list[tuple], ranks: dict[str, int]) -> None:
semantic_rank=sem_entry["rank"] if sem_entry is not None else None,
fts_rank=fts_rank,
k=k,
alpha=rrf_vector_alpha,
alpha=fusion_alpha,
)
if not warm_rrf and trigram_rank is not None:
score += (1.0 - RRF_VECTOR_ALPHA) / (k + trigram_rank)
Expand Down Expand Up @@ -2434,7 +2445,7 @@ def _ingest_keyword_rows(rows: list[tuple], ranks: dict[str, int]) -> None:

# Recency boost: exponential decay with 30-day half-life
created = meta.get("created_at")
if recency_rerank and created and isinstance(created, str):
if recency_decay and created and isinstance(created, str):
try:
dt = datetime.fromisoformat(created.replace("Z", "+00:00"))
if dt.tzinfo is None:
Expand Down
50 changes: 50 additions & 0 deletions tests/test_hybrid_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,38 @@ def test_warm_weighted_rrf_drops_trigram_fusion_leg(self, store):

assert results["ids"][0] == []

def test_warm_fts_fallback_preserves_fts_rank_when_alpha_is_one(self, store, monkeypatch):
monkeypatch.setenv("BRAINLAYER_RRF_ALPHA", "1.0")
_insert_chunk(
store,
chunk_id="exact-fts",
content="alpha fallback",
embedding=_embed("distant exact"),
)
_insert_chunk(
store,
chunk_id="verbose-fts",
content="alpha fallback with several unrelated padding words",
embedding=_embed("distant verbose"),
)
captured: list[tuple] = []

def capture_ranked(scored, *, n_results):
captured.extend(scored)
return scored

monkeypatch.setattr(store, "_mmr_rerank_scored_results", capture_ranked)

store.hybrid_search(
query_embedding=None,
query_text="alpha fallback",
n_results=2,
warm_rrf=True,
)

assert [item[1] for item in captured] == ["exact-fts", "verbose-fts"]
assert captured[0][0] > captured[1][0] > 0.0

Comment on lines +287 to +318

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Missing mirror-branch coverage for the fts-empty fusion-alpha case.

Only the "semantic leg empty → alpha=0.0" branch (this test) is covered. The symmetric branch — semantic leg populated but FTS leg empty (warm_rrf and not fts_ranks: fusion_alpha = 1.0 in search_repo.py) — has no dedicated test asserting it forces pure-vector ranking under a low BRAINLAYER_RRF_ALPHA.

✅ Suggested additional test
def test_warm_semantic_fallback_preserves_vector_rank_when_alpha_is_zero(self, store, monkeypatch):
    monkeypatch.setenv("BRAINLAYER_RRF_ALPHA", "0.0")
    query_embedding = _embed("semantic fallback query")
    _insert_chunk(
        store,
        chunk_id="close-vec",
        content="tokens with zero overlap versus the query text",
        embedding=query_embedding,
    )
    _insert_chunk(
        store,
        chunk_id="far-vec",
        content="tokens with zero overlap versus the query text too",
        embedding=[v + 0.01 for v in query_embedding],
    )
    store.build_binary_index()
    captured: list[tuple] = []

    def capture_ranked(scored, *, n_results):
        captured.extend(scored)
        return scored

    monkeypatch.setattr(store, "_mmr_rerank_scored_results", capture_ranked)

    store.hybrid_search(
        query_embedding=query_embedding,
        query_text="xyzzy plugh unmatched lexical tokens",
        n_results=2,
        warm_rrf=True,
    )

    assert [item[1] for item in captured] == ["close-vec", "far-vec"]
    assert captured[0][0] > captured[1][0] > 0.0
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_hybrid_search.py` around lines 287 - 318, Add a dedicated test
beside test_warm_fts_fallback_preserves_fts_rank_when_alpha_is_one covering the
symmetric warm_rrf branch where semantic results exist but the FTS leg is empty.
Set BRAINLAYER_RRF_ALPHA to 0.0, insert close and far vector matches with query
text guaranteed to have no lexical matches, build the binary index, capture
_mmr_rerank_scored_results, and assert pure-vector ordering and positive
descending scores.

def test_hybrid_search_default_does_not_apply_recency_or_importance_boost(self, store, monkeypatch):
monkeypatch.delenv("BRAINLAYER_SCORE_IMPORTANCE_BOOST", raising=False)
monkeypatch.delenv("BRAINLAYER_SCORE_RECENCY_DECAY", raising=False)
Expand Down Expand Up @@ -409,6 +441,24 @@ def capture_scores(scored, *, n_results):
assert captured_scores["old-result"] == pytest.approx(base_rrf * old_recency_boost)
assert captured_scores["fresh-result"] == pytest.approx(base_rrf)

def test_recency_decay_env_does_not_enable_recent_candidate_fallback(self, store, monkeypatch):
monkeypatch.setenv("BRAINLAYER_SCORE_RECENCY_DECAY", "1")
_insert_chunk(
store,
chunk_id="recent-unrelated",
content="fresh notes without query overlap",
embedding=_embed("unrelated recent content"),
created_at="2999-01-01T00:00:00Z",
)

results = store.hybrid_search(
query_embedding=None,
query_text="latest deployment status",
n_results=5,
)

assert results["ids"][0] == []

def test_hybrid_search_opt_in_recency_and_importance_rerank_change_scores(self, store, monkeypatch):
monkeypatch.delenv("BRAINLAYER_SCORE_IMPORTANCE_BOOST", raising=False)
monkeypatch.delenv("BRAINLAYER_SCORE_RECENCY_DECAY", raising=False)
Expand Down
3 changes: 3 additions & 0 deletions tests/test_search_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ async def test_brain_search_mcp_threads_agent_id_to_hybrid_search(monkeypatch):

assert store.hybrid_kwargs is not None
assert store.hybrid_kwargs["agent_id"] == "codex-test-agent"
assert store.hybrid_kwargs["warm_rrf"] is True


@pytest.mark.asyncio
Expand Down Expand Up @@ -840,6 +841,7 @@ async def test_brain_search_think_route_threads_agent_id_to_hybrid_search(monkey

assert store.hybrid_kwargs is not None
assert store.hybrid_kwargs["agent_id"] == "codex-test-agent"
assert store.hybrid_kwargs["warm_rrf"] is True


@pytest.mark.asyncio
Expand All @@ -861,3 +863,4 @@ async def test_brain_search_recall_route_threads_agent_id_to_hybrid_search(monke

assert store.hybrid_kwargs is not None
assert store.hybrid_kwargs["agent_id"] == "codex-test-agent"
assert store.hybrid_kwargs["warm_rrf"] is True
Loading