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}