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 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.
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)