agnt_core/observer.rs
1//! The [`Observer`] trait — single extension point for lifecycle hooks.
2//!
3//! Observers see every step start/end and every tool start/end during an
4//! agent's execution. They are the designated integration point for:
5//!
6//! - Audit logging (persist everything to an external system)
7//! - Human-in-the-loop approval (block or deny tool calls)
8//! - Event bus publishing (stream to NATS, Kafka, Redis)
9//! - Metrics collection (latency histograms, error rates)
10//! - OpenTelemetry spans (via a `tracing-opentelemetry` bridge)
11//!
12//! There is exactly ONE observer per [`Agent`](crate::Agent), not a Vec.
13//! Users who want to fan out to multiple destinations can wrap their
14//! concerns in a single composite `Observer` impl.
15
16use crate::message::{Message, ToolCall};
17
18/// Result of a tool execution, passed to observers after dispatch.
19#[derive(Debug, Clone)]
20pub struct ToolResult {
21 pub name: String,
22 pub output: Result<String, String>,
23 pub duration_us: u64,
24}
25
26/// Context for a step lifecycle event.
27///
28/// Carries the session id and the user input that triggered this step.
29/// Expands in v0.3 to include step number and deadline.
30#[derive(Debug, Clone)]
31pub struct StepContext {
32 pub session: String,
33 pub user_input: String,
34}
35
36/// Lifecycle observer. Every method has a default no-op implementation so
37/// implementors override only the hooks they care about.
38pub trait Observer: Send + Sync {
39 /// Called when [`Agent::step`](crate::Agent::step) begins.
40 fn on_step_start(&self, _ctx: &StepContext) {}
41
42 /// Called before each tool dispatch inside the step loop.
43 fn on_tool_start(&self, _call: &ToolCall) {}
44
45 /// Called after each tool dispatch completes, with the result.
46 fn on_tool_end(&self, _call: &ToolCall, _result: &ToolResult) {}
47
48 /// Called when the step loop terminates with a final assistant message.
49 fn on_step_end(&self, _response: &Message) {}
50
51 /// Called if the step loop errors out before producing a final message.
52 fn on_step_error(&self, _error: &str) {}
53}
54
55/// A no-op observer used as the default when the agent is constructed
56/// without one.
57pub struct NoOpObserver;
58
59impl Observer for NoOpObserver {}