Skip to main content

heartbit_core/memory/
reflection.rs

1//! Reflection tracker — triggers agent self-reflection when cumulative memory importance exceeds a threshold.
2
3use parking_lot::Mutex;
4
5/// Tracks cumulative importance of stored memories to trigger reflection.
6///
7/// When the sum of importance values since the last trigger exceeds `threshold`,
8/// `record()` returns `true` and resets the accumulator. This follows the
9/// Generative Agents (Park et al., 2023) reflection pattern.
10pub struct ReflectionTracker {
11    threshold: u32,
12    accumulated: Mutex<u32>,
13}
14
15impl ReflectionTracker {
16    /// Create a tracker that fires when cumulative importance reaches `threshold`.
17    pub fn new(threshold: u32) -> Self {
18        Self {
19            threshold,
20            accumulated: Mutex::new(0),
21        }
22    }
23
24    /// Record an importance value. Returns `true` if the threshold was exceeded,
25    /// triggering a reflection. The accumulator is reset after triggering.
26    pub fn record(&self, importance: u8) -> bool {
27        let mut acc = self.accumulated.lock();
28        *acc += importance as u32;
29        if *acc >= self.threshold {
30            *acc = 0;
31            true
32        } else {
33            false
34        }
35    }
36
37    /// Current accumulated importance (for testing/debugging).
38    pub fn accumulated(&self) -> u32 {
39        *self.accumulated.lock()
40    }
41}
42
43/// Hint text appended to store tool output when a reflection is triggered.
44pub const REFLECTION_HINT: &str = "\n\n[Reflection suggested] You have stored several important memories recently. \
45     Take a moment to reflect on what you've learned. Use memory_store with high importance \
46     to record any insights, patterns, or connections you notice across your recent memories.";
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn tracker_triggers_at_threshold() {
54        let tracker = ReflectionTracker::new(10);
55
56        // Accumulate to just below threshold
57        assert!(!tracker.record(5)); // 5
58        assert!(!tracker.record(4)); // 9
59        // This should trigger
60        assert!(tracker.record(1)); // 10 >= 10
61    }
62
63    #[test]
64    fn tracker_resets_after_trigger() {
65        let tracker = ReflectionTracker::new(10);
66
67        // Trigger
68        assert!(tracker.record(10));
69        assert_eq!(tracker.accumulated(), 0);
70
71        // Should need full threshold again
72        assert!(!tracker.record(5));
73        assert_eq!(tracker.accumulated(), 5);
74    }
75
76    #[test]
77    fn tracker_exceeds_threshold() {
78        let tracker = ReflectionTracker::new(10);
79
80        // Single large value exceeding threshold
81        assert!(tracker.record(15));
82        assert_eq!(tracker.accumulated(), 0);
83    }
84
85    #[test]
86    fn tracker_many_small_values() {
87        let tracker = ReflectionTracker::new(10);
88
89        for _ in 0..9 {
90            assert!(!tracker.record(1));
91        }
92        // 9 accumulated, one more should trigger
93        assert!(tracker.record(1));
94    }
95
96    #[test]
97    fn tracker_triggers_multiple_times() {
98        let tracker = ReflectionTracker::new(5);
99
100        assert!(tracker.record(5)); // first trigger
101        assert!(tracker.record(5)); // second trigger
102        assert!(!tracker.record(3)); // not yet
103        assert!(tracker.record(2)); // third trigger
104    }
105}