> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rkat.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Rust SDK overview

> Embed Meerkat as a Rust library: the full engine with no subprocess overhead.

The Rust SDK is the primary interface. The Python/TypeScript SDKs and all API
servers are thin wrappers over this same engine. A production Rust host
composes `PersistentSessionService` with `MeerkatMachine`; the service is the
session substrate and the machine owns runtime transitions. The direct
`EphemeralSessionService` path remains useful for small embedded programs and
tests that intentionally do not need runtime-backed persistence or delivery.

## Method overview

| Area                         | Method / Type                                        | Purpose                                                                        |
| ---------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------ |
| **Setup**                    | `Config::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_with_runtime_adapter(...)` | Build the persistent service and its shared `MeerkatMachine`                   |
|                              | `build_persistent_service(...)`                      | Build only the low-level persistent service half                               |
|                              | `build_ephemeral_service(factory, config, cap)`      | Build a direct in-memory Queue-only service                                    |
| **Direct embedded sessions** | `service.create_session(req)`                        | Create an ephemeral session and run the first turn                             |
|                              | `service.start_turn(id, req)`                        | Continue an ephemeral session                                                  |
|                              | `service.read(id)`                                   | Read ephemeral session state                                                   |
|                              | `service.list(query)`                                | List ephemeral sessions                                                        |
|                              | `service.archive(id)`                                | Remove an ephemeral session                                                    |
| **Runtime-backed host**      | `surface::materialize_session(...)`                  | Attach a persistent session actor and machine executor                         |
|                              | `machine.accept_input_with_completion(...)`          | Admit runtime-owned input and await its typed completion                       |
|                              | `service.archive_with_machine_protocol(...)`         | Archive through the machine-owned retire protocol                              |
| **Agent**                    | `agent.run(prompt)`                                  | Run agent with a prompt                                                        |
|                              | `agent.run_with_events(prompt, tx)`                  | Run with event streaming                                                       |
|                              | `agent.cancel()`                                     | Cancel the current run                                                         |
| **Platform services**        | `ScheduleService`                                    | Schedule lifecycle and delivery state; durability follows the configured store |
|                              | `WorkGraphService`                                   | Work items, goals, evidence, and attention projections                         |
|                              | `DetachedJobService`                                 | Detached job lifecycle, progress, results, and delivery state                  |
|                              | `HostAuthService`                                    | Native host login, credential, and auth-binding lifecycle                      |
|                              | `ProviderRuntimeRegistry`                            | Provider runtime and realm-scoped auth resolution                              |

Mob orchestration is layered on this facade by the separate `meerkat-mob`
crate. The `live` feature composes `meerkat-live` into the runtime; direct
adapter types remain owned by `meerkat-live`.

## Installation

<Steps>
  <Step title="Add the dependency">
    ```toml theme={null}
    [dependencies]
    meerkat = "=0.8.33"
    tokio = { version = "1", features = ["full"] }
    ```
  </Step>

  <Step title="Choose feature flags">
    The default feature set enables all three standard LLM providers; add
    optional gates as needed:

    <CodeGroup>
      ```toml Default (all providers, no storage) theme={null}
      [dependencies]
      meerkat = "=0.8.33"
      ```

      ```toml Add persistence theme={null}
      [dependencies]
      meerkat = { version = "=0.8.33", features = ["sqlite-store", "session-store"] }
      ```

      ```toml Full native harness theme={null}
      [dependencies]
      meerkat = { version = "=0.8.33", features = [
          "sqlite-store", "session-store", "session-compaction",
          "memory-store-session", "atif", "comms", "mcp", "skills",
          "schedule", "workgraph", "openai-realtime", "live",
          "live-webrtc", "native-keyring"
      ] }
      ```

      ```toml Minimal (single provider) theme={null}
      [dependencies]
      meerkat = { version = "=0.8.33", default-features = false, features = ["anthropic"] }
      ```
    </CodeGroup>
  </Step>
</Steps>

<Accordion title="Feature flag reference">
  | Feature                  | Description                                                              | Default |
  | ------------------------ | ------------------------------------------------------------------------ | ------- |
  | `anthropic`              | Anthropic Claude API client                                              | Yes     |
  | `openai`                 | OpenAI API client                                                        | Yes     |
  | `openai-realtime`        | OpenAI realtime provider client; implies `openai`                        | No      |
  | `gemini`                 | Google Gemini API client                                                 | Yes     |
  | `all-providers`          | Shorthand for the three default non-realtime providers                   | No      |
  | `native-keyring`         | Native provider credential keyring                                       | No      |
  | `sqlite-store`           | SQLite store implementations; pair with `session-store` for realms       | No      |
  | `jsonl-store`            | JSONL session store; pair with `session-store` for realms                | No      |
  | `memory-store`           | In-memory session storage (testing)                                      | No      |
  | `session-store`          | Persistent session lifecycle support                                     | No      |
  | `session-compaction`     | Auto-compact long conversations                                          | No      |
  | `memory-store-session`   | Semantic memory indexing                                                 | No      |
  | `atif`                   | ATIF trajectory export vocabulary                                        | No      |
  | `comms`                  | Ed25519 inter-agent messaging                                            | No      |
  | `mcp`                    | MCP protocol client and tool routing                                     | No      |
  | `schedule`               | Predicate schedule runnable helpers; `ScheduleService` is always linked  | No      |
  | `workgraph`              | Compatibility opt-in; `WorkGraphService` and its types are always linked | No      |
  | `live`                   | Live-channel orchestration and realtime adapter helpers                  | No      |
  | `live-webrtc`            | WebRTC bootstrap for live channels; implies `live`                       | No      |
  | `skills`                 | Composable knowledge packs                                               | No      |
  | `integration-real-tests` | Test-only real-provider integration gate                                 | No      |
  | `test-realtime-fixtures` | Test-only deterministic realtime fixtures                                | No      |
</Accordion>

***

## Quick start

Production surfaces (CLI, REST, RPC, MCP) use the **runtime-backed** path where
`PersistentSessionService` is substrate and `MeerkatMachine` owns keep-alive,
Queue/Steer routing, interruption, terminal publication, and archive. Start a
native host with both halves:

```rust theme={null}
use meerkat::{
    AgentFactory, Config, build_persistent_service_with_runtime_adapter,
    open_realm_persistence_in,
};
use meerkat_store::{RealmBackend, realm_paths_in};

#[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 realm_id = "team-alpha";
    let realm_paths = realm_paths_in(&realms_root, realm_id);
    let (_manifest, persistence) = open_realm_persistence_in(
        &realms_root,
        realm_id,
        Some(RealmBackend::Sqlite),
        None,
    ).await?;
    let store_path = persistence
        .store_path()
        .unwrap_or(&realm_paths.root)
        .to_path_buf();
    let factory = AgentFactory::new(store_path).runtime_root(realm_paths.root);
    let (service, machine) = build_persistent_service_with_runtime_adapter(
        factory,
        config,
        64,
        persistence,
    );

    // A host now materializes actors through meerkat::surface helpers and
    // admits work through `machine`. Keep both values for the host lifetime.
    let _host_parts = (service, machine);
    Ok(())
}
```

<Warning>
  `build_persistent_service()` returns only the persistent service half. Its
  direct `start_turn`, `interrupt`, and `archive` methods intentionally return
  `SessionError::Unsupported`; a runtime-backed host must use the machine commit,
  interrupt, and retire protocols. Use the public helpers in `meerkat::surface`
  when building a native host, or use the ready-made RPC, REST, CLI, or MCP host.
</Warning>

For a direct embedded or testing flow that does not need machine-owned runtime
semantics, use the ephemeral service:

```rust theme={null}
use meerkat::{
    AgentFactory, Config, CreateSessionRequest, DeferredPromptPolicy,
    SessionService, SystemPromptOverride, build_ephemeral_service,
};
use meerkat_core::service::InitialTurnPolicy;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = Config::load().await?;
    let factory = AgentFactory::new(std::env::current_dir()?);
    let service = build_ephemeral_service(factory, config, 64);

    let result = service.create_session(CreateSessionRequest {
        model: "claude-sonnet-4-6".into(),
        prompt: "What is the capital of France?".into(),
        injected_context: Vec::new(),
        system_prompt: SystemPromptOverride::Set("You are a helpful assistant.".into()),
        max_tokens: Some(1024),
        event_tx: None,
        initial_turn: InitialTurnPolicy::RunImmediately,
        deferred_prompt_policy: DeferredPromptPolicy::Discard,
        build: None,
        labels: None,
    }).await?;

    println!("Response: {}", result.text);
    println!("Session ID: {}", result.session_id);
    Ok(())
}
```

***

## Sessions

The examples below use the direct `EphemeralSessionService` lifecycle. The
same request and result types are used inside production hosts, but persistent
follow-up turns, interruption, and archive are machine-owned transitions and
must not call these trait methods directly.

### Multi-turn conversations

```rust theme={null}
use meerkat::{
    CreateSessionRequest, DeferredPromptPolicy, SessionService,
    StartTurnRequest, StartTurnRuntimeSemantics, SystemPromptOverride,
};
use meerkat_core::service::InitialTurnPolicy;

// Turn 1: create session
let result = service.create_session(CreateSessionRequest {
    model: "claude-sonnet-4-6".into(),
    prompt: "My name is Alice.".into(),
    injected_context: Vec::new(),
    system_prompt: SystemPromptOverride::Set("You are a helpful assistant with memory.".into()),
    max_tokens: None,
    event_tx: None,
    initial_turn: InitialTurnPolicy::RunImmediately,
    deferred_prompt_policy: DeferredPromptPolicy::Discard,
    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(),
    injected_context: Vec::new(),
    system_prompt: None,
    event_tx: None,
    runtime: StartTurnRuntimeSemantics::default(),
}).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?;
```

### Append a System message at a turn boundary

```rust theme={null}
use meerkat::{
    CreateSessionRequest, DeferredPromptPolicy, SessionService,
    StartTurnRequest, StartTurnRuntimeSemantics, SystemPromptOverride,
};
use meerkat_core::service::InitialTurnPolicy;

let created = service.create_session(CreateSessionRequest {
    model: "claude-sonnet-4-6".into(),
    prompt: "Let's wait before we run.".into(),
    injected_context: Vec::new(),
    system_prompt: SystemPromptOverride::Set("You are the original prompt.".into()),
    max_tokens: None,
    event_tx: None,
    initial_turn: InitialTurnPolicy::Defer,
    deferred_prompt_policy: DeferredPromptPolicy::Stage,
    build: None,
    labels: None,
}).await?;

let result = service.start_turn(&created.session_id, StartTurnRequest {
    prompt: "Continue under the new instruction.".into(),
    injected_context: Vec::new(),
    system_prompt: Some("For this point forward, answer concisely.".into()),
    event_tx: None,
    runtime: StartTurnRuntimeSemantics::default(),
}).await?;
```

`system_prompt` appends one ordinary ordered `System` message immediately
before that turn. It may be used on any turn; prior System messages and the
rest of the transcript remain unchanged.

### Error handling

```rust theme={null}
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

<Warning>
  `Agent::run(...)` and `AgentBuilder` are expert-level escape hatches. Prefer
  the persistent service plus machine composition for a production host, or the
  ephemeral session service for a deliberately direct embedded flow.
</Warning>

## Running agents directly

### Basic run

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

### Run with event streaming

```rust theme={null}
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: Some(usage), .. } => {
                // `usage` is per-call `TurnUsage`; its `accounting` names the
                // resolved provider/model and the normalized presented input.
                println!(
                    "\n[{} {}: {} tokens]",
                    usage.accounting().provider.as_str(),
                    usage.accounting().model,
                    usage.normalized_total_tokens()
                );
            }
            AgentEvent::TurnCompleted { usage: None, .. } => {
                // Accounting was absent. Skip this row - do not treat it as
                // zero. TurnUsageAccountingUnmeasured carries the reason.
            }
            _ => {}
        }
    }
});

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

<Note>
  `turn_completed` carries optional usage for one provider call while `run_completed`
  carries the session-cumulative total, and their `input_tokens` fields use
  different denominators. `usage: None` means no accounting exists for that
  turn, not zero usage. Read
  [Usage accounting](/reference/usage-accounting) before aggregating either.
</Note>

### Agent methods

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

```rust theme={null}
use meerkat::AgentError;

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

***

## Events

<Accordion title="All event types">
  ```rust theme={null}
  use meerkat::AgentEvent;

  match event {
      // Session lifecycle
      AgentEvent::RunStarted { session_id, input } => {}
      AgentEvent::RunCompleted { session_id, result, structured_output, extraction_required, usage, .. } => {}
      AgentEvent::RunFailed { session_id, error_report, .. } => {}

      // Structured-output extraction (after a completed main run)
      AgentEvent::ExtractionSucceeded { session_id, structured_output, schema_warnings } => {}
      AgentEvent::ExtractionFailed { session_id, last_output, attempts, reason } => {}

      // Hook lifecycle
      AgentEvent::HookStarted { hook_id, point } => {}
      AgentEvent::HookCompleted { hook_id, point, duration_ms } => {}
      AgentEvent::HookFailed { hook_id, point, reason } => {}
      AgentEvent::HookDenied { hook_id, point, reason_code, message, .. } => {}

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

      // Tool execution
      AgentEvent::ToolExecutionStarted { id, name } => {}
      AgentEvent::ToolExecutionCompleted { id, name, content, 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 { reason } => {}

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

      // Retry (typed LlmRetrySchedule owns failure kind + attempt/delay plan)
      AgentEvent::Retrying { retry } => {}

      // Skills
      AgentEvent::SkillsResolved { skills, injection_bytes } => {}
      AgentEvent::SkillResolutionFailed { skill_key, reason } => {}

      // Comms interaction lifecycle
      AgentEvent::InteractionComplete { interaction_id, result, .. } => {}
      AgentEvent::InteractionCallbackPending { interaction_id, tool_name, args, .. } => {}
      AgentEvent::InteractionFailed { interaction_id, reason } => {}
      AgentEvent::StreamTruncated { reason } => {}

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

      // Background jobs and transcript rewrites
      AgentEvent::BackgroundJobCompleted { job_id, display_name, terminal_status, detail } => {}
      AgentEvent::TranscriptRewriteCommitted { session_id, record } => {}
      AgentEvent::TranscriptRewriteAuditReceiptCommitted { session_id, receipt, final_assistant_text } => {}

      // Provider evidence and peer-content observability
      AgentEvent::ProviderCacheBreakpointsDiscarded { session_id, retained, discarded } => {}
      AgentEvent::PeerContentIngested { kind, peer, request_id, sender_taint } => {}
      AgentEvent::TurnUsageAccountingUnmeasured { session_id, unmeasured } => {}
      AgentEvent::TurnUsageAccountingIdentityDisputed { session_id, dispute } => {}

      _ => {} // non_exhaustive: forward compatibility
  }
  ```
</Accordion>

***

## Core types

### Message and ContentBlock

```rust theme={null}
use meerkat::{Message, UserMessage, BlockAssistantMessage, SystemMessage, ToolResult};
use meerkat_core::{ContentBlock, ImageData};

let system = Message::System(SystemMessage::new("You are helpful."));

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

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

### ContentInput

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

```rust theme={null}
use meerkat_core::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: ImageData::Inline { data: base64_data },
    },
]);
```

### ToolCall and ToolResult

```rust theme={null}
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::new("tc_123".to_string(), "Sunny, 25C".to_string(), false);
let error = ToolResult::new("tc_123".to_string(), "City not found".to_string(), true);
```

### RunResult

```rust theme={null}
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](/rust/tools-and-stores) - tool system, session stores, MCP integration
* [Advanced](/rust/advanced) - expert-only direct agent construction, providers, budgets, and hooks
* [API reference](/reference/api-reference) - quick-lookup type index
