# How ai-memory Distinguishes Between TTL Expires-At, Pinned Pages, and Feedback Signals in Retention

> Learn how ai-memory differentiates TTL expires-at, pinned pages, and feedback signals for effective retention. Understand the priority in page deletion and salience scoring.

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

---

**ai-memory uses a strict three-tier hierarchy: TTL expiration triggers hard deletion first, pinned pages bypass all decay logic entirely, and feedback signals adjust salience scores only for non-pinned, non-expired pages.**

The ai-memory system implements a deterministic retention pipeline that balances automatic cleanup, manual curation, and community feedback. Understanding how these three mechanisms interact is essential for managing knowledge bases that persist across sessions without growing indefinitely.

## The Three Retention Mechanisms in ai-memory

ai-memory stores pages with three orthogonal concepts governing their lifespan. Each mechanism operates at a different layer of the retention decision, creating a predictable priority order.

### TTL-Based Expires-At: The Hard Deadline

The **expires_at** field in `PageMeta` enforces absolute temporal boundaries. Parsed from Markdown front-matter in [`crates/ai-memory-wiki/src/wiki.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/wiki.rs)【L1925-L1950】, this timestamp is evaluated by the reader with a `WHERE expires_at IS NULL OR expires_at > now` clause in [`crates/ai-memory-store/src/reader.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/reader.rs)【L51-L56】.

Pages with elapsed TTL receive **hard deletion** regardless of other signals. The admin delete path in [`crates/ai-memory-mcp/src/server.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/server.rs)【L2390-L2393】 executes this removal during retention sweeps.

```rust
// TTL page – expires in 7 days
let ttl_req = wiki::WriteRequest {
    path: "notes/todo.md".into(),
    body: "Buy milk".into(),
    meta: serde_json::json!({ 
        "title": "Todo", 
        "expires_at": "2026-09-07" 
    }),
    ..Default::default()
};
wiki.write_page(ttl_req).await?;

```

### Pinned Pages: Immunity from Decay

The **pinned** boolean flag, set via front-matter (`pinned: true`) or implicitly for slot pages (`is_slot_path`), grants complete eviction immunity. The flag is read and written in [`crates/ai-memory-wiki/src/wiki.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/wiki.rs)【L560-L564】 and persists in the `pages.pinned` database column.

The retention formula **completely ignores the decay term** for pinned pages. In [`crates/ai-memory-store/src/reader.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/reader.rs), the query includes `pinned = 1` as a disjunctive clause ensuring pinned rows survive breadth and age predicates. The slot visibility tests in [`crates/ai-memory-store/tests/slot_visibility.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/tests/slot_visibility.rs)【L40-L53】 verify this behavior.

```rust
// Pinned page via front-matter
let pinned_req = wiki::WriteRequest {
    path: "notes/pinned.md".into(),
    body: "Critical design decision".into(),
    meta: serde_json::json!({ 
        "title": "Design", 
        "pinned": true 
    }),
    ..Default::default()
};
wiki.write_page(pinned_req).await?;

```

### Feedback Signals: Salience Adjustment

Community **feedback** mutates page **salience** without triggering deletion. Stored in the append-only `page_feedback` table, each row tracks `kind` (`helpful`, `not_helpful`, `stale`, `wrong`), optional `reason`, and author. The `record_page_feedback` function in [`crates/ai-memory-store/src/ops.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/ops.rs)【L1651-L1694】 handles insertion.

The `decay::salience_after_feedback` function in [`crates/ai-memory-store/src/decay.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/decay.rs)【L141-L152】 computes updated salience values. These feed into `retention_score` in [`crates/ai-memory-store/src/decay.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/decay.rs)【L48-L66】, where positive feedback elevates retention priority and negative feedback accelerates decay.

```rust
let feedback = Feedback {
    page_path: "notes/pinned.md".into(),
    kind: FeedbackKind::Helpful,
    reason: Some("clarifies the architecture".into()),
    author_id: user.id,
};
store.writer.record_page_feedback(feedback).await?;

```

## The Retention Hierarchy: Priority Order

The ai-memory retention pipeline applies these mechanisms in strict sequence:

1. **Hard-delete check** — TTL expiration takes precedence; elapsed timestamps trigger immediate removal
2. **Pinned guard** — pinned pages bypass decay scoring entirely, becoming eviction-immune
3. **Feedback-adjusted salience** — for remaining pages, retention_score = `f(age, access, salience, …)`

This ordering ensures **TTL → Pinned → Feedback-adjusted retention** determinism. A page with negative feedback survives if pinned. A pinned page with expired TTL still gets deleted. Feedback modulates decay only within the non-pinned, non-expired subset.

## Running the Retention Sweep

Execute the full four-pass sweep via CLI:

```bash
ai-memory retention

```

This command, implemented in [`crates/ai-memory-cli/src/cli.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-cli/src/cli.rs)【L106-L108】, applies the TTL guard, pinned guard, feedback-adjusted salience calculation, and finally evicts low-score pages.

## Key Source Files

| File | Responsibility |
|------|---------------|
| [`crates/ai-memory-wiki/src/wiki.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/wiki.rs) | Parses `expires_at` and `pinned` from front-matter |
| [`crates/ai-memory-store/src/reader.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/reader.rs) | Filters by TTL and propagates pinned flag in queries |
| [`crates/ai-memory-store/src/ops.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/ops.rs) | Inserts feedback records and manages salience updates |
| [`crates/ai-memory-store/src/decay.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/decay.rs) | Computes `salience_after_feedback` and `retention_score` |
| [`crates/ai-memory-cli/src/cli.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-cli/src/cli.rs) | Exposes the `retention` command |
| [`crates/ai-memory-mcp/src/server.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/server.rs) | Admin TTL deletion and feedback API endpoints |

## Summary

- **TTL expires_at** provides hard deadlines with absolute priority in eviction decisions
- **Pinned pages** receive complete immunity from decay-based retention scoring
- **Feedback signals** adjust salience scores bidirectionally but only affect non-pinned, non-expired pages
- The three-tier hierarchy guarantees predictable, deterministic cleanup behavior

## Frequently Asked Questions

### What happens if a page has both an expired TTL and is pinned?

The TTL check executes first in the retention pipeline. According to [`crates/ai-memory-mcp/src/server.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/server.rs)【L2390-L2393】, expired timestamps trigger hard deletion regardless of pinned status. Pinning does not override temporal expiration.

### Can feedback signals alone delete a page from ai-memory?

No. Feedback modifies **salience** through `salience_after_feedback` in [`crates/ai-memory-store/src/decay.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/decay.rs)【L141-L152】, which influences the retention score. Deletion requires the score to fall below threshold during the retention sweep, and pinned pages never reach this evaluation stage.

### How do slot pages automatically become pinned?

Slot pages receive implicit pinning through the `is_slot_path` check in [`crates/ai-memory-wiki/src/wiki.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/wiki.rs)【L560-L564】, setting `pinned = true` without explicit front-matter. This ensures slot-based organizational content persists indefinitely unless TTL-specified.

### Does ai-memory support negative TTL or retroactive expiration?

The parser in [`crates/ai-memory-wiki/src/wiki.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/wiki.rs)【L1925-L1950】 validates `expires_at` as a timestamp. Past dates are accepted and trigger immediate hard deletion on the next retention sweep, effectively enabling retroactive expiration.