feat: time-decay scoring for search results — prioritize recent memories (#331)#337
Conversation
web3guru888
left a comment
There was a problem hiding this comment.
Solid implementation of #331. A few observations from our integration perspective:
What works well:
- Exponential decay with configurable half-life is the right model. 90-day default is reasonable.
_apply_time_decay()preservingoriginal_similarityanddecayfields is clean — lets consumers decompose the score.- Graceful fallback for missing/invalid
filed_at(age=0, no penalty) avoids breaking old memories that lack timestamps. time_decay: boolparameter ontool_searchis a good escape hatch.
Integration considerations:
- We use
search_memories()directly in our OODA pipeline. Withtime_decay=Trueas the new default, we'll get decay applied automatically on next upgrade. For our Orient (breadth) phase, this could actually be beneficial — cross-domain discovery benefits from recency bias. For Evaluate (precision), we may wanttime_decay=Falseto preserve pure similarity ranking. The boolean toggle gives us that control. - This composes well with the RetrievalProfile concept from #335 — decay becomes a per-profile parameter alongside k, wing filters, and similarity thresholds.
- Our tiered dedup (
check_duplicatewith hardcoded thresholds) is unaffected since decay only applies tosearch_memories(), not the raw ChromaDB similarity in dedup.
One suggestion: The time_decay field in the response dict is a bool, but callers might want to know the half-life that was applied (especially if it came from config). Consider adding half_life_days to the response metadata alongside time_decay: true.
- Addresses @web3guru888 suggestion to expose the applied half-life - Response now includes half_life_days (int) when time_decay is true, None when false Made-with: Cursor
|
@web3guru888 Good suggestion — implemented in dd4aa38.
This way your OODA pipeline can inspect which half-life was actually applied without needing to read the config separately. Useful for logging/debugging when different profiles use different settings. |
|
Perfect — having Our Orient pass runs Will wire this into our session telemetry immediately. |
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 #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.
|
Hi, thanks for the contribution. This PR has merge conflicts with Could you rebase onto If this change is no longer relevant, feel free to close the PR. (This message is part of a periodic backlog pass, sent to all open PRs that match this state.) |
Closes #331
Summary
Add time-decay scoring so recent memories rank higher than older ones in search results.
Problem
mempalace_searchranks results purely by ChromaDB vector similarity. A tech-stack decision from six months ago and a discussion from yesterday are treated with equal weight. AI agents retrieving outdated decisions as top results can act on stale context without realizing it.Solution
Decay formula
decay = 0.5 ^ (age_days / half_life_days) final_score = similarity * decay
With the default 90-day half-life: yesterday's memory keeps ~99% of its score, 90-day-old memory drops to 50%, 180-day-old drops to 25%.
Config layer (
config.py)time_decay_half_life_daysproperty (default: 90, set to 0 to disable)Search layer (
searcher.py)_apply_time_decay(): standalone function that re-ranks hits by applying exponential decay to similarity scores using thefiled_atmetadata timestampsearch_memories()acceptstime_decay=True(default) to enable/disabledecay,original_similarity, and adjustedsimilarityfieldsfiled_attimestamps receive zero penalty (decay=1.0)MCP layer (
mcp_server.py)mempalace_search: newtime_decayboolean parameter (default true)time_decay=falsefor historical research queriesNon-breaking
time_decay=falserestores exact previous behaviorfiled_atmetadataTesting
tests/test_time_decay.pycovering:filed_athandlingoriginal_similaritypreservationRelated: #332 (soft-archive wings — the other half of time-aware memory management)