agent-kernel 0.1.0

Minimal Agent orchestration kernel for multi-agent discussion
Documentation
# agent-kernel

[![Crates.io](https://img.shields.io/crates/v/agent-kernel.svg)](https://crates.io/crates/agent-kernel)
[![License](https://img.shields.io/badge/license-MIT%20%2F%20Apache--2.0-blue.svg)](LICENSE-MIT)
[![Rust 1.87+](https://img.shields.io/badge/rust-1.87%2B-orange.svg)](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)):

| 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`

```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:

| 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`

```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.