Decay Retention Formula in ai-memory-store: How It Calculates Page Eviction Scores
The decay retention formula in ai-memory-store computes a page's retention score by combining a time-decayed salience term with an access-frequency term, evicting pages that fall below a configurable cold threshold.
The ai-memory repository (akitaonrails/ai-memory) implements a deterministic forgetting mechanism in its storage layer to manage memory retention. The core logic resides in crates/ai-memory-store/src/decay.rs, which provides pure mathematical functions that determine whether a stored page should be kept or evicted based on age, access patterns, and optional breadth metrics.
The Mathematical Formula
The retention formula calculates a final score as the sum of two components: a time term representing intrinsic value decay and an access term representing usage reinforcement.
score = time_term + access_term
Pages with scores below params.cold_threshold (default 0.20) become candidates for eviction.
Time Term (Salience Decay)
The time term models how a page's intrinsic salience decays exponentially over calendar age:
time_term = salience * exp(-λ * age_days)
λ(params.lambda): The per-day exponential decay rate, defaulting to 0.02 (approximately a 35-day half-life).salience: The page's explicit salience value, orparams.salience_default(1.0) when not specified.age_days: The page's age in days since creation or last major update.
Access Term (Frequency and Recency)
The access term rewards pages with high access counts while penalizing stale access patterns:
access_term = σ * ln(1 + access_count) * exp(-μ * days_since_access) * breadth
σ(params.sigma): The magnitude of the access-reinforcement boost.access_count: Total historical search hits for the page.μ(params.mu): The per-day decay rate applied to the age of the last access, defaulting to 0.04.days_since_access: Days elapsed since the page was last accessed.breadth: An optional multiplier for distinct actor counts (see below).
Breadth Multiplier (Optional)
When tracking distinct actors, the system applies a breadth scaling factor:
breadth = 1.0 + breadth_weight * ln(1 + (distinct_actors.max(1) - 1))
By default, breadth_weight is 0.0, disabling this feature. When enabled (e.g., breadth_weight = 0.5), pages accessed by multiple distinct operators receive a logarithmic boost to their retention scores.
Implementation in crates/ai-memory-store/src/decay.rs
The source file defines two public entry points and a configuration struct:
retention_score: The standard interface (lines 48–75) that calls the breadth-aware variant withdistinct_actors = 0andbreadth_weight = 0.0.retention_score_with_breadth: The full implementation (lines 94–119) that accepts actor diversity parameters.DecayParams: A configuration struct (lines 15–33) bundling all formula constants with aDefaultimplementation (lines 35–45) that sets λ = 0.02, σ = 0.6, μ = 0.04, andcold_threshold= 0.20.
All functions are pure—they depend only on their inputs and produce deterministic outputs—making them fully unit-tested in the #[cfg(test)] module at the bottom of the file.
Code Examples
Basic Retention Scoring
Calculate scores using the default parameter set:
use ai_memory_store::decay::{DecayParams, retention_score};
fn main() {
// Default parameters (λ = 0.02, σ = 0.6, μ = 0.04)
let params = DecayParams::default();
// Fresh page with no access history
let score = retention_score(
¶ms,
0.0, // age_days
0, // access_count
None, // days_since_access
None // salience
);
println!("Fresh page score ≈ {score:.3}"); // ≈ 1.0
// Old page (200 days) with many recent hits
let score = retention_score(
¶ms,
200.0, // age_days
50, // access_count
Some(2.0), // days_since_access
None // salience
);
println!("Hot old page score ≈ {score:.3}");
}
Scoring with Actor Breadth
Include distinct actor counts for collaborative filtering scenarios:
use ai_memory_store::decay::{DecayParams, retention_score_with_breadth};
fn main() {
let params = DecayParams::default();
// Page accessed by 10 distinct operators with breadth weighting
let score = retention_score_with_breadth(
¶ms,
30.0, // age_days
20, // access_count
Some(1.0), // days_since_access
None, // salience
10, // distinct_actors
0.5 // breadth_weight
);
println!("Score with breadth ≈ {score:.3}");
}
Integration with the Eviction Pipeline
The decay functions integrate into the storage layer through several coordinated components:
crates/ai-memory-store/src/writer.rs: Contains the forgetting-sweep job that iterates over stored rows, invokesretention_score, and marks low-scoring pages for eviction.crates/ai-memory-store/src/ops.rs: Provides high-level API operations that rely on the decay logic to make eviction decisions during memory pressure events.crates/ai-memory-store/migrations/V03__decay.sql: Database migration adding theaccess_countandlast_accessed_atcolumns required by the formula.
This architecture separates the pure mathematical policy (in decay.rs) from the imperative eviction mechanics (in writer.rs), allowing the retention strategy to be tested and tuned independently of database operations.
Summary
- The decay retention formula combines exponential time decay with logarithmic access frequency to produce a deterministic retention score.
- Two public functions—
retention_scoreandretention_score_with_breadth—implement the logic incrates/ai-memory-store/src/decay.rs. - Configurable parameters (
DecayParams) control decay rates (λ, μ), access boost magnitude (σ), and the eviction threshold (default 0.20). - Pure functional design ensures deterministic, testable behavior without side effects.
- The breadth multiplier optionally rewards pages accessed by multiple distinct actors when
breadth_weight > 0.0.
Frequently Asked Questions
What is the default half-life for page salience?
With the default lambda value of 0.02, the time-decay component has an approximate half-life of 35 days. This means a page's intrinsic salience decays to 50% of its original value roughly five weeks after creation if never accessed.
How does the breadth weight affect retention scores?
The breadth_weight parameter (default 0.0) scales the impact of distinct actor counts. When set above zero, the formula applies a logarithmic multiplier ln(1 + distinct_actors - 1) to the access term, meaning pages touched by multiple operators receive higher retention scores than pages accessed by a single operator with the same frequency.
What happens when a page's score drops below the cold threshold?
Pages scoring below params.cold_threshold (default 0.20) become candidates for eviction during the next forgetting-sweep cycle executed by writer.rs. The sweep job periodically queries stored pages, recalculates their scores using current timestamps and access statistics, and removes entries that fail to exceed the threshold.
Where are the decay parameters configured?
All parameters are bundled in the DecayParams struct defined in crates/ai-memory-store/src/decay.rs (lines 15–33). The system uses the Default trait implementation for standard values, but instances can be constructed programmatically with custom values for lambda, sigma, mu, salience_default, cold_threshold, and other tuning constants.
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 →