#mcp #llm #mcp-tool #ai-agent

orra

Context-aware agent session management for any application

2 releases

0.0.2 Feb 22, 2026
0.0.1 Feb 22, 2026

#219 in Asynchronous

MIT license

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 usage
  • hooks::approval — gates tool execution on user approval (with per-session "chaos mode" to auto-approve)
  • hooks::working_directory — injects a working directory into exec and claude_code tool 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, save
  • providerProvider and StreamingProvider traits
  • toolTool trait and ToolRegistry
  • storeSessionStore trait with InMemoryStore
  • context — Token budgets and automatic context truncation
  • namespace — Hierarchical session keys
  • message — Messages, tool calls, tool results
  • policy — Per-namespace tool allow/deny lists
  • hook — Lifecycle hook trait and HookRegistry
  • hooks — Built-in hook implementations (logging, approval, working directory)

Providers:

  • providers::claude — Anthropic Claude API with streaming
  • providers::openai — OpenAI Chat Completions (works with any compatible endpoint)
  • providers::dynamic — Hot-swappable provider wrapper

Tools:

  • tools::exec — Shell command execution with allowlist and timeout
  • tools::web_fetch — Web page fetching with HTML-to-text extraction
  • tools::web_search — Brave Search API
  • tools::browser — Web page reading with readability extraction
  • tools::discord — Discord bot tools (channels, messages, guild info)
  • tools::github — GitHub issue/PR management
  • tools::documents — Document search and retrieval (TF-IDF)
  • tools::image_gen — DALL-E image generation
  • tools::delegation — Sub-agent spawning for complex subtasks
  • tools::memory — Remember/recall/forget for persistent context
  • tools::claude_code — Delegate coding tasks to the Claude Code CLI
  • tools::cron — AI-managed scheduled tasks

Channels:

  • channels::discord — Discord Gateway WebSocket
  • channels::gateway — HTTP REST + WebSocket gateway

Infrastructure:

  • mcp — Model Context Protocol client for external tool servers
  • memory — Long-term memory with keyword search
  • scheduler — Cron-based task scheduling
  • routing — Message routing across channels and runtimes
  • metrics — Counters, gauges, histograms with pluggable sinks
  • auth — OAuth2 token management
  • voice — 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