ceylon_runtime/config/
mesh_config.rs

1//! Mesh configuration from TOML files.
2
3use super::{AgentConfig, MemoryConfig};
4use crate::core::error::{Error, Result};
5use serde::{Deserialize, Serialize};
6use std::path::Path;
7
8/// Configuration for a multi-agent mesh.
9///
10/// This struct allows defining multiple agents in a single TOML file.
11///
12/// # Example
13///
14/// ```toml
15/// name = "research_mesh"
16///
17/// [memory]
18/// backend = "sqlite"
19/// path = "./data/knowledge.db"
20///
21/// [[agents]]
22/// name = "researcher"
23/// model = "ollama::gemma3:latest"
24/// system_prompt = "You are a research assistant."
25/// temperature = 0.7
26///
27/// [[agents]]
28/// name = "summarizer"
29/// model = "ollama::gemma3:latest"
30/// system_prompt = "You are a summarization expert."
31/// temperature = 0.5
32/// ```
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct MeshConfig {
35    /// Name of the mesh
36    pub name: String,
37
38    /// Shared memory configuration for all agents (can be overridden per-agent)
39    #[serde(default)]
40    pub memory: Option<MemoryConfig>,
41
42    /// List of agent configurations
43    #[serde(default)]
44    pub agents: Vec<AgentConfig>,
45}
46
47impl Default for MeshConfig {
48    fn default() -> Self {
49        Self {
50            name: "mesh".to_string(),
51            memory: None,
52            agents: Vec::new(),
53        }
54    }
55}
56
57impl MeshConfig {
58    /// Create a new MeshConfig with a name.
59    pub fn new(name: impl Into<String>) -> Self {
60        Self {
61            name: name.into(),
62            memory: None,
63            agents: Vec::new(),
64        }
65    }
66
67    /// Parse a MeshConfig from a TOML string.
68    ///
69    /// # Example
70    ///
71    /// ```rust
72    /// use ceylon_runtime::config::MeshConfig;
73    ///
74    /// let toml = r#"
75    /// name = "my_mesh"
76    ///
77    /// [[agents]]
78    /// name = "agent1"
79    /// model = "ollama::llama2"
80    /// "#;
81    ///
82    /// let config = MeshConfig::from_toml(toml).unwrap();
83    /// assert_eq!(config.name, "my_mesh");
84    /// assert_eq!(config.agents.len(), 1);
85    /// ```
86    pub fn from_toml(toml_str: &str) -> Result<Self> {
87        toml::from_str(toml_str)
88            .map_err(|e| Error::ConfigError(format!("Failed to parse TOML: {}", e)))
89    }
90
91    /// Load a MeshConfig from a TOML file.
92    ///
93    /// # Example
94    ///
95    /// ```rust,no_run
96    /// use ceylon_runtime::config::MeshConfig;
97    ///
98    /// let config = MeshConfig::from_file("agents.toml").unwrap();
99    /// ```
100    pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
101        let content = std::fs::read_to_string(path.as_ref()).map_err(|e| {
102            Error::ConfigError(format!(
103                "Failed to read file '{}': {}",
104                path.as_ref().display(),
105                e
106            ))
107        })?;
108        Self::from_toml(&content)
109    }
110
111    /// Add an agent configuration.
112    pub fn add_agent(mut self, agent: AgentConfig) -> Self {
113        self.agents.push(agent);
114        self
115    }
116
117    /// Get agent config by name.
118    pub fn get_agent(&self, name: &str) -> Option<&AgentConfig> {
119        self.agents.iter().find(|a| a.name == name)
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn test_parse_mesh_config() {
129        let toml = r#"
130            name = "test_mesh"
131
132            [[agents]]
133            name = "agent1"
134            model = "ollama::llama2"
135            system_prompt = "First agent"
136
137            [[agents]]
138            name = "agent2"
139            model = "openai::gpt-4"
140            temperature = 0.8
141        "#;
142
143        let config = MeshConfig::from_toml(toml).unwrap();
144        assert_eq!(config.name, "test_mesh");
145        assert_eq!(config.agents.len(), 2);
146        assert_eq!(config.agents[0].name, "agent1");
147        assert_eq!(config.agents[1].name, "agent2");
148        assert_eq!(config.agents[1].temperature, Some(0.8));
149    }
150
151    #[test]
152    fn test_get_agent() {
153        let config = MeshConfig::new("test")
154            .add_agent(AgentConfig::new("agent1", "ollama::llama2"))
155            .add_agent(AgentConfig::new("agent2", "ollama::llama2"));
156
157        assert!(config.get_agent("agent1").is_some());
158        assert!(config.get_agent("agent2").is_some());
159        assert!(config.get_agent("nonexistent").is_none());
160    }
161
162    #[test]
163    fn test_empty_agents() {
164        let toml = r#"
165            name = "empty_mesh"
166        "#;
167
168        let config = MeshConfig::from_toml(toml).unwrap();
169        assert_eq!(config.name, "empty_mesh");
170        assert!(config.agents.is_empty());
171    }
172}