feat(search): add temporal decay to recall ranking#928
Conversation
Recall fuses BM25 + vector + graph into a single RRF relevance score with no notion of time, so an equally-relevant note from this morning and one from a year ago rank identically. This adds an opt-in temporal decay reweight that blends an exponential recency factor (and optional importance term) into the relevance score, so fresh and reinforced memories surface ahead of equally-relevant stale ones. Design: - Exponential decay parameterized by a configurable HALF-LIFE (days), the interpretable forgetting-curve knob. - Multiplicative reweight of the (small, unnormalized) RRF score rather than additive, so relevance stays the dominant signal. - A floor on the multiplier so decay demotes but never erases an old-but-highly-relevant hit. - "Use it or lose it": effective age is measured from the later of creation or last access (reuses the existing AccessLog), so recall refreshes recency. - Importance slows decay, mirroring the Generative Agents importance term. OFF by default (AGENTMEMORY_TEMPORAL_DECAY=true), matching the project's opt-in posture for changes that alter recall ordering. When disabled the reweight short-circuits with zero added cost. Adds src/functions/temporal-decay.ts (pure, fully unit-tested), config getters + safeParseFloat, HybridSearch integration, and .env.example docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Srinath <srinath12519@gmail.com>
|
@Srinath279 is attempting to deploy a commit to the rohitg00's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughA new temporal decay system reweights hybrid search result scores based on memory age, importance, and last-access time. A ChangesTemporal Decay Memory Recall
Sequence DiagramsequenceDiagram
participant Client
participant HybridSearch
participant applyDecayReweight
participant KV
participant applyTemporalDecay
participant Reranker
Client->>HybridSearch: tripleStreamSearch(query)
HybridSearch->>applyDecayReweight: enriched results[]
applyDecayReweight->>KV: getAccessLog per observationId
KV-->>applyDecayReweight: AccessLog with lastAccessIso
applyDecayReweight->>applyTemporalDecay: combinedScore, effectiveTimestampMs, importance
applyTemporalDecay-->>applyDecayReweight: decayed score (relevance × multiplier)
applyDecayReweight-->>HybridSearch: re-sorted decayed results[]
HybridSearch->>Reranker: decayed results window
Reranker-->>HybridSearch: reranked results or decayed truncated fallback
HybridSearch-->>Client: final scored results
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~28 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/state/hybrid-search.ts (1)
269-275: Consider requesting a batched read API for StateKV to reduce access-log lookup overhead in decay reweight.The
applyDecayReweightmethod issues onekv.getper result viaPromise.all, but StateKV has no batch/multi-get primitive—only individualget,set,update,delete, andlistoperations. WhilePromise.allparallelizes these calls, a true batch API would reduce per-query RPC overhead, especially for expansion-heavy searches with many results. If access patterns justify it, consider adding agetManyorbatchmethod to StateKV.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/state/hybrid-search.ts` around lines 269 - 275, The applyDecayReweight method currently issues individual kv.get calls for each result via Promise.all, which creates unnecessary RPC overhead. Add a batched read method (such as getMany or batch) to the StateKV class to support fetching multiple access logs in a single operation, then refactor the code that maps over results and calls kv.get for each r.observation.id to use this new batched method instead, passing all the IDs at once.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/config.ts`:
- Around line 19-23: The safeParseFloat function uses parseFloat which is too
lenient and accepts strings with trailing non-numeric characters like "0.2oops".
Replace the parseFloat call with Number(value) to enforce strict numeric parsing
that rejects partial matches. Number() returns NaN for invalid input, which
Number.isFinite() will properly catch and trigger the fallback value.
---
Nitpick comments:
In `@src/state/hybrid-search.ts`:
- Around line 269-275: The applyDecayReweight method currently issues individual
kv.get calls for each result via Promise.all, which creates unnecessary RPC
overhead. Add a batched read method (such as getMany or batch) to the StateKV
class to support fetching multiple access logs in a single operation, then
refactor the code that maps over results and calls kv.get for each
r.observation.id to use this new batched method instead, passing all the IDs at
once.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8f9ad511-3312-4398-9d06-448c65b79109
📒 Files selected for processing (6)
.env.examplesrc/config.tssrc/functions/temporal-decay.tssrc/index.tssrc/state/hybrid-search.tstest/temporal-decay.test.ts
parseFloat accepts trailing non-numeric text ("0.2oops" -> 0.2), so a
malformed env value was silently honored instead of falling back to the
default. Switch to Number() for strict parsing and guard the
empty-after-trim case (Number("") is 0, not NaN). Addresses CodeRabbit
review feedback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Srinath <srinath12519@gmail.com>
Recall fuses BM25 + vector + graph into a single RRF relevance score with no notion of time, so an equally-relevant note from this morning and one from a year ago rank identically. This adds an opt-in temporal decay reweight that blends an exponential recency factor (and optional importance term) into the relevance score, so fresh and reinforced memories surface ahead of equally-relevant stale ones.
Design:
OFF by default (AGENTMEMORY_TEMPORAL_DECAY=true), matching the project's opt-in posture for changes that alter recall ordering. When disabled the reweight short-circuits with zero added cost.
Adds src/functions/temporal-decay.ts (pure, fully unit-tested), config getters + safeParseFloat, HybridSearch integration, and .env.example docs.
Summary by CodeRabbit
New Features
Chores