Skip to main content

lago_core/
hive.rs

1//! Hive task aggregate — reconstructs hive state from events.
2
3use crate::event::EventEnvelope;
4use crate::id::SessionId;
5use aios_protocol::HiveTaskId;
6
7/// Aggregate view of a hive collaborative task, built from events.
8#[derive(Debug, Clone)]
9pub struct HiveTask {
10    pub hive_task_id: HiveTaskId,
11    pub objective: String,
12    pub agent_sessions: Vec<SessionId>,
13    pub current_generation: u32,
14    pub best_score: Option<f32>,
15    pub best_session_id: Option<SessionId>,
16    pub completed: bool,
17}
18
19impl HiveTask {
20    /// Reconstruct a HiveTask aggregate from a sequence of events.
21    ///
22    /// Scans for hive-related `EventKind` variants and builds the aggregate.
23    /// Returns `None` if no `HiveTaskCreated` event is found.
24    pub fn from_events(events: &[EventEnvelope]) -> Option<Self> {
25        let mut task: Option<Self> = None;
26
27        for envelope in events {
28            match &envelope.payload {
29                aios_protocol::EventKind::HiveTaskCreated {
30                    hive_task_id,
31                    objective,
32                    ..
33                } => {
34                    task = Some(Self {
35                        hive_task_id: hive_task_id.clone(),
36                        objective: objective.clone(),
37                        agent_sessions: Vec::new(),
38                        current_generation: 0,
39                        best_score: None,
40                        best_session_id: None,
41                        completed: false,
42                    });
43                }
44                aios_protocol::EventKind::HiveArtifactShared {
45                    source_session_id,
46                    score,
47                    ..
48                } => {
49                    if let Some(ref mut t) = task {
50                        let sid = SessionId::from_string(source_session_id.as_str());
51                        if !t.agent_sessions.iter().any(|s| s.as_str() == sid.as_str()) {
52                            t.agent_sessions.push(sid);
53                        }
54                        if t.best_score.is_none_or(|best| *score > best) {
55                            t.best_score = Some(*score);
56                            t.best_session_id =
57                                Some(SessionId::from_string(source_session_id.as_str()));
58                        }
59                    }
60                }
61                aios_protocol::EventKind::HiveSelectionMade {
62                    winning_session_id,
63                    winning_score,
64                    generation,
65                    ..
66                } => {
67                    if let Some(ref mut t) = task {
68                        t.current_generation = *generation;
69                        t.best_score = Some(*winning_score);
70                        t.best_session_id =
71                            Some(SessionId::from_string(winning_session_id.as_str()));
72                    }
73                }
74                aios_protocol::EventKind::HiveGenerationCompleted { generation, .. } => {
75                    if let Some(ref mut t) = task {
76                        t.current_generation = *generation;
77                    }
78                }
79                aios_protocol::EventKind::HiveTaskCompleted { .. } => {
80                    if let Some(ref mut t) = task {
81                        t.completed = true;
82                    }
83                }
84                _ => {}
85            }
86        }
87
88        task
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use crate::event::{EventEnvelope, EventPayload};
96    use crate::id::{BranchId, EventId};
97    use std::collections::HashMap;
98
99    fn make_envelope(payload: EventPayload) -> EventEnvelope {
100        EventEnvelope {
101            event_id: EventId::from_string("E1"),
102            session_id: SessionId::from_string("S1"),
103            branch_id: BranchId::from_string("main"),
104            run_id: None,
105            seq: 1,
106            timestamp: 100,
107            parent_id: None,
108            payload,
109            metadata: HashMap::new(),
110            schema_version: 1,
111        }
112    }
113
114    #[test]
115    fn from_events_empty() {
116        assert!(HiveTask::from_events(&[]).is_none());
117    }
118
119    #[test]
120    fn from_events_full_lifecycle() {
121        let events = vec![
122            make_envelope(EventPayload::HiveTaskCreated {
123                hive_task_id: HiveTaskId::from_string("H1"),
124                objective: "optimize".into(),
125                agent_count: 3,
126            }),
127            make_envelope(EventPayload::HiveArtifactShared {
128                hive_task_id: HiveTaskId::from_string("H1"),
129                source_session_id: aios_protocol::SessionId::from_string("SA"),
130                score: 0.8,
131                mutation_summary: "first try".into(),
132            }),
133            make_envelope(EventPayload::HiveArtifactShared {
134                hive_task_id: HiveTaskId::from_string("H1"),
135                source_session_id: aios_protocol::SessionId::from_string("SB"),
136                score: 0.9,
137                mutation_summary: "better".into(),
138            }),
139            make_envelope(EventPayload::HiveSelectionMade {
140                hive_task_id: HiveTaskId::from_string("H1"),
141                winning_session_id: aios_protocol::SessionId::from_string("SB"),
142                winning_score: 0.9,
143                generation: 1,
144            }),
145            make_envelope(EventPayload::HiveTaskCompleted {
146                hive_task_id: HiveTaskId::from_string("H1"),
147                total_generations: 1,
148                total_trials: 6,
149                final_score: 0.95,
150            }),
151        ];
152
153        let task = HiveTask::from_events(&events).unwrap();
154        assert_eq!(task.hive_task_id.as_str(), "H1");
155        assert_eq!(task.objective, "optimize");
156        assert_eq!(task.agent_sessions.len(), 2);
157        assert_eq!(task.current_generation, 1);
158        assert_eq!(task.best_score, Some(0.9));
159        assert_eq!(task.best_session_id.as_ref().unwrap().as_str(), "SB");
160        assert!(task.completed);
161    }
162
163    #[test]
164    fn from_events_no_task_created() {
165        let events = vec![make_envelope(EventPayload::HiveArtifactShared {
166            hive_task_id: HiveTaskId::from_string("H1"),
167            source_session_id: aios_protocol::SessionId::from_string("SA"),
168            score: 0.8,
169            mutation_summary: "orphan".into(),
170        })];
171        assert!(HiveTask::from_events(&events).is_none());
172    }
173}