Skip to main content
Meerkat is modular. The meerkat facade defaults to the three provider features (anthropic, openai, gemini) plus the shared copilot backend; realm persistence, compaction, comms, MCP, skills, semantic memory, ATIF facade export, and live-channel orchestration are compile-time options. Schedule, WorkGraph, and durable-job substrates are always linked by the facade. Their runtime services and tools still require host composition and policy activation; the empty schedule and workgraph Cargo features are compatibility aliases, not selectors. These matrices show what works in common profiles. This page contains several intentionally separate matrices. Build profiles describe facade compile-time composition. The session-operation matrix covers only session lifecycle behavior. Generic CapabilityId entries describe tool-family policy, while runtime/capabilities describes one host process. None of those projections is a complete list of every API method or runtime authority.

Build profiles

Durable jobs do not have a facade feature switch of their own. The job types and services are compiled into the facade, but durable detached execution needs a persistent realm job store, persistent blob store, runtime delivery inbox, and realm-bound delivery projector. Memory-only and standalone builds fail detached shell submission closed instead of falling back to a volatile process.

Capability behavior

This table is scoped to session lifecycle operations. It does not describe jobs, approvals, mobs, live transports, or every host surface. 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): In 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, AgentError::SessionNotFound -32001, AgentError::SkillResolutionFailed -32022, and remaining causes -32603. Temporary councils are agent-internal mob orchestration, not a public wire family. The council agent tool returns structured results through the ordinary tool-result channel. Cleanup debt is deliberately not a tool failure: a sealed result whose cleanup retained debt carries cleanup.status = "debt" or "pending". Seating failures, wiring gaps, exhausted budgets, and elapsed deadlines remain typed exit_reason values on that result. 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), durable resume holds use SessionNotRunning (-32003), and typed agent causes use the canonical meerkat_contracts::ErrorCode::jsonrpc_code() values. Remaining 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: General commands use 0 for success, 1 for error, and 2 for graceful budget exhaustion. Multi-host mob commands additionally preserve the typed operator failures: 45 scope denied, 46 host unavailable, 47 stale cursor, and 48 stale fence. 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

Meerkat exposes two related capability projections:
  • GET /capabilities and JSON-RPC capabilities/get return the registered CapabilityStatus inventory. A status distinguishes available, policy-disabled, and not-compiled behavior.
  • GET /runtime/capabilities and JSON-RPC runtime/capabilities return RuntimeHostCapabilities, including explicit boolean facts about the current host and its registered endpoints.
Do not infer one layer from the other. A generic capability token describes a feature or policy family; a host feature flag answers whether this concrete runtime surface can serve the corresponding host protocol.

Capability inventory

The complete CapabilityId vocabulary is: WorkGraph’s companion skill uses the same canonical work_graph token as the public inventory. Adaptive mobpacks stamp adaptive_flow into their requirements so a host that does not know or satisfy the requirement fails closed instead of silently running a downgraded flow. 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 aliases. For example, meerkat-schedule owns Schedule policy, meerkat-workgraph owns WorkGraph policy and registration, and meerkat-skills owns Skills policy.

Runtime host feature flags

RuntimeHostFeatureFlags contains these exact boolean fields: Older payloads that predate multi_host_mobs or durable_jobs decode those fields as false. Clients must check the explicit flag instead of guessing from mobs, the server version, or the presence of shared schema components. The approval flag covers request, decision, status, and audit truth. It does not claim an automatic in-loop tool gate. A named built-in memory realm still has a store_path, so RPC persists its approval sidecar and may report approvals=true; only an ad hoc bundle without a store path falls back to a process-local service and reports the flag as false. Likewise, durable_jobs=true reports host protocol wiring, not permission to silently run a volatile detached process. Background shell needs a persistent realm job store, blob store, runtime delivery inbox, and delivery projector or fails closed.

Public surface coverage

This table describes dedicated public protocol or command families. It does not treat an agent’s ability to call a model-visible tool as an operator API. Shared wire types do not grant exposure. In particular, durable member role migration is absent from every public CLI, REST, JSON-RPC, MCP, and generated SDK spawn request even though the private remote-host bridge shares schema types with public artifacts. Temporary councils are exposed only as the council tool in AgentMobToolSurface, beside delegate and fork_off. Its authority is the calling agent’s machine-minted mob-tool context: it must be allowed to create the temporary mob and manage every source mob it names. No CLI, REST, JSON-RPC, public MCP, or generated SDK council verb exists.

Live transport gates

Realtime model capability, live orchestration, and transport activation are separate facts: The WebRTC build feature alone does not enable signaling. The live/* family is advertised only when at least one live transport is active, and live/webrtc/answer is advertised only when the WebRTC runtime gate is active.

Event delivery and durability

Live session subscriptions use a bounded 256-entry best-effort broadcast. A consumer that falls behind receives stream_truncated with reason.kind: "stream_lagged" and an exact reason.dropped count, then continues from the retained tail. Built-in realm-backed persistent sessions separately feed event envelopes into a dedicated unbounded projector queue and append them asynchronously to a durable audit/replay log. Custom persistent hosts receive this surface only when they install the optional event projection seam. The projector warns when its backlog reaches 1,024. A durable append fault latches a typed halt and refuses to append later sequence numbers past the hole, but does not undo the committed turn. A slow UI subscriber therefore does not itself truncate audit projection input. Reconcile live gaps through available durable replay, a snapshot, or ATIF export rather than treating the live tail as complete evidence. SessionStore and RuntimeStore remain session authority; .rkat/sessions/ views and ATIF output are derived projections.

Model capabilities

ModelProfile describes per-model capabilities used for feature gating and tool visibility. Two fields are relevant to multimodal content support: Catalog eligibility is not host availability. gpt-realtime-2 uses the openai-realtime composition. Both GPT Live rows need the separate openai-live composition, a host-installed and advertised execution_identity authority with the appropriate host-owned binding/profile, and an explicit transport: "webrtc" on live/open. Transport flags alone do not install that authority, and these rows are not interchangeable --live-ws targets. gpt-live-1-codex additionally requires the experimental-gpt-live feature and private experimental admission. Both Live rows currently use provider-managed turns, with no vision or explicit-commit capability. These fields are set in crates/meerkat-models/src/capabilities/** (the types live in crates/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 later System rows into a top-level prefix or merge or delimiter-join distinct canonical System rows. Anthropic’s supports_mid_conversation_system_messages catalog capability gates later System rows. Supported placements must follow a user turn (including user-role notice/tool-result projections) or an assistant whose last block is a web_search_tool_result server-tool result. Turn-scoped instructions authored before their governing User can be placed after that User and before the Assistant in the wire request only; authored durable order is unchanged. Unsupported models or unrepresentable placements return a typed InvalidInputShape error. Leading System rows remain distinct top-level blocks. 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, for OpenAI, model-capability-derived prompt-cache options; the Anthropic prompt-cache default (automatic on the Anthropic API, Vertex, and Foundry, disabled on Bedrock and Copilot) is owned by the provider runtime’s backend client rather than by build defaults, and an explicit provider_tag.cache_control overrides it. 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 OR the measured request budget for the boundary (hydrated messages + exact visible tool definitions + effective output reserve) crosses the threshold on its input side
  • 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. A request-budget crossing does not bypass it: tool definitions and the output reserve survive compaction, so a crossing they cause is a cost decision, not a recovery one.
  • Failure: Ordinary recoverable failures preserve uncompacted history and normally emit CompactionFailed, allowing continuation. A typed provider PolicyStop or unresolved durable compaction-stage authority/cleanup error terminates the run fail-closed. Preserved history can still exceed provider capacity, so the next request is not guaranteed to succeed.
  • Budget: Compaction LLM call draws from the same token budget as regular turns.
The exact staged transcript-rewrite/memory transaction is not the optional asynchronous event-audit projection. An audit append failure does not undo an already-committed turn; unresolved compaction-stage authority must not be suppressed on that basis. 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: A successful turn commits an exact store-issued physical successor according to the pinned realm backend. The serialized Session remains domain state, not an embedded checkpoint or proof of currentness.
  • Intra-turn persistence may write provisional physical successors. Each write returns an exact receipt, and the final runtime boundary promotes the latest receipt without reserializing or reapplying the turn.
  • WholeBlob currentness binds the session ID, store revision, and blob digest. HeadCanonical currentness binds the session ID, store revision, boundary head, and committed head token.
  • Backends are pinned per realm (realm_manifest.json). Surfaces share state only when both realm_id and the resolved physical provider/root match.
  • 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). An undeclared non-persistent durable slot needs a manifest ephemeral_domains declaration or startup fails typed. A provider can instead return typed DeclaredEphemeral directly; neither path permits silent in-memory fallback.

See also