kernex-runtime 0.4.0

The Rust runtime for AI agents — composable engine with sandbox, providers, learning, and pipelines
Documentation

Kernex is a composable Rust framework for building AI agent systems. It provides sandboxed execution, multi-provider AI backends, persistent memory with reward-based learning, skill loading, and topology-driven multi-agent pipelines — all as independent, embeddable crates.

Prerequisites

  • Rust 1.74+ — Install from rustup.rs
  • Cargo — Comes with Rust

For running examples:

  • Ollama (optional) — For local AI without API keys. Install
  • Node.js 18+ (optional) — For MCP-based skills. Install

Features

  • Sandbox-first execution — OS-level protection via Seatbelt (macOS) and Landlock (Linux) combined with highly configurable SandboxProfile allow/deny lists
  • 6 AI providers — Claude Code CLI, Anthropic, OpenAI, Ollama, OpenRouter, Gemini
  • OpenAI-compatible base URL — works with LiteLLM, Cerebras, DeepSeek, Hugging Face, and any compatible endpoint
  • Dynamic instantiation — instantiate any provider from a config map at runtime via ProviderFactory
  • Agentic run loopRuntime::run() with configurable turn limits; providers handle tool dispatch internally
  • Hook system — intercept every tool call with HookRunner: allow, block, audit, or rate-limit before execution
  • Typed tool schemas — Auto-generated JSON Schema for tool parameters via schemars
  • MCP client — stdio-based Model Context Protocol for external tool integration
  • Persistent memory — SQLite-backed conversations, facts, reward-based learning, scheduled tasks
  • Skills.sh compatible — load skills from SKILL.md files with TOML/YAML frontmatter; 12 builtin agent personas included
  • Multi-agent pipelines — TOML-defined topologies with corrective loops and file-mediated handoffs
  • Trait-based composition — implement Provider or Store to plug in your own backends
  • Secure by default — API keys protected in memory via secrecy::SecretString; prompt caching support for Anthropic

Architecture

Kernex is a Cargo workspace with 7 composable crates:

graph TD
    classDef facade fill:#2B6CB0,stroke:#2C5282,stroke-width:2px,color:#fff
    classDef core fill:#4A5568,stroke:#2D3748,stroke-width:2px,color:#fff
    classDef impl fill:#319795,stroke:#285E61,stroke-width:2px,color:#fff

    R[kernex-runtime]:::facade
    C[kernex-core]:::core
    S[kernex-sandbox]:::impl
    P[kernex-providers]:::impl
    M[kernex-memory]:::impl
    K[kernex-skills]:::impl
    PL[kernex-pipelines]:::impl

    R --> C
    R --> S
    R --> P
    R --> M
    R --> K
    R --> PL

    P --> C
    M --> C
    K --> C
    PL --> C
    S -.o|OS Protection| P
Crate crates.io Description
kernex-core Shared types, traits, config, sanitization
kernex-sandbox OS-level sandbox (Seatbelt + Landlock)
kernex-providers 6 AI providers, tool executor, MCP client
kernex-memory SQLite memory, FTS5 search, reward learning
kernex-skills Skill/project loader, trigger matching
kernex-pipelines TOML topology, multi-agent orchestration
kernex-runtime Facade crate with RuntimeBuilder

Quick Start

Add Kernex to your project:

[dependencies]
kernex-runtime = "0.3"
kernex-core = "0.3"
kernex-providers = "0.3"
tokio = { version = "1", features = ["full"] }

Send a message and get a response with persistent memory:

use kernex_runtime::RuntimeBuilder;
use kernex_core::traits::Provider;
use kernex_core::message::Request;
use kernex_providers::factory::ProviderFactory;
use kernex_providers::ProviderConfig;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Elegant, environment-based construction via `from_env()` 
    // Uses $KERNEX_DATA_DIR, $KERNEX_SYSTEM_PROMPT, and $KERNEX_CHANNEL
    let runtime = RuntimeBuilder::from_env().build().await?;

    let mut config = ProviderConfig::default();
    config.model = Some("llama3.2".to_string());
    config.base_url = Some("http://localhost:11434".to_string());

    let provider = ProviderFactory::create("ollama", Some(serde_json::to_value(config)?))?;


    let request = Request::text("user-1", "What is Rust?");
    let response = runtime.complete(&provider, &request).await?;
    println!("{}", response.text);

    Ok(())
}

runtime.complete() handles the full pipeline: build context from memory → enrich with skills → send to provider → save exchange.

Use individual crates for fine-grained control:

use kernex_providers::openai::OpenAiProvider;
use kernex_memory::Store;
use kernex_skills::load_skills;
use kernex_pipelines::load_topology;

Runtime API

RuntimeBuilder assembles all subsystems. All options are optional — defaults work out of the box:

use std::sync::Arc;
use kernex_runtime::RuntimeBuilder;

let runtime = RuntimeBuilder::new()
    .data_dir("~/.my-agent")         // persistent data root, default: ~/.kernex
    .system_prompt("You are...")     // base system prompt prepended every turn
    .channel("cli")                  // channel ID for memory scoping
    .project("my-project")           // project scope for facts and lessons
    .hook_runner(Arc::new(my_hooks)) // lifecycle hook runner (see Hooks section)
    .build()
    .await?;

Two completion methods:

Method When to use
runtime.complete(&provider, &request) Single context-enriched turn. Memory is built, provider runs its internal loop.
runtime.run(&provider, &request, &config) Explicit turn-limit control. Sets max_turns on the context and fires on_stop after completion.
use kernex_core::run::{RunConfig, RunOutcome};

let config = RunConfig { max_turns: 20 };

match runtime.run(&provider, &request, &config).await? {
    RunOutcome::EndTurn(response) => println!("{}", response.text),
    RunOutcome::MaxTurns => eprintln!("turn limit reached"),
}

Hooks

Implement HookRunner to intercept every tool call across all providers:

use kernex_core::hooks::{HookRunner, HookOutcome};
use async_trait::async_trait;
use serde_json::Value;

#[derive(Debug)]
struct AuditHooks;

#[async_trait]
impl HookRunner for AuditHooks {
    async fn pre_tool(&self, tool_name: &str, _input: &Value) -> HookOutcome {
        tracing::info!("tool: {tool_name}");
        HookOutcome::Allow
        // or: HookOutcome::Blocked("reason".into())  to cancel execution
    }
    async fn post_tool(&self, tool_name: &str, _result: &str, is_error: bool) {
        tracing::debug!("{tool_name} finished, error={is_error}");
    }
    async fn on_stop(&self, _final_text: &str) {}
}

let runtime = RuntimeBuilder::new()
    .hook_runner(Arc::new(AuditHooks))
    .build().await?;

pre_tool runs before dispatch; returning Blocked cancels the tool and returns the reason as a tool error. post_tool fires after completion. on_stop fires at the end of Runtime::run().

Providers

Kernex ships with 6 built-in AI providers:

Provider Module API Key Required
Claude Code CLI claude_code No (uses local CLI)
Anthropic anthropic Yes
OpenAI openai Yes
Ollama ollama No (local)
OpenRouter openrouter Yes
Gemini gemini Yes

Prompt caching (Anthropic)

Place KERNEX_CACHE_BOUNDARY in your system prompt to split it into a cached stable prefix and a dynamic per-turn suffix. Anthropic caches the stable prefix across turns, reducing token costs on long sessions.

use kernex_providers::anthropic::CACHE_BOUNDARY;

let system = format!(
    "You are a coding assistant.\n{CACHE_BOUNDARY}\nActive project: {}.",
    project_name
);

let runtime = RuntimeBuilder::new()
    .system_prompt(&system)
    .build().await?;

Text before the marker gets cache_control: ephemeral. Text after is sent as a plain block each turn. The anthropic-beta: prompt-caching-2024-07-31 header is added automatically when the boundary is present.

Using any OpenAI-compatible endpoint

The OpenAI provider accepts a custom base_url, making it work with any compatible service:

use kernex_providers::openai::OpenAiProvider;

// LiteLLM proxy
let provider = OpenAiProvider::from_config(
    "http://localhost:4000/v1".into(),
    "sk-...".into(),
    "gpt-4".into(),
    None,
)?;

// DeepSeek
let provider = OpenAiProvider::from_config(
    "https://api.deepseek.com/v1".into(),
    "sk-...".into(),
    "deepseek-chat".into(),
    None,
)?;

// Cerebras
let provider = OpenAiProvider::from_config(
    "https://api.cerebras.ai/v1".into(),
    "csk-...".into(),
    "llama3.1-70b".into(),
    None,
)?;

Implementing a custom provider

use kernex_core::traits::Provider;
use kernex_core::context::Context;
use kernex_core::message::Response;

#[async_trait::async_trait]
impl Provider for MyProvider {
    fn name(&self) -> &str { "my-provider" }
    fn requires_api_key(&self) -> bool { true }
    async fn is_available(&self) -> bool { true }

    async fn complete(&self, context: &Context) -> kernex_core::error::Result<Response> {
        // Your implementation here
        todo!()
    }
}

Project Structure

~/.kernex/                  # Default data directory
├── config.toml             # Runtime configuration
├── memory.db               # SQLite persistent memory
├── skills/                 # Skill definitions
│   └── my-skill/
│       └── SKILL.md        # TOML/YAML frontmatter + instructions
├── projects/               # Project definitions
│   └── my-project/
│       └── AGENTS.md       # Project instructions + skills (or ROLE.md)
└── topologies/             # Pipeline definitions
    └── my-pipeline/
        ├── TOPOLOGY.toml   # Phase definitions
        └── agents/         # Agent .md files

Examples

Example Description Prerequisites Run
simple_chat Interactive chat with local LLM Ollama running cargo run --example simple_chat
memory_agent Persistent facts and lessons None cargo run --example memory_agent
skill_loader Load skills and match triggers None cargo run --example skill_loader
pipeline_loader Multi-agent topology demo None cargo run --example pipeline_loader

All examples are in crates/kernex-runtime/examples/.

Skills

Kernex supports Skills.sh compatible skills. 21 ready-to-use skills included: 9 tool-integration skills and 12 builtin agent personas.

Tool skills

Skill Backend Description
filesystem MCP Secure file operations
git MCP Repository operations
playwright MCP Browser automation
github MCP GitHub API integration
postgres MCP PostgreSQL read-only access
sqlite MCP SQLite read/write access
brave-search MCP Web search via Brave API
pdf CLI Extract text from PDFs
webhook CLI Send HTTP webhooks

See examples/skills/ for documentation and templates.

Builtin agent skills

12 agent persona skills ship in examples/skills/builtin/. Install all at once:

cp -r examples/skills/builtin/* ~/.kernex/skills/
Skill Purpose
frontend-developer UI/UX, component architecture
backend-architect APIs, databases, system design
security-engineer Threat modeling, secure code review
devops-automator CI/CD, infrastructure, containers
reality-checker Assumptions audit, edge case analysis
api-tester API contract and integration testing
performance-benchmarker Profiling and optimization
senior-developer Cross-domain code review
ai-engineer ML/AI integration patterns
accessibility-auditor WCAG, a11y review
agents-orchestrator Multi-agent workflow design
project-manager Planning, scope, delivery

Skills activate automatically when a message matches their triggers frontmatter. No configuration needed beyond placing the file.

Creating custom skills

# Copy the template
cp -r examples/skills/_template ~/.kernex/skills/my-skill

# Edit SKILL.md with your triggers and MCP config
# See examples/skills/README.md for full guide

Common Errors

"unknown provider type: xyz"

The provider name must match exactly. Valid values: openai, anthropic, ollama, gemini, openrouter, claude-code.

"config error: failed to create data dir"

Ensure ~/.kernex/ is writable:

mkdir -p ~/.kernex && chmod 755 ~/.kernex

"provider error: timeout"

The provider took longer than the configured timeout (default 120s). Check your internet connection or increase the timeout in ProviderConfig.

"Ollama not available"

Ollama server isn't running. Start it in a separate terminal:

ollama serve

"model not found" (Ollama)

Pull the model first:

ollama pull llama3.2

Development

# Build all crates
cargo build --workspace

# Run all tests
cargo test --workspace

# Lint
cargo clippy --workspace -- -D warnings

# Format
cargo fmt --check

Versioning

This project follows Semantic Versioning. All crates in the workspace share the same version number.

  • MAJOR — breaking API changes
  • MINOR — new features, backward compatible
  • PATCH — bug fixes, backward compatible

See CHANGELOG.md for release history.

Contributing

Contributions are welcome. Please:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feat/my-feature)
  3. Ensure all checks pass: cargo build && cargo clippy -- -D warnings && cargo test && cargo fmt --check
  4. Commit with conventional commits (feat:, fix:, refactor:, docs:, test:)
  5. Open a Pull Request

License

Licensed under either of:

at your option.