Skip to content

Commit 3690b2f

Browse files
Coryclaude
andcommitted
Add TTL decay for expired working memory in reranker
- Expired working memories (age > ttl_days) get 0.3x score multiplier - Near-expiry (age > 80% of TTL) get 0.7x score multiplier - Long-term memories and unclassified entries unaffected - Re-sorts results after decay to maintain relevance order 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent af58d44 commit 3690b2f

File tree

1 file changed

+53
-3
lines changed

1 file changed

+53
-3
lines changed

src/mcp_server_qdrant/reranker.py

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import logging
22
import httpx
3+
from datetime import datetime, timezone
34
from typing import Any
45

56
from mcp_server_qdrant.qdrant import Entry
@@ -59,7 +60,7 @@ async def rerank(self, query: str, entries: list[Entry], top_k: int | None = Non
5960
# Map reranked results back to original entries
6061
# API returns: {results: [{index: N, relevance_score: X}, ...]}
6162
reranked_entries = []
62-
for item in data["results"][:top_k]: # Force limit to top_k
63+
for item in data["results"]: # Process all results, will re-sort and trim
6364
index = item.get("index")
6465
score = item.get("relevance_score") or item.get("score")
6566

@@ -71,12 +72,22 @@ async def rerank(self, query: str, entries: list[Entry], top_k: int | None = Non
7172
entry = entries[index]
7273
if entry.metadata is None:
7374
entry.metadata = {}
74-
entry.metadata["rerank_score"] = score
75+
76+
# Apply TTL decay for expired working memories
77+
adjusted_score = self._apply_ttl_decay(entry, score)
78+
79+
entry.metadata["rerank_score"] = adjusted_score
7580
entry.metadata["reranked"] = True
7681

7782
reranked_entries.append(entry)
7883

79-
logger.info(f"Reranked {len(entries)}{len(reranked_entries)} results")
84+
# Re-sort by adjusted score (TTL decay may have changed order)
85+
reranked_entries.sort(key=lambda e: e.metadata.get("rerank_score", 0), reverse=True)
86+
87+
# Trim to top_k after re-sorting
88+
reranked_entries = reranked_entries[:top_k]
89+
90+
logger.info(f"Reranked {len(entries)}{len(reranked_entries)} results (with TTL decay)")
8091
return reranked_entries
8192

8293
except httpx.HTTPError as e:
@@ -86,6 +97,45 @@ async def rerank(self, query: str, entries: list[Entry], top_k: int | None = Non
8697
logger.error(f"Reranker error: {e}")
8798
raise RuntimeError(f"Reranker failed: {e}")
8899

100+
def _apply_ttl_decay(self, entry: Entry, score: float) -> float:
101+
"""
102+
Apply decay multiplier to scores for expired or near-expired working memory.
103+
104+
Long-term memories and unclassified entries pass through unchanged.
105+
Working memories get decayed based on TTL expiration status.
106+
"""
107+
metadata = entry.metadata or {}
108+
memory_type = metadata.get("memory_type")
109+
ttl_days = metadata.get("ttl_days")
110+
timestamp = metadata.get("timestamp")
111+
112+
# Only decay working memory with valid TTL info
113+
if memory_type != "working" or not ttl_days or not timestamp:
114+
return score
115+
116+
try:
117+
# Parse timestamp (handle ISO format with Z or +00:00)
118+
ts = timestamp.replace("Z", "+00:00") if isinstance(timestamp, str) else str(timestamp)
119+
created = datetime.fromisoformat(ts)
120+
age_days = (datetime.now(timezone.utc) - created).days
121+
122+
if age_days > ttl_days:
123+
# Expired - heavy decay
124+
decay = 0.3
125+
logger.debug(f"TTL expired ({age_days}d > {ttl_days}d): score {score:.3f}{score * decay:.3f}")
126+
return score * decay
127+
elif age_days > ttl_days * 0.8:
128+
# Near expiry (>80% of TTL) - mild decay
129+
decay = 0.7
130+
logger.debug(f"TTL near expiry ({age_days}d / {ttl_days}d): score {score:.3f}{score * decay:.3f}")
131+
return score * decay
132+
133+
return score
134+
135+
except (ValueError, TypeError) as e:
136+
logger.warning(f"Failed to parse TTL metadata: {e}")
137+
return score
138+
89139
async def close(self):
90140
"""Close HTTP client."""
91141
await self.client.aclose()

0 commit comments

Comments
 (0)