mod container;
mod local;
mod memory;
pub use container::ContainerRuntime;
pub use local::LocalProcessRuntime;
pub use memory::InMemoryAgentRuntime;
use std::path::PathBuf;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::core::AgentConfig;
use crate::registry::AgentId;
#[derive(Debug, Clone)]
pub struct AgentSpec {
pub id: AgentId,
pub config_path: PathBuf,
pub endpoint: String,
}
impl AgentSpec {
pub fn from_config_path(path: impl Into<PathBuf>) -> Result<Self, RuntimeError> {
let config_path = path.into();
let config = AgentConfig::from_file(&config_path)
.map_err(|e| RuntimeError::Config(e.to_string()))?;
Ok(Self {
id: AgentId::from_name(&config.agent.name),
endpoint: config.agent_url(),
config_path,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum RuntimeHealth {
Provisioned,
Healthy,
Unhealthy,
Stopped,
}
#[derive(Debug, Clone)]
pub struct RuntimeStatus {
pub id: AgentId,
pub health: RuntimeHealth,
pub endpoint: String,
}
#[derive(Debug, Error)]
pub enum RuntimeError {
#[error("no agent provisioned with id '{0}'")]
NotFound(AgentId),
#[error("agent '{0}' is already running")]
AlreadyRunning(AgentId),
#[error("failed to spawn agent '{id}': {source}")]
Spawn {
id: AgentId,
#[source]
source: std::io::Error,
},
#[error("invalid agent config: {0}")]
Config(String),
#[error("runtime backend error: {0}")]
Backend(String),
}
#[async_trait]
pub trait AgentRuntime: Send + Sync {
async fn provision(&self, spec: AgentSpec) -> Result<AgentId, RuntimeError>;
async fn start(&self, id: &AgentId) -> Result<(), RuntimeError>;
async fn stop(&self, id: &AgentId) -> Result<(), RuntimeError>;
async fn health(&self, id: &AgentId) -> Result<RuntimeHealth, RuntimeError>;
async fn list(&self) -> Result<Vec<RuntimeStatus>, RuntimeError>;
}