use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::capability::AgentCapabilities;
use crate::process::Pid;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum AgentRole {
Root,
Supervisor,
Service,
Worker,
User,
Custom(String),
}
impl std::fmt::Display for AgentRole {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AgentRole::Root => write!(f, "root"),
AgentRole::Supervisor => write!(f, "supervisor"),
AgentRole::Service => write!(f, "service"),
AgentRole::Worker => write!(f, "worker"),
AgentRole::User => write!(f, "user"),
AgentRole::Custom(name) => write!(f, "custom({name})"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Agency {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_children: Option<usize>,
#[serde(default)]
pub allowed_roles: Vec<AgentRole>,
#[serde(default)]
pub capability_ceiling: AgentCapabilities,
#[serde(default)]
pub children: Vec<Pid>,
}
impl Agency {
pub fn root() -> Self {
Self {
max_children: None,
allowed_roles: vec![
AgentRole::Supervisor,
AgentRole::Service,
AgentRole::Worker,
AgentRole::User,
],
capability_ceiling: AgentCapabilities::root(),
children: Vec::new(),
}
}
pub fn supervisor(max_children: usize) -> Self {
Self {
max_children: Some(max_children),
allowed_roles: vec![AgentRole::Service, AgentRole::Worker],
capability_ceiling: AgentCapabilities::default(),
children: Vec::new(),
}
}
pub fn service(max_workers: usize) -> Self {
Self {
max_children: Some(max_workers),
allowed_roles: vec![AgentRole::Worker],
capability_ceiling: AgentCapabilities::default(),
children: Vec::new(),
}
}
pub fn none() -> Self {
Self {
max_children: Some(0),
allowed_roles: Vec::new(),
capability_ceiling: AgentCapabilities::default(),
children: Vec::new(),
}
}
pub fn can_spawn(&self, role: &AgentRole) -> bool {
if let Some(max) = self.max_children
&& self.children.len() >= max
{
return false;
}
self.allowed_roles.contains(role)
}
pub fn add_child(&mut self, pid: Pid) {
self.children.push(pid);
}
pub fn remove_child(&mut self, pid: Pid) {
self.children.retain(|&p| p != pid);
}
pub fn remaining_capacity(&self) -> Option<usize> {
self.max_children
.map(|max| max.saturating_sub(self.children.len()))
}
}
impl Default for Agency {
fn default() -> Self {
Self::none()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentManifest {
pub name: String,
#[serde(default = "default_version")]
pub version: String,
#[serde(default)]
pub description: String,
pub role: AgentRole,
#[serde(default)]
pub capabilities: AgentCapabilities,
#[serde(default)]
pub agency: Agency,
#[serde(default)]
pub tools: Vec<String>,
#[serde(default)]
pub topics_publish: Vec<String>,
#[serde(default)]
pub topics_subscribe: Vec<String>,
#[serde(default)]
pub resources: AgentResources,
#[serde(default)]
pub interface: AgentInterface,
#[serde(default)]
pub health: AgentHealth,
#[serde(default)]
pub dependencies: AgentDependencies,
#[serde(default)]
pub filesystem_access: Vec<String>,
#[serde(default)]
pub labels: HashMap<String, String>,
}
fn default_version() -> String {
"0.1.0".into()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentResources {
#[serde(default = "default_max_memory_mb")]
pub max_memory_mb: u64,
#[serde(default = "default_max_concurrent")]
pub max_concurrent_requests: u32,
#[serde(default)]
pub priority: AgentPriority,
}
fn default_max_memory_mb() -> u64 {
256
}
fn default_max_concurrent() -> u32 {
100
}
impl Default for AgentResources {
fn default() -> Self {
Self {
max_memory_mb: default_max_memory_mb(),
max_concurrent_requests: default_max_concurrent(),
priority: AgentPriority::default(),
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum AgentPriority {
Low,
#[default]
Normal,
High,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentInterface {
#[serde(default)]
pub protocol: InterfaceProtocol,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub request_topic: Option<String>,
#[serde(default)]
pub response_mode: ResponseMode,
}
impl Default for AgentInterface {
fn default() -> Self {
Self {
protocol: InterfaceProtocol::Ipc,
request_topic: None,
response_mode: ResponseMode::Direct,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum InterfaceProtocol {
#[default]
Ipc,
Rest,
Grpc,
Mcp,
}
#[non_exhaustive]
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum ResponseMode {
#[default]
Direct,
Broadcast,
Topic(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentHealth {
#[serde(default = "default_check_interval")]
pub check_interval_secs: u64,
#[serde(default = "default_timeout")]
pub timeout_secs: u64,
#[serde(default)]
pub restart_policy: AgentRestartPolicy,
#[serde(default = "default_max_restarts")]
pub max_restarts: u32,
}
fn default_check_interval() -> u64 {
30
}
fn default_timeout() -> u64 {
5
}
fn default_max_restarts() -> u32 {
5
}
impl Default for AgentHealth {
fn default() -> Self {
Self {
check_interval_secs: default_check_interval(),
timeout_secs: default_timeout(),
restart_policy: AgentRestartPolicy::default(),
max_restarts: default_max_restarts(),
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum AgentRestartPolicy {
Never,
#[default]
OnFailure,
Always,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AgentDependencies {
#[serde(default)]
pub requires: Vec<String>,
#[serde(default)]
pub after: Vec<String>,
}
impl AgentCapabilities {
pub fn root() -> Self {
Self {
can_spawn: true,
can_ipc: true,
can_exec_tools: true,
can_network: true,
ipc_scope: crate::capability::IpcScope::All,
resource_limits: crate::capability::ResourceLimits {
max_memory_bytes: u64::MAX,
max_cpu_time_ms: u64::MAX,
max_tool_calls: u64::MAX,
max_messages: u64::MAX,
#[cfg(feature = "os-patterns")]
max_disk_bytes: u64::MAX,
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn agent_role_display() {
assert_eq!(AgentRole::Root.to_string(), "root");
assert_eq!(AgentRole::Service.to_string(), "service");
assert_eq!(AgentRole::Worker.to_string(), "worker");
assert_eq!(
AgentRole::Custom("analytics".into()).to_string(),
"custom(analytics)"
);
}
#[test]
fn root_agency_unlimited() {
let agency = Agency::root();
assert!(agency.max_children.is_none());
assert!(agency.can_spawn(&AgentRole::Service));
assert!(agency.can_spawn(&AgentRole::Worker));
assert!(agency.can_spawn(&AgentRole::User));
assert!(!agency.can_spawn(&AgentRole::Root)); }
#[test]
fn supervisor_agency() {
let agency = Agency::supervisor(10);
assert_eq!(agency.max_children, Some(10));
assert!(agency.can_spawn(&AgentRole::Service));
assert!(agency.can_spawn(&AgentRole::Worker));
assert!(!agency.can_spawn(&AgentRole::Root));
assert!(!agency.can_spawn(&AgentRole::User));
}
#[test]
fn service_agency() {
let agency = Agency::service(5);
assert!(agency.can_spawn(&AgentRole::Worker));
assert!(!agency.can_spawn(&AgentRole::Service));
}
#[test]
fn worker_has_no_agency() {
let agency = Agency::none();
assert!(!agency.can_spawn(&AgentRole::Worker));
assert_eq!(agency.remaining_capacity(), Some(0));
}
#[test]
fn agency_child_tracking() {
let mut agency = Agency::service(2);
agency.add_child(100);
agency.add_child(101);
assert!(!agency.can_spawn(&AgentRole::Worker)); assert_eq!(agency.remaining_capacity(), Some(0));
agency.remove_child(100);
assert!(agency.can_spawn(&AgentRole::Worker));
assert_eq!(agency.remaining_capacity(), Some(1));
}
#[test]
fn agency_serde_roundtrip() {
let agency = Agency::supervisor(8);
let json = serde_json::to_string(&agency).unwrap();
let restored: Agency = serde_json::from_str(&json).unwrap();
assert_eq!(restored.max_children, Some(8));
assert!(restored.allowed_roles.contains(&AgentRole::Service));
}
#[test]
fn root_capabilities() {
let caps = AgentCapabilities::root();
assert!(caps.can_spawn);
assert!(caps.can_ipc);
assert!(caps.can_exec_tools);
assert!(caps.can_network);
assert_eq!(caps.resource_limits.max_memory_bytes, u64::MAX);
}
#[test]
fn agent_manifest_serde_roundtrip() {
let manifest = AgentManifest {
name: "memory-service".into(),
version: "0.1.0".into(),
description: "Persistent memory storage".into(),
role: AgentRole::Service,
capabilities: AgentCapabilities::default(),
agency: Agency::service(10),
tools: vec!["memory_store".into(), "memory_retrieve".into()],
topics_publish: vec!["memory.stored".into()],
topics_subscribe: vec!["memory.request".into()],
resources: AgentResources::default(),
interface: AgentInterface {
protocol: InterfaceProtocol::Ipc,
request_topic: Some("memory.request".into()),
response_mode: ResponseMode::Direct,
},
health: AgentHealth::default(),
dependencies: AgentDependencies {
requires: vec![],
after: vec!["kernel-init".into()],
},
filesystem_access: vec!["data/memory/".into()],
labels: HashMap::new(),
};
let json = serde_json::to_string_pretty(&manifest).unwrap();
let restored: AgentManifest = serde_json::from_str(&json).unwrap();
assert_eq!(restored.name, "memory-service");
assert_eq!(restored.role, AgentRole::Service);
assert_eq!(restored.tools.len(), 2);
}
#[test]
fn minimal_manifest_serde() {
let json = r#"{"name":"worker-1","role":"Worker"}"#;
let manifest: AgentManifest = serde_json::from_str(json).unwrap();
assert_eq!(manifest.name, "worker-1");
assert_eq!(manifest.role, AgentRole::Worker);
assert!(manifest.tools.is_empty());
assert_eq!(manifest.version, "0.1.0"); }
#[test]
fn agent_priority_ordering() {
assert!(AgentPriority::Low < AgentPriority::Normal);
assert!(AgentPriority::Normal < AgentPriority::High);
assert!(AgentPriority::High < AgentPriority::Critical);
}
#[test]
fn agent_health_defaults() {
let health = AgentHealth::default();
assert_eq!(health.check_interval_secs, 30);
assert_eq!(health.timeout_secs, 5);
assert_eq!(health.max_restarts, 5);
assert_eq!(health.restart_policy, AgentRestartPolicy::OnFailure);
}
#[test]
fn interface_protocol_default() {
assert_eq!(InterfaceProtocol::default(), InterfaceProtocol::Ipc);
}
#[test]
fn response_mode_serde() {
let modes = vec![
ResponseMode::Direct,
ResponseMode::Broadcast,
ResponseMode::Topic("events".into()),
];
for mode in &modes {
let json = serde_json::to_string(mode).unwrap();
let _restored: ResponseMode = serde_json::from_str(&json).unwrap();
}
}
#[test]
fn agent_dependencies_serde() {
let deps = AgentDependencies {
requires: vec!["message-bus".into()],
after: vec!["kernel-init".into(), "message-bus".into()],
};
let json = serde_json::to_string(&deps).unwrap();
let restored: AgentDependencies = serde_json::from_str(&json).unwrap();
assert_eq!(restored.requires, vec!["message-bus"]);
assert_eq!(restored.after.len(), 2);
}
#[test]
fn agent_resources_defaults() {
let res = AgentResources::default();
assert_eq!(res.max_memory_mb, 256);
assert_eq!(res.max_concurrent_requests, 100);
assert_eq!(res.priority, AgentPriority::Normal);
}
}