Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content
Memory lifecycle

Utility-weighted forgetting

Bounded memory growth without losing what matters — unused episodic evidence archives out reversibly while credited, retrieved memories stay hot.

A busy agent fleet writes episodic evidence far faster than it rereads it: traces, logs, tool outputs. Left alone, retrieval wades through an ever-growing candidate pool where the signal never got denser. Utility-weighted forgetting is the opt-in instance feature that moves aged, never-used episodic evidence to an archive tier — out of retrieval, still reachable by ID, and reversible. Use this pattern on long-lived, high-volume scopes where most episodes are routine.

⚠️Warning

Forgetting ≠ forget(). This page is about the runtime's archival feature. The SDK's forget() helper is a different, immediate operation: explicit deletion. forget(lesson_id=...) deletes one lesson; forget(session_id=...) deletes a whole session's memory. Deletion is permanent — archival is not. And despite the similar name, archive_block entries (written by archive(), read by dereference()) are exact-storage artifacts, not part of the archive tier — forgetting never touches them, and dereference() is unaffected.

How it works

The idle-time consolidation worker (the same one behind sleep-time consolidation) runs an archival pass per sweep. An entry moves to the archive tier only if all of these hold:

  • Episodic evidence only. Only trace, log, tool_input, tool_output, and step_outcome entries qualify. Guidance (lessons, rules, mental models), beliefs (facts, observations), handoffs, and archive_block artifacts are never archived — beliefs age out via supersession, guidance via consolidation.
  • Aged. Older than the minimum window (default 7 days). Entries with no parsable timestamp are never archived — no evidence of age is not evidence of disuse.
  • Never credited. No positive outcome ever attributed to it (its success and reinforcement counters are zero) — see The learning loop.
  • Not recently used. Not retrieved and not outcome-touched within the window. A retrieval resets the clock — being read is being useful.

Archived entries leave the retrieval candidate pool and all overlays, but remain reachable by ID (dereference / get-by-id), so stored references never dangle. Each sweep archives at most a bounded batch, so a first pass over a large backlog cannot flood the store.

Reversibility. Archival writes markers; it does not destroy anything. An un-archive lever (MUBIT_CL_UNFORGET=1) suppresses forgetting and progressively revives previously archived entries on each sweep until the archive tier drains — a standing rollback, not a one-shot restore.

Why utility beats TTL

A TTL asks one question — how old is it? — and deletes the answer. Utility-weighting asks has it ever mattered? using signals the store already tracks:

PolicyKeepsLoses
TTL / max-ageEverything recent, useful or notThe 11-month-old incident trace you needed exactly once a year
Utility-weightedAnything retrieved or credited — usage resets the clock, outcomes pin itOnly evidence that aged out and was never read and never earned credit

Because outcome attribution marks which entries contributed to results, value — not age — decides retention. And it's archival, not deletion, so a wrong call costs a revival, not data.

Walkthrough

Nimbus's support scope accrues thousands of traces from routine, resolved tickets. The incident run's evidence, though older, stays hot: its traces were credited when the failure lesson was distilled, and the lesson itself is guidance — never archivable.

# Watch the episodic tier shrink as archival sweeps run (opt-in feature).
health = client.memory_health(session_id="support:ari", stale_threshold_days=30)
print(health["entry_counts"])
# Routine ticket traces age out of retrieval; credited incident
# evidence and every lesson/rule/fact stays.
 
# Distinct operation: explicit, immediate deletion.
client.forget(lesson_id=bad_lesson_id)      # delete one lesson
client.forget(session_id="support:scratch") # delete a session's memory
# Exactly one of lesson_id / session_id — deletion is permanent,
# unlike archival.
 
# archive() artifacts are exact-storage, not the archive tier:
# dereference() keeps working regardless of forgetting.
exact = client.dereference(reference_id=sql_ref_id)
ℹ️Note

There is no client call to trigger or tune archival — it is instance configuration (see below), and the SDK surface it touches is only what you observe through memory_health() and ID-based reads. forget() is available in all three SDKs (JS: forget({ lesson_id }) / forget({ session_id }); Rust: ForgetOptions with lesson_id or run_id).

When not to use it

  • Regulated or audit-bound evidence with hard retention rules. Archival is a retrieval-tier decision, not a records policy. Manage retention explicitly rather than assuming what archives or survives.
  • Low-volume scopes. If retrieval isn't wading through noise, leave it off — the default is off for a reason.
  • You actually need deletion. Poisoned entries, user data removal: forget() (or delete_run) deletes; archival deliberately doesn't.
  • Evidence that is rarely read but must stay retrievable by content. Archived entries are ID-reachable, not searchable. If it must surface in future queries despite years of disuse, attach an outcome to it (credit pins it) or store it as guidance instead of a trace.

Related

ℹ️Note

Configuration. Utility-weighted forgetting is an opt-in instance feature: MUBIT_CL_FORGET=1 enables it. Knobs: MUBIT_CL_FORGET_MIN_AGE_SECS (age and quiet window, default 604800 = 7 days), MUBIT_CL_FORGET_MAX_PER_SWEEP (archival cap per sweep, default 200 — also the per-sweep revival cap). Rollback: set MUBIT_CL_UNFORGET=1 to suppress forgetting and progressively revive the archive tier.