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