Skip to main content

zeph_memory/
types.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4/// Memory tier classification for the AOI three-layer architecture.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
6#[serde(rename_all = "lowercase")]
7pub enum MemoryTier {
8    /// Current conversation window. Virtual tier — not stored in the DB.
9    Working,
10    /// Session-bound messages. Default tier for all persisted messages.
11    Episodic,
12    /// Cross-session distilled facts. Promoted from Episodic when a fact
13    /// appears in `promotion_min_sessions`+ distinct sessions.
14    Semantic,
15}
16
17impl MemoryTier {
18    #[must_use]
19    pub fn as_str(self) -> &'static str {
20        match self {
21            Self::Working => "working",
22            Self::Episodic => "episodic",
23            Self::Semantic => "semantic",
24        }
25    }
26}
27
28impl std::fmt::Display for MemoryTier {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        f.write_str(self.as_str())
31    }
32}
33
34impl std::str::FromStr for MemoryTier {
35    type Err = String;
36    fn from_str(s: &str) -> Result<Self, Self::Err> {
37        match s {
38            "working" => Ok(Self::Working),
39            "episodic" => Ok(Self::Episodic),
40            "semantic" => Ok(Self::Semantic),
41            other => Err(format!("unknown memory tier: {other}")),
42        }
43    }
44}
45
46/// Strongly typed wrapper for conversation row IDs.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::Type)]
48#[sqlx(transparent)]
49pub struct ConversationId(pub i64);
50
51/// Strongly typed wrapper for message row IDs.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::Type)]
53#[sqlx(transparent)]
54pub struct MessageId(pub i64);
55
56impl std::fmt::Display for ConversationId {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        write!(f, "{}", self.0)
59    }
60}
61
62impl std::fmt::Display for MessageId {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        write!(f, "{}", self.0)
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn memory_tier_round_trip() {
74        for tier in [
75            MemoryTier::Working,
76            MemoryTier::Episodic,
77            MemoryTier::Semantic,
78        ] {
79            let s = tier.as_str();
80            let parsed: MemoryTier = s.parse().expect("should parse");
81            assert_eq!(parsed, tier);
82            assert_eq!(format!("{tier}"), s);
83        }
84    }
85
86    #[test]
87    fn memory_tier_unknown_string_errors() {
88        assert!("unknown".parse::<MemoryTier>().is_err());
89    }
90
91    #[test]
92    fn memory_tier_serde_round_trip() {
93        let json = serde_json::to_string(&MemoryTier::Semantic).unwrap();
94        assert_eq!(json, "\"semantic\"");
95        let parsed: MemoryTier = serde_json::from_str(&json).unwrap();
96        assert_eq!(parsed, MemoryTier::Semantic);
97    }
98
99    #[test]
100    fn conversation_id_display() {
101        let id = ConversationId(42);
102        assert_eq!(format!("{id}"), "42");
103    }
104
105    #[test]
106    fn message_id_display() {
107        let id = MessageId(7);
108        assert_eq!(format!("{id}"), "7");
109    }
110
111    #[test]
112    fn conversation_id_eq() {
113        assert_eq!(ConversationId(1), ConversationId(1));
114        assert_ne!(ConversationId(1), ConversationId(2));
115    }
116
117    #[test]
118    fn message_id_copy() {
119        let id = MessageId(5);
120        let copied = id;
121        assert_eq!(id, copied);
122    }
123}