Skip to content

feat: add Weibull decay and rerank pipeline for search results#1032

Open
zackchiutw wants to merge 1 commit intoMemPalace:developfrom
zackchiutw:feat/rerank-pipeline
Open

feat: add Weibull decay and rerank pipeline for search results#1032
zackchiutw wants to merge 1 commit intoMemPalace:developfrom
zackchiutw:feat/rerank-pipeline

Conversation

@zackchiutw
Copy link
Copy Markdown

Summary

Introduces a config-driven post-retrieval rerank pipeline that adjusts search ranking beyond raw cosine distance. Four stages, independently toggleable via ~/.mempalace/config.json:

  • Weibull time-decay — newer memories surface higher; old ones decay toward a configurable floor
  • Keyword overlap boost — lexical overlap between query and hit text nudges distance down
  • Importance boost — hits with higher emotional_weight metadata get a small boost
  • LLM rerank (optional) — final top-K pass via Anthropic API

All stages are off by default. No config = identical behavior to before. When any stage is enabled, search_memories over-fetches 3× candidates, runs the pipeline after the existing closet-boost + BM25 hybrid rank (both preserved), re-applies max_distance against the post-rerank fused_distance, and flags the response with reranked: True for transparency.

Why

Raw cosine distance treats every memory as if it were written yesterday. For a memory system where identity and recency matter, that's a regression trap — old notes crowd out fresh ones. The pipeline is modular so users can tune the trade-off per palace (e.g., λ=90 days for a work palace, λ=365 for a personal history wing).

What changes

File Change
mempalace/reranker.py (new) 4-stage pipeline, unified fused_distance field
mempalace/searcher.py rerank=True param on search_memories; pipeline runs after _hybrid_rank so closet/BM25 signals are preserved; _load_rerank_config + _apply_rerank helpers keep complexity under the C901 threshold
mempalace/config.py rerank_config property reading config.json["rerank"]
mempalace/mcp_server.py rerank param + schema entry on mempalace_search
mempalace/layers.py Layer1 optional read-time decay (k=1.2, λ=365, floor=0.6); Layer3 refactored to consume search_memories() dict
mempalace/knowledge_graph.py query_entity(apply_decay=...) applies the same Weibull curve to stored confidence at read time
tests/test_reranker.py (new) 77 unit tests covering every stage + edge cases
tests/test_layers.py Updated for new Layer3 return shape

Example config

{
  "rerank": {
    "weibull_decay":   {"enabled": true,  "k": 1.5, "lambda": 90,  "floor": 0.3},
    "keyword_boost":   {"enabled": true,  "weight": 0.30},
    "importance_boost":{"enabled": false, "weight": 0.15},
    "llm_rerank":      {"enabled": false, "model": "claude-haiku-4-5-20251001", "top_k": 10}
  }
}

Test plan

  • pytest tests/test_reranker.py tests/test_searcher.py tests/test_layers.py tests/test_config.py tests/test_mcp_server.py — 186 passed
  • ruff check . — all checks passed
  • ruff format --check on modified files — clean
  • Backward compatibility verified: with no rerank key in config, search_memories returns identical output (no reranked flag, no new fields in response)
  • mempalace_search MCP tool accepts rerank: false to bypass the pipeline when desired

Notes for reviewers

  • The pipeline composes on top of the existing closet-boost + BM25 hybrid rank, rather than replacing either — earlier drafts that bypassed them regressed on closet-rich corpora
  • When rerank is enabled but no stages are configured, the pipeline is an identity function and the reranked flag stays false
  • No new dependencies required; llm_rerank stage is gated behind a local import of anthropic and skipped if unavailable

🤖 Generated with Claude Code

Introduce a config-driven post-retrieval rerank pipeline that adjusts
search ranking beyond raw cosine distance. Four stages are supported:
Weibull time-decay, keyword overlap boost, emotional_weight boost, and
optional LLM reranking via Anthropic API. All stages are independently
toggleable via ~/.mempalace/config.json and backward-compatible — no
config means identical behavior to before.

The pipeline runs after upstream's closet boost + BM25 hybrid rank so
those signals are preserved. When any stage is enabled, search_memories
over-fetches 3x candidates so the reorder has headroom, applies the
max_distance filter on the post-rerank fused_distance, and returns a
reranked=True flag for transparency.

Layer1 now supports optional read-time decay (k=1.2, λ=365, floor=0.6)
and Layer3 is refactored to use the unified search_memories() output.
Knowledge graph query_entity() accepts apply_decay to apply the same
Weibull curve to stored confidence values at read time.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@zackchiutw zackchiutw force-pushed the feat/rerank-pipeline branch from 4520e22 to b3b9881 Compare April 19, 2026 12:48
jphein added a commit to jphein/mempalace that referenced this pull request Apr 19, 2026
Scanned all 233 open upstream PRs today against our open PRs and
fork-ahead / planned-work items. Findings merged into README:

- P2 (decay) and P3 Tier-0 (LLM rerank): both covered by MemPalace#1032
  (@zackchiutw, MERGEABLE, 2026-04-19 — Weibull decay + 4-stage
  rerank pipeline). Older simpler version at MemPalace#337. Dropped as
  fork work; watching MemPalace#1032.
- P7 (alternative storage): formally out of scope. RFC 001 MemPalace#743
  (@igorls) defines the plugin contract; four backend PRs already
  in flight (MemPalace#700, MemPalace#381 Qdrant; MemPalace#574, MemPalace#575 LanceDB). Fork consumes,
  does not rebuild.
- P0 (multi-label tags): still fork/upstream candidate. MemPalace#1033
  (@zackchiutw) ships adjacent privacy-tag + progressive disclosure
  but not the full multi-label scheme.
- Merged MemPalace#1023 section acknowledges complementary MemPalace#976 (felipetruman)
  which adds broader mine_global_lock() + HNSW num_threads pin.

Gives future-us a map so we don't re-file MemPalace#1036-style duplicates.
jphein added a commit to jphein/mempalace that referenced this pull request Apr 19, 2026
Changes (all 7 approved items):
1. Tighten line-15 status line to keep only non-redundant bits
   (test count, Discussion MemPalace#1017, issues link)
2. Remove "Fork Changes / Headlines" subsection — duplicated the
   three differentiators already in the lead paragraph
3. Remove "Planned work / Done" — 5 bullets of PR status already
   in the "Open upstream PRs" table
4. Trim P2 "original design notes" — MemPalace#1032 is MERGEABLE; kept
   the prune CLI fork-opportunity note
5. Tighten MemPalace#1023 row in "Merged upstream" — moved the MemPalace#976 mapping
   detail to a single compact sentence
6. Collapse "Pulled in from upstream v3.3.1" from 6 bullets to
   one paragraph pointing at the release notes
7. Prune three oldest "Superseded by upstream" bullets (epsilon
   mtime, .jsonl, max_distance — all upstream-authored or shipped
   by upstream before the fork claimed them as contributions)
8. Drop P7 "Original fork-mode design notes" (flashcard/AAAK/
   diary modes) now that the section explicitly marks P7 as
   dropped fork work

Net: 345 → 316 lines. No unique info removed; anything cut is
either duplicated elsewhere in the same README or retrievable
from the cited PR/release link.
@igorls igorls added enhancement New feature or request area/search Search and retrieval labels Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/search Search and retrieval enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants