Quick Start

Basic Example with Anthropic

use yoagent::{Agent, AgentEvent, StreamDelta};
use yoagent::provider::ModelConfig;
use yoagent::tools::default_tools;

#[tokio::main]
async fn main() {
    // The provider is selected from the config's protocol, and the API key is
    // read from ANTHROPIC_API_KEY. Add `.with_api_key(key)` to override it.
    let mut agent = Agent::from_config(ModelConfig::anthropic("claude-sonnet-5", "Sonnet 5"))
        .with_system_prompt("You are a helpful coding assistant.")
        .with_tools(default_tools());

    let mut rx = agent.prompt("List the files in the current directory").await;

    while let Some(event) = rx.recv().await {
        match event {
            AgentEvent::MessageUpdate { delta, .. } => match delta {
                StreamDelta::Text { delta } => print!("{}", delta),
                StreamDelta::Thinking { delta } => print!("[thinking] {}", delta),
                _ => {}
            },
            AgentEvent::ToolExecutionStart { tool_name, .. } => {
                println!("\n→ Running tool: {}", tool_name);
            }
            AgentEvent::ToolExecutionEnd { tool_name, result, is_error, .. } => {
                if is_error {
                    println!("  āœ— {} failed", tool_name);
                } else {
                    println!("  āœ“ {} done", tool_name);
                }
            }
            AgentEvent::AgentEnd { .. } => {
                println!("\n\nDone.");
            }
            _ => {}
        }
    }
}

Example with OpenAI-Compatible Provider

For OpenAI, xAI, Groq, DeepSeek, Mistral, MiniMax, Z.ai, Qwen, Ollama, or any compatible API, pass the matching ModelConfig preset — from_config picks the OpenAI-compatible provider and resolves the provider's env key automatically:

use yoagent::{Agent, AgentEvent};
use yoagent::provider::ModelConfig;
use yoagent::tools::default_tools;

#[tokio::main]
async fn main() {
    // Provider inferred from the preset; key from OPENAI_API_KEY.
    let mut agent = Agent::from_config(ModelConfig::openai("gpt-5.5", "GPT-5.5"))
        .with_system_prompt("You are a helpful assistant.")
        .with_tools(default_tools());

    let mut rx = agent.prompt("What is 2 + 2?").await;

    while let Some(event) = rx.recv().await {
        match event {
            AgentEvent::MessageUpdate { delta, .. } => {
                if let yoagent::StreamDelta::Text { delta } = delta {
                    print!("{}", delta);
                }
            }
            AgentEvent::AgentEnd { .. } => println!(),
            _ => {}
        }
    }
}

Real-Time Streaming

agent.prompt() spawns the agent loop concurrently and returns a receiver immediately, so events stream in real-time as they're produced. For cases where you want to provide your own channel (e.g., to share a sender across tasks), use prompt_with_sender():

use yoagent::{Agent, AgentEvent, StreamDelta};
use yoagent::provider::ModelConfig;
use yoagent::tools::default_tools;

#[tokio::main]
async fn main() {
    let mut agent = Agent::from_config(ModelConfig::anthropic("claude-sonnet-5", "Sonnet 5"))
        .with_system_prompt("You are a helpful assistant.")
        .with_tools(default_tools());

    let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();

    // Consume events in real-time on a separate task
    tokio::spawn(async move {
        while let Some(event) = rx.recv().await {
            match event {
                AgentEvent::MessageUpdate { delta, .. } => {
                    if let StreamDelta::Text { delta } = delta {
                        print!("{}", delta);
                    }
                }
                AgentEvent::AgentEnd { .. } => println!(),
                _ => {}
            }
        }
    });

    // This blocks until the loop finishes; state is restored automatically
    agent.prompt_with_sender("What is 2 + 2?", tx).await;

    // Agent is ready for another prompt immediately
    let _rx = agent.prompt("Follow up question").await;
}

Using the Low-Level API

For more control, use agent_loop() directly:

use yoagent::agent_loop::{agent_loop, AgentLoopConfig};
use yoagent::provider::AnthropicProvider;
use yoagent::types::*;
use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;

#[tokio::main]
async fn main() {
    let (tx, mut rx) = mpsc::unbounded_channel();
    let cancel = CancellationToken::new();

    let mut context = AgentContext {
        system_prompt: "You are helpful.".into(),
        messages: Vec::new(),
        tools: yoagent::tools::default_tools(),
    };

    let config = AgentLoopConfig {
        provider: std::sync::Arc::new(AnthropicProvider),
        model: "claude-sonnet-5".into(),
        api_key: std::env::var("ANTHROPIC_API_KEY").unwrap(),
        thinking_level: ThinkingLevel::Off,
        max_tokens: None,
        temperature: None,
        model_config: None,
        convert_to_llm: None,
        transform_context: None,
        get_steering_messages: None,
        get_follow_up_messages: None,
        context_config: None,
        compaction_strategy: None,
        execution_limits: None,
        cache_config: CacheConfig::default(),
        tool_execution: ToolExecutionStrategy::default(),
        retry_config: yoagent::RetryConfig::default(),
        before_turn: None,
        after_turn: None,
        on_error: None,
        input_filters: vec![],
    };

    let prompts = vec![AgentMessage::Llm(Message::user("Hello!"))];
    let new_messages = agent_loop(prompts, &mut context, &config, tx, cancel).await;

    // Drain events
    while let Ok(event) = rx.try_recv() {
        // handle events...
    }

    println!("Got {} new messages", new_messages.len());
}