Skip to main content

oxios_kernel/
agent_group.rs

1//! Agent group types for oxios orchestration.
2//!
3//! oxios has its own `OxiosAgentGroup` struct for managing groups of agents
4//! spawned by the orchestrator (Seed splitting, state persistence, events).
5//!
6//! For multi-agent execution within a pipeline/parallel/orchestrated workflow,
7//! use the re-exports from oxi_sdk: `SdkAgentGroup`, `SdkGroupResult`.
8//! See `lib.rs` for oxi-sdk re-exports.
9
10use chrono::Utc;
11use oxios_ouroboros::Seed;
12use serde::{Deserialize, Serialize};
13use uuid::Uuid;
14
15/// Status of an agent within a group.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17pub enum OxiosAgentGroupStatus {
18    /// Agent is pending execution.
19    Pending,
20    /// Agent is currently running.
21    Running,
22    /// Agent completed successfully.
23    Completed,
24    /// Agent failed.
25    Failed,
26}
27
28/// A single agent's entry in a group.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct OxiosGroupAgent {
31    /// Unique ID for this group agent.
32    pub id: Uuid,
33    /// The child seed this agent executes.
34    pub seed: Seed,
35    /// Current status.
36    pub status: OxiosAgentGroupStatus,
37    /// Result output (when completed).
38    pub result: Option<String>,
39}
40
41/// A group of agents executing in parallel.
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct OxiosAgentGroup {
44    /// Unique group ID.
45    pub id: Uuid,
46    /// The parent seed that spawned this group.
47    pub parent_seed_id: Uuid,
48    /// Agents in this group.
49    pub agents: Vec<OxiosGroupAgent>,
50}
51
52impl OxiosAgentGroup {
53    /// Create a new agent group by splitting a parent seed into subtasks.
54    pub fn new(parent_seed: &Seed, subtask_descriptions: Vec<String>) -> Self {
55        let agents = subtask_descriptions
56            .into_iter()
57            .map(|desc| {
58                let child_seed = Seed {
59                    id: Uuid::new_v4(),
60                    goal: desc,
61                    constraints: parent_seed.constraints.clone(),
62                    acceptance_criteria: vec!["Task completes successfully".into()],
63                    ontology: parent_seed.ontology.clone(),
64                    created_at: Utc::now(),
65                    generation: parent_seed.generation + 1,
66                    parent_seed_id: Some(parent_seed.id),
67                    cspace_hint: parent_seed.cspace_hint.clone(),
68                };
69                OxiosGroupAgent {
70                    id: child_seed.id,
71                    seed: child_seed,
72                    status: OxiosAgentGroupStatus::Pending,
73                    result: None,
74                }
75            })
76            .collect();
77
78        Self {
79            id: Uuid::new_v4(),
80            parent_seed_id: parent_seed.id,
81            agents,
82        }
83    }
84
85    /// Get all pending agents.
86    pub fn pending_agents(&self) -> Vec<&OxiosGroupAgent> {
87        self.agents
88            .iter()
89            .filter(|a| a.status == OxiosAgentGroupStatus::Pending)
90            .collect()
91    }
92
93    /// Get all completed agents.
94    pub fn completed_agents(&self) -> Vec<&OxiosGroupAgent> {
95        self.agents
96            .iter()
97            .filter(|a| a.status == OxiosAgentGroupStatus::Completed)
98            .collect()
99    }
100
101    /// Get all failed agents.
102    pub fn failed_agents(&self) -> Vec<&OxiosGroupAgent> {
103        self.agents
104            .iter()
105            .filter(|a| a.status == OxiosAgentGroupStatus::Failed)
106            .collect()
107    }
108
109    /// Check if all agents in the group have completed.
110    pub fn all_completed(&self) -> bool {
111        self.agents
112            .iter()
113            .all(|a| a.status == OxiosAgentGroupStatus::Completed)
114    }
115
116    /// Check if any agent has failed.
117    pub fn any_failed(&self) -> bool {
118        self.agents
119            .iter()
120            .any(|a| a.status == OxiosAgentGroupStatus::Failed)
121    }
122
123    /// Get completion percentage.
124    pub fn completion_pct(&self) -> f64 {
125        if self.agents.is_empty() {
126            return 0.0;
127        }
128        let completed = self
129            .agents
130            .iter()
131            .filter(|a| a.status == OxiosAgentGroupStatus::Completed)
132            .count();
133        completed as f64 / self.agents.len() as f64
134    }
135
136    /// Combine results from all completed agents.
137    pub fn combined_results(&self) -> String {
138        self.completed_agents()
139            .iter()
140            .filter_map(|a| a.result.as_ref())
141            .map(|r| r.as_str())
142            .collect::<Vec<_>>()
143            .join("\n\n")
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn test_group_new_splits_seed() {
153        let parent = Seed {
154            id: Uuid::new_v4(),
155            goal: "Test goal".into(),
156            constraints: vec!["constraint1".into()],
157            acceptance_criteria: vec!["Criterion".into()],
158            ontology: vec![],
159            created_at: Utc::now(),
160            generation: 0,
161            parent_seed_id: None,
162            cspace_hint: None,
163        };
164
165        let descriptions = vec!["subtask 1".into(), "subtask 2".into()];
166        let group = OxiosAgentGroup::new(&parent, descriptions);
167
168        assert_eq!(group.agents.len(), 2);
169        assert!(group.pending_agents().len() == 2);
170        assert!(!group.all_completed());
171        assert_eq!(group.parent_seed_id, parent.id);
172    }
173
174    #[test]
175    fn test_completion_pct_empty_group() {
176        let parent = Seed {
177            id: Uuid::new_v4(),
178            goal: "Test".into(),
179            constraints: vec![],
180            acceptance_criteria: vec![],
181            ontology: vec![],
182            created_at: Utc::now(),
183            generation: 0,
184            parent_seed_id: None,
185            cspace_hint: None,
186        };
187
188        let group = OxiosAgentGroup::new(&parent, vec![]);
189        assert!((group.completion_pct() - 0.0).abs() < f64::EPSILON);
190    }
191}