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