---
title: "JSON-RPC stdio server"
description: "Stateful JSON-RPC 2.0 interface for stdio, TCP, and optional live audio WebSocket hosting."
icon: "terminal"
---

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 an optional live audio WebSocket listener (`--live-ws`). Unlike REST and MCP, the RPC server keeps agents alive between turns for fast multi-turn conversations.

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

<Steps>
  <Step title="Start the server">
    ```bash
    rkat-rpc
    ```

    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:

    ```bash
    rkat-rpc --tcp 127.0.0.1:9001
    rkat-rpc --tcp 127.0.0.1:9001 --live-ws 127.0.0.1:9002
    ```

    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`.
  </Step>
  <Step title="Send the handshake">
    ```json
    {"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}
    ```
  </Step>
  <Step title="Create a session">
    ```json
    {"jsonrpc":"2.0","id":2,"method":"session/create","params":{"prompt":"Hello!"}}
    ```
  </Step>
</Steps>

<Note>
`rkat-rpc` defaults to a new isolated realm each time. Use `--realm <id>` to share config/sessions with other surfaces or processes.
</Note>

## Runtime scope

`rkat-rpc` accepts global scope flags:

```bash
rkat-rpc [--realm <id>] [--isolated] [--instance <id>] [--realm-backend <sqlite|jsonl>] [--state-root <path>] [--context-root <path>] [--user-config-root <path>]
```

- `--realm`: explicit sharing/isolation key
- `--instance`: optional instance metadata
- `--realm-backend`: creation hint only; actual backend is pinned per realm manifest

## Method overview

This table mirrors the generated catalog in `artifacts/schemas/rpc-methods.json`
(`meerkat_contracts::rpc_method_catalog`). `initialize` returns the methods
enabled in the running binary, so reduced or feature-limited builds expose a
subset.

| Method | Category | Description |
|--------|----------|-------------|
| `initialize` | Handshake | Returns server capabilities |
| `help/ask` | Help | Ask Meerkat usage help with the embedded platform skill |
| `runtime/host_info` | Runtime | Read host identity, endpoints, capabilities, health, and realm projection |
| `runtime/capabilities` | Runtime | Read runtime host capability flags |
| `runtime/health` | Runtime | Read runtime host health |
| `session/create` | Session | Create session and run first turn |
| `session/list` | Session | List active sessions |
| `session/read` | Session | Get session state |
| `session/history` | Session | Get committed session transcript messages |
| `session/fork_at` | Session | Fork an idle session at a transcript message index |
| `session/fork_replace` | Session | Fork an idle session and apply a typed transcript replacement |
| `blob/get` | Session | Fetch a blob payload by id |
| `artifact/list` | Artifact | List stable artifact records |
| `artifact/get` | Artifact | Get one stable artifact record |
| `artifact/download` | Artifact | Download blob-backed artifact payload bytes |
| `approval/request` | Approval | Create a durable approval request |
| `approval/list` | Approval | List approval records |
| `approval/get` | Approval | Get one approval record |
| `approval/decide` | Approval | Record an approval decision |
| `events/latest_cursor` | Events | Read the latest replay cursor for a typed event scope |
| `events/list_since` | Events | List replayable events after a typed cursor |
| `events/snapshot` | Events | Read a point-in-time snapshot with its replay cursor |
| `session/archive` | Session | Remove session from runtime |
| `session/external_event` | Session | Queue a runtime-backed external event |
| `session/peer_response_terminal` | Session | Admit a correlated terminal peer response through the typed runtime ingress |
| `session/inject_context` | Session | Append runtime-backed system context to the next turn |
| `turn/start` | Turn | Start a new turn on existing session |
| `turn/interrupt` | Turn | Cancel in-flight turn |
| `live/open` | Live | Open a live audio/text channel for a session |
| `live/webrtc/answer` | Live | Answer a browser WebRTC offer for an already-open live channel |
| `live/status` | Live | Get the status of a live channel |
| `live/close` | Live | Close a live channel |
| `live/send_input` | Live | Send an input chunk (audio/text) to a live channel |
| `live/commit_input` | Live | Commit pending input on a live channel (turn boundary) |
| `live/interrupt` | Live | Interrupt the assistant turn on a live channel (barge-in) |
| `live/truncate` | Live | Truncate assistant output at a client-tracked playback cursor |
| `live/refresh` | Live | Apply mutable session config (instructions/tools/audio) to an open live channel |
| `session/stream_open` | Session | Open a standalone session event stream |
| `session/stream_close` | Session | Close a standalone session event stream |
| `mob/create` | Mob | Create a mob from a definition (mob feature) |
| `mob/list` | Mob | List mobs (mob feature) |
| `mob/status` | Mob | Get mob status (mob feature) |
| `mob/members` | Mob | List mob members (mob feature) |
| `mob/spawn` | Mob | Spawn a member into a mob (mob feature) |
| `mob/spawn_many` | Mob | Spawn multiple members into a mob (mob feature) |
| `mob/retire` | Mob | Retire a mob member (mob feature) |
| `mob/respawn` | Mob | Respawn a mob member (mob feature) |
| `mob/wire` | Mob | Wire two mob members for comms (mob feature) |
| `mob/wire_members_batch` | Mob | Wire multiple local mob member edges in one batch (mob feature) |
| `mob/unwire` | Mob | Remove mob comms wiring (mob feature) |
| `mob/lifecycle` | Mob | Stop, resume, complete, destroy, or reset a mob (mob feature) |
| `mob/events` | Mob | Read mob events by cursor (mob feature) |
| `mob/ingress_interaction` | Mob | Ensure an ingress member and deliver user input with replay anchors (mob feature) |
| `mob/stream_open` | Mob | Open mob or member event stream (mob feature) |
| `mob/stream_close` | Mob | Close mob or member event stream (mob feature) |
| `mob/member_send` | Mob | Deliver ordinary content to a specific mob member via the host control plane (mob feature) |
| `mob/append_system_context` | Mob | Stage system context for a member session (mob feature) |
| `mob/flows` | Mob | List mob flows (mob feature) |
| `mob/flow_run` | Mob | Start a mob flow run (mob feature) |
| `mob/flow_status` | Mob | Get flow run status (mob feature) |
| `mob/flow_cancel` | Mob | Cancel a flow run (mob feature) |
| `mob/ensure_member` | Mob | Idempotent spawn — returns existing entry if member already exists (mob feature) |
| `mob/reconcile` | Mob | Declarative roster reconcile against a desired spec list (mob feature) |
| `mob/list_members_matching` | Mob | Label-filtered member listing with server-side filter (mob feature) |
| `mob/spawn_helper` | Mob | Spawn a helper member and wait for its result (mob feature) |
| `mob/fork_helper` | Mob | Fork a helper from an existing member and wait for its result (mob feature) |
| `mob/force_cancel` | Mob | Force-cancel a member's active work (mob feature) |
| `mob/turn_start` | Mob | Start a turn on a mob member by identity (mob feature) |
| `mob/member_status` | Mob | Read canonical status for a specific member (mob feature) |
| `mob/snapshot` | Mob | Point-in-time aggregate of mob status plus member list (mob feature) |
| `mob/destroy` | Mob | Destroy a mob and surface the structured MobDestroyReport (mob feature) |
| `mob/rotate_supervisor` | Mob | Rotate the supervisor bridge for all members of a mob (mob feature) |
| `mob/submit_work` | Mob | Submit a unit of work to a mob member through the work lane (mob feature) |
| `mob/cancel_work` | Mob | Cancel a previously submitted unit of work (mob feature) |
| `mob/cancel_all_work` | Mob | Cancel all in-flight work for a mob member (mob feature) |
| `mob/wait_kickoff` | Mob | Wait for kickoff completion for a member (mob feature) |
| `mob/wait_ready` | Mob | Wait for mob startup readiness (members bound but kickoff not required, mob feature) |
| `mob/profile/create` | Mob | Create a realm-scoped mob profile (mob feature) |
| `mob/profile/get` | Mob | Read a realm-scoped mob profile (mob feature) |
| `mob/profile/list` | Mob | List realm-scoped mob profiles (mob feature) |
| `mob/profile/update` | Mob | Update a realm-scoped mob profile (mob feature) |
| `mob/profile/delete` | Mob | Delete a realm-scoped mob profile (mob feature) |
| `schedule/create` | Schedule | Create a new schedule (schedule feature) |
| `schedule/get` | Schedule | Get a schedule by ID (schedule feature) |
| `schedule/list` | Schedule | List schedules (schedule feature) |
| `schedule/update` | Schedule | Update a schedule (schedule feature) |
| `schedule/pause` | Schedule | Pause a schedule (schedule feature) |
| `schedule/resume` | Schedule | Resume a paused schedule (schedule feature) |
| `schedule/delete` | Schedule | Delete a schedule (schedule feature) |
| `schedule/occurrences` | Schedule | List occurrences for a schedule (schedule feature) |
| `schedule/tools` | Schedule | List schedule tools (schedule feature) |
| `schedule/call` | Schedule | Call a schedule tool directly (schedule feature) |
| `workgraph/get` | WorkGraph | Read a WorkGraph item (workgraph feature) |
| `workgraph/list` | WorkGraph | List WorkGraph items (workgraph feature) |
| `workgraph/ready` | WorkGraph | List ready WorkGraph items (workgraph feature) |
| `workgraph/snapshot` | WorkGraph | Read a WorkGraph observability snapshot (workgraph feature) |
| `workgraph/events` | WorkGraph | Read WorkGraph event history (workgraph feature) |
| `comms/send` | Comms | Dispatch a comms command to a session |
| `comms/peers` | Comms | List discoverable peers (comms feature) |
| `skills/list` | Skills | List skills with provenance |
| `mcp/add` | MCP | Stage live MCP server addition (mcp feature) |
| `mcp/remove` | MCP | Stage live MCP server removal (mcp feature) |
| `mcp/reload` | MCP | Reload one or all MCP servers (mcp feature) |
| `models/catalog` | Models | List available models with capabilities and provider profiles |
| `capabilities/get` | Capabilities | Runtime capability report |
| `config/get` | Config | Read current config |
| `config/set` | Config | Replace config |
| `config/patch` | Config | Merge-patch config (RFC 7396) |
| `auth/profile/list` | Auth | List auth profiles in a realm |
| `auth/profile/get` | Auth | Get one auth profile |
| `auth/profile/create` | Auth | Create an auth profile (CLI-driven for now) |
| `auth/profile/delete` | Auth | Delete an auth profile (CLI-driven for now) |
| `auth/login/start` | Auth | Begin interactive authorization-code OAuth (returns authorize_url + pkce_verifier) |
| `auth/login/complete` | Auth | Exchange authorization code for tokens; persists to TokenStore |
| `auth/login/device_start` | Auth | Begin device-code OAuth; returns user_code + verification URL |
| `auth/login/device_complete` | Auth | Single-poll completion for a device-code OAuth login |
| `auth/login/provision_api_key` | Auth | Console-OAuth → API key provisioning (Anthropic oauth_to_api_key) |
| `auth/status/get` | Auth | Get auth status for a profile |
| `auth/logout` | Auth | Revoke + remove persisted credentials |
| `realm/list` | Realm | List realm summaries |
| `realm/get` | Realm | Get full realm connection set |

<Note>
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.
</Note>

<Note>
Live channels provide low-latency audio/text streaming. 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.
</Note>

<Note>
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.
</Note>

<Note>
WorkGraph RPC methods are read-only observability. Programmatic hosts should
inspect WorkGraph through `workgraph/get`, `list`, `ready`, `snapshot`, and
`events`; agents mutate WorkGraph through the `workgraph_*` tools.
</Note>

## Protocol

Standard JSON-RPC 2.0 with `"jsonrpc": "2.0"` on every message. Three message types:

- **Request** (client -> server): has `id`, `method`, `params`
- **Response** (server -> client): has `id`, `result` or `error`
- **Notification** (server -> client): has `method`, `params`, no `id`

<Accordion title="Full lifecycle diagram">
  ```text
  Client                          Server
    |                                |
    |-- initialize ----------------->|
    |<-- result { capabilities } ----|
    |-- initialized (notification) ->|
    |                                |
    |-- session/create { prompt } -->|
    |<-- session/event (notif) ------|  // AgentEvent stream
    |<-- session/event (notif) ------|
    |<-- result { session_id, ... } -|
    |                                |
    |-- turn/start { session_id } -->|
    |<-- session/event (notif) ------|
    |<-- result { text, usage } -----|
    |                                |
    |-- turn/interrupt { sid } ----->|
    |<-- result {} ------------------|
    |                                |
    |-- session/archive { sid } ---->|
    |<-- result {} ------------------|
  ```
</Accordion>

## Session methods

### initialize

Handshake. Returns server capabilities.

<CodeGroup>
```json Request
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}
```

```json Response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "server_info": {
      "name": "meerkat-rpc",
      "version": "0.6.19"
    },
    "contract_version": "0.6.19",
    "methods": [
      "initialize", "initialized",
      "session/create", "session/list", "session/read",
      "turn/start", "turn/interrupt",
      "runtime/host_info", "config/get",
      "live/open", "mob/create"
    ]
  }
}
```
</CodeGroup>

The actual array contains every method compiled into the server. For the full
documented list and parameter/result type names, use
`artifacts/schemas/rpc-methods.json`.

<ResponseField name="server_info.name" type="string">
  Server name.
</ResponseField>

<ResponseField name="server_info.version" type="string">
  Server version.
</ResponseField>

<ResponseField name="contract_version" type="string">
  Protocol contract version.
</ResponseField>

<ResponseField name="methods" type="array">
  List of supported method names.
</ResponseField>

### 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`.

<CodeGroup>
```json Request (minimal)
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "session/create",
  "params": {
    "prompt": "What is Rust?"
  }
}
```

```json Request (full)
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "session/create",
  "params": {
    "prompt": "What is Rust?",
    "model": "claude-sonnet-4-6",
    "provider": "anthropic",
    "max_tokens": 4096,
    "system_prompt": "You are a helpful assistant.",
    "output_schema": {
      "schema": {"type": "object", "properties": {"answer": {"type": "string"}}},
      "name": "answer"
    },
    "structured_output_retries": 2,
    "hooks_override": null,
    "enable_builtins": false,
    "enable_shell": false,
    "enable_memory": false,
    "enable_schedule": null,
    "enable_workgraph": null,
    "keep_alive": null,
    "comms_name": null,
    "provider_params": null,
    "preload_skills": [
      {"source_uuid": "dc256086-0d2f-4f61-a307-320d4148107f", "skill_name": "extraction-email"}
    ],
    "skill_refs": [
      {
        "kind": "structured",
        "source_uuid": "dc256086-0d2f-4f61-a307-320d4148107f",
        "skill_name": "formatting-markdown"
      }
    ]
  }
}
```

```json Response
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "session_id": "01936f8a-7b2c-7000-8000-000000000001",
    "text": "Rust is a systems programming language...",
    "turns": 1,
    "tool_calls": 0,
    "usage": {
      "input_tokens": 50,
      "output_tokens": 200,
      "total_tokens": 250
    },
    "structured_output": null,
    "schema_warnings": null
  }
}
```
</CodeGroup>

Only `prompt` is required. All other fields are optional and fall back to config defaults. During execution, `session/event` notifications are emitted (see [Notifications](#notifications)).

#### Parameter reference

<ParamField body="prompt" type="string" required>
  The user prompt to send to the agent.
</ParamField>

<ParamField body="model" type="string | null" default="claude-sonnet-4-6">
  Model name (e.g. `"claude-opus-4-6"`, `"gpt-5.5"`).
</ParamField>

<ParamField body="provider" type="string | null" default="inferred from model">
  Provider name: `"anthropic"`, `"openai"`, `"gemini"`, `"self_hosted"`, `"other"`.
</ParamField>

<ParamField body="max_tokens" type="u32 | null" default="config default">
  Max tokens per turn.
</ParamField>

<ParamField body="system_prompt" type="string | null" default="null">
  Override system prompt.
</ParamField>

<ParamField body="output_schema" type="object | null" default="null">
  JSON schema for structured output extraction (wrapper or raw schema).
</ParamField>

<ParamField body="structured_output_retries" type="u32" default="2">
  Max retries for structured output validation.
</ParamField>

<ParamField body="hooks_override" type="HookRunOverrides | null" default="null">
  Run-scoped hook overrides (entries to add, hook IDs to disable).
</ParamField>

<ParamField body="enable_builtins" type="bool" default="false">
  Enable built-in tools (task management, etc.).
</ParamField>

<ParamField body="enable_shell" type="bool" default="false">
  Enable shell tool (requires `enable_builtins`).
</ParamField>

<ParamField body="enable_memory" type="bool" default="false">
  Enable semantic memory (`memory_search` tool + compaction indexing).
</ParamField>

<ParamField body="enable_schedule" type="bool | null" default="null">
  Override schedule tools for this session. `null` uses the surface default.
</ParamField>

<ParamField body="enable_workgraph" type="bool | null" default="null">
  Override WorkGraph tools for this session. `null` uses the surface default.
</ParamField>

<ParamField body="keep_alive" type="bool | null" default="null">
  Keep session alive after turn for comms. `null` = inherit persisted session intent, `true` = enable, `false` = disable. Requires `comms_name` when enabled.
</ParamField>

<ParamField body="comms_name" type="string | null" default="null">
  Agent name for inter-agent communication.
</ParamField>

<ParamField body="provider_params" type="object | null" default="null">
  Provider-specific parameters (e.g., thinking config, reasoning effort).
</ParamField>

#### Response fields

<ResponseField name="session_id" type="string">
  UUID of the created session.
</ResponseField>

<ResponseField name="text" type="string">
  The agent's response text.
</ResponseField>

<ResponseField name="turns" type="u32">
  Number of LLM calls made.
</ResponseField>

<ResponseField name="tool_calls" type="u32">
  Number of tool calls executed.
</ResponseField>

<ResponseField name="usage" type="WireUsage">
  Token usage breakdown.
</ResponseField>

<ResponseField name="structured_output" type="object | null">
  Parsed structured output.
</ResponseField>

<ResponseField name="schema_warnings" type="array | null">
  Schema compatibility warnings.
</ResponseField>

### session/history

Read committed transcript history for an existing session.

```json
{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "session/history",
  "params": {
    "session_id": "01936f8a-7b2c-7000-8000-000000000001",
    "offset": 0,
    "limit": 50
  }
}
```

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.

### 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.

```json
{
  "jsonrpc": "2.0",
  "id": 8,
  "method": "session/fork_at",
  "params": {
    "session_id": "01936f8a-7b2c-7000-8000-000000000001",
    "message_index": 2,
    "running_behavior": "reject"
  }
}
```

```json
{
  "jsonrpc": "2.0",
  "id": 8,
  "result": {
    "source_session_id": "01936f8a-7b2c-7000-8000-000000000001",
    "session_id": "01936f8a-7b2c-7000-8000-000000000009",
    "message_count": 2
  }
}
```

`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.

```json
{
  "jsonrpc": "2.0",
  "id": 9,
  "method": "session/fork_replace",
  "params": {
    "session_id": "01936f8a-7b2c-7000-8000-000000000001",
    "message_index": 2,
    "replacement": {
      "type": "message",
      "message": {
        "role": "user",
        "content": "Edited follow-up"
      }
    },
    "running_behavior": "reject"
  }
}
```

```json
{
  "jsonrpc": "2.0",
  "id": 9,
  "result": {
    "source_session_id": "01936f8a-7b2c-7000-8000-000000000001",
    "session_id": "01936f8a-7b2c-7000-8000-000000000010",
    "message_count": 3
  }
}
```

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 active sessions.

<CodeGroup>
```json Request
{"jsonrpc":"2.0","id":3,"method":"session/list"}
```

```json Response
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "sessions": [
      {"session_id": "01936f8a-...", "state": "idle"},
      {"session_id": "01936f8b-...", "state": "running"}
    ]
  }
}
```
</CodeGroup>

### session/read

Get session state.

<CodeGroup>
```json Request
{"jsonrpc":"2.0","id":4,"method":"session/read","params":{"session_id":"01936f8a-..."}}
```

```json Response
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "session_id": "01936f8a-...",
    "state": "idle",
    "message_count": 4,
    "total_tokens": 500
  }
}
```
</CodeGroup>

<ParamField body="session_id" type="string" required>
  Session ID to read.
</ParamField>

### session/archive

Remove a session from the runtime.

<CodeGroup>
```json Request
{"jsonrpc":"2.0","id":5,"method":"session/archive","params":{"session_id":"01936f8a-..."}}
```

```json Response
{"jsonrpc":"2.0","id":5,"result":{"archived":true}}
```
</CodeGroup>

<ParamField body="session_id" type="string" required>
  Session ID to archive.
</ParamField>

## Turn methods

### turn/start

Start a new turn on an existing session.

<CodeGroup>
```json Request
{
  "jsonrpc": "2.0",
  "id": 6,
  "method": "turn/start",
  "params": {
    "session_id": "01936f8a-...",
    "prompt": "Can you explain ownership?",
    "skill_refs": [
      {
        "kind": "structured",
        "source_uuid": "dc256086-0d2f-4f61-a307-320d4148107f",
        "skill_name": "rust-patterns-ownership"
      }
    ]
  }
}
```

```json Response
{
  "jsonrpc": "2.0",
  "id": 6,
  "result": {
    "session_id": "01936f8a-...",
    "text": "Ownership is Rust's approach to memory management...",
    "turns": 1,
    "tool_calls": 0,
    "usage": {
      "input_tokens": 300,
      "output_tokens": 400,
      "total_tokens": 700
    },
    "structured_output": null,
    "schema_warnings": null
  }
}
```
</CodeGroup>

Returns the same result shape as `session/create`. Fails with error code `-32001` (SESSION_BUSY) if a turn is already in progress.

<ParamField body="session_id" type="string" required>
  Session ID to continue.
</ParamField>

<ParamField body="prompt" type="string" required>
  The follow-up prompt.
</ParamField>

<ParamField body="model" type="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.
</ParamField>

<ParamField body="provider" type="string | null" default="from session">
  Provider override (e.g. `"anthropic"`, `"openai"`, `"gemini"`). Typically inferred from `model`.
</ParamField>

<ParamField body="provider_params" type="object | null" default="from session">
  Provider-specific parameters (e.g. `{"thinking_budget": 10000}`). Applied alongside model/provider override.
</ParamField>

<ParamField body="max_tokens" type="u32 | null" default="from session">
  Max tokens override.
</ParamField>

<ParamField body="skill_refs" type="object[] | null">
  Tagged structured skill references to resolve and inject for this turn. Each entry uses `{"kind":"structured","source_uuid":"...","skill_name":"..."}`.
</ParamField>

### turn/interrupt

Cancel an in-flight turn. No-op if the session is idle.

<CodeGroup>
```json Request
{"jsonrpc":"2.0","id":7,"method":"turn/interrupt","params":{"session_id":"01936f8a-..."}}
```

```json Response
{"jsonrpc":"2.0","id":7,"result":{"interrupted":true}}
```
</CodeGroup>

<ParamField body="session_id" type="string" required>
  Session ID to interrupt.
</ParamField>

## Event methods

### session/external_event

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

<CodeGroup>
```json Request
{
  "jsonrpc": "2.0",
  "id": 8,
  "method": "session/external_event",
  "params": {
    "session_id": "01936f8a-...",
    "kind": "generic_json",
    "event_type": "ci-pipeline",
    "payload": {"alert": "build failed", "repo": "main"}
  }
}
```

```json Response
{
  "jsonrpc": "2.0",
  "id": 8,
  "result": {
    "outcome_type": "accepted"
  }
}
```
</CodeGroup>

### session/peer_response_terminal

Admit a correlated terminal peer response through the typed runtime ingress.

<CodeGroup>
```json Request
{
  "jsonrpc": "2.0",
  "id": 9,
  "method": "session/peer_response_terminal",
  "params": {
    "session_id": "01936f8a-...",
    "peer_id": "00000000-0000-4000-8000-000000000161",
    "display_name": "analyst",
    "request_id": "00000000-0000-4000-8000-000000000162",
    "status": "completed",
    "result": {"token": "silver harbor"}
  }
}
```

```json Response
{
  "jsonrpc": "2.0",
  "id": 9,
  "result": {
    "outcome_type": "accepted",
    "input_id": "01936f8a-..."
  }
}
```
</CodeGroup>

<ParamField body="session_id" type="string" required>
  Session ID to admit the peer response to.
</ParamField>

<ParamField body="peer_id" type="string" required>
  Canonical peer routing ID.
</ParamField>

<ParamField body="display_name" type="string">
  Optional presentation label. It is not used as routing identity.
</ParamField>

<ParamField body="request_id" type="string" required>
  Peer correlation ID for the request this terminal response completes.
</ParamField>

<ParamField body="status" type="string" required>
  Terminal response status: `"completed"`, `"failed"`, or `"cancelled"`.
</ParamField>

<ParamField body="result" type="any JSON" required>
  Peer-returned terminal payload.
</ParamField>

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.

<CodeGroup>
```json Request
{
  "jsonrpc": "2.0",
  "id": 9,
  "method": "comms/peers",
  "params": {
    "session_id": "01936f8a-..."
  }
}
```

```json Response
{
  "jsonrpc": "2.0",
  "id": 9,
  "result": {
    "peers": [
      {"name": "reviewer", "address": "127.0.0.1:4201"},
      {"name": "coordinator", "address": "inproc"}
    ]
  }
}
```
</CodeGroup>

<ParamField body="session_id" type="string" required>
  Session ID to query peers for.
</ParamField>

### skills/list

List all skills with provenance information, including active and shadowed entries.

<CodeGroup>
```json Request
{"jsonrpc":"2.0","id":12,"method":"skills/list"}
```

```json Response
{
  "jsonrpc": "2.0",
  "id": 12,
  "result": {
    "skills": [
      {
        "key": {
          "source_uuid": "00000000-0000-4b11-8111-000000000001",
          "skill_name": "task-workflow"
        },
        "name": "Task Workflow",
        "description": "How to use task tools",
        "scope": "builtin",
        "source": {
          "source_uuid": "00000000-0000-4b11-8111-000000000001",
          "display_name": "embedded",
          "transport_kind": "embedded",
          "fingerprint": "embedded:inventory",
          "status": "active"
        },
        "is_active": true
      },
      {
        "key": {
          "source_uuid": "00000000-0000-4b11-8111-000000000002",
          "skill_name": "my-deployment"
        },
        "name": "Deployment Guide",
        "description": "Project-specific deployment",
        "scope": "project",
        "source": {
          "source_uuid": "00000000-0000-4b11-8111-000000000002",
          "display_name": "project",
          "transport_kind": "filesystem",
          "fingerprint": "filesystem:.rkat/skills",
          "status": "active"
        },
        "is_active": true
      },
      {
        "key": {
          "source_uuid": "dc256086-0d2f-4f61-a307-320d4148107f",
          "skill_name": "task-workflow"
        },
        "name": "Custom Task Workflow",
        "description": "Override tasks",
        "scope": "project",
        "source": {
          "source_uuid": "dc256086-0d2f-4f61-a307-320d4148107f",
          "display_name": "company",
          "transport_kind": "git",
          "fingerprint": "repo-7cc66f36fd9db1a1",
          "status": "active"
        },
        "is_active": true
      }
    ]
  }
}
```
</CodeGroup>

<ResponseField name="skills" type="array">
  List of `SkillEntry` objects with `key`, `name`, `description`, `scope`, canonical `source` provenance, `is_active`, and optional canonical `shadowed_by` provenance.
</ResponseField>

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.

## Live channel methods

Live channels provide low-latency audio and text streaming 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. The `--live-ws <addr>` flag on `rkat-rpc` enables the WebSocket listener required for audio transport. See the [Live Channels guide](/guides/realtime) for the full flow.

### Opening a live channel

Create a session on a realtime-capable model and call `live/open`:

```json
{
  "jsonrpc": "2.0",
  "id": 19,
  "method": "session/create",
  "params": {
    "prompt": "Let's talk.",
    "model": "gpt-realtime-2",
    "provider": "openai"
  }
}
```

```json
{
  "jsonrpc": "2.0",
  "id": 20,
  "method": "live/open",
  "params": {
    "session_id": "01936f8a-7b2c-7000-8000-000000000001"
  }
}
```

`live/open` returns a `LiveOpenResult` with transport bootstrap (e.g. WebSocket URL), `WireLiveChannelCapabilities`, and `WireLiveContinuityMode`.

### live/status

Read the current state of a live channel.

<CodeGroup>
```json Request
{
  "jsonrpc": "2.0",
  "id": 22,
  "method": "live/status",
  "params": {
    "channel_id": "ch_01936f8a-..."
  }
}
```
</CodeGroup>

### Live channel lifecycle methods (`live/*`)

| Method | Purpose |
|--------|---------|
| `live/open` | Open a live audio/text channel for a session |
| `live/webrtc/answer` | Answer a browser WebRTC offer for an already-open live channel |
| `live/status` | Get the status of a live channel |
| `live/close` | Close a live channel |
| `live/send_input` | Send an input chunk (audio/text) to a live channel |
| `live/commit_input` | Commit pending input on a live channel (turn boundary) |
| `live/interrupt` | Interrupt the assistant turn on a live channel (barge-in) |
| `live/truncate` | Truncate assistant output at a client-tracked playback cursor |
| `live/refresh` | Apply mutable session config (instructions/tools/audio) to an open channel; config-only, does not replay history; identity swaps (model/provider) require close + reopen |

These require `rkat-rpc` to be started with `--live-ws <addr>` for WebSocket transport.

## MCP methods

### mcp/add

Stage a live MCP server addition for a running session. The server is connected at the next turn boundary.

<CodeGroup>
```json Request
{
  "jsonrpc": "2.0",
  "id": 14,
  "method": "mcp/add",
  "params": {
    "session_id": "01936f8a-...",
    "server_config": {
      "name": "my-tools",
      "command": "npx",
      "args": ["-y", "@my/mcp-server"]
    },
    "persisted": false
  }
}
```

```json Response
{
  "jsonrpc": "2.0",
  "id": 14,
  "result": {
    "session_id": "01936f8a-...",
    "operation": "add",
    "server_name": "my-tools",
    "status": "staged",
    "persisted": false,
    "applied_at_turn": null
  }
}
```
</CodeGroup>

<ParamField body="session_id" type="string" required>
  Session ID to add the server to.
</ParamField>

<ParamField body="server_config" type="object" required>
  Typed MCP server configuration. Include `name` with either stdio fields (`command`, optional `args`/`env`) or HTTP fields (`url`, optional `headers`/`transport`).
</ParamField>

<ParamField body="persisted" type="bool" default="false">
  Whether to also write the server to disk config.
</ParamField>

### 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.

<CodeGroup>
```json Request
{
  "jsonrpc": "2.0",
  "id": 15,
  "method": "mcp/remove",
  "params": {
    "session_id": "01936f8a-...",
    "server_name": "my-tools",
    "persisted": false
  }
}
```

```json Response
{
  "jsonrpc": "2.0",
  "id": 15,
  "result": {
    "session_id": "01936f8a-...",
    "operation": "remove",
    "server_name": "my-tools",
    "status": "staged",
    "persisted": false,
    "applied_at_turn": null
  }
}
```
</CodeGroup>

<ParamField body="session_id" type="string" required>
  Session ID to remove the server from.
</ParamField>

<ParamField body="server_name" type="string" required>
  Name of the MCP server to remove.
</ParamField>

<ParamField body="persisted" type="bool" default="false">
  Whether to also remove from disk config.
</ParamField>

### mcp/reload

Reload one or all MCP servers for a running session. Useful after config changes.

<CodeGroup>
```json Request
{
  "jsonrpc": "2.0",
  "id": 16,
  "method": "mcp/reload",
  "params": {
    "session_id": "01936f8a-...",
    "server_name": "my-tools"
  }
}
```

```json Response
{
  "jsonrpc": "2.0",
  "id": 16,
  "result": {
    "session_id": "01936f8a-...",
    "operation": "reload",
    "server_name": "my-tools",
    "status": "staged",
    "persisted": false,
    "applied_at_turn": null
  }
}
```
</CodeGroup>

<ParamField body="session_id" type="string" required>
  Session ID to reload servers for.
</ParamField>

<ParamField body="server_name" type="string | null" default="null">
  Specific server to reload. If null, reloads all servers.
</ParamField>

## Models

### models/catalog

Return the curated model catalog with provider profiles, capability metadata, and parameter schemas.

<CodeGroup>
```json Request
{"jsonrpc":"2.0","id":17,"method":"models/catalog"}
```

```json Response
{
  "jsonrpc": "2.0",
  "id": 17,
  "result": {
    "contract_version": "0.6.19",
    "providers": [
      {
        "provider": "anthropic",
        "default_model_id": "claude-opus-4-7",
        "models": [
          {
            "id": "claude-opus-4-7",
            "display_name": "Claude Opus 4.7",
            "tier": "recommended",
            "context_window": 1000000,
            "max_output_tokens": 128000,
            "profile": {
              "model_family": "claude-opus-4",
              "supports_temperature": false,
              "supports_thinking": true,
              "supports_reasoning": false,
              "params_schema": {}
            }
          }
        ]
      }
    ]
  }
}
```
</CodeGroup>

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`.

<ResponseField name="contract_version" type="string">
  Contract version for the catalog response.
</ResponseField>

<ResponseField name="providers" type="array">
  List of provider entries, each containing `provider`, `default_model_id`, and `models`.
</ResponseField>

<ResponseField name="providers[].models[].profile" type="object">
  Model profile with capability flags (`supports_temperature`, `supports_thinking`, `supports_reasoning`) and `params_schema`.
</ResponseField>

## Capabilities

### capabilities/get

Return runtime capabilities with status resolved against config. This lists every capability known to Meerkat with its current status (available, disabled by policy, not compiled, etc.).

<CodeGroup>
```json Request
{"jsonrpc":"2.0","id":8,"method":"capabilities/get"}
```

```json Response
{
  "jsonrpc": "2.0",
  "id": 8,
  "result": {
    "contract_version": {"major": 0, "minor": 6, "patch": 6},
    "capabilities": [
      {
        "id": "sessions",
        "description": "Session lifecycle management",
        "status": "Available"
      },
      {
        "id": "structured_output",
        "description": "Structured output extraction with JSON schema",
        "status": "Available"
      },
      {
        "id": "hooks",
        "description": "Lifecycle hooks for tool and turn events",
        "status": "Available"
      },
      {
        "id": "builtins",
        "description": "Built-in tools (task management)",
        "status": {"DisabledByPolicy": {"description": "Disabled by config"}}
      },
      {
        "id": "shell",
        "description": "Shell command execution",
        "status": {"DisabledByPolicy": {"description": "Disabled by config"}}
      },
      {
        "id": "comms",
        "description": "Inter-agent communication",
        "status": {"NotCompiled": {"feature": "comms"}}
      },
      {
        "id": "memory_store",
        "description": "Semantic memory indexing",
        "status": "Available"
      },
      {
        "id": "skills",
        "description": "Skill loading and injection",
        "status": "Available"
      }
    ]
  }
}
```
</CodeGroup>

Possible `status` values:

| Status | Shape | Meaning |
|--------|-------|---------|
| `Available` | `"Available"` | Compiled in, config-enabled, protocol supports it |
| `DisabledByPolicy` | `{"DisabledByPolicy": {"description": "..."}}` | Compiled in but disabled by policy |
| `NotCompiled` | `{"NotCompiled": {"feature": "..."}}` | Feature flag absent at compile time |
| `NotSupportedByProtocol` | `{"NotSupportedByProtocol": {"reason": "..."}}` | This protocol surface does not support it |

## Config methods

### config/get

Read current realm config envelope.

<CodeGroup>
```json Request
{"jsonrpc":"2.0","id":9,"method":"config/get"}
```

```json Response
{
  "jsonrpc": "2.0",
  "id": 9,
  "result": {
    "config": {
      "agent": {
        "model": "claude-sonnet-4-6",
        "max_tokens_per_turn": 8192
      }
    },
    "generation": 3,
    "realm_id": "team-alpha",
    "instance_id": "rpc-worker-1",
    "backend": "sqlite",
    "resolved_paths": {
      "root": "/.../meerkat/realms/team-alpha",
      "manifest_path": "/.../realm_manifest.json",
      "config_path": "/.../config.toml",
      "sessions_sqlite_path": "/.../sessions.sqlite3",
      "sessions_jsonl_dir": "/.../sessions_jsonl"
    }
  }
}
```
</CodeGroup>

### config/set

Replace config, optionally using generation CAS.

<CodeGroup>
```json Request
{
  "jsonrpc":"2.0",
  "id":10,
  "method":"config/set",
  "params":{
    "config": {"agent":{"model":"gpt-5.5"}},
    "expected_generation": 3
  }
}
```

```json Response
{
  "jsonrpc":"2.0",
  "id":10,
  "result": {
    "config": {"agent":{"model":"gpt-5.5"}},
    "generation": 4,
    "realm_id": "team-alpha",
    "instance_id": null,
    "backend": "sqlite",
    "resolved_paths": { "...": "..." }
  }
}
```
</CodeGroup>

### config/patch

Merge-patch the config (RFC 7396).

<CodeGroup>
```json Request
{
  "jsonrpc":"2.0",
  "id":11,
  "method":"config/patch",
  "params":{
    "patch":{"agent":{"max_tokens_per_turn":8192}},
    "expected_generation": 4
  }
}
```

```json Response
{
  "jsonrpc":"2.0",
  "id":11,
  "result": {
    "config": {"agent":{"max_tokens_per_turn":8192}},
    "generation": 5,
    "realm_id": "team-alpha",
    "instance_id": null,
    "backend": "sqlite",
    "resolved_paths": { "...": "..." }
  }
}
```
</CodeGroup>

<Note>
`config/set` also accepts a direct config object as params for compatibility. In that mode, no CAS check is applied.
</Note>

## Notifications

During turn execution, the server emits `session/event` notifications containing serialized `AgentEvent` payloads:

```json
{
  "jsonrpc": "2.0",
  "method": "session/event",
  "params": {
    "session_id": "01936f8a-...",
    "event": {
      "type": "text_delta",
      "delta": "Rust is"
    }
  }
}
```

Event types match the `AgentEvent` enum in `meerkat-core`:

| Event | Description |
|-------|-------------|
| `run_started` | Agent execution began |
| `run_completed` | Agent run finished |
| `run_failed` | Agent run failed |
| `turn_started` | New LLM call within the turn |
| `text_delta` | Streaming text chunk from LLM |
| `text_complete` | Full text for this turn |
| `tool_call_requested` | LLM wants to call a tool |
| `tool_result_received` | Tool result processed |
| `turn_completed` | LLM call finished |
| `tool_execution_started` | Tool dispatch began |
| `tool_execution_completed` | Tool returned a result |
| `tool_execution_timed_out` | Tool exceeded timeout |
| `compaction_started` | Context compaction began |
| `compaction_completed` | Compaction finished |
| `compaction_failed` | Compaction failed |
| `budget_warning` | Approaching resource limits |
| `retrying` | Retrying after transient error |
| `hook_started` | Hook execution began |
| `hook_completed` | Hook finished |
| `hook_failed` | Hook execution failed |
| `hook_denied` | Hook blocked operation |
| `skills_resolved` | Skills loaded for turn |
| `skill_resolution_failed` | Skill resolution failed |
| `tool_config_changed` | Tool visibility or MCP config changed |
| `background_job_completed` | Background shell job reached a typed terminal status; `terminal_status` is semantic and `status` is only an optional display mirror |
| `interaction_complete` | Interaction-scoped stream completed |
| `interaction_callback_pending` | Interaction reached a callback boundary and is waiting for tool results |
| `interaction_failed` | Interaction-scoped stream failed |
| `stream_truncated` | Backpressure dropped intermediate events |

### session/stream_event notifications

When a session event stream is open (via `session/stream_open`), the server emits `session/stream_event` notifications. The notification payload mirrors `session/event`, but is scoped to the explicit stream handle.

```json
{
  "jsonrpc": "2.0",
  "method": "session/stream_event",
  "params": {
    "stream_id": "550e8400-e29b-41d4-a716-446655440000",
    "sequence": 1,
    "session_id": "sess_123",
    "event": { "...": "..." }
  }
}
```

### 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.

```json
{
  "jsonrpc": "2.0",
  "method": "mob/stream_event",
  "params": {
    "stream_id": "550e8400-e29b-41d4-a716-446655440000",
    "sequence": 1,
    "event": { "...": "..." }
  }
}
```

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.

## Error codes

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

| Code | Name | Description |
|------|------|-------------|
| -32700 | Parse error | Invalid JSON |
| -32600 | Invalid request | Not a valid JSON-RPC request |
| -32601 | Method not found | Unknown method |
| -32602 | Invalid params | Missing or invalid parameters |
| -32603 | Internal error | Server error |
| -32001 | Session not found | Session ID does not exist |
| -32002 | Session busy | Turn already in progress |
| -32003 | Session not running | Session not in running state |
| -32010 | Provider error | LLM provider issue (missing key, auth) |
| -32011 | Budget exhausted | Resource limits reached |
| -32012 | Hook denied | Hook blocked the operation |
| -32013 | Agent error | Internal agent error |
| -32020 | Capability unavailable | Required capability not available |
| -32021 | Skill not found | Requested skill does not exist |
| -32022 | Skill resolution failed | Skill resolution error |

## Architecture

The RPC server is **stateful**: agents stay alive between turns. This is the key difference from REST (stateless per-request) and MCP (callback pattern).

```text
Client (IDE, app) <--stdio JSONL--> RpcServer
                                       |
                                  MethodRouter
                                       |
                                  SessionRuntime
                                   /    |    \
                            Session  Session  Session
                             Task    Task     Task
                              |       |        |
                            Agent   Agent    Agent
                         (exclusive ownership)
```

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.

<Note>
**Backpressure:** The notification channel is bounded. When the client reads slowly, the agent naturally slows down.
</Note>

## Comparison with other surfaces

| | CLI | REST | MCP | RPC |
|---|---|---|---|---|
| Stateful | No | No | No | **Yes** |
| Streaming | stderr | SSE | Optional | JSONL notifications |
| Multi-session | No | No | No | **Yes** |
| Cancellation | Ctrl+C | N/A | N/A | `turn/interrupt` |
| Bidirectional | No | No (SSE one-way) | Partial | **Yes** |

## See also

- [CLI reference](/cli/commands)
- [REST API](/api/rest)
- [MCP](/api/mcp)
- [Architecture](/reference/architecture)
