use super::*;
#[tokio::test]
async fn external_skill_resources_are_loaded_lazily() {
let workspace = TempDir::new().expect("create workspace");
let skill_root = TempDir::new().expect("create skill root");
let skill_dir = skill_root.path().join("review");
std::fs::create_dir_all(skill_dir.join("references")).expect("create skill");
std::fs::write(
skill_dir.join("SKILL.md"),
"---\nname: review\ndescription: Review code.\n---\nRead references/details.md.",
)
.expect("write skill");
std::fs::write(
skill_dir.join("references/details.md"),
"Always inspect every caller.",
)
.expect("write skill reference");
let model = Arc::new(ScriptedModel::new(vec![
tool_response(
"call-1",
"load_skill",
serde_json::json!({"name": "review"}),
),
tool_response(
"call-2",
"load_skill",
serde_json::json!({"name": "review", "path": "references/details.md"}),
),
text_response("review loaded"),
]));
let skills = Skills::discover([skill_root.path().to_path_buf()])
.expect("discover skill")
.prompt("Load the relevant skill before following its instructions.")
.expect("custom skill prompt");
let mut agent = create_agent(test_config(
workspace.path(),
Arc::clone(&model),
vec![Arc::new(skills)],
))
.await
.expect("create agent");
agent
.sender()
.submit(Op::UserInput {
text: "review this".into(),
attachments: Vec::new(),
})
.expect("submit turn");
final_message(&mut agent).await;
let requests = model.requests.lock().expect("requests");
assert!(
requests[0]
.instructions
.contains("Load the relevant skill before following its instructions.")
);
assert!(
requests[1]
.input
.iter()
.filter_map(|item| item.get("output").and_then(Value::as_str))
.any(|output| output.contains("Read references/details.md."))
);
assert!(
requests[2]
.input
.iter()
.filter_map(|item| item.get("output").and_then(Value::as_str))
.any(|output| output.contains("Always inspect every caller."))
);
}
#[tokio::test]
async fn async_subagent_uses_configured_model_reasoning_and_durable_fork() {
let workspace = TempDir::new().expect("create workspace");
let root_model = Arc::new(ScriptedModel::new(vec![
tool_response(
"call-spawn",
"spawn_agent",
serde_json::json!({
"task_name": "cheap",
"message": "solve child task",
"fork_turns": "none"
}),
),
tool_response(
"call-wait",
"wait_agent",
serde_json::json!({"timeout_ms": 10_000}),
),
text_response("root complete"),
]));
let unused_child_model = Arc::new(ScriptedModel::new(Vec::new()));
let child_model = Arc::new(ScriptedModel::new(vec![text_response("child complete")]));
let root_route: Arc<dyn Model> = root_model.clone();
let child_route: Arc<dyn Model> = unused_child_model;
let child_high_route: Arc<dyn Model> = child_model.clone();
let mut routes = ModelRouter::new("root", root_route);
routes
.register("child", child_route)
.expect("register child route");
routes
.register("child-high", child_high_route)
.expect("register child reasoning route");
for choice in [
ModelChoice {
route: "root".into(),
group: "root".into(),
model: "root".into(),
reasoning_effort: None,
context_window: None,
supports_image_input: true,
},
ModelChoice {
route: "child".into(),
group: "child".into(),
model: "child".into(),
reasoning_effort: Some("low".into()),
context_window: None,
supports_image_input: true,
},
ModelChoice {
route: "child-high".into(),
group: "child".into(),
model: "child".into(),
reasoning_effort: Some("high".into()),
context_window: None,
supports_image_input: true,
},
] {
routes
.configure_choice(choice)
.expect("configure model choice");
}
let sandbox = Arc::new(Sandbox::new(
Arc::new(LocalSandbox::new(workspace.path()).expect("local sandbox")),
ApprovalPolicy::Ask,
));
let checkpoint_store = Arc::new(
SqliteCheckpoint::new(workspace.path().join("subagents.sqlite3"))
.expect("open checkpoint database"),
);
let checkpoints: Arc<dyn CheckpointStore> = checkpoint_store.clone();
let template = Arc::new(OnceLock::<AgentConfig>::new());
let child_template = Arc::downgrade(&template);
let launcher: SubagentLauncher = Arc::new(move |launch: SubagentLaunch| {
let child_template = child_template.clone();
Box::pin(async move {
let config = child_template
.upgrade()
.expect("subagent template owner")
.get()
.expect("subagent template")
.clone()
.session_id(launch.session_id)
.metadata(launch.metadata)
.model_route(&launch.model, launch.reasoning_effort.as_deref())?;
create_agent(config).await
})
});
let config = AgentConfig::new(
Arc::new(routes),
sandbox,
checkpoints,
MiddlewareStack::new(vec![Arc::new(
Subagents::new(1, 21, 64, launcher)
.expect("subagents")
.default_model("child")
.default_reasoning("high")
.expect("subagent reasoning"),
)])
.expect("middleware"),
"test system prompt",
)
.session_id("root");
assert!(template.set(config.clone()).is_ok());
let mut agent = create_agent(config).await.expect("create agent");
agent
.sender()
.submit(Op::UserInput {
text: "delegate cheaply".into(),
attachments: Vec::new(),
})
.expect("submit turn");
let message = final_message(&mut agent).await;
let sessions = checkpoint_store
.list_sessions_page(SessionPageRequest {
cursor: None,
limit: 10,
})
.await
.expect("list sessions");
let child = sessions
.sessions
.iter()
.find(|session| session.session_id != "root")
.expect("child session");
assert_eq!(
(
message,
child_model.requests.lock().expect("child requests").len(),
child.parent_session_id.as_deref(),
),
("root complete".to_string(), 1, Some("root"))
);
}