Skip to content

Fact lifecycle decay: two-stage expiry with explicit ratification (#14) - #16

Merged
codenamev merged 5 commits into
codenamev:mainfrom
minerva-sky:feat/fact-lifecycle-decay
Sep 1, 2026
Merged

Fact lifecycle decay: two-stage expiry with explicit ratification (#14)#16
codenamev merged 5 commits into
codenamev:mainfrom
minerva-sky:feat/fact-lifecycle-decay

Conversation

@minerva-sky

Copy link
Copy Markdown
Contributor

Implements the measured stigmergy-decay scope from #14 (Val-directed 2026-08-31).

What

  • Migration 021: reaffirmed_at + expiring_since on facts; two-stage lifecycle active → expiring → expired.
  • Decay clock decoupled from passive recall: mark_expiring_facts keys off created_at/reaffirmed_at only — RecallTimestampRefresher's bulk touch of last_recalled_at no longer resets the clock (the self-defeat noted in Fact lifecycle: expiry-unless-reaffirmed + ratification surface #14).
  • Recall surfaces expiring facts: StalenessAnnotator adds an expiring marker on all predicates (outranks the heuristic stale marker — it is the ratification prompt); shortcuts recall includes expiring alongside active.
  • Ratification surface: store.ratify_fact (restores expired too; refuses rejected/superseded/disputed), MCP tools memory.list_expiring_facts + memory.ratify_fact, CLI ratify command.
  • Dashboard: expiring counts in health check message + knowledge totals.

Out of scope (still gated on your read of the issues)

Provenance/authority (#13) and relation edges (#15) — implementation beans exist, gated.

Testing

Targeted specs green (maintenance, sweeper, dashboard, store); full suite via CI.

🤖 Generated with Claude Code

minerva-sky and others added 5 commits August 31, 2026 21:20
…enamev#14

- Migration 021: reaffirmed_at + expiring_since columns on facts
- Maintenance#mark_expiring_facts: stale (created + last recall + reaffirmed
  all past stale_fact_threshold_days, default 180) active facts → expiring
- Maintenance#expire_unratified_facts: expiring past ratify_window_days
  (default 30) without ratification → expired (never deleted)
- Wired into Sweeper budget loop; specs for both stages

Co-Authored-By: Claude Fable 5 <[email protected]>
…enamev#14)

- mark_expiring_facts no longer counts last_recalled_at as freshness:
  RecallTimestampRefresher bulk-touches that column on every passive
  recall, which self-defeated decay. Clock = created_at + reaffirmed_at.
- StalenessAnnotator: expiring lifecycle marker (any predicate) that
  outranks the heuristic stale marker — the ratification prompt.
- build_facts_dataset selects reaffirmed_at + expiring_since;
  shortcuts fetch includes expiring facts alongside active.

Co-Authored-By: Claude Fable 5 <[email protected]>
…namev#14)

Ratification is the only signal that resets the decay clock — passive
recall never does. ratify_fact also restores expired facts; superseded/
rejected/disputed are refused.
Comment on lines +18 to +19
add_column :reaffirmed_at, String # ISO 8601, explicit ratification only
add_column :expiring_since, String # ISO 8601, entered expiring stage

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't these be datetimes?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deliberate, following the schema-wide convention: every timestamp in this DB is an ISO-8601 UTC string — created_at (001), vec_indexed_at (012), last_recalled_at (017), promoted_at (020). SQLite has no native datetime type, and all the existing query logic compares these lexicographically (ISO-8601 UTC sorts chronologically), e.g. the sweeper's expiring_since < cutoff. Typing just these two as DateTime would make them the only columns Sequel round-trips as Time objects and break symmetry with every comparison in the codebase.

If you'd rather move the whole schema to typed timestamps, I'd do that as its own migration + issue rather than smuggling two odd columns in here. Happy to file it — say the word.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File it for investigation for performance. Not required if working well as-is

@codenamev
codenamev merged commit a9f2334 into codenamev:main Sep 1, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants