# How ai-memory Calculates Memory Decay: Retention Scores, Decay Parameters & Eviction Logic

> Discover how ai-memory calculates memory decay. Explore retention scores, decay parameters, and eviction logic to understand how your AI remembers and forgets.

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

---

**ai-memory calculates memory decay using a tunable retention score that combines exponential time decay, access reinforcement, user feedback salience, and optional breadth multipliers to determine when pages should be evicted.**

The `akitaonrails/ai-memory` repository implements a mathematically-driven decay system in Rust. Rather than simple least-recently-used (LRU) eviction, it uses a sophisticated scoring function that weighs page age, access patterns, explicit user feedback, and even how many distinct actors have interacted with content. This article breaks down exactly how the formulas work and where to find them in the source.

---

## Decay Parameters: The Tunable Constants

All decay behavior is controlled by the **`DecayParams`** struct in [`crates/ai-memory-store/src/decay.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/decay.rs) (lines 15-33). These six fields shape every retention decision:

| Field | Default | Purpose |
|-------|---------|---------|
| `lambda` | `0.02` | Per-day exponential decay rate for the time-since-update term (~35 day half-life) |
| `sigma` | `0.6` | Weight multiplier for the access-reinforcement term |
| `mu` | `0.04` | Per-day exponential decay rate for days-since-last-access |
| `salience_default` | `1.0` | Baseline salience when no explicit feedback exists |
| `cold_threshold` | `0.1` | Score boundary below which pages become eviction candidates |
| `hard_delete_after_days` | `90` | Tombstone lifetime before permanent removal |

The default implementation (`lines 35-44`) provides these values, but operators can override any parameter for different decay curves.

---

## Retention Score Formula: The Core Decay Calculation

The **`retention_score`** function (lines 48-75) is the entry point for calculating page value. It delegates to `retention_score_with_breadth` with breadth disabled, preserving backward compatibility with the original formula.

### Base Inputs

- `age_days` — days elapsed since `updated_at`
- `access_count` — cumulative search hits against the page
- `days_since_access` — optional days since most recent read
- `salience` — optional explicit salience from user feedback

### Breadth-Aware Extension

When enabled, **`retention_score_with_breadth`** (lines 77-101) adds a "distinct actors" multiplier:

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

```

**Three components drive the final score:**

- **Time term (`salience * e^(-lambda * age_days)`)** — unconditional exponential decay scaled by salience; pages lose value over time regardless of access
- **Access term** — logarithmic reward for total accesses (`ln(access_count + 1)`), multiplied by exponential decay of recency (`e^(-mu * days_since_access)`) and breadth
- **Breadth factor (`1 + breadth_weight * ln(distinct_actors)`)** — pages touched by many different operators decay more slowly; set `breadth_weight = 0.0` to disable

If the summed score drops below `cold_threshold`, the page qualifies for soft deletion.

---

## Feedback-Driven Salience Adjustment

User feedback directly modifies how quickly pages decay. The **`salience_after_feedback`** function (lines 41-55) maps `FeedbackKind` to salience shifts:

| Feedback | Effect | Salience Range |
|----------|--------|---------------|
| `Helpful` | Increase by 0.25 | Clamped to `SALIENCE_MAX = 2.0` |
| `NotHelpful` | Decrease by 0.25 | Clamped to `SALIENCE_MIN = 0.25` |
| `Stale` / `Wrong` | Drop to minimum | Set to `0.25` immediately |

The adjusted salience feeds directly into the `time_term` (and indirectly `access_term` via breadth), creating a feedback loop where valued content persists longer and rejected content accelerates toward eviction.

---

## The Forget-Sweep Eviction Process

Decay calculation triggers actual eviction through a coordinated three-phase sweep implemented across [`ops.rs`](https://github.com/akitaonrails/ai-memory/blob/main/ops.rs) and [`wiki.rs`](https://github.com/akitaonrails/ai-memory/blob/main/wiki.rs):

1. **Candidate identification** — `reader.decay_candidates` queries pages likely below threshold based on stored metadata
2. **Score recomputation** — fresh access statistics re-evaluate each candidate's current retention score
3. **Soft deletion** — pages scoring below `cold_threshold` become tombstones via `soft_delete_for_decay_if_latest`
4. **Hard deletion** — tombstones exceeding `hard_delete_after_days` are permanently purged via `hard_delete_decayed_page_chain`

This two-stage deletion (soft then hard) allows for potential recovery mechanisms or audit trails before permanent data loss.

---

## Complete Working Example

```rust
use ai_memory_store::decay::{DecayParams, retention_score, salience_after_feedback};
use ai_memory_core::FeedbackKind;

// Default decay configuration
let params = DecayParams::default();

// Page: 30 days old, 10 total accesses, last read 2 days ago
let score = retention_score(&params, 30.0, 10, Some(2.0), None);
println!("Retention score: {:.4}", score);  // Example: ~0.42

// User marks as helpful: salience increases from 1.0 → 1.25
let boosted_salience = salience_after_feedback(&params, None, FeedbackKind::Helpful);
let boosted_score = retention_score(&params, 30.0, 10, Some(2.0), Some(boosted_salience));
println!("After helpful feedback: {:.4}", boosted_score);  // Higher persistence

```

---

## Summary

- **ai-memory decay** uses exponential formulas, not simple timestamps, to model memory degradation
- **`DecayParams`** in [`decay.rs`](https://github.com/akitaonrails/ai-memory/blob/main/decay.rs) provides six tunable coefficients controlling curve shape
- **Retention score** combines time decay, access reinforcement logarithms, and optional breadth multipliers
- **Feedback signals** directly modulate salience, creating user-influenced memory persistence
- **Two-phase eviction** soft-deletes candidates below threshold, then hard-deletes aged tombstones

---

## Frequently Asked Questions

### How does ai-memory memory decay differ from standard LRU cache eviction?

ai-memory replaces fixed-size LRU with a scored decay model where retention depends on mathematical functions of age, access patterns, and explicit feedback rather than recency alone. The `retention_score_with_breadth` formula allows configuration of half-lives (`lambda`, `mu`) and user-driven salience shifts that no LRU can express.

### What happens when breadth_weight is set to zero?

Setting `breadth_weight = 0.0` disables the distinct-actors multiplier, collapsing `retention_score_with_breadth` to the original `retention_score` behavior. The breadth factor becomes exactly `1.0`, and decay depends solely on time, access count, recency, and salience.

### Can retention scores increase over time, or only decrease?

Scores can increase. A surge in `access_count`, a recent access resetting `days_since_access`, positive `FeedbackKind::Helpful` signals raising salience, or new distinct actors (with breadth enabled) all boost the computed score. The formulas are dynamic evaluations, not monotonic counters.

### Where is the cold_threshold actually enforced during eviction?

The threshold comparison happens during the forget-sweep in [`crates/ai-memory-store/src/ops.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/ops.rs), where recomputed scores are checked before `soft_delete_for_decay_if_latest` tombstones the page. The sweep runner in [`crates/ai-memory-wiki/src/wiki.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/wiki.rs) orchestrates this periodic job.