How Pinned Pages Are Handled in ai‑memory's Decay System

ai-memory excludes pinned pages from decay by checking the pinned flag on the PageMeta struct and the front‑matter pinned field during the forget‑sweep, rendering both slot pages and user‑pinned content immune to eviction.

ai-memory implements a forget‑sweep mechanism to manage episodic memory decay, yet certain knowledge must remain permanent. The system treats pages as pinned when they reside in the special _slots/ hierarchy or when their front‑matter explicitly sets pinned: true, ensuring these entries never enter the decay evaluation pipeline according to the source code in akitaonrails/ai-memory.

What Makes a Page Pinned in ai-memory

The repository recognizes two distinct mechanisms for pinning a page, both ultimately setting the pinned field on the PageMeta struct to true at crates/ai-memory-wiki/src/wiki.rs lines 1185‑1187.

Automatic Pinning via the Slots Directory

Any page written to a path prefixed with _slots/ is automatically considered a slot page. During the write operation, the is_slot_path(&path) helper returns true, which forces the pinned flag to true regardless of other metadata.

Explicit Pinning via Front-Matter

Users can manually pin any page by including pinned: true in the YAML front‑matter. When the page is indexed, the derive_index_metadata function reads this field and sets meta.pinned = true according to the implementation at crates/ai-memory-wiki/src/wiki.rs lines 84‑86.

The Decay Exclusion Logic in sweep.rs

The core protection logic resides in the is_decayable function within crates/ai-memory-consolidate/src/sweep.rs. This function short‑circuits decay eligibility through multiple guard clauses:

fn is_decayable(c: &DecayCandidate) -> bool {
    if c.tier != Tier::Episodic { return false; }
    if c.pinned { return false; }                       // <-- pinned flag blocks decay
    if let Ok(fm) = serde_json::from_str::<serde_json::Value>(&c.frontmatter_json)
        && fm.get("pinned").and_then(serde_json::Value::as_bool) == Some(true)
    {
        return false;                                   // <-- front‑matter overrides
    }
    true
}

As shown at lines 30‑41, the function immediately returns false if c.pinned is true, preventing the page from being marked for eviction. It also performs a secondary check on the raw front‑matter JSON to catch pinned status that might not have been propagated to the struct flag.

How the Forget-Sweep Processes Candidates

The decay pipeline begins in crates/ai-memory-store/src/reader.rs, where Reader::decay_candidates collects all rows with is_latest = 1. These candidates are then filtered through is_decayable during the sweep execution.

Only pages that satisfy all three conditions—episodic tier, not struct‑pinned, and not front‑matter‑pinned—proceed to eviction or TTL deletion. Semantic tier pages are excluded earlier by the tier check, while pinned episodic pages are filtered out by the pinning logic before reaching crates/ai-memory-store/src/ops.rs.

Practical Code Examples

Creating a Pinned Slot Page (Auto‑Pinned)

When writing to a _slots/ path, the system automatically pins the page:

let path = PagePath::new("_slots/personal/todo.md").unwrap();
let req = NewPage {
    // … other fields …
    pinned: is_slot_path(&path),   // true because of `_slots/` prefix
    ..Default::default()
};
wiki.write_page(req).await?;

Explicitly Pinning via Front-Matter

---
title: "Important Fact"
tier: episodic
pinned: true
---
The fact should never decay.

When indexed, this front‑matter ensures meta.pinned = true, protecting the page during subsequent sweeps.

Running the Forget-Sweep

The sweep logic filters candidates before deletion:

let candidates = reader.decay_candidates(ws, proj).await?;
let decayable = candidates.into_iter().filter(is_decayable);
for cand in decayable {
    // eviction or TTL delete …
}

Only non‑pinned episodic pages flow through the filter, ensuring pinned content remains untouched.

Summary

  • Two pinning methods: Automatic for _slots/ paths, manual via front‑matter pinned: true.
  • Protection mechanism: The is_decayable function in sweep.rs checks both the PageMeta struct flag and the front‑matter JSON.
  • Tier restriction: Only episodic pages are candidates for decay; semantic pages are inherently excluded.
  • File locations: Logic resides in crates/ai-memory-wiki/src/wiki.rs (writing) and crates/ai-memory-consolidate/src/sweep.rs (sweeping).

Frequently Asked Questions

What is the difference between slot pages and user-pinned pages?

Slot pages reside in the _slots/ directory and are automatically pinned by the write path in wiki.rs. User‑pinned pages can exist anywhere in the hierarchy and rely on explicit pinned: true front‑matter. Both receive identical decay protection once the pinned flag is set.

Can semantic tier pages be pinned?

Yes, but pinning is redundant for semantic tier pages. The is_decayable function returns false immediately for any page where c.tier != Tier::Episodic, meaning semantic pages never enter the decay pipeline regardless of their pinned status.

Where does the decay sweep physically delete pages?

The actual deletion operations—soft delete and hard delete—are implemented in crates/ai-memory-store/src/ops.rs. However, these functions only receive candidates that have already passed the is_decayable filter in sweep.rs, ensuring pinned pages never reach the deletion stage.

How can I verify a page is pinned before it gets swept?

Inspect the pinned field on the PageMeta struct stored in the database, or query the front‑matter JSON directly. If either shows pinned as true, the page will be excluded when Reader::decay_candidates feeds rows into the sweep pipeline defined in crates/ai-memory-consolidate/src/sweep.rs.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →