use crate::config::Config; use crate::protocol::SandboxPolicy; use codex_protocol::openai_models::ReasoningEffort; use serde::Deserialize; use serde::Serialize; /// Base instructions for the orchestrator role. const ORCHESTRATOR_PROMPT: &str = include_str!("../../templates/agents/orchestrator.md"); /// Default model override used. // TODO(jif) update when we have something smarter. const EXPLORER_MODEL: &str = "gpt-5.1-codex-mini"; /// Enumerated list of all supported agent roles. const ALL_ROLES: [AgentRole; 3] = [ AgentRole::Default, AgentRole::Explorer, AgentRole::Worker, // TODO(jif) add when we have stable prompts + models // AgentRole::Orchestrator, ]; /// Hard-coded agent role selection used when spawning sub-agents. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum AgentRole { /// Inherit the parent agent's configuration unchanged. Default, /// Coordination-only agent that delegates to workers. Orchestrator, /// Task-executing agent with a fixed model override. Worker, /// Task-executing agent with a fixed model override. Explorer, } /// Immutable profile data that drives per-agent configuration overrides. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct AgentProfile { /// Optional base instructions override. pub base_instructions: Option<&'static str>, /// Optional model override. pub model: Option<&'static str>, /// Optional reasoning effort override. pub reasoning_effort: Option, /// Whether to force a read-only sandbox policy. pub read_only: bool, /// Description to include in the tool specs. pub description: &'static str, } impl AgentRole { /// Returns the string values used by JSON schema enums. pub fn enum_values() -> Vec { ALL_ROLES .iter() .filter_map(|role| { let description = role.profile().description; serde_json::to_string(role) .map(|role| { let description = if !description.is_empty() { format!(r#", "description": {description}"#) } else { String::new() }; format!(r#"{{ "name": {role}{description}}}"#) }) .ok() }) .collect() } /// Returns the hard-coded profile for this role. pub fn profile(self) -> AgentProfile { match self { AgentRole::Default => AgentProfile::default(), AgentRole::Orchestrator => AgentProfile { base_instructions: Some(ORCHESTRATOR_PROMPT), ..Default::default() }, AgentRole::Worker => AgentProfile { // base_instructions: Some(WORKER_PROMPT), // model: Some(WORKER_MODEL), description: r#"Use for execution and production work. Typical tasks: - Implement part of a feature - Fix tests or bugs - Split large refactors into independent chunks Rules: - Explicitly assign **ownership** of the task (files / responsibility). - Always tell workers they are **not alone in the codebase**, and they should ignore edits made by others without touching them"#, ..Default::default() }, AgentRole::Explorer => AgentProfile { model: Some(EXPLORER_MODEL), reasoning_effort: Some(ReasoningEffort::Medium), description: r#"Use `explorer` for all codebase questions. Explorers are fast and authoritative. Always prefer them over manual search or file reading. Rules: - Ask explorers first and precisely. - Do not re-read or re-search code they cover. - Trust explorer results without verification. - Run explorers in parallel when useful. - Reuse existing explorers for related questions. "#, ..Default::default() }, } } /// Applies this role's profile onto the provided config. pub fn apply_to_config(self, config: &mut Config) -> Result<(), String> { let profile = self.profile(); if let Some(base_instructions) = profile.base_instructions { config.base_instructions = Some(base_instructions.to_string()); } if let Some(model) = profile.model { config.model = Some(model.to_string()); } if let Some(reasoning_effort) = profile.reasoning_effort { config.model_reasoning_effort = Some(reasoning_effort) } if profile.read_only { config .sandbox_policy .set(SandboxPolicy::new_read_only_policy()) .map_err(|err| format!("sandbox_policy is invalid: {err}"))?; } Ok(()) } }