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

Capability behavior

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): 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. The durable resume holds are not service faults: the session exists, its content is retained intact, and no runnable authority is handed out until machine-authorized recovery or reconciliation clears it. They classify as “durably present, not runnable” — the canonical SessionNotRunning wire code (JSON-RPC -32003, HTTP 409) — and carry a typed durable_resume_hold structured payload (tail_held_for_recovery / recovery_refused / evidence_quarantined, plus session_error_code, session_id, and content_retained: true) so callers distinguish the hold class without parsing messages.

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.

Mob control scopes

Plane-(b) operator access to a mob is scope-gated (multi-host mobs §8) with an owner-implicit-full, default-deny posture: Grants are recorded per principal with an optional raw expires_at_ms; expiry is evaluated once per decision at the enforcement seam (expired ⟺ expires_at_ms <= now_ms, never cached, never a machine fact). Grant administration requires AdminGrants; AdminHost covers host bind/revoke only — the two are distinct. Denials are typed end to end: MobError::ScopeDenied carries { required, presented }, projected as ErrorCode::ScopeDenied (JSON-RPC -32025, HTTP 403, CLI exit 45) with a WireScopeDeniedDetail details payload; presented is always the caller’s own effective set. Member upcalls and member-context tool surfaces ride the separate agent authority lane (MobToolAuthorityContext): grants never gate upcalls, and upcalls never satisfy scope checks. Revocation and expiry take effect at admission time — already-open event streams are not torn down in v1 (recorded v2 hardening).

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. 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: 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.
  • Still images on live channels — a realtime model’s vision capability projects to LiveOpenResult.capabilities.image_in; clients must check that channel capability before sending an image chunk.
  • Provider-native web search — injected by default when supports_web_search is true and the corresponding provider_tools.* config is enabled.

System-message projection

The durable Session transcript is the authority for instructions. It retains every System and SystemNotice message at its exact authored position; an adapter projection never rewrites that transcript. Provider limitations never redefine Session validity. Meerkat authors every System row as ordinary ordered history. A limited provider request projection returns a typed, non-retryable error when the resulting transcript is unrepresentable; rebinding to an exact provider can execute the same Session unchanged. Adapters never hoist, merge, or delimiter-join distinct canonical System rows. Realtime reconstruction applies the same ordered replay-window policy to every role and replays each retained System row as an in-conversation System item. runtime_system_context remains retired, and the provider’s separate session instructions field is not a shadow transcript authority. SystemNotice remains an explicit in-place history event. 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 request defaults at build time as a typed, never-persisted ProviderTag (the legacy-named tool_defaults half of ProviderParamsCarrier). These defaults include provider-native tool bodies and model-capability-derived cache policy. Effective per-turn params are a typed field-wise merge: explicit provider_params win and build defaults fill only the unset provider-native slots — the old RFC 7396 raw-JSON merge-patch is retired. 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 request 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. Ordinary compactions wait at least 3 turns; a live history-token or exact provider-byte capacity crossing bypasses that cost guard so an already-oversized request can recover immediately.
  • 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.
  • Every composed store slot carries a machine-readable durability class (durable / rebuildable_cache / scratch); a durable slot resolving to a non-persistent store without a manifest ephemeral_domains declaration is a typed startup error, never a silent in-memory fallback.

See also