1use std::collections::HashMap;
16#[cfg(feature = "exochain")]
17use std::sync::Arc;
18
19use serde::{Deserialize, Serialize};
20
21use crate::capability::AgentCapabilities;
22use crate::process::Pid;
23
24#[non_exhaustive]
26#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
27pub enum AgentRole {
28 Root,
30 Supervisor,
32 Service,
34 Worker,
36 User,
38 Custom(String),
40}
41
42impl std::fmt::Display for AgentRole {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 match self {
45 AgentRole::Root => write!(f, "root"),
46 AgentRole::Supervisor => write!(f, "supervisor"),
47 AgentRole::Service => write!(f, "service"),
48 AgentRole::Worker => write!(f, "worker"),
49 AgentRole::User => write!(f, "user"),
50 AgentRole::Custom(name) => write!(f, "custom({name})"),
51 }
52 }
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct Agency {
62 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub max_children: Option<usize>,
66
67 #[serde(default)]
69 pub allowed_roles: Vec<AgentRole>,
70
71 #[serde(default)]
73 pub capability_ceiling: AgentCapabilities,
74
75 #[serde(default)]
77 pub children: Vec<Pid>,
78
79 #[cfg(feature = "exochain")]
81 #[serde(skip)]
82 pub chain_manager: Option<Arc<crate::chain::ChainManager>>,
83}
84
85impl Agency {
86 pub fn root() -> Self {
88 Self {
89 max_children: None,
90 allowed_roles: vec![
91 AgentRole::Supervisor,
92 AgentRole::Service,
93 AgentRole::Worker,
94 AgentRole::User,
95 ],
96 capability_ceiling: AgentCapabilities::root(),
97 children: Vec::new(),
98 #[cfg(feature = "exochain")]
99 chain_manager: None,
100 }
101 }
102
103 pub fn supervisor(max_children: usize) -> Self {
105 Self {
106 max_children: Some(max_children),
107 allowed_roles: vec![AgentRole::Service, AgentRole::Worker],
108 capability_ceiling: AgentCapabilities::default(),
109 children: Vec::new(),
110 #[cfg(feature = "exochain")]
111 chain_manager: None,
112 }
113 }
114
115 pub fn service(max_workers: usize) -> Self {
117 Self {
118 max_children: Some(max_workers),
119 allowed_roles: vec![AgentRole::Worker],
120 capability_ceiling: AgentCapabilities::default(),
121 children: Vec::new(),
122 #[cfg(feature = "exochain")]
123 chain_manager: None,
124 }
125 }
126
127 pub fn none() -> Self {
129 Self {
130 max_children: Some(0),
131 allowed_roles: Vec::new(),
132 capability_ceiling: AgentCapabilities::default(),
133 children: Vec::new(),
134 #[cfg(feature = "exochain")]
135 chain_manager: None,
136 }
137 }
138
139 #[cfg(feature = "exochain")]
141 pub fn set_chain_manager(&mut self, cm: Arc<crate::chain::ChainManager>) {
142 self.chain_manager = Some(cm);
143 }
144
145 pub fn can_spawn(&self, role: &AgentRole) -> bool {
147 if let Some(max) = self.max_children
148 && self.children.len() >= max
149 {
150 return false;
151 }
152 self.allowed_roles.contains(role)
153 }
154
155 pub fn add_child(&mut self, pid: Pid) {
157 #[cfg(feature = "exochain")]
158 if let Some(ref cm) = self.chain_manager {
159 cm.append(
160 "agency",
161 crate::chain::EVENT_KIND_AGENT_HIERARCHY_ADD,
162 Some(serde_json::json!({
163 "child_pid": pid,
164 "current_children": self.children.len(),
165 })),
166 );
167 }
168
169 self.children.push(pid);
170 }
171
172 pub fn remove_child(&mut self, pid: Pid) {
174 #[cfg(feature = "exochain")]
175 if let Some(ref cm) = self.chain_manager {
176 cm.append(
177 "agency",
178 crate::chain::EVENT_KIND_AGENT_HIERARCHY_REMOVE,
179 Some(serde_json::json!({
180 "child_pid": pid,
181 "current_children": self.children.len(),
182 })),
183 );
184 }
185
186 self.children.retain(|&p| p != pid);
187 }
188
189 pub fn remaining_capacity(&self) -> Option<usize> {
191 self.max_children
192 .map(|max| max.saturating_sub(self.children.len()))
193 }
194}
195
196impl Default for Agency {
197 fn default() -> Self {
198 Self::none()
199 }
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct AgentManifest {
209 pub name: String,
211
212 #[serde(default = "default_version")]
214 pub version: String,
215
216 #[serde(default)]
218 pub description: String,
219
220 pub role: AgentRole,
222
223 #[serde(default)]
225 pub capabilities: AgentCapabilities,
226
227 #[serde(default)]
229 pub agency: Agency,
230
231 #[serde(default)]
233 pub tools: Vec<String>,
234
235 #[serde(default)]
237 pub topics_publish: Vec<String>,
238
239 #[serde(default)]
241 pub topics_subscribe: Vec<String>,
242
243 #[serde(default)]
245 pub resources: AgentResources,
246
247 #[serde(default)]
249 pub interface: AgentInterface,
250
251 #[serde(default)]
253 pub health: AgentHealth,
254
255 #[serde(default)]
257 pub dependencies: AgentDependencies,
258
259 #[serde(default)]
261 pub filesystem_access: Vec<String>,
262
263 #[serde(default)]
265 pub labels: HashMap<String, String>,
266}
267
268fn default_version() -> String {
269 "0.1.0".into()
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct AgentResources {
275 #[serde(default = "default_max_memory_mb")]
277 pub max_memory_mb: u64,
278
279 #[serde(default = "default_max_concurrent")]
281 pub max_concurrent_requests: u32,
282
283 #[serde(default)]
285 pub priority: AgentPriority,
286}
287
288fn default_max_memory_mb() -> u64 {
289 256
290}
291
292fn default_max_concurrent() -> u32 {
293 100
294}
295
296impl Default for AgentResources {
297 fn default() -> Self {
298 Self {
299 max_memory_mb: default_max_memory_mb(),
300 max_concurrent_requests: default_max_concurrent(),
301 priority: AgentPriority::default(),
302 }
303 }
304}
305
306#[non_exhaustive]
308#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
309pub enum AgentPriority {
310 Low,
312 #[default]
314 Normal,
315 High,
317 Critical,
319}
320
321#[derive(Debug, Clone, Serialize, Deserialize)]
323pub struct AgentInterface {
324 #[serde(default)]
326 pub protocol: InterfaceProtocol,
327
328 #[serde(default, skip_serializing_if = "Option::is_none")]
330 pub request_topic: Option<String>,
331
332 #[serde(default)]
334 pub response_mode: ResponseMode,
335}
336
337impl Default for AgentInterface {
338 fn default() -> Self {
339 Self {
340 protocol: InterfaceProtocol::Ipc,
341 request_topic: None,
342 response_mode: ResponseMode::Direct,
343 }
344 }
345}
346
347#[non_exhaustive]
349#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
350pub enum InterfaceProtocol {
351 #[default]
353 Ipc,
354 Rest,
356 Grpc,
358 Mcp,
360}
361
362#[non_exhaustive]
364#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
365pub enum ResponseMode {
366 #[default]
368 Direct,
369 Broadcast,
371 Topic(String),
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct AgentHealth {
378 #[serde(default = "default_check_interval")]
380 pub check_interval_secs: u64,
381
382 #[serde(default = "default_timeout")]
384 pub timeout_secs: u64,
385
386 #[serde(default)]
388 pub restart_policy: AgentRestartPolicy,
389
390 #[serde(default = "default_max_restarts")]
392 pub max_restarts: u32,
393}
394
395fn default_check_interval() -> u64 {
396 30
397}
398
399fn default_timeout() -> u64 {
400 5
401}
402
403fn default_max_restarts() -> u32 {
404 5
405}
406
407impl Default for AgentHealth {
408 fn default() -> Self {
409 Self {
410 check_interval_secs: default_check_interval(),
411 timeout_secs: default_timeout(),
412 restart_policy: AgentRestartPolicy::default(),
413 max_restarts: default_max_restarts(),
414 }
415 }
416}
417
418#[non_exhaustive]
420#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
421pub enum AgentRestartPolicy {
422 Never,
424 #[default]
426 OnFailure,
427 Always,
429}
430
431#[derive(Debug, Clone, Default, Serialize, Deserialize)]
433pub struct AgentDependencies {
434 #[serde(default)]
436 pub requires: Vec<String>,
437
438 #[serde(default)]
440 pub after: Vec<String>,
441}
442
443impl AgentCapabilities {
444 pub fn root() -> Self {
446 Self {
447 can_spawn: true,
448 can_ipc: true,
449 can_exec_tools: true,
450 can_network: true,
451 ipc_scope: crate::capability::IpcScope::All,
452 resource_limits: crate::capability::ResourceLimits {
453 max_memory_bytes: u64::MAX,
454 max_cpu_time_ms: u64::MAX,
455 max_tool_calls: u64::MAX,
456 max_messages: u64::MAX,
457 #[cfg(feature = "os-patterns")]
458 max_disk_bytes: u64::MAX,
459 },
460 }
461 }
462}
463
464#[cfg(test)]
465mod tests {
466 use super::*;
467
468 #[test]
469 fn agent_role_display() {
470 assert_eq!(AgentRole::Root.to_string(), "root");
471 assert_eq!(AgentRole::Service.to_string(), "service");
472 assert_eq!(AgentRole::Worker.to_string(), "worker");
473 assert_eq!(
474 AgentRole::Custom("analytics".into()).to_string(),
475 "custom(analytics)"
476 );
477 }
478
479 #[test]
480 fn root_agency_unlimited() {
481 let agency = Agency::root();
482 assert!(agency.max_children.is_none());
483 assert!(agency.can_spawn(&AgentRole::Service));
484 assert!(agency.can_spawn(&AgentRole::Worker));
485 assert!(agency.can_spawn(&AgentRole::User));
486 assert!(!agency.can_spawn(&AgentRole::Root)); }
488
489 #[test]
490 fn supervisor_agency() {
491 let agency = Agency::supervisor(10);
492 assert_eq!(agency.max_children, Some(10));
493 assert!(agency.can_spawn(&AgentRole::Service));
494 assert!(agency.can_spawn(&AgentRole::Worker));
495 assert!(!agency.can_spawn(&AgentRole::Root));
496 assert!(!agency.can_spawn(&AgentRole::User));
497 }
498
499 #[test]
500 fn service_agency() {
501 let agency = Agency::service(5);
502 assert!(agency.can_spawn(&AgentRole::Worker));
503 assert!(!agency.can_spawn(&AgentRole::Service));
504 }
505
506 #[test]
507 fn worker_has_no_agency() {
508 let agency = Agency::none();
509 assert!(!agency.can_spawn(&AgentRole::Worker));
510 assert_eq!(agency.remaining_capacity(), Some(0));
511 }
512
513 #[test]
514 fn agency_child_tracking() {
515 let mut agency = Agency::service(2);
516 agency.add_child(100);
517 agency.add_child(101);
518 assert!(!agency.can_spawn(&AgentRole::Worker)); assert_eq!(agency.remaining_capacity(), Some(0));
520
521 agency.remove_child(100);
522 assert!(agency.can_spawn(&AgentRole::Worker));
523 assert_eq!(agency.remaining_capacity(), Some(1));
524 }
525
526 #[test]
527 fn agency_serde_roundtrip() {
528 let agency = Agency::supervisor(8);
529 let json = serde_json::to_string(&agency).unwrap();
530 let restored: Agency = serde_json::from_str(&json).unwrap();
531 assert_eq!(restored.max_children, Some(8));
532 assert!(restored.allowed_roles.contains(&AgentRole::Service));
533 }
534
535 #[test]
536 fn root_capabilities() {
537 let caps = AgentCapabilities::root();
538 assert!(caps.can_spawn);
539 assert!(caps.can_ipc);
540 assert!(caps.can_exec_tools);
541 assert!(caps.can_network);
542 assert_eq!(caps.resource_limits.max_memory_bytes, u64::MAX);
543 }
544
545 #[test]
546 fn agent_manifest_serde_roundtrip() {
547 let manifest = AgentManifest {
548 name: "memory-service".into(),
549 version: "0.1.0".into(),
550 description: "Persistent memory storage".into(),
551 role: AgentRole::Service,
552 capabilities: AgentCapabilities::default(),
553 agency: Agency::service(10),
554 tools: vec!["memory_store".into(), "memory_retrieve".into()],
555 topics_publish: vec!["memory.stored".into()],
556 topics_subscribe: vec!["memory.request".into()],
557 resources: AgentResources::default(),
558 interface: AgentInterface {
559 protocol: InterfaceProtocol::Ipc,
560 request_topic: Some("memory.request".into()),
561 response_mode: ResponseMode::Direct,
562 },
563 health: AgentHealth::default(),
564 dependencies: AgentDependencies {
565 requires: vec![],
566 after: vec!["kernel-init".into()],
567 },
568 filesystem_access: vec!["data/memory/".into()],
569 labels: HashMap::new(),
570 };
571 let json = serde_json::to_string_pretty(&manifest).unwrap();
572 let restored: AgentManifest = serde_json::from_str(&json).unwrap();
573 assert_eq!(restored.name, "memory-service");
574 assert_eq!(restored.role, AgentRole::Service);
575 assert_eq!(restored.tools.len(), 2);
576 }
577
578 #[test]
579 fn minimal_manifest_serde() {
580 let json = r#"{"name":"worker-1","role":"Worker"}"#;
581 let manifest: AgentManifest = serde_json::from_str(json).unwrap();
582 assert_eq!(manifest.name, "worker-1");
583 assert_eq!(manifest.role, AgentRole::Worker);
584 assert!(manifest.tools.is_empty());
585 assert_eq!(manifest.version, "0.1.0"); }
587
588 #[test]
589 fn agent_priority_ordering() {
590 assert!(AgentPriority::Low < AgentPriority::Normal);
591 assert!(AgentPriority::Normal < AgentPriority::High);
592 assert!(AgentPriority::High < AgentPriority::Critical);
593 }
594
595 #[test]
596 fn agent_health_defaults() {
597 let health = AgentHealth::default();
598 assert_eq!(health.check_interval_secs, 30);
599 assert_eq!(health.timeout_secs, 5);
600 assert_eq!(health.max_restarts, 5);
601 assert_eq!(health.restart_policy, AgentRestartPolicy::OnFailure);
602 }
603
604 #[test]
605 fn interface_protocol_default() {
606 assert_eq!(InterfaceProtocol::default(), InterfaceProtocol::Ipc);
607 }
608
609 #[test]
610 fn response_mode_serde() {
611 let modes = vec![
612 ResponseMode::Direct,
613 ResponseMode::Broadcast,
614 ResponseMode::Topic("events".into()),
615 ];
616 for mode in &modes {
617 let json = serde_json::to_string(mode).unwrap();
618 let _restored: ResponseMode = serde_json::from_str(&json).unwrap();
619 }
620 }
621
622 #[test]
623 fn agent_dependencies_serde() {
624 let deps = AgentDependencies {
625 requires: vec!["message-bus".into()],
626 after: vec!["kernel-init".into(), "message-bus".into()],
627 };
628 let json = serde_json::to_string(&deps).unwrap();
629 let restored: AgentDependencies = serde_json::from_str(&json).unwrap();
630 assert_eq!(restored.requires, vec!["message-bus"]);
631 assert_eq!(restored.after.len(), 2);
632 }
633
634 #[test]
635 fn agent_resources_defaults() {
636 let res = AgentResources::default();
637 assert_eq!(res.max_memory_mb, 256);
638 assert_eq!(res.max_concurrent_requests, 100);
639 assert_eq!(res.priority, AgentPriority::Normal);
640 }
641}