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