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 updated
  • per_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:

  1. Applies exponential decay to the current salience using both time factors
  2. Adds a feedback boost proportional to the signal strength and type
  3. 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 = true are 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(&params)
        .await?;
    
    Ok(())
}

For operational convenience, the CLI provides a single command:


# Run complete M8 forget sweep (tombstone + purge)

ai-memory forget

Summary

  • DecayParams in decay.rs defines 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 via soft_delete_for_decay_if_latest
  • Hard eviction uses recursive SQL in hard_delete_decayed_page_chain for complete chain removal
  • M8 sweep automates this pipeline, available both programmatically and via ai-memory forget CLI

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →