enki-runtime 0.1.4

A Rust-based agent mesh framework for building local and distributed AI agent systems
Documentation
//! # Enki Runtime
//!
//! A Rust-based agent mesh framework for building local and distributed AI agent systems.
//!
//! ## Architecture
//!
//! Enki Runtime is modular, split into focused sub-crates that are re-exported here:
//!
//! | Crate | Description |
//! |-------|-------------|
//! | [`core`] | Core abstractions: Agent, Memory, Mesh, Message |
//! | [`llm`] | LLM integration with 13+ providers |
//! | [`local`] | Local mesh implementation |
//! | [`memory`] | Memory backend implementations |
//! | [`logging`]/[`metrics`] | Observability |
//! | [`mcp`] | Model Context Protocol (feature: `mcp`) |
//!
//! ## Features
//!
//! - **Agent Framework**: Build autonomous AI agents with a simple trait-based API
//! - **Local Mesh**: Connect multiple agents for inter-agent communication
//! - **LLM Integration**: Built-in support for OpenAI, Anthropic, Ollama, Google, and 10+ more
//! - **Memory Backends**: Pluggable memory (in-memory, SQLite, Redis)
//! - **TOML Configuration**: Load agents from config files
//! - **Async-first**: Built on Tokio for high performance
//! - **Observability**: Structured logging and metrics
//!
//! ## Quick Start
//!
//! ### Custom Agent
//!
//! ```rust,no_run
//! use enki_runtime::{Agent, AgentContext, LocalMesh, Message};
//! use enki_runtime::core::error::Result;
//! use enki_runtime::core::mesh::Mesh;
//! use async_trait::async_trait;
//!
//! struct MyAgent { name: String }
//!
//! #[async_trait]
//! impl Agent for MyAgent {
//!     fn name(&self) -> String { self.name.clone() }
//!
//!     async fn on_message(&mut self, msg: Message, _ctx: &mut AgentContext) -> Result<()> {
//!         println!("Received: {:?}", msg.topic);
//!         Ok(())
//!     }
//! }
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//!     let mesh = LocalMesh::new("my-mesh");
//!     mesh.add_agent(Box::new(MyAgent { name: "agent".into() })).await?;
//!     mesh.start().await?;
//!     Ok(())
//! }
//! ```
//!
//! ### LLM Agent
//!
//! ```rust,no_run
//! use enki_runtime::{LlmAgent, AgentContext};
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//!     let mut agent = LlmAgent::builder("assistant", "ollama::gemma3:latest")
//!         .with_system_prompt("You are helpful.")
//!         .with_temperature(0.7)
//!         .build()?;
//!
//!     let mut ctx = AgentContext::new("test".into(), None);
//!     let response = agent.send_message_and_get_response("Hello!", &mut ctx).await?;
//!     println!("{}", response);
//!     Ok(())
//! }
//! ```
//!
//! ### TOML Configuration
//!
//! ```rust,no_run
//! use enki_runtime::{LlmAgent, LlmAgentFromConfig};
//! use enki_runtime::config::AgentConfig;
//!
//! let config = AgentConfig::from_file("agent.toml").unwrap();
//! let agent = LlmAgent::from_config(config).unwrap();
//! ```
//!
//! ## Feature Flags
//!
//! - `sqlite` - SQLite memory backend
//! - `redis` - Redis memory backend  
//! - `mcp` - Model Context Protocol support
//! - `full` - All optional features

// Configuration module
pub mod config;

// Re-export sub-crates as modules
pub use enki_core as core;
pub use enki_llm as llm;
pub use enki_local as local;
pub use enki_memory as memory;
pub use enki_observability::logging;
pub use enki_observability::metrics;

#[cfg(feature = "mcp")]
pub use enki_mcp as mcp;

// Convenience re-exports at crate root
pub use config::{AgentConfig, MemoryBackendType, MemoryConfig, MeshConfig};
pub use enki_core::{
    Agent, AgentContext, AgentEvent, Memory, MemoryEntry, MemoryQuery, Mesh, Message, VectorMemory,
};
pub use enki_llm::{LLMConfig, LlmAgent, LlmAgentBuilder, UniversalLLMClient};
pub use enki_local::LocalMesh;
pub use enki_memory::InMemoryBackend;

#[cfg(feature = "sqlite")]
pub use enki_memory::SqliteBackend;

#[cfg(feature = "redis")]
pub use enki_memory::RedisBackend;

#[cfg(feature = "qdrant")]
pub use enki_memory::QdrantBackend;

#[cfg(feature = "chromadb")]
pub use enki_memory::ChromaDBBackend;

#[cfg(feature = "pinecone")]
pub use enki_memory::PineconeBackend;

#[cfg(feature = "mcp")]
pub use enki_mcp::{McpAgentServer, McpClient, McpTransport};

/// Extension trait for creating [`LlmAgent`] from TOML configuration.
///
/// Import this trait to use [`LlmAgent::from_config`]:
///
/// ```rust,no_run
/// use enki_runtime::{LlmAgent, LlmAgentFromConfig};
/// use enki_runtime::config::AgentConfig;
///
/// let config = AgentConfig::from_file("agent.toml").unwrap();
/// let agent = LlmAgent::from_config(config).unwrap();
/// ```
pub trait LlmAgentFromConfig {
    /// Create an LlmAgent from an [`AgentConfig`].
    fn from_config(config: AgentConfig) -> core::error::Result<LlmAgent>;
}

impl LlmAgentFromConfig for LlmAgent {
    fn from_config(config: AgentConfig) -> core::error::Result<LlmAgent> {
        let mut builder = LlmAgentBuilder::new(&config.name, &config.model)
            .with_system_prompt(&config.system_prompt);

        if let Some(ref api_key) = config.api_key {
            builder = builder.with_api_key(api_key);
        }

        if let Some(temperature) = config.temperature {
            builder = builder.with_temperature(temperature);
        }

        if let Some(max_tokens) = config.max_tokens {
            builder = builder.with_max_tokens(max_tokens);
        }

        builder.build()
    }
}

/// Extension trait for creating memory backends from TOML configuration.
///
/// # Example
///
/// ```rust,no_run
/// use enki_runtime::{InMemoryBackend, MemoryFromConfig};
/// use enki_runtime::config::MemoryConfig;
///
/// # async fn example() -> anyhow::Result<()> {
/// let config = MemoryConfig::in_memory().with_max_entries(1000);
/// let memory = InMemoryBackend::from_config(&config);
/// # Ok(())
/// # }
/// ```
pub trait MemoryFromConfig {
    /// Create a memory backend from a [`MemoryConfig`].
    fn from_config(config: &MemoryConfig) -> Self;
}

impl MemoryFromConfig for InMemoryBackend {
    fn from_config(config: &MemoryConfig) -> Self {
        let mut backend = InMemoryBackend::new();

        if let Some(max_entries) = config.max_entries {
            backend = backend.with_max_entries(max_entries);
        }

        if let Some(ttl_seconds) = config.ttl_seconds {
            backend = backend.with_ttl_seconds(ttl_seconds);
        }

        backend
    }
}

/// Async trait for creating memory backends that require async initialization.
///
/// Used for SQLite, Redis, and vector database backends that need async connection setup.
#[cfg(any(
    feature = "sqlite",
    feature = "redis",
    feature = "qdrant",
    feature = "chromadb",
    feature = "pinecone"
))]
#[async_trait::async_trait]
pub trait MemoryFromConfigAsync {
    /// Create a memory backend from a [`MemoryConfig`] asynchronously.
    async fn from_config_async(config: &MemoryConfig) -> core::error::Result<Self>
    where
        Self: Sized;
}

#[cfg(feature = "sqlite")]
#[async_trait::async_trait]
impl MemoryFromConfigAsync for SqliteBackend {
    async fn from_config_async(config: &MemoryConfig) -> core::error::Result<Self> {
        let path = config.path.as_deref().unwrap_or(":memory:");
        SqliteBackend::new(path).await
    }
}

#[cfg(feature = "redis")]
#[async_trait::async_trait]
impl MemoryFromConfigAsync for RedisBackend {
    async fn from_config_async(config: &MemoryConfig) -> core::error::Result<Self> {
        let url = config.url.as_deref().unwrap_or("redis://localhost:6379");
        let mut backend = RedisBackend::new(url).await?;

        if let Some(ref prefix) = config.prefix {
            backend = backend.with_prefix(prefix);
        }

        if let Some(ttl_seconds) = config.ttl_seconds {
            backend = backend.with_ttl_seconds(ttl_seconds);
        }

        Ok(backend)
    }
}

#[cfg(feature = "qdrant")]
#[async_trait::async_trait]
impl MemoryFromConfigAsync for QdrantBackend {
    async fn from_config_async(config: &MemoryConfig) -> core::error::Result<Self> {
        let grpc_url = config
            .grpc_url
            .as_deref()
            .unwrap_or("http://localhost:6334");
        let collection = config.collection.as_deref().unwrap_or("enki_memory");
        let dimensions = config.dimensions.unwrap_or(384);

        QdrantBackend::new(grpc_url, collection, dimensions).await
    }
}

#[cfg(feature = "chromadb")]
#[async_trait::async_trait]
impl MemoryFromConfigAsync for ChromaDBBackend {
    async fn from_config_async(config: &MemoryConfig) -> core::error::Result<Self> {
        let url = config.url.as_deref().unwrap_or("http://localhost:8000");
        let collection = config.collection.as_deref().unwrap_or("enki_memory");

        ChromaDBBackend::new(url, collection).await
    }
}

#[cfg(feature = "pinecone")]
#[async_trait::async_trait]
impl MemoryFromConfigAsync for PineconeBackend {
    async fn from_config_async(config: &MemoryConfig) -> core::error::Result<Self> {
        let api_key = config
            .api_key
            .as_deref()
            .ok_or_else(|| core::error::Error::MemoryError("Pinecone requires api_key".into()))?;
        let collection = config.collection.as_deref().unwrap_or("enki-memory");
        let dimensions = config.dimensions.unwrap_or(1536);

        PineconeBackend::new(api_key, collection, dimensions).await
    }
}