feat(retrieval): Wilson prior, exploration slot, embeddings + three-leg fusion#83
Conversation
…tings) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… 48-session gate Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eted Replaces the popularity + overlooked-weight + ignored-demotion CASE ORDER BY in retrieve_reflections with wilson_lower_bound over (useful+overlooked)/rated, computed in Python and sorted (wilson DESC, confidence DESC, updated_at DESC). Deletes OVERLOOKED_RANKING_WEIGHT, IGNORED_DEMOTION_FLOOR, IGNORED_DEMOTION_WEIGHT from memory_rating.py and their two superseded pinning tests. semantic.py's list_for_project also imported the deleted constants; bridged its ORDER BY to plain popularity (useful_count DESC, created_at DESC) pending Task 3's full Wilson rewrite there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ries Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…bedder and 60s breaker Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ncEmbedder Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cEmbedder Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ction edit The management UI built ReflectionService and SemanticMemoryService without a sync_embedder, so UI-driven writes (edit reflection, create/update semantic memory) never populated the vec0 embeddings tables — only MCP/synthesis writes did, requiring a manual CLI backfill. - create_app() gains a sync_embedder kwarg (defaults to auto-detect from get_config().embeddings_backend, mirroring mcp/server.py and storage/sqlite.py); wired into ReflectionService and all SemanticMemoryService construction sites. - ReflectionService.update_text now re-embeds via a new module-level _write_reflection_embedding() (extracted from ReflectionSynthesisService._store_embedding so both classes share it) — previously an edit left a stale vector pointing at the old text. - tests/ui/conftest.py's client fixture pins BETTER_MEMORY_EMBEDDINGS_BACKEND=sqlite so ordinary route tests don't depend on a live Ollama instance (default backend is ollama); tests proving the wiring build their own app with an injected fake embedder. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…self-heal Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ade order Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…contract Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔴 Claude BugBot Analysis
Found 1 potential bug in this PR.
medium: 1
Reviewed the retrieval-quality PR (Wilson-score ranking, exploration slot, RRF vector fusion, SyncEmbedder circuit breaker, and embedding write/backfill paths) in depth; the ranking/fusion/bucketing logic all checked out correctly against their tests, but the SyncEmbedder wiring creates two independent circuit-breaker instances instead of the one shared, process-wide breaker the code documents.
…er and backend Addresses PR #83 review: SqliteBackend built a second SyncEmbedder, splitting the process-wide circuit breaker into two independent ones. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟢 Claude BugBot Analysis
Reviewed the retrieval-quality PR (Wilson-score ranking, reflection/semantic embeddings, SyncEmbedder circuit breaker, exploration slot) across all changed source files; no genuine new defects found. The previously reported SyncEmbedder-instantiated-twice bug is fixed: build_backend/SqliteBackend now accept and forward the caller's shared sync_embedder instead of constructing a second one.
No bugs were detected in this PR.
Summary
Implements PR-A of the retrieval-quality design (
docs/superpowers/specs/2026-07-23-retrieval-quality-design.md):services/scoring.py) — lower bound (z=1.96) on(useful+overlooked)/(useful+overlooked+ignored), computed in Python; replaces theuseful_count + 3*overlookedORDER BY and the fix(memory): close the rating loop and stop diluting what gets served #81 ignored-demotion CASE (constants deleted). Hit rate now beats raw serve-count: 3/4 outranks 67/192.SyncEmbedder: freshOllamaEmbedder(timeout=5, max_retries=1)per bridge call (httpx client is loop-bound), 60s circuit breaker, never raises. Lazy self-heal on query retrieves (cap 20) + one-shotpython -m better_memory.cli.backfill_embeddings. Migration 0014 addssemantic_embeddings(additive).rrf_k=60matchingsearch/hybrid.py; every leg degrades independently (no embedder / breaker open / no match ⇒ exactly the prior behaviour, test-pinned incl. multi-row order equality).Validation
ignoredat first — the designed cost of acquiring ranking signal; (b) live-installed contextual hooks fired inside A8 sessions (absent in the baseline arm); excluding that channel, z = −1.381. The metric to watch post-merge is liveuseful%trend as exploration converts untested memories into rated ones.Deploy
After merge: restart Claude Code (migration 0014 applies on server start), then
python -m better_memory.cli.backfill_embeddingsto embed the historical corpus.Carried follow-ups (triaged Minor): agentcore backend still uses the legacy linear ranking client-side;
sync_embedfinally-clause exception masking polish;storage/sqlite.pyReflectionService wiring-for-consistency (dead write path today).🤖 Generated with Claude Code