---
title: "Capability matrix"
description: "Build profiles, feature gates, error codes, and capability behavior across Meerkat configurations."
icon: "table-cells"
---

Meerkat is modular. The `meerkat` facade defaults to the three provider
features (`anthropic`, `openai`, `gemini`); storage, comms, MCP, skills,
schedule, memory, and live-channel orchestration are opt-in. This matrix shows
what works in common build profiles.

## Build profiles

| Profile | Cargo Features | Use Case |
|---------|---------------|----------|
| **Minimal** | `--no-default-features` | Ephemeral agent, no persistence, no compaction |
| **Provider default** | default features | Anthropic + OpenAI + Gemini providers, no persistence or optional subsystems |
| **Persistent** | `session-store` | Sessions survive restart with the realm-pinned backend (`sqlite` by default when compiled) |
| **Compacting** | `session-compaction` | Auto-compact long conversations |
| **Full** | `session-store,session-compaction` | Persistent + compacting |
| **Live-capable** | `openai-realtime,live` | Caller-initiated `live/*` channels for realtime-capable sessions |

## Capability behavior

| Operation | Minimal | Persistent | Compacting | Full |
|-----------|---------|-----------|------------|------|
| `create_session` | Works | Works | Works | Works |
| `start_turn` | Works | Works + snapshot saved | Works | Works + snapshot saved |
| `interrupt` | Works | Works | Works | Works |
| `read` (running session) | Works | Works | Works | Works |
| `read` (archived) | `SESSION_NOT_FOUND` | Falls back to store | `SESSION_NOT_FOUND` | Falls back to store |
| `list` | Live sessions only | Live + stored sessions | Live sessions only | Live + stored sessions |
| `archive` | Removes from memory | Removes from memory, snapshot kept | Removes from memory | Removes + snapshot kept |
| Auto-compaction | No-op | No-op | Triggers at threshold | Triggers at threshold |

## Error codes

Two layers matter here:

- **Session transport mapping**: how `SessionError` is projected by session-facing transports
- **Canonical wire `ErrorCode` mapping**: the higher-level `meerkat-contracts` envelope model used across public protocol surfaces

The table below is the current **session transport** mapping:

| `SessionError` | Code | JSON-RPC | REST | MCP | CLI |
|----------------|------|----------|------|-----|-----|
| `NotFound` | `SESSION_NOT_FOUND` | -32001 | 404 | tool error | exit 1 |
| `Busy` | `SESSION_BUSY` | -32002 | 409 | tool error | exit 1 |
| `PersistenceDisabled` | `SESSION_PERSISTENCE_DISABLED` | -32003 | 501 | tool error | exit 0 |
| `CompactionDisabled` | `SESSION_COMPACTION_DISABLED` | -32004 | 501 | tool error | exit 0 |
| `NotRunning` | `SESSION_NOT_RUNNING` | -32005 | 409 | tool error | exit 1 |
| `Store` | `SESSION_STORE_ERROR` | -32006 | 500 | tool error | exit 1 |
| `Unsupported` | `SESSION_UNSUPPORTED` | -32007 | 501 | tool error | exit 1 |
| `Agent` | `AGENT_ERROR` | -32000 | 500 | tool error | exit 1 |

### Transport error mapping summary

- **JSON-RPC**: Custom numeric transport codes in the -32000..-32099 range.
- **REST**: HTTP status codes (404, 409, 500, 501). Error body includes `{ "code": "...", "message": "..." }`.
- **MCP Server**: Tool calls return `is_error: true` with the error message and code in the content.
- **CLI**: `SessionError` transport projection is intentionally coarse: most failures collapse to exit 1, while persistence/compaction-disabled remain informational (exit 0). Error details are printed to stderr.

## Pick only what you need

```toml
# Minimal: just the agent loop
[dependencies]
meerkat = { version = "0.6.19", default-features = false, features = ["anthropic"] }

# Add persistence
meerkat = { version = "0.6.19", default-features = false, features = ["anthropic", "session-store"] }

# Add compaction
meerkat = { version = "0.6.19", default-features = false, features = ["anthropic", "session-store", "session-compaction"] }

# Add scheduling
meerkat = { version = "0.6.19", default-features = false, features = ["anthropic", "session-store", "schedule"] }

# Kitchen sink
meerkat = { version = "0.6.19", features = [
  "all-providers", "openai-realtime", "session-store", "session-compaction",
  "memory-store-session", "comms", "mcp", "skills", "schedule", "workgraph", "live"
] }
```

## Runtime Capabilities

`GET /capabilities` and `capabilities/get` report compiled and policy-enabled
runtime capabilities. Tool-family companion skills use the same capability
tokens for gating.

| Capability | Config gate | Notes |
|------------|-------------|-------|
| `builtins` | `tools.builtins_enabled` | Builtin tasks and utility tools |
| `shell` | `tools.shell_enabled` | Shell and shell job tools |
| `schedule` | `tools.schedule_enabled` | Durable schedules and occurrence delivery |
| `work_graph` | `tools.workgraph_enabled` | WorkGraph tools and companion workflow |
| `memory_store` | feature/runtime store | Semantic memory retrieval |
| `skills` | `skills.enabled` and compiled skills support | Skill discovery and loading |

WorkGraph's companion skill uses the same canonical capability token as public
capability reports: `work_graph`.

Optional capability bundles are declared by the owning feature crate.
`meerkat-capabilities` supplies the typed vocabulary and collection seam;
contracts and facades may project those declarations into `CapabilityStatus`,
but they must not invent feature policy or translate capability aliases. For
example, `meerkat-schedule` owns the Schedule policy declaration,
`meerkat-workgraph` owns the WorkGraph policy declaration and registration, and
`meerkat-skills` owns the Skills policy declaration.

## Model capabilities

`ModelProfile` describes per-model capabilities used for feature gating and tool visibility. Two fields are relevant to multimodal content support:

| Capability | Description | Anthropic | OpenAI | Gemini |
|------------|-------------|-----------|--------|--------|
| `vision` | Model can process image content in user messages | Yes | Yes | Yes |
| `image_tool_results` | Model can process image content in tool results | Yes | No | Yes |
| `inline_video` | Model can process inline video content in user messages | No | No | Yes |
| `realtime` | Model can back a caller-initiated live channel | No | `gpt-realtime-2` only | No current catalog row |
| `supports_web_search` | Model supports provider-native web search tools | Yes | Yes for chat catalog rows; not `gpt-realtime-2` / Codex | Yes |

These fields are set in `meerkat-core/src/model_profile/**` (`meerkat-models` is a compatibility shim) and exposed via `ModelProfile`. They control:

- **`view_image` tool visibility** -- currently hidden via `ToolScope` when `image_tool_results` is `false`.
- **`ContentBlock::Image` in tool results** -- providers that do not support `image_tool_results` will receive image content blocks converted to text descriptions.
- **`ContentBlock::Image` in user messages** -- providers that do not support `vision` will not receive image content blocks in user messages.
- **Provider-native web search** -- injected by default when `supports_web_search` is `true` and the corresponding `provider_tools.*` config is enabled.

Image generation uses provider-owned image profiles rather than `ModelProfile` chat-model flags. OpenAI and Gemini provider crates advertise image targets, supported backend plans, dimensions/aspect ratios, and provider-specific `provider_params`; the runtime exposes one model-facing `generate_image` tool and commits generated outputs as blob-backed assistant image blocks.

## Provider-native web search

Web search is enabled by default for all catalog models with `supports_web_search: true`. The factory resolves provider-specific tool config at build time and injects it as non-persisted `AgentConfig.provider_tool_defaults`, merged per-turn with explicit `provider_params` via RFC 7396 merge-patch.

| Provider | Tool type | Config key | Default |
|----------|-----------|------------|---------|
| Anthropic | `web_search_20250305` | `provider_tools.anthropic.web_search` | `true` |
| OpenAI | `web_search` | `provider_tools.openai.web_search` | `true` |
| Gemini | `google_search` | `provider_tools.gemini.google_search` | `true` |
| Self-hosted | Not wired (v1) | `SelfHostedModelConfig.supports_web_search` | `false` |

**Opt-out:**
- Config-level: set the matching config key from the table above to `false`
- CLI: `rkat run --no-web-search "..."`.
- Per-request Anthropic/OpenAI: `provider_params: {"web_search": null}` (RFC 7396 null removal)
- Per-request Gemini: `provider_params: {"google_search": null}` (RFC 7396 null removal)
- Per-request boolean: `false` for the provider-native search key is treated as disable

**Resume behavior:** Tool defaults are re-derived on every build (including resume) from current config and model profile. Config changes take effect immediately on resumed sessions. Explicit `provider_params` overrides are persisted in `SessionMetadata`.

**Extraction turns:** Web search tools are stripped during structured output extraction to maintain the deterministic, tool-free invariant.

**Hook interaction:** Pre-LLM hooks see the merged tool defaults in `HookLlmRequest.provider_params` as an observational projection. Hooks cannot rewrite effective provider params.

## MCP server loading

MCP server connections are **non-blocking across all surfaces**. Servers connect in parallel in the background after `apply_staged()`. The agent loop polls `poll_external_updates()` at each `CallingLlm` boundary and injects a `[MCP_PENDING]` system notice while servers are still connecting. Tools become visible as each server completes its handshake.

- Per-server timeout: `connect_timeout_secs` in MCP config (default: 10s)
- CLI: `--wait-for-mcp` blocks before the first turn until all servers finish connecting
- SDK: `McpRouterAdapter::wait_until_ready(timeout)` provides the same blocking behavior programmatically

## Compaction behavior

When `session-compaction` is enabled:

- **Trigger**: `last_input_tokens >= threshold` OR `estimated_history_tokens >= threshold`
- **Guards**: Never on first turn. Minimum 3 turns between compactions.
- **Failure**: Non-fatal. `CompactionFailed` event emitted, agent continues with uncompacted history.
- **Budget**: Compaction LLM call draws from the same token budget as regular turns.

Events emitted: `CompactionStarted`, `CompactionCompleted`, `CompactionFailed`.

## Concurrency

- At most one turn runs per session at a time.
- Second `start_turn` while one is in-flight returns `SESSION_BUSY`.
- `interrupt()` cancels the in-flight turn.
- `read()` and `list()` are non-blocking.
- No queueing: callers retry on `Busy`.

## Durability

- **Ephemeral**: Process death loses all state.
- **Persistent**: Snapshot saved after turn completion according to the pinned realm backend.
- Backends are pinned per realm (`realm_manifest.json`) and shared across surfaces only when `realm_id` matches.
- SQLite-backed realms are the standard same-realm multi-process mode.

## See also

- [Session contracts](/reference/session-contracts) -- concurrency, durability, and compaction semantics
- [Architecture](/reference/architecture) -- crate structure and agent loop details
