Skip to main content

aether_core/core/
agent_registry.rs

1use crate::agent_spec::AgentSpec;
2use std::sync::Arc;
3use thiserror::Error;
4
5/// A cheap-to-clone registry of resolved agent specifications.
6#[derive(Clone, Debug, Default)]
7pub struct AgentRegistry {
8    specs: Arc<[AgentSpec]>,
9}
10
11impl AgentRegistry {
12    pub fn new(specs: Vec<AgentSpec>) -> Self {
13        Self { specs: specs.into() }
14    }
15
16    pub fn all(&self) -> &[AgentSpec] {
17        &self.specs
18    }
19
20    pub fn get(&self, name: &str) -> Option<&AgentSpec> {
21        self.specs.iter().find(|spec| spec.name == name)
22    }
23
24    pub fn agent_invocable(&self) -> impl Iterator<Item = &AgentSpec> {
25        self.specs.iter().filter(|spec| spec.exposure.agent_invocable)
26    }
27
28    /// Resolve an agent that is exposed for delegation.
29    pub fn resolve_agent_invocable(&self, name: &str) -> Result<AgentSpec, AgentRegistryError> {
30        let spec = self.get(name).ok_or_else(|| AgentRegistryError::NotFound { name: name.to_string() })?;
31        if !spec.exposure.agent_invocable {
32            return Err(AgentRegistryError::NotAgentInvocable { name: name.to_string() });
33        }
34        Ok(spec.clone())
35    }
36}
37
38#[derive(Clone, Debug, Error, Eq, PartialEq)]
39pub enum AgentRegistryError {
40    #[error("Agent '{name}' not found")]
41    NotFound { name: String },
42    #[error("Agent '{name}' is not agent-invocable")]
43    NotAgentInvocable { name: String },
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49    use crate::agent_spec::AgentSpecExposure;
50
51    #[test]
52    fn delegation_resolution_distinguishes_missing_and_unexposed_agents() {
53        let model = "anthropic:claude-sonnet-4-5".parse().unwrap();
54        let mut delegate = AgentSpec::bare(&model, None, Vec::new());
55        delegate.name = "delegate".to_string();
56        delegate.exposure = AgentSpecExposure::agent_only();
57        let mut user_only = AgentSpec::bare(&model, None, Vec::new());
58        user_only.name = "user-only".to_string();
59        user_only.exposure = AgentSpecExposure::user_only();
60        let registry = AgentRegistry::new(vec![delegate, user_only]);
61
62        assert_eq!(registry.resolve_agent_invocable("delegate").unwrap().name, "delegate");
63        assert!(matches!(
64            registry.resolve_agent_invocable("user-only"),
65            Err(AgentRegistryError::NotAgentInvocable { name }) if name == "user-only"
66        ));
67        assert!(matches!(
68            registry.resolve_agent_invocable("missing"),
69            Err(AgentRegistryError::NotFound { name }) if name == "missing"
70        ));
71    }
72}