You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Design proposal (companion to #13). Grounded in current code; no implementation yet.
What exists
Recall::StaleDetector (#35) surfaces access-stale active facts (last_recalled_at past threshold), explicitly read-only: "the sweeper never acts on this."
Sweep::RecallTimestampRefresher touches last_recalled_at on recall/context-injection.
Fact statuses: active / superseded / rejected (plus disputed per Provenance + authority on facts; keep and surface contested claims #13). Observations already have an expired retire path (observation_writes.rb:79); facts have no lifecycle terminal for "nobody has needed or confirmed this in a long time."
Gap
Staleness is a report, not a lifecycle. A stale-but-wrong fact stays active and keeps winning recall forever unless the user happens to read the stale list. And the only freshness signal is passive recall — which self-defeats: recalling a fact touches last_recalled_at, so frequently-recalled-but-wrong facts can never age out.
Proposal
Two-stage expiry with distinct signals per stage:
active → expiring (sweeper, automatic): stale past threshold_days per existing StaleDetector predicate. expiring facts still recall, but annotated (staleness_annotator already exists) and down-weighted. Passive recall during this window does NOT reset the clock back to active — that's the self-defeat above.
expiring → expired (sweeper, automatic): after a second window (ratify_window_days) with no ratification. expired facts are excluded from default recall (like superseded), never deleted, and restorable.
Ratification (explicit, user/agent-initiated): new reaffirmed_at column, set only by the ratification surface — never by passive recall. Ratifying returns the fact to active and resets both clocks.
Ratification surface
CLI: claude-memory ratify — interactive bulk pass over expiring facts (keep / expire / edit), modeled on the existing stale-list review flow.
MCP: list_expiring_facts + ratify_fact(id) so agents can raise expiring facts mid-session ("still true that X?") — the cheapest honest signal, since the user is present.
Dashboard: expiring count next to the existing stale count in the sidebar.
Open questions
Should expiring facts lose conflict-resolution priority in Resolve::Resolver? (Lean: yes — a contested claim between active and expiring should prefer active.)
Per-predicate thresholds? Identity facts (name, timezone) age slower than state facts (current project). PredicatePolicy is the natural home.
Design proposal (companion to #13). Grounded in current code; no implementation yet.
What exists
Recall::StaleDetector(#35) surfaces access-stale active facts (last_recalled_atpast threshold), explicitly read-only: "the sweeper never acts on this."Sweep::RecallTimestampRefreshertoucheslast_recalled_aton recall/context-injection.active/superseded/rejected(plusdisputedper Provenance + authority on facts; keep and surface contested claims #13). Observations already have anexpiredretire path (observation_writes.rb:79); facts have no lifecycle terminal for "nobody has needed or confirmed this in a long time."Gap
Staleness is a report, not a lifecycle. A stale-but-wrong fact stays
activeand keeps winning recall forever unless the user happens to read the stale list. And the only freshness signal is passive recall — which self-defeats: recalling a fact toucheslast_recalled_at, so frequently-recalled-but-wrong facts can never age out.Proposal
Two-stage expiry with distinct signals per stage:
active→expiring(sweeper, automatic): stale pastthreshold_daysper existing StaleDetector predicate.expiringfacts still recall, but annotated (staleness_annotatoralready exists) and down-weighted. Passive recall during this window does NOT reset the clock back toactive— that's the self-defeat above.expiring→expired(sweeper, automatic): after a second window (ratify_window_days) with no ratification.expiredfacts are excluded from default recall (likesuperseded), never deleted, and restorable.reaffirmed_atcolumn, set only by the ratification surface — never by passive recall. Ratifying returns the fact toactiveand resets both clocks.Ratification surface
claude-memory ratify— interactive bulk pass overexpiringfacts (keep / expire / edit), modeled on the existing stale-list review flow.list_expiring_facts+ratify_fact(id)so agents can raise expiring facts mid-session ("still true that X?") — the cheapest honest signal, since the user is present.Open questions
expiringfacts lose conflict-resolution priority inResolve::Resolver? (Lean: yes — a contested claim betweenactiveandexpiringshould preferactive.)PredicatePolicyis the natural home.