2 releases
| 0.0.2 | Feb 22, 2026 |
|---|---|
| 0.0.1 | Feb 22, 2026 |
#219 in Asynchronous
705KB
17K
SLoC
orra
A Rust library for building AI agents. Handles sessions, context management, tool execution, hooks, and the agent loop so you can focus on your domain logic.
Why this exists
Most AI/LLM libraries focus on model abstraction or prompt chaining. What they don't give you is a good answer to: "How do I run this for multiple users without their conversations bleeding into each other? How do I control which tools each user can access? How do I keep context from blowing past the token limit?"
agentic is built around those problems. It's designed for applications where AI is a feature, not the whole product — think project management tools, internal dashboards, customer support platforms, developer tooling. Places where you already have domain-specific data and operations, and you want users to interact with them through natural language.
Quick start
[dependencies]
orra = { version = "0.0.2", features = ["claude"] }
use std::sync::Arc;
use orra::context::CharEstimator;
use orra::message::Message;
use orra::namespace::Namespace;
use orra::policy::PolicyRegistry;
use orra::providers::claude::ClaudeProvider;
use orra::runtime::{Runtime, RuntimeConfig};
use orra::store::InMemoryStore;
use orra::tool::ToolRegistry;
let provider = Arc::new(ClaudeProvider::new("your-api-key", "claude-sonnet-4-5-20250929"));
let store = Arc::new(InMemoryStore::new());
let runtime = Runtime::new(
provider,
store,
ToolRegistry::new(),
PolicyRegistry::default(),
CharEstimator::default(),
RuntimeConfig {
system_prompt: Some("You are a helpful assistant.".into()),
..RuntimeConfig::default()
},
);
let ns = Namespace::new("user").child("bob");
let result = runtime.run(&ns, Message::user("Hello!")).await?;
println!("{}", result.final_message.content);
Feature flags
| Feature | What it enables |
|---|---|
claude |
Anthropic Claude API with streaming |
openai |
OpenAI-compatible API (GPT-4o, Ollama, vLLM, etc.) |
discord |
Discord gateway channel and bot tools |
mcp |
Model Context Protocol client for external tool servers |
documents |
Document knowledge store with TF-IDF search |
github |
GitHub issue and PR tools |
claude-code |
Claude Code CLI delegation tool |
web-fetch |
Web page fetching with HTML-to-text extraction |
web-search |
Brave Search API tool |
parallel-tools |
Concurrent tool execution |
file-store |
File-based session persistence |
gateway |
HTTP/WebSocket gateway channel |
Core concepts
Namespaced sessions. Every conversation lives under a namespace like tenant:acme:user:bob. Sessions are fully isolated. Policies cascade from parent namespaces so you can set access control at the org level.
Token-aware context management. The runtime tracks token budgets and auto-truncates old messages when context gets too long, keeping the system prompt and recent messages intact.
Hooks. The Hook trait lets you intercept any point in the agent lifecycle — before/after LLM calls, before/after tool execution, session load/save. The library ships with three built-in hooks:
hooks::logging— logs runtime activity and tracks token usagehooks::approval— gates tool execution on user approval (with per-session "chaos mode" to auto-approve)hooks::working_directory— injects a working directory intoexecandclaude_codetool calls from session metadata
Pluggable everything. The Provider trait wraps any LLM. The Tool trait exposes operations. The SessionStore trait handles persistence. Implement what you need, use the defaults for the rest.
Architecture
+-----------+
| Runtime | orchestrates the agent loop
+-----+-----+
|
+-------+-------+-------+-------+
| | | | |
Provider Tools Store Hooks Policies
(LLM) (ops) (persist) (lifecycle) (access)
Modules
Runtime:
runtime— Agent loop: load session, build context, call LLM, execute tools, saveprovider—ProviderandStreamingProvidertraitstool—Tooltrait andToolRegistrystore—SessionStoretrait withInMemoryStorecontext— Token budgets and automatic context truncationnamespace— Hierarchical session keysmessage— Messages, tool calls, tool resultspolicy— Per-namespace tool allow/deny listshook— Lifecycle hook trait andHookRegistryhooks— Built-in hook implementations (logging, approval, working directory)
Providers:
providers::claude— Anthropic Claude API with streamingproviders::openai— OpenAI Chat Completions (works with any compatible endpoint)providers::dynamic— Hot-swappable provider wrapper
Tools:
tools::exec— Shell command execution with allowlist and timeouttools::web_fetch— Web page fetching with HTML-to-text extractiontools::web_search— Brave Search APItools::browser— Web page reading with readability extractiontools::discord— Discord bot tools (channels, messages, guild info)tools::github— GitHub issue/PR managementtools::documents— Document search and retrieval (TF-IDF)tools::image_gen— DALL-E image generationtools::delegation— Sub-agent spawning for complex subtaskstools::memory— Remember/recall/forget for persistent contexttools::claude_code— Delegate coding tasks to the Claude Code CLItools::cron— AI-managed scheduled tasks
Channels:
channels::discord— Discord Gateway WebSocketchannels::gateway— HTTP REST + WebSocket gateway
Infrastructure:
mcp— Model Context Protocol client for external tool serversmemory— Long-term memory with keyword searchscheduler— Cron-based task schedulingrouting— Message routing across channels and runtimesmetrics— Counters, gauges, histograms with pluggable sinksauth— OAuth2 token managementvoice— TTS and STT traits
Hooks
Register hooks to observe or modify behavior at any lifecycle point:
use std::sync::Arc;
use orra::hook::HookRegistry;
use orra::hooks::logging::LoggingHook;
use orra::hooks::working_directory::WorkingDirectoryHook;
let mut hooks = HookRegistry::new();
hooks.register(Arc::new(LoggingHook::new()));
hooks.register(Arc::new(WorkingDirectoryHook::new()));
let mut runtime = Runtime::new(/* ... */);
runtime.set_hooks(hooks);
The approval hook requires a channel to communicate with your UI:
use orra::hooks::approval::{ApprovalHook, ApprovalRequest};
let (tx, rx) = tokio::sync::mpsc::channel::<ApprovalRequest>(32);
hooks.register(Arc::new(ApprovalHook::new(tx)));
// In your WebSocket/UI handler, receive from `rx` and send back
// true (approved) or false (denied) via the oneshot channel.
Using with OpenAI
use orra::providers::openai::OpenAIProvider;
// OpenAI
let provider = OpenAIProvider::new("your-key", "gpt-4o");
// Local server (Ollama, vLLM, etc.)
let provider = OpenAIProvider::new("not-needed", "llama3")
.with_api_url("http://localhost:11434/v1");
MCP integration
Connect to external tool servers via the Model Context Protocol:
use orra::mcp::transport::StdioTransport;
use orra::mcp::register_mcp_tools;
let transport = Arc::new(
StdioTransport::spawn("npx", &["-y", "@modelcontextprotocol/server-filesystem", "/tmp"])
.await?
);
let mut tools = ToolRegistry::new();
register_mcp_tools(&mut tools, transport).await?;
Examples
See the examples/ directory:
- kanban — CLI kanban board assistant with multi-user namespacing and role-based tool policies
- discord_bot — Discord bot using the Discord channel and tools
- github_issues — Manage GitHub issues through natural language
ANTHROPIC_API_KEY=... cargo run --features claude --example kanban
ANTHROPIC_API_KEY=... DISCORD_TOKEN=... cargo run --features claude,discord --example discord_bot
ANTHROPIC_API_KEY=... GITHUB_TOKEN=... cargo run --features claude,github --example github_issues -- owner/repo
License
MIT
Dependencies
~4–15MB
~282K SLoC