oxios_kernel/
agent_group.rs1use chrono::Utc;
11use oxios_ouroboros::Seed;
12use serde::{Deserialize, Serialize};
13use uuid::Uuid;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17pub enum OxiosAgentGroupStatus {
18 Pending,
20 Running,
22 Completed,
24 Failed,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct OxiosGroupAgent {
31 pub id: Uuid,
33 pub seed: Seed,
35 pub status: OxiosAgentGroupStatus,
37 pub result: Option<String>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct OxiosAgentGroup {
44 pub id: Uuid,
46 pub parent_seed_id: Uuid,
48 pub agents: Vec<OxiosGroupAgent>,
50}
51
52impl OxiosAgentGroup {
53 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 pub fn pending_agents(&self) -> Vec<&OxiosGroupAgent> {
89 self.agents
90 .iter()
91 .filter(|a| a.status == OxiosAgentGroupStatus::Pending)
92 .collect()
93 }
94
95 pub fn completed_agents(&self) -> Vec<&OxiosGroupAgent> {
97 self.agents
98 .iter()
99 .filter(|a| a.status == OxiosAgentGroupStatus::Completed)
100 .collect()
101 }
102
103 pub fn failed_agents(&self) -> Vec<&OxiosGroupAgent> {
105 self.agents
106 .iter()
107 .filter(|a| a.status == OxiosAgentGroupStatus::Failed)
108 .collect()
109 }
110
111 pub fn all_completed(&self) -> bool {
113 self.agents
114 .iter()
115 .all(|a| a.status == OxiosAgentGroupStatus::Completed)
116 }
117
118 pub fn any_failed(&self) -> bool {
120 self.agents
121 .iter()
122 .any(|a| a.status == OxiosAgentGroupStatus::Failed)
123 }
124
125 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 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}