How ai-memory Handles Memory Decay and Eviction: A Deep Dive into Salience-Based Forgetting
ai-memory treats every stored page as a time-sensitive knowledge item and uses exponential decay on salience scores to gradually phase out stale information, culminating in soft-delete tombstones and eventual hard eviction for rarely accessed data.
This Rust-based memory store, developed by akitaonrails, implements a biologically-inspired forgetting mechanism. Instead of crude LRU caches or arbitrary TTLs, it models memory as a continuously degrading resource where importance decays over time unless reinforced by feedback. The system keeps frequently used knowledge accessible while automatically reclaiming storage from obsolete entries.
Core Decay Parameters and Configuration
The mathematical foundation lives in crates/ai-memory-store/src/decay.rs. Here, the DecayParams struct defines the exponential decay rates:
// From decay.rs around lines 17-23
pub struct DecayParams {
pub per_day_age_decay: f64, // Decay based on time since last update
pub per_day_access_decay: f64, // Decay based on time since last access
}
per_day_age_decay— Applied to the interval since a page was updatedper_day_access_decay— Applied to the interval since a page was last accessed
These parameters create a dual-factor decay model where both staleness (old content) and disuse (unpopular content) accelerate forgetting.
Computing Salience After Feedback
Whenever a page receives new signals—user ratings, search hits, or observational feedback—the salience_after_feedback function recalculates its standing. This happens in crates/ai-memory-store/src/ops.rs:
// From ops.rs around line 1687
let next_salience = crate::decay::salience_after_feedback(
params,
current_salience,
kind,
);
The function:
- Applies exponential decay to the current salience using both time factors
- Adds a feedback boost proportional to the signal strength and type
- Returns the new bounded salience value
This design ensures that well-maintained knowledge resists decay while neglected information gradually drifts toward eviction thresholds.
Identifying Decay Candidates: The M8 Forget Sweep
The periodic cleanup mechanism—internally designated M8 or the "weekly forget sweep"—queries pages eligible for decay. The Reader::decay_candidates method performs this selection:
// From reader.rs around line 3132
pub async fn decay_candidates(&self, threshold: f64) -> Result<Vec<PageId>, Error> {
// Returns pages with salience below threshold, excluding pinned entries
}
Key behaviors:
- Pages marked with
pinned = trueare excluded entirely—these represent "decay-immune" knowledge - Results are ordered by salience, lowest first, enabling prioritized cleanup
- The threshold is configurable via
DecayParams
Soft-Delete: Creating Decay Tombstones
Before permanent removal, the system performs a soft delete that preserves audit trails. The soft_delete_for_decay_if_latest function in ops.rs writes a decay tombstone:
// From ops.rs around line 1731
pub async fn soft_delete_for_decay_if_latest(
&self,
page_id: PageId,
) -> Result<bool, Error> {
// Sets superseded_at timestamp if this is the latest version
}
The tombstone mechanism (superseded_at timestamp) serves multiple purposes:
- Maintains ancestry chains for versioned content
- Allows recovery of recently-decayed items if needed
- Creates a grace period before physical deletion
Hard-Delete and Chain Eviction
After the configured grace period expires, hard_delete_decayed_page_chain performs permanent removal. This recursive SQL operation in ops.rs (around line 1910) handles complex deletion:
-- Simplified structure of the recursive query used
WITH RECURSIVE decay_chain(id) AS (
SELECT id FROM pages
WHERE superseded_at < $1 AND pinned = false
UNION ALL
SELECT p.id FROM pages p
JOIN decay_chain dc ON p.parent_id = dc.id
WHERE p.superseded_at IS NOT NULL
)
DELETE FROM pages WHERE id IN (SELECT id FROM decay_chain);
This chain-aware eviction ensures that entire obsolete version histories are cleaned up, not just individual snapshots.
Database Schema Support
The persistence layer includes dedicated migrations for decay functionality:
| Migration | Purpose |
|---|---|
V03__decay.sql |
Creates initial decay columns (salience, last_accessed, superseded_at) |
V49__decay_tombstone_index.sql |
Adds indexed lookup for efficient tombstone queries |
These schema decisions reflect production concerns: decay operations must remain fast even with millions of pages, and the tombstone index prevents table scans during cleanup sweeps.
Practical Implementation Example
Here's how to manually trigger the decay pipeline using the public API:
use ai_memory_store::{Store, decay::DecayParams};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize store with configuration
let store = Store::new(config).await?;
// Step 1: Identify decay candidates below threshold
let candidates = store
.reader()
.await
.decay_candidates(0.1) // threshold salience
.await?;
println!("Found {} pages for decay", candidates.len());
// Step 2: Apply soft-delete tombstones
for page_id in candidates {
store
.ops()
.soft_delete_for_decay_if_latest(page_id)
.await?;
}
// Step 3: Purge expired tombstones after grace period
let params = DecayParams::default();
store
.ops()
.hard_delete_decayed_page_chain(¶ms)
.await?;
Ok(())
}
For operational convenience, the CLI provides a single command:
# Run complete M8 forget sweep (tombstone + purge)
ai-memory forget
Summary
DecayParamsindecay.rsdefines dual exponential decay rates for age and access patterns- Salience computation combines decay with feedback boosts in
salience_after_feedback decay_candidates(reader.rs) identifies low-salience pages, respecting pinned exclusions- Soft-delete tombstones (
superseded_at) create recoverable grace periods viasoft_delete_for_decay_if_latest - Hard eviction uses recursive SQL in
hard_delete_decayed_page_chainfor complete chain removal - M8 sweep automates this pipeline, available both programmatically and via
ai-memory forgetCLI
Frequently Asked Questions
How does ai-memory prevent important memories from being deleted?
Pinned pages (pinned = true) are completely excluded from decay processing. The decay_candidates query filters these out, and they bypass both soft-delete and hard-delete operations. This serves as a manual override for critical knowledge that should persist indefinitely regardless of access patterns.
What is the difference between age decay and access decay?
Age decay (per_day_age_decay) penalizes content based on time since last modification—favoring recently updated information. Access decay (per_day_access_decay) penalizes based on time since last read—favoring frequently consulted information. Together they ensure that actively maintained and actively used knowledge survives longest.
Can decayed memories be recovered after soft-delete?
Yes, during the grace period. The tombstone (superseded_at timestamp) marks a page as decayed but preserves its data. Recovery would involve clearing the timestamp and recalculating salience. Once hard_delete_decayed_page_chain executes, however, the data is permanently removed along with its entire decayed ancestry chain.
How do I configure decay parameters for my deployment?
Modify DecayParams at store initialization. The struct accepts per_day_age_decay and per_day_access_decay as f64 values (typically between 0.0 and 1.0). Higher values accelerate forgetting. The grace period for hard deletion is also configured here. These settings can be tuned based on your data velocity and retention requirements.
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 →