# How ai-memory Handles Memory Decay and Eviction: A Deep Dive into Salience-Based Forgetting

> Discover how ai-memory manages memory decay and eviction using salience-based forgetting. Learn about exponential decay, tombstones, and data eviction for optimal knowledge management.

- Repository: [Fabio Akita/ai-memory](https://github.com/akitaonrails/ai-memory)
- Tags: deep-dive
- Published: 2026-08-28

---

**`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`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/decay.rs). Here, the `DecayParams` struct defines the exponential decay rates:

```rust
// 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`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/ops.rs):

```rust
// 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:

```rust
// 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`](https://github.com/akitaonrails/ai-memory/blob/main/ops.rs) writes a decay tombstone:

```rust
// 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`](https://github.com/akitaonrails/ai-memory/blob/main/ops.rs) (around line 1910) handles complex deletion:

```sql
-- 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`](https://github.com/akitaonrails/ai-memory/blob/main/V03__decay.sql) | Creates initial decay columns (`salience`, `last_accessed`, `superseded_at`) |
| [`V49__decay_tombstone_index.sql`](https://github.com/akitaonrails/ai-memory/blob/main/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:

```rust
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:

```bash

# Run complete M8 forget sweep (tombstone + purge)

ai-memory forget

```

## Summary

- **`DecayParams`** in [`decay.rs`](https://github.com/akitaonrails/ai-memory/blob/main/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.