Skip to main content
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

ProfileCargo FeaturesUse Case
Minimal--no-default-featuresEphemeral agent, no persistence, no compaction
Provider defaultdefault featuresAnthropic + OpenAI + Gemini providers, no persistence or optional subsystems
Persistentsession-storeSessions survive restart with the realm-pinned backend (sqlite by default when compiled)
Compactingsession-compactionAuto-compact long conversations
Fullsession-store,session-compactionPersistent + compacting
Live-capableopenai-realtime,liveCaller-initiated live/* channels for realtime-capable sessions

Capability behavior

OperationMinimalPersistentCompactingFull
create_sessionWorksWorksWorksWorks
start_turnWorksWorks + snapshot savedWorksWorks + snapshot saved
interruptWorksWorksWorksWorks
read (running session)WorksWorksWorksWorks
read (archived)Final in-memory viewSESSION_NOT_FOUNDFinal in-memory viewSESSION_NOT_FOUND
read_history (archived)SESSION_PERSISTENCE_DISABLEDWorks (committed transcript)SESSION_PERSISTENCE_DISABLEDWorks (committed transcript)
listLive sessions only (archived hidden)Live + stored sessions (archived hidden)Live sessions only (archived hidden)Live + stored sessions (archived hidden)
archiveRetires live handle, keeps final in-memory viewDurable archive commit, then runtime retireRetires live handle, keeps final in-memory viewDurable archive commit, then runtime retire
Auto-compactionNo-opNo-opTriggers at thresholdTriggers at threshold
Archive lifecycle truth is owned by the canonical 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 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() is the stable string code; JSON-RPC numbers come from session_error_to_rpc):
SessionErrorCodeJSON-RPCMCPCLI
NotFoundSESSION_NOT_FOUND-32001tool errorexit 1
BusySESSION_BUSY-32002tool errorexit 1
PersistenceDisabledSESSION_PERSISTENCE_DISABLED-32603tool errorexit 1
CompactionDisabledSESSION_COMPACTION_DISABLED-32603tool errorexit 1
NotRunningSESSION_NOT_RUNNING-32603tool errorexit 1
StoreSESSION_STORE_ERROR-32603tool errorexit 1
UnsupportedSESSION_UNSUPPORTED-32603tool errorexit 1
AgentAGENT_ERRORby cause (see below)tool errorexit 1 (exit 2 on budget exhaustion)
FailedWithDataSESSION_ERROR-32603tool errorexit 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/Busy keep dedicated codes (-32001/-32002); typed agent causes use the canonical meerkat_contracts::ErrorCode::jsonrpc_code() values; all other session variants collapse to -32603 with the message preserved.
  • REST: Endpoint-typed ApiError mapping (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: true with 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

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

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

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

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

# Kitchen sink
meerkat = { version = "0.7.11", 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.
CapabilityConfig gateNotes
builtinstools.builtins_enabledBuiltin tasks and utility tools
shelltools.shell_enabledShell and shell job tools
scheduletools.schedule_enabledDurable schedules and occurrence delivery
work_graphtools.workgraph_enabledWorkGraph tools and companion workflow
memory_storefeature/runtime storeSemantic memory retrieval
skillsskills.enabled and compiled skills supportSkill 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:
CapabilityDescriptionAnthropicOpenAIGemini
visionModel can process image content in user messagesYesYesYes
image_tool_resultsModel can process image content in tool resultsYesNoYes
inline_videoModel can process video content in user messages, including inline bytes and provider-readable URI referencesNoNoYes
realtimeModel can back a caller-initiated live channelNogpt-realtime-2 onlyNo current catalog row
supports_web_searchModel supports provider-native web search toolsYesYes for chat catalog rows; not gpt-realtime-2 / CodexYes
These fields are set in meerkat-models/src/capabilities/** (the types live in meerkat-core/src/model_profile/**) and exposed via ModelProfile. They control:
  • view_image tool visibility — hidden via the capability-base ToolFilter (capability_base_filter_for_image_tool_results) 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. 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. Web search is enabled by default for all catalog models with supports_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.
ProviderTool typeConfig keyDefault
Anthropicweb_search_20250305provider_tools.anthropic.web_searchtrue
OpenAIweb_searchprovider_tools.openai.web_searchtrue
Geminigoogle_searchprovider_tools.gemini.google_searchtrue
Self-hostedNot wired (v1)SelfHostedModelConfig.supports_web_searchfalse
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: {"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 (false or null) in the typed search slot blocks the build-derived default and no search tool is emitted
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