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
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):
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/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.
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_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.- Still images on live channels — a realtime model’s
visioncapability projects toLiveOpenResult.capabilities.image_in; clients must check that channel capability before sending an image chunk. - Provider-native web search — injected by default when
supports_web_searchistrueand the correspondingprovider_tools.*config is enabled.
System-message projection
The durable Session transcript is the authority for instructions. It retains everySystem 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.
Provider-native web search
Web search is enabled by default for all catalog models withsupports_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 (
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 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 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. 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.
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.
- 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 manifestephemeral_domainsdeclaration is a typed startup error, never a silent in-memory fallback.
See also
- Session contracts — concurrency, durability, and compaction semantics
- Architecture — crate structure and agent loop details
