diff --git a/clients/agent-runtime/src/agent/agent.rs b/clients/agent-runtime/src/agent/agent.rs index f042a2c38..1ba5b0f38 100755 --- a/clients/agent-runtime/src/agent/agent.rs +++ b/clients/agent-runtime/src/agent/agent.rs @@ -227,6 +227,16 @@ impl Agent { AgentBuilder::new() } + pub(crate) fn from_config_with_profile(config: &Config, profile: &str) -> Result { + let bootstrap = bootstrap::BootstrapContext::from_config_with_profile(config, profile)?; + + Self::from_bootstrap(config, bootstrap) + } + + pub(crate) fn code_from_config(config: &Config) -> Result { + Self::from_config_with_profile(config, "code") + } + pub fn history(&self) -> &[ConversationMessage] { &self.history } @@ -238,6 +248,10 @@ impl Agent { pub fn from_config(config: &Config) -> Result { let bootstrap = bootstrap::BootstrapContext::from_config(config)?; + Self::from_bootstrap(config, bootstrap) + } + + fn from_bootstrap(config: &Config, bootstrap: bootstrap::BootstrapContext) -> Result { let model_name = config .default_model .as_deref() @@ -246,6 +260,20 @@ impl Agent { let provider: Box = bootstrap::create_routed_provider(config, &model_name)?; + Self::from_bootstrap_with_provider(config, bootstrap, provider) + } + + fn from_bootstrap_with_provider( + config: &Config, + bootstrap: bootstrap::BootstrapContext, + provider: Box, + ) -> Result { + let model_name = config + .default_model + .as_deref() + .unwrap_or("anthropic/claude-sonnet-4-20250514") + .to_string(); + let dispatcher_choice = config.agent.tool_dispatcher.as_str(); let tool_dispatcher: Box = match dispatcher_choice { "native" => Box::new(NativeToolDispatcher), @@ -1113,8 +1141,11 @@ pub async fn run( #[cfg(test)] mod tests { use super::*; + use crate::test_support::test_config; use async_trait::async_trait; use parking_lot::Mutex; + use std::collections::HashSet; + use tempfile::TempDir; struct MockProvider { responses: Mutex>, @@ -1307,6 +1338,33 @@ mod tests { assert_eq!(response, "hello"); } + #[tokio::test] + async fn code_profile_agent_uses_bootstrap_components_for_basic_turn() { + let tmp = TempDir::new().unwrap(); + let mut config = test_config(&tmp); + config.agent.profile = "full".into(); + + let bootstrap = + bootstrap::BootstrapContext::from_config_with_profile(&config, "code").unwrap(); + let tool_names: HashSet<&str> = bootstrap.tools.iter().map(|tool| tool.name()).collect(); + + assert!(tool_names.contains("shell")); + assert!(tool_names.contains("git_operations")); + assert!(!tool_names.contains("schedule")); + + let provider = Box::new(MockProvider { + responses: Mutex::new(vec![crate::providers::ChatResponse { + text: Some("code-ready".into()), + tool_calls: vec![], + }]), + }); + + let mut agent = Agent::from_bootstrap_with_provider(&config, bootstrap, provider).unwrap(); + + let response = agent.turn("review this patch").await.unwrap(); + assert_eq!(response, "code-ready"); + } + #[tokio::test] async fn turn_with_native_dispatcher_handles_tool_results_variant() { let provider = Box::new(MockProvider { diff --git a/clients/agent-runtime/src/bootstrap/mod.rs b/clients/agent-runtime/src/bootstrap/mod.rs index 2595658c9..e8d7089a4 100644 --- a/clients/agent-runtime/src/bootstrap/mod.rs +++ b/clients/agent-runtime/src/bootstrap/mod.rs @@ -162,6 +162,17 @@ fn init_memory_and_observer( impl BootstrapContext { pub fn from_config(config: &Config) -> anyhow::Result { + Self::from_config_with_profile(config, config.agent.profile.as_str()) + } + + pub(crate) fn from_config_with_profile(config: &Config, profile: &str) -> anyhow::Result { + let mut effective_config = config.clone(); + effective_config.agent.profile = profile.to_string(); + + Self::from_effective_config(&effective_config) + } + + fn from_effective_config(config: &Config) -> anyhow::Result { let profile = AgentProfile::from_config(config)?; let (memory, observer) = init_memory_and_observer(config, profile)?; let runtime: Arc = Arc::from(runtime::create_runtime(&config.runtime)?); diff --git a/clients/web/apps/docs/src/content/docs/en/clients/agent-runtime/architecture.md b/clients/web/apps/docs/src/content/docs/en/clients/agent-runtime/architecture.md index d0bb70737..2fb07b868 100644 --- a/clients/web/apps/docs/src/content/docs/en/clients/agent-runtime/architecture.md +++ b/clients/web/apps/docs/src/content/docs/en/clients/agent-runtime/architecture.md @@ -129,6 +129,18 @@ The agent uses an execution loop pattern that alternates between thinking phases During thinking phases, the agent analyzes available context and decides which tools to invoke. During action phases, it executes selected tools and processes results. +#### Internal Specialized Agent Paths + +Internal consumers can now instantiate a code-specialized agent without duplicating bootstrap +wiring. The canonical path is `Agent::code_from_config(&config)`, which reuses shared bootstrap +assembly while forcing the `code` capability profile for that agent instance only. Lower-level +consumers that need bootstrap components directly can use +`BootstrapContext::from_config_with_profile(&config, "code")`. + +This keeps provider selection, memory setup, observer wiring, and tool filtering aligned with the +main runtime path while allowing future specialized agents to be added as thin profile-based entry +points instead of separate setup trees. + ### Providers The `providers/` module includes implementations for multiple language model providers. Each diff --git a/clients/web/apps/docs/src/content/docs/es/clients/agent-runtime/architecture.md b/clients/web/apps/docs/src/content/docs/es/clients/agent-runtime/architecture.md index 19195164e..96a8a2b08 100644 --- a/clients/web/apps/docs/src/content/docs/es/clients/agent-runtime/architecture.md +++ b/clients/web/apps/docs/src/content/docs/es/clients/agent-runtime/architecture.md @@ -133,6 +133,19 @@ acción. Durante las fases de pensamiento, el agente analiza el contexto disponi herramientas invocar. Durante las fases de acción, ejecuta las herramientas seleccionadas y procesa los resultados. +#### Rutas internas para agentes especializados + +Los consumidores internos ahora pueden instanciar un agente especializado en código sin duplicar el +bootstrap. La ruta canónica es `Agent::code_from_config(&config)`, que reutiliza el ensamblaje +compartido del bootstrap y fuerza el perfil de capacidades `code` solo para esa instancia del +agente. Los consumidores de más bajo nivel que necesiten acceder directamente a los componentes del +bootstrap pueden usar `BootstrapContext::from_config_with_profile(&config, "code")`. + +Esto mantiene alineados la selección de proveedor, la memoria, la observabilidad y el filtrado de +herramientas con la ruta principal del runtime, mientras permite agregar futuros agentes +especializados como entrypoints delgados basados en perfiles en lugar de árboles de configuración +separados. + ### Proveedores El módulo `providers/` incluye implementaciones para múltiples proveedores de modelos de lenguaje.