daimon 0.17.0

A Rust-native AI agent framework
docs.rs failed to build daimon-0.17.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: daimon-0.16.0

Daimon

A Rust-native AI agent framework for building LLM-powered agents with tool use, memory, and streaming.

Daimon implements the ReAct (Reason-Act-Observe) pattern: the agent calls a model, optionally invokes tools, observes results, and repeats until it produces a final response. It is designed to be easy to use while leveraging Rust's type system, async runtime, and performance characteristics.

Features

  • ReAct agent loop with configurable iteration limits
  • Multiple LLM providers behind feature flags — OpenAI, Anthropic, AWS Bedrock
  • Tool system with async execution, parallel tool calls, and a typed registry
  • Streaming with full ReAct loop support (tool calls accumulate and re-invoke within a single stream)
  • Conversation memory with pluggable backends (sliding window included)
  • Lifecycle hooks for observability and control
  • Cancellation via tokio_util::CancellationToken
  • Tracing instrumentation on all agent and provider operations
  • Retry logic with exponential backoff for transient provider errors

Quick Start

Add Daimon to your Cargo.toml:

[dependencies]
daimon = "0.17"
tokio = { version = "1", features = ["full"] }

Create an agent and prompt it:

use daimon::prelude::*;

#[tokio::main]
async fn main() -> daimon::Result<()> {
    let agent = Agent::builder()
        .model(daimon::model::openai::OpenAi::new("gpt-4o"))
        .system_prompt("You are a helpful assistant.")
        .build()?;

    let response = agent.prompt("What is Rust?").await?;
    println!("{}", response.text());
    Ok(())
}

Tools

Define tools by implementing the Tool trait:

use daimon::prelude::*;

struct Calculator;

impl Tool for Calculator {
    fn name(&self) -> &str { "calculator" }
    fn description(&self) -> &str { "Evaluate math expressions" }

    fn parameters_schema(&self) -> Value {
        json!({
            "type": "object",
            "properties": {
                "operation": { "type": "string", "enum": ["add", "subtract", "multiply", "divide"] },
                "a": { "type": "number" },
                "b": { "type": "number" }
            },
            "required": ["operation", "a", "b"]
        })
    }

    async fn execute(&self, input: &Value) -> daimon::Result<ToolOutput> {
        let op = input["operation"].as_str().unwrap_or("add");
        let a = input["a"].as_f64().unwrap_or(0.0);
        let b = input["b"].as_f64().unwrap_or(0.0);

        let result = match op {
            "add" => a + b,
            "subtract" => a - b,
            "multiply" => a * b,
            "divide" if b != 0.0 => a / b,
            "divide" => return Ok(ToolOutput::error("Division by zero")),
            _ => return Ok(ToolOutput::error(format!("Unknown operation: {op}"))),
        };

        Ok(ToolOutput::text(format!("{result}")))
    }
}

#[tokio::main]
async fn main() -> daimon::Result<()> {
    let agent = Agent::builder()
        .model(daimon::model::openai::OpenAi::new("gpt-4o"))
        .system_prompt("Use the calculator tool to solve math problems.")
        .tool(Calculator)
        .build()?;

    let response = agent.prompt("What is 42 * 17 + 3?").await?;
    println!("{}", response.text());
    println!("Completed in {} iteration(s)", response.iterations);
    Ok(())
}

Streaming

Stream responses token-by-token with the full ReAct loop. Streaming is not a degraded path: it runs the same loop as prompt() — conversation memory is loaded and persisted, lifecycle hooks fire, guardrails are enforced, and tool calls accumulate and re-invoke the model within a single stream until a final response is produced.

use daimon::prelude::*;

#[tokio::main]
async fn main() -> daimon::Result<()> {
    let agent = Agent::builder()
        .model(daimon::model::openai::OpenAi::new("gpt-4o"))
        .build()?;

    let mut stream = agent.prompt_stream("Explain quantum computing.").await?;

    while let Some(event) = stream.next().await {
        match event? {
            StreamEvent::TextDelta(text) => print!("{text}"),
            StreamEvent::ToolResult { content, .. } => eprintln!("\n[tool result: {content}]"),
            StreamEvent::Done => { println!(); break; }
            _ => {}
        }
    }

    Ok(())
}

Feature Flags

Feature Default Description
openai Yes OpenAI Chat Completions API
anthropic Yes Anthropic Messages API
macros Yes #[tool_fn] proc macro for defining tools
bedrock No AWS Bedrock Converse API
gemini No Google Gemini / Vertex AI provider
azure No Azure OpenAI Service provider
ollama No Ollama local model provider
mcp No Model Context Protocol client & server
sqlite No SQLite memory backend
redis No Redis memory backend + task broker + checkpoint
nats No NATS JetStream task broker + checkpoint
amqp No RabbitMQ (AMQP) task broker
qdrant No Qdrant vector store retriever
pgvector No pgvector-backed vector store (via daimon-plugin-pgvector)
opensearch No OpenSearch k-NN vector store (via daimon-plugin-opensearch)
otel No OpenTelemetry OTLP span export
http-server No HTTP agent server (AgentServer)
grpc No gRPC transport for distributed execution
full No All providers + macros + MCP + SQLite + Redis + NATS + AMQP + OTel + HTTP server + Qdrant + pgvector + OpenSearch + gRPC + eval + SQS + Pub/Sub + Service Bus

The core framework compiles with no features enabled. Enable only the providers you need:

# Only Anthropic
daimon = { version = "0.17", default-features = false, features = ["anthropic"] }

# All providers
daimon = { version = "0.17", features = ["full"] }

# Core only (bring your own Model impl)
daimon = { version = "0.17", default-features = false }

Provider Configuration

All providers support configurable timeout, retries, and provider-specific options:

use std::time::Duration;

// OpenAI with custom settings
let model = daimon::model::openai::OpenAi::new("gpt-4o")
    .with_timeout(Duration::from_secs(30))
    .with_max_retries(5)
    .with_response_format("json_object")
    .with_parallel_tool_calls(true);

// Anthropic with prompt caching
let model = daimon::model::anthropic::Anthropic::new("claude-sonnet-4-20250514")
    .with_timeout(Duration::from_secs(60))
    .with_prompt_caching();

// AWS Bedrock with guardrails
let model = daimon::model::bedrock::Bedrock::new("anthropic.claude-3-5-sonnet-20241022-v2:0")
    .with_guardrail("my-guardrail-id", "DRAFT");

Agent Configuration

use daimon::prelude::*;

let agent = Agent::builder()
    .model(model)                              // required
    .system_prompt("You are helpful.")         // optional system prompt
    .tool(Calculator)                          // register tools
    .tool(WebSearch)
    .memory(SlidingWindowMemory::new(100))     // custom memory (default: 50 messages)
    .hooks(MyObserver)                         // lifecycle hooks
    .max_iterations(10)                        // default: 25
    .temperature(0.7)                          // sampling temperature
    .max_tokens(4096)                          // max output tokens
    .build()?;

// Standard prompt
let response = agent.prompt("Hello").await?;
println!("{}", response.text());
println!("Tokens used: {}", response.usage.total_tokens());

// With cancellation
let cancel = CancellationToken::new();
let response = agent.prompt_with_cancellation("Hello", &cancel).await?;

// With pre-built messages
let messages = vec![Message::user("Hello")];
let response = agent.prompt_with_messages(messages).await?;

Architecture

┌──────────────────────────────────────────────────┐
│                    Agent                          │
│  ┌────────────┐  ┌──────────┐  ┌──────────────┐ │
│  │   Model     │  │  Tools   │  │   Memory     │ │
│  │  (trait)    │  │ Registry │  │   (trait)    │ │
│  └─────┬──────┘  └────┬─────┘  └──────┬───────┘ │
│        │              │               │          │
│  ┌─────┴──────────────┴───────────────┴───────┐  │
│  │            ReAct Loop                      │  │
│  │  1. Load memory → build request            │  │
│  │  2. Call model                             │  │
│  │  3. Tool calls? → execute (parallel) → 2   │  │
│  │  4. Final response → save to memory        │  │
│  └────────────────────────────────────────────┘  │
│        │                                         │
│  ┌─────┴──────┐  ┌──────────┐                   │
│  │   Hooks    │  │ Streaming │                   │
│  │ (lifecycle)│  │  Events   │                   │
│  └────────────┘  └──────────┘                   │
└──────────────────────────────────────────────────┘

Environment Variables

Each provider reads its API key from standard environment variables:

Provider Variable Notes
OpenAI OPENAI_API_KEY Required for openai feature
Anthropic ANTHROPIC_API_KEY Required for anthropic feature
AWS Bedrock Standard AWS credentials AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY or IAM role
Google Gemini GOOGLE_APPLICATION_CREDENTIALS Service account JSON path
Azure OpenAI AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT Required for azure feature
Ollama OLLAMA_HOST Defaults to http://localhost:11434

Testing

# Default features (openai + anthropic)
cargo test

# All features
cargo test --features full

# Core only (no providers)
cargo test --no-default-features

# Coverage (requires cargo-llvm-cov)
cargo llvm-cov --features full --fail-under-lines 90

See CONTRIBUTING.md for full testing and development setup.

Minimum Supported Rust Version

Rust 1.85 (edition 2024).

License

Licensed under either of

at your option.

Related Repos

Contributing

See CONTRIBUTING.md for development setup, coding standards, and contribution guidelines. Note that AI-assisted contributions must include proper attribution.