enki-runtime 0.1.4

A Rust-based agent mesh framework for building local and distributed AI agent systems
Documentation
//! Multi-Agent with Separate Memory Example
//!
//! This example demonstrates multiple agents with **separate isolated memory systems**:
//! - Each agent has its own memory backend
//! - Agents don't share knowledge unless explicitly passed
//! - Shows how to query different agents independently
//!
//! # Prerequisites
//!
//! 1. Install Ollama from https://ollama.ai
//! 2. Pull the gemma3 model: `ollama pull gemma3:latest`
//! 3. Ensure Ollama is running (default: http://127.0.0.1:11434)
//!
//! # Running
//!
//! ```bash
//! cargo run --example multi_agent_memory
//! ```

use enki_runtime::config::{AgentConfig, MemoryConfig};
use enki_runtime::core::agent::AgentContext;
use enki_runtime::core::error::Result;
use enki_runtime::core::memory::{Memory, MemoryEntry};
use enki_runtime::llm::LlmAgent;
use enki_runtime::memory::InMemoryBackend;
use enki_runtime::{LlmAgentFromConfig, MemoryFromConfig};
use serde_json::json;

/// A simple agent with its own isolated memory
struct MemoryAgent {
    name: String,
    agent: LlmAgent,
    memory: InMemoryBackend,
}

impl MemoryAgent {
    /// Create a new agent with its own memory from config
    fn new(config: AgentConfig, memory_config: MemoryConfig) -> Result<Self> {
        let name = config.name.clone();
        let agent = LlmAgent::from_config(config)?;
        let memory = InMemoryBackend::from_config(&memory_config);

        println!("✓ Created agent '{}' with isolated memory", name);

        Ok(Self {
            name,
            agent,
            memory,
        })
    }

    /// Store knowledge in this agent's memory
    async fn remember(&self, key: &str, content: &str) -> Result<String> {
        let entry = MemoryEntry::new(content).with_metadata("key", json!(key));
        let id = self.memory.store(entry).await?;
        println!("  📝 {} remembered: '{}'", self.name, key);
        Ok(id)
    }

    /// Query this agent with its own memory context
    async fn ask(&mut self, question: &str, ctx: &mut AgentContext) -> Result<String> {
        println!("\n🔍 Asking {}: {}", self.name, question);

        // Search agent's own memory for context
        let query = enki_runtime::MemoryQuery::new()
            .with_semantic_query(question)
            .with_limit(3);
        let memories = self.memory.search(query).await?;

        // Build context from this agent's memory
        let context = if memories.is_empty() {
            "No relevant information in memory.".to_string()
        } else {
            memories
                .iter()
                .map(|m| m.content.clone())
                .collect::<Vec<_>>()
                .join("\n\n")
        };

        println!("  📚 {} found {} memories", self.name, memories.len());

        // Ask the LLM with the agent's context
        let prompt = format!(
            "Based on your knowledge:\n{}\n\nQuestion: {}\n\nAnswer concisely:",
            context, question
        );

        let response = self
            .agent
            .send_message_and_get_response(&prompt, ctx)
            .await?;

        Ok(response)
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    println!("=== Multi-Agent with Separate Memory ===\n");
    println!("This example shows multiple agents with isolated memory systems.\n");

    // === Create Agent 1: Tech Expert ===
    let tech_config = AgentConfig::new("tech_expert", "ollama::gemma3:latest")
        .with_system_prompt(
            "You are a technology expert. Answer questions about technology \
             based on your knowledge. Be concise.",
        )
        .with_temperature(0.3);

    let tech_memory = MemoryConfig::in_memory()
        .with_max_entries(100)
        .with_ttl_seconds(3600);

    let mut tech_agent = MemoryAgent::new(tech_config, tech_memory)?;

    // === Create Agent 2: History Expert ===
    let history_config = AgentConfig::new("history_expert", "ollama::gemma3:latest")
        .with_system_prompt(
            "You are a history expert. Answer questions about historical events \
             based on your knowledge. Be concise.",
        )
        .with_temperature(0.3);

    let history_memory = MemoryConfig::in_memory()
        .with_max_entries(100)
        .with_ttl_seconds(3600);

    let mut history_agent = MemoryAgent::new(history_config, history_memory)?;

    println!("\n--- Storing knowledge in separate memories ---\n");

    // Store knowledge in tech agent's memory
    tech_agent
        .remember(
            "rust_lang",
            "Rust is a systems programming language focused on safety and performance. \
             It was created by Mozilla and first released in 2015.",
        )
        .await?;

    tech_agent
        .remember(
            "enki_runtime",
            "Enki Runtime is a Rust-based agent framework for building AI systems. \
             It supports 13+ LLM providers and has a pluggable memory system.",
        )
        .await?;

    // Store knowledge in history agent's memory
    history_agent
        .remember(
            "moon_landing",
            "The Apollo 11 moon landing occurred on July 20, 1969. \
             Neil Armstrong was the first human to walk on the moon.",
        )
        .await?;

    history_agent
        .remember(
            "world_war_2",
            "World War 2 lasted from 1939 to 1945. It was the deadliest conflict \
             in human history, involving most of the world's nations.",
        )
        .await?;

    // Create contexts for each agent
    let mut tech_ctx = AgentContext::new("tech_session".to_string(), None);
    let mut history_ctx = AgentContext::new("history_session".to_string(), None);

    println!("\n--- Querying agents with separate memories ---");

    // Ask tech agent about technology (should have context)
    println!("\n{}", "=".repeat(50));
    match tech_agent
        .ask("What is Enki Runtime?", &mut tech_ctx)
        .await
    {
        Ok(response) => println!("💬 tech_expert:\n{}", response),
        Err(e) => eprintln!("❌ Error: {}", e),
    }
    println!("{}", "=".repeat(50));

    // Ask history agent about history (should have context)
    println!("\n{}", "=".repeat(50));
    match history_agent
        .ask("When did humans first walk on the moon?", &mut history_ctx)
        .await
    {
        Ok(response) => println!("💬 history_expert:\n{}", response),
        Err(e) => eprintln!("❌ Error: {}", e),
    }
    println!("{}", "=".repeat(50));

    // Demonstrate isolation: Ask tech agent about history (no context!)
    println!("\n--- Demonstrating Memory Isolation ---");
    println!("\n{}", "=".repeat(50));
    match tech_agent.ask("When did WW2 end?", &mut tech_ctx).await {
        Ok(response) => {
            println!(
                "💬 tech_expert (asked about history - no context!):\n{}",
                response
            );
        }
        Err(e) => eprintln!("❌ Error: {}", e),
    }
    println!("{}", "=".repeat(50));

    // Ask history agent about tech (no context!)
    println!("\n{}", "=".repeat(50));
    match history_agent
        .ask("What is Rust programming language?", &mut history_ctx)
        .await
    {
        Ok(response) => {
            println!(
                "💬 history_expert (asked about tech - no context!):\n{}",
                response
            );
        }
        Err(e) => eprintln!("❌ Error: {}", e),
    }
    println!("{}", "=".repeat(50));

    println!("\n=== Multi-Agent Example Complete ===");
    println!("\nKey takeaway: Each agent has ISOLATED memory!");
    println!("- tech_expert knows about Rust and Enki, not history");
    println!("- history_expert knows about moon landing and WW2, not tech");

    Ok(())
}