Phase: Phase 3 — Hybrid recall + Learning + Admin · Status:
closed · Filed 2026-04-16What Worked
- RRF for hybrid recall — ranks-only fusion, zero normalization tuning needed
- Generated tsvector column — Postgres recomputes on content change, zero app-layer sync
- Idempotent candidate sync — checks existing (retro_id, lesson_index) before insert
- Promote/reject in DB instead of docs — enforced in code, audit trail permanent
- Dedicated audit session for last_used_at — decoupled from request semantics (ADR-020)
- Smoke-test-as-you-go continued — caught candidate backfill gap immediately
- Dogfooding the new endpoints — product is used to build itself
What Didn’t
- global_fact_candidates was a Phase 0 stub that sat empty for three phases — stubs decay
- PowerShell quoting tripped up in bash-spawned pipeline — use single quotes or here-doc
- Forgot to commit Phase 2 retro to DB first time — PID-PGR01 wasn’t registered as project
- First learning_service Write call failed because file existed as Phase 0 stub — always Read first
- regex parameter on FastAPI Query deprecated in favor of pattern — trivial, deferred
- MEMORY.md global feedback memory wasn’t checked at session start
Lessons
- [project] L8 — Generated columns over application-maintained denorm. Prefer Postgres GENERATED ALWAYS AS … STORED
- [project] L9 — Idempotency via composite key check before insert is the Prism idiom
- [project] L10 — Audit-style writes own their own sessions. Don’t piggyback on the request session
- [project] L11 — Stubs decay. If a model is in the registry, it should be a real table
- [global] G5 — RRF (RRF_K=60) is the default for hybrid retrieval in agent-memory systems
- [global] G6 — Cross-platform script parity from day one. Every .ps1 ships with a .sh

