Skip to main content

behest_event/
agent_event.rs

1//! Canonical agent event definitions.
2//!
3//! This module owns the [`AgentEvent`] enum and its 17 variant payload
4//! structs. It used to live in `behest::runtime::event` but was moved
5//! here so the event contract lives next to `Hook` and `HookStack` in
6//! the same crate.
7
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use uuid::Uuid;
11
12use behest_core::id::RunId;
13use behest_core::message::{FinishReason, TokenUsage};
14use behest_core::tool_types::ToolCall;
15
16/// Event emitted during agent runtime execution.
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub enum AgentEvent {
19    /// Run has started.
20    RunStarted(RunStarted),
21    /// Context has been built.
22    ContextBuilt(ContextBuilt),
23    /// Model call has started.
24    ModelStarted(ModelStarted),
25    /// Text delta from model.
26    TextDelta(TextDelta),
27    /// Tool call has started.
28    ToolCallStarted(ToolCallStarted),
29    /// Tool call arguments delta.
30    ToolCallDelta(ToolCallDelta),
31    /// Tool call has completed.
32    ToolCallCompleted(ToolCallCompleted),
33    /// Tool execution has started.
34    ToolExecutionStarted(ToolExecutionStarted),
35    /// Tool execution has finished.
36    ToolExecutionFinished(ToolExecutionFinished),
37    /// Assistant message has been committed to store.
38    AssistantMessageCommitted(MessageCommitted),
39    /// Tool message has been committed to store.
40    ToolMessageCommitted(MessageCommitted),
41    /// Usage has been recorded.
42    UsageRecorded(UsageRecorded),
43    /// Prompt cache metrics from a single model call.
44    CacheMetrics(CacheMetrics),
45    /// Run has completed successfully.
46    RunCompleted(RunCompleted),
47    /// Run has failed.
48    RunFailed(RunFailed),
49    /// Run has been cancelled.
50    RunCancelled(RunCancelled),
51    /// Doom loop has been detected.
52    DoomLoopDetected(DoomLoopDetected),
53    /// Compaction circuit breaker has opened due to repeated failures.
54    CompactionCircuitOpened(CompactionCircuitOpened),
55}
56
57impl AgentEvent {
58    /// Returns the run identifier for any event variant without pattern matching.
59    #[must_use]
60    pub fn run_id(&self) -> RunId {
61        match self {
62            AgentEvent::RunStarted(e) => e.run_id,
63            AgentEvent::ContextBuilt(e) => e.run_id,
64            AgentEvent::ModelStarted(e) => e.run_id,
65            AgentEvent::TextDelta(e) => e.run_id,
66            AgentEvent::ToolCallStarted(e) => e.run_id,
67            AgentEvent::ToolCallDelta(e) => e.run_id,
68            AgentEvent::ToolCallCompleted(e) => e.run_id,
69            AgentEvent::ToolExecutionStarted(e) => e.run_id,
70            AgentEvent::ToolExecutionFinished(e) => e.run_id,
71            AgentEvent::AssistantMessageCommitted(e) | AgentEvent::ToolMessageCommitted(e) => {
72                e.run_id
73            }
74            AgentEvent::UsageRecorded(e) => e.run_id,
75            AgentEvent::CacheMetrics(e) => e.run_id,
76            AgentEvent::RunCompleted(e) => e.run_id,
77            AgentEvent::RunFailed(e) => e.run_id,
78            AgentEvent::RunCancelled(e) => e.run_id,
79            AgentEvent::DoomLoopDetected(e) => e.run_id,
80            AgentEvent::CompactionCircuitOpened(e) => e.run_id,
81        }
82    }
83
84    /// Returns true when this event signals a terminal run state.
85    #[must_use]
86    pub fn is_terminal(&self) -> bool {
87        matches!(
88            self,
89            AgentEvent::RunCompleted(_) | AgentEvent::RunFailed(_) | AgentEvent::RunCancelled(_)
90        )
91    }
92}
93
94/// Emitted when a run begins execution after session load and input admission.
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct RunStarted {
97    /// Run identifier.
98    pub run_id: RunId,
99    /// Session identifier.
100    pub session_id: Uuid,
101    /// Provider used for model calls.
102    pub provider: behest_core::id::ProviderId,
103    /// Model used for generation.
104    pub model: behest_core::id::ModelName,
105    /// When the run started.
106    pub timestamp: DateTime<Utc>,
107}
108
109/// Emitted after context has been assembled from session history and adapters.
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct ContextBuilt {
112    /// Run identifier.
113    pub run_id: RunId,
114    /// Number of messages in context.
115    pub message_count: usize,
116    /// When context was built.
117    pub timestamp: DateTime<Utc>,
118}
119
120/// Emitted when a model invocation begins, carrying iteration count.
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct ModelStarted {
123    /// Run identifier.
124    pub run_id: RunId,
125    /// Provider being called.
126    pub provider: behest_core::id::ProviderId,
127    /// Model being used.
128    pub model: behest_core::id::ModelName,
129    /// Iteration number.
130    pub iteration: usize,
131    /// When the model call started.
132    pub timestamp: DateTime<Utc>,
133}
134
135/// Streaming text delta emitted during model response generation.
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct TextDelta {
138    /// Run identifier.
139    pub run_id: RunId,
140    /// Text delta.
141    pub delta: String,
142    /// When the delta was emitted.
143    pub timestamp: DateTime<Utc>,
144}
145
146/// Emitted when the model requests a tool call.
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct ToolCallStarted {
149    /// Run identifier.
150    pub run_id: RunId,
151    /// Tool call ID.
152    pub call_id: String,
153    /// Tool name.
154    pub tool_name: String,
155    /// When the tool call started.
156    pub timestamp: DateTime<Utc>,
157}
158
159/// Streaming delta for tool call arguments during model response.
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct ToolCallDelta {
162    /// Run identifier.
163    pub run_id: RunId,
164    /// Tool call ID.
165    pub call_id: String,
166    /// Arguments delta.
167    pub delta: String,
168    /// When the delta was emitted.
169    pub timestamp: DateTime<Utc>,
170}
171
172/// Emitted when the model finishes emitting a complete tool call.
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct ToolCallCompleted {
175    /// Run identifier.
176    pub run_id: RunId,
177    /// Completed tool call.
178    pub call: ToolCall,
179    /// When the tool call completed.
180    pub timestamp: DateTime<Utc>,
181}
182
183/// Emitted when a tool function begins executing.
184#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct ToolExecutionStarted {
186    /// Run identifier.
187    pub run_id: RunId,
188    /// Tool call ID.
189    pub call_id: String,
190    /// Tool name.
191    pub tool_name: String,
192    /// When execution started.
193    pub timestamp: DateTime<Utc>,
194}
195
196/// Emitted when a tool function completes, carrying the result and duration.
197#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct ToolExecutionFinished {
199    /// Run identifier.
200    pub run_id: RunId,
201    /// Tool call ID.
202    pub call_id: String,
203    /// Tool name.
204    pub tool_name: String,
205    /// Execution result.
206    pub result: ToolExecutionResult,
207    /// Execution duration in milliseconds.
208    pub duration_ms: u64,
209    /// When execution finished.
210    pub timestamp: DateTime<Utc>,
211}
212
213/// Result of a single tool execution.
214#[derive(Debug, Clone, Serialize, Deserialize)]
215pub enum ToolExecutionResult {
216    /// Tool executed successfully.
217    Success {
218        /// Output value returned by the tool.
219        output: serde_json::Value,
220    },
221    /// Tool execution failed.
222    Failure {
223        /// Error message describing why the tool failed.
224        error: String,
225    },
226}
227
228/// Notification that a message has been persisted to the session store.
229#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct MessageCommitted {
231    /// Run identifier.
232    pub run_id: RunId,
233    /// Message ID.
234    pub message_id: Uuid,
235    /// When the message was committed.
236    pub timestamp: DateTime<Utc>,
237}
238
239/// Emitted after each model invocation to record token consumption.
240#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct UsageRecorded {
242    /// Run identifier.
243    pub run_id: RunId,
244    /// Token usage.
245    pub usage: TokenUsage,
246    /// When usage was recorded.
247    pub timestamp: DateTime<Utc>,
248}
249
250/// Emitted after each model call when the provider reported prompt cache
251/// statistics. All fields are zero when the provider did not report cache
252/// hits or writes for this call.
253#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct CacheMetrics {
255    /// Run identifier.
256    pub run_id: RunId,
257    /// Number of input tokens written to the cache by this call
258    /// (Anthropic: `cache_creation_input_tokens`).
259    pub cache_creation_input_tokens: u64,
260    /// Number of input tokens served from the cache by this call
261    /// (Anthropic: `cache_read_input_tokens`, DeepSeek: `prompt_cache_hit_tokens`).
262    pub cache_read_input_tokens: u64,
263    /// Number of input tokens served from the cache by this call
264    /// (OpenAI: `prompt_tokens_details.cached_tokens`).
265    pub cached_input_tokens: u64,
266    /// When the metrics were recorded.
267    pub timestamp: DateTime<Utc>,
268}
269
270impl CacheMetrics {
271    /// Returns the total cache-related input tokens.
272    #[must_use]
273    pub const fn total_cache_tokens(&self) -> u64 {
274        self.cache_creation_input_tokens + self.cache_read_input_tokens + self.cached_input_tokens
275    }
276}
277
278/// Terminal event emitted when a run finishes successfully.
279#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct RunCompleted {
281    /// Run identifier.
282    pub run_id: RunId,
283    /// Final finish reason.
284    pub finish_reason: FinishReason,
285    /// Total iterations.
286    pub iterations: usize,
287    /// When the run completed.
288    pub timestamp: DateTime<Utc>,
289}
290
291/// Terminal event emitted when a run terminates with an error.
292#[derive(Debug, Clone, Serialize, Deserialize)]
293pub struct RunFailed {
294    /// Run identifier.
295    pub run_id: RunId,
296    /// Error message.
297    pub error: String,
298    /// When the run failed.
299    pub timestamp: DateTime<Utc>,
300}
301
302/// Terminal event emitted when a run is cancelled before completion.
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct RunCancelled {
305    /// Run identifier.
306    pub run_id: RunId,
307    /// When the run was cancelled.
308    pub timestamp: DateTime<Utc>,
309}
310
311/// Emitted when the agent is detected to be in a repetitive tool call cycle.
312#[derive(Debug, Clone, Serialize, Deserialize)]
313pub struct DoomLoopDetected {
314    /// Run identifier.
315    pub run_id: RunId,
316    /// Description of the doom loop pattern detected.
317    pub description: String,
318    /// When the doom loop was detected.
319    pub timestamp: DateTime<Utc>,
320}
321
322/// Emitted when the compaction circuit breaker opens due to repeated failures.
323#[derive(Debug, Clone, Serialize, Deserialize)]
324pub struct CompactionCircuitOpened {
325    /// Run identifier.
326    pub run_id: RunId,
327    /// Number of consecutive failures that triggered the breaker.
328    pub consecutive_failures: u32,
329    /// When the breaker opened.
330    pub timestamp: DateTime<Utc>,
331}