How Memory Decay Is Calculated in ai-memory: A Deep Dive into the Retention Score Algorithm
ai-memory calculates memory decay using a mathematically-driven retention score that combines exponential time decay, access frequency reinforcement, breadth-aware multipliers, and feedback-adjusted salience to determine when pages should be evicted from memory.
The akitaonrails/ai-memory project implements a sophisticated decay system for managing long-term memory in AI systems. Unlike simple LRU caches, it uses tunable mathematical formulas that balance recency, usage patterns, collaborative breadth, and explicit user feedback. This article breaks down exactly how the decay calculation works, referencing the actual Rust source code in crates/ai-memory-store/src/decay.rs.
Core Decay Parameters (DecayParams)
The foundation of memory decay calculation in ai-memory is the DecayParams struct, defined at lines 15-33 of decay.rs. This configuration object controls the shape of every decay curve across the system.
| Field | Purpose | Default Value |
|---|---|---|
lambda |
Per-day exponential decay rate for time-since-update | 0.02 (~35-day half-life) |
sigma |
Weight multiplier for access reinforcement | 0.6 |
mu |
Per-day exponential decay rate for days-since-last-access | 0.04 |
salience_default |
Baseline salience without feedback | 1.0 |
cold_threshold |
Score cutoff for eviction candidacy | 0.3 |
hard_delete_after_days |
Tombstone lifetime before permanent removal | 90 |
These defaults are established in the Default implementation at lines 35-44. You can instantiate custom parameters to make memory decay faster or slower based on your use case.
use ai_memory_store::decay::DecayParams;
// Default conservative decay
let params = DecayParams::default();
// Aggressive decay: 10-day half-life, lower threshold
let aggressive = DecayParams {
lambda: 0.07, // ~10 day half-life
cold_threshold: 0.5, // Evict sooner
..Default::default()
};
The Retention Score Formula
The primary function retention_score (lines 48-75) computes whether a memory page survives another day. It accepts four inputs and delegates to the breadth-aware variant with breadth disabled.
Input Parameters
age_days: Days since the page was last updatedaccess_count: Total historical searches that returned this pagedays_since_access: Optional days since most recent accesssalience: Optional explicit salience from user feedback
The function signature in crates/ai-memory-store/src/decay.rs:
pub fn retention_score(
params: &DecayParams,
age_days: f64,
access_count: u32,
days_since_access: Option<f64>,
salience: Option<f64>,
) -> f64
At lines 66-73, this function simply calls retention_score_with_breadth with distinct_actors = 0 and breadth_weight = 0.0, preserving backward compatibility with the original formula.
Breadth-Aware Decay Extension
The full calculation lives in retention_score_with_breadth (lines 77-101). This extension adds a "distinct actors" factor so that pages read by many different operators decay more slowly—a key insight for collaborative knowledge systems.
The Complete Formula
let time_term = salience * (-params.lambda * age_days).exp();
let breadth = 1.0 + breadth_weight * (f64::from(distinct_actors.max(1)) - 1.0).ln_1p();
let access_term = days_since_access.map_or(0.0, |d| {
params.sigma * (1.0 + f64::from(access_count)).ln()
* (-params.mu * d).exp()
* breadth
});
time_term + access_term
Component Breakdown
Time term: salience × e^(-lambda × age_days)
- Exponentially decays based on page age
- Scaled by salience (feedback-adjusted importance)
- With default
lambda = 0.02, halves every ~35 days at base salience
Access term: Complex reinforcement based on usage patterns
ln(access_count + 1): Diminishing returns for repeated accesse^(-mu × days_since_access): Recent access matters more than old access- Multiplied by
sigmato control overall weight
Breadth factor: 1 + breadth_weight × ln(distinct_actors)
- Default
breadth_weight = 0.0disables this feature - When enabled, collaborative pages (many distinct readers) resist decay
- Uses
ln_1pfor numerical stability at low actor counts
The final score is the sum of time and access terms. Pages scoring below cold_threshold become eviction candidates.
Feedback-Driven Salience Adjustment
User feedback directly modifies how memory decay applies to specific pages. The salience_after_feedback function (lines 41-55) implements this closed-loop control.
Feedback Impact Mapping
| Feedback Kind | Salience Change | Result |
|---|---|---|
Helpful |
Increase by 0.25 | Slower decay, longer retention |
NotHelpful |
Decrease by 0.25 | Faster decay, quicker eviction |
Stale or Wrong |
Set to SALIENCE_MIN (0.25) |
Rapid decay, near-immediate candidate status |
The function enforces bounds: SALIENCE_MIN = 0.25 and SALIENCE_MAX = 2.0. This means feedback can at most halve or double the time-term decay rate.
use ai_memory_core::FeedbackKind;
use ai_memory_store::decay::salience_after_feedback;
// Apply positive reinforcement
let boosted = salience_after_feedback(
¶ms,
Some(1.0), // Current salience
FeedbackKind::Helpful
); // Returns 1.25
// Penalize outdated information
let penalized = salience_after_feedback(
¶ms,
Some(1.5),
FeedbackKind::Stale
); // Returns 0.25 (minimum)
The updated salience feeds directly into subsequent retention_score calculations, creating a feedback loop between user judgments and automatic memory management.
The Forget-Sweep Eviction Process
Decay calculations alone don't remove pages—a periodic forget-sweep job orchestrates actual eviction. This process spans multiple files in the codebase.
Sweep Implementation Flow
- Query candidates:
reader.decay_candidatesfilters pages potentially below threshold - Recompute scores: Fresh retention scores using latest access statistics
- Soft delete:
soft_delete_for_decay_if_latestcreates tombstone for confirmed candidates (score <cold_threshold) - Hard delete:
hard_delete_decayed_page_chainpermanently removes tombstones older thanhard_delete_after_days
The sweep logic resides primarily in crates/ai-memory-store/src/ops.rs and is invoked by crates/ai-memory-wiki/src/wiki.rs as a background maintenance task.
Practical Calculation Examples
Example 1: Abandoned Page
use ai_memory_store::decay::{DecayParams, retention_score};
let params = DecayParams::default();
// Page: 60 days old, accessed 3 times, last access 45 days ago
let score = retention_score(¶ms, 60.0, 3, Some(45.0), None);
// Calculation: 1.0 * e^(-0.02*60) + 0.6 * ln(4) * e^(-0.04*45)
// ≈ 0.301 + 0.6 * 1.386 * 0.165
// ≈ 0.301 + 0.137 = 0.438
// Result: Above cold_threshold (0.3), survives this sweep
Example 2: Actively Used Page with Breadth
use ai_memory_store::decay::retention_score_with_breadth;
// Same page, but with 5 distinct readers and breadth enabled
let score = retention_score_with_breadth(
¶ms, 60.0, 3, Some(5.0), None, 5, 0.3
);
// Breadth factor: 1 + 0.3 * ln(5) ≈ 1.48
// Access term now multiplied by 1.48, significantly boosting retention
Example 3: Negative Feedback Cascade
let params = DecayParams::default();
// Initial state: healthy page
let mut salience = Some(1.0);
let mut score = retention_score(¶ms, 30.0, 20, Some(2.0), salience);
// score ≈ 0.55 + 0.52 = 1.07 (well above threshold)
// Receive Wrong feedback
salience = Some(salience_after_feedback(¶ms, salience, FeedbackKind::Wrong));
// salience = 0.25
// Recalculate
score = retention_score(¶ms, 30.0, 20, Some(2.0), salience);
// score ≈ 0.138 + 0.13 = 0.268 (below threshold, eviction candidate)
Key Files and Their Roles
| File Path | Functionality |
|---|---|
crates/ai-memory-store/src/decay.rs |
Core decay mathematics: DecayParams, retention_score, retention_score_with_breadth, salience_after_feedback |
crates/ai-memory-store/src/ops.rs |
Database operations: soft_delete_for_decay_if_latest, hard_delete_decayed_page_chain, decay_candidates query |
crates/ai-memory-wiki/src/wiki.rs |
Orchestrates periodic background sweep using decay logic |
crates/ai-memory-core/src/feedback.rs |
Defines FeedbackKind enum used in salience adjustment |
Summary
- Memory decay in ai-memory is calculated through a tunable retention score combining exponential time decay, logarithmic access reinforcement, and optional breadth multipliers.
- The
DecayParamsstruct indecay.rsprovides six coefficients to customize decay curves without code changes. retention_scoreandretention_score_with_breadthimplement the pure mathematical functions—no side effects, easy to test and reason about.- Feedback integration via
salience_after_feedbackcreates a closed loop where user judgments directly modify mathematical decay rates. - Eviction is two-phase: soft-delete creates tombstones scored below
cold_threshold, then hard-delete removes them afterhard_delete_after_days.
Frequently Asked Questions
How does the lambda parameter affect memory decay?
The lambda parameter controls the exponential decay rate for the time-since-update term in the retention score formula. With the default value of 0.02, a page loses half its time-term contribution approximately every 35 days. Increasing lambda to 0.07 shortens this half-life to roughly 10 days, making the system forget older information faster. This parameter directly scales the exponent in e^(-lambda × age_days) as implemented in crates/ai-memory-store/src/decay.rs lines 89-90.
Can memory decay be disabled entirely?
Complete disabling is not supported, but you can approximate it by setting extreme parameter values. Set lambda and mu to 0.0 to eliminate exponential decay, sigma to a large value to maximize access reinforcement, and cold_threshold to near zero. However, this configuration is untested and may cause unbounded memory growth. The system is designed for managed decay—consider adjusting hard_delete_after_days to a large value instead if you need extended retention.
What happens when multiple users access the same page?
When breadth_weight is non-zero in retention_score_with_breadth, pages accessed by more distinct actors receive a multiplicative boost to their access term. The breadth factor 1 + breadth_weight × ln(distinct_actors) means that purely collaborative pages resist decay proportionally to how widely they've been read. This implements the insight that broadly useful institutional knowledge should persist longer than personally relevant but niche information.
How does feedback interact with the decay calculation?
Feedback modifies the salience variable that scales the time term in every retention score calculation. Helpful feedback increases salience by 0.25 (capped at 2.0), causing the page to decay more slowly. NotHelpful decreases it by 0.25 (floored at 0.25). Stale or Wrong feedback immediately drops salience to the minimum of 0.25, often pushing the page below cold_threshold and triggering eviction. This salience adjustment persists across all future decay calculations until new feedback arrives.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →