atomr-infer-cli 0.8.0

atomr-infer serve CLI binary. Boots the actor system, applies project-file [[deployment]] entries, and mounts the gateway.
Documentation
//! `atomr-infer serve` runtime — boot the actor system, register every
//! `[[deployment]]`, mount the gateway, wait for shutdown.

use std::sync::Arc;

use anyhow::Result;
use atomr_config::Config;
use atomr_core::actor::{ActorSystem, Props};

use atomr_infer_runtime::{
    spawn_gateway, DeploymentManagerActor, DeploymentManagerMsg, DpCoordinatorActor, GatewayConfig,
    MetricsActor,
};

use crate::config::ProjectFile;

pub async fn run_server(project: ProjectFile) -> Result<()> {
    let sys = ActorSystem::create(project.cluster.name.clone(), Config::reference())
        .await
        .map_err(|e| anyhow::anyhow!("create actor system: {e}"))?;
    tracing::info!(name = %sys.name(), "actor system started");

    // Cluster-wide singletons (in v0 — single-process — they're just
    // top-level actors; cluster registration is added when we wire up
    // `atomr_cluster_tools::ClusterSingletonManager`).
    let dp = sys
        .actor_of(Props::create(DpCoordinatorActor::new), "dp-coordinator")
        .map_err(|e| anyhow::anyhow!("spawn coordinator: {e}"))?;
    let mgr = sys
        .actor_of(
            Props::create(DeploymentManagerActor::default),
            "deployment-manager",
        )
        .map_err(|e| anyhow::anyhow!("spawn manager: {e}"))?;
    let _metrics = sys
        .actor_of(Props::create(MetricsActor::default), "metrics")
        .map_err(|e| anyhow::anyhow!("spawn metrics: {e}"))?;

    // Apply each deployment from the project file.
    for d in project.deployments {
        let name = d.name.clone();
        let (tx, rx) = tokio::sync::oneshot::channel();
        mgr.tell(DeploymentManagerMsg::Apply {
            deployment: d,
            reply: tx,
        });
        match rx.await {
            Ok(Ok(())) => tracing::info!(deployment = %name, "applied"),
            Ok(Err(e)) => tracing::error!(deployment = %name, ?e, "deployment validation failed"),
            Err(_) => tracing::error!(deployment = %name, "manager dropped reply"),
        }
    }

    // Auto-provision the local Gemma 4 deployment when the
    // `gemma-default` feature is on, the env probe passes, and the
    // operator hasn't disabled it via ATOMR_INFER_GEMMA_AUTO=skip.
    #[cfg(feature = "gemma-default")]
    {
        use atomr_infer::defaults::gemma;
        let cfg = gemma::GemmaDefaults::from_env();
        if cfg.auto_provision {
            match gemma::provision_if_ready(&mgr, &cfg).await {
                Ok(gemma::ProvisionOutcome::Ready { deployment_name }) => {
                    tracing::info!(deployment = %deployment_name, "local Gemma 4 provisioned");
                }
                Ok(gemma::ProvisionOutcome::Skipped { reason, hint }) => {
                    tracing::info!(reason = %reason, hint = %hint, "local Gemma 4 skipped");
                }
                Err(e) => {
                    tracing::warn!(?e, "local Gemma 4 auto-provision failed");
                }
            }
        }
    }

    // Mount the gateway.
    let gateway_cfg = GatewayConfig {
        bind: project.cluster.bind,
    };
    let _gateway = spawn_gateway(&sys, gateway_cfg, dp).map_err(|e| anyhow::anyhow!("spawn gateway: {e}"))?;
    tracing::info!(bind = %project.cluster.bind, "gateway mounted");

    // Block until ctrl-c.
    let _ = tokio::signal::ctrl_c().await;
    tracing::info!("shutdown requested");
    sys.terminate().await;
    let _ = Arc::new(()); // keep arc-swap dep referenced where applicable
    Ok(())
}