claude_agent/subagents/
builtin.rs

1//! Built-in subagent definitions matching Claude Code CLI.
2
3use super::SubagentIndex;
4use crate::client::ModelType;
5use crate::common::{ContentSource, SourceType};
6
7/// Bash agent - Command execution specialist.
8/// CLI name: "Bash"
9pub fn bash_subagent() -> SubagentIndex {
10    SubagentIndex::new(
11        "Bash",
12        "Command execution specialist for running bash commands. Use this for git operations, command execution, and other terminal tasks.",
13    )
14    .with_source(ContentSource::in_memory(
15        r#"You are a Bash agent specialized for command execution.
16
17Your task is to execute shell commands efficiently and safely:
18- Run git operations (status, diff, log, commit, push, etc.)
19- Execute build and test commands
20- Perform system operations
21
22Always verify command safety before execution. Return clear, concise results."#,
23    ))
24    .with_source_type(SourceType::Builtin)
25    .with_tools(["Bash"])
26    .with_model_type(ModelType::Small)
27}
28
29/// Explore agent - Fast codebase exploration.
30/// CLI name: "Explore"
31pub fn explore_subagent() -> SubagentIndex {
32    SubagentIndex::new(
33        "Explore",
34        "Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns, search code for keywords, or answer questions about the codebase. When calling this agent, specify the desired thoroughness level: \"quick\" for basic searches, \"medium\" for moderate exploration, or \"very thorough\" for comprehensive analysis across multiple locations and naming conventions.",
35    )
36    .with_source(ContentSource::in_memory(
37        r#"You are an Explore agent specialized for investigating codebases.
38
39Your task is to quickly find relevant information through:
40- Pattern matching with Glob (e.g., "src/components/**/*.tsx")
41- Content search with Grep (e.g., "API endpoints", "function\\s+\\w+")
42- File reading with Read
43
44Thoroughness levels:
45- "quick": Basic searches, first matches only
46- "medium": Moderate exploration, check multiple locations
47- "very thorough": Comprehensive analysis across multiple locations and naming conventions
48
49Be thorough but efficient. Return a concise summary of your findings."#,
50    ))
51    .with_source_type(SourceType::Builtin)
52    .with_tools(["Read", "Grep", "Glob", "Bash", "TodoWrite", "KillShell"])
53    .with_model_type(ModelType::Small)
54}
55
56/// Plan agent - Software architect for implementation planning.
57/// CLI name: "Plan"
58pub fn plan_subagent() -> SubagentIndex {
59    SubagentIndex::new(
60        "Plan",
61        "Software architect agent for designing implementation plans. Use this when you need to plan the implementation strategy for a task. Returns step-by-step plans, identifies critical files, and considers architectural trade-offs.",
62    )
63    .with_source(ContentSource::in_memory(
64        r#"You are a Plan agent for designing implementation strategies.
65
66Your task is to:
671. Understand the requirements thoroughly
682. Explore the codebase to understand existing patterns and context
693. Identify critical files that will need modification
704. Design a step-by-step implementation plan
715. Consider architectural trade-offs and potential issues
72
73Present your plan clearly with:
74- Numbered implementation steps
75- Files to be modified/created
76- Potential risks or considerations
77- Recommended approach with rationale"#,
78    ))
79    .with_source_type(SourceType::Builtin)
80    .with_tools(["Read", "Grep", "Glob", "Bash", "TodoWrite", "KillShell"])
81    .with_model_type(ModelType::Primary)
82}
83
84/// General-purpose agent - Full capability for complex tasks.
85/// CLI name: "general-purpose"
86pub fn general_purpose_subagent() -> SubagentIndex {
87    SubagentIndex::new(
88        "general-purpose",
89        "General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries, use this agent to perform the search for you.",
90    )
91    .with_source(ContentSource::in_memory(
92        r#"You are a general-purpose agent capable of handling complex, multi-step tasks.
93
94You have full access to all tools and can:
95- Read and modify files
96- Execute shell commands
97- Search and explore codebases
98- Implement features and fix bugs
99- Create and manage tasks
100
101Work autonomously and methodically:
1021. Understand the task requirements
1032. Plan your approach
1043. Execute step by step
1054. Verify results
1065. Return comprehensive results when complete"#,
107    ))
108    .with_source_type(SourceType::Builtin)
109    .with_model_type(ModelType::Primary)
110}
111
112pub fn builtin_subagents() -> Vec<SubagentIndex> {
113    vec![
114        bash_subagent(),
115        explore_subagent(),
116        plan_subagent(),
117        general_purpose_subagent(),
118    ]
119}
120
121pub fn find_builtin(name: &str) -> Option<SubagentIndex> {
122    match name {
123        "Bash" => Some(bash_subagent()),
124        "Explore" => Some(explore_subagent()),
125        "Plan" => Some(plan_subagent()),
126        "general-purpose" => Some(general_purpose_subagent()),
127        _ => None,
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    use crate::common::ToolRestricted;
135
136    #[test]
137    fn test_builtin_subagents() {
138        let builtins = builtin_subagents();
139        assert_eq!(builtins.len(), 4);
140
141        let names: Vec<&str> = builtins.iter().map(|s| s.name.as_str()).collect();
142        assert!(names.contains(&"Bash"));
143        assert!(names.contains(&"Explore"));
144        assert!(names.contains(&"Plan"));
145        assert!(names.contains(&"general-purpose"));
146    }
147
148    #[test]
149    fn test_find_builtin_cli_names() {
150        assert!(find_builtin("Bash").is_some());
151        assert!(find_builtin("Explore").is_some());
152        assert!(find_builtin("Plan").is_some());
153        assert!(find_builtin("general-purpose").is_some());
154        assert!(find_builtin("nonexistent").is_none());
155    }
156
157    #[test]
158    fn test_bash_agent_tool_restriction() {
159        let bash = bash_subagent();
160        assert!(bash.has_tool_restrictions());
161        assert!(bash.is_tool_allowed("Bash"));
162        assert!(!bash.is_tool_allowed("Read"));
163        assert!(!bash.is_tool_allowed("Write"));
164    }
165
166    #[test]
167    fn test_explore_has_tool_restrictions() {
168        let explore = explore_subagent();
169        assert!(explore.has_tool_restrictions());
170        assert!(explore.is_tool_allowed("Read"));
171        assert!(explore.is_tool_allowed("Grep"));
172        assert!(explore.is_tool_allowed("Glob"));
173        assert!(explore.is_tool_allowed("Bash"));
174        // Should NOT allow write operations
175        assert!(!explore.is_tool_allowed("Write"));
176        assert!(!explore.is_tool_allowed("Edit"));
177    }
178
179    #[test]
180    fn test_plan_has_tool_restrictions() {
181        let plan = plan_subagent();
182        assert!(plan.has_tool_restrictions());
183        assert!(plan.is_tool_allowed("Read"));
184        assert!(plan.is_tool_allowed("Grep"));
185        // Should NOT allow write operations
186        assert!(!plan.is_tool_allowed("Write"));
187        assert!(!plan.is_tool_allowed("Edit"));
188    }
189
190    #[test]
191    fn test_general_purpose_no_restrictions() {
192        let gp = general_purpose_subagent();
193        assert!(!gp.has_tool_restrictions());
194        assert!(gp.is_tool_allowed("Anything"));
195    }
196}