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) | Final in-memory view | SESSION_NOT_FOUND | Final in-memory view | SESSION_NOT_FOUND |
read_history (archived) | SESSION_PERSISTENCE_DISABLED | Works (committed transcript) | SESSION_PERSISTENCE_DISABLED | Works (committed transcript) |
list | Live sessions only (archived hidden) | Live + stored sessions (archived hidden) | Live sessions only (archived hidden) | Live + stored sessions (archived hidden) |
archive | Retires live handle, keeps final in-memory view | Durable archive commit, then runtime retire | Retires live handle, keeps final in-memory view | Durable archive commit, then runtime retire |
| Auto-compaction | No-op | No-op | Triggers at threshold | Triggers at threshold |
SessionDocumentMachine for all
profiles: the durable archived verdict commits first and the runtime handle is
retired second, so a half-archived state is unrepresentable.
Error codes
Two layers matter here:- Session transport mapping: how
SessionErroris projected by session-facing transports - Canonical wire
ErrorCodemapping: the higher-levelmeerkat-contractsenvelope model used across public protocol surfaces
SessionError::code() is the stable string code; JSON-RPC numbers come from session_error_to_rpc):
SessionError | Code | JSON-RPC | MCP | CLI |
|---|---|---|---|---|
NotFound | SESSION_NOT_FOUND | -32001 | tool error | exit 1 |
Busy | SESSION_BUSY | -32002 | tool error | exit 1 |
PersistenceDisabled | SESSION_PERSISTENCE_DISABLED | -32603 | tool error | exit 1 |
CompactionDisabled | SESSION_COMPACTION_DISABLED | -32603 | tool error | exit 1 |
NotRunning | SESSION_NOT_RUNNING | -32603 | tool error | exit 1 |
Store | SESSION_STORE_ERROR | -32603 | tool error | exit 1 |
Unsupported | SESSION_UNSUPPORTED | -32603 | tool error | exit 1 |
Agent | AGENT_ERROR | by cause (see below) | tool error | exit 1 (exit 2 on budget exhaustion) |
FailedWithData | SESSION_ERROR | -32603 | tool error | exit 1 |
Agent errors map per typed cause to the canonical contracts codes: provider
failures -32010, budget exhaustion -32011, hook denial -32012, cancellation
-32005, config errors -32602, everything else -32603.
Transport error mapping summary
- JSON-RPC:
NotFound/Busykeep dedicated codes (-32001/-32002); typed agent causes use the canonicalmeerkat_contracts::ErrorCode::jsonrpc_code()values; all other session variants collapse to -32603 with the message preserved. - REST: Endpoint-typed
ApiErrormapping (NotFound-> 404;Busy-> 400 on create/turn, 409 on interrupt conflicts; archived/unavailable history -> 400; agent/internal -> 500). Where canonical wire errors are emitted,ErrorCode::http_status()decides the status. Error body includes{ "code": "...", "message": "..." }. - MCP Server: Tool calls return
is_error: truewith the error message and code in the content. - CLI: Exit codes are coarse: 0 success, 1 error, 2 graceful budget exhaustion. Error details are printed to stderr.
Pick only what you need
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 |
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 video content in user messages, including inline bytes and provider-readable URI references | 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 |
meerkat-models/src/capabilities/** (the types live in meerkat-core/src/model_profile/**) and exposed via ModelProfile. They control:
view_imagetool visibility — hidden via the capability-baseToolFilter(capability_base_filter_for_image_tool_results) whenimage_tool_resultsisfalse.ContentBlock::Imagein tool results — providers that do not supportimage_tool_resultswill receive image content blocks converted to text descriptions.ContentBlock::Imagein user messages — providers that do not supportvisionwill not receive image content blocks in user messages.- Provider-native web search — injected by default when
supports_web_searchistrueand the correspondingprovider_tools.*config is enabled.
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.
Gemini video URI support follows the active Google backend. Vertex Gemini accepts gs:// references directly. Gemini API sessions pass public or already-registered file URIs as fileData, and register gs:// references through the Files API only when Google bearer auth is available; API-key-only sessions should use public/pre-registered file URIs or Vertex for direct GCS references.
Provider-native web search
Web search is enabled by default for all catalog models withsupports_web_search: true. The factory resolves provider-specific tool defaults at build time as a typed, never-persisted ProviderTag (the tool_defaults half of ProviderParamsCarrier). Effective per-turn params are a typed field-wise merge: explicit provider_params win and tool defaults fill only the unset provider-native slots — the old RFC 7396 raw-JSON merge-patch is retired.
| 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 |
- 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: {"provider_tag": {"provider": "anthropic", "web_search": false}}(or"provider": "open_ai") - Per-request Gemini:
provider_params: {"provider_tag": {"provider": "gemini", "google_search": false}} - An explicit non-object value (
falseornull) in the typed search slot blocks the build-derived default and no search tool is emitted
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 afterapply_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_secsin MCP config (default: 10s) - CLI:
--wait-for-mcpblocks before the first turn until all servers finish connecting - SDK:
McpRouterAdapter::wait_until_ready(timeout)provides the same blocking behavior programmatically
Compaction behavior
Whensession-compaction is enabled:
- Trigger:
last_input_tokens >= thresholdORestimated_history_tokens >= threshold - Guards: Never on first turn. Minimum 3 turns between compactions.
- Failure: Non-fatal.
CompactionFailedevent emitted, agent continues with uncompacted history. - Budget: Compaction LLM call draws from the same token budget as regular turns.
CompactionStarted, CompactionCompleted, CompactionFailed.
Concurrency
- At most one turn runs per session at a time.
- Second
start_turnwhile one is in-flight returnsSESSION_BUSY. interrupt()cancels the in-flight turn.read()andlist()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 whenrealm_idmatches. - SQLite-backed realms are the standard same-realm multi-process mode.
See also
- Session contracts — concurrency, durability, and compaction semantics
- Architecture — crate structure and agent loop details
