Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions clients/agent-runtime/src/agent/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,16 @@ impl Agent {
AgentBuilder::new()
}

pub(crate) fn from_config_with_profile(config: &Config, profile: &str) -> Result<Self> {
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> {
Self::from_config_with_profile(config, "code")
}

pub fn history(&self) -> &[ConversationMessage] {
&self.history
}
Expand All @@ -238,6 +248,10 @@ impl Agent {
pub fn from_config(config: &Config) -> Result<Self> {
let bootstrap = bootstrap::BootstrapContext::from_config(config)?;

Self::from_bootstrap(config, bootstrap)
}

fn from_bootstrap(config: &Config, bootstrap: bootstrap::BootstrapContext) -> Result<Self> {
let model_name = config
.default_model
.as_deref()
Expand All @@ -246,6 +260,20 @@ impl Agent {

let provider: Box<dyn Provider> = 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<dyn Provider>,
) -> Result<Self> {
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<dyn ToolDispatcher> = match dispatcher_choice {
"native" => Box::new(NativeToolDispatcher),
Expand Down Expand Up @@ -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<Vec<crate::providers::ChatResponse>>,
Expand Down Expand Up @@ -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 {
Expand Down
11 changes: 11 additions & 0 deletions clients/agent-runtime/src/bootstrap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,17 @@ fn init_memory_and_observer(

impl BootstrapContext {
pub fn from_config(config: &Config) -> anyhow::Result<Self> {
Self::from_config_with_profile(config, config.agent.profile.as_str())
}

pub(crate) fn from_config_with_profile(config: &Config, profile: &str) -> anyhow::Result<Self> {
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<Self> {
let profile = AgentProfile::from_config(config)?;
let (memory, observer) = init_memory_and_observer(config, profile)?;
let runtime: Arc<dyn RuntimeAdapter> = Arc::from(runtime::create_runtime(&config.runtime)?);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading