vex_temporal/
horizon.rs

1//! Time horizon definitions for agents
2
3use chrono::{DateTime, Duration, Utc};
4use serde::{Deserialize, Serialize};
5
6/// Time horizon scale for an agent
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8pub enum TimeHorizon {
9    /// Immediate: 0-5 minutes of context
10    Immediate,
11    /// Short-term: 5-60 minutes
12    ShortTerm,
13    /// Medium-term: 1-24 hours
14    MediumTerm,
15    /// Long-term: days to weeks
16    LongTerm,
17    /// Permanent: never expires
18    Permanent,
19}
20
21impl TimeHorizon {
22    /// Get the duration for this horizon
23    pub fn duration(&self) -> Option<Duration> {
24        match self {
25            Self::Immediate => Some(Duration::minutes(5)),
26            Self::ShortTerm => Some(Duration::hours(1)),
27            Self::MediumTerm => Some(Duration::hours(24)),
28            Self::LongTerm => Some(Duration::weeks(1)),
29            Self::Permanent => None,
30        }
31    }
32
33    /// Get compression level for this horizon
34    pub fn compression_level(&self) -> f64 {
35        match self {
36            Self::Immediate => 0.0,  // No compression
37            Self::ShortTerm => 0.2,  // Light
38            Self::MediumTerm => 0.5, // Moderate
39            Self::LongTerm => 0.7,   // Heavy
40            Self::Permanent => 0.9,  // Maximum
41        }
42    }
43
44    /// Check if a timestamp is within this horizon
45    pub fn contains(&self, timestamp: DateTime<Utc>) -> bool {
46        match self.duration() {
47            Some(d) => Utc::now() - timestamp <= d,
48            None => true,
49        }
50    }
51
52    /// Get recommended horizon for agent depth
53    pub fn for_depth(depth: u8) -> Self {
54        match depth {
55            0 => Self::LongTerm,   // Root agents have long memory
56            1 => Self::MediumTerm, // First-level children
57            2 => Self::ShortTerm,  // Second-level
58            _ => Self::Immediate,  // Deeper agents are ephemeral
59        }
60    }
61}
62
63/// Configuration for time horizon behavior
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct HorizonConfig {
66    /// The active horizon
67    pub horizon: TimeHorizon,
68    /// Maximum entries to keep
69    pub max_entries: usize,
70    /// Whether to auto-compress old entries
71    pub auto_compress: bool,
72    /// Whether to auto-evict expired entries
73    pub auto_evict: bool,
74}
75
76impl Default for HorizonConfig {
77    fn default() -> Self {
78        Self {
79            horizon: TimeHorizon::MediumTerm,
80            max_entries: 100,
81            auto_compress: true,
82            auto_evict: true,
83        }
84    }
85}
86
87impl HorizonConfig {
88    /// Create config for a given agent depth
89    pub fn for_depth(depth: u8) -> Self {
90        let horizon = TimeHorizon::for_depth(depth);
91        let max_entries = match horizon {
92            TimeHorizon::Immediate => 10,
93            TimeHorizon::ShortTerm => 25,
94            TimeHorizon::MediumTerm => 50,
95            TimeHorizon::LongTerm => 100,
96            TimeHorizon::Permanent => 500,
97        };
98        Self {
99            horizon,
100            max_entries,
101            auto_compress: true,
102            auto_evict: true,
103        }
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_horizon_duration() {
113        assert!(TimeHorizon::Immediate.duration().is_some());
114        assert!(TimeHorizon::Permanent.duration().is_none());
115    }
116
117    #[test]
118    fn test_horizon_for_depth() {
119        assert_eq!(TimeHorizon::for_depth(0), TimeHorizon::LongTerm);
120        assert_eq!(TimeHorizon::for_depth(3), TimeHorizon::Immediate);
121    }
122
123    #[test]
124    fn test_contains() {
125        let now = Utc::now();
126        assert!(TimeHorizon::Immediate.contains(now));
127    }
128}