Skip to content

Lexmata/daimon

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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.1"
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:

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
bedrock No AWS Bedrock Converse API
full No All providers

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

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

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

# Core only (bring your own Model impl)
daimon = { version = "0.1", 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   │                   │
│  └────────────┘  └──────────┘                   │
└──────────────────────────────────────────────────┘

Minimum Supported Rust Version

Rust 1.85 (edition 2024).

License

Licensed under either of

at your option.

Contributing

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

About

A Rust-native AI agent framework

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages