codetether_agent/agent/
builtin.rs1use super::{AgentInfo, AgentMode};
4use std::path::Path;
5
6pub fn build_agent() -> AgentInfo {
8 AgentInfo {
9 name: "build".to_string(),
10 description: Some("Full access agent for development work".to_string()),
11 mode: AgentMode::Primary,
12 native: true,
13 hidden: false,
14 model: None,
15 temperature: None,
16 top_p: None,
17 max_steps: Some(100),
18 }
19}
20
21pub fn plan_agent() -> AgentInfo {
23 AgentInfo {
24 name: "plan".to_string(),
25 description: Some("Read-only agent for analysis and code exploration".to_string()),
26 mode: AgentMode::Primary,
27 native: true,
28 hidden: false,
29 model: None,
30 temperature: None,
31 top_p: None,
32 max_steps: Some(50),
33 }
34}
35
36pub fn explore_agent() -> AgentInfo {
38 AgentInfo {
39 name: "explore".to_string(),
40 description: Some("Fast agent for exploring codebases".to_string()),
41 mode: AgentMode::Subagent,
42 native: true,
43 hidden: false,
44 model: None,
45 temperature: None,
46 top_p: None,
47 max_steps: Some(20),
48 }
49}
50
51#[allow(dead_code)]
53pub const BUILD_SYSTEM_PROMPT: &str = r#"You are an expert AI programming assistant called CodeTether Agent.
54
55You help users with software development tasks including:
56- Writing and editing code
57- Debugging and fixing issues
58- Implementing new features
59- Refactoring and improving code quality
60- Explaining code and concepts
61
62You have access to tools that let you:
63- Read and write files
64- Run shell commands
65- Search the codebase
66- List directories
67- Spawn specialized sub-agents and delegate tasks to them
68
69For complex tasks, use the `agent` tool to spawn focused sub-agents:
70- Spawn a sub-agent with a specific role (e.g., "reviewer", "architect", "tester")
71- Send it targeted messages and get back results
72- Each sub-agent has its own conversation history and full tool access
73- Use sub-agents when a task benefits from a dedicated focus or parallel exploration
74
75Always:
76- Be concise and helpful
77- Show your work by using tools
78- Explain what you're doing
79- Ask clarifying questions when needed
80- Follow best practices for the language/framework being used
81
82Current working directory: {cwd}
83"#;
84
85#[allow(dead_code)]
87pub const PLAN_SYSTEM_PROMPT: &str = r#"You are an expert AI assistant for code analysis and planning.
88
89Your role is to:
90- Explore and understand codebases
91- Analyze code structure and architecture
92- Plan changes and refactoring
93- Answer questions about the code
94
95You have read-only access to the codebase. You can:
96- Read files
97- Search the codebase
98- List directories
99- Run safe commands
100
101You should NOT modify any files. If the user wants changes, explain what should be changed and suggest switching to the build agent.
102
103Current working directory: {cwd}
104"#;
105
106#[allow(dead_code)]
111pub const EXPLORE_SYSTEM_PROMPT: &str = r#"You are a fast, focused agent for codebase exploration.
112
113Your job is to quickly find relevant code and information. Use tools efficiently:
114- Use glob to find files by pattern
115- Use grep to search for text
116- Use list to see directory contents
117- Read files to get details
118
119Be thorough but fast. Return the most relevant results without unnecessary exploration.
120"#;
121
122pub fn load_agents_md(start_dir: &Path) -> Option<(String, std::path::PathBuf)> {
125 let mut current = start_dir.to_path_buf();
126 let repo_root = find_git_root(start_dir);
127
128 loop {
129 let agents_path = current.join("AGENTS.md");
130 if agents_path.exists() {
131 if let Ok(content) = std::fs::read_to_string(&agents_path) {
132 return Some((content, agents_path));
133 }
134 }
135
136 if repo_root.as_ref() == Some(¤t) {
138 break;
139 }
140
141 if !current.pop() {
143 break;
144 }
145 }
146
147 None
148}
149
150pub fn load_all_agents_md(start_dir: &Path) -> Vec<(String, std::path::PathBuf)> {
154 let mut results = Vec::new();
155 let mut current = start_dir.to_path_buf();
156 let repo_root = find_git_root(start_dir);
157
158 loop {
159 let agents_path = current.join("AGENTS.md");
160 if agents_path.exists() {
161 if let Ok(content) = std::fs::read_to_string(&agents_path) {
162 results.push((content, agents_path));
163 }
164 }
165
166 if repo_root.as_ref() == Some(¤t) {
168 break;
169 }
170
171 if !current.pop() {
173 break;
174 }
175 }
176
177 results
178}
179
180fn find_git_root(start_dir: &Path) -> Option<std::path::PathBuf> {
181 let mut current = start_dir.to_path_buf();
182
183 loop {
184 if current.join(".git").exists() {
185 return Some(current);
186 }
187
188 if !current.pop() {
189 break;
190 }
191 }
192
193 None
194}
195
196pub fn build_system_prompt(cwd: &Path) -> String {
198 let base_prompt = BUILD_SYSTEM_PROMPT.replace("{cwd}", &cwd.display().to_string());
199
200 let agents_files = load_all_agents_md(cwd);
202
203 if agents_files.is_empty() {
204 return base_prompt;
205 }
206
207 let mut agents_section = String::new();
209 agents_section.push_str("\n\n## Project Instructions (AGENTS.md)\n\n");
210 agents_section
211 .push_str("The following instructions were loaded from AGENTS.md files in the project.\n");
212 agents_section
213 .push_str("Follow these project-specific guidelines when working on this codebase.\n\n");
214
215 for (content, path) in agents_files.iter().rev() {
217 agents_section.push_str(&format!("### From {}\n\n", path.display()));
218 agents_section.push_str(content);
219 agents_section.push_str("\n\n");
220 }
221
222 format!("{base_prompt}{agents_section}")
223}
224
225#[allow(dead_code)]
227pub fn build_plan_system_prompt(cwd: &Path) -> String {
228 let base_prompt = PLAN_SYSTEM_PROMPT.replace("{cwd}", &cwd.display().to_string());
229
230 let agents_files = load_all_agents_md(cwd);
232
233 if agents_files.is_empty() {
234 return base_prompt;
235 }
236
237 let mut agents_section = String::new();
238 agents_section.push_str("\n\n## Project Instructions (AGENTS.md)\n\n");
239
240 for (content, path) in agents_files.iter().rev() {
241 agents_section.push_str(&format!("### From {}\n\n", path.display()));
242 agents_section.push_str(content);
243 agents_section.push_str("\n\n");
244 }
245
246 format!("{base_prompt}{agents_section}")
247}
248
249#[cfg(test)]
250mod tests {
251 use super::{load_agents_md, load_all_agents_md};
252 use tempfile::tempdir;
253
254 #[test]
255 fn load_agents_md_stops_at_git_root() {
256 let tmp = tempdir().expect("tempdir");
257 let parent_agents = tmp.path().join("AGENTS.md");
258 std::fs::write(&parent_agents, "parent instructions").expect("write parent AGENTS");
259
260 let repo_root = tmp.path().join("repo");
261 let nested = repo_root.join("src/nested");
262 std::fs::create_dir_all(repo_root.join(".git")).expect("create .git dir");
263 std::fs::create_dir_all(&nested).expect("create nested dir");
264 let repo_agents = repo_root.join("AGENTS.md");
265 std::fs::write(&repo_agents, "repo instructions").expect("write repo AGENTS");
266
267 let loaded = load_agents_md(&nested).expect("expected AGENTS");
268 assert_eq!(loaded.1, repo_agents);
269 assert_eq!(loaded.0, "repo instructions");
270 }
271
272 #[test]
273 fn load_all_agents_md_collects_within_repo_only() {
274 let tmp = tempdir().expect("tempdir");
275 let parent_agents = tmp.path().join("AGENTS.md");
276 std::fs::write(&parent_agents, "parent instructions").expect("write parent AGENTS");
277
278 let repo_root = tmp.path().join("repo");
279 let subdir = repo_root.join("service");
280 let nested = subdir.join("src");
281 std::fs::create_dir_all(repo_root.join(".git")).expect("create .git dir");
282 std::fs::create_dir_all(&nested).expect("create nested dir");
283 let repo_agents = repo_root.join("AGENTS.md");
284 std::fs::write(&repo_agents, "repo instructions").expect("write repo AGENTS");
285 let sub_agents = subdir.join("AGENTS.md");
286 std::fs::write(&sub_agents, "sub instructions").expect("write sub AGENTS");
287
288 let loaded = load_all_agents_md(&nested);
289
290 assert_eq!(loaded.len(), 2);
291 assert_eq!(loaded[0].1, sub_agents);
292 assert_eq!(loaded[1].1, repo_agents);
293 assert!(!loaded.iter().any(|(_, path)| path == &parent_agents));
294 }
295}