symbi_runtime/reasoning/
agent_registry.rs1use crate::reasoning::inference::InferenceProvider;
7use crate::types::AgentId;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::sync::Arc;
11use tokio::sync::RwLock;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct RegisteredAgent {
16 pub agent_id: AgentId,
18 pub name: String,
20 pub system_prompt: String,
22 pub tools: Vec<String>,
24 pub response_format: Option<String>,
26 pub created_at: chrono::DateTime<chrono::Utc>,
28}
29
30#[derive(Clone)]
32pub struct AgentRegistry {
33 agents: Arc<RwLock<HashMap<String, RegisteredAgent>>>,
34}
35
36impl Default for AgentRegistry {
37 fn default() -> Self {
38 Self::new()
39 }
40}
41
42impl AgentRegistry {
43 pub fn new() -> Self {
45 Self {
46 agents: Arc::new(RwLock::new(HashMap::new())),
47 }
48 }
49
50 pub async fn spawn_agent(
52 &self,
53 name: impl Into<String>,
54 system_prompt: impl Into<String>,
55 tools: Vec<String>,
56 response_format: Option<String>,
57 ) -> AgentId {
58 let name = name.into();
59 let agent_id = AgentId::new();
60
61 let agent = RegisteredAgent {
62 agent_id,
63 name: name.clone(),
64 system_prompt: system_prompt.into(),
65 tools,
66 response_format,
67 created_at: chrono::Utc::now(),
68 };
69
70 self.agents.write().await.insert(name, agent);
71 agent_id
72 }
73
74 pub async fn get_agent(&self, name: &str) -> Option<RegisteredAgent> {
76 self.agents.read().await.get(name).cloned()
77 }
78
79 pub async fn list_agents(&self) -> Vec<RegisteredAgent> {
81 self.agents.read().await.values().cloned().collect()
82 }
83
84 pub async fn remove_agent(&self, name: &str) -> bool {
86 self.agents.write().await.remove(name).is_some()
87 }
88
89 pub async fn has_agent(&self, name: &str) -> bool {
91 self.agents.read().await.contains_key(name)
92 }
93
94 pub async fn ask_agent(
99 &self,
100 name: &str,
101 message: &str,
102 provider: &dyn InferenceProvider,
103 ) -> Result<String, AgentRegistryError> {
104 let agent = self
105 .get_agent(name)
106 .await
107 .ok_or_else(|| AgentRegistryError::NotFound {
108 name: name.to_string(),
109 })?;
110
111 use crate::reasoning::conversation::{Conversation, ConversationMessage};
112 use crate::reasoning::inference::InferenceOptions;
113
114 let mut conv = Conversation::with_system(&agent.system_prompt);
115 conv.push(ConversationMessage::user(message));
116
117 let options = InferenceOptions::default();
118 let response = provider.complete(&conv, &options).await.map_err(|e| {
119 AgentRegistryError::InferenceError {
120 agent_name: name.to_string(),
121 message: e.to_string(),
122 }
123 })?;
124
125 Ok(response.content)
126 }
127}
128
129#[derive(Debug, thiserror::Error)]
131pub enum AgentRegistryError {
132 #[error("Agent '{name}' not found")]
133 NotFound { name: String },
134
135 #[error("Inference error for agent '{agent_name}': {message}")]
136 InferenceError { agent_name: String, message: String },
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[tokio::test]
144 async fn test_spawn_and_get_agent() {
145 let registry = AgentRegistry::new();
146
147 let id = registry
148 .spawn_agent(
149 "researcher",
150 "You are a researcher.",
151 vec!["search".into()],
152 None,
153 )
154 .await;
155
156 let agent = registry.get_agent("researcher").await.unwrap();
157 assert_eq!(agent.agent_id, id);
158 assert_eq!(agent.name, "researcher");
159 assert_eq!(agent.tools, vec!["search"]);
160 }
161
162 #[tokio::test]
163 async fn test_list_agents() {
164 let registry = AgentRegistry::new();
165
166 registry.spawn_agent("a", "Agent A", vec![], None).await;
167 registry.spawn_agent("b", "Agent B", vec![], None).await;
168
169 let agents = registry.list_agents().await;
170 assert_eq!(agents.len(), 2);
171 }
172
173 #[tokio::test]
174 async fn test_remove_agent() {
175 let registry = AgentRegistry::new();
176
177 registry
178 .spawn_agent("temp", "Temporary", vec![], None)
179 .await;
180 assert!(registry.has_agent("temp").await);
181
182 assert!(registry.remove_agent("temp").await);
183 assert!(!registry.has_agent("temp").await);
184 }
185
186 #[tokio::test]
187 async fn test_get_nonexistent() {
188 let registry = AgentRegistry::new();
189 assert!(registry.get_agent("nope").await.is_none());
190 }
191
192 #[tokio::test]
193 async fn test_spawn_replaces_existing() {
194 let registry = AgentRegistry::new();
195
196 let id1 = registry.spawn_agent("agent", "v1", vec![], None).await;
197 let id2 = registry.spawn_agent("agent", "v2", vec![], None).await;
198
199 assert_ne!(id1, id2);
200 let agent = registry.get_agent("agent").await.unwrap();
201 assert_eq!(agent.system_prompt, "v2");
202 }
203}