use super::{AgentInfo, AgentMode};
use std::path::Path;
pub fn build_agent() -> AgentInfo {
AgentInfo {
name: "build".to_string(),
description: Some("Full access agent for development work".to_string()),
mode: AgentMode::Primary,
native: true,
hidden: false,
model: None,
temperature: None,
top_p: None,
max_steps: Some(100),
}
}
pub fn plan_agent() -> AgentInfo {
AgentInfo {
name: "plan".to_string(),
description: Some("Read-only agent for analysis and code exploration".to_string()),
mode: AgentMode::Primary,
native: true,
hidden: false,
model: None,
temperature: None,
top_p: None,
max_steps: Some(50),
}
}
pub fn explore_agent() -> AgentInfo {
AgentInfo {
name: "explore".to_string(),
description: Some("Fast agent for exploring codebases".to_string()),
mode: AgentMode::Subagent,
native: true,
hidden: false,
model: None,
temperature: None,
top_p: None,
max_steps: Some(20),
}
}
#[allow(dead_code)]
pub const BUILD_SYSTEM_PROMPT: &str = r#"You are an expert AI programming assistant called CodeTether Agent.
You help users with software development tasks including:
- Writing and editing code
- Debugging and fixing issues
- Implementing new features
- Refactoring and improving code quality
- Explaining code and concepts
You have access to tools that let you:
- Read and write files
- Run shell commands
- Search the codebase
- List directories
- Spawn specialized sub-agents and delegate tasks to them
For complex tasks, use the `agent` tool to spawn focused sub-agents:
- Spawn a sub-agent with a specific role (e.g., "reviewer", "architect", "tester")
- Send it targeted messages and get back results
- Each sub-agent has its own conversation history and full tool access
- Use sub-agents when a task benefits from a dedicated focus or parallel exploration
Always:
- Be concise and helpful
- Show your work by using tools
- Explain what you're doing
- Ask clarifying questions when needed
- Follow best practices for the language/framework being used
Current working directory: {cwd}
"#;
#[allow(dead_code)]
pub const PLAN_SYSTEM_PROMPT: &str = r#"You are an expert AI assistant for code analysis and planning.
Your role is to:
- Explore and understand codebases
- Analyze code structure and architecture
- Plan changes and refactoring
- Answer questions about the code
You have read-only access to the codebase. You can:
- Read files
- Search the codebase
- List directories
- Run safe commands
You should NOT modify any files. If the user wants changes, explain what should be changed and suggest switching to the build agent.
Current working directory: {cwd}
"#;
#[allow(dead_code)]
pub const EXPLORE_SYSTEM_PROMPT: &str = r#"You are a fast, focused agent for codebase exploration.
Your job is to quickly find relevant code and information. Use tools efficiently:
- Use glob to find files by pattern
- Use grep to search for text
- Use list to see directory contents
- Read files to get details
Be thorough but fast. Return the most relevant results without unnecessary exploration.
"#;
pub fn load_agents_md(start_dir: &Path) -> Option<(String, std::path::PathBuf)> {
let mut current = start_dir.to_path_buf();
let repo_root = find_git_root(start_dir);
loop {
let agents_path = current.join("AGENTS.md");
if agents_path.exists() {
if let Ok(content) = std::fs::read_to_string(&agents_path) {
return Some((content, agents_path));
}
}
if repo_root.as_ref() == Some(¤t) {
break;
}
if !current.pop() {
break;
}
}
None
}
pub fn load_all_agents_md(start_dir: &Path) -> Vec<(String, std::path::PathBuf)> {
let mut results = Vec::new();
let mut current = start_dir.to_path_buf();
let repo_root = find_git_root(start_dir);
loop {
let agents_path = current.join("AGENTS.md");
if agents_path.exists() {
if let Ok(content) = std::fs::read_to_string(&agents_path) {
results.push((content, agents_path));
}
}
if repo_root.as_ref() == Some(¤t) {
break;
}
if !current.pop() {
break;
}
}
results
}
fn find_git_root(start_dir: &Path) -> Option<std::path::PathBuf> {
let mut current = start_dir.to_path_buf();
loop {
if current.join(".git").exists() {
return Some(current);
}
if !current.pop() {
break;
}
}
None
}
pub fn build_system_prompt(cwd: &Path) -> String {
let base_prompt = BUILD_SYSTEM_PROMPT.replace("{cwd}", &cwd.display().to_string());
let agents_files = load_all_agents_md(cwd);
if agents_files.is_empty() {
return base_prompt;
}
let mut agents_section = String::new();
agents_section.push_str("\n\n## Project Instructions (AGENTS.md)\n\n");
agents_section
.push_str("The following instructions were loaded from AGENTS.md files in the project.\n");
agents_section
.push_str("Follow these project-specific guidelines when working on this codebase.\n\n");
for (content, path) in agents_files.iter().rev() {
agents_section.push_str(&format!("### From {}\n\n", path.display()));
agents_section.push_str(content);
agents_section.push_str("\n\n");
}
format!("{base_prompt}{agents_section}")
}
#[allow(dead_code)]
pub fn build_plan_system_prompt(cwd: &Path) -> String {
let base_prompt = PLAN_SYSTEM_PROMPT.replace("{cwd}", &cwd.display().to_string());
let agents_files = load_all_agents_md(cwd);
if agents_files.is_empty() {
return base_prompt;
}
let mut agents_section = String::new();
agents_section.push_str("\n\n## Project Instructions (AGENTS.md)\n\n");
for (content, path) in agents_files.iter().rev() {
agents_section.push_str(&format!("### From {}\n\n", path.display()));
agents_section.push_str(content);
agents_section.push_str("\n\n");
}
format!("{base_prompt}{agents_section}")
}
#[cfg(test)]
mod tests {
use super::{load_agents_md, load_all_agents_md};
use tempfile::tempdir;
#[test]
fn load_agents_md_stops_at_git_root() {
let tmp = tempdir().expect("tempdir");
let parent_agents = tmp.path().join("AGENTS.md");
std::fs::write(&parent_agents, "parent instructions").expect("write parent AGENTS");
let repo_root = tmp.path().join("repo");
let nested = repo_root.join("src/nested");
std::fs::create_dir_all(repo_root.join(".git")).expect("create .git dir");
std::fs::create_dir_all(&nested).expect("create nested dir");
let repo_agents = repo_root.join("AGENTS.md");
std::fs::write(&repo_agents, "repo instructions").expect("write repo AGENTS");
let loaded = load_agents_md(&nested).expect("expected AGENTS");
assert_eq!(loaded.1, repo_agents);
assert_eq!(loaded.0, "repo instructions");
}
#[test]
fn load_all_agents_md_collects_within_repo_only() {
let tmp = tempdir().expect("tempdir");
let parent_agents = tmp.path().join("AGENTS.md");
std::fs::write(&parent_agents, "parent instructions").expect("write parent AGENTS");
let repo_root = tmp.path().join("repo");
let subdir = repo_root.join("service");
let nested = subdir.join("src");
std::fs::create_dir_all(repo_root.join(".git")).expect("create .git dir");
std::fs::create_dir_all(&nested).expect("create nested dir");
let repo_agents = repo_root.join("AGENTS.md");
std::fs::write(&repo_agents, "repo instructions").expect("write repo AGENTS");
let sub_agents = subdir.join("AGENTS.md");
std::fs::write(&sub_agents, "sub instructions").expect("write sub AGENTS");
let loaded = load_all_agents_md(&nested);
assert_eq!(loaded.len(), 2);
assert_eq!(loaded[0].1, sub_agents);
assert_eq!(loaded[1].1, repo_agents);
assert!(!loaded.iter().any(|(_, path)| path == &parent_agents));
}
}