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
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):
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/Busykeep dedicated codes (-32001/-32002), durable resume holds useSessionNotRunning(-32003), and typed agent causes use the canonicalmeerkat_contracts::ErrorCode::jsonrpc_code()values. Remaining 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: 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 /capabilitiesand JSON-RPCcapabilities/getreturn the registeredCapabilityStatusinventory. A status distinguishes available, policy-disabled, and not-compiled behavior.GET /runtime/capabilitiesand JSON-RPCruntime/capabilitiesreturnRuntimeHostCapabilities, including explicit boolean facts about the current host and its registered endpoints.
Capability inventory
The completeCapabilityId 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 receivesstream_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_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 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.
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, 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 (
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 >= thresholdOR 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 providerPolicyStopor 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.
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: A successful turn commits an exact store-issued physical successor according to the pinned realm backend. The serialized
Sessionremains 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.
WholeBlobcurrentness binds the session ID, store revision, and blob digest.HeadCanonicalcurrentness 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 bothrealm_idand 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 manifestephemeral_domainsdeclaration or startup fails typed. A provider can instead return typedDeclaredEphemeraldirectly; neither path permits silent in-memory fallback.
See also
- Session contracts — concurrency, durability, and compaction semantics
- Architecture — crate structure and agent loop details
