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}