# We Gave Our AI Agent a Memory That Forgets — On Purpose. Memory Sentinel

> Source: <https://dev.to/jackymencz/we-gave-our-ai-agent-a-memory-that-forgets-on-purpose-memory-sentinel-4b1h>
> Published: 2026-10-08 15:38:32+00:00

*How Sentinel's memory works, why it can't grow forever, and why forgetting turned out to be the hardest part to design.*

Every AI agent demo shows you what the agent *remembers*. Nobody shows you what happens after three months — when the agent's memory file is 400 MB of stale decisions, half-true observations and resolved incidents it keeps re-acting on.

Human memory solved this a long time ago. You don't remember every day of your life. You remember what was important, you forget what was routine, and a few bad experiences shape your caution for years. The rest quietly fades.

We built our autonomous agent — Sentinel — around the same idea. Its memory is not a database that grows forever. It is a layered system that **forgets by design**, keeps what matters, and archives the rest instead of losing it completely.

**1. Everything has a hard limit.** Each kind of memory — decisions, audit trail, rollbacks, observations — has a fixed maximum number of entries. The memory is one JSON file, so the total size is capped by the sum of those limits. It physically cannot swell to infinity. Disk stays quiet.

**2. It forgets the least important, not the oldest.** When a list is full, Sentinel doesn't drop the oldest entry — it scores every entry for importance and drops the weakest. A decision to actually *change* code is more memorable than a routine skip. A serious rejection is memorable. A "nothing happened" day is not.

**3. Some things are never forgotten.** The agent's identity — its mission, its rules, its hard "never do this" list — cannot be wiped by compaction. Forgetting is for experience, not for DNA.

**4. Forgotten ≠ deleted.** Dropped entries go to a compressed, checksummed archive (gzip + SHA-256) written in the background. If the agent ever needs the old detail, it can be restored. Losing an archive write can never crash the agent — it fails open and moves on.

Think of Sentinel's memory like a desk:

There's even a "trauma center" — a counter of things that went badly (rollbacks, security blocks, failed simulations), categorized so the agent knows *what* it is cautious about, not just *that* it is cautious. Same file judged the same way twice is collapsed into one entry with a `repeats` count — repeated evidence, not new facts.

An agent that never forgets becomes slow, expensive, and eventually wrong — acting on context that stopped being true months ago. An agent that forgets everything can't learn. Sentinel tries to sit in between: bounded, importance-weighted, restorable. And because every forgetting event is counted (`memory_compaction` counters), the forgetting itself is auditable — it's a designed behavior, not silent data loss.

All of Sentinel's working memory lives in a single JSON state file (`data/sentinel_memory.json`, gitignored, mirrored to GitLab on a schedule). The caps are constants in `libs/core/memory-manager.js`:

``` js
// libs/core/memory-manager.js:9-24
const LIMITS = {
    decisionStream: 10,
    evolutionHistory: 50,
    auditLog: 50,
    externalSignals: 10,
    rollbackHistory: 20,
    maxStringLength: 180,
    observationSamples: 30,
    semanticModules: 200,
    reflectionLog: 30,
    intentEntries: 100,
    intentExamples: 5,
    decisionAnalytics: 40,
    // Same horizon DecisionQuality writes with (HISTORY_LIMIT), one row per day.
    decisionQualityHistory: 90
};
```

Two TTLs sit on top: `CACHE_TTL_MS = 5 min` for ephemeral scratch (L0) and `WORKING_MEMORY_TTL_MS = 1 h` for stale current-task cleanup (L1) — `memory-manager.js:26-27`. Individual strings are clipped at 180 chars, so a single oversized payload can't bloat a capped list.

This is the mechanism that makes it more than a ring buffer:

```
// libs/core/memory-manager.js:301-327 (condensed)
// Compaction forgets the LEAST important memory, not merely the oldest —
// closer to how human memory decays.
function decisionImportance(entry) {
    const action = String(decision.action || '').toUpperCase();
    let importance = 0.3;
    if (action === 'EVOLVE') importance = 0.8;
    else if (action === 'REJECT') importance = 0.7;
    else if (action === 'SKIP') importance = 0.2;
    // ... reason keywords (security, rollback, blocked) push it higher
    return importance;
}
```

Real changes (EVOLVE) and refusals (REJECT) outrank routine skips. Within the same class, a security/rollback-flavored reason adds weight. Age is *not* the primary axis — a six-week-old rejection that mattered still beats yesterday's noise.

`compactDecisionAnalytics` folds entries with the same fingerprint into one row with a `repeats` counter — the comment in `memory-manager.js:836` puts it as *"the same file, judged the same way, is one fact repeated — not new evidence."*

``` js
// libs/core/memory-manager.js:67-112
const TRAUMA_CATEGORIES = ['rollback', 'security', 'performance',
                           'hallucination', 'architecture'];
```

`trauma_center` tracks `shadow_guard_blocks`, `simulation_failures`, `rollback_events`, plus per-category counters — so downstream policy can be cautious *about something* rather than generically timid.

`defaultIdentity()` (`memory-manager.js:34-60`) holds the mission, four operating rules, a five-item `never` list (never send raw repo dumps or secrets to external agents, never persist foreign source in memory, never bypass governance or the daily budget, never commit output that failed the trust boundary, never move funds without an approved decision), an `always` list, and the budget philosophy. It's versioned via `IDENTITY_VERSION` — bumping it refreshes stale snapshots from code, so identity lives in the code, not in whatever an old file contains. Compaction never touches it.

`libs/knowledge/l6-archive.js` is the cold-storage layer:

`enqueue()` returns immediately; gzip + disk write happens on `setImmediate`, never blocking the runtime hot path.` restore()` verifies integrity.`data/archive/l6` (gitignored, never committed).
Every compaction run increments counters (`memory-manager.js:243-256`): runs, duplicates removed, entries trimmed per stream (` decision`, `analytics`, `quality`, `evolution`, `audit`, `external`, `rollback`), plus `last_compaction_ts`. You can diff two snapshots and see exactly what the agent chose to forget.

**Gets you:**

`l6` restore away, checksum-verified.`tests/unit/memory-brain-layers.test.js`, `memory-layers.test.js`, `memory-sync.test.js`; full suite currently 575 tests).
**Costs you:**

`decisionAnalytics: 40` is a rolling window, which is why quality snapshots (`decisionQualityHistory: 90`, one row/day) exist as the long-horizon view.
This is *engineered* memory inspired by human decay — importance scoring, TTLs, categorized trauma, restorable archives — not neuroscience, and not a vector DB. It works for an agent whose job is bounded-state decision-making, and its limits are deliberate, documented, and measured. If you're building an agent that runs unattended for months, "how does it forget?" is probably a better first question than "how much can it store?"
