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                    original_request: parent_seed.original_request.clone(),
69                    output_schema: None,
70                };
71                OxiosGroupAgent {
72                    id: child_seed.id,
73                    seed: child_seed,
74                    status: OxiosAgentGroupStatus::Pending,
75                    result: None,
76                }
77            })
78            .collect();
79
80        Self {
81            id: Uuid::new_v4(),
82            parent_seed_id: parent_seed.id,
83            agents,
84        }
85    }
86
87    /// Get all pending agents.
88    pub fn pending_agents(&self) -> Vec<&OxiosGroupAgent> {
89        self.agents
90            .iter()
91            .filter(|a| a.status == OxiosAgentGroupStatus::Pending)
92            .collect()
93    }
94
95    /// Get all completed agents.
96    pub fn completed_agents(&self) -> Vec<&OxiosGroupAgent> {
97        self.agents
98            .iter()
99            .filter(|a| a.status == OxiosAgentGroupStatus::Completed)
100            .collect()
101    }
102
103    /// Get all failed agents.
104    pub fn failed_agents(&self) -> Vec<&OxiosGroupAgent> {
105        self.agents
106            .iter()
107            .filter(|a| a.status == OxiosAgentGroupStatus::Failed)
108            .collect()
109    }
110
111    /// Check if all agents in the group have completed.
112    pub fn all_completed(&self) -> bool {
113        self.agents
114            .iter()
115            .all(|a| a.status == OxiosAgentGroupStatus::Completed)
116    }
117
118    /// Check if any agent has failed.
119    pub fn any_failed(&self) -> bool {
120        self.agents
121            .iter()
122            .any(|a| a.status == OxiosAgentGroupStatus::Failed)
123    }
124
125    /// Get completion percentage.
126    pub fn completion_pct(&self) -> f64 {
127        if self.agents.is_empty() {
128            return 0.0;
129        }
130        let completed = self
131            .agents
132            .iter()
133            .filter(|a| a.status == OxiosAgentGroupStatus::Completed)
134            .count();
135        completed as f64 / self.agents.len() as f64
136    }
137
138    /// Combine results from all completed agents.
139    pub fn combined_results(&self) -> String {
140        self.completed_agents()
141            .iter()
142            .filter_map(|a| a.result.as_ref())
143            .map(|r| r.as_str())
144            .collect::<Vec<_>>()
145            .join("\n\n")
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn test_group_new_splits_seed() {
155        let parent = Seed {
156            id: Uuid::new_v4(),
157            goal: "Test goal".into(),
158            constraints: vec!["constraint1".into()],
159            acceptance_criteria: vec!["Criterion".into()],
160            ontology: vec![],
161            created_at: Utc::now(),
162            generation: 0,
163            parent_seed_id: None,
164            cspace_hint: None,
165            original_request: String::new(),
166            output_schema: None,
167        };
168
169        let descriptions = vec!["subtask 1".into(), "subtask 2".into()];
170        let group = OxiosAgentGroup::new(&parent, descriptions);
171
172        assert_eq!(group.agents.len(), 2);
173        assert!(group.pending_agents().len() == 2);
174        assert!(!group.all_completed());
175        assert_eq!(group.parent_seed_id, parent.id);
176    }
177
178    #[test]
179    fn test_completion_pct_empty_group() {
180        let parent = Seed {
181            id: Uuid::new_v4(),
182            goal: "Test".into(),
183            constraints: vec![],
184            acceptance_criteria: vec![],
185            ontology: vec![],
186            created_at: Utc::now(),
187            generation: 0,
188            parent_seed_id: None,
189            cspace_hint: None,
190            original_request: String::new(),
191            output_schema: None,
192        };
193
194        let group = OxiosAgentGroup::new(&parent, vec![]);
195        assert!((group.completion_pct() - 0.0).abs() < f64::EPSILON);
196    }
197}