use crate::{
BuiltinProvider, ModelInfo,
agent::ToolProfile,
runtime::{Runtime, RuntimeError, RuntimeIntrinsicTool},
};
use super::support::{ScriptedProvider, model_info, text_stream};
#[tokio::test]
async fn narrowed_tool_profile_restricts_the_childs_roster_and_keeps_task_hidden() {
let model = model_info("model", BuiltinProvider::Anthropic);
let provider = ScriptedProvider::new(BuiltinProvider::Anthropic, vec![model.clone()], vec![]);
let runtime = Runtime::empty_builder()
.with_provider_instance(provider)
.build()
.expect("build runtime");
let parent = runtime.spawn("parent", model).expect("spawn parent");
let template = parent
.disposable_subagent_template()
.with_tool_profile(ToolProfile::only(["read", "grep", "task"]));
let child = parent
.spawn_subagent_from(template)
.await
.expect("spawn from an overridden template");
assert!(
child.can_use_tool("read"),
"allowlisted tool must be usable"
);
assert!(
child.can_use_tool("grep"),
"allowlisted tool must be usable"
);
assert!(
!child.can_use_tool("write"),
"a tool outside the narrowed allowlist must be blocked"
);
assert!(
!child.can_use_tool(&RuntimeIntrinsicTool::Task.to_string()),
"task stays hidden from a subagent even when an overridden profile allows it"
);
}
#[tokio::test]
async fn overridden_model_reaches_the_childs_config_and_routing() {
let parent_model = model_info("parent-model", BuiltinProvider::Anthropic);
let anthropic = ScriptedProvider::new(
BuiltinProvider::Anthropic,
vec![parent_model.clone()],
vec![],
);
let anthropic_handle = anthropic.clone();
let mut cheap_model = ModelInfo::new("cheap-model", BuiltinProvider::OpenAI);
cheap_model.context_window = Some(4_096);
let openai = ScriptedProvider::new(
BuiltinProvider::OpenAI,
vec![cheap_model.clone()],
vec![text_stream(&cheap_model.id, "child done")],
);
let openai_handle = openai.clone();
let runtime = Runtime::empty_builder()
.with_provider_instance(anthropic)
.with_provider_instance(openai)
.build()
.expect("build runtime");
let parent = runtime.spawn("parent", parent_model).expect("spawn parent");
let template = parent
.disposable_subagent_template()
.with_model(cheap_model.clone());
let mut child = parent
.spawn_subagent_from(template)
.await
.expect("spawn from an overridden template");
assert_eq!(child.model(), cheap_model.id);
assert_eq!(child.context_window(), cheap_model.context_window);
child
.run(
vec![crate::ContentBlock::text("go")],
crate::runtime::RunOptions::default(),
)
.await
.expect("child run succeeds on the overridden provider");
assert_eq!(openai_handle.recorded_requests().await.len(), 1);
assert_eq!(anthropic_handle.recorded_requests().await.len(), 0);
}
#[tokio::test]
async fn overridden_model_with_no_context_window_gets_the_runtime_listed_one() {
let parent_model = model_info("parent-model", BuiltinProvider::Anthropic);
let mut listed_cheap_model = ModelInfo::new("cheap-model", BuiltinProvider::Anthropic);
listed_cheap_model.context_window = Some(8_192);
let provider = ScriptedProvider::new(
BuiltinProvider::Anthropic,
vec![parent_model.clone(), listed_cheap_model],
vec![],
);
let runtime = Runtime::empty_builder()
.with_provider_instance(provider)
.build()
.expect("build runtime");
let parent = runtime.spawn("parent", parent_model).expect("spawn parent");
let hand_built_override = ModelInfo::new("cheap-model", BuiltinProvider::Anthropic);
assert_eq!(hand_built_override.context_window, None);
let template = parent
.disposable_subagent_template()
.with_model(hand_built_override);
let child = parent
.spawn_subagent_from(template)
.await
.expect("spawn from an overridden template");
assert_eq!(child.context_window(), Some(8_192));
}
#[tokio::test]
async fn overridden_model_naming_an_unregistered_provider_fails_at_spawn() {
let model = model_info("model", BuiltinProvider::Anthropic);
let provider = ScriptedProvider::new(BuiltinProvider::Anthropic, vec![model.clone()], vec![]);
let runtime = Runtime::empty_builder()
.with_provider_instance(provider)
.build()
.expect("build runtime");
let parent = runtime.spawn("parent", model).expect("spawn parent");
let ghost_model = ModelInfo::new("ghost-model", BuiltinProvider::Gemini);
let template = parent
.disposable_subagent_template()
.with_model(ghost_model);
match parent.spawn_subagent_from(template).await {
Err(RuntimeError::ProviderNotFound(_)) => {}
Err(other) => panic!("unexpected error: {other}"),
Ok(_) => panic!("no provider is registered for the overridden model"),
}
}
#[tokio::test]
async fn overridden_system_still_carries_the_subagent_suffix() {
let model = model_info("model", BuiltinProvider::Anthropic);
let provider = ScriptedProvider::new(BuiltinProvider::Anthropic, vec![model.clone()], vec![]);
let runtime = Runtime::empty_builder()
.with_provider_instance(provider)
.build()
.expect("build runtime");
let parent = runtime.spawn("parent", model).expect("spawn parent");
let baseline = parent.spawn_subagent().expect("spawn baseline child");
let suffix = baseline
.config()
.system
.clone()
.expect("a subagent always gets a system prompt");
let template = parent
.disposable_subagent_template()
.with_system("You are a narrow triage worker.");
let overridden = parent
.spawn_subagent_from(template)
.await
.expect("spawn from an overridden template");
assert_eq!(
overridden.config().system.as_deref(),
Some(format!("You are a narrow triage worker.\n\n{suffix}").as_str())
);
}
#[tokio::test]
async fn default_template_spawns_byte_identically_to_spawn_subagent() {
let model = model_info("model", BuiltinProvider::Anthropic);
let provider = ScriptedProvider::new(BuiltinProvider::Anthropic, vec![model.clone()], vec![]);
let runtime = Runtime::empty_builder()
.with_provider_instance(provider)
.build()
.expect("build runtime");
let parent = runtime.spawn("parent", model).expect("spawn parent");
let via_spawn_subagent = parent.spawn_subagent().expect("spawn via spawn_subagent");
let via_template = parent
.spawn_subagent_from(parent.disposable_subagent_template())
.await
.expect("spawn via an un-overridden template");
assert_eq!(via_spawn_subagent.name(), via_template.name());
assert_eq!(via_spawn_subagent.model(), via_template.model());
assert_eq!(
via_spawn_subagent.context_window(),
via_template.context_window()
);
assert_eq!(
serde_json::to_value(via_spawn_subagent.config()).expect("serialize config"),
serde_json::to_value(via_template.config()).expect("serialize config")
);
assert_eq!(via_spawn_subagent.max_rounds(), via_template.max_rounds());
for tool in ["read", "grep", "write", "task"] {
assert_eq!(
via_spawn_subagent.can_use_tool(tool),
via_template.can_use_tool(tool),
"tool visibility diverged for {tool:?}"
);
}
}
#[tokio::test]
async fn spawn_subagent_from_refuses_a_template_from_a_different_agent() {
let model = model_info("model", BuiltinProvider::Anthropic);
let provider = ScriptedProvider::new(BuiltinProvider::Anthropic, vec![model.clone()], vec![]);
let runtime = Runtime::empty_builder()
.with_provider_instance(provider)
.build()
.expect("build runtime");
let agent_a = runtime
.spawn("agent-a", model.clone())
.expect("spawn agent a");
let agent_b = runtime.spawn("agent-b", model).expect("spawn agent b");
let template = agent_a.disposable_subagent_template();
match agent_b.spawn_subagent_from(template).await {
Err(RuntimeError::SubagentTemplateMismatch { .. }) => {}
Err(other) => panic!("unexpected error: {other}"),
Ok(_) => panic!("a template from agent_a must not spawn through agent_b"),
}
}
#[tokio::test]
async fn spawn_subagent_from_refuses_a_template_from_a_different_runtime() {
let model = model_info("model", BuiltinProvider::Anthropic);
let provider_a = ScriptedProvider::new(BuiltinProvider::Anthropic, vec![model.clone()], vec![]);
let runtime_a = Runtime::empty_builder()
.with_provider_instance(provider_a)
.build()
.expect("build runtime a");
let agent_a = runtime_a
.spawn("agent", model.clone())
.expect("spawn agent on runtime a");
let provider_b = ScriptedProvider::new(BuiltinProvider::Anthropic, vec![model.clone()], vec![]);
let runtime_b = Runtime::empty_builder()
.with_provider_instance(provider_b)
.build()
.expect("build runtime b");
let agent_b = runtime_b
.spawn("agent", model)
.expect("spawn agent on runtime b");
let template = agent_a.disposable_subagent_template();
match agent_b.spawn_subagent_from(template).await {
Err(RuntimeError::SubagentTemplateMismatch { .. }) => {}
Err(other) => panic!("unexpected error: {other}"),
Ok(_) => panic!("a template from runtime_a must not spawn through runtime_b"),
}
}