# The Feather DB Adaptive Scoring Formula: Similarity × Recency × Importance > Feather DB's adaptive scoring formula combines cosine similarity, time-decay recency, and explicit importance — with stickiness preventing recalled memories from aging out. Here's the full breakdown with worked numerical examples. - **Category**: Architecture - **Read time**: 7 min read - **Date**: June 16, 2026 - **Author**: Feather DB (Engineering) - **URL**: https://getfeather.store/theory/feather-db-adaptive-scoring-explained --- ## The scoring formula Every memory in Feather DB gets a final retrieval score that blends three signals: vector similarity, time-based recency, and an explicit importance weight. The full formula is: ```python stickiness = 1 + log(1 + recall_count) effective_age = age_days / stickiness recency = 0.5 ** (effective_age / half_life) score = ((1 - tw) * similarity + tw * recency) * importance ``` Where `tw` is the time weight (default 0.3), controlling how much recency competes with similarity. The four components interact: similarity anchors the result to the query, recency penalizes old memories, importance amplifies the whole expression, and stickiness protects frequently recalled memories from the recency penalty. ## Component 1: Similarity Similarity is the cosine similarity between the query vector and the stored memory vector, ranging from 0.0 (orthogonal) to 1.0 (identical). Feather DB's HNSW search returns approximate nearest neighbors; the adaptive scoring then reranks within the candidate set using the full formula. At the default `tw=0.3`, similarity contributes 70% of the non-importance part of the score. A highly relevant memory with similarity=0.92 will still score strongly even if it was written 60 days ago — the similarity component dominates unless recency is very low. ## Component 2: Stickiness and effective age Stickiness is the key innovation that separates Feather DB's scoring from simple time-decay. Every time a memory is recalled (returned in a search result and written back with `update_recall()`), its `recall_count` increments. Stickiness is: ```python stickiness = 1 + log(1 + recall_count) ``` Stickiness at different recall counts: recall_countstickinesseffective age (at day 90) 01.0090.0 days 51.7950.3 days 102.4037.5 days 203.0429.6 days 503.9322.9 days A memory recalled 10 times at day 90 has an effective age of only 37.5 days. A memory never recalled at day 90 has an effective age of 90 days. The logarithmic form ensures stickiness grows usefully up to recall_count ≈ 50 and then flattens, preventing highly-recalled memories from becoming permanent fixtures. ## Component 3: Recency Recency is a half-life exponential decay applied to the effective age: ```python recency = 0.5 ** (effective_age / half_life) ``` At `effective_age == half_life`, recency = 0.5. At `effective_age == 0`, recency = 1.0. At `effective_age == 2 * half_life`, recency = 0.25. The half_life parameter is the primary tuning knob for your domain. ## Worked numerical examples Let's trace a memory with similarity=0.85, importance=1.0, tw=0.3, half_life=30 days, at three points in time: **Day 0 — freshly written, recall_count=0:** ```python stickiness = 1 + log(1 + 0) = 1.00 effective_age = 0 / 1.00 = 0.0 recency = 0.5 ** (0.0 / 30) = 1.000 score = (0.7 * 0.85 + 0.3 * 1.000) * 1.0 = 0.895 ``` **Day 30 — recall_count=0 (never recalled):** ```python stickiness = 1.00 effective_age = 30 / 1.00 = 30.0 recency = 0.5 ** (30 / 30) = 0.500 score = (0.7 * 0.85 + 0.3 * 0.500) * 1.0 = 0.745 ``` **Day 30 — recall_count=10 (recalled 10 times):** ```python stickiness = 1 + log(1 + 10) = 2.398 effective_age = 30 / 2.398 = 12.5 recency = 0.5 ** (12.5 / 30) = 0.748 score = (0.7 * 0.85 + 0.3 * 0.748) * 1.0 = 0.819 ``` **Day 90 — recall_count=0:** ```python stickiness = 1.00 effective_age = 90.0 recency = 0.5 ** (90 / 30) = 0.125 score = (0.7 * 0.85 + 0.3 * 0.125) * 1.0 = 0.633 ``` **Day 90 — recall_count=10:** ```python stickiness = 2.398 effective_age = 37.5 recency = 0.5 ** (37.5 / 30) = 0.420 score = (0.7 * 0.85 + 0.3 * 0.420) * 1.0 = 0.721 ``` The recalled memory scores 0.721 vs 0.633 for the never-recalled memory at day 90 — a 14% lift just from being useful enough to recall 10 times over three months. ## Component 4: Importance Importance is a multiplicative scalar (default 1.0, range typically 0.1–2.0) that amplifies or suppresses the entire score. Because it multiplies the combined similarity-recency score, importance=2.0 doubles a memory's effective rank weight across all time and similarity values. Use importance for structural signals that aren't captured by the content: a user's explicitly stated preference is more important than an inferred one, a confirmed fact is more important than a hypothesis, a pinned memory is more important than a transient session note. ```python import feather_db as fdb db = fdb.DB.open("agent.feather", dim=768) # High importance: explicitly stated user preference mem = db.add(vec, text="User prefers concise responses, stated directly.") mem.meta.set_attribute("importance", 2.0) # Normal importance: inferred preference mem2 = db.add(vec2, text="User seemed to prefer bullet points in session 4.") # importance defaults to 1.0 # Low importance: speculative or uncertain fact mem3 = db.add(vec3, text="User might be in the EU based on timezone.") mem3.meta.set_attribute("importance", 0.5) ``` ## Tuning half_life for your domain Half_life is the most important tuning parameter. It determines how quickly an unrecalled memory loses relevance: DomainRecommended half_lifeRationale News / current events agent7–14 daysFacts go stale fast; last week's headlines are rarely relevant Customer support conversation14–30 daysIssue context relevant for a month; older tickets less so Personal assistant memory30–60 daysUser preferences stable but evolve over weeks Research assistant90–180 daysPaper claims stay valid for months; foundational work longer Architecture decisions180–365 daysWhy we chose PostgreSQL in 2024 is still relevant in 2025 ```python # Fast-moving domain: news agent results = db.search(query_vec, k=10, half_life=14) # Stable domain: architecture decisions results = db.search(query_vec, k=10, half_life=365) # Mixed: search with domain-appropriate half_life per query def search_with_domain(db, vec, domain): half_lives = {"news": 14, "preferences": 60, "architecture": 365} return db.search(vec, k=10, half_life=half_lives.get(domain, 30)) ``` ## Time weight: how much recency competes with similarity The `tw` parameter (time weight, default 0.3) controls the fraction of the score allocated to recency vs similarity. At tw=0.0, the formula reduces to pure similarity search — identical to a standard vector DB. At tw=1.0, only recency matters. The default 0.3 gives recency meaningful influence without letting it override strong similarity signals. For applications where freshness is paramount (real-time news summarizer, live meeting notes), consider tw=0.5 or higher. For applications where factual relevance trumps timing (knowledge base Q&A, policy lookup), tw=0.1 or lower is appropriate. The formula is deliberately simple and interpretable. Every component has a clear meaning, every parameter has a clear effect, and the worked examples above let you predict what any memory's score will be at any point in time — which is exactly what you want when debugging why a memory did or did not surface. **Install:** `pip install feather-db` · **GitHub:** [github.com/feather-store/feather](https://github.com/feather-store/feather) --- *This is the machine-readable mirror of the theory post at [getfeather.store/theory/feather-db-adaptive-scoring-explained](https://getfeather.store/theory/feather-db-adaptive-scoring-explained). For the full Feather DB documentation, see [getfeather.store/llms-full.txt](https://getfeather.store/llms-full.txt).*