Protocols

App server

roder app-server starts Roder's local control plane. The app server is the boundary between the stable Rust runtime and every product surface that wants to drive it: the reference TUI, desktop apps, IDE integrations, CI harnesses, local scripts, and remote pairing clients.

The important design rule is that clients talk to the app server, not directly to roder-core. The server owns runtime construction, method dispatch, policy enforcement, event-to-notification projection, user config persistence, background tasks, and remote pairing. A client can therefore start turns, observe streaming deltas, answer approvals, browse tools, manage memories, or install plugins without linking the core crate.

Status. The app-server protocol is active Rust implementation, not just roadmap text. It is still alpha and method names/types may change while roder-protocol stabilizes. Prefer the typed Rust protocol structs when writing in Rust, and keep JSON clients tolerant of added fields.

Start the server

From a source checkout, run the server through the CLI package. An installed distribution can use the generated binary name instead of cargo run.

cargo run -p roder-cli -- app-server

By default the server speaks newline-delimited JSON-RPC 2.0 over stdio. Each inbound line must be one JSON-RPC request. Each response is one JSON object on stdout. Runtime warnings and remote pairing details are printed on stderr.

echo '{"jsonrpc":"2.0","id":1,"method":"initialize"}' | cargo run -p roder-cli -- app-server

Configuration passed through to the runtime

App-server startup accepts the same runtime-oriented options as the interactive CLI. For example, use --mode to set the policy mode before clients connect, or use provider/model settings from the normal Roder config file.

cargo run -p roder-cli -- app-server --mode ask
cargo run -p roder-cli -- app-server --mode bypass

The server builds a normal Runtime from local configuration and installed extensions. Provider API keys still come from environment variables or provider configuration; do not pass secrets in ad-hoc client payloads unless the method is explicitly a configuration method such as providers/configure.

Remote WebSocket mode

For local-network or paired desktop/mobile clients, start the app server with --remote. This enables a WebSocket JSON-RPC transport in addition to the same underlying app-server methods. The default remote listen address is ws://0.0.0.0:0, which binds an ephemeral port and prints connection details.

cargo run -p roder-cli -- app-server --remote
cargo run -p roder-cli -- app-server --remote --listen ws://127.0.0.1:9786
RODER_REMOTE_TOKEN=dev-token cargo run -p roder-cli -- app-server --remote --auth-token env:RODER_REMOTE_TOKEN

Remote mode uses bearer-token authentication. The server accepts the token either as an Authorization: Bearer ... header or through a WebSocket subprotocol of the form bearer.<token>. Clients should also offer the roder.remote.v1 subprotocol; if present, the server echoes it in the WebSocket handshake.

const socket = new WebSocket(
  "ws://127.0.0.1:9786",
  ["roder.remote.v1", "bearer.dev-token"]
);

When --remote starts, the terminal output includes URLs, a token preview, and a roder://connect?payload=... deep link. With QR output enabled, the same deep link is rendered as a terminal QR code for pairing. Disable QR rendering if a launcher or test harness needs simpler stderr output.

cargo run -p roder-cli -- app-server --remote --print-qr=false
Security note. Remote app-server mode is bearer-authenticated but not TLS-terminated by Roder. Prefer loopback, Tailscale, or another trusted encrypted network. Avoid exposing ws://0.0.0.0 on untrusted LANs because app-server methods can read files, run policy-permitted commands, start turns, and manipulate local Roder state.

JSON-RPC envelope

Requests use the standard JSON-RPC shape. id is optional in the type, but clients should include it for ordinary request/response calls.

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {}
}

Successful responses contain result. Failed responses contain error with JSON-RPC-style codes. Common errors are:

  • -32700 — parse error, usually malformed JSON on the transport.
  • -32601 — method not found.
  • -32602 — invalid parameters or missing params for a method that requires them.
  • -32000 — internal/runtime error.
  • -32004 — policy denied the requested command or tool action.

Initialize and discover capabilities

Start every client session with initialize. The result reports the active provider, model, and current working directory. Remote WebSocket clients also get a remote object with authentication and workspace metadata.

{"jsonrpc":"2.0","id":1,"method":"initialize"}

After initialization, discover installed extension and provider state instead of hard-coding assumptions about a distribution.

{"jsonrpc":"2.0","id":2,"method":"extensions/list"}
{"jsonrpc":"2.0","id":3,"method":"providers/list"}
{"jsonrpc":"2.0","id":4,"method":"model/list"}
{"jsonrpc":"2.0","id":5,"method":"tools/list"}
{"jsonrpc":"2.0","id":6,"method":"agents/list"}

Thread and turn lifecycle

The desktop-oriented protocol uses thread/* and turn/* methods. A thread is the long-lived conversation/session shown by clients. A turn is one model interaction within that thread.

Create or list threads

{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "thread/start",
  "params": {
    "cwd": "/absolute/path/to/workspace",
    "modelProvider": "mock",
    "model": "mock",
    "ephemeral": false
  }
}
{"jsonrpc":"2.0","id":11,"method":"thread/list","params":{"limit":20}}
{"jsonrpc":"2.0","id":12,"method":"thread/read","params":{"threadId":"THREAD_ID","includeTurns":true}}

Start, steer, or interrupt a turn

Use turn/start to submit a prompt. The protocol also accepts structured input items for text and attachments; simple clients can provide only prompt.

{
  "jsonrpc": "2.0",
  "id": 20,
  "method": "turn/start",
  "params": {
    "threadId": "THREAD_ID",
    "prompt": "summarize this repository and list the main crates",
    "taskLedgerRequired": false
  }
}

Eval and automation clients can set taskLedgerRequired on turn/start. The runtime then treats the task ledger as part of finalization and asks the model to checkpoint scoreable outputs before the deadline reserve closes.

{
  "jsonrpc": "2.0",
  "id": 21,
  "method": "turn/steer",
  "params": {
    "threadId": "THREAD_ID",
    "expectedTurnId": "TURN_ID",
    "prompt": "focus on app-server code paths"
  }
}
{
  "jsonrpc": "2.0",
  "id": 22,
  "method": "turn/interrupt",
  "params": { "threadId": "THREAD_ID", "turnId": "TURN_ID" }
}

Streaming notifications

The app server projects runtime events into client-facing JSON-RPC notifications. Stdio clients receive notifications as additional JSON lines on stdout while the server runs. Remote clients receive responses over the WebSocket transport; local Rust clients can subscribe through LocalAppClient::subscribe_notifications() and LocalAppClient::subscribe_events().

Core thread and turn notifications include:

  • thread/started and thread/status/changed
  • turn/started and turn/completed
  • item/started and item/completed
  • item/agentMessage/delta for streamed model text and reasoning deltas
  • command/exec/output_delta for command output when streaming is requested
{
  "jsonrpc": "2.0",
  "method": "item/agentMessage/delta",
  "params": {
    "threadId": "THREAD_ID",
    "turnId": "TURN_ID",
    "itemId": "TURN_ID-agent-final_answer",
    "delta": "Partial text from the model"
  }
}

Feature-specific notifications are emitted for teams, subagent traces, plan reviews, hunks, workflow imports, media artifacts, and memory updates. Treat notifications as an append-only stream: render what you understand and ignore unknown methods until your client adds support.

Session mode, approvals, and user input

Clients can read and change session policy mode through app-server methods. Policy still lives in the runtime: changing mode affects whether tool calls are allowed, denied, auto-approved, or require approval.

{"jsonrpc":"2.0","id":30,"method":"session/get"}
{"jsonrpc":"2.0","id":31,"method":"session/set_mode","params":{"mode":"ask","reason":"desktop toggle"}}

When a turn requires approval or user input, the runtime emits events and the client answers through the server.

{"jsonrpc":"2.0","id":32,"method":"session/resolve_approval","params":{"approvalId":"APPROVAL_ID","approved":true}}
{"jsonrpc":"2.0","id":33,"method":"session/resolve_user_input","params":{"requestId":"REQUEST_ID","answers":{"choice":"yes"}}}
{"jsonrpc":"2.0","id":34,"method":"session/exit_plan","params":{"requestId":"PLAN_REQUEST_ID","approved":true}}

Provider and settings methods

Provider methods let a client show available backends, configure a provider, and select the default model for future work. Settings methods expose web-search mode and default policy mode.

{"jsonrpc":"2.0","id":40,"method":"providers/list"}
{"jsonrpc":"2.0","id":41,"method":"providers/select","params":{"provider":"openai","model":"gpt-5.1","reasoning":null}}
{"jsonrpc":"2.0","id":42,"method":"providers/configure","params":{"provider":"openrouter","api_key":"sk-..."}}
{"jsonrpc":"2.0","id":43,"method":"settings/get"}
{"jsonrpc":"2.0","id":44,"method":"settings/set_web_search","params":{"mode":"auto"}}
{"jsonrpc":"2.0","id":45,"method":"settings/set_default_mode","params":{"mode":"ask"}}

If user config persistence is enabled by the CLI, provider selections and settings are written back to the user's Roder configuration. providers/configure persists API keys for API-key providers such as OpenRouter when config persistence is enabled. Clients should make this clear in UI because these choices outlive the current connection.

Filesystem and command methods

The app server exposes filesystem helpers for UI clients. Paths must be absolute. File contents are returned as base64 so binary files can be transported safely.

{"jsonrpc":"2.0","id":50,"method":"fs/readDirectory","params":{"path":"/absolute/path/to/workspace"}}
{"jsonrpc":"2.0","id":51,"method":"fs/readFile","params":{"path":"/absolute/path/to/file.rs"}}

command/exec runs a local process after policy evaluation. It supports a command vector, optional absolute cwd, environment overrides, timeout, and output caps. By default commands time out after 30 seconds. In eval-profile turns with a deadline, command timeouts are clamped to leave a finalization reserve.

{
  "jsonrpc": "2.0",
  "id": 52,
  "method": "command/exec",
  "params": {
    "command": ["git", "status", "--short"],
    "cwd": "/absolute/path/to/workspace",
    "timeoutMs": 30000,
    "outputBytesCap": 20000
  }
}

To receive command output as notifications, set streamStdoutStderr and a processId. The response then contains empty stdout/stderr and the server emits command/exec/output_delta notifications with base64 chunks.

Tool-facing exec_command and shell results also expose effective timeout and timeout status so clients and eval analyzers can distinguish process timeout, policy denial, provider failure, and ordinary non-zero exit.

Tools, commands, agents, and background tasks

The app server can list available slash commands, expand a command without running it, run a command as a turn, call a registered tool directly, list agents, and manage background task executors.

{"jsonrpc":"2.0","id":60,"method":"commands/list"}
{"jsonrpc":"2.0","id":61,"method":"commands/expand","params":{"name":"review","arguments":"src/main.rs","workspace":"/absolute/path"}}
{"jsonrpc":"2.0","id":62,"method":"commands/run","params":{"threadId":"THREAD_ID","name":"review","arguments":"src/main.rs"}}
{"jsonrpc":"2.0","id":63,"method":"tools/call","params":{"threadId":"THREAD_ID","toolName":"example_tool","arguments":{}}}
{"jsonrpc":"2.0","id":64,"method":"tasks/list"}

Background task methods are tasks/submit, tasks/list, tasks/get, tasks/cancel, and tasks/subscribe. A task is submitted to an installed executor by executorId, with optional threadId, turnId, and workspace context.

Teams and subagents

Team methods expose multi-agent orchestration over the same control plane. Use team/start to create a team, team/member/start to add members, team/member/message to send a prompt to a member, and interrupt/focus methods to manage active work. Split-pane methods currently return an unsupported error in the app server.

{"jsonrpc":"2.0","id":70,"method":"team/start","params":{"leadThreadId":"THREAD_ID","members":[{"name":"reviewer"}]}}
{"jsonrpc":"2.0","id":71,"method":"team/list","params":{"limit":10}}
{"jsonrpc":"2.0","id":72,"method":"turn/subagentTraces/list","params":{"threadId":"THREAD_ID","turnId":"TURN_ID"}}

Review, workflows, media, and memory

Higher-level product features are also controlled through JSON-RPC methods:

  • Plan review: plan/review/read, plan/review/comment, plan/review/rewrite, plan/review/approve, and plan/review/reject.
  • Hunks: hunk/list, hunk/read, and hunk/rollback.
  • Git change review: git/changes/list, git/changes/read, and observed shell/exec changes through workspace/changes/list.
  • Workflow imports: workflow/scan, workflow/preview, workflow/enable, workflow/ignore, workflow/refresh, and workflow/remove.
  • Dynamic workflows: workflows/plan, workflows/approve, workflows/list, workflows/get, workflows/pause, workflows/resume, workflows/stop, workflows/restartAgent, and workflow script methods.
  • Media: media/list, media/read, media/thumbnail, media/delete, and media/attachToTurn.
  • Memory: memory/list, memory/read, memory/save, memory/update, memory/delete, memory/query, memory/provider/list, memory/provider/set, and memory/recall/preview.
{"jsonrpc":"2.0","id":80,"method":"memory/query","params":{"scope":"project","text":"release process","limit":5,"includeGlobal":true}}
{"jsonrpc":"2.0","id":81,"method":"media/list","params":{"threadId":"THREAD_ID"}}
{"jsonrpc":"2.0","id":82,"method":"hunk/list","params":{"threadId":"THREAD_ID","turnId":"TURN_ID"}}
{"jsonrpc":"2.0","id":83,"method":"git/changes/list","params":{"workspace":"/absolute/path/to/workspace"}}
{"jsonrpc":"2.0","id":84,"method":"workflows/list","params":{"limit":10}}

Git change review marks untracked binary files with binary: true and additions: 0. Reading one through git/changes/read returns a binary-file patch summary instead of trying to decode bytes as text.

Marketplaces and plugins

Marketplace methods let clients manage external plugin catalogs and installed plugin variants without shelling out. The protocol distinguishes catalog sources, de-duplicated search results, concrete plugin variants, and installed records.

{"jsonrpc":"2.0","id":90,"method":"marketplaces/list"}
{"jsonrpc":"2.0","id":91,"method":"marketplaces/search","params":{"query":"rust"}}
{"jsonrpc":"2.0","id":92,"method":"plugins/list_installed"}

Installation methods include marketplaces/install_default, marketplaces/add, marketplaces/remove, marketplaces/refresh, marketplaces/plugin, plugins/preview_install, plugins/install, plugins/install_all_variants, plugins/disable, and plugins/uninstall.

Method reference by area

  • Handshake: initialize.
  • Discovery: extensions/list, providers/list, model/list, tools/list, agents/list.
  • Providers/auth/settings: providers/configure, providers/select, auth/codex/login, auth/codex/status, auth/codex/logout, auth/supergrok/login, auth/supergrok/status, auth/supergrok/logout, settings/get, settings/set_web_search, settings/set_default_mode.
  • Threads/turns/session: thread/start, thread/list, thread/read, turn/start, turn/steer, turn/interrupt, session/get, session/set_mode, session/exit_plan, session/resolve_approval, session/resolve_user_input.
  • Filesystem/process/tools: fs/readFile, fs/readDirectory, command/exec, commands/list, commands/expand, commands/run, tools/call.
  • Runners/tasks: runners/list, runners/select, runners/session, runners/snapshot, runners/delete, runners/ports, tasks/submit, tasks/list, tasks/get, tasks/cancel, tasks/subscribe.
  • Teams/subagents: team/start, team/list, team/read, team/member/start, team/member/message, team/member/interrupt, team/member/focus, team/cleanup, team/pane/focus, team/pane/cleanup, turn/subagentTraces/list, turn/subagentTrace/read.
  • Review/workflow/state: plan/review/read, plan/review/comment, plan/review/rewrite, plan/review/approve, plan/review/reject, git/changes/list, git/changes/read, workspace/changes/list, hunk/list, hunk/read, hunk/rollback, workflow/* import methods, and workflows/* dynamic workflow methods.
  • Marketplace/media/memory: marketplaces/list, marketplaces/install_default, marketplaces/add, marketplaces/remove, marketplaces/refresh, marketplaces/search, marketplaces/plugin, plugins/preview_install, plugins/install, plugins/install_all_variants, plugins/list_installed, plugins/disable, plugins/uninstall, media/list, media/read, media/thumbnail, media/delete, media/attachToTurn, memory/list, memory/read, memory/save, memory/update, memory/delete, memory/query, memory/provider/list, memory/provider/set, memory/recall/preview.

Implementing a client

  1. Start roder app-server as a child process or connect to remote WebSocket mode.
  2. Send initialize, then discover providers, models, tools, commands, and extensions.
  3. Create or select a thread with thread/start, thread/list, and thread/read.
  4. Submit work with turn/start or commands/run.
  5. Render notifications for turn/item deltas, approvals, plan reviews, workspace changes, workflows, memory updates, and task changes.
  6. Resolve approvals and user input through session/resolve_approval and session/resolve_user_input.
  7. Keep method handling forward-compatible: ignore unknown notification methods and tolerate extra result fields.
Use absolute paths. Filesystem methods and command working directories intentionally require absolute paths. Resolve workspace-relative UI paths on the client side before calling the app server.
Remote agent-node roadmap. The existing remote WebSocket mode is for authenticated local-network or trusted-tunnel clients. The agent-node plan keeps this same JSON-RPC surface but adds Roder-to-Roder control with encrypted transport, controller identity, reconnect behavior, and remote runtime ownership.