# How the ai-memory Decay and Sweep Mechanism Works for Automatic Memory Pruning

> Understand the ai-memory decay and sweep mechanism. Learn how exponential decay and access frequency prune cold pages automatically, optimizing memory usage.

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

---

**The ai-memory decay and sweep mechanism calculates retention scores using exponential time decay and access frequency, then soft-deletes cold pages below a configurable threshold before permanently removing them after a grace period.**

The akitaonrails/ai-memory repository implements an automatic memory pruning system that prevents unbounded SQLite storage growth. This decay and sweep mechanism evaluates page metadata to determine which observations and hand-offs have become stale, removing them through a two-phase deletion process that balances storage reclamation with operational safety.

## How Retention Scoring Works

### The Decay Formula

The core algorithm lives in [`crates/ai-memory-store/src/decay.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/decay.rs). The `retention_score` function combines **time-decay** with **access-boost** terms to determine whether a page remains valuable.

The formula applies two exponential decay factors:

1. **Age decay**: `exp(-λ·age)` where λ (`lambda`) controls how quickly old pages lose value
2. **Access decay**: `σ·log(1+access_count)·exp(-μ·days_since_access)` where σ (`sigma`) weights access frequency and μ (`mu`) decays the boost over time

The `DecayParams` struct encapsulates these coefficients along with `cold_threshold` (the score below which pages become deletion candidates).

### Optional Breadth Weighting

For multi-actor scenarios, the `retention_score_with_breadth` function adds a `breadth_weight` parameter. This adjusts scores based on the number of distinct actors who have accessed the page, preventing premature deletion of widely-shared knowledge.

## Candidate Selection and Sweep Actions

### Scanning for Decay Candidates

The `Reader` struct in [`crates/ai-memory-store/src/reader.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/reader.rs) identifies targets through two query methods:

- **`decay_candidates`** (lines ~2962–2986): Returns pages where `retention_score < cold_threshold` and `pinned = false`
- **`decay_tombstones_before`** (lines ~2997–3006): Returns already-soft-deleted rows older than `hard_delete_after_days`

Both queries exclude **pinned pages** (where `pinned = true`), ensuring critical data survives sweeps regardless of score.

### Soft-Delete and Tombstone Creation

When a candidate is identified, the system calls `soft_delete_for_decay_if_latest` in [`crates/ai-memory-store/src/ops.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/ops.rs) (lines ~1729–1760). This operation:

1. Verifies the page is the latest version in its chain
2. Sets `supersedes = NULL` and `superseded_at = now()` to create a tombstone
3. Preserves the data for the grace period defined by `hard_delete_after_days`

Soft-deleted pages remain readable but are excluded from future candidate scans, preventing redundant processing.

### Hard-Delete with Recursive Chain Removal

After the grace period expires, `hard_delete_decayed_page_chain` (lines ~1883–1912 in [`ops.rs`](https://github.com/akitaonrails/ai-memory/blob/main/ops.rs)) executes a **recursive CTE** that walks the ancestry chain (`decay_chain`) and deletes every row in a single transaction. This SQLite-level recursion guarantees **linear-time execution** regardless of chain depth.

## The Background Sweep Scheduler

The `ai-memory-mcp` server coordinates automatic pruning via `Server::run_forget_sweep` (around line 2125 in [`crates/ai-memory-mcp/src/server.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/server.rs)). This background task:

1. Runs every `interval_seconds` (default 600 seconds)
2. Loads current `DecayParams` from [`config.toml`](https://github.com/akitaonrails/ai-memory/blob/main/config.toml) on each iteration
3. Pulls candidates and tombstones via the `Reader`
4. Executes soft-deletes for candidates
5. Executes hard-deletes for expired tombstones

Because the scheduler reads configuration fresh on every cycle, parameter changes take effect immediately without server restart.

## Configuration and Tuning

Configure the mechanism via the `[decay]` section in [`config.toml`](https://github.com/akitaonrails/ai-memory/blob/main/config.toml):

```toml
[decay]
lambda = 0.02                # per‑day exponential decay of age

sigma = 0.6                  # strength of access‑boost

mu = 0.04                    # decay of the access‑boost term

salience_default = 1.0
cold_threshold = 0.20        # below this → candidate

hard_delete_after_days = 180
breadth_weight = 0.0         # optional per‑actor weighting

interval_seconds = 600       # how often the sweep runs

```

Adjust `cold_threshold` to make the system more or less aggressive, or modify `hard_delete_after_days` to extend the recovery window for soft-deleted pages.

## Code Examples

Programmatically evaluate retention scores using the decay module:

```rust
use ai_memory_store::{decay::{DecayParams, retention_score}, Store};

#[tokio::main]
async fn main() {
    let store = Store::open("./ai_memory.db").await.unwrap();
    
    // Retrieve page metadata
    let page = store.reader.get_page("notes/todo.md").await.unwrap();
    let age_days = page.age_days();
    let access_cnt = page.access_count;
    let days_since_access = page.days_since_last_access();

    // Calculate retention score
    let params = DecayParams::default();
    let score = retention_score(
        &params,
        age_days,
        access_cnt,
        days_since_access,
        page.salience,
    );

    if score < params.cold_threshold {
        store.writer.soft_delete_for_decay_if_latest(&page.id).await.unwrap();
    }
}

```

Manually trigger the sweep process for testing:

```bash

# Start the server with automatic sweeping

ai-memory --config ./config.toml

# Or invoke manually

ai-memory-mcp sweep-decay

```

## Summary

- **Exponential decay formula**: Combines age decay and access frequency in [`crates/ai-memory-store/src/decay.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/decay.rs)
- **Two-phase deletion**: Soft-delete creates tombstones with a grace period; hard-delete uses recursive SQL for permanent removal
- **Pinned protection**: Setting `pinned = true` exempts pages from all candidate scans
- **Configurable thresholds**: `cold_threshold` and `hard_delete_after_days` control aggressiveness and safety windows
- **SQLite-backed recursion**: The `hard_delete_decayed_page_chain` function leverages recursive CTEs for efficient chain deletion

## Frequently Asked Questions

### What happens to pinned pages during a sweep?

Pinned pages are excluded from both `decay_candidates` and `decay_tombstones_before` queries. When `pinned = true`, the page bypasses scoring entirely and survives regardless of age or access patterns. You must manually unpin a page before it becomes eligible for decay.

### How is the retention score calculated?

The score combines `exp(-λ·age)` with `σ·log(1+access_count)·exp(-μ·days_since_access)`. The `retention_score` function in [`crates/ai-memory-store/src/decay.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/decay.rs) multiplies these terms by the page's `salience` value. Higher access counts boost the score, but this boost decays exponentially based on days since last access.

### Can I recover a page after it has been soft-deleted?

Yes. Soft-deletion creates a tombstone row that persists for `hard_delete_after_days` (default 180 days). During this grace period, operators can restore the page using an admin "undelete" command that clears the tombstone flags. Once hard-deletion runs, the page and its entire ancestry chain are permanently removed via recursive SQL deletion.

### How often does the sweep run?

The background task runs every `interval_seconds` as defined in [`config.toml`](https://github.com/akitaonrails/ai-memory/blob/main/config.toml) (default 600 seconds/10 minutes). The scheduler reads fresh `DecayParams` on each iteration, so configuration changes apply immediately to the next sweep cycle without requiring server restart.