loa_core/builder.rs
1//! Builder pattern for constructing Agent instances
2
3use crate::{agent::Agent, supervisor, Error, Result};
4use std::path::{Path, PathBuf};
5
6/// Builder for configuring and creating an Agent
7///
8/// # Example
9///
10/// ```no_run
11/// use elo::Agent;
12///
13/// # async fn example() -> anyhow::Result<()> {
14/// let agent = Agent::builder()
15/// .storage_path("/var/lib/loa")
16/// .identity_path("/etc/loa/agent_id.key")
17/// .dashboard_port(3000)
18/// .build()
19/// .await?;
20/// # Ok(())
21/// # }
22/// ```
23pub struct AgentBuilder {
24 storage_path: Option<PathBuf>,
25 identity_path: Option<PathBuf>,
26 dashboard_port: Option<u16>,
27 claim_token_id: Option<String>,
28}
29
30impl AgentBuilder {
31 /// Create a new agent builder with default settings
32 pub(crate) fn new() -> Self {
33 Self {
34 storage_path: None,
35 identity_path: None,
36 dashboard_port: None,
37 claim_token_id: None,
38 }
39 }
40
41 /// Set the storage path for databases and persistent data
42 ///
43 /// This is required. If not set, build() will return an error.
44 ///
45 /// # Example
46 ///
47 /// ```no_run
48 /// use elo::Agent;
49 ///
50 /// # async fn example() -> anyhow::Result<()> {
51 /// let agent = Agent::builder()
52 /// .storage_path("/var/lib/loa")
53 /// .build()
54 /// .await?;
55 /// # Ok(())
56 /// # }
57 /// ```
58 pub fn storage_path<P: AsRef<Path>>(mut self, path: P) -> Self {
59 self.storage_path = Some(path.as_ref().to_path_buf());
60 self
61 }
62
63 /// Set the identity path for agent identity key
64 ///
65 /// If not specified, defaults to `{storage_path}/agent_id.key`
66 ///
67 /// # Example
68 ///
69 /// ```no_run
70 /// use elo::Agent;
71 ///
72 /// # async fn example() -> anyhow::Result<()> {
73 /// let agent = Agent::builder()
74 /// .storage_path("/var/lib/loa")
75 /// .identity_path("/etc/loa/agent_id.key")
76 /// .build()
77 /// .await?;
78 /// # Ok(())
79 /// # }
80 /// ```
81 pub fn identity_path<P: AsRef<Path>>(mut self, path: P) -> Self {
82 self.identity_path = Some(path.as_ref().to_path_buf());
83 self
84 }
85
86 /// Set the dashboard port
87 ///
88 /// Optional. If set, a dashboard will be served on this port (future feature).
89 ///
90 /// # Example
91 ///
92 /// ```no_run
93 /// use elo::Agent;
94 ///
95 /// # async fn example() -> anyhow::Result<()> {
96 /// let agent = Agent::builder()
97 /// .storage_path("/var/lib/loa")
98 /// .dashboard_port(3000)
99 /// .build()
100 /// .await?;
101 /// # Ok(())
102 /// # }
103 /// ```
104 pub fn dashboard_port(mut self, port: u16) -> Self {
105 self.dashboard_port = Some(port);
106 self
107 }
108
109 /// Claim this agent for a workspace using a claim token
110 ///
111 /// This allows immediate agent claiming during startup. The claim_token_id is sent
112 /// with the heartbeat for self-registration, linking the agent to a workspace.
113 ///
114 /// # Example
115 ///
116 /// ```no_run
117 /// use elo::Agent;
118 ///
119 /// # async fn example() -> anyhow::Result<()> {
120 /// let agent = Agent::builder()
121 /// .storage_path("/var/lib/loa")
122 /// .claim("550e8400-e29b-41d4-a716-446655440000")
123 /// .build()
124 /// .await?;
125 /// # Ok(())
126 /// # }
127 /// ```
128 pub fn claim<S: Into<String>>(mut self, claim_token_id: S) -> Self {
129 self.claim_token_id = Some(claim_token_id.into());
130 self
131 }
132
133 /// Build the agent and spawn all internal actors
134 ///
135 /// This will:
136 /// 1. Validate configuration
137 /// 2. Create storage directories if needed
138 /// 3. Spawn the root supervisor
139 /// 4. Spawn all core actors (currently empty)
140 /// 5. Return an Agent handle
141 ///
142 /// # Errors
143 ///
144 /// Returns an error if:
145 /// - storage_path is not set
146 /// - storage directories cannot be created
147 /// - supervisor fails to spawn
148 ///
149 /// # Example
150 ///
151 /// ```no_run
152 /// use elo::Agent;
153 ///
154 /// # async fn example() -> anyhow::Result<()> {
155 /// let agent = Agent::builder()
156 /// .storage_path("/var/lib/loa")
157 /// .build()
158 /// .await?;
159 ///
160 /// agent.run().await?;
161 /// # Ok(())
162 /// # }
163 /// ```
164 pub async fn build(self) -> Result<Agent> {
165 // Validate required fields
166 let storage_path = self
167 .storage_path
168 .ok_or_else(|| Error::Config("storage_path is required".to_string()))?;
169
170 // Apply defaults
171 let identity_path = self.identity_path.or_else(|| {
172 Some(storage_path.join("agent_id.key"))
173 });
174
175 tracing::debug!("Building agent...");
176 tracing::debug!(" Storage: {}", storage_path.display());
177 if let Some(ref identity_path) = identity_path {
178 tracing::debug!(" Identity: {}", identity_path.display());
179 }
180 if let Some(port) = self.dashboard_port {
181 tracing::debug!(" Dashboard: localhost:{}", port);
182 }
183
184 // Create storage directory if it doesn't exist
185 if !storage_path.exists() {
186 tracing::debug!("Creating storage directory: {}", storage_path.display());
187 std::fs::create_dir_all(&storage_path)?;
188 }
189
190 // Load or generate agent identity (wrapped in Arc for efficient sharing)
191 let identity_file = identity_path.as_ref().unwrap();
192 tracing::debug!("Loading agent identity from {}", identity_file.display());
193 let identity = std::sync::Arc::new(crate::AgentIdentity::from_file_or_generate_new(identity_file)?);
194 let peer_id = identity.peer_id().clone();
195 tracing::debug!(" Agent PeerId: {}", peer_id);
196
197 // Initialize global identity singleton for access from all actors
198 crate::identity::init_global_identity(identity.clone());
199
200 // Spawn root supervisor with shared identity, claim_token_id, and storage_path
201 tracing::debug!("Spawning root supervisor...");
202 let (supervisor, supervisor_handle) = supervisor::spawn_root(identity, self.claim_token_id, storage_path.clone()).await?;
203 tracing::debug!(" \u{2713} Root supervisor spawned");
204
205 tracing::info!("Agent ready (PeerId: {}, Storage: {})", peer_id, storage_path.display());
206
207 Ok(Agent::new(
208 storage_path,
209 identity_path,
210 self.dashboard_port,
211 supervisor,
212 supervisor_handle,
213 ))
214 }
215}