Skip to main content

loa_core/
agent.rs

1//! Agent - Main entry point for running an observability agent
2
3use crate::{builder::AgentBuilder, Result};
4use ractor::ActorRef;
5use ractor_supervisor::SupervisorMsg;
6use std::path::PathBuf;
7use tokio::signal::ctrl_c;
8use tokio::task::JoinHandle;
9
10/// The main agent handle returned by the builder
11///
12/// Holds references to the root supervisor and can be used to run the agent until shutdown.
13pub struct Agent {
14    storage_path: PathBuf,
15    identity_path: Option<PathBuf>,
16    dashboard_port: Option<u16>,
17    supervisor: ActorRef<SupervisorMsg>,
18    supervisor_handle: JoinHandle<()>,
19}
20
21impl Agent {
22    /// Create a new agent builder
23    ///
24    /// This is the recommended way to create an agent.
25    /// Use `.build().await` to construct the agent after configuration.
26    ///
27    /// # Example
28    ///
29    /// ```no_run
30    /// use elo::Agent;
31    ///
32    /// # async fn example() -> anyhow::Result<()> {
33    /// let agent = Agent::builder()
34    ///     .storage_path("/var/lib/loa")
35    ///     .dashboard_port(3000)
36    ///     .build()
37    ///     .await?;
38    /// # Ok(())
39    /// # }
40    /// ```
41    pub fn builder() -> AgentBuilder {
42        AgentBuilder::new()
43    }
44
45    /// Internal constructor used by the builder
46    #[doc(hidden)]
47    pub(crate) fn new(
48        storage_path: PathBuf,
49        identity_path: Option<PathBuf>,
50        dashboard_port: Option<u16>,
51        supervisor: ActorRef<SupervisorMsg>,
52        supervisor_handle: JoinHandle<()>,
53    ) -> Self {
54        Self {
55            storage_path,
56            identity_path,
57            dashboard_port,
58            supervisor,
59            supervisor_handle,
60        }
61    }
62
63    /// Run the agent until shutdown signal (Ctrl+C)
64    ///
65    /// This will block until:
66    /// - Ctrl+C is pressed
67    /// - An unrecoverable error occurs
68    /// - shutdown() is called from another task
69    ///
70    /// # Example
71    ///
72    /// ```no_run
73    /// use elo::Agent;
74    ///
75    /// # async fn example() -> anyhow::Result<()> {
76    /// let agent = Agent::builder()
77    ///     .storage_path("/var/lib/loa")
78    ///     .build()
79    ///     .await?;
80    ///
81    /// agent.run().await?;
82    /// # Ok(())
83    /// # }
84    /// ```
85    pub async fn run(self) -> Result<()> {
86        tracing::debug!("Agent running");
87        tracing::debug!("  Storage: {}", self.storage_path.display());
88        if let Some(ref identity_path) = self.identity_path {
89            tracing::debug!("  Identity: {}", identity_path.display());
90        }
91        if let Some(port) = self.dashboard_port {
92            tracing::debug!("  Dashboard: http://localhost:{}", port);
93        }
94
95        // Wait for shutdown signal
96        ctrl_c().await?;
97
98        tracing::info!("Shutdown signal received");
99
100        // Stop supervisor (will cascade to all children)
101        tracing::debug!("Stopping supervisor...");
102        self.supervisor.stop(None);
103
104        // Wait for supervisor to finish
105        let _ = self.supervisor_handle.await;
106
107        tracing::info!("Agent stopped");
108
109        Ok(())
110    }
111}