Skip to content

feat(memory): temporal memory horizon for episodic classification#47

Merged
Mathews-Tom merged 2 commits into
mainfrom
feat/memory-horizon
Mar 25, 2026
Merged

feat(memory): temporal memory horizon for episodic classification#47
Mathews-Tom merged 2 commits into
mainfrom
feat/memory-horizon

Conversation

@Mathews-Tom

Copy link
Copy Markdown
Owner

Summary

Add temporal memory horizon classification to episodic memory, inspired by memora-lab/memory-service-public's four-category observation taxonomy (long-term people, long-term preferences, short-term preferences, short-term content). Episodes are now classified as short_term or long_term with age-based auto-promotion, enabling time-stratified retrieval and different handling per temporal class.

How It Works

Episode created → memory_horizon = SHORT_TERM (default)
                       ↓
          After short_term_days (default 30)
                       ↓
promote_to_long_term() → memory_horizon = LONG_TERM

Promotion is explicit (called from scheduler or CLI), not implicit on read — avoids surprise side effects during queries.

Memory Horizon Enum

class MemoryHorizon(StrEnum):
    SHORT_TERM = "short_term"   # Recent decisions, active context
    LONG_TERM = "long_term"     # Consolidated knowledge, historical patterns

Changes

Modified Files

  • src/vaultmind/memory/models.py — Added MemoryHorizon StrEnum and memory_horizon: MemoryHorizon = MemoryHorizon.SHORT_TERM field to Episode dataclass
  • src/vaultmind/memory/store.py — Schema adds memory_horizon TEXT NOT NULL DEFAULT 'short_term' column with index. Migration via ALTER TABLE with contextlib.suppress for idempotency (migration runs before CREATE INDEX to handle legacy DBs). _row_to_episode() reads horizon from row dict with fallback. create() persists memory_horizon. New methods:
    • promote_to_long_term(age_days=30) — UPDATE episodes older than threshold from short_term to long_term, returns count promoted
    • query_by_horizon(horizon, limit=50) — filtered retrieval by temporal class
  • src/vaultmind/config.py — Added short_term_days: int = 30 to EpisodicConfig
  • config/default.toml — Added short_term_days = 30 to [episodic] section

New Files

  • tests/test_memory_horizon.py (202 lines) — 15 tests across 7 classes

Backward Compatibility

  • memory_horizon defaults to SHORT_TERM — existing episodes treated as short-term
  • Schema migration adds column to existing DBs automatically on EpisodeStore.__init__()
  • _row_to_episode() handles rows without memory_horizon column (fallback to "short_term")
  • All existing episodic tests pass unchanged (17 in test_episodic.py)
  • No changes to episode creation API — memory_horizon is auto-assigned

Schema Migration Strategy

1. ALTER TABLE episodes ADD COLUMN memory_horizon ... (suppress if exists)
2. CREATE TABLE IF NOT EXISTS episodes ... (includes memory_horizon)
3. CREATE INDEX IF NOT EXISTS idx_episodes_horizon ... (requires column)

Migration runs before schema creation to ensure the column exists before the index is created. contextlib.suppress(OperationalError) makes it idempotent for both fresh and existing databases.

Test plan

  • 15 new tests in test_memory_horizon.py across 7 classes:
    • Model: enum values, default short_term, explicit long_term (3)
    • Persistence: created episode has short_term, roundtrip (2)
    • Promotion: old episodes promoted, recent not promoted, no double-promote, batch promote (4)
    • Query by horizon: short_term only, long_term only, empty returns [], respects limit (4)
    • Config: short_term_days default 30 (1)
    • Migration: legacy DB without memory_horizon column migrates correctly (1)
  • All existing episodic tests pass unchanged (17 in test_episodic.py)
  • Full suite: 996/996 tests pass, 0 regressions
  • ruff check — clean
  • mypy --ignore-missing-imports — clean
  • Manual: create episodes, wait (or backdate), run promote, verify horizon change
  • Integration: wire promote_to_long_term into scheduler loop

Add MemoryHorizon enum (short_term, long_term) to Episode model.
New episodes default to short_term. EpisodeStore gains
promote_to_long_term(age_days) for age-based auto-promotion and
query_by_horizon() for filtered retrieval per temporal class.

Schema migration adds memory_horizon column to existing DBs via
ALTER TABLE with contextlib.suppress for idempotency. Migration
runs before CREATE INDEX to handle legacy databases.

Add short_term_days config field to EpisodicConfig (default 30).
15 tests across 7 classes: model enum values and defaults (3), store
persistence roundtrip (2), promote_to_long_term with age threshold (4),
query_by_horizon filtering (4), EpisodicConfig short_term_days (1),
and legacy DB migration without memory_horizon column (1).
@Mathews-Tom Mathews-Tom merged commit 3aac9ee into main Mar 25, 2026
3 checks passed
@Mathews-Tom Mathews-Tom deleted the feat/memory-horizon branch March 25, 2026 18:42
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.

1 participant