agent-kernel 0.1.0

Minimal Agent orchestration kernel for multi-agent discussion
Documentation

agent-kernel

Crates.io License Rust 1.87+

Minimal Rust kernel for multi-agent LLM discussion. One trait, one function, full observability.


Quick Start

Step 1 — Add the dependency:

[dependencies]
agent-kernel = "0.1"
tokio = { version = "1", features = ["full"] }
tokio-util = "0.7"
anyhow = "1"

Step 2 — Implement AgentRuntime:

use agent_kernel::{Agent, AgentRuntime, BoxFuture, Message};

struct MyRuntime { /* your HTTP client, API key, etc. */ }

impl AgentRuntime for MyRuntime {
    fn respond<'a>(
        &'a self,
        agent: &'a Agent,
        history: &'a [Message],
    ) -> BoxFuture<'a, anyhow::Result<String>> {
        Box::pin(async move {
            // Call your LLM provider here.
            // Convert `history` to the provider's format inside this block
            // (late-binding principle: conversion only at the boundary).
            Ok(format!("[{}] response after {} messages", agent.name, history.len()))
        })
    }
}

Step 3 — Call discuss():

use agent_kernel::{Agent, AgentEvent, discuss};
use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let runtime = MyRuntime { /* ... */ };

    let agents = vec![
        Agent { name: "alice".into(), soul_md: "You are Alice.".into(), model: "gpt-4o".into() },
        Agent { name: "bob".into(),   soul_md: "You are Bob.".into(),   model: "gpt-4o".into() },
    ];

    let (tx, mut rx) = mpsc::channel::<AgentEvent>(64);
    let cancel = CancellationToken::new();

    let summary = discuss(&runtime, &agents, "What is Rust?", 3, cancel, tx).await?;

    while let Ok(event) = rx.try_recv() {
        println!("{event:?}");
    }
    println!("Summary: {summary}");
    Ok(())
}

See examples/simple_discussion.rs for a runnable version with a MockRuntime.


Architecture

┌─────────────────────────────────────────────────┐
│            Product shell (your app)             │
│                                                 │
│  Discord Bot  │  HTTP API  │  CLI  │  Tests     │
└───────────────────────┬─────────────────────────┘
                        │  depends on
┌───────────────────────┴─────────────────────────┐
│              agent-kernel (this crate)          │
│                                                 │
│  Agent struct    AgentRuntime trait             │
│  discuss()       AgentEvent stream              │
│  Message/Role    EvolutionRuntime trait         │
│                                                 │
│  deps: tokio(sync) + futures + anyhow           │
│  NOT included: axum / serenity / reqwest /      │
│                rusqlite / serde / clap          │
└─────────────────────────────────────────────────┘
                        │  implements
┌───────────────────────┴─────────────────────────┐
│           LLM Provider (your impl)              │
│                                                 │
│  Anthropic  │  OpenAI  │  OpenRouter  │  Ollama │
└─────────────────────────────────────────────────┘

Kernel boundary rule: agent-kernel depends only on tokio (sync feature), futures, anyhow, and tokio-util. Everything else belongs in the product shell.

Design principles (inherited from pi-mono):

Principle How it applies here
Extreme minimalism AgentRuntime has exactly 1 method; discuss() is the only orchestration primitive
Full trust Caller passes rounds: usize directly — no policy wrapper
Observability first AgentEvent is a first-class output via mpsc::Sender
Late binding Message stays in kernel format until your respond() converts it
Persistent self-evolution EvolutionRuntime trait — feedback, memory, SOUL.md iteration as kernel primitives

API Reference

Agent

pub struct Agent {
    pub name: String,    // unique identifier
    pub soul_md: String, // full SOUL.md system prompt
    pub model: String,   // LLM model ID, e.g. "anthropic/claude-sonnet-4-6"
}

SOUL.md is the sole configuration source for an agent. No built-in role or persona concept.

AgentRuntime

pub trait AgentRuntime: Send + Sync {
    fn respond<'a>(
        &'a self,
        agent: &'a Agent,
        history: &'a [Message],
    ) -> BoxFuture<'a, anyhow::Result<String>>;
}

Implement this to connect the kernel to any LLM provider. See examples/custom_runtime.rs for the full pattern.

discuss()

pub async fn discuss(
    runtime: &dyn AgentRuntime,
    agents: &[Agent],
    topic: &str,
    rounds: usize,
    cancel: CancellationToken,
    events: mpsc::Sender<AgentEvent>,
) -> anyhow::Result<String>

The only orchestration primitive. Runs exactly rounds rounds — no convergence logic (caller decides policy). agents[0] is the primary agent and produces the final summary. Returns the primary agent's last response.

AgentEvent

Emitted via the mpsc::Sender during a discussion:

Variant When
Progress { current_round, max_rounds } Start of each round
Round { round, agent_name, content } Each agent's response
Converged { reason } Reserved for future convergence detection
Summary { content } Final summary from agents[0]
Cancelled CancellationToken was triggered
Completed All rounds finished
Evolved { agent, old_version, new_version } SOUL.md was evolved (Phase 2)

Message and Role

pub struct Message { pub role: Role, pub content: String }
pub enum Role { System, User, Assistant }

Internal message format. Convert to your provider's format inside AgentRuntime::respond().

EvolutionRuntime (Phase 2)

pub trait EvolutionRuntime: Send + Sync {
    fn feedback<'a>(...) -> BoxFuture<'a, anyhow::Result<f64>>;
    fn remember<'a>(...) -> BoxFuture<'a, anyhow::Result<()>>;
    fn evolve<'a>(...)   -> BoxFuture<'a, anyhow::Result<String>>;
    fn rollback<'a>(...) -> BoxFuture<'a, anyhow::Result<String>>;
}

Persistent self-evolution primitives. Implement in your product shell to wire up feedback scoring, memory storage, SOUL.md optimization, and version rollback. The kernel defines the contract; you provide the storage and LLM calls.

BoxFuture

pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;

Used as the return type of AgentRuntime::respond() and EvolutionRuntime methods. Avoids the async_trait macro dependency.


Examples

# MockRuntime + 2 agents + 3 rounds
cargo run --example simple_discussion

# Pattern for a real HTTP-based runtime
cargo run --example custom_runtime

License

Licensed under either of MIT or Apache-2.0 at your option.