How ai-memory Implements Decay Math and Salience-Scaled Retention for Forget-Sweep
ai-memory calculates retention scores using exponential decay formulas that combine page age, access frequency, operator feedback, and breadth of readership to determine which episodic pages survive the periodic forget-sweep.
The akitaonrails/ai-memory repository implements a deterministic, mathematically-grounded approach to knowledge eviction in its long-term memory store. The algorithm lives primarily in crates/ai-memory-store/src/decay.rs, where pure functions compute retention scores without hidden state—making the sweep fully testable and predictable.
Core Decay Parameters in DecayParams
The DecayParams struct defines six coefficients that shape the retention curve. These defaults are tuned for roughly 35-day half-life on aging content:
| Field | Default | Purpose |
|---|---|---|
lambda |
0.02 | Exponential decay per day for page age |
sigma |
0.6 | Magnitude of access-reinforcement term |
mu |
0.04 | Decay per day for days-since-last-access |
salience_default |
1.0 | Fallback when no operator feedback exists |
cold_threshold |
0.5 | Score floor for eviction candidacy |
hard_delete_after_days |
30 | Tombstone lifetime before permanent removal |
The Default implementation for DecayParams lives at lines 15-45 of crates/ai-memory-store/src/decay.rs. All parameters are pub and can be overridden at store initialization or via the MCP server admin API.
The Retention Score Formula
Basic Score: retention_score()
The entry point retention_score() (lines 48-75) accepts age, access count, days since access, and optional salience. It delegates to retention_score_with_breadth() with breadth_weight: 0.0, preserving backward compatibility with the historical formula.
Extended Score: retention_score_with_breadth()
Lines 94-119 contain the full computation. The final score sums two terms:
Time term: salience · exp(-λ · age_days)
Access term: σ · ln(1 + access_count) · exp(-μ · days_since_access) · breadth
The breadth factor rewards pages read by distinct operators: 1 + breadth_weight · ln(1 + distinct_actors - 1). This prevents "siloed" pages—those accessed many times by one operator—from gaming the access term.
use ai_memory_store::decay::{DecayParams, retention_score};
let params = DecayParams::default();
let score = retention_score(
¶ms,
120.0, // age_days: 4 months old
35, // access_count
Some(3.0), // days_since_access: recent hit
None, // no explicit salience
);
// Score combines: (1.0 · e^(-2.4)) + (0.6 · ln(36) · e^(-0.12))
Salience Scaling via Operator Feedback
The salience_after_feedback() function (lines 40-55) adjusts a page's retention multiplier based on operator judgments. The implementation uses hard bounds and discrete steps:
- SALIENCE_MIN: 0.25 (floor for Stale/Wrong feedback)
- SALIENCE_MAX: 2.0 (ceiling)
- SALIENCE_STEP: 0.25 (increment/decrement for Helpful/NotHelpful)
Stale or Wrong feedback immediately drops salience to 0.25. Helpful increments by 0.25; NotHelpful decrements by 0.25.
use ai_memory_store::decay::{DecayParams, salience_after_feedback};
use ai_memory_core::FeedbackKind;
let params = DecayParams::default();
let boosted = salience_after_feedback(¶ms, None, FeedbackKind::Helpful);
// Returns 1.25: default 1.0 + SALIENCE_STEP
let penalized = salience_after_feedback(¶ms, Some(1.5), FeedbackKind::Wrong);
// Returns 0.25: immediate floor
The feedback system creates a closed-loop decay model: operator engagement directly modulates the mathematical survival curve.
The Four-Stage Forget-Sweep Flow
Stage 1: Candidate Selection
Reader::decay_candidates() (lines 3183-3218 in reader.rs) queries the pages table and filters:
- Computed retention score below
cold_threshold pinned = false(pinned pages are decay-immune)- Not already superseded
The query applies the retention_score formula in SQL or loads rows for Rust-side scoring depending on index availability.
Stage 2: Soft Delete
For each candidate, Ops::soft_delete_for_decay_if_latest (lines 1655-1720 in ops.rs) marks:
superseded_at = NOW()on the page record- Creates a decay tombstone entry tracking the deletion reason
Soft deletion preserves version history and allows potential resurrection if the page is re-imported.
Stage 3: Hard Delete
After hard_delete_after_days elapse, Reader::decay_tombstones_before() lists expired tombstones. Ops::hard_delete_decayed_page_chain (lines 1800-1885) recursively removes:
- The tombstone record
- All version ancestry from the database
- Associated wiki files on disk
Stage 4: Pinning Protection
Pages can be manually pinned via CLI or API. The pinned boolean is checked in every sweep query, providing a simple override for critical knowledge.
use ai_memory_store::Store;
let store = Store::open("data/")?;
store.run_forget_sweep(&DecayParams::default())?;
// Executes full pipeline: candidates → soft delete → hard delete cleanup
Orchestration by the Consolidator
The sweep is not triggered automatically by the store. Instead, crates/ai-memory-consolidate/src/sweep.rs implements a periodic job that:
- Fetches
decay_candidates()from the store - Optionally applies a configured
decay_breadth_weight(viaServerBuilder::with_decay_breadth_weight) - Invokes the soft/hard delete operations
- Logs metrics on pages evicted vs. retained
This separation of concerns allows the store to remain stateless while the consolidator handles scheduling and observability.
Pure-Functional Design Benefits
The decay implementation avoids hidden state:
retention_score_with_breadthtakes only parameters—it does not query the databasesalience_after_feedbackcomputes deterministically from inputs- Test suites exercise edge cases: ancient pages (large
age_days), recent access spikes, boundary salience values, and breadth-weight variations
This design ensures that identical inputs always produce identical scores, making the forget-sweep behavior reproducible across environments.
Summary
- DecayParams configures six coefficients (λ, σ, μ, thresholds) governing the retention curve
- retention_score_with_breadth computes survival probability from age, access history, recency, salience, and operator breadth
- salience_after_feedback implements bounded, stepped salience adjustments based on operator judgments
- Four-stage sweep: candidate selection → soft delete → tombstone aging → hard delete
- Pinning provides manual override for decay immunity
- Pure functions make the algorithm testable and deterministic
Frequently Asked Questions
How does ai-memory balance age decay against access frequency?
The retention formula uses a sum of two exponential terms. Age-driven decay (exp(-λ · age_days)) continuously erodes the salience-weighted base score, while access history (ln(1 + access_count)) provides multiplicative reinforcement that itself undergoes recency decay (exp(-μ · days_since_access)). A page with massive historical access but no recent hits still decays—the access term's half-life is roughly 17 days with default μ=0.04.
What happens when multiple operators access the same page?
The breadth factor in retention_score_with_breadth rewards distribution across distinct actors. With positive breadth_weight, a page read once each by five operators scores higher than a page read five times by one operator—even with identical total access counts. This prevents "echo chamber" retention where single-user obsessive access preserves niche content.
Can the decay parameters be changed at runtime?
Yes. The MCP server in crates/ai-memory-mcp/src/server.rs exposes DecayParams through its admin API. ServerBuilder::with_decay_breadth_weight specifically allows tuning the breadth multiplier without code changes. However, parameter changes only affect future sweep calculations—already-computed scores in running systems are not retroactively adjusted.
Why does ai-memory use soft deletion before hard deletion?
The two-phase eviction supports operational safety and potential recovery. Soft deletion (setting superseded_at) immediately removes the page from search results while preserving data. If the page is re-ingested from an external source, the system can detect version ancestry. The hard_delete_after_days delay (default 30 days) provides a window for administrative intervention before permanent removal.
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 →