use crate::llm::LlmClient;
use anyhow::Context;
pub const SYSTEM_DEFAULT: &str = include_str!("../prompts/system_default.md");
pub const CONTINUATION: &str = include_str!("../prompts/continuation.md");
pub const SUBAGENT_EXPLORE: &str = include_str!("../prompts/subagent_explore.md");
pub const SUBAGENT_PLAN: &str = include_str!("../prompts/subagent_plan.md");
pub const SUBAGENT_CODE_REVIEW: &str = include_str!("../prompts/subagent_code_review.md");
pub const SUBAGENT_TITLE: &str = include_str!("../prompts/subagent_title.md");
pub const SUBAGENT_SUMMARY: &str = include_str!("../prompts/subagent_summary.md");
pub const CONTEXT_COMPACT: &str = include_str!("../prompts/context_compact.md");
pub const CONTEXT_SUMMARY_PREFIX: &str = include_str!("../prompts/context_summary_prefix.md");
#[allow(dead_code)]
pub const TITLE_GENERATE: &str = include_str!("../prompts/title_generate.md");
pub const LLM_PLAN_SYSTEM: &str = include_str!("../prompts/llm_plan_system.md");
pub const LLM_GOAL_EXTRACT_SYSTEM: &str = include_str!("../prompts/llm_goal_extract_system.md");
pub const LLM_GOAL_CHECK_SYSTEM: &str = include_str!("../prompts/llm_goal_check_system.md");
pub const PLAN_EXECUTE_GOAL: &str = include_str!("../prompts/plan_execute_goal.md");
pub const PLAN_EXECUTE_STEP: &str = include_str!("../prompts/plan_execute_step.md");
pub const PLAN_FALLBACK_STEP: &str = include_str!("../prompts/plan_fallback_step.md");
pub const PLAN_PARALLEL_RESULTS: &str = include_str!("../prompts/plan_parallel_results.md");
pub const TEAM_LEAD: &str = include_str!("../prompts/team_lead.md");
pub const TEAM_REVIEWER: &str = include_str!("../prompts/team_reviewer.md");
pub const SKILLS_CATALOG_HEADER: &str = include_str!("../prompts/skills_catalog_header.md");
pub const BTW_SYSTEM: &str = include_str!("../prompts/btw_system.md");
pub const AGENT_VERIFICATION: &str = include_str!("../prompts/agent_verification.md");
pub const AGENT_VERIFICATION_RESTRICTIONS: &str =
include_str!("../prompts/agent_verification_restrictions.md");
pub const INTENT_CLASSIFY_SYSTEM: &str = include_str!("../prompts/intent_classify_system.md");
pub const SESSION_MEMORY_TEMPLATE: &str = include_str!("../prompts/system_session_memory.md");
pub const PROMPT_SUGGESTION: &str = include_str!("../prompts/service_prompt_suggestion.md");
pub const UNDERCOVER_INSTRUCTIONS: &str = include_str!("../prompts/undercover_instructions.md");
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum PlanningMode {
#[default]
Auto,
Disabled,
Enabled,
}
impl PlanningMode {
pub fn should_plan(&self, message: &str) -> bool {
match self {
PlanningMode::Auto => AgentStyle::detect_from_message(message).requires_planning(),
PlanningMode::Enabled => true,
PlanningMode::Disabled => false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum AgentStyle {
#[default]
GeneralPurpose,
Plan,
Verification,
Explore,
CodeReview,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DetectionConfidence {
High,
Medium,
Low,
}
impl AgentStyle {
pub fn base_prompt(&self) -> &'static str {
match self {
AgentStyle::GeneralPurpose => SYSTEM_DEFAULT,
AgentStyle::Plan => SUBAGENT_PLAN,
AgentStyle::Verification => AGENT_VERIFICATION,
AgentStyle::Explore => SUBAGENT_EXPLORE,
AgentStyle::CodeReview => SYSTEM_DEFAULT, }
}
pub fn guidelines(&self) -> Option<&'static str> {
match self {
AgentStyle::GeneralPurpose => None,
AgentStyle::Plan => None, AgentStyle::Verification => None, AgentStyle::Explore => None, AgentStyle::CodeReview => Some(CODE_REVIEW_GUIDELINES),
}
}
pub fn description(&self) -> &'static str {
match self {
AgentStyle::GeneralPurpose => {
"General purpose coding agent for research and multi-step tasks"
}
AgentStyle::Plan => "Read-only planning and architecture analysis agent",
AgentStyle::Verification => "Adversarial verification specialist — tries to break code",
AgentStyle::Explore => "Fast read-only file search and codebase exploration agent",
AgentStyle::CodeReview => "Code review focused — analyzes quality and best practices",
}
}
pub fn builtin_agent_name(&self) -> &'static str {
match self {
AgentStyle::GeneralPurpose => "general",
AgentStyle::Plan => "plan",
AgentStyle::Verification => "verification",
AgentStyle::Explore => "explore",
AgentStyle::CodeReview => "review",
}
}
pub fn runtime_mode(&self) -> &'static str {
match self {
AgentStyle::GeneralPurpose => "general",
AgentStyle::Plan => "planning",
AgentStyle::Verification => "verification",
AgentStyle::Explore => "explore",
AgentStyle::CodeReview => "code_review",
}
}
pub fn requires_planning(&self) -> bool {
matches!(self, AgentStyle::Plan | AgentStyle::GeneralPurpose)
}
pub fn detect_with_confidence(message: &str) -> (Self, DetectionConfidence) {
let lower = message.to_lowercase();
if lower.contains("try to break")
|| lower.contains("find vulnerabilities")
|| lower.contains("adversarial")
|| lower.contains("security audit")
{
return (AgentStyle::Verification, DetectionConfidence::High);
}
if lower.contains("help me plan")
|| lower.contains("help me design")
|| lower.contains("create a plan")
|| lower.contains("implementation plan")
|| lower.contains("step-by-step plan")
|| lower.contains("帮我规划")
|| lower.contains("帮我设计")
|| lower.contains("架构设计")
|| lower.contains("系统设计")
{
return (AgentStyle::Plan, DetectionConfidence::High);
}
if lower.contains("find all files")
|| lower.contains("search for all")
|| lower.contains("locate all")
{
return (AgentStyle::Explore, DetectionConfidence::High);
}
if lower.contains("verify")
|| lower.contains("verification")
|| lower.contains("break")
|| lower.contains("debug")
|| lower.contains("test")
|| lower.contains("check if")
{
return (AgentStyle::Verification, DetectionConfidence::Medium);
}
if lower.contains("plan")
|| lower.contains("design")
|| lower.contains("architecture")
|| lower.contains("approach")
|| lower.contains("规划")
|| lower.contains("设计")
|| lower.contains("架构")
|| lower.contains("方案")
{
return (AgentStyle::Plan, DetectionConfidence::Medium);
}
if lower.contains("find")
|| lower.contains("search")
|| lower.contains("where is")
|| lower.contains("where's")
|| lower.contains("locate")
|| lower.contains("explore")
|| lower.contains("look for")
{
return (AgentStyle::Explore, DetectionConfidence::Medium);
}
if lower.contains("review")
|| lower.contains("code review")
|| lower.contains("analyze")
|| lower.contains("assess")
|| lower.contains("quality")
|| lower.contains("best practice")
{
return (AgentStyle::CodeReview, DetectionConfidence::Medium);
}
(AgentStyle::GeneralPurpose, DetectionConfidence::Low)
}
pub fn detect_from_message(message: &str) -> Self {
Self::detect_with_confidence(message).0
}
pub async fn detect_with_llm(llm: &dyn LlmClient, message: &str) -> anyhow::Result<Self> {
use crate::llm::Message;
let system = INTENT_CLASSIFY_SYSTEM;
let messages = vec![Message::user(message)];
let response = llm
.complete(&messages, Some(system), &[])
.await
.context("LLM intent classification failed")?;
let text = response.text().trim().to_lowercase();
let style = match text.as_str() {
"plan" => AgentStyle::Plan,
"explore" => AgentStyle::Explore,
"verification" => AgentStyle::Verification,
"codereview" | "code review" => AgentStyle::CodeReview,
_ => AgentStyle::GeneralPurpose,
};
Ok(style)
}
}
const CODE_REVIEW_GUIDELINES: &str = r#"## Code Review Focus
When reviewing code, pay attention to:
1. **Correctness** — Does the code do what it's supposed to do? Are edge cases handled?
2. **Security** — Are there potential vulnerabilities (injection, auth bypass, data exposure)?
3. **Performance** — Are there obvious inefficiencies (N+1 queries, unnecessary allocations)?
4. **Maintainability** — Is the code readable? Are names clear? Is there appropriate documentation?
5. **Best Practices** — Does it follow language/framework conventions? Are there anti-patterns?
Be specific in your review. Quote the actual code you're referring to. Suggest concrete improvements with examples where possible.
Remember: your job is to find issues, not to be nice. A code review that finds nothing is a missed opportunity."#;
#[derive(Debug, Clone, Default)]
pub struct SystemPromptSlots {
pub style: Option<AgentStyle>,
pub role: Option<String>,
pub guidelines: Option<String>,
pub response_style: Option<String>,
pub extra: Option<String>,
}
const DEFAULT_ROLE_LINE: &str = include_str!("../prompts/system_default_role_line.md");
const DEFAULT_RESPONSE_FORMAT: &str = include_str!("../prompts/system_default_response_format.md");
impl SystemPromptSlots {
pub fn build(&self) -> String {
self.build_with_style(self.style.unwrap_or_default())
}
pub fn build_with_message(&self, initial_message: &str) -> String {
let style = self
.style
.unwrap_or_else(|| AgentStyle::detect_from_message(initial_message));
self.build_with_style(style)
}
fn build_with_style(&self, style: AgentStyle) -> String {
let mut parts: Vec<String> = Vec::new();
let base_prompt = style.base_prompt().replace('\r', "");
let default_role_line = DEFAULT_ROLE_LINE.replace('\r', "");
let default_response_format = DEFAULT_RESPONSE_FORMAT.replace('\r', "");
let core = if let Some(ref role) = self.role {
if style == AgentStyle::GeneralPurpose {
let custom_role = format!(
"{}. You operate in an agentic loop: you\nthink, use tools, observe results, and keep working until the task is fully complete.",
role.trim_end_matches('.')
);
base_prompt.replace(&default_role_line, &custom_role)
} else {
format!("{}\n\n{}", role, base_prompt)
}
} else {
base_prompt
};
let core = if self.response_style.is_some() {
core.replace(&default_response_format, "")
.trim_end()
.to_string()
} else {
core.trim_end().to_string()
};
parts.push(core);
if let Some(ref style) = self.response_style {
parts.push(format!("## Response Format\n\n{}", style));
}
let style_guidelines = style.guidelines();
if style_guidelines.is_some() || self.guidelines.is_some() {
let mut guidelines_parts = Vec::new();
if let Some(sg) = style_guidelines {
guidelines_parts.push(sg.to_string());
}
if let Some(ref g) = self.guidelines {
guidelines_parts.push(g.clone());
}
parts.push(format!(
"## Guidelines\n\n{}",
guidelines_parts.join("\n\n")
));
}
if let Some(ref extra) = self.extra {
parts.push(extra.clone());
}
parts.join("\n\n")
}
pub fn from_legacy(prompt: String) -> Self {
Self {
extra: Some(prompt),
..Default::default()
}
}
pub fn is_empty(&self) -> bool {
self.style.is_none()
&& self.role.is_none()
&& self.guidelines.is_none()
&& self.response_style.is_none()
&& self.extra.is_none()
}
pub fn with_style(mut self, style: AgentStyle) -> Self {
self.style = Some(style);
self
}
pub fn with_role(mut self, role: impl Into<String>) -> Self {
self.role = Some(role.into());
self
}
pub fn with_guidelines(mut self, guidelines: impl Into<String>) -> Self {
self.guidelines = Some(guidelines.into());
self
}
pub fn with_response_style(mut self, style: impl Into<String>) -> Self {
self.response_style = Some(style.into());
self
}
pub fn with_extra(mut self, extra: impl Into<String>) -> Self {
self.extra = Some(extra.into());
self
}
}
pub fn render(template: &str, vars: &[(&str, &str)]) -> String {
let mut result = template.to_string();
for (key, value) in vars {
result = result.replace(&format!("{{{}}}", key), value);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_all_prompts_loaded() {
assert!(!SYSTEM_DEFAULT.is_empty());
assert!(!CONTINUATION.is_empty());
assert!(!SUBAGENT_EXPLORE.is_empty());
assert!(!SUBAGENT_PLAN.is_empty());
assert!(!SUBAGENT_CODE_REVIEW.is_empty());
assert!(!SUBAGENT_TITLE.is_empty());
assert!(!SUBAGENT_SUMMARY.is_empty());
assert!(!CONTEXT_COMPACT.is_empty());
assert!(!TITLE_GENERATE.is_empty());
assert!(!LLM_PLAN_SYSTEM.is_empty());
assert!(!LLM_GOAL_EXTRACT_SYSTEM.is_empty());
assert!(!LLM_GOAL_CHECK_SYSTEM.is_empty());
assert!(!TEAM_LEAD.is_empty());
assert!(!TEAM_REVIEWER.is_empty());
assert!(!SKILLS_CATALOG_HEADER.is_empty());
assert!(!BTW_SYSTEM.is_empty());
assert!(!PLAN_EXECUTE_GOAL.is_empty());
assert!(!PLAN_EXECUTE_STEP.is_empty());
assert!(!PLAN_FALLBACK_STEP.is_empty());
assert!(!PLAN_PARALLEL_RESULTS.is_empty());
}
#[test]
fn test_render_template() {
let result = render(
PLAN_EXECUTE_GOAL,
&[("goal", "Build app"), ("steps", "1. Init")],
);
assert!(result.contains("Build app"));
assert!(!result.contains("{goal}"));
}
#[test]
fn test_render_multiple_placeholders() {
let template = "Goal: {goal}\nCriteria: {criteria}\nState: {current_state}";
let result = render(
template,
&[
("goal", "Build a REST API"),
("criteria", "- Endpoint works\n- Tests pass"),
("current_state", "API is deployed"),
],
);
assert!(result.contains("Build a REST API"));
assert!(result.contains("Endpoint works"));
assert!(result.contains("API is deployed"));
}
#[test]
fn test_subagent_prompts_contain_guidelines() {
assert!(SUBAGENT_EXPLORE.contains("Guidelines"));
assert!(SUBAGENT_EXPLORE.contains("read-only"));
assert!(SUBAGENT_PLAN.contains("Guidelines"));
assert!(SUBAGENT_PLAN.contains("read-only"));
}
#[test]
fn test_context_summary_prefix() {
assert!(CONTEXT_SUMMARY_PREFIX.contains("Context Summary"));
}
#[test]
fn test_slots_default_builds_system_default() {
let slots = SystemPromptSlots::default();
let built = slots.build();
assert!(built.contains("Core Behaviour"));
assert!(built.contains("Tool Usage Strategy"));
assert!(built.contains("Completion Criteria"));
assert!(built.contains("Response Format"));
assert!(built.contains("A3S Code"));
}
#[test]
fn test_slots_custom_role_replaces_default() {
let slots = SystemPromptSlots {
role: Some("You are a senior Python developer".to_string()),
..Default::default()
};
let built = slots.build();
assert!(built.contains("You are a senior Python developer"));
assert!(!built.contains("You are A3S Code"));
assert!(built.contains("Core Behaviour"));
assert!(built.contains("Tool Usage Strategy"));
}
#[test]
fn test_slots_custom_guidelines_appended() {
let slots = SystemPromptSlots {
guidelines: Some("Always use type hints. Follow PEP 8.".to_string()),
..Default::default()
};
let built = slots.build();
assert!(built.contains("## Guidelines"));
assert!(built.contains("Always use type hints"));
assert!(built.contains("Core Behaviour"));
}
#[test]
fn test_slots_custom_response_style_replaces_default() {
let slots = SystemPromptSlots {
response_style: Some("Be concise. Use bullet points.".to_string()),
..Default::default()
};
let built = slots.build();
assert!(built.contains("Be concise. Use bullet points."));
assert!(!built.contains("emit tool calls, no prose"));
assert!(built.contains("Core Behaviour"));
}
#[test]
fn test_slots_extra_appended() {
let slots = SystemPromptSlots {
extra: Some("Remember: always write tests first.".to_string()),
..Default::default()
};
let built = slots.build();
assert!(built.contains("Remember: always write tests first."));
assert!(built.contains("Core Behaviour"));
}
#[test]
fn test_slots_from_legacy() {
let slots = SystemPromptSlots::from_legacy("You are a helpful assistant.".to_string());
let built = slots.build();
assert!(built.contains("You are a helpful assistant."));
assert!(built.contains("Core Behaviour"));
assert!(built.contains("Tool Usage Strategy"));
}
#[test]
fn test_slots_all_slots_combined() {
let slots = SystemPromptSlots {
style: None,
role: Some("You are a Rust expert".to_string()),
guidelines: Some("Use clippy. No unwrap.".to_string()),
response_style: Some("Short answers only.".to_string()),
extra: Some("Project uses tokio.".to_string()),
};
let built = slots.build();
assert!(built.contains("You are a Rust expert"));
assert!(built.contains("Core Behaviour"));
assert!(built.contains("## Guidelines"));
assert!(built.contains("Use clippy"));
assert!(built.contains("Short answers only"));
assert!(built.contains("Project uses tokio"));
assert!(!built.contains("emit tool calls, no prose"));
}
#[test]
fn test_slots_is_empty() {
assert!(SystemPromptSlots::default().is_empty());
assert!(!SystemPromptSlots {
role: Some("test".to_string()),
..Default::default()
}
.is_empty());
assert!(!SystemPromptSlots {
style: Some(AgentStyle::Plan),
..Default::default()
}
.is_empty());
}
#[test]
fn test_agent_style_default_is_general_purpose() {
assert_eq!(AgentStyle::default(), AgentStyle::GeneralPurpose);
}
#[test]
fn test_agent_style_base_prompt() {
assert_eq!(AgentStyle::GeneralPurpose.base_prompt(), SYSTEM_DEFAULT);
assert_eq!(AgentStyle::Plan.base_prompt(), SUBAGENT_PLAN);
assert_eq!(AgentStyle::Explore.base_prompt(), SUBAGENT_EXPLORE);
assert_eq!(AgentStyle::Verification.base_prompt(), AGENT_VERIFICATION);
assert_eq!(AgentStyle::CodeReview.base_prompt(), SYSTEM_DEFAULT);
}
#[test]
fn test_agent_style_guidelines() {
assert!(AgentStyle::GeneralPurpose.guidelines().is_none());
assert!(AgentStyle::Plan.guidelines().is_none()); assert!(AgentStyle::Explore.guidelines().is_none());
assert!(AgentStyle::Verification.guidelines().is_none());
assert!(AgentStyle::CodeReview.guidelines().is_some());
assert!(AgentStyle::CodeReview
.guidelines()
.unwrap()
.contains("Correctness"));
}
#[test]
fn test_agent_style_builtin_agent_name_mapping() {
assert_eq!(AgentStyle::GeneralPurpose.builtin_agent_name(), "general");
assert_eq!(AgentStyle::Plan.builtin_agent_name(), "plan");
assert_eq!(AgentStyle::Explore.builtin_agent_name(), "explore");
assert_eq!(
AgentStyle::Verification.builtin_agent_name(),
"verification"
);
assert_eq!(AgentStyle::CodeReview.builtin_agent_name(), "review");
}
#[test]
fn test_agent_style_runtime_mode_mapping() {
assert_eq!(AgentStyle::GeneralPurpose.runtime_mode(), "general");
assert_eq!(AgentStyle::Plan.runtime_mode(), "planning");
assert_eq!(AgentStyle::Explore.runtime_mode(), "explore");
assert_eq!(AgentStyle::Verification.runtime_mode(), "verification");
assert_eq!(AgentStyle::CodeReview.runtime_mode(), "code_review");
}
#[test]
fn test_agent_style_detect_plan() {
assert_eq!(
AgentStyle::detect_from_message("Help me plan a new feature"),
AgentStyle::Plan
);
assert_eq!(
AgentStyle::detect_from_message("Design the architecture for this"),
AgentStyle::Plan
);
assert_eq!(
AgentStyle::detect_from_message("What's the implementation approach?"),
AgentStyle::Plan
);
assert_eq!(
AgentStyle::detect_from_message("帮我设计一个Agent as a Service 平台"),
AgentStyle::Plan
);
assert_eq!(
AgentStyle::detect_from_message("请给我一个系统设计方案"),
AgentStyle::Plan
);
}
#[test]
fn test_agent_style_detect_verification() {
assert_eq!(
AgentStyle::detect_from_message("Verify that this works correctly"),
AgentStyle::Verification
);
assert_eq!(
AgentStyle::detect_from_message("Test the login flow"),
AgentStyle::Verification
);
assert_eq!(
AgentStyle::detect_from_message("Check if the API handles edge cases"),
AgentStyle::Verification
);
}
#[test]
fn test_agent_style_detect_explore() {
assert_eq!(
AgentStyle::detect_from_message("Find all files related to auth"),
AgentStyle::Explore
);
assert_eq!(
AgentStyle::detect_from_message("Where is the user model defined?"),
AgentStyle::Explore
);
assert_eq!(
AgentStyle::detect_from_message("Search for password hashing code"),
AgentStyle::Explore
);
}
#[test]
fn test_agent_style_detect_code_review() {
assert_eq!(
AgentStyle::detect_from_message("Review the PR changes"),
AgentStyle::CodeReview
);
assert_eq!(
AgentStyle::detect_from_message("Analyze this code for best practices"),
AgentStyle::CodeReview
);
assert_eq!(
AgentStyle::detect_from_message("Assess code quality"),
AgentStyle::CodeReview
);
}
#[test]
fn test_agent_style_detect_default_is_general_purpose() {
assert_eq!(
AgentStyle::detect_from_message("Implement the new feature"),
AgentStyle::GeneralPurpose
);
assert_eq!(
AgentStyle::detect_from_message("Write code for the API"),
AgentStyle::GeneralPurpose
);
}
#[test]
fn test_build_with_message_auto_detects_style() {
let slots = SystemPromptSlots::default();
let built = slots.build_with_message("Help me plan a new feature");
assert!(built.contains("planning agent") || built.contains("READ-ONLY"));
}
#[test]
fn test_build_with_message_explicit_style_overrides() {
let slots = SystemPromptSlots {
style: Some(AgentStyle::Verification),
..Default::default()
};
let built = slots.build_with_message("Help me plan a new feature");
assert!(built.contains("verification specialist") || built.contains("try to break"));
}
#[test]
fn test_build_with_message_plan_style() {
let slots = SystemPromptSlots::default();
let built = slots.build_with_message("Design the system architecture");
assert!(built.contains("planning agent") || built.contains("READ-ONLY"));
}
#[test]
fn test_build_with_message_explore_style() {
let slots = SystemPromptSlots::default();
let built = slots.build_with_message("Find all authentication files");
assert!(built.contains("exploration agent") || built.contains("explore"));
}
#[test]
fn test_build_with_message_code_review_style() {
let slots = SystemPromptSlots::default();
let built = slots.build_with_message("Review this code");
assert!(built.contains("Correctness") || built.contains("Code Review"));
}
#[test]
fn test_builder_methods() {
let slots = SystemPromptSlots::default()
.with_style(AgentStyle::Plan)
.with_role("You are a Python expert")
.with_guidelines("Use type hints")
.with_response_style("Be brief")
.with_extra("Additional instructions");
assert_eq!(slots.style, Some(AgentStyle::Plan));
assert_eq!(slots.role, Some("You are a Python expert".to_string()));
assert_eq!(slots.guidelines, Some("Use type hints".to_string()));
assert_eq!(slots.response_style, Some("Be brief".to_string()));
assert_eq!(slots.extra, Some("Additional instructions".to_string()));
let built = slots.build();
assert!(built.contains("Python expert"));
assert!(built.contains("Use type hints"));
assert!(built.contains("Be brief"));
assert!(built.contains("Additional instructions"));
}
#[test]
fn test_code_review_guidelines_appended() {
let slots = SystemPromptSlots {
style: Some(AgentStyle::CodeReview),
..Default::default()
};
let built = slots.build();
assert!(built.contains("Correctness"));
assert!(built.contains("Security"));
assert!(built.contains("Performance"));
assert!(built.contains("Maintainability"));
}
}