live/* JSON-RPC methods. To use a live channel, create a session on a realtime-capable model (for example gpt-realtime-2) and call live/open to start the channel explicitly. Channel creation, status, refresh, input, interruption, truncation, and close are all caller-initiated through the live surface.
This guide covers how to open live channels, send input, and observe channel state.
What this guide is for
Use this guide when you want to:- open a live audio/text channel on a session
- send audio, text, or model-supported image input to a live channel
- understand live channel lifecycle and capabilities
- reason about live channels in the context of the normal session model
ModelCapabilities.realtime remains the capability bit that gates whether live/open succeeds. Image input is a separate per-channel capability: check LiveOpenResult.capabilities.image_in before sending an image. The --live-ws <addr> flag on rkat-rpc enables the WebSocket listener required for audio transport.Mental model
A session has exactly one conversation history. The session’s LLM client is the active delivery mechanism for that history; most models deliver via request/response (e.g. Anthropicclaude-opus-4-8, OpenAI gpt-5.6-sol), a small class delivers via a persistent bidirectional socket (e.g. OpenAI gpt-realtime-2). The only thing a realtime-capable model changes is how the model is reached — the session still owns history, tools, context, and turn boundaries.
- One canonical history. The session is the source of conversational truth. Committed audio and text join the same history as non-live turns at turn boundaries. A provider-acknowledged image is materialized and persisted as canonical context before the channel emits its redacted
user_content_committedreceipt. - Capability gates channel open.
ModelCapabilities.realtimeis the signal that determines whetherlive/opensucceeds. No channel is opened automatically. - Channel lifecycle is caller-initiated. Call
live/opento start a channel,live/closeto end it, andlive/send_input/live/commit_input/live/interrupt/live/truncateto control flow. - Refresh without history replay.
live/refreshapplies mutable session config (instructions, tools, audio format) to an open channel without interrupting audio flow. Identity swaps (model/provider) and canonical transcript or user-content-registry rewrites cannot be hot-applied; they return a typed reopen-required error and requirelive/close+live/open.
Realtime-capable models
ModelCapabilities.realtime: bool is set per model in the curated catalog (meerkat-models; the ModelCapabilities type lives in meerkat_core::model_profile). Capability is catalog data, not prefix inference:
- OpenAI:
gpt-realtime-2— the only realtime-capable model in the current catalog, with still-image input advertised ascapabilities.image_in == true. - Gemini: reserved for future
*-live*endpoints — no production models today. - Anthropic: no realtime-capable models today.
- Self-hosted:
realtime = falseby default.
GET /models/catalog (REST) or models/catalog (RPC) to inspect which models advertise realtime == true in the running runtime.
Opening a live channel
A live channel is opened by creating a session on a realtime-capable model and callinglive/open. Two paths:
At session creation
Create a session on a realtime-capable model, then calllive/open:
live/open returns a LiveOpenResult containing the transport bootstrap (e.g. WebSocket URL), WireLiveChannelCapabilities, and WireLiveContinuityMode. The --live-ws <addr> flag must be set on rkat-rpc for WebSocket transport.
The direct WebSocket input path accepts JSON text chunks and negotiated raw
PCM audio only, with a 2 MiB aggregate and per-frame ceiling. It does not
accept inline images. Send every image through JSON-RPC
live/send_input;
the JSONL control plane accepts frames up to 64 MiB (excluding the newline),
which accommodates the documented 20 MiB decoded-image ceiling plus base64
and envelope overhead.Bounding the initial seed
By default,live/open projects the full canonical history into the
new realtime provider session. Long-lived sessions can request a smaller seed
with the optional positive seed_max_chars parameter:
System and SystemNotice rows do not consume this replay budget because
OpenAI Realtime does not replay them as conversation items. Their instruction
projection is derived separately from the full active materialized transcript,
so even a System larger than seed_max_chars remains exact. An existing
compaction summary may be retained as the head before the suffix.
If selection omits any history, LiveOpenResult.continuity reports
mode: "degraded". Omitting seed_max_chars preserves the full-seed behavior.
The value must be positive; the server rejects zero.
Ordered transcript instructions, image identity, tombstones, and aggregate
accounting are outside the seed-message window and remain complete even when
older dialogue is omitted. Runtime instructions have no separate provider
sidecar.
Configuration defaults
The session’s default model can be set in config (~/.rkat/config.toml or project-local) via default_model. Any session created without an explicit model parameter inherits the configured default — so setting default_model = "gpt-realtime-2" makes live channels available by default. See the Configuration guide.
Observing channel status
Uselive/status to read the current state of a live channel:
Live channel methods
Sending image input
Image input is turn context, not a response trigger by itself. Check the channel’simage_in capability, submit the image with a caller-stable
idempotency_key, wait for its durable receipt, then follow it with dependent
text or audio. On an explicitly committed channel, live/commit_input can
request a response from image-only context after that receipt arrives.
image_input_idempotency_conflict.
The data value contains the encoded image bytes as standard base64; do not
include a data: URL prefix. OpenAI Realtime currently admits PNG and JPEG,
verifies that the byte signature agrees with the declared MIME type, and
enforces a 20 MiB decoded-image safety ceiling before provider send. Invalid
keys, malformed base64, unsupported MIME types, content mismatches, and oversized images use the
typed rejection reasons image_input_idempotency_key_invalid,
image_input_invalid_base64, image_input_unsupported_mime,
image_input_content_mismatch, and image_input_too_large. Before provider
send, Meerkat also checks the canonical session’s cumulative decoded image
history. A new image that would take that history above 40 MiB is rejected as
image_input_history_budget_exceeded; it is not sent or persisted. A binding
without image support uses image_input_not_implemented.
Do not place an image behind uncommitted text or audio. Commit that input first,
then submit the image as the first content in the fresh sequence; otherwise the
adapter rejects it with image_input_requires_commit. This keeps the durable
predecessor identity unambiguous.
Acceptance, rejection, and durability
live/send_input has deliberately layered outcomes:
The same rejection reason can be immediate or asynchronous depending on which
layer detects it. For example, malformed base64 or an invalid key is rejected
before queue acceptance, while a content conflict discovered against durable
session identity is reported after the command drains. A terminal live error
or a missing receipt is never evidence of persistence.
For a WebRTC or direct-WebSocket channel, send the image through JSON-RPC
live/send_input, not the WebRTC data channel or direct WebSocket. The data
channel’s fixed message ceiling is suitable
for control/audio coordination but not full images. An image envelope delivered
whole and decoded within the effective 65,535-byte ceiling receives the scoped
image_input_transport_unsupported rejection, and that data channel remains
open. A larger envelope may be rejected by the browser or SCTP transport before
Meerkat can classify it, so it cannot receive a server-side typed rejection.
Route every image through JSON-RPC; the data channel carries the receipt
described below.
If the adapter’s bounded image queue or provider-ack window is full,
image_input_backpressured reports the byte ceiling without retaining another
caller payload. Retry the same key after earlier image receipts arrive.
The public transport does not echo image bytes back. After Meerkat has applied
the image to canonical session history, it emits a redacted
user_content_committed observation containing the item identity, content
index, media type, and caller idempotency_key. WebRTC callers must wait for
that receipt before sending RTP audio that depends on the image.
Reopening the same session hydrates blob-backed user images and replays the
typed images to the provider. Canonical live image history has a 40 MiB
aggregate decoded-image ceiling (two maximum-size images); every history
occurrence counts, including repeated references to the same blob. The live
input gate enforces that same ceiling before accepting each new image, so a
successfully committed live image cannot make an otherwise valid session
unreopenable later. Existing legacy or out-of-band history above the ceiling,
a missing blob, or bytes that do not match the durable content-addressed
identity still fail live/open instead of trimming or silently changing
visual context. Reduce canonical history explicitly or start a fresh session;
reconnect never substitutes placeholders for accepted images.
End-to-end example
Live channels and mobs
Each mob member has its own session, so live channels are per-member by construction. To make a member live-capable, set its profile’smodel to a realtime-capable model (for example in the MobDefinition TOML):
Limitations and known gaps
- OpenAI Realtime API only. The shipped provider integration is OpenAI’s Realtime API (
gpt-realtime-2). Azure OpenAI (azure_openai) and other providers are not yet wired into the live transport layer. - One live channel per session. A session has at most one live channel at a time. For per-member live channels in mobs, open channels against individual member sessions.
- Deferred sessions are model-gated.
live/openmay materialize a deferred session whose resolved model is realtime-capable; a deferred non-realtime session is rejected before channel creation. - Identity/history rewrites require close/reopen.
live/refreshapplies config-only changes (instructions, tools, audio format); model/provider swaps and canonical transcript or user-content-registry rewrites requirelive/close+live/open.
See also
- Mobs guide — spawning members, profiles, realtime-capable per-member models
- Configuration guide — setting
default_model - JSON-RPC API —
session/create,live/*methods - REST API — REST routes for session creation
