Skip to main content

driven/agents/
registry.rs

1//! Agent Registry
2//!
3//! Manages registration and lookup of agents.
4
5use super::Agent;
6use crate::{DrivenError, Result};
7use std::collections::HashMap;
8use std::path::Path;
9
10/// Registry for managing agents
11#[derive(Debug, Default)]
12pub struct AgentRegistry {
13    agents: HashMap<String, Agent>,
14}
15
16impl AgentRegistry {
17    /// Create a new empty registry
18    pub fn new() -> Self {
19        Self {
20            agents: HashMap::new(),
21        }
22    }
23
24    /// Load all built-in agents
25    pub fn load_builtin(&mut self) {
26        let builtin_agents = super::builtin::all_agents();
27        for agent in builtin_agents {
28            self.agents.insert(agent.id.clone(), agent);
29        }
30    }
31
32    /// Load custom agents from a YAML file
33    pub fn load_custom(&mut self, path: &Path) -> Result<()> {
34        let content = std::fs::read_to_string(path)?;
35        let agents: Vec<Agent> = serde_yaml::from_str(&content)
36            .map_err(|e| DrivenError::Config(format!("Failed to parse agent YAML: {}", e)))?;
37
38        for agent in agents {
39            self.register(agent)?;
40        }
41        Ok(())
42    }
43
44    /// Load custom agents from a directory
45    pub fn load_custom_dir(&mut self, dir: &Path) -> Result<()> {
46        if !dir.exists() {
47            return Ok(());
48        }
49
50        for entry in std::fs::read_dir(dir)? {
51            let entry = entry?;
52            let path = entry.path();
53            if path
54                .extension()
55                .is_some_and(|ext| ext == "yaml" || ext == "yml")
56            {
57                self.load_custom(&path)?;
58            }
59        }
60        Ok(())
61    }
62
63    /// Register a new agent
64    pub fn register(&mut self, agent: Agent) -> Result<()> {
65        if self.agents.contains_key(&agent.id) {
66            return Err(DrivenError::Config(format!(
67                "Agent with id '{}' already registered",
68                agent.id
69            )));
70        }
71        self.agents.insert(agent.id.clone(), agent);
72        Ok(())
73    }
74
75    /// Get an agent by ID
76    pub fn get(&self, id: &str) -> Option<&Agent> {
77        self.agents.get(id)
78    }
79
80    /// Get a mutable reference to an agent by ID
81    pub fn get_mut(&mut self, id: &str) -> Option<&mut Agent> {
82        self.agents.get_mut(id)
83    }
84
85    /// List all registered agents
86    pub fn list(&self) -> Vec<&Agent> {
87        self.agents.values().collect()
88    }
89
90    /// List agents by capability
91    pub fn list_by_capability(&self, capability: &str) -> Vec<&Agent> {
92        self.agents
93            .values()
94            .filter(|a| a.has_capability(capability))
95            .collect()
96    }
97
98    /// List agents by workflow
99    pub fn list_by_workflow(&self, workflow: &str) -> Vec<&Agent> {
100        self.agents
101            .values()
102            .filter(|a| a.workflows.iter().any(|w| w == workflow))
103            .collect()
104    }
105
106    /// List only built-in agents
107    pub fn list_builtin(&self) -> Vec<&Agent> {
108        self.agents.values().filter(|a| a.builtin).collect()
109    }
110
111    /// List only custom agents
112    pub fn list_custom(&self) -> Vec<&Agent> {
113        self.agents.values().filter(|a| !a.builtin).collect()
114    }
115
116    /// Remove an agent by ID
117    pub fn remove(&mut self, id: &str) -> Option<Agent> {
118        self.agents.remove(id)
119    }
120
121    /// Check if an agent exists
122    pub fn contains(&self, id: &str) -> bool {
123        self.agents.contains_key(id)
124    }
125
126    /// Get the number of registered agents
127    pub fn len(&self) -> usize {
128        self.agents.len()
129    }
130
131    /// Check if the registry is empty
132    pub fn is_empty(&self) -> bool {
133        self.agents.is_empty()
134    }
135
136    /// Delegate a request from one agent to another
137    pub fn delegate(&self, from_agent_id: &str, to_agent_id: &str) -> Result<&Agent> {
138        let from_agent = self.get(from_agent_id).ok_or_else(|| {
139            DrivenError::Config(format!("Source agent '{}' not found", from_agent_id))
140        })?;
141
142        if !from_agent.can_delegate_to(to_agent_id) {
143            return Err(DrivenError::Config(format!(
144                "Agent '{}' cannot delegate to '{}'",
145                from_agent_id, to_agent_id
146            )));
147        }
148
149        self.get(to_agent_id)
150            .ok_or_else(|| DrivenError::Config(format!("Target agent '{}' not found", to_agent_id)))
151    }
152
153    /// Process a delegation request
154    pub fn process_delegation(
155        &self,
156        request: &super::DelegationRequest,
157    ) -> Result<super::DelegationResult> {
158        // Validate the delegation is allowed
159        let target_agent = self.delegate(&request.from_agent, &request.to_agent)?;
160
161        // Return success with the target agent info
162        Ok(super::DelegationResult::success(
163            &target_agent.id,
164            Some(format!(
165                "Delegated task '{}' from {} to {}",
166                request.task, request.from_agent, request.to_agent
167            )),
168        ))
169    }
170
171    /// Get all agents that a given agent can delegate to
172    pub fn get_delegates(&self, agent_id: &str) -> Vec<&Agent> {
173        let agent = match self.get(agent_id) {
174            Some(a) => a,
175            None => return Vec::new(),
176        };
177
178        agent
179            .delegates_to
180            .iter()
181            .filter_map(|id| self.get(id))
182            .collect()
183    }
184
185    /// Find the best agent for a given capability
186    pub fn find_by_capability(&self, capability: &str) -> Option<&Agent> {
187        self.agents.values().find(|a| a.has_capability(capability))
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn test_registry_new() {
197        let registry = AgentRegistry::new();
198        assert!(registry.is_empty());
199    }
200
201    #[test]
202    fn test_load_builtin() {
203        let mut registry = AgentRegistry::new();
204        registry.load_builtin();
205        assert!(!registry.is_empty());
206        assert!(registry.len() >= 15);
207    }
208
209    #[test]
210    fn test_get_agent() {
211        let mut registry = AgentRegistry::new();
212        registry.load_builtin();
213
214        let pm = registry.get("pm");
215        assert!(pm.is_some());
216        assert_eq!(pm.unwrap().name, "Product Manager");
217    }
218
219    #[test]
220    fn test_list_by_capability() {
221        let mut registry = AgentRegistry::new();
222        registry.load_builtin();
223
224        let architects = registry.list_by_capability("architecture");
225        assert!(!architects.is_empty());
226    }
227
228    #[test]
229    fn test_delegation() {
230        let mut registry = AgentRegistry::new();
231        registry.load_builtin();
232
233        // BMad Master can delegate to others
234        let result = registry.delegate("bmad-master", "architect");
235        assert!(result.is_ok());
236    }
237}
238
239#[test]
240fn test_custom_agent_yaml_parsing() {
241    use super::*;
242
243    let yaml = r#"
244- id: custom-agent
245  name: Custom Agent
246  title: Custom Title
247  icon: "🔧"
248  persona:
249    role: Custom Role
250    identity: Custom identity description
251    communication_style: Custom style
252    principles:
253      - Principle 1
254      - Principle 2
255    traits:
256      - Trait 1
257      - Trait 2
258  capabilities:
259    - custom-capability
260  workflows:
261    - custom-workflow
262  delegates_to:
263    - developer
264  builtin: false
265"#;
266
267    let agents: Vec<Agent> = serde_yaml::from_str(yaml).unwrap();
268    assert_eq!(agents.len(), 1);
269
270    let agent = &agents[0];
271    assert_eq!(agent.id, "custom-agent");
272    assert_eq!(agent.name, "Custom Agent");
273    assert!(agent.has_capability("custom-capability"));
274    assert!(agent.can_delegate_to("developer"));
275}