# agent-kernel
[](https://crates.io/crates/agent-kernel)
[](LICENSE-MIT)
[](https://www.rust-lang.org)
Minimal Rust kernel for multi-agent LLM discussion. One trait, one function, full observability.
---
## Quick Start
**Step 1 — Add the dependency:**
```toml
[dependencies]
agent-kernel = "0.1"
tokio = { version = "1", features = ["full"] }
tokio-util = "0.7"
anyhow = "1"
```
**Step 2 — Implement `AgentRuntime`:**
```rust
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()`:**
```rust
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`](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](https://github.com/badlogic/pi-mono)):
| 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`
```rust
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`
```rust
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`](examples/custom_runtime.rs) for the full pattern.
### `discuss()`
```rust
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:
| `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`
```rust
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)
```rust
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`
```rust
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
```bash
# 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](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE) at your option.