Skip to content

Backlog: TTL Pruning — Auto-delete stale entries by age #14

Description

@asvarnon

Problem

The current design uses max_entries as a capacity cap with LRU/LeastRelevant eviction at the boundary. The 24h recency half-life helps ranking but does not delete old entries. Over weeks/months of daily use, the database accumulates entries where even the "top" entries may be months old and irrelevant.

Without age-based pruning, a user with max_entries: 500 could have all 500 entries spanning 3 months — the DB stays bounded by count but not by freshness.

Proposed Solution

Add a configurable TTL (time-to-live) that auto-deletes entries older than N days.

Config Change

pub struct CoreConfig {
    // ... existing fields ...
    /// Maximum age of entries in seconds. Entries older than this are pruned.
    /// `None` disables TTL pruning. Default: 30 days (2_592_000 seconds).
    pub max_age_seconds: Option<u64>,
}

Engine Change

Add prune_expired(&self) -> Result<usize> to ContextEngine:

  • Runs DELETE FROM entries WHERE timestamp < ? with now - max_age_seconds
  • Returns count of deleted rows
  • Called at the start of save_snapshot() (before the capacity eviction check)
  • Also exposed as a standalone method for CLI/napi callers

CLI Surface

context-forge vacuum --db <path> [--max-age-days <n>]
  Prunes expired entries and runs PRAGMA optimize + VACUUM.

SQLite Maintenance

  • PRAGMA optimize — lets SQLite re-analyze query plans periodically
  • VACUUM — reclaims disk space after bulk deletes (optional, can be expensive on large DBs)

Complexity

Low — single SQL DELETE statement, one new config field, one new engine method.

Prep During V1

  • CoreConfig already uses serde — adding max_age_seconds: Option<u64> is backward-compatible (defaults to None via #[serde(default)])
  • No schema changes needed — idx_entries_timestamp already exists for efficient range deletes
  • The vacuum CLI subcommand can be added alongside Phase 4 CLI work

Dependencies

  • Can be implemented after Phase 4 (CLI) for the vacuum subcommand surface
  • Engine-side prune_expired() could land as early as Phase 3 patch
  • No schema migration required — uses existing timestamp column + index

Labels

backlog, core, cli

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions