Skip to main content

AgentObserver

Trait AgentObserver 

Source
pub trait AgentObserver: Send {
    // Required methods
    fn check_interrupt(&mut self) -> LoopControl;
    fn on_status(&mut self, message: &str);
    fn on_tool_result(
        &mut self,
        tool_name: &str,
        tool_call_id: &str,
        action: &AgentAction,
        result: &AgentActionResult,
    );
    fn on_error(&mut self, error: &str);
    fn on_generation_start(&mut self);
    fn on_generation_complete(&mut self, tokens: usize);

    // Provided methods
    fn on_message_appended(&mut self, _msg: &ChatMessage) { ... }
    fn call_model<'life0, 'life1, 'life2, 'async_trait>(
        &'life0 mut self,
        model: Arc<RwLock<Box<dyn Model>>>,
        messages: &'life1 [ChatMessage],
        config: &'life2 ModelConfig,
    ) -> Pin<Box<dyn Future<Output = Result<ModelCallOutput>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait,
             'life2: 'async_trait { ... }
    fn run_subagents<'life0, 'life1, 'async_trait>(
        &'life0 mut self,
        specs: Vec<(String, String)>,
        model: Arc<RwLock<Box<dyn Model>>>,
        config: &'life1 ModelConfig,
    ) -> Pin<Box<dyn Future<Output = Vec<SubagentResult>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait { ... }
}
Expand description

How the agent loop communicates with its environment.

The six small sync hooks (check_interrupt, on_status, etc.) are called between steps. The two async hooks (call_model, run_subagents) do the heavy lifting and have default implementations suitable for non-interactive use — the TUI overrides them to thread channel-based streaming and live rendering through the shared loop without forking it.

Required Methods§

Source

fn check_interrupt(&mut self) -> LoopControl

Called between steps to check for user interruption or injected messages. Returns LoopControl::Continue to proceed, Interrupt to stop, or InjectMessage(text) to redirect the agent with new user input.

Source

fn on_status(&mut self, message: &str)

Called when the loop status changes (e.g., “Iteration 3 - executing tools”)

Source

fn on_tool_result( &mut self, tool_name: &str, tool_call_id: &str, action: &AgentAction, result: &AgentActionResult, )

Called after a tool call is executed.

Observers may use this to mirror tool results into side storage (e.g., the TUI commits the tool message to session_state for live rendering) — but the loop ALSO appends the tool message to its own messages vec so the next model call sees it. When that vec IS the side storage (TUI passes &mut session_state .messages), both paths land on the same data.

Source

fn on_error(&mut self, error: &str)

Called when the model returns an error.

Source

fn on_generation_start(&mut self)

Called when model generation starts (for status tracking).

Source

fn on_generation_complete(&mut self, tokens: usize)

Called when model generation completes with token count.

Provided Methods§

Source

fn on_message_appended(&mut self, _msg: &ChatMessage)

Called whenever the loop appends a message to its messages vec.

Default: no-op. The TUI overrides this to mirror the message into app.session_state.messages (its UI source of truth) without requiring run_agent_loop to borrow session_state mutably (which would alias through the observer).

Source

fn call_model<'life0, 'life1, 'life2, 'async_trait>( &'life0 mut self, model: Arc<RwLock<Box<dyn Model>>>, messages: &'life1 [ChatMessage], config: &'life2 ModelConfig, ) -> Pin<Box<dyn Future<Output = Result<ModelCallOutput>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait,

Call the model and return its response.

Default: direct model.chat_typed() with a typed-event accumulator — matches what non-interactive mode and subagents need. Reasoning chunks are accumulated separately so they don’t pollute the returned text content. Tool calls are deduped against the response (the adapter populates ModelResponse.tool_calls as a fallback if the typed stream emitted none).

Override for: channel-based streaming, mid-stream interrupt, UI rendering between chunks.

Source

fn run_subagents<'life0, 'life1, 'async_trait>( &'life0 mut self, specs: Vec<(String, String)>, model: Arc<RwLock<Box<dyn Model>>>, config: &'life1 ModelConfig, ) -> Pin<Box<dyn Future<Output = Vec<SubagentResult>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Run a batch of subagents to completion and return their results.

Default: spawn_subagents + collect_subagent_results with no rendering between polls. Override for live progress rendering.

Implementors§