use chrono::{DateTime, Utc};
use tokio::sync::broadcast;
use tokio_util::sync::CancellationToken;
use uuid::Uuid;
use bamboo_agent_core::AgentEvent;
fn subagent_lifecycle_child_id(event: &AgentEvent) -> Option<&str> {
match event {
AgentEvent::SubAgentStarted {
child_session_id, ..
}
| AgentEvent::SubAgentCompleted {
child_session_id, ..
} => Some(child_session_id),
_ => None,
}
}
#[derive(Debug, Clone)]
pub enum AgentStatus {
Pending,
Running,
Completed,
Cancelled,
Error(String),
}
#[derive(Debug, Clone)]
pub struct AgentRunner {
pub event_sender: broadcast::Sender<AgentEvent>,
pub cancel_token: CancellationToken,
pub status: AgentStatus,
pub started_at: DateTime<Utc>,
pub completed_at: Option<DateTime<Utc>>,
pub last_budget_event: Option<AgentEvent>,
pub last_critical_events: Vec<AgentEvent>,
pub last_tool_name: Option<String>,
pub last_tool_phase: Option<String>,
pub last_event_at: Option<DateTime<Utc>>,
pub round_count: u32,
pub run_id: String,
}
impl Default for AgentRunner {
fn default() -> Self {
Self::new()
}
}
impl AgentRunner {
pub const EVENT_CHANNEL_CAPACITY: usize = 1000;
pub const CRITICAL_EVENTS_CAPACITY: usize = 32;
pub fn new() -> Self {
let (event_sender, _) = broadcast::channel(Self::EVENT_CHANNEL_CAPACITY);
Self {
event_sender,
cancel_token: CancellationToken::new(),
status: AgentStatus::Pending,
started_at: Utc::now(),
completed_at: None,
last_budget_event: None,
last_critical_events: Vec::new(),
last_tool_name: None,
last_tool_phase: None,
last_event_at: None,
round_count: 0,
run_id: Uuid::new_v4().to_string(),
}
}
pub fn push_critical_event(&mut self, event: AgentEvent) {
let lifecycle_id = match &event {
AgentEvent::WorkflowActivated { event_id, .. }
| AgentEvent::WorkflowDeactivated { event_id, .. } => Some(event_id),
_ => None,
};
if lifecycle_id.is_some_and(|event_id| {
self.last_critical_events
.iter()
.any(|existing| match existing {
AgentEvent::WorkflowActivated {
event_id: existing_id,
..
}
| AgentEvent::WorkflowDeactivated {
event_id: existing_id,
..
} => existing_id == event_id,
_ => false,
})
}) {
return;
}
if let Some(child_session_id) = subagent_lifecycle_child_id(&event) {
self.last_critical_events
.retain(|existing| subagent_lifecycle_child_id(existing) != Some(child_session_id));
}
if self.last_critical_events.len() >= Self::CRITICAL_EVENTS_CAPACITY {
self.last_critical_events.remove(0);
}
self.last_critical_events.push(event);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn started(child_session_id: &str, title: &str) -> AgentEvent {
AgentEvent::SubAgentStarted {
parent_session_id: "parent".to_string(),
child_session_id: child_session_id.to_string(),
title: Some(title.to_string()),
}
}
fn completed(child_session_id: &str, status: &str) -> AgentEvent {
AgentEvent::SubAgentCompleted {
parent_session_id: "parent".to_string(),
child_session_id: child_session_id.to_string(),
status: status.to_string(),
error: None,
}
}
#[test]
fn critical_replay_keeps_only_latest_generation_state_per_child() {
let mut runner = AgentRunner::new();
for event in [
started("resident", "generation-1"),
completed("resident", "completed"),
started("resident", "generation-2"),
completed("resident", "completed"),
started("resident", "generation-3"),
] {
runner.push_critical_event(event);
}
assert_eq!(runner.last_critical_events.len(), 1);
assert!(matches!(
&runner.last_critical_events[0],
AgentEvent::SubAgentStarted {
child_session_id,
title: Some(title),
..
} if child_session_id == "resident" && title == "generation-3"
));
runner.push_critical_event(completed("resident", "completed"));
assert_eq!(runner.last_critical_events.len(), 1);
assert!(matches!(
&runner.last_critical_events[0],
AgentEvent::SubAgentCompleted {
child_session_id,
status,
..
} if child_session_id == "resident" && status == "completed"
));
}
#[test]
fn subagent_coalescing_preserves_other_children_and_recency() {
let mut runner = AgentRunner::new();
runner.push_critical_event(started("child-a", "a1"));
runner.push_critical_event(started("child-b", "b1"));
runner.push_critical_event(completed("child-a", "completed"));
assert_eq!(runner.last_critical_events.len(), 2);
assert!(matches!(
&runner.last_critical_events[0],
AgentEvent::SubAgentStarted {
child_session_id, ..
} if child_session_id == "child-b"
));
assert!(matches!(
&runner.last_critical_events[1],
AgentEvent::SubAgentCompleted {
child_session_id, ..
} if child_session_id == "child-a"
));
}
}