Skip to main content
Meerkat exposes a JSON-RPC 2.0 surface for IDE integration, desktop apps, and automation tools. Stdio is the default, and the binary also supports TCP plus optional WebSocket and feature-gated WebRTC live transports. Like the REST and MCP servers, RPC composes the shared runtime-backed service so agents can stay alive between turns. RPC adds a broad typed method and notification surface suited to interactive clients. The RPC surface is fully runtime-backed:
  • keep_alive is runtime/session behavior
  • session/external_event queues runtime-backed external work
  • committed success is not rewritten to cancellation

Getting started

1

Start the server

The server reads newline-delimited JSON (JSONL) from stdin and writes JSONL to stdout. Each line is a complete JSON-RPC 2.0 message.Optional listener modes:
TCP and live WebSocket listeners are local-only by default. Binding a non-loopback address such as 0.0.0.0:9001 requires --allow-remote; that flag only opts in to network exposure and does not add authentication or encryption. Use it behind a production-safe transport wrapper such as SSH tunneling, mTLS, or another authenticated encrypted channel. Plain TCP host capabilities continue to report secure_remote_rpc: false.rkat-rpc --tcp is a JSON-RPC host transport. It is not the signed Meerkat peer/comms channel used by remote agents or external mob members; use rkat run --comms-listen-tcp ... for that.
2

Send the handshake

3

Create a session

rkat-rpc defaults to a new isolated realm each time. To share sessions with another process, use the same --realm <id> and physical --state-root, or a matching --context-root that resolves the same project-local root.

Runtime scope

rkat-rpc accepts global scope flags:
  • --realm: explicit sharing/isolation key
  • --instance: optional instance metadata
  • --realm-backend: creation hint only; actual backend is pinned per realm manifest
Transport and live-channel flags are:
--live-webrtc is present only in builds with the live-webrtc feature. --live-ws-scheme wss changes the advertised bootstrap URL for a TLS-terminating proxy; the listener itself still accepts plain WebSocket bytes.

Method overview

This table mirrors the generated catalog in artifacts/schemas/rpc-methods.json (meerkat_contracts::rpc_method_catalog). initialize returns the methods enabled by both the compiled build and runtime composition. Reduced builds, an absent skill runtime, or unconfigured live transports can expose a subset of this full catalog. Both helper parameter types (MobSpawnHelperParams, MobForkHelperParams) require result_label and max_text_bytes. MobHelperResult is the exact operation carrier: output, tokens_used, agent_identity, member_ref, bounded_result, session_id, usage, turns, and tool_calls are required, while retirement_error reports post-result cleanup debt when present.
Every Schedule result is one flattened public object. Configuration fields including planning_horizon_days, planning_horizon_occurrences, labels, created_at_utc, and updated_at_utc are top-level; there is no nested config and persisted machine authority is not exposed as machine_state.
WorkGraph goal and attention mutation methods require trusted host/session authority. JSON-RPC exposes observability reads; goal creation, reassignment, policy escalation, confirmation, and closure stay on trusted in-process host and agent-tool surfaces.
Generated assistant images use the same surface-neutral history and blob APIs as other blob-backed artifacts. Read session/history, find assistant blocks with block_type: "image", then call blob/get with data.blob_ref.blob_id to retrieve the base64 payload.
Live channels provide low-latency audio/text streaming with model-gated image input. Create a session with a realtime-capable model (e.g. gpt-realtime-2), then call live/open to start the channel. The --live-ws <addr> flag on rkat-rpc enables the WebSocket listener required for audio transport. Use live/status to observe channel state and check live/open’s capabilities.image_in before sending image context.
RPC is the canonical typed substrate for the SDKs: use explicit mob/* lifecycle, host-ingress, and observation methods from apps. Inside running sessions, mob capability is exposed by composing meerkat-mob-mcp (MobMcpState + AgentMobToolSurfaceFactory) into SessionBuildOptions.mob_tools, which provides authorized mob_* tools to the agent. external_tools remains for callback and MCP-backed tool dispatchers.
WorkGraph RPC methods are observability reads. Programmatic hosts inspect WorkGraph through workgraph/get, list, ready, snapshot, events, goal/status, and attention/list; agents mutate ordinary WorkGraph state through the workgraph_* tools. Authority-bearing attention reassignment and policy escalation are runtime-injected WorkGraph tool operations, not public JSON-RPC methods.

Runtime host projections and health

runtime/host_info is a read-only projection of process identity, host ID scope, realm and endpoint metadata, feature flags, and health. It does not enroll a member host or grant mob placement authority. runtime/capabilities returns a contract version plus boolean RuntimeHostFeatureFlags: runtime_backed_sessions, mobs, mcp_live, comms, blobs, session_events, session_streams, schedules, skills, event_replay, artifacts, approvals, external_members, secure_remote_rpc, multi_host_mobs, and durable_jobs. These host facts are distinct from the generic status-bearing CapabilityId entries returned by capabilities/get. Older payloads that omit multi_host_mobs or durable_jobs decode those fields as false; clients should use the flag and not infer multi-host support from a version number. runtime/health returns the worse-wins rollup across five declared dimensions: jobs, session_liveness, session_durability, session_runtime_loop, and session_run_start. This RPC host probes all five. A plain check key is a measured result. unreadable:<dimension> means a probe ran but could not obtain a reading and rolls the overall status to at least degraded. unmeasured:<dimension> is a coverage marker for a surface with no probe and does not enter the rollup. The session liveness check covers queued work parked on an active executor with no run in flight, including repeated staging churn. session_run_start covers a current staged run that is overdue to begin. Neither claims to measure a run that began and then stopped producing progress.

Protocol

Standard JSON-RPC 2.0 with "jsonrpc": "2.0" on every message. Three message types:
  • Request (either direction): has id, method, params. The server sends tool/execute requests to clients for registered callback tools.
  • Response (back to the requester): echoes id with result or error.
  • Notification (either direction): has method, params, no id, and receives no response.

Request cancellation

Cancel an in-flight request by its exact JSON-RPC request ID, preserving its number/string type. For example, while session/create request 2 is still in flight, send this notification:
Client -> server notification
The notification has no id and receives no reply. When cancellation wins before the original request commits success, the original request receives REQUEST_CANCELLED (-32005):
Server -> client response to the original request
The target is a request ID, not a session ID, so cancellation can be sent before session creation returns an identity. It is not a rollback guarantee: committed success is not rewritten to cancellation. This is separate from turn/interrupt, which targets an existing session’s turn. MCP uses the different notifications/cancelled notification and requestId spelling described in the MCP reference.

Callback tools

Register client-owned foreground tools with tools/register:
Registration advertises the tool; execution requires the reverse exchange. During a turn, the server sends its own JSON-RPC request:
Echo the server request’s id, not the turn’s ID or tool_use_id. tool_use_id is the provider’s tool-call correlation identity; name selects the registered tool and arguments contains its JSON arguments. In the result, content defaults to an empty string and is_error defaults to false if omitted. A client can instead return a JSON-RPC error for the same request ID; the server projects it as a tool-execution error. Keep reading and answering reverse requests while waiting for the turn’s result, or the turn cannot finish its callback. This foreground RPC exchange is neither MCP’s pending_tool_calls/meerkat_resume continuation nor the separate detached-job callback extensions. tool/execute is not a client-callable catalog method.

Session methods

initialize

Handshake. Returns server capabilities.
The example array is abbreviated. The actual array reflects the compiled build and the runtime’s enabled composition: skills/list requires a skill runtime; live/* requires a configured live transport, and live/webrtc/answer additionally requires enabled WebRTC support. For the full catalog rather than this runtime subset, including parameter/result type names, use artifacts/schemas/rpc-methods.json.
string
Server name.
string
Server version.
string
Protocol contract version.
array
List of supported method names.

session/create

Create a new session and run the first turn. Set initial_turn to "deferred" to return a pending session_id without running the first turn; that returned session_id is valid for the first turn/start, including sessions created with inline external_tools.
Only prompt is required. Other fields follow the per-field defaults and override rules below; not every omitted value inherits configuration. During execution, session/event notifications are emitted (see Notifications).

Parameter reference

ContentInput (string | ContentBlock[])
required
The user prompt as a string or typed content-block array, for example [{"type":"text","text":"Hello"}]. Media support depends on the selected model/provider; see model capabilities.
ContentInput[] | null
default:"null"
Host-attached injected context for the first turn. Each entry materializes as a separate typed injected-context user-channel message immediately before the first turn’s user message, in order; injected context is excluded from semantic-memory indexing.
string | null
default:"null"
Non-empty, exact request-only host facts for the immediate first turn; whitespace is significant. The pending runtime input retains the bytes for crash retry; the Session transcript, compaction summarizer, and extraction phase never receive them. Deferred create rejects this field; provide it on the eventual turn/start.
string | null
default:"config/catalog default"
Model name (e.g. "claude-opus-4-8", "gpt-5.5").
string | null
default:"inferred from model"
Provider name: "anthropic", "openai", "gemini", or "self_hosted".
WireAuthBindingRef | null
default:"null"
Structural credential selector, for example {"realm":"prod","binding":"anthropic","profile":"claude_oauth"}. realm and binding are required when supplied; profile is optional. Configure the referenced realm, binding, and auth profile first. Omission/null lets the create resolver choose credentials. A colon-delimited CLI string is not accepted; provenance is server-owned.
u32 | null
default:"config default"
Max tokens per turn.
SystemPromptOverride
default:"inherit"
Omission/null inherits the base prompt; a string sets an explicit base prompt; {"action":"disable"} suppresses inherited/config/default/AGENTS base prompt sources. Separately appended dispatcher, tool, and additional instruction sections may remain, so disabling is not a promise of no System content. This create-time policy differs from turn/start’s ordinary ordered System-message string.
object | null
default:"null"
JSON schema for structured output extraction (wrapper or raw schema).
u32 | null
default:"null (config default)"
Max retries for structured output validation. Omission/null inherits configuration (stock default 2); explicit 0 is preserved.
HookRunOverrides | null
default:"null"
Run-scoped hook overrides (entries to add, hook IDs to disable).
bool | null
default:"null (host/factory default)"
Override built-in tools (task management, etc.). Omission/null inherits host/factory configuration; explicit false disables them.
bool | null
default:"null (host/factory default)"
Override the shell tool (requires built-ins). Omission/null inherits host/factory configuration; explicit false disables it.
bool | null
default:"null (host/factory default)"
Override semantic memory (memory_search tool + compaction indexing). Omission/null inherits host/factory configuration; explicit false disables it.
bool | null
default:"null"
Override schedule tools for this session. null uses the surface default.
bool | null
default:"null"
Override WorkGraph tools for this session. null uses the surface default.
bool
default:"false"
Keep the new session alive after its turn for comms. Omission is false; null is rejected on create. true requires comms_name. This differs from turn/start’s nullable override of existing session intent.
string | null
default:"null"
Agent name for inter-agent communication.
object | null
default:"null"
Provider-specific parameters (e.g., thinking config, reasoning effort).

Response fields

string
UUID of the created session.
string
The agent’s response text.
u32
Number of LLM calls made.
u32
Number of tool calls executed.
WireUsage
Token usage breakdown.
JSON (optional)
Parsed structured output when extraction succeeds. Omitted when absent.
ExtractionError (optional)
Post-turn extraction failure details: last_output is the committed main-turn assistant text that extraction attempted to transform, attempts is the number of extraction attempts made, and reason describes the failure. Omitted when absent.
SchemaWarning[] (optional)
Schema compatibility warnings. Omitted when absent.
A successful run result can retain committed text even when post-turn structured-output extraction fails. Inspect structured_output together with extraction_error; absence of structured output does not prove extraction was never requested. These optional fields in WireRunResult are omitted when absent, not filled with null.

session/history

Read committed transcript history for an existing session.
Returns oldest-to-newest committed messages plus pagination metadata. This method follows the same owner-resolution rules as session/read, including mob-owned session IDs when mob support is enabled. When limit is omitted, the server returns at most 100 messages. The maximum accepted limit is 1,000 and the maximum offset is 1,000,000; larger values fail with INVALID_PARAMS before the session store is queried.

session/export_atif

Export the currently available event audit/replay projection as an ATIF (Agent Trajectory Interchange Format) v1.7 trajectory document.
agent_name, agent_version, and model_name are optional overrides for the trajectory’s agent block; when model_name is omitted the session’s model is used. The export replays the currently projected event log page by page, so it requires a host with event projection installed. Projection is asynchronous best-effort derived state and the handler does not drain it first; an export can therefore lag the latest committed session state. Outcomes:
  • A host without durable event replay fails with INVALID_REQUEST and event replay is not enabled for this runtime host.
  • An existing session whose durable log is empty (for example a session created with a deferred first turn) returns a trajectory that names the session with no steps and final_metrics.total_steps of 0. It is not a missing session.
  • A session that does not exist fails with SESSION_NOT_FOUND.
  • A session whose durable log exceeds 500,000 events fails with INVALID_PARAMS naming the bound; page the log with events/list_since, or export it to a file with rkat session export-atif, which has no such bound.
  • A session whose trajectory outgrows the outbound message limit (32 MiB) fails with BUDGET_EXHAUSTED naming that limit and the bytes it had accumulated. The fold measures itself page by page, so the refusal arrives while the document is still partial rather than after the whole log is in memory.
The two bounds answer with different codes on purpose: the event bound is a property of the request (INVALID_PARAMS), the size bound is a property of the response, so it answers the way outbound admission answers any oversized result (BUDGET_EXHAUSTED). They cover different content mixes: an all-delta log can hold hundreds of thousands of events and still fold into a small document, which is what the event bound is for; a tool-heavy log outgrows the response long before that count, which is what the size bound is for.

session/rewrite_transcript

Commit a transcript rewrite without changing the session identity. The request names a message-range selection, supplies replacement messages, and records a machine-readable reason. The session appends a rewrite commit, advances its transcript head, and retains the parent revision for audit and restore.
Returns the stable session_id, the parent revision, the new revision, and the updated message count. running_behavior currently supports "reject", which returns SESSION_BUSY if the target session has active work.

session/transcript_revision

Read one retained transcript revision body by revision id.
Use "current" to read the active transcript head, or pass a concrete revision returned by session/rewrite_transcript or session/restore_transcript_revision.

session/transcript_revisions

List retained transcript revision commits (oldest first) together with the current transcript head revision. Each entry records the revision the commit produced, the parent it was applied against, the recorded actor, the rendered rewrite reason, and the commit timestamp.
Sessions without any rewrite commit return an empty list; head_revision still names the active transcript head. Pass a returned revision to session/transcript_revision to read the retained body, or to session/restore_transcript_revision to restore it.

session/restore_transcript_revision

Restore a retained transcript revision as the active transcript head without changing the session identity. Restore is represented as another rewrite commit so the graph remains append-only even though the source transcript projection is updated in place. revision accepts the same selector as session/transcript_revision: "current" resolves to the head revision, so restoring it surfaces the typed no-op rewrite error.

session/fork_at

Create a new idle session whose transcript is the source session prefix ending before message_index. The source session is not mutated.
running_behavior currently supports "reject", which returns SESSION_BUSY if the source session has active work.

session/fork_replace

Create a new idle session from the source prefix through message_index, replacing the addressed message or block with a typed replacement. Later source messages are intentionally omitted so callers continue from the edited branch instead of replaying stale descendants.
Supported replacement variants are message, user_content_block, assistant_block, and tool_result_content_block. Edits always create a new session identity.

session/list

List sessions.
Each row is a WireSessionSummary with created_at and updated_at in Unix seconds. is_active is the reported session activity flag, not a runtime-state enum. Optional session_ref and non-empty labels may also be present. session/list is owner-paginated rather than load-all/truncate: omitted limit defaults to 100, the maximum is 1,000, and offset is capped at 1,000,000. The same 100/1,000 collection limits apply to events/list_since, workgraph/list, workgraph/ready, workgraph/snapshot, and workgraph/events. WorkGraph ready-set and snapshot projections are atomic only within explicit process bounds. A projection that would need to scan more than 1,000 items, 1,000 edges, or 1,000 attention bindings fails closed with INVALID_PARAMS; narrow the realm/namespace/filter and retry. The store does not materialize an oversized graph and truncate it after the fact.

session/read

Get session metadata.
string
required
Session ID to read.
The result is WireSessionInfo, not the list summary: it includes model/provider but no total_tokens or state. Timestamps are Unix seconds, and is_active is the reported activity flag. Optional fields include session_ref, last_assistant_text, resolved_capabilities, and non-empty labels.

session/archive

Remove a session from the runtime.
string
required
Session ID to archive.

Turn methods

turn/start

Start a new turn on an existing session.
Returns the same run-result shape as session/create. Fails with error code -32002 (SESSION_BUSY) if a turn is already in progress.
string
required
Session ID to continue.
ContentInput (string | ContentBlock[])
required
The follow-up prompt as text or typed content blocks, for example [{"type":"text","text":"Continue"}]. Media support is model/provider dependent; see model capabilities.
ContentInput[] | null
default:"null"
Host-attached injected context for this turn. Each entry materializes as a separate typed injected-context user-channel message immediately before the turn’s user message, in order; injected context is excluded from semantic-memory indexing.
string | null
default:"null"
Non-empty, exact request-only host facts for this newly admitted turn; whitespace is significant. Projected before the admitted conversational user message for foreground model calls only; never appended to Session.
string | null
default:"from session"
Model override for this turn. On pending (deferred) sessions this sets the model before materialization. On materialized sessions this hot-swaps the LLM client for the remainder of the session.
string | null
default:"from session"
Provider override (e.g. "anthropic", "openai", "gemini"). Typically inferred from model.
TurnMetadataOverride | null
default:"preserve"
Omission/null preserves current session parameters. {"action":"set","value":{"thinking_budget_tokens":10000}} replaces the stored override; {"action":"clear"} clears it. The compatibility form {"thinking_budget_tokens":10000} is also accepted as a Set payload. The inner object is the typed ProviderParamsOverride with temperature, top_p, max_output_tokens, reasoning, thinking_budget_tokens, and typed provider_tag extensions, not an arbitrary bag of provider-native keys. Applies alongside model/provider overrides.
TurnMetadataOverride | null
default:"preserve"
Omission/null preserves the session’s binding. {"action":"set","value":{"realm":"prod","binding":"anthropic","profile":"claude_oauth"}} selects an existing configured binding/profile; profile is optional. A bare binding object such as {"realm":"prod","binding":"anthropic"} is also accepted as a Set payload. {"action":"clear"} removes the persisted override and resumes normal credential resolution; it does not disable credentials. Do not supply server-owned provenance.
u32 | null
default:"from session"
Max tokens override.
object[] | null
Tagged structured skill references to resolve and inject for this turn. Each entry uses {"kind":"structured","source_uuid":"...","skill_name":"..."}.

turn/interrupt

Cancel an in-flight turn. No-op if the session is idle.
string
required
Session ID to interrupt.
interrupted is derived from the typed result. The public classifier reports "interrupted"/true for an existing idle session’s no-op as well as a live interruption; it is not proof that a live turn was cancelled. An admitted deferred/staged session has no materialized run to interrupt and returns "staged_noop"/false. Failures use JSON-RPC errors rather than these successful outcomes.

Event methods

session/external_event

Queue a runtime-backed external event for an existing session.

session/peer_response_terminal

Admit a correlated terminal peer response through the typed runtime ingress.
string
required
Session ID to admit the peer response to.
string
required
Canonical peer routing ID.
string
Optional presentation label. It is not used as routing identity.
string
required
Peer correlation ID for the request this terminal response completes.
string
required
Terminal response status: "completed", "failed", or "cancelled".
any JSON
required
Peer-returned terminal payload.
Error -32603 if runtime admission fails, -32602 if the session locator is invalid. This is a queue-only runtime admission path; it does not create a second direct execution loop.

comms/peers

List discoverable peers from configured TrustedPeers and active in-process registrations. Requires the comms feature.
string
required
Session ID to query peers for.
Use peer_id as the to value for comms/send; name is display-only and may collide. Each entry uses the canonical typed PeerDirectoryEntry shape: address is {transport, endpoint}, source records discovery provenance, sendable_kinds states supported comms operations, capabilities carries the versioned capability envelope, and meta contains supplementary description and labels.

skills/list

List all skills with provenance information, including active and shadowed entries.
array
List of SkillEntry objects with key, name, description, scope, canonical source provenance, is_active, and optional canonical shadowed_by provenance.
Returns error -32603 if skills are not enabled. skills/inspect is not part of the advertised RPC catalog. Use skills/list for provenance, or the CLI/MCP skill inspection surfaces when full skill bodies are needed.

Event replay and durable projection

The events/* family is the complete cursor-based replay surface for hosts that report runtime/capabilities.features.event_replay = true:
  • events/latest_cursor returns the latest cursor for a typed scope
  • events/list_since pages envelopes after an optional cursor and reports latest_cursor plus has_more
  • events/snapshot returns a point-in-time session snapshot paired with its cursor
The current public EventReplayScope is {"type":"session","session_id":"..."}. A cursor is scope-bound and monotonic; scope mismatch and a cursor ahead of the latest sequence fail typed rather than being silently clamped. Live session subscribers use a bounded 256-entry broadcast. If a consumer falls behind, it receives AgentEvent::StreamTruncated with StreamLagged { dropped }, then retained events. Persistent hosts can install one internal audit projector through a separate unbounded queue and warn when the queue reaches 1,024 pending events. UI lag cannot drop projector input. EventStore append remains asynchronous best-effort derived state: an append fault latches projection halt and replay fails closed, but does not undo the committed turn. The RuntimeStore/backend carrier remains session authority; SessionStore rows and EventStore are component/content and projection seams.

Durable jobs and monitors

The app-facing jobs/* methods expose realm-scoped job projections without worker attempt IDs or fence tokens. Read methods cover summaries, progress, terminal results, artifact references, and health. jobs/cancel commits a cancellation request, jobs/retry asks the generated job machine to schedule a retry at retry_due_at_ms, and subscribe/unsubscribe manage durable record/notification/event delivery. monitors/start is the explicit high-trust script-monitor entry point. It requires session_id, a caller-stable submission_key, command, positive timeout_secs, restart_class, and delivery. The default output protocol is framed_jsonl; lines is also supported. Agent-authored monitors cannot claim the adoptable restart class. Detached shell execution fails closed unless the realm supplies persistent job and blob stores plus its runtime delivery projector; there is no volatile fallback. The mobkit/jobs/* methods are host-worker mutation methods. Every heartbeat, progress, checkpoint, completion, failure, or cancellation acknowledgement is admitted under the exact attempt and fence authority carried by its typed params. Application clients should not synthesize worker mutations from the safe JobSummary projection.

jobs/health

jobs/health returns { "detached_jobs": JobHealthSummary }. The summary separates three states:
  • ok: the complete census observed no unhealthy job fact
  • degraded: the census observed a stale lease, attention state, or backlog
  • unreadable: a read failed or the bounded census was truncated, so it did not establish health
coverage is either {"kind":"complete"} or {"kind":"truncated","scanned":N,"limit":N}. A truncated census always has status: "unreadable"; its counts are lower bounds. Keep pending_outbox_jobs separate from runtime_inbox_backlog: the former counts realm jobs whose delivery was never handed to a runtime, while the latter is a host-store-scoped count of committed runtime deliveries that were accepted but not drained. It can therefore include sessions from other logical realm IDs on the same host.

Blobs and artifacts

blob/get accepts a realm-local blob ID and returns BlobPayload with blob_id, media_type, and base64 data. Assistant image blocks and job results can carry blob references instead of inline bytes. Artifacts are stable metadata records over generated output. artifact/list filters by optional session_id and exact labels, artifact/get reads one record, and artifact/download resolves a blob-backed payload with an optional expected_media_type integrity check. Artifact IDs are opaque realm-local identities, not filesystem paths.

Approvals

The approval family consists of approval/request, approval/list, approval/get, and approval/decide. Requests carry a typed owner, resource, proposed action, risk, non-empty allowed_decisions, optional expiry, metadata, and provenance.
Request
Owners are runtime, session, mob, run, tool_call, or external_member. Resource kinds are file, shell_command, tool_call, device, runtime, network, or other; risk is low, medium, high, or critical. Proposed action kinds are shell_command, file_write, file_delete, network_call, device_control, tool_call, or other. Decisions are approve or deny, and record status is pending, approved, denied, expired, or cancelled. The service rejects an empty decision set, an unsupported decision, or a second terminal decision. Expiry is evaluated lazily on approval/get, approval/list, or approval/decide: a past-due request may first be stored as pending, but its next observation persists expired and it can no longer be decided. cancelled is a durable status that can be restored, but the current public RPC service has no approval/cancel transition. external_member is an audit owner shape, not a cross-host forwarding contract. Multi-host v1 keeps approval records and decisions host-local. RPC opens <store_path>/approvals.json whenever its PersistenceBundle exposes a store path, including a named built-in memory realm. Only an ad hoc bundle with no store path falls back to process-local approval records. The approvals runtime host feature is true only when this approval service is persistent. The file store is a one-host sidecar. It serializes updates with a process-local mutex and atomic temp-file rename, but has no cross-process lock, revision, or CAS. Run only one approval-owning RPC host against a given approvals.json; shared-state multi-host approval writing is not supported.
Approval records are audit data, not a secret store. The proposed action body, request body, metadata, and provenance are persisted as supplied. Do not put credentials or tokens in those fields, and protect the resolved realm store directory with host filesystem permissions. FileApprovalStore does not currently force a private file mode independently of the process umask.
These methods maintain approval audit lifecycle records only. Creating or deciding a record does not automatically gate, authorize, or execute a shell command, tool call, device action, or any other side effect.
requester and the actor supplied to approval/decide are audit identities, not transport authentication proofs. rkat-rpc does not authenticate them. Authorize approval decisions in the trusted host or authenticated proxy before forwarding the RPC call. REST, MCP, and CLI do not expose approval methods.

Schedules, WorkGraph, and multi-host mobs

The schedule family provides CRUD, pause/resume, occurrence listing, tool schema discovery, and direct typed tool calls. Schedule and WorkGraph state are realm-scoped durable domains when the owning feature and store are available. Public WorkGraph RPC is intentionally observational: item get/list, ready work, snapshot, event history, goal status, and attention-list reads. Agents mutate WorkGraph through the workgraph_* tool family; trusted in-process hosts own goal and attention mutation authority. Multi-host mob RPC includes host bind/revoke, host and route observation, scope grants, hard cancel, placed member history, and member live-channel control. Check runtime/capabilities.features.multi_host_mobs rather than inferring support from generic mobs or the server version. mob/member_live_open supports WebSocket transport only for both local and placed members. A request for transport: "webrtc" is rejected; WebRTC uses the session-scoped live/open and live/webrtc/answer flow below only when the caller can address the owning session directly. In multi-host v1, controller session/* methods do not proxy a placed member’s host-local session, so there is no controller-side WebRTC path to a placed member. Durable member role migration is not part of mob/spawn, profiles, or any public JSON-RPC/SDK params. The one-shot resume_from_role declaration exists only on trusted in-process SpawnMemberSpec and the private remote-host MaterializeLaunchMode::Resume wire. It authorizes one exact cold migration from the stored predecessor role while keeping mob ID, member identity, session, and transcript fixed; live sessions and mismatched declarations fail closed.

workgraph/get

Read one WorkGraph item by id. Requires the workgraph feature and a realm-scoped runtime; a runtime without a realm identity returns a typed fault instead of inventing a default scope.
The response is abridged; the full WorkItem shape is in the WorkGraph reference.
string
required
The WorkItemId.
string
Defaults to the runtime’s realm. Any other value is rejected: the service holds exactly one WorkGraphNamespaceGrant.
string
Defaults to the granted namespace (default on standard surfaces). Any other value is rejected.
Every evidence entry, here and everywhere a WorkEvidenceRef appears (the workgraph_add_evidence tool argument, host mutation surfaces such as MobKit’s mobkit/workgraph/evidence/add), has the shape {"kind": string, "id": string, "label"?: string, "summary"?: string}. kind is an opaque provenance label (artifact, pr, log, summary, notification, …); the reserved confirmation kinds (host_confirmation, principal_confirmation, supervisor_confirmation, reviewer_confirmation) are refused from generic callers and stamped only by trusted goal-confirm authority.

workgraph/list

List items in the granted realm and namespace. Params are a WorkItemFilter; all fields are optional and an empty object lists live work.
string
Defaults to the runtime’s realm; must equal the granted realm.
string
Defaults to the granted namespace; must equal it.
boolean
default:"false"
Present in the wire shape, rejected by the standard single-grant services. A namespace grant never implies a multi-namespace capability.
WorkStatus[]
Keep only items whose status is listed: open, in_progress, blocked, completed, cancelled, failed. Checked before the terminality rule below.
string[]
Every listed label must be present on the item (a conjunction). ["release", "docs"] returns only items carrying both; filter on one label to widen the result.
boolean
default:"false"
When false, completed, cancelled, and failed items are dropped even if statuses names them: {"statuses": ["completed"]} alone returns an empty list. Set it to true to read closed work.
integer
default:"100"
Maximum items returned. Omitted means 100; values above 1,000 fail with INVALID_PARAMS before the store is queried.
Items and edges are never deleted. There is no unlink or delete method on any surface and no tool for it; the graph is an append-only ledger. To take an item out of live views, close it as cancelled (it then hides behind include_terminal); to replace it, create the successor and link a supersedes edge from the new item (from_id) to the old one (to_id).

workgraph/ready

List items whose machine-derived readiness holds right now: live status, no unresolved blocks edge, parent join satisfied, and time gates passed. Params are a ReadyWorkFilter.
string
Defaults to the runtime’s realm; must equal the granted realm.
string
Defaults to the granted namespace; must equal it.
string[]
Every listed label must be present (same conjunction as workgraph/list).
integer
default:"100"
Maximum items returned after sorting by priority (high first), then creation time, then id. Omitted means 100; above 1,000 is INVALID_PARAMS.
There is no include_terminal here because a terminal item is never ready. Readiness is evaluated atomically over the namespace; a namespace with more than 1,000 items fails with INVALID_PARAMS and must be narrowed rather than truncated. The result is {"items": [...]}.

workgraph/snapshot

Read one atomic observation of the namespace. Params are a WorkGraphSnapshotFilter with the same fields and semantics as WorkItemFilter (realm_id, namespace, all_namespaces, statuses, labels, include_terminal, limit).
statuses, labels, and include_terminal select the items exactly as in workgraph/list; limit then bounds them (default 100, maximum 1,000). edges contains only edges whose both endpoints are among the returned items, attention only bindings on those items, and ready_item_ids the subset of returned items that is ready at captured_at. A snapshot therefore omits an edge to a terminal item unless include_terminal is true. The atomic scan is bounded before filtering: a namespace with more than 1,000 items, 1,000 edges, or 1,000 attention bindings fails with INVALID_PARAMS and must be narrowed.

workgraph/events

Read the public event history of the namespace. Params are a WorkGraphEventFilter.
string
Defaults to the runtime’s realm; must equal the granted realm.
string
Defaults to the granted namespace; must equal it.
boolean
default:"false"
Rejected by the standard single-grant services.
integer
Return only events with seq strictly greater than this value. Use the last seq you saw, or a snapshot’s event_high_water_mark, to page forward.
integer
default:"100"
Maximum events returned; above 1,000 is INVALID_PARAMS.
Each event carries seq, realm_id, namespace, optional item_id, kind (created, updated, readiness_observed, claimed, released, blocked, closed, linked, evidence_added, attention_created, attention_updated), at, a kind-specific payload, and any typed facts committed with the mutation ({"kind": "item_ready" | "lease_expired" | "namespace_terminal", ...}). Flow execution-binding events are kept in the durable journal but omitted from this projection.

workgraph/goal/status

Read one goal: the work item plus its attention binding. Params are a GoalStatusRequest.
string
required
The WorkAttentionBindingId.
string
Defaults to the runtime’s realm; must equal the granted realm.
string
Defaults to the granted namespace; must equal it.
The result is {"item": WorkItem, "attention": WorkAttentionBinding}. An unknown binding is INVALID_PARAMS with the typed not-found message.

workgraph/attention/list

List attention bindings in the granted namespace, optionally narrowed by target or status. Params are an AttentionListRequest; an empty object lists every binding.
string
Defaults to the runtime’s realm; must equal the granted realm.
string
Defaults to the granted namespace; must equal it.
WorkAttentionTarget
{"kind": "session", "session_id": ...} or {"kind": "lowered_owner", "owner_key": {...}}.
WorkAttentionStatus
Tagged by state: {"state": "active"}, {"state": "paused", "until"?: timestamp}, {"state": "superseded"}, or {"state": "stopped"}.
The result is {"attention": [WorkAttentionBinding, ...]}. Attention reassignment and policy escalation are not RPC methods; they are authority-bearing tools injected into turns that carry an active attention projection.

Live channel methods

Live channels provide low-latency audio and text streaming plus model-gated still-image input on sessions that use a realtime-capable model. Create a session with a model like gpt-realtime-2, then call live/open to start the channel explicitly. Start rkat-rpc with --live-ws <addr> for WebSocket transport, or with the feature-gated --live-webrtc flag for WebRTC signaling. At least one live transport must be enabled for the live/* family to be advertised. See the Live Channels guide for the full flow.

Opening a live channel

Create a session on a realtime-capable model and call live/open:
live/open returns a LiveOpenResult with transport bootstrap (e.g. WebSocket URL), WireLiveChannelCapabilities, and WireLiveContinuityMode.
string
required
Session whose canonical history seeds the live channel.
provider_managed | explicit_commit | null
default:"null"
Optional input-turning mode. Omitted preserves provider-managed behavior.
websocket | webrtc | null
default:"null"
Optional live transport. Omitted uses the server default.
usize | null
default:"null"
Optional positive serialized-character budget for the core-owned seed message window. Omitted preserves the complete canonical history. When present, core selects a recent complete-turn replay suffix. Contiguous System, SystemNotice, and injected-context messages immediately preceding a selected user turn stay with that turn; older System rows may be omitted. An existing compaction-summary head may also be retained. Any truncation returns degraded continuity. Zero is rejected.
System messages follow the same bounded replay policy as other ordered transcript data; their combined historical size need not fit the window. The complete canonical System sequence is retained separately as a refresh drift witness, not as hidden provider instructions that restore omitted rows. Canonical multimodal sidecars also remain outside the message window: image identity, tombstones, and aggregate accounting stay complete even when older replay messages are omitted.

live/webrtc/answer

For WebRTC, request "transport": "webrtc" on live/open. The returned transport bootstrap contains a single-use token and names live/webrtc/answer as the canonical signaling method. Send the browser offer SDP with that token:
Request
The result is { "answer_sdp": "v=0..." }. Tokens are channel-bound, short-lived, and single-use; replay, expiry, or a channel mismatch fails typed.

live/status

Read the current state of a live channel.

live/send_input

Send one typed input chunk. Image input is available only when LiveOpenResult.capabilities.image_in is true; an image is staged as context for the next text, audio, or explicitly committed response. Every image must carry a caller-stable, session-scoped idempotency_key. The JSON-RPC JSONL transport accepts frames up to 64 MiB, excluding the trailing newline. Direct live WebSocket frames are a different input path: they support JSON text chunks and negotiated raw PCM audio only, with a 2 MiB aggregate/per-frame ceiling. Images always use this JSON-RPC method, even when the channel’s observation/audio transport is WebSocket or WebRTC. Server-originated JSON-RPC responses are capped at 32 MiB and queued notifications at 8 MiB. Their byte/count reservations are process-wide and remain owned through the bounded transport write; oversized collection reads must use pagination instead of relying on socket buffering.
The key must be non-empty, no more than 128 UTF-8 bytes, contain no control characters, and have no leading or trailing whitespace. Replay the same key with the same canonical MIME and bytes after a lost receipt: Meerkat does not resend it to the provider and emits the existing durable identity again. Reusing a key for different content is rejected as image_input_idempotency_conflict. If text or audio is already staged, commit it before submitting an image; otherwise the image is rejected as image_input_requires_commit. { "status": "sent" } proves only that the adapter queue accepted the command. Wire validation and queue admission can instead fail immediately with typed LiveSendInputErrorData. A check that fails after acceptance is emitted as a typed command_rejected observation. Only a user_content_committed observation carrying the same idempotency_key proves the provider-acknowledged image reached durable canonical session history. The receipt is redacted: it contains identity, ordering, media type, and the key, never image bytes. Each image is limited to 20 MiB decoded, and canonical live image history is limited to 40 MiB decoded in aggregate, counting repeated references as separate occurrences. live/send_input rejects a new image that would cross the aggregate ceiling as image_input_history_budget_exceeded before provider send or persistence. That keeps every successfully committed live image reopenable. Legacy or out-of-band history already above the ceiling, missing blobs, and content-address mismatches fail live/open rather than trimming, substituting, or changing image context. See the Live Channels guide for retry and transport details.

Live channel lifecycle methods (live/*)

WebSocket calls require --live-ws <addr>. WebRTC open/answer requires a build with the live-webrtc feature and startup with --live-webrtc. If both are enabled, select the transport explicitly or accept the server default.

MCP methods

mcp/add

Stage a live MCP server addition for a running session. The server is connected at the next turn boundary.
string
required
Session ID to add the server to.
object
required
Typed MCP server configuration. Include name with either stdio fields (command, optional args/env) or HTTP fields (url, optional headers/transport).
bool
default:"false"
Whether to also write the server to disk config.

mcp/remove

Stage removal of an MCP server from a running session. Active tool calls drain before the server disconnects at the next turn boundary.
string
required
Session ID to remove the server from.
string
required
Name of the MCP server to remove.
bool
default:"false"
Whether to also remove from disk config.

mcp/reload

Reload one or all MCP servers for a running session. Useful after config changes.
string
required
Session ID to reload servers for.
string | null
default:"null"
Specific server to reload. If null, reloads all servers.

Models

models/catalog

Return the curated model catalog with provider profiles, capability metadata, and parameter schemas.
No parameters are required. The catalog is resolved from built-in model metadata plus config-backed provider/server entries; invalid provider config can yield INVALID_PARAMS.
ContractVersion (object)
Structured catalog contract version with integer major, minor, and patch components. Unlike initialize, this result does not stringify it.
array
List of provider entries, each containing provider, default_model_id, and models.
object
Model profile with capability flags (supports_temperature, supports_thinking, supports_reasoning) and params_schema.

Capabilities

capabilities/get

Return capabilities registered by the running build’s linked owners, with status resolved against current config. This is not an enumeration of every capability vocabulary member: an unavailable feature can have no row at all. A registered declaration can still report {"NotCompiled":{"feature":"..."}}; missing declarations are not automatically synthesized with that status.
Possible status values:

Config methods

config/get

Read current realm config envelope.

Config write results

RPC config/set and config/patch return ConfigWriteResult: the committed ConfigEnvelope fields (config, generation, realm_id, instance_id, backend, resolved_paths) are flattened at the top level, with optional live_propagation. The report is omitted when the write does not fan out; the current fan-out trigger is a change to agent.model. When present, it contains: Inspect skipped even when clean is true: clean does not mean every session changed, nor that identity lookup succeeded for every session. It describes this report’s synchronous failure lists, not eventual provider application. Observe subsequent live-channel status and errors to determine whether an accepted refresh was applied. Propagation occurs after the config generation commits. A non-clean report is a successful config write with live-propagation failures, not a rollback of the committed generation. Reconcile the listed sessions/channels separately. This report is specific to RPC writes, not config/get or the REST/MCP config envelope.

config/set

Replace config, optionally using generation CAS. Returns the config write result.

config/patch

Merge-patch the config (RFC 7396). Returns the same config write result. This example changes only max_tokens_per_turn, leaving agent.model unchanged, so its result omits live_propagation.
config/set also accepts a direct config object as params for compatibility. In that mode, no CAS check is applied.

Notifications

During turn execution, the server emits session/event notifications with an EventEnvelope<AgentEvent> under params.event:
Inspect params.event.payload.type and (for text deltas) params.event.payload.delta, not params.event.type. event_id, typed source, seq, and timestamp_ms belong to the envelope; optional mob_id is envelope metadata. Event types match the AgentEvent enum in crates/meerkat-core/src/event.rs (agent_event_type assigns each variant its stable wire name) and its schema projection in artifacts/schemas/events.json. New variants must appear in both that enum and this table. The decodable vocabulary includes the explicitly marked legacy row; not every variant is emitted by current writers. stream_truncated is subscriber-local. It reports UI-ring lag, not an EventStore projection result. Use events/list_since or events/snapshot when that optional replay surface is healthy, and reconcile against current session state after receiving it.

session/stream_event notifications

When a session event stream is open (via session/stream_open), the server emits session/stream_event notifications. params.event is the same nested EventEnvelope<AgentEvent> as in session/event, scoped by an outer stream handle.
Outer sequence numbers the explicit stream’s deliveries and is distinct from the source envelope’s event.seq. Outer stream_id and session_id are routing fields; scoped streams can additionally carry scope_id and scope_path. None of these flattens or replaces event.payload.

session/stream_end notifications

An explicit session stream terminates with this notification:
outcome is "remote_end", "terminal_error", or "explicit_close". Calling session/stream_close terminates the handle with explicit_close; normal upstream exhaustion uses remote_end. When a terminal error is reported, the notification additionally contains error: {"code":"stream_queue_overflow","message":"..."} or error: {"code":"stream_receiver_gone","message":"..."}. stream_truncated is an event-level lag observation, not this terminal protocol. Stop consuming a terminated handle and reopen/reconcile as needed. A disconnected transport or lost notification receiver cannot be relied on to deliver its final end notification.

mob/stream_event notifications

When a mob event stream is open (via mob/stream_open), the server emits mob/stream_event notifications. Requires the mob feature.
For mob-wide streams the event is an AttributedEvent (source member identity + profile + envelope). For per-member streams the event is the raw EventEnvelope<AgentEvent>. Runtime incarnation ids and fence tokens are bridge-internal and are not part of public stream payloads.

mob/stream_end notifications

Requires the mob feature. The mob terminal payload omits session_id:
As with session streams, outcome is "remote_end", "terminal_error", or "explicit_close"; mob/stream_close selects explicit-close termination. Conditional error contains code ("stream_queue_overflow" or "stream_receiver_gone") and message; it is absent without an error. Stop using the terminated handle and reopen/reconcile as appropriate. Transport disconnection can prevent delivery of this final notification. Neither stream-end notification is a callable RPC method.

Error codes

Standard JSON-RPC codes plus Meerkat-specific application codes:

Architecture

The RPC server is stateful: agents stay alive between turns through the shared runtime-backed service also used by REST and MCP. RPC’s distinguishing surface is its broad JSON-RPC method catalog, scoped subscriptions, and server notifications.
Each session gets a dedicated tokio task that exclusively owns the Agent. This solves the cancel(&mut self) requirement without mutex. Commands (StartTurn, Interrupt, Shutdown) are sent via channels.
The JSONL writer and notification queues are bounded, and ordinary session subscriptions are best-effort. A slow client can therefore receive a typed stream_truncated marker instead of every live event. Persistent event audit projection can use a separate unbounded internal queue, so UI lag does not drop projector input. The EventStore remains asynchronous derived state rather than session completeness authority.

Comparison with other surfaces

See also