roboticus-api 0.11.3

HTTP routes, WebSocket, auth, rate limiting, and dashboard for the Roboticus agent runtime
Documentation
#[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"
    );
}