Fact lifecycle: expiry-unless-reaffirmed + ratification surface #14
Copy link
Copy link
Open
Description
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.- Fact statuses:
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 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 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.- Ratification (explicit, user/agent-initiated): new
reaffirmed_atcolumn, set only by the ratification surface — never by passive recall. Ratifying returns the fact toactiveand resets both clocks.
Ratification surface
- CLI:
claude-memory ratify— interactive bulk pass overexpiringfacts (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
expiringfacts lose conflict-resolution priority inResolve::Resolver? (Lean: yes — a contested claim betweenactiveandexpiringshould preferactive.) - Per-predicate thresholds? Identity facts (name, timezone) age slower than state facts (current project).
PredicatePolicyis the natural home. - Does ratification need authority per Provenance + authority on facts; keep and surface contested claims #13? (Lean: yes — user ratification > agent ratification, recorded in the same provenance columns.)
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
Decouple decay clock from passive recall; surface expiring facts (cod…
Add MCP ratification surface: list_expiring_facts + ratify_fact (code…
Add CLI ratify command (codenamev#14)
Surface expiring facts on the dashboard (codenamev#14)
Add two-stage fact expiry lifecycle (active→expiring→expired) per #14
Merge pull request #16 from minerva-sky/feat/fact-lifecycle-decay
docs: WORLD.md restructured under the fleet's six headings (fleet-5b)