Skip to main content

bamboo_engine/runtime/execution/
runner_state.rs

1//! Runner state types for background agent execution.
2//!
3//! Provides the `AgentRunner` and `AgentStatus` types that track the lifecycle
4//! of an in-progress agent execution. These are used by the execution
5//! orchestration layer across all background paths (HTTP execute, spawn, schedule).
6
7use chrono::{DateTime, Utc};
8use tokio::sync::broadcast;
9use tokio_util::sync::CancellationToken;
10use uuid::Uuid;
11
12use bamboo_agent_core::AgentEvent;
13
14/// Status of an agent execution runner.
15///
16/// Represents the lifecycle state of an agent run from initialization
17/// through completion or error.
18#[derive(Debug, Clone)]
19pub enum AgentStatus {
20    /// Agent is initialized but not yet running.
21    Pending,
22
23    /// Agent is currently executing.
24    Running,
25
26    /// Agent completed successfully.
27    Completed,
28
29    /// Agent execution was cancelled by user.
30    Cancelled,
31
32    /// Agent execution failed with an error message.
33    Error(String),
34}
35
36/// Runner that manages agent execution for a session.
37///
38/// Each active agent run has an associated `AgentRunner` that coordinates
39/// event broadcasting, cancellation, and status tracking.
40///
41/// # Event Broadcasting
42///
43/// Uses a broadcast channel to support multiple subscribers watching
44/// the same agent run simultaneously.
45///
46/// # Cancellation
47///
48/// Provides a cancellation token that can be used to gracefully stop
49/// an in-progress agent execution.
50#[derive(Debug, Clone)]
51pub struct AgentRunner {
52    /// Broadcast sender for agent events.
53    ///
54    /// Allows multiple clients to subscribe to agent events
55    /// via `event_sender.subscribe()`.
56    pub event_sender: broadcast::Sender<AgentEvent>,
57
58    /// Cancellation token for graceful shutdown.
59    ///
60    /// When triggered, the agent should stop execution at the
61    /// next safe point.
62    pub cancel_token: CancellationToken,
63
64    /// Current status of the agent run.
65    pub status: AgentStatus,
66
67    /// Timestamp when the run was started.
68    pub started_at: DateTime<Utc>,
69
70    /// Timestamp when the run completed (if finished).
71    pub completed_at: Option<DateTime<Utc>>,
72
73    /// Last token budget event to replay for new subscribers.
74    ///
75    /// When a new client subscribes to an ongoing run, this
76    /// allows them to receive the most recent token usage info.
77    pub last_budget_event: Option<AgentEvent>,
78
79    /// Small ring of critical state events (TaskListUpdated, SubSession*, etc.)
80    /// cached for replay to late/reconnecting subscribers.
81    ///
82    /// Bounded to [`CRITICAL_EVENTS_CAPACITY`] entries; oldest are evicted.
83    pub last_critical_events: Vec<AgentEvent>,
84
85    /// Name of the most recently executed tool (if any).
86    /// Updated live during execution for diagnostic visibility.
87    pub last_tool_name: Option<String>,
88
89    /// Phase of the most recently executed tool: "begin", "finished", or "error".
90    /// Updated live during execution for diagnostic visibility.
91    pub last_tool_phase: Option<String>,
92
93    /// Timestamp of the last event received during this run.
94    /// Updated live during execution for liveness checks.
95    pub last_event_at: Option<DateTime<Utc>>,
96
97    /// Number of completed rounds (turns) so far.
98    /// Updated live during execution for progress tracking.
99    pub round_count: u32,
100
101    /// Unique identifier for this execution run.
102    /// Generated fresh for every `try_reserve_runner` call so that
103    /// frontend SSE events can be matched to the correct run even
104    /// across reconnects.
105    pub run_id: String,
106}
107
108impl Default for AgentRunner {
109    fn default() -> Self {
110        Self::new()
111    }
112}
113
114impl AgentRunner {
115    /// Broadcast channel capacity for agent events.
116    pub const EVENT_CHANNEL_CAPACITY: usize = 1000;
117
118    /// Maximum number of critical events cached for late-subscriber replay.
119    pub const CRITICAL_EVENTS_CAPACITY: usize = 32;
120
121    /// Create a new agent runner with default settings.
122    ///
123    /// Initializes a broadcast channel, a fresh cancellation token,
124    /// and `Pending` status.
125    pub fn new() -> Self {
126        let (event_sender, _) = broadcast::channel(Self::EVENT_CHANNEL_CAPACITY);
127        Self {
128            event_sender,
129            cancel_token: CancellationToken::new(),
130            status: AgentStatus::Pending,
131            started_at: Utc::now(),
132            completed_at: None,
133            last_budget_event: None,
134            last_critical_events: Vec::new(),
135            last_tool_name: None,
136            last_tool_phase: None,
137            last_event_at: None,
138            round_count: 0,
139            run_id: Uuid::new_v4().to_string(),
140        }
141    }
142
143    /// Push a critical event into the bounded replay cache.
144    ///
145    /// If the cache is full, the oldest entry is evicted.
146    pub fn push_critical_event(&mut self, event: AgentEvent) {
147        if self.last_critical_events.len() >= Self::CRITICAL_EVENTS_CAPACITY {
148            self.last_critical_events.remove(0);
149        }
150        self.last_critical_events.push(event);
151    }
152}