Skip to main content

traitclaw_core/
factory.rs

1//! Agent factory for shared-provider multi-agent creation.
2//!
3//! `AgentFactory` holds a provider configuration and spawns agents from it,
4//! eliminating repeated builder boilerplate when creating multiple agents
5//! from the same provider.
6
7use std::sync::Arc;
8
9use crate::agent::Agent;
10use crate::agent_builder::AgentBuilder;
11use crate::traits::provider::Provider;
12use crate::Result;
13
14/// A factory for creating multiple agents from a shared provider.
15///
16/// `AgentFactory` solves the "N agents from one provider" problem:
17/// instead of repeating `.provider(p)` for each agent, create
18/// a factory once and call [`spawn()`](Self::spawn) with different prompts.
19///
20/// # Example
21///
22/// ```rust,no_run
23/// use traitclaw_core::factory::AgentFactory;
24/// use traitclaw_core::traits::provider::Provider;
25///
26/// # fn example(provider: impl Provider) {
27/// let factory = AgentFactory::new(provider);
28///
29/// let researcher = factory.spawn("You are a researcher.");
30/// let writer = factory.spawn("You are a technical writer.");
31/// let reviewer = factory.spawn("You are a code reviewer.");
32/// // All three agents share the same provider config (via Arc)
33/// # }
34/// ```
35///
36/// ## How It Works
37///
38/// The factory wraps the provider in `Arc<dyn Provider>`, which is
39/// cheaply cloneable. Each [`spawn()`](Self::spawn) call clones the Arc
40/// (incrementing the reference count) and creates a new agent.
41#[derive(Clone)]
42pub struct AgentFactory {
43    provider: Arc<dyn Provider>,
44}
45
46impl std::fmt::Debug for AgentFactory {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        f.debug_struct("AgentFactory")
49            .field("model", &self.provider.model_info().name)
50            .finish()
51    }
52}
53
54impl AgentFactory {
55    /// Create a new factory from a provider.
56    ///
57    /// The provider is wrapped in an `Arc` for cheap cloning. Each
58    /// spawned agent shares the same underlying provider instance.
59    #[must_use]
60    pub fn new(provider: impl Provider) -> Self {
61        Self {
62            provider: Arc::new(provider),
63        }
64    }
65
66    /// Create a factory from an already-wrapped `Arc<dyn Provider>`.
67    ///
68    /// Use this when you already hold a shared provider reference.
69    #[must_use]
70    pub fn from_arc(provider: Arc<dyn Provider>) -> Self {
71        Self { provider }
72    }
73
74    /// Spawn an agent with the factory's provider and a system prompt.
75    ///
76    /// Each spawned agent holds its own `Arc` clone of the provider,
77    /// making agents fully independent (cheap reference-counted sharing).
78    ///
79    /// # Example
80    ///
81    /// ```rust,no_run
82    /// use traitclaw_core::factory::AgentFactory;
83    /// use traitclaw_core::traits::provider::Provider;
84    ///
85    /// # fn example(provider: impl Provider) {
86    /// let factory = AgentFactory::new(provider);
87    /// let agent = factory.spawn("You are a helpful assistant.");
88    /// # }
89    /// ```
90    ///
91    /// # Panics
92    ///
93    /// This method cannot panic under normal usage — the internal builder
94    /// always has a valid provider.
95    #[must_use]
96    pub fn spawn(&self, system: impl Into<String>) -> Agent {
97        AgentBuilder::new()
98            .provider_arc(Arc::clone(&self.provider))
99            .system(system)
100            .build()
101            .expect("AgentFactory::spawn is infallible: provider is always set")
102    }
103
104    /// Spawn an agent with custom builder configuration.
105    ///
106    /// Use this escape hatch when you need more than just a system prompt
107    /// (e.g., adding tools, setting memory, configuring hooks).
108    ///
109    /// The closure receives an [`AgentBuilder`] with the factory's provider
110    /// already set. Call builder methods as needed.
111    ///
112    /// # Example
113    ///
114    /// ```rust,no_run
115    /// use traitclaw_core::factory::AgentFactory;
116    /// use traitclaw_core::traits::provider::Provider;
117    ///
118    /// # fn example(provider: impl Provider) -> traitclaw_core::Result<()> {
119    /// let factory = AgentFactory::new(provider);
120    /// let agent = factory.spawn_with(|b| {
121    ///     b.system("You are a researcher with tools.")
122    ///      .max_iterations(10)
123    /// })?;
124    /// # Ok(())
125    /// # }
126    /// ```
127    ///
128    /// # Errors
129    ///
130    /// Returns an error if the builder customization produces an invalid
131    /// agent configuration.
132    pub fn spawn_with(&self, f: impl FnOnce(AgentBuilder) -> AgentBuilder) -> Result<Agent> {
133        let builder = AgentBuilder::new().provider_arc(Arc::clone(&self.provider));
134        f(builder).build()
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use crate::types::completion::{CompletionRequest, CompletionResponse, ResponseContent, Usage};
142    use crate::types::model_info::{ModelInfo, ModelTier};
143    use crate::types::stream::CompletionStream;
144    use async_trait::async_trait;
145
146    #[derive(Clone)]
147    struct MockCloneProvider {
148        info: ModelInfo,
149    }
150
151    impl MockCloneProvider {
152        fn new() -> Self {
153            Self {
154                info: ModelInfo::new("mock-clone", ModelTier::Small, 4_096, false, false, false),
155            }
156        }
157    }
158
159    #[async_trait]
160    impl Provider for MockCloneProvider {
161        async fn complete(&self, _req: CompletionRequest) -> crate::Result<CompletionResponse> {
162            Ok(CompletionResponse {
163                content: ResponseContent::Text("ok".into()),
164                usage: Usage {
165                    prompt_tokens: 1,
166                    completion_tokens: 1,
167                    total_tokens: 2,
168                },
169            })
170        }
171        async fn stream(&self, _req: CompletionRequest) -> crate::Result<CompletionStream> {
172            unimplemented!()
173        }
174        fn model_info(&self) -> &ModelInfo {
175            &self.info
176        }
177    }
178
179    #[test]
180    fn test_factory_new() {
181        let factory = AgentFactory::new(MockCloneProvider::new());
182        assert_eq!(factory.provider.model_info().name, "mock-clone");
183    }
184
185    #[test]
186    fn test_factory_from_arc() {
187        let provider: Arc<dyn Provider> = Arc::new(MockCloneProvider::new());
188        let factory = AgentFactory::from_arc(provider);
189        assert_eq!(factory.provider.model_info().name, "mock-clone");
190    }
191
192    #[test]
193    fn test_factory_spawn_creates_agent_with_system_prompt() {
194        let factory = AgentFactory::new(MockCloneProvider::new());
195        let agent = factory.spawn("You are a researcher.");
196        assert_eq!(
197            agent.config.system_prompt.as_deref(),
198            Some("You are a researcher.")
199        );
200    }
201
202    #[test]
203    fn test_factory_spawn_produces_independent_agents() {
204        let factory = AgentFactory::new(MockCloneProvider::new());
205        let agent_a = factory.spawn("Agent A");
206        let agent_b = factory.spawn("Agent B");
207
208        assert_eq!(agent_a.config.system_prompt.as_deref(), Some("Agent A"));
209        assert_eq!(agent_b.config.system_prompt.as_deref(), Some("Agent B"));
210        // Both have the same provider model
211        assert_eq!(agent_a.provider.model_info().name, "mock-clone");
212        assert_eq!(agent_b.provider.model_info().name, "mock-clone");
213    }
214
215    #[test]
216    fn test_factory_spawn_with_custom_config() {
217        let factory = AgentFactory::new(MockCloneProvider::new());
218        let agent = factory
219            .spawn_with(|b| b.system("Custom").max_iterations(5))
220            .expect("spawn_with should succeed");
221
222        assert_eq!(agent.config.system_prompt.as_deref(), Some("Custom"));
223        assert_eq!(agent.config.max_iterations, 5);
224    }
225
226    #[test]
227    fn test_factory_spawn_with_no_system() {
228        let factory = AgentFactory::new(MockCloneProvider::new());
229        let agent = factory
230            .spawn_with(|b| b.max_iterations(3))
231            .expect("spawn_with without system should succeed");
232
233        assert!(agent.config.system_prompt.is_none());
234    }
235
236    // Compile-time check: AgentFactory is Send + Sync
237    fn _assert_send_sync<T: Send + Sync>() {}
238    #[test]
239    fn test_factory_is_send_sync() {
240        _assert_send_sync::<AgentFactory>();
241    }
242}