# Ralph Progress - BrainLayer Search Relevance

## Iteration 1 - BUG-005: Fix format=format parameter bug shadowing Python builtin
- Model: sonnet
- Renamed `format` param to `output_format` in `_search()` — fixes Python builtin shadowing
- Fixed `detail='full'` truncation bug: removed `doc[:1000]` in 3 places, now returns full content
- Added regression tests in test_search_quality.py: TestDetailFullReturnsFullContent (2 new tests)
- Updated test_smart_search_entity_dedup.py to reflect new param name
- CodeRabbit: ran, 2 false alarms (passes field and backward compat for unused internal kwarg), no real fixes needed
- 726 tests pass

## Iteration 2 - US-001: Expand FTS5 to index summary, tags, resolved_query
- Model: sonnet
- Implementation was already complete from a prior session (vector_store.py lines 186-526)
- FTS5 columns: content, summary, tags, resolved_query, chunk_id UNINDEXED
- Triggers: insert/delete/update on chunks table keep FTS in sync
- Tests: 8 passing tests in TestFTS5Expansion class (test_search_quality.py)
- CodeRabbit: skipped (repo not in allowed list)
- All 726 tests pass

## Iteration 3 - US-002: Post-RRF reranking with importance and recency boosting
- Model: sonnet
- Implementation was already complete from a prior session (search_repo.py lines 542-568)
- Importance boost: 1.0 + importance/20 (range 1.0x to 1.5x for importance 0-10)
- Recency boost: exponential decay, 30-day half-life, range 0.7x (old) to 1.0x (fresh)
- Tests: 4 existing tests in TestPostRRFReranking; added 1 new test (test_reranking_preserves_normal_ordering)
- CodeRabbit: skipped (repo not in allowed list)
- 727 tests pass (2 skipped)

## Iteration 4 - MP-003: Fix embedding truncation for chunks longer than 512 characters
- Model: opus
- Implementation was already complete from a prior session (embeddings.py)
- Old code: `MAX_EMBEDDING_CHARS = 512` with `content[:MAX_EMBEDDING_CHARS - 50] + "..."` truncation
- New code: passes full content to sentence-transformers; model handles token-level truncation at 512 tokens (~2000+ chars)
- Approach chosen: let sentence-transformers handle tokenization natively (no char truncation, no multi-embed)
- Query cap renamed to `MAX_QUERY_CHARS = 2000` (generous cap for degenerate inputs only)
- Tests: 8 passing in test_embedding_truncation.py (no-truncation, query caps, performance)
- CodeRabbit: passed, no findings
- 735 tests pass (2 skipped)

## Iteration 5 - MP-004: Wire kg_hybrid_search into brain_search auto-routing
- Model: opus
- Implementation was already complete from a prior session (search_handler.py)
- Entity detection: `_detect_entities()` checks bigrams + capitalized words against kg_entities + FTS
- Routing: when entities detected and no active filters, routes to `kg_hybrid_search`
- KG results include chunks + facts, formatted into structured output
- Fallback: if KG search fails, falls through to standard RRF search
- Added test: entity detection latency under 50ms (100 iterations averaged)
- CodeRabbit: passed, no findings
- 736 tests pass (2 skipped)

## Learnings
- Python project (FastAPI + SQLite + FTS5)
- Tests: pytest
- Key files: src/brainlayer/search.py, src/brainlayer/kg_search.py, src/brainlayer/mcp/search_handler.py
- 726 tests passing as of March 5 2026
- `_search()` is internal — no external callers use `format=` kwarg, safe to rename
