mojentic/agents/
base_agent.rs

1//! Base trait for synchronous agents.
2//!
3//! This module defines the core `BaseAgent` trait that synchronous agents
4//! implement. Agents receive events and can produce new events in response.
5//!
6//! For agents that need to perform async operations (I/O, LLM calls, etc.),
7//! use the `BaseAsyncAgent` trait instead.
8
9use crate::event::Event;
10
11/// Base trait for synchronous agents in the system.
12///
13/// Agents process events and optionally emit new events. This trait defines
14/// the simplest agent interface for synchronous event processing.
15///
16/// # When to Use
17///
18/// Use `BaseAgent` when:
19/// - Event processing doesn't require I/O operations
20/// - Processing is fast and won't block
21/// - You have a simple transformation pipeline
22///
23/// Use `BaseAsyncAgent` when:
24/// - You need to call LLMs or external APIs
25/// - Processing involves database queries
26/// - Operations may take significant time
27///
28/// # Examples
29///
30/// ```
31/// use mojentic::agents::BaseAgent;
32/// use mojentic::event::Event;
33///
34/// struct TransformAgent;
35///
36/// impl BaseAgent for TransformAgent {
37///     fn receive_event(&self, event: Box<dyn Event>) -> Vec<Box<dyn Event>> {
38///         // Process the event synchronously and return new events
39///         vec![]
40///     }
41/// }
42/// ```
43pub trait BaseAgent: Send + Sync {
44    /// Process an event synchronously and return resulting events.
45    ///
46    /// This method is called when an event is routed to this agent. The agent
47    /// can process the event and return zero or more new events to be processed.
48    ///
49    /// # Arguments
50    ///
51    /// * `event` - The event to process
52    ///
53    /// # Returns
54    ///
55    /// A vector of new events to be dispatched (can be empty).
56    fn receive_event(&self, event: Box<dyn Event>) -> Vec<Box<dyn Event>>;
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use crate::event::Event;
63    use serde::{Deserialize, Serialize};
64    use std::any::Any;
65
66    #[derive(Debug, Clone, Serialize, Deserialize)]
67    struct TestEvent {
68        source: String,
69        correlation_id: Option<String>,
70        data: String,
71    }
72
73    impl Event for TestEvent {
74        fn source(&self) -> &str {
75            &self.source
76        }
77
78        fn correlation_id(&self) -> Option<&str> {
79            self.correlation_id.as_deref()
80        }
81
82        fn set_correlation_id(&mut self, id: String) {
83            self.correlation_id = Some(id);
84        }
85
86        fn as_any(&self) -> &dyn Any {
87            self
88        }
89
90        fn clone_box(&self) -> Box<dyn Event> {
91            Box::new(self.clone())
92        }
93    }
94
95    struct SimpleAgent;
96
97    impl BaseAgent for SimpleAgent {
98        fn receive_event(&self, _event: Box<dyn Event>) -> Vec<Box<dyn Event>> {
99            vec![]
100        }
101    }
102
103    struct EchoAgent;
104
105    impl BaseAgent for EchoAgent {
106        fn receive_event(&self, event: Box<dyn Event>) -> Vec<Box<dyn Event>> {
107            // Echo back a new event
108            let new_event = TestEvent {
109                source: "EchoAgent".to_string(),
110                correlation_id: event.correlation_id().map(|s| s.to_string()),
111                data: "echoed".to_string(),
112            };
113            vec![Box::new(new_event)]
114        }
115    }
116
117    struct MultiEventAgent;
118
119    impl BaseAgent for MultiEventAgent {
120        fn receive_event(&self, event: Box<dyn Event>) -> Vec<Box<dyn Event>> {
121            vec![
122                Box::new(TestEvent {
123                    source: "MultiEventAgent".to_string(),
124                    correlation_id: event.correlation_id().map(|s| s.to_string()),
125                    data: "event1".to_string(),
126                }),
127                Box::new(TestEvent {
128                    source: "MultiEventAgent".to_string(),
129                    correlation_id: event.correlation_id().map(|s| s.to_string()),
130                    data: "event2".to_string(),
131                }),
132            ]
133        }
134    }
135
136    #[test]
137    fn test_simple_agent_returns_empty() {
138        let agent = SimpleAgent;
139        let event = Box::new(TestEvent {
140            source: "Test".to_string(),
141            correlation_id: None,
142            data: "test".to_string(),
143        }) as Box<dyn Event>;
144
145        let result = agent.receive_event(event);
146        assert_eq!(result.len(), 0);
147    }
148
149    #[test]
150    fn test_echo_agent_returns_event() {
151        let agent = EchoAgent;
152        let event = Box::new(TestEvent {
153            source: "Test".to_string(),
154            correlation_id: Some("test-123".to_string()),
155            data: "original".to_string(),
156        }) as Box<dyn Event>;
157
158        let result = agent.receive_event(event);
159        assert_eq!(result.len(), 1);
160
161        let returned_event = result[0].as_any().downcast_ref::<TestEvent>().unwrap();
162        assert_eq!(returned_event.source(), "EchoAgent");
163        assert_eq!(returned_event.correlation_id(), Some("test-123"));
164        assert_eq!(returned_event.data, "echoed");
165    }
166
167    #[test]
168    fn test_agent_preserves_correlation_id() {
169        let agent = EchoAgent;
170        let event = Box::new(TestEvent {
171            source: "Test".to_string(),
172            correlation_id: Some("preserve-456".to_string()),
173            data: "test".to_string(),
174        }) as Box<dyn Event>;
175
176        let result = agent.receive_event(event);
177        assert_eq!(result.len(), 1);
178
179        let correlation = result[0].correlation_id();
180        assert_eq!(correlation, Some("preserve-456"));
181    }
182
183    #[test]
184    fn test_agent_can_return_multiple_events() {
185        let agent = MultiEventAgent;
186        let event = Box::new(TestEvent {
187            source: "Test".to_string(),
188            correlation_id: Some("multi-789".to_string()),
189            data: "test".to_string(),
190        }) as Box<dyn Event>;
191
192        let result = agent.receive_event(event);
193        assert_eq!(result.len(), 2);
194
195        let event1 = result[0].as_any().downcast_ref::<TestEvent>().unwrap();
196        assert_eq!(event1.data, "event1");
197        assert_eq!(event1.correlation_id(), Some("multi-789"));
198
199        let event2 = result[1].as_any().downcast_ref::<TestEvent>().unwrap();
200        assert_eq!(event2.data, "event2");
201        assert_eq!(event2.correlation_id(), Some("multi-789"));
202    }
203
204    #[test]
205    fn test_agent_without_correlation_id() {
206        let agent = EchoAgent;
207        let event = Box::new(TestEvent {
208            source: "Test".to_string(),
209            correlation_id: None,
210            data: "test".to_string(),
211        }) as Box<dyn Event>;
212
213        let result = agent.receive_event(event);
214        assert_eq!(result.len(), 1);
215
216        let returned_event = result[0].as_any().downcast_ref::<TestEvent>().unwrap();
217        assert_eq!(returned_event.correlation_id(), None);
218    }
219}