Skip to main content
The Rust SDK is the primary interface. The Python/TypeScript SDKs and all API servers are thin wrappers over this same engine. The default embedding path is runtime-backed SessionService, which keeps Rust aligned with the same session lifecycle semantics used by every other Meerkat surface while still avoiding subprocess or JSON-RPC overhead.

Method overview

AreaMethod / TypePurpose
SetupConfig::load()Load configuration from disk
AgentFactory::new(store_root)Create a factory for building agents
open_realm_persistence_in(...)Open a realm-backed persistence bundle
build_persistent_service(factory, config, cap, persistence)Build the runtime-backed persistent session substrate
build_ephemeral_service(factory, config, cap)Build an in-memory Queue-only substrate
Sessionsservice.create_session(req)Create a session and run the first turn
service.start_turn(id, req)Continue an existing session
service.read(id)Read session state
service.list()List active sessions
service.archive(id)Remove a session
Agentagent.run(prompt)Run agent with a prompt
agent.run_with_events(prompt, tx)Run with event streaming
agent.cancel()Cancel the current run

Installation

1

Add the dependency

[dependencies]
meerkat = "0.5.0"
tokio = { version = "1", features = ["full"] }
2

Choose feature flags

The default includes all three LLM providers and nothing else — add subsystems as needed:
[dependencies]
meerkat = "0.5.0"
FeatureDescriptionDefault
anthropicAnthropic Claude API clientYes
openaiOpenAI API clientYes
geminiGoogle Gemini API clientYes
all-providersShorthand for all three providersNo
sqlite-storeSQLite-backed persistent realmsNo
jsonl-storeFile-based session persistenceNo
memory-storeIn-memory session storage (testing)No
session-storePersistent session lifecycle supportNo
session-compactionAuto-compact long conversationsNo
memory-store-sessionSemantic memory indexingNo
commsEd25519 inter-agent messagingNo
mcpMCP protocol client and tool routingNo
skillsComposable knowledge packsNo

Quick start

Production surfaces (CLI, REST, RPC, MCP) use the runtime-backed path where SessionService is substrate and RuntimeSessionAdapter owns keep-alive, Queue/Steer routing, and comms drain. See meerkat-rpc or meerkat-rest for those entry points. For the main production embedding path, use the runtime-backed persistent flow:
use meerkat::{
    AgentFactory, Config, CreateSessionRequest, SessionService,
    build_persistent_service, open_realm_persistence_in,
};
use meerkat_core::service::InitialTurnPolicy;
use meerkat_store::RealmBackend;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = Config::load().await?;
    let realms_root = std::env::current_dir()?.join(".rkat").join("realms");
    let (_manifest, persistence) = open_realm_persistence_in(
        &realms_root,
        "team-alpha",
        Some(RealmBackend::Sqlite),
        None,
    ).await?;
    let factory = AgentFactory::new(realms_root.clone()).runtime_root(realms_root);
    let service = build_persistent_service(factory, config, 64, persistence);

    let result = service.create_session(CreateSessionRequest {
        model: "claude-sonnet-4-5".into(),
        prompt: "What is the capital of France?".into(),
        render_metadata: None,
        system_prompt: Some("You are a helpful assistant.".into()),
        max_tokens: Some(1024),
        event_tx: None,
        skill_references: None,
        initial_turn: InitialTurnPolicy::RunImmediately,
        build: None,
        labels: None,
    }).await?;

    println!("Response: {}", result.text);
    println!("Session ID: {}", result.session_id);
    Ok(())
}
For testing or embedded use where runtime semantics are intentionally not needed, build_ephemeral_service remains available as a direct Queue-only substrate.

Sessions

SessionService is the canonical lifecycle API. All surfaces (CLI, REST, MCP, RPC) route through it.

Multi-turn conversations

use meerkat::{CreateSessionRequest, StartTurnRequest, SessionService};
use meerkat_core::service::{InitialTurnPolicy, HandlingMode};

// Turn 1: create session
let result = service.create_session(CreateSessionRequest {
    model: "claude-sonnet-4-5".into(),
    prompt: "My name is Alice.".into(),
    render_metadata: None,
    system_prompt: Some("You are a helpful assistant with memory.".into()),
    max_tokens: None,
    event_tx: None,
    skill_references: None,
    initial_turn: InitialTurnPolicy::RunImmediately,
    build: None,
    labels: None,
}).await?;

let session_id = result.session_id;

// Turn 2: agent remembers "Alice"
let result = service.start_turn(&session_id, StartTurnRequest {
    prompt: "What's my name?".into(),
    system_prompt: None,
    render_metadata: None,
    handling_mode: HandlingMode::Queue,
    event_tx: None,
    skill_references: None,
    flow_tool_overlay: None,
    additional_instructions: None,
}).await?;

// Read session state
let view = service.read(&session_id).await?;
println!("Messages: {}", view.state.message_count);

// Archive when done
service.archive(&session_id).await?;

Deferred first-turn system prompt override

use meerkat::{CreateSessionRequest, StartTurnRequest, SessionService};
use meerkat_core::service::{HandlingMode, InitialTurnPolicy};

let created = service.create_session(CreateSessionRequest {
    model: "claude-sonnet-4-5".into(),
    prompt: "Let's wait before we run.".into(),
    render_metadata: None,
    system_prompt: Some("You are the original prompt.".into()),
    max_tokens: None,
    event_tx: None,
    skill_references: None,
    initial_turn: InitialTurnPolicy::Defer,
    build: None,
    labels: None,
}).await?;

let result = service.start_turn(&created.session_id, StartTurnRequest {
    prompt: "Now run with the first-turn override.".into(),
    system_prompt: Some("You are the deferred first-turn override.".into()),
    render_metadata: None,
    handling_mode: HandlingMode::Queue,
    event_tx: None,
    skill_references: None,
    flow_tool_overlay: None,
    additional_instructions: None,
}).await?;
system_prompt on StartTurnRequest is only supported for a deferred session’s first turn. Once the session has existing history/messages, turn-time system_prompt overrides are rejected.

Error handling

use meerkat::SessionError;

match service.start_turn(&id, req).await {
    Ok(result) => println!("Response: {}", result.text),
    Err(SessionError::NotFound { id }) => println!("Session {} not found", id),
    Err(SessionError::Busy { id }) => println!("Session {} is busy, retry later", id),
    Err(e) => println!("Error: {}", e),
}

Direct agent APIs

Agent::run(...) and AgentBuilder are expert-level escape hatches. Prefer SessionService for normal embedding so your Rust code follows the same runtime-backed session semantics as CLI, REST, RPC, MCP, Python, and TypeScript.

Running agents directly

Basic run

let result = agent.run("What is 2 + 2?".into()).await?;
println!("Answer: {}", result.text);

Run with event streaming

use tokio::sync::mpsc;
use meerkat::AgentEvent;

let (tx, mut rx) = mpsc::channel::<AgentEvent>(100);

tokio::spawn(async move {
    while let Some(event) = rx.recv().await {
        match event {
            AgentEvent::TextDelta { delta } => print!("{}", delta),
            AgentEvent::ToolExecutionStarted { name, .. } => {
                println!("[Calling {}...]", name);
            }
            AgentEvent::TurnCompleted { usage, .. } => {
                println!("\n[Tokens: {}]", usage.total_tokens());
            }
            _ => {}
        }
    }
});

let result = agent.run_with_events("Tell me a story".into(), tx).await?;

Agent methods

MethodDescription
run(prompt)Run agent with a ContentInput prompt (text or multimodal)
run_with_events(prompt, tx)Run with event streaming; prompt is ContentInput
session()Get current session (read-only)
budget()Get current budget tracker
state()Get current loop state
cancel()Cancel the current run

Error handling

use meerkat::AgentError;

match agent.run("prompt".into()).await {
    Ok(result) => println!("Success: {}", result.text),
    Err(AgentError::LlmError(msg)) => println!("LLM error: {}", msg),
    Err(AgentError::TokenBudgetExceeded { used, limit }) => {
        println!("Token budget exceeded: {} / {}", used, limit);
    }
    Err(e) => println!("Other error: {}", e),
}

Events

use meerkat::AgentEvent;

match event {
    // Session lifecycle
    AgentEvent::RunStarted { session_id, prompt } => {}
    AgentEvent::RunCompleted { session_id, result, usage } => {}
    AgentEvent::RunFailed { session_id, error } => {}

    // Hook lifecycle
    AgentEvent::HookStarted { hook_id, point } => {}
    AgentEvent::HookCompleted { hook_id, point, duration_ms } => {}
    AgentEvent::HookFailed { hook_id, point, error } => {}
    AgentEvent::HookDenied { hook_id, point, reason_code, message, .. } => {}
    AgentEvent::HookRewriteApplied { hook_id, point, patch } => {}
    AgentEvent::HookPatchPublished { hook_id, point, envelope } => {}

    // LLM interaction
    AgentEvent::TurnStarted { turn_number } => {}
    AgentEvent::ReasoningDelta { delta } => {}
    AgentEvent::ReasoningComplete { content } => {}
    AgentEvent::TextDelta { delta } => {}
    AgentEvent::TextComplete { content } => {}
    AgentEvent::ToolCallRequested { id, name, args } => {}
    AgentEvent::ToolResultReceived { id, name, is_error } => {}
    AgentEvent::TurnCompleted { stop_reason, usage } => {}

    // Tool execution
    AgentEvent::ToolExecutionStarted { id, name } => {}
    AgentEvent::ToolExecutionCompleted { id, name, result, is_error, duration_ms } => {}
    AgentEvent::ToolExecutionTimedOut { id, name, timeout_ms } => {}

    // Compaction
    AgentEvent::CompactionStarted { input_tokens, estimated_history_tokens, message_count } => {}
    AgentEvent::CompactionCompleted { summary_tokens, messages_before, messages_after } => {}
    AgentEvent::CompactionFailed { error } => {}

    // Budget
    AgentEvent::BudgetWarning { budget_type, used, limit, percent } => {}

    // Retry
    AgentEvent::Retrying { attempt, max_attempts, error, delay_ms } => {}

    // Skills
    AgentEvent::SkillsResolved { skills, injection_bytes } => {}
    AgentEvent::SkillResolutionFailed { reference, error } => {}

    // Comms interaction lifecycle
    AgentEvent::InteractionComplete { interaction_id, result } => {}
    AgentEvent::InteractionFailed { interaction_id, error } => {}
    AgentEvent::StreamTruncated { reason } => {}

    // Tool config changes
    AgentEvent::ToolConfigChanged { payload } => {}

    _ => {} // non_exhaustive: forward compatibility
}

Core types

Message and ContentBlock

use meerkat::{Message, UserMessage, AssistantMessage, SystemMessage, ToolResult};
use meerkat::ContentBlock;

let system = Message::System(SystemMessage { content: "You are helpful.".to_string() });

// Text-only user message (convenience)
let user = Message::User(UserMessage::text("Hello!"));

// Multimodal user message with text and image
let user = Message::User(UserMessage {
    content: vec![
        ContentBlock::Text { text: "What is in this image?".to_string() },
        ContentBlock::Image {
            media_type: "image/png".to_string(),
            data: base64_data,
            source_path: None,
        },
    ],
});

ContentInput

ContentInput is the prompt type accepted by CreateSessionRequest and StartTurnRequest. It supports both text-only and multimodal prompts:
use meerkat::ContentInput;

// Text-only (most common) — implements From<&str> and From<String>
let prompt: ContentInput = "What is Rust?".into();

// Multimodal — blocks with mixed content types
let prompt = ContentInput::Blocks(vec![
    ContentBlock::Text { text: "Describe this image.".to_string() },
    ContentBlock::Image {
        media_type: "image/jpeg".to_string(),
        data: base64_data,
        source_path: None,
    },
]);

ToolCall and ToolResult

use meerkat::{ToolCall, ToolResult};

let tool_call = ToolCall {
    id: "tc_123".to_string(),
    name: "get_weather".to_string(),
    args: json!({"city": "Tokyo"}),
};

let result = ToolResult::success("tc_123", "Sunny, 25C");
let error = ToolResult::error("tc_123", "City not found");

RunResult

let result: RunResult = agent.run("Hello".into()).await?;

println!("Response: {}", result.text);
println!("Session: {}", result.session_id);
println!("Tokens: {}", result.usage.total_tokens());
println!("Turns: {}", result.turns);
println!("Tool calls: {}", result.tool_calls);

See also

  • Tools and stores - tool system, session stores, MCP integration
  • Advanced - expert-only direct agent construction, providers, budgets, and hooks
  • API reference - quick-lookup type index