claude_agent_sdk/subagents/mod.rs
1//! Subagent system for Claude Agent SDK
2//!
3//! This module provides functionality for creating and managing subagents,
4//! which are specialized Claude instances with specific capabilities and instructions.
5
6mod types;
7
8pub use types::{
9 DelegationStrategy, Subagent, SubagentCall, SubagentConfig, SubagentError,
10 SubagentOutput,
11};
12
13/// Subagent executor for managing and executing subagents
14///
15/// # Example
16///
17/// ```no_run
18/// use claude_agent_sdk::subagents::{SubagentExecutor, Subagent, DelegationStrategy};
19///
20/// #[tokio::main]
21/// async fn example() -> Result<(), Box<dyn std::error::Error>> {
22/// let mut executor = SubagentExecutor::new(DelegationStrategy::Auto);
23///
24/// // Register a subagent
25/// let subagent = Subagent {
26/// name: "code-reviewer".to_string(),
27/// description: "Expert code reviewer".to_string(),
28/// instructions: "Review code for bugs and best practices".to_string(),
29/// allowed_tools: vec!["Read".to_string(), "Grep".to_string()],
30/// max_turns: Some(5),
31/// model: Some("claude-sonnet-4".to_string()),
32/// };
33///
34/// executor.register(subagent)?;
35///
36/// // Execute the subagent
37/// let output = executor.execute("code-reviewer", "Review this file").await?;
38/// println!("Output: {:?}", output);
39///
40/// Ok(())
41/// }
42/// ```
43pub struct SubagentExecutor {
44 subagents: std::collections::HashMap<String, Subagent>,
45 strategy: DelegationStrategy,
46}
47
48impl SubagentExecutor {
49 /// Create a new subagent executor
50 ///
51 /// # Arguments
52 ///
53 /// * `strategy` - The delegation strategy to use
54 ///
55 /// # Example
56 ///
57 /// ```no_run
58 /// # use claude_agent_sdk::subagents::{SubagentExecutor, DelegationStrategy};
59 /// let executor = SubagentExecutor::new(DelegationStrategy::Auto);
60 /// ```
61 pub fn new(strategy: DelegationStrategy) -> Self {
62 Self {
63 subagents: std::collections::HashMap::new(),
64 strategy,
65 }
66 }
67
68 /// Register a subagent
69 ///
70 /// # Arguments
71 ///
72 /// * `subagent` - The subagent to register
73 ///
74 /// # Errors
75 ///
76 /// Returns an error if a subagent with the same name already exists
77 ///
78 /// # Example
79 ///
80 /// ```no_run
81 /// # use claude_agent_sdk::subagents::{SubagentExecutor, Subagent, DelegationStrategy};
82 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
83 /// # let mut executor = SubagentExecutor::new(DelegationStrategy::Auto);
84 /// let subagent = Subagent {
85 /// name: "my-agent".to_string(),
86 /// description: "Description".to_string(),
87 /// instructions: "Instructions".to_string(),
88 /// allowed_tools: vec![],
89 /// max_turns: Some(5),
90 /// model: None,
91 /// };
92 /// executor.register(subagent)?;
93 /// # Ok(())
94 /// # }
95 /// ```
96 pub fn register(&mut self, subagent: Subagent) -> Result<(), SubagentError> {
97 if self.subagents.contains_key(&subagent.name) {
98 return Err(SubagentError::AlreadyExists(subagent.name));
99 }
100 self.subagents.insert(subagent.name.clone(), subagent);
101 Ok(())
102 }
103
104 /// Execute a subagent by name
105 ///
106 /// # Arguments
107 ///
108 /// * `name` - The name of the subagent to execute
109 /// * `input` - The input to provide to the subagent
110 ///
111 /// # Errors
112 ///
113 /// Returns an error if the subagent is not found or execution fails
114 ///
115 /// # Example
116 ///
117 /// ```no_run
118 /// # use claude_agent_sdk::subagents::{SubagentExecutor, DelegationStrategy};
119 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
120 /// # let executor = SubagentExecutor::new(DelegationStrategy::Auto);
121 /// # // ... register subagent ...
122 /// let output = executor.execute("my-agent", "Hello").await?;
123 /// # Ok(())
124 /// # }
125 /// ```
126 pub async fn execute(
127 &self,
128 name: &str,
129 input: &str,
130 ) -> Result<SubagentOutput, SubagentError> {
131 let subagent = self
132 .subagents
133 .get(name)
134 .ok_or_else(|| SubagentError::NotFound(name.to_string()))?;
135
136 // Build system prompt from description and instructions
137 let system_prompt = format!(
138 "{}\n\nInstructions:\n{}",
139 subagent.description, subagent.instructions
140 );
141
142 // Build ClaudeAgentOptions using match to handle conditional fields
143 let options = match (&subagent.model, subagent.max_turns) {
144 (Some(model), Some(max_turns)) => {
145 crate::types::config::ClaudeAgentOptions::builder()
146 .system_prompt(crate::types::config::SystemPrompt::Text(
147 system_prompt,
148 ))
149 .allowed_tools(subagent.allowed_tools.clone())
150 .model(model.clone())
151 .max_turns(max_turns)
152 .build()
153 }
154 (Some(model), None) => {
155 crate::types::config::ClaudeAgentOptions::builder()
156 .system_prompt(crate::types::config::SystemPrompt::Text(
157 system_prompt,
158 ))
159 .allowed_tools(subagent.allowed_tools.clone())
160 .model(model.clone())
161 .build()
162 }
163 (None, Some(max_turns)) => {
164 crate::types::config::ClaudeAgentOptions::builder()
165 .system_prompt(crate::types::config::SystemPrompt::Text(
166 system_prompt,
167 ))
168 .allowed_tools(subagent.allowed_tools.clone())
169 .max_turns(max_turns)
170 .build()
171 }
172 (None, None) => {
173 crate::types::config::ClaudeAgentOptions::builder()
174 .system_prompt(crate::types::config::SystemPrompt::Text(
175 system_prompt,
176 ))
177 .allowed_tools(subagent.allowed_tools.clone())
178 .build()
179 }
180 };
181
182 // Execute query
183 let messages = crate::query::query(input, Some(options))
184 .await
185 .map_err(|e| SubagentError::ExecutionFailed(format!("Query failed: {}", e)))?;
186
187 // Convert messages to JSON values for SubagentOutput
188 let json_messages = messages
189 .into_iter()
190 .map(|msg| serde_json::to_value(msg).map_err(|e| {
191 SubagentError::ExecutionFailed(format!("Failed to serialize message: {}", e))
192 }))
193 .collect::<Result<Vec<_>, _>>()?;
194
195 Ok(SubagentOutput {
196 subagent_name: name.to_string(),
197 messages: json_messages,
198 })
199 }
200
201 /// Get all registered subagent names
202 ///
203 /// # Returns
204 ///
205 /// A vector of subagent names
206 ///
207 /// # Example
208 ///
209 /// ```no_run
210 /// # use claude_agent_sdk::subagents::{SubagentExecutor, DelegationStrategy};
211 /// # let executor = SubagentExecutor::new(DelegationStrategy::Auto);
212 /// let names = executor.list_subagents();
213 /// println!("Available subagents: {:?}", names);
214 /// ```
215 pub fn list_subagents(&self) -> Vec<String> {
216 self.subagents.keys().cloned().collect()
217 }
218
219 /// Check if a subagent exists
220 ///
221 /// # Arguments
222 ///
223 /// * `name` - The name of the subagent to check
224 ///
225 /// # Returns
226 ///
227 /// `true` if the subagent exists, `false` otherwise
228 ///
229 /// # Example
230 ///
231 /// ```no_run
232 /// # use claude_agent_sdk::subagents::{SubagentExecutor, DelegationStrategy};
233 /// # let executor = SubagentExecutor::new(DelegationStrategy::Auto);
234 /// if executor.has_subagent("my-agent") {
235 /// println!("Agent exists");
236 /// }
237 /// ```
238 pub fn has_subagent(&self, name: &str) -> bool {
239 self.subagents.contains_key(name)
240 }
241
242 /// Get the delegation strategy
243 ///
244 /// # Returns
245 ///
246 /// The current delegation strategy
247 pub fn strategy(&self) -> &DelegationStrategy {
248 &self.strategy
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn test_executor_creation() {
258 let executor = SubagentExecutor::new(DelegationStrategy::Auto);
259 assert!(matches!(executor.strategy(), &DelegationStrategy::Auto));
260 }
261
262 #[test]
263 fn test_register_subagent() {
264 let mut executor = SubagentExecutor::new(DelegationStrategy::Auto);
265
266 let subagent = Subagent {
267 name: "test-agent".to_string(),
268 description: "Test agent".to_string(),
269 instructions: "Test instructions".to_string(),
270 allowed_tools: vec![],
271 max_turns: Some(5),
272 model: None,
273 };
274
275 assert!(executor.register(subagent).is_ok());
276 assert!(executor.has_subagent("test-agent"));
277 }
278
279 #[test]
280 fn test_register_duplicate_fails() {
281 let mut executor = SubagentExecutor::new(DelegationStrategy::Auto);
282
283 let subagent = Subagent {
284 name: "test-agent".to_string(),
285 description: "Test agent".to_string(),
286 instructions: "Test instructions".to_string(),
287 allowed_tools: vec![],
288 max_turns: Some(5),
289 model: None,
290 };
291
292 assert!(executor.register(subagent.clone()).is_ok());
293 assert!(matches!(
294 executor.register(subagent),
295 Err(SubagentError::AlreadyExists(_))
296 ));
297 }
298
299 #[test]
300 fn test_list_subagents() {
301 let mut executor = SubagentExecutor::new(DelegationStrategy::Auto);
302
303 let subagent1 = Subagent {
304 name: "agent1".to_string(),
305 description: "Agent 1".to_string(),
306 instructions: "Instructions 1".to_string(),
307 allowed_tools: vec![],
308 max_turns: Some(5),
309 model: None,
310 };
311
312 let subagent2 = Subagent {
313 name: "agent2".to_string(),
314 description: "Agent 2".to_string(),
315 instructions: "Instructions 2".to_string(),
316 allowed_tools: vec![],
317 max_turns: Some(10),
318 model: Some("claude-sonnet-4".to_string()),
319 };
320
321 executor.register(subagent1).unwrap();
322 executor.register(subagent2).unwrap();
323
324 let names = executor.list_subagents();
325 assert_eq!(names.len(), 2);
326 assert!(names.contains(&"agent1".to_string()));
327 assert!(names.contains(&"agent2".to_string()));
328 }
329
330 #[test]
331 fn test_execute_not_found() {
332 let executor = SubagentExecutor::new(DelegationStrategy::Auto);
333
334 let result = tokio::runtime::Runtime::new()
335 .unwrap()
336 .block_on(executor.execute("nonexistent", "input"));
337
338 assert!(matches!(result, Err(SubagentError::NotFound(_))));
339 }
340}