#[tokio::test]
async fn delegation_repairs_hollow_subagent_before_selection() {
let state = crate::api::routes::tests::test_state();
let row = roboticus_db::agents::SubAgentRow {
id: uuid::Uuid::new_v4().to_string(),
name: "moltbook-monitor".to_string(),
display_name: Some("Moltbook Monitor".to_string()),
model: "auto".to_string(),
fallback_models_json: Some("[]".to_string()),
role: "subagent".to_string(),
description: Some("Monitors the moltbook feed and reports changes".to_string()),
skills_json: Some("[]".to_string()),
enabled: true,
session_count: 0,
last_used_at: None,
};
roboticus_db::agents::upsert_sub_agent(&state.db, &row).unwrap();
let sid = roboticus_db::sessions::find_or_create(&state.db, "test-turn-agent", None).unwrap();
let turn_id =
roboticus_db::sessions::create_turn(&state.db, &sid, None, None, None, None).unwrap();
let out = super::execute_virtual_subagent_tool_call(
&state,
"select-subagent-model",
&serde_json::json!({"specialist":"moltbook-monitor","task":"check the moltbook feed and summarize updates"}),
&turn_id,
roboticus_core::InputAuthority::Creator,
roboticus_core::SurvivalTier::Normal,
)
.await
.unwrap();
assert!(out.contains("selected_subagent=moltbook-monitor"));
let repaired = roboticus_db::agents::list_sub_agents(&state.db)
.unwrap()
.into_iter()
.find(|a| a.name == "moltbook-monitor")
.unwrap();
let repaired_skills = super::parse_skills_json(repaired.skills_json.as_deref());
assert!(
!repaired_skills.is_empty(),
"hollow subagent should be repaired with real skill names"
);
assert!(
repaired_skills
.iter()
.all(|skill| { !skill.is_empty() && !skill.contains(' ') && skill.len() < 64 }),
"repaired skills should be valid skill identifiers, not fabricated capability tokens"
);
let runtime = state.registry.get_agent("moltbook-monitor").await.unwrap();
assert_eq!(
runtime.state,
roboticus_agent::subagents::AgentRunState::Running
);
let session_counts = roboticus_db::agents::list_session_counts_by_agent(&state.db).unwrap();
assert!(session_counts.get("moltbook-monitor").copied().unwrap_or(0) > 0);
}
#[tokio::test]
async fn subagent_composition_evaluation_and_retirement_lifecycle() {
let state = crate::api::routes::tests::test_state();
let sid = roboticus_db::sessions::find_or_create(&state.db, "test-turn-agent", None).unwrap();
let turn_id =
roboticus_db::sessions::create_turn(&state.db, &sid, None, None, None, None).unwrap();
let registry = crate::api::routes::subagent_integrity::skill_registry_names(&state);
let known_skills: Vec<String> = registry.iter().take(2).cloned().collect();
assert!(
known_skills.len() >= 2,
"test state should expose at least two registry skills"
);
let composed_name = "fleet-risk-specialist";
let compose_out = super::execute_virtual_orchestration_tool(
&state,
"compose-subagent",
&serde_json::json!({
"name": composed_name,
"description": "Assesses fleet operational and market risk",
"skills": known_skills,
"model": "auto"
}),
&turn_id,
roboticus_core::InputAuthority::Creator,
roboticus_core::SurvivalTier::Normal,
)
.await
.unwrap();
assert!(
compose_out.contains("created subagent"),
"composition should create a new specialist: {compose_out}"
);
let row = roboticus_db::agents::list_sub_agents(&state.db)
.unwrap()
.into_iter()
.find(|a| a.name == composed_name)
.expect("composed subagent row should exist");
assert!(row.enabled, "composed subagent should be enabled");
let runtime = state.registry.get_agent(composed_name).await;
let session_count = roboticus_db::agents::list_session_counts_by_agent(&state.db)
.unwrap()
.get(composed_name)
.copied()
.unwrap_or(0);
let integrity = crate::api::routes::subagent_integrity::assess_subagent_integrity(
&row,
runtime.as_ref(),
session_count,
);
assert!(
integrity.has_fixed_skills,
"composed subagent must have fixed skills"
);
assert!(
integrity.runtime_registered && integrity.runtime_running,
"composed taskable subagent should be running in runtime registry"
);
assert_eq!(integrity.runtime_state, "running");
let retire = crate::api::routes::subagents::execute_retire_unused_subagents_tool(
&state,
0,
false,
Some(vec![composed_name.to_string()]),
)
.await
.unwrap();
let retired = retire
.get("retired")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
assert!(
retired.iter().any(|v| v.as_str() == Some(composed_name)),
"retirement should include composed subagent: {retire}"
);
let post = roboticus_db::agents::list_sub_agents(&state.db)
.unwrap()
.into_iter()
.find(|a| a.name == composed_name)
.expect("retired subagent row should still exist");
assert!(!post.enabled, "retirement should soft-disable subagent");
assert!(
state.registry.get_agent(composed_name).await.is_none(),
"retired subagent should be removed from runtime registry"
);
}
#[tokio::test]
async fn subagent_retirement_dry_run_reports_without_disabling() {
let state = crate::api::routes::tests::test_state();
let sid = roboticus_db::sessions::find_or_create(&state.db, "test-turn-agent", None).unwrap();
let turn_id =
roboticus_db::sessions::create_turn(&state.db, &sid, None, None, None, None).unwrap();
let known_skill = crate::api::routes::subagent_integrity::skill_registry_names(&state)
.into_iter()
.next()
.expect("test state should expose at least one registry skill");
let name = "dry-run-specialist";
super::execute_virtual_orchestration_tool(
&state,
"compose-subagent",
&serde_json::json!({
"name": name,
"description": "Validates retirement dry-run behavior",
"skills": [known_skill],
"model": "auto"
}),
&turn_id,
roboticus_core::InputAuthority::Creator,
roboticus_core::SurvivalTier::Normal,
)
.await
.unwrap();
let dry = crate::api::routes::subagents::execute_retire_unused_subagents_tool(
&state,
0,
true,
Some(vec![name.to_string()]),
)
.await
.unwrap();
assert_eq!(dry.get("dry_run").and_then(|v| v.as_bool()), Some(true));
let would_retire = dry
.get("would_retire")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
assert!(
would_retire.iter().any(|v| v.as_str() == Some(name)),
"dry run should report candidate in would_retire: {dry}"
);
let post = roboticus_db::agents::list_sub_agents(&state.db)
.unwrap()
.into_iter()
.find(|a| a.name == name)
.expect("subagent row should still exist after dry-run");
assert!(post.enabled, "dry-run must not disable subagent");
assert!(
state.registry.get_agent(name).await.is_some(),
"dry-run must not unregister runtime subagent"
);
}