Skip to main content

mnemo_core/model/
event.rs

1use serde::{Deserialize, Serialize};
2use uuid::Uuid;
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5pub struct AgentEvent {
6    pub id: Uuid,
7    pub agent_id: String,
8    pub thread_id: Option<String>,
9    pub run_id: Option<String>,
10    pub parent_event_id: Option<Uuid>,
11    pub event_type: EventType,
12    pub payload: serde_json::Value,
13    // OTel fields
14    pub trace_id: Option<String>,
15    pub span_id: Option<String>,
16    pub model: Option<String>,
17    pub tokens_input: Option<i64>,
18    pub tokens_output: Option<i64>,
19    pub latency_ms: Option<i64>,
20    pub cost_usd: Option<f64>,
21    // Temporal
22    pub timestamp: String,
23    pub logical_clock: i64,
24    // Integrity
25    pub content_hash: Vec<u8>,
26    pub prev_hash: Option<Vec<u8>>,
27    // Optional embedding of the event payload
28    pub embedding: Option<Vec<f32>>,
29}
30
31impl AgentEvent {
32    /// Create a new `AgentEvent` with required fields; all optional fields default to `None`.
33    pub fn new(
34        agent_id: String,
35        event_type: EventType,
36        payload: serde_json::Value,
37        timestamp: String,
38        content_hash: Vec<u8>,
39    ) -> Self {
40        Self {
41            id: Uuid::now_v7(),
42            agent_id,
43            thread_id: None,
44            run_id: None,
45            parent_event_id: None,
46            event_type,
47            payload,
48            trace_id: None,
49            span_id: None,
50            model: None,
51            tokens_input: None,
52            tokens_output: None,
53            latency_ms: None,
54            cost_usd: None,
55            timestamp,
56            logical_clock: 0,
57            content_hash,
58            prev_hash: None,
59            embedding: None,
60        }
61    }
62
63    /// Create an `AgentEvent` with all fields specified.
64    /// Intended for storage backends that reconstruct events from database rows.
65    #[allow(clippy::too_many_arguments)]
66    pub fn from_parts(
67        id: Uuid,
68        agent_id: String,
69        thread_id: Option<String>,
70        run_id: Option<String>,
71        parent_event_id: Option<Uuid>,
72        event_type: EventType,
73        payload: serde_json::Value,
74        trace_id: Option<String>,
75        span_id: Option<String>,
76        model: Option<String>,
77        tokens_input: Option<i64>,
78        tokens_output: Option<i64>,
79        latency_ms: Option<i64>,
80        cost_usd: Option<f64>,
81        timestamp: String,
82        logical_clock: i64,
83        content_hash: Vec<u8>,
84        prev_hash: Option<Vec<u8>>,
85        embedding: Option<Vec<f32>>,
86    ) -> Self {
87        Self {
88            id,
89            agent_id,
90            thread_id,
91            run_id,
92            parent_event_id,
93            event_type,
94            payload,
95            trace_id,
96            span_id,
97            model,
98            tokens_input,
99            tokens_output,
100            latency_ms,
101            cost_usd,
102            timestamp,
103            logical_clock,
104            content_hash,
105            prev_hash,
106            embedding,
107        }
108    }
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
112#[serde(rename_all = "snake_case")]
113pub enum EventType {
114    MemoryWrite,
115    MemoryRead,
116    MemoryDelete,
117    MemoryShare,
118    MemoryExpired,
119    MemoryRedact,
120    Checkpoint,
121    Branch,
122    Merge,
123    UserMessage,
124    AssistantMessage,
125    ToolCall,
126    ToolResult,
127    Error,
128    RetrievalQuery,
129    RetrievalResult,
130    Decision,
131    /// `run_reflection_pass` finished successfully. Payload carries the
132    /// `ReflectionReport` so cadence-aware callers can skip when
133    /// `last_reflection_at` is too recent.
134    ReflectionCompleted,
135    /// An Auto Dream organization-report trailer was parsed and
136    /// ingested. Payload carries merged/removed/re-indexed counts.
137    DreamReportIngested,
138}
139
140impl std::fmt::Display for EventType {
141    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142        match self {
143            EventType::MemoryWrite => write!(f, "memory_write"),
144            EventType::MemoryRead => write!(f, "memory_read"),
145            EventType::MemoryDelete => write!(f, "memory_delete"),
146            EventType::MemoryShare => write!(f, "memory_share"),
147            EventType::MemoryExpired => write!(f, "memory_expired"),
148            EventType::MemoryRedact => write!(f, "memory_redact"),
149            EventType::Checkpoint => write!(f, "checkpoint"),
150            EventType::Branch => write!(f, "branch"),
151            EventType::Merge => write!(f, "merge"),
152            EventType::UserMessage => write!(f, "user_message"),
153            EventType::AssistantMessage => write!(f, "assistant_message"),
154            EventType::ToolCall => write!(f, "tool_call"),
155            EventType::ToolResult => write!(f, "tool_result"),
156            EventType::Error => write!(f, "error"),
157            EventType::RetrievalQuery => write!(f, "retrieval_query"),
158            EventType::RetrievalResult => write!(f, "retrieval_result"),
159            EventType::Decision => write!(f, "decision"),
160            EventType::ReflectionCompleted => write!(f, "reflection_completed"),
161            EventType::DreamReportIngested => write!(f, "dream_report_ingested"),
162        }
163    }
164}
165
166impl std::str::FromStr for EventType {
167    type Err = crate::error::Error;
168    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
169        match s {
170            "memory_write" => Ok(EventType::MemoryWrite),
171            "memory_read" => Ok(EventType::MemoryRead),
172            "memory_delete" => Ok(EventType::MemoryDelete),
173            "memory_share" => Ok(EventType::MemoryShare),
174            "memory_expired" => Ok(EventType::MemoryExpired),
175            "memory_redact" => Ok(EventType::MemoryRedact),
176            "checkpoint" => Ok(EventType::Checkpoint),
177            "branch" => Ok(EventType::Branch),
178            "merge" => Ok(EventType::Merge),
179            "user_message" => Ok(EventType::UserMessage),
180            "assistant_message" => Ok(EventType::AssistantMessage),
181            "tool_call" => Ok(EventType::ToolCall),
182            "tool_result" => Ok(EventType::ToolResult),
183            "error" => Ok(EventType::Error),
184            "retrieval_query" => Ok(EventType::RetrievalQuery),
185            "retrieval_result" => Ok(EventType::RetrievalResult),
186            "decision" => Ok(EventType::Decision),
187            "reflection_completed" => Ok(EventType::ReflectionCompleted),
188            "dream_report_ingested" => Ok(EventType::DreamReportIngested),
189            _ => Err(crate::error::Error::Validation(format!(
190                "invalid event type: {s}"
191            ))),
192        }
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_agent_event_serde() {
202        let event = AgentEvent {
203            id: Uuid::now_v7(),
204            agent_id: "agent-1".to_string(),
205            thread_id: Some("thread-1".to_string()),
206            run_id: None,
207            parent_event_id: None,
208            event_type: EventType::MemoryWrite,
209            payload: serde_json::json!({"memory_id": "abc"}),
210            trace_id: None,
211            span_id: None,
212            model: None,
213            tokens_input: None,
214            tokens_output: None,
215            latency_ms: None,
216            cost_usd: None,
217            timestamp: "2025-01-01T00:00:00Z".to_string(),
218            logical_clock: 1,
219            content_hash: vec![1, 2, 3],
220            prev_hash: None,
221            embedding: None,
222        };
223        let json = serde_json::to_string(&event).unwrap();
224        let deserialized: AgentEvent = serde_json::from_str(&json).unwrap();
225        assert_eq!(event, deserialized);
226    }
227
228    #[test]
229    fn test_event_type_display_fromstr() {
230        assert_eq!(EventType::MemoryWrite.to_string(), "memory_write");
231        assert_eq!(
232            "memory_read".parse::<EventType>().unwrap(),
233            EventType::MemoryRead
234        );
235        assert_eq!(
236            "checkpoint".parse::<EventType>().unwrap(),
237            EventType::Checkpoint
238        );
239        assert_eq!("error".parse::<EventType>().unwrap(), EventType::Error);
240        assert!("invalid".parse::<EventType>().is_err());
241    }
242}