Skip to main content

subcog/models/
consolidation.rs

1//! Memory consolidation types for lifecycle management.
2//!
3//! This module provides types for managing memory retention, tiering, and
4//! relationships between memories during consolidation operations.
5//!
6//! # Memory Tiers
7//!
8//! Memories are organized into tiers based on access patterns and importance:
9//!
10//! | Tier | Score Range | Behavior |
11//! |------|-------------|----------|
12//! | Hot | ≥ 0.7 | Frequently accessed, high priority |
13//! | Warm | 0.4 - 0.7 | Moderately accessed (default) |
14//! | Cold | 0.2 - 0.4 | Rarely accessed, low priority |
15//! | Archive | < 0.2 | Long-term storage only |
16//!
17//! # Edge Types
18//!
19//! Relationships between memories are modeled as directed edges:
20//!
21//! - `Contradicts` - Memory A conflicts with memory B
22//! - `Supersedes` - Memory A replaces memory B
23//! - `RelatedTo` - Memory A is contextually related to memory B
24//! - `Refines` - Memory A adds detail to memory B
25//! - `ParentOf` / `ChildOf` - Hierarchical relationships
26//! - `SummarizedBy` / `SourceOf` - Consolidation relationships (original → summary, summary → originals)
27//!
28//! # Retention Scoring
29//!
30//! [`RetentionScore`] calculates a composite score from:
31//! - **Access frequency** (20% weight) - How often the memory is retrieved
32//! - **Recency** (30% weight) - When the memory was last accessed
33//! - **Importance** (50% weight) - LLM-assessed significance
34//!
35//! # Example
36//!
37//! ```rust
38//! use subcog::models::{MemoryTier, RetentionScore};
39//!
40//! // Create a retention score for a frequently-accessed, recent, important memory
41//! let score = RetentionScore::new(0.8, 0.9, 0.95);
42//! assert_eq!(score.suggested_tier(), MemoryTier::Hot);
43//!
44//! // A rarely-accessed, old, low-importance memory
45//! let cold_score = RetentionScore::new(0.1, 0.2, 0.1);
46//! assert_eq!(cold_score.suggested_tier(), MemoryTier::Archive);
47//! ```
48
49use std::fmt;
50
51/// Memory tier for retention management.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
53pub enum MemoryTier {
54    /// Hot tier: frequently accessed, high priority.
55    Hot,
56    /// Warm tier: moderately accessed (default).
57    #[default]
58    Warm,
59    /// Cold tier: rarely accessed, low priority.
60    Cold,
61    /// Archive tier: long-term storage only.
62    Archive,
63}
64
65impl MemoryTier {
66    /// Returns the tier as a string slice.
67    #[must_use]
68    pub const fn as_str(&self) -> &'static str {
69        match self {
70            Self::Hot => "hot",
71            Self::Warm => "warm",
72            Self::Cold => "cold",
73            Self::Archive => "archive",
74        }
75    }
76}
77
78impl fmt::Display for MemoryTier {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        write!(f, "{}", self.as_str())
81    }
82}
83
84/// Type of relationship edge between memories.
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
86pub enum EdgeType {
87    /// Memory A contradicts memory B.
88    Contradicts,
89    /// Memory A supersedes memory B.
90    Supersedes,
91    /// Memory A is related to memory B.
92    RelatedTo,
93    /// Memory A refines memory B.
94    Refines,
95    /// Memory A is a parent of memory B (hierarchy).
96    ParentOf,
97    /// Memory A is a child of memory B (hierarchy).
98    ChildOf,
99    /// Memory A is summarized by memory B (A is an original, B is the summary).
100    SummarizedBy,
101    /// Memory A is a source of memory B (A is a summary, B is an original).
102    SourceOf,
103}
104
105impl EdgeType {
106    /// Returns the edge type as a string slice.
107    #[must_use]
108    pub const fn as_str(&self) -> &'static str {
109        match self {
110            Self::Contradicts => "contradicts",
111            Self::Supersedes => "supersedes",
112            Self::RelatedTo => "related_to",
113            Self::Refines => "refines",
114            Self::ParentOf => "parent_of",
115            Self::ChildOf => "child_of",
116            Self::SummarizedBy => "summarized_by",
117            Self::SourceOf => "source_of",
118        }
119    }
120
121    /// Returns the inverse edge type.
122    #[must_use]
123    pub const fn inverse(&self) -> Self {
124        match self {
125            Self::Contradicts => Self::Contradicts,
126            Self::Supersedes => Self::Supersedes, // No inverse defined
127            Self::RelatedTo => Self::RelatedTo,
128            Self::Refines => Self::Refines, // No inverse defined
129            Self::ParentOf => Self::ChildOf,
130            Self::ChildOf => Self::ParentOf,
131            Self::SummarizedBy => Self::SourceOf,
132            Self::SourceOf => Self::SummarizedBy,
133        }
134    }
135}
136
137impl fmt::Display for EdgeType {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        write!(f, "{}", self.as_str())
140    }
141}
142
143/// Retention score for memory lifecycle management.
144#[derive(Debug, Clone, Copy, PartialEq)]
145pub struct RetentionScore {
146    /// Overall score (0.0 to 1.0, higher = keep longer).
147    score: f32,
148    /// Access frequency component.
149    access_frequency: f32,
150    /// Recency component.
151    recency: f32,
152    /// Importance component (from LLM analysis).
153    importance: f32,
154}
155
156impl RetentionScore {
157    /// Creates a new retention score.
158    ///
159    /// # Panics
160    ///
161    /// Panics if any component is outside the 0.0 to 1.0 range.
162    #[must_use]
163    pub fn new(access_frequency: f32, recency: f32, importance: f32) -> Self {
164        debug_assert!(
165            (0.0..=1.0).contains(&access_frequency),
166            "access_frequency must be 0.0 to 1.0"
167        );
168        debug_assert!((0.0..=1.0).contains(&recency), "recency must be 0.0 to 1.0");
169        debug_assert!(
170            (0.0..=1.0).contains(&importance),
171            "importance must be 0.0 to 1.0"
172        );
173
174        // Weighted average: importance > recency > frequency
175        let score = access_frequency.mul_add(0.2, importance.mul_add(0.5, recency * 0.3));
176
177        Self {
178            score,
179            access_frequency,
180            recency,
181            importance,
182        }
183    }
184
185    /// Returns the overall score.
186    #[must_use]
187    pub const fn score(&self) -> f32 {
188        self.score
189    }
190
191    /// Returns the suggested tier based on score.
192    #[must_use]
193    pub fn suggested_tier(&self) -> MemoryTier {
194        if self.score >= 0.7 {
195            MemoryTier::Hot
196        } else if self.score >= 0.4 {
197            MemoryTier::Warm
198        } else if self.score >= 0.2 {
199            MemoryTier::Cold
200        } else {
201            MemoryTier::Archive
202        }
203    }
204}
205
206impl Default for RetentionScore {
207    fn default() -> Self {
208        Self::new(0.5, 0.5, 0.5)
209    }
210}