Skip to main content

AgentLoop

Struct AgentLoop 

Source
pub struct AgentLoop<Ctx, P, H, M, S>{ /* private fields */ }
Expand description

The main agent loop that orchestrates LLM calls and tool execution.

AgentLoop is the core component that:

  • Manages conversation state via message and state stores
  • Calls the LLM provider and processes responses
  • Executes tools through the tool registry
  • Emits events for real-time updates via an async channel
  • Enforces hooks for tool permissions and lifecycle events

§Type Parameters

  • Ctx: Application-specific context passed to tools (e.g., user ID, database)
  • P: The LLM provider implementation
  • H: The hooks implementation for lifecycle customization
  • M: The message store implementation
  • S: The state store implementation

§Event Channel Behavior

The agent uses a bounded channel (capacity 100) for events. Events are sent using non-blocking sends:

  • If the channel has space, events are sent immediately
  • If the channel is full, the agent waits up to 30 seconds before timing out
  • If the receiver is dropped, the agent continues processing without blocking

This design ensures that slow consumers don’t stall the LLM stream, but events may be dropped if the consumer is too slow or disconnects.

§Running the Agent

let (mut events, final_state) = agent.run(
    thread_id,
    AgentInput::Text("Hello!".to_string()),
    tool_ctx,
);
while let Some(event) = events.recv().await {
    match event {
        AgentEvent::Text { text } => println!("{}", text),
        AgentEvent::Done { .. } => break,
        _ => {}
    }
}

Implementations§

Source§

impl<Ctx, P, H, M, S> AgentLoop<Ctx, P, H, M, S>
where Ctx: Send + Sync + 'static, P: LlmProvider + 'static, H: AgentHooks + 'static, M: MessageStore + 'static, S: StateStore + 'static,

Source

pub fn new( provider: P, tools: ToolRegistry<Ctx>, hooks: H, message_store: M, state_store: S, config: AgentConfig, ) -> Self

Create a new agent loop with all components specified directly.

Source

pub fn with_compaction( provider: P, tools: ToolRegistry<Ctx>, hooks: H, message_store: M, state_store: S, config: AgentConfig, compaction_config: CompactionConfig, ) -> Self

Create a new agent loop with compaction enabled.

Source

pub fn run( &self, thread_id: ThreadId, input: AgentInput, tool_context: ToolContext<Ctx>, ) -> (Receiver<AgentEvent>, Receiver<AgentRunState>)
where Ctx: Clone,

Run the agent loop.

This method allows the agent to pause when a tool requires confirmation, returning an AgentRunState::AwaitingConfirmation that contains the state needed to resume.

§Arguments
  • thread_id - The thread identifier for this conversation
  • input - Either a new text message or a resume with confirmation decision
  • tool_context - Context passed to tools
§Returns

A tuple of:

  • mpsc::Receiver<AgentEvent> - Channel for streaming events
  • oneshot::Receiver<AgentRunState> - Channel for the final state
§Example
let (events, final_state) = agent.run(
    thread_id,
    AgentInput::Text("Hello".to_string()),
    tool_ctx,
);

while let Some(event) = events.recv().await {
    // Handle events...
}

match final_state.await.unwrap() {
    AgentRunState::Done { .. } => { /* completed */ }
    AgentRunState::AwaitingConfirmation { continuation, .. } => {
        // Get user decision, then resume:
        let (events2, state2) = agent.run(
            thread_id,
            AgentInput::Resume {
                continuation,
                tool_call_id: id,
                confirmed: true,
                rejection_reason: None,
            },
            tool_ctx,
        );
    }
    AgentRunState::Error(e) => { /* handle error */ }
}
Source

pub fn run_turn( &self, thread_id: ThreadId, input: AgentInput, tool_context: ToolContext<Ctx>, ) -> (Receiver<AgentEvent>, Receiver<TurnOutcome>)
where Ctx: Clone,

Run a single turn of the agent loop.

Unlike run(), this method executes exactly one turn and returns control to the caller. This enables external orchestration where each turn can be dispatched as a separate message (e.g., via Artemis or another message queue).

§Arguments
  • thread_id - The thread identifier for this conversation
  • input - Text to start, Resume after confirmation, or Continue after a turn
  • tool_context - Context passed to tools
§Returns

A tuple of:

  • mpsc::Receiver<AgentEvent> - Channel for streaming events from this turn
  • TurnOutcome - The turn’s outcome
§Turn Outcomes
  • NeedsMoreTurns - Turn completed, call again with AgentInput::Continue
  • Done - Agent completed successfully
  • AwaitingConfirmation - Tool needs confirmation, call again with AgentInput::Resume
  • Error - An error occurred
§Example
// Start conversation
let (events, outcome) = agent.run_turn(
    thread_id.clone(),
    AgentInput::Text("What is 2+2?".to_string()),
    tool_ctx.clone(),
).await;

// Process events...
while let Some(event) = events.recv().await { /* ... */ }

// Check outcome
match outcome {
    TurnOutcome::NeedsMoreTurns { turn, .. } => {
        // Dispatch another message to continue
        // (e.g., schedule an Artemis message)
    }
    TurnOutcome::Done { .. } => {
        // Conversation complete
    }
    TurnOutcome::AwaitingConfirmation { continuation, .. } => {
        // Get user confirmation, then resume
    }
    TurnOutcome::Error(e) => {
        // Handle error
    }
}

Auto Trait Implementations§

§

impl<Ctx, P, H, M, S> Freeze for AgentLoop<Ctx, P, H, M, S>

§

impl<Ctx, P, H, M, S> !RefUnwindSafe for AgentLoop<Ctx, P, H, M, S>

§

impl<Ctx, P, H, M, S> Send for AgentLoop<Ctx, P, H, M, S>

§

impl<Ctx, P, H, M, S> Sync for AgentLoop<Ctx, P, H, M, S>

§

impl<Ctx, P, H, M, S> Unpin for AgentLoop<Ctx, P, H, M, S>

§

impl<Ctx, P, H, M, S> !UnwindSafe for AgentLoop<Ctx, P, H, M, S>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more