agentrs 0.1.1

Facade crate and prelude for agentrs
Documentation

agentrs

agentrs is a production-oriented, async-native Rust SDK for building AI agents with a unified provider interface, composable tools, Model Context Protocol (MCP) integration, configurable memory, multi-agent orchestration, and YAML-driven runtime loading.

The project is designed for developers who want an ergonomic Rust API without giving up architectural flexibility. You can start with a single assistant in a few lines, then scale to tool-using agents, web MCP integrations, and orchestrated multi-agent workflows without changing the core programming model.

Why agentrs

  • Provider-agnostic abstractions for OpenAI, Azure OpenAI, Anthropic, Gemini, and Ollama
  • Async-first execution built on tokio, futures, and async_trait
  • Tool calling with built-in tools, MCP tools, and macro-generated custom tools
  • Memory backends for simple chat history, sliding windows, token budgets, and vector retrieval
  • Multiple agent loop strategies such as ReAct, chain-of-thought style single-pass execution, and plan-and-execute
  • Multi-agent orchestration for sequential and parallel execution
  • YAML configuration support for loading single-agent and multi-agent runtimes without hand-writing builders
  • Feature-gated architecture so consumers only compile the integrations they need

Architecture

agentrs is organized as a workspace of focused crates:

agentrs/
├── crates/agentrs-core     # Core traits, shared types, streaming helpers, errors
├── crates/agentrs-llm      # LLM provider implementations
├── crates/agentrs-tools    # Tool registry, built-in tools, proc-macro integration
├── crates/agentrs-mcp      # MCP stdio and Streamable HTTP clients
├── crates/agentrs-memory   # Memory backends
├── crates/agentrs-agents   # Agent builders and execution loops
├── crates/agentrs-multi    # Multi-agent orchestration
└── crates/agentrs          # Facade crate, prelude, YAML runtime loader

Core Design Principles

  • agentrs-core defines the contracts: LlmProvider, Tool, Memory, and Agent
  • agentrs-llm adapts concrete model providers into a common chat-completion interface
  • agentrs-tools keeps tool execution decoupled from providers and agent loops
  • agentrs-mcp lets the SDK consume both local MCP servers and remote web MCP endpoints
  • agentrs-memory keeps memory as a pluggable concern rather than coupling it into a runner
  • agentrs-agents focuses on agent behavior and loop control
  • agentrs-multi composes multiple agents without changing the single-agent contract
  • agentrs re-exports the public API and adds configuration loading for production setups

Technologies Used

  • tokio for async runtime, process management, filesystem access, and concurrency primitives
  • futures and tokio-stream for streaming and async composition
  • async-trait for async trait contracts across providers, tools, memory, and agents
  • serde, serde_json, and serde_yaml for configuration, requests, and schema-like payloads
  • reqwest for HTTP transport to model providers and web MCP endpoints
  • thiserror for structured error handling
  • schemars for JSON Schema generation in macro-generated tools
  • proc-macro, syn, and quote for the #[tool] developer ergonomics layer

Feature Flags

The facade crate exposes the following main features:

[dependencies]
agentrs = { version = "0.1", features = ["openai", "mcp", "tool-search"] }

Available features:

  • openai
  • azureopenai
  • anthropic
  • ollama
  • gemini
  • memory-redis
  • tool-fetch
  • tool-search
  • tool-fs
  • tool-bash
  • tool-python
  • mcp
  • full

The default build enables openai, azureopenai, tool-fetch, tool-search, tool-fs, and mcp.

Installation

[dependencies]
agentrs = { version = "0.1", features = ["openai", "mcp", "tool-fetch", "tool-search"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1"

For local models with Ollama:

[dependencies]
agentrs = { version = "0.1", features = ["ollama", "tool-fs"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1"

Quick Start

use agentrs::prelude::*;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let llm = OpenAiProvider::from_env().model("gpt-4o-mini").build()?;

    let mut agent = Agent::builder()
        .llm(llm)
        .system("You are a concise assistant.")
        .tool(CalculatorTool::new())
        .loop_strategy(LoopStrategy::ReAct { max_steps: 4 })
        .build()?;

    let output = agent.run("What is 7 * (8 + 1)?").await?;
    println!("{}", output.text);

    Ok(())
}

Provider Integrations

All providers implement the same LlmProvider contract.

OpenAI

use agentrs::prelude::*;

let llm = OpenAiProvider::from_env()
    .model("gpt-4o-mini")
    .build()?;

Azure OpenAI

use agentrs::prelude::*;

let llm = AzureOpenAiProvider::from_env()
    .base_url("https://your-resource.openai.azure.com/openai/v1")
    .model("gpt-4o")
    .build()?;

Anthropic

use agentrs::prelude::*;

let llm = AnthropicProvider::from_env()
    .model("claude-3-5-sonnet-latest")
    .build()?;

Gemini

use agentrs::prelude::*;

let llm = GeminiProvider::from_env()
    .model("gemini-2.0-flash")
    .build()?;

Ollama

use agentrs::prelude::*;

let llm = OllamaProvider::builder()
    .base_url("http://localhost:11434/v1")
    .model("llama3.2")
    .build()?;

Tools

Tools are independent components that can be registered in a ToolRegistry and exposed to the model as callable functions.

Built-in Tools

Available built-in tools include:

  • CalculatorTool
  • WebFetchTool
  • WebSearchTool
  • FileReadTool
  • FileWriteTool
  • BashTool behind tool-bash
  • PythonTool behind tool-python

Registering Tools

use agentrs::prelude::*;

let tools = ToolRegistry::new()
    .register(CalculatorTool::new())
    .register(WebSearchTool::new())
    .register(FileReadTool::new(Some(".".into())));

Custom Tools with #[tool]

use agentrs::prelude::*;

#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
struct ReverseInput {
    text: String,
}

#[tool(name = "reverse_text", description = "Reverse a string")]
async fn reverse_text(input: ReverseInput) -> Result<String> {
    Ok(input.text.chars().rev().collect())
}

let tools = ToolRegistry::new().register(ReverseTextTool::new());

Memory Backends

Memory is pluggable and independent from the agent loop.

In-Memory History

use agentrs::prelude::*;

let memory = InMemoryMemory::new();

Sliding Window Memory

use agentrs::prelude::*;

let memory = SlidingWindowMemory::new(12);

Token-Aware Memory

use agentrs::prelude::*;

let memory = TokenAwareMemory::new(4_000);

Vector Memory

use agentrs::prelude::*;

let memory = VectorMemory::new();

Agent Execution Strategies

ReAct

Best when the model may need to call tools iteratively.

let mut agent = Agent::builder()
    .llm(llm)
    .tools(tools)
    .loop_strategy(LoopStrategy::ReAct { max_steps: 8 })
    .build()?;

Chain-of-Thought Style Single Pass

Best for direct reasoning without tools.

let mut agent = Agent::builder()
    .llm(llm)
    .loop_strategy(LoopStrategy::CoT)
    .build()?;

Plan and Execute

Useful for tasks that benefit from an explicit planning stage.

let mut agent = Agent::builder()
    .llm(llm)
    .tools(tools)
    .loop_strategy(LoopStrategy::PlanAndExecute { max_steps: 6 })
    .build()?;

Streaming Runs

use agentrs::prelude::*;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let llm = OpenAiProvider::from_env().model("gpt-4o-mini").build()?;
    let mut agent = Agent::builder().llm(llm).build()?;

    let mut stream = agent.stream_run("Say hello in five words").await?;
    while let Some(event) = stream.next().await {
        match event? {
            AgentEvent::Token(token) => print!("{token}"),
            AgentEvent::Done(output) => println!("\nDone: {}", output.text),
            _ => {}
        }
    }

    Ok(())
}

MCP Integration

agentrs supports both local MCP servers over stdio and remote MCP endpoints over Streamable HTTP.

Local MCP Server

use agentrs::prelude::*;

let tools = ToolRegistry::new()
    .register_mcp("npx -y @modelcontextprotocol/server-filesystem .")
    .await?;

Remote MCP Endpoint

use agentrs::prelude::*;

let tools = ToolRegistry::new()
    .register_mcp_http("https://mcp.context7.com/mcp")
    .await?;

Remote MCP with Optional API Key

use agentrs::prelude::*;

let options = WebMcpOptions::new()
    .api_key("your-api-key")
    .api_key_header("X-Context7-API-Key")
    .api_key_prefix("");

let tools = ToolRegistry::new()
    .register_mcp_http_with_options("https://mcp.context7.com/mcp", options)
    .await?;

MCP Example

use agentrs::prelude::*;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let llm = OpenAiProvider::from_env().model("gpt-4o-mini").build()?;

    let mut mcp_options = WebMcpOptions::new();
    if let Ok(api_key) = std::env::var("CONTEXT7_API_KEY") {
        mcp_options = mcp_options
            .api_key(api_key)
            .api_key_header("X-Context7-API-Key")
            .api_key_prefix("");
    }

    let tools = ToolRegistry::new()
        .register(CalculatorTool::new())
        .register_mcp_http_with_options("https://mcp.context7.com/mcp", mcp_options)
        .await?;

    let mut agent = Agent::builder().llm(llm).tools(tools).build()?;
    let output = agent
        .run("Use Context7 MCP to find Tokio documentation for spawning tasks.")
        .await?;

    println!("{}", output.text);
    Ok(())
}

Multi-Agent Orchestration

agentrs-multi composes existing agents into higher-level workflows.

Sequential Routing

use agentrs::prelude::*;

let researcher = Agent::builder()
    .llm(OpenAiProvider::from_env().model("gpt-4o-mini").build()?)
    .system("You gather facts.")
    .tool(WebSearchTool::new())
    .build()?;

let writer = Agent::builder()
    .llm(OpenAiProvider::from_env().model("gpt-4o-mini").build()?)
    .system("You write the final answer.")
    .build()?;

let mut orchestrator = MultiAgentOrchestrator::builder()
    .add_agent("researcher", researcher)
    .add_agent("writer", writer)
    .routing(RoutingStrategy::Sequential(vec![
        "researcher".to_string(),
        "writer".to_string(),
    ]))
    .build()?;

let output = orchestrator.run("Explain Tokio task spawning").await?;
println!("{}", output.text);

Parallel Routing

use agentrs::prelude::*;

let mut orchestrator = MultiAgentOrchestrator::builder()
    .add_agent("researcher_a", researcher_a)
    .add_agent("researcher_b", researcher_b)
    .routing(RoutingStrategy::Parallel(vec![
        "researcher_a".to_string(),
        "researcher_b".to_string(),
    ]))
    .build()?;

YAML Runtime Loading

One of the main production features of agentrs is the ability to create single agents and multi-agent runtimes from .yaml files.

This is useful when:

  • prompts should change without recompiling code
  • environments use different providers or tools
  • orchestrations are controlled by deployment configuration
  • you want a clean separation between application code and agent definitions

Load a Single Agent from YAML

use agentrs::prelude::*;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut agent = load_agent_from_yaml("examples/configs/single-agent.yaml").await?;
    let output = agent.run("What is 21 * 2?").await?;
    println!("{}", output.text);
    Ok(())
}

Example YAML:

kind: agent
name: calculator-assistant
llm:
  provider: open_ai
  model: gpt-4o-mini
system: You are a concise assistant that uses tools when helpful.
memory:
  type: sliding_window
  window_size: 8
tools:
  - type: calculator
loop_strategy:
  type: re_act
  max_steps: 4
temperature: 0.1
max_tokens: 512

Load a Multi-Agent Orchestrator from YAML

use agentrs::prelude::*;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut orchestrator = load_multi_agent_from_yaml("examples/configs/multi-agent.yaml").await?;
    let output = orchestrator
        .run("Research and explain how Tokio tasks are spawned")
        .await?;
    println!("{}", output.text);
    Ok(())
}

Example YAML:

kind: multi_agent
agents:
  - name: researcher
    llm:
      provider: open_ai
      model: gpt-4o-mini
    system: You gather technical facts and use documentation tools.
    tools:
      - type: web_search
      - type: mcp
        target: https://mcp.context7.com/mcp
        api_key_header: X-Context7-API-Key
        api_key_prefix: ""
    loop_strategy:
      type: re_act
      max_steps: 5
  - name: writer
    llm:
      provider: open_ai
      model: gpt-4o-mini
    system: You write a polished final answer from the previous agent output.
    memory:
      type: token_aware
      max_tokens: 1200
    loop_strategy:
      type: chain_of_thought
routing:
  type: sequential
  order:
    - researcher
    - writer

Load a Generic Runtime from YAML

use agentrs::prelude::*;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut runtime = load_runtime_from_yaml("examples/configs/runtime-agent.yaml").await?;
    let output = runtime.run("Summarize Rust async best practices").await?;
    println!("{}", output.text);
    Ok(())
}

This generic loader is useful when a deployment can switch between single-agent and multi-agent modes.

Error Handling

The SDK uses structured errors from agentrs-core:

  • provider transport and API failures
  • tool validation and execution failures
  • memory backend failures
  • MCP protocol and transport failures
  • invalid configuration and missing fields
  • max-step exhaustion in agent loops

Typical usage:

use agentrs::prelude::*;

match agent.run("hello").await {
    Ok(output) => println!("{}", output.text),
    Err(error) => eprintln!("agent failed: {error}"),
}

Practical Examples Summary

The repository includes runnable examples for the main features:

  • examples/simple_agent.rs
  • examples/tool_use.rs
  • examples/streaming.rs
  • examples/multi_agent.rs
  • examples/mcp_integration.rs
  • examples/yaml_single_agent.rs
  • examples/yaml_multi_agent.rs
  • examples/yaml_runtime.rs

Run them with:

cargo run --example simple_agent
cargo run --example tool_use
cargo run --example streaming
cargo run --example multi_agent
cargo run --example mcp_integration
cargo run --example yaml_single_agent
cargo run --example yaml_multi_agent
cargo run --example yaml_runtime

Environment Variables

Common environment variables used by the provider builders:

  • OPENAI_API_KEY
  • OPENAI_BASE_URL
  • OPENAI_MODEL
  • AZURE_OPENAI_KEY
  • AZURE_OPENAI_ENDPOINT
  • AZURE_OPENAI_MODEL
  • ANTHROPIC_API_KEY
  • GEMINI_API_KEY
  • CONTEXT7_API_KEY

For Ollama you usually only need the local service running at http://localhost:11434.

Testing and Validation

Typical commands used during development:

cargo fmt --all
cargo test --workspace
cargo check --examples

Current Scope and Notes

  • OpenAI, Azure OpenAI, and Ollama currently provide the most complete request/stream integration
  • Anthropic and Gemini support complete() and can be used today, but their streaming implementations are intentionally conservative in this version
  • Multi-agent YAML currently supports sequential and parallel routing
  • YAML config is intended to cover the practical runtime surface, not every internal experimental type

Roadmap Ideas

  • YAML support for graph and supervisor orchestration
  • richer streaming behavior for all providers
  • tool catalogs and dynamic custom tool loading
  • deployment-focused validation and schema export for YAML configs

License

Licensed under either:

  • MIT license
  • Apache License, Version 2.0

at your option.