Skip to main content

autoagents_core/runtime/
mod.rs

1use crate::actor::{AnyActor, CloneableMessage, Transport};
2use async_trait::async_trait;
3use autoagents_protocol::{Event, RuntimeID};
4use ractor::ActorRef;
5use std::any::{Any, TypeId};
6use std::fmt::Debug;
7use std::sync::Arc;
8use tokio::sync::mpsc;
9use tokio::sync::mpsc::error::SendError;
10use tokio::task::JoinError;
11
12pub(crate) mod manager;
13mod single_threaded;
14use crate::actor::Topic;
15use crate::utils::BoxEventStream;
16pub use single_threaded::SingleThreadedRuntime;
17
18/// Configuration for runtime instances.
19#[derive(Debug, Clone)]
20pub struct RuntimeConfig {
21    pub queue_size: Option<usize>,
22}
23
24impl Default for RuntimeConfig {
25    fn default() -> Self {
26        Self {
27            queue_size: Some(100),
28        }
29    }
30}
31
32/// Error types for runtime operations and message routing.
33#[derive(Debug, thiserror::Error)]
34pub enum RuntimeError {
35    #[error("Send Message Error: {0}")]
36    SendMessage(String),
37
38    #[error("TopicTypeMismatch")]
39    TopicTypeMismatch(String, TypeId),
40
41    #[error("Join Error: {0}")]
42    JoinError(JoinError),
43
44    #[error("Runtime operation failed: {0}")]
45    OperationFailed(String),
46
47    #[error("Event error: {0}")]
48    EventError(#[from] Box<SendError<Event>>),
49}
50
51/// Abstract runtime that manages actor subscriptions, pub/sub delivery, and
52/// emission of protocol events. Implementations can provide different threading
53/// or transport strategies.
54#[async_trait]
55pub trait Runtime: Send + Sync {
56    fn id(&self) -> RuntimeID;
57
58    async fn subscribe_any(
59        &self,
60        topic_name: &str,
61        topic_type: TypeId,
62        actor: Arc<dyn AnyActor>,
63    ) -> Result<(), RuntimeError>;
64
65    async fn publish_any(
66        &self,
67        topic_name: &str,
68        topic_type: TypeId,
69        message: Arc<dyn Any + Send + Sync>,
70    ) -> Result<(), RuntimeError>;
71
72    /// Local event processing sender. Agents receive this and emit protocol
73    /// `Event`s through it. The runtime is responsible for forwarding them to
74    /// the owning `Environment`.
75    fn tx(&self) -> mpsc::Sender<Event>;
76    async fn transport(&self) -> Arc<dyn Transport>;
77    async fn take_event_receiver(&self) -> Option<BoxEventStream<Event>>;
78    /// Subscribe to runtime protocol events without consuming the receiver.
79    async fn subscribe_events(&self) -> BoxEventStream<Event>;
80    /// Run the runtime event loop and process internal messages until stopped.
81    async fn run(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
82    /// Request shutdown of the runtime.
83    async fn stop(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
84}
85
86/// Type-safe convenience layer over `Runtime` for strongly-typed topics and
87/// direct messaging to actors.
88#[async_trait]
89pub trait TypedRuntime: Runtime {
90    async fn subscribe<M>(&self, topic: &Topic<M>, actor: ActorRef<M>) -> Result<(), RuntimeError>
91    where
92        M: CloneableMessage + 'static,
93    {
94        let arc_actor = Arc::new(actor) as Arc<dyn AnyActor>;
95        self.subscribe_any(topic.name(), TypeId::of::<M>(), arc_actor)
96            .await
97    }
98
99    async fn publish<M>(&self, topic: &Topic<M>, message: M) -> Result<(), RuntimeError>
100    where
101        M: CloneableMessage + 'static,
102    {
103        let arc_msg = Arc::new(message) as Arc<dyn Any + Send + Sync>;
104        self.publish_any(topic.name(), TypeId::of::<M>(), arc_msg)
105            .await
106    }
107
108    async fn send_message<M: CloneableMessage + 'static>(
109        &self,
110        message: M,
111        addr: ActorRef<M>,
112    ) -> Result<(), RuntimeError> {
113        addr.cast(message)
114            .map_err(|e| RuntimeError::SendMessage(e.to_string()))
115    }
116}
117
118// Auto-implement TypedRuntime for all Runtime implementations
119impl<T: Runtime + ?Sized> TypedRuntime for T {}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use crate::actor::{ActorMessage, CloneableMessage, LocalTransport, Topic, Transport};
125    use async_trait::async_trait;
126    use futures::stream;
127    use tokio::sync::Mutex;
128
129    #[derive(Debug, Clone)]
130    struct TestMessage {
131        value: String,
132    }
133
134    impl ActorMessage for TestMessage {}
135    impl CloneableMessage for TestMessage {}
136
137    #[derive(Debug)]
138    struct TestRuntime {
139        published: Arc<Mutex<Vec<(String, TypeId, String)>>>,
140        tx: mpsc::Sender<Event>,
141    }
142
143    impl TestRuntime {
144        fn new() -> Self {
145            let (tx, _rx) = mpsc::channel(1);
146            Self {
147                published: Arc::new(Mutex::new(Vec::new())),
148                tx,
149            }
150        }
151    }
152
153    #[async_trait]
154    impl Runtime for TestRuntime {
155        fn id(&self) -> RuntimeID {
156            RuntimeID::new_v4()
157        }
158
159        async fn subscribe_any(
160            &self,
161            _topic_name: &str,
162            _topic_type: TypeId,
163            _actor: Arc<dyn AnyActor>,
164        ) -> Result<(), RuntimeError> {
165            Ok(())
166        }
167
168        async fn publish_any(
169            &self,
170            topic_name: &str,
171            topic_type: TypeId,
172            message: Arc<dyn Any + Send + Sync>,
173        ) -> Result<(), RuntimeError> {
174            let msg = message
175                .downcast_ref::<TestMessage>()
176                .map(|m| m.value.clone())
177                .unwrap_or_default();
178            let mut published = self.published.lock().await;
179            published.push((topic_name.to_string(), topic_type, msg));
180            Ok(())
181        }
182
183        fn tx(&self) -> mpsc::Sender<Event> {
184            self.tx.clone()
185        }
186
187        async fn transport(&self) -> Arc<dyn Transport> {
188            Arc::new(LocalTransport)
189        }
190
191        async fn take_event_receiver(&self) -> Option<BoxEventStream<Event>> {
192            None
193        }
194
195        async fn subscribe_events(&self) -> BoxEventStream<Event> {
196            Box::pin(stream::empty())
197        }
198
199        async fn run(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
200            Ok(())
201        }
202
203        async fn stop(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
204            Ok(())
205        }
206    }
207
208    #[tokio::test]
209    async fn test_typed_runtime_publish_records_message() {
210        let runtime = TestRuntime::new();
211        let topic = Topic::<TestMessage>::new("topic");
212        runtime
213            .publish(
214                &topic,
215                TestMessage {
216                    value: "hello".to_string(),
217                },
218            )
219            .await
220            .unwrap();
221
222        let published = runtime.published.lock().await.clone();
223        assert_eq!(published.len(), 1);
224        assert_eq!(published[0].0, "topic");
225        assert_eq!(published[0].2, "hello");
226    }
227}