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 toroder-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 whileroder-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 isws://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 acceptsAuthorization: Bearer ... or a bearer.<token> WebSocket subprotocol. Clients should also offer roder.remote.v1, which the server echoes in the handshake. Hosted browser clients use the same subprotocol shape and revalidate open sockets before request dispatch; query-string credentials are rejected.

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 containerror 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. thread/read can include redacted lifecycle state for interrupted-turn recovery without loading every turn item.

Create or list threads

{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "thread/start",
  "params": {
    "cwd": "/absolute/path/to/workspace",
    "modelProvider": "mock",
    "model": "mock",
    "mcpAuthToken": "short-lived-capability-token",
    "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 onlyprompt.

{
  "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 onturn/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. Clients can also refresh a volatile thread-scopedmcpAuthToken on turn/start without persisting the credential in thread metadata.

{
  "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/outputDelta for command output when streaming is requested
  • agentSwarm/* for fanout work and team/* notifications for teammate roster, status, deltas, and terminal results
{
  "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, swarm mode, media artifacts, memory updates, and durable thread goal changes. Treat notifications as an append-only stream: render what you understand and ignore unknown methods until your client adds support.

Thread mode, approvals, and goals

Clients can read and change live policy mode through thread 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":"thread/state"}
{"jsonrpc":"2.0","id":31,"method":"thread/set_mode","params":{"mode":"ask","reason":"desktop toggle"}}
{"jsonrpc":"2.0","id":32,"method":"thread/set_agent_swarm_mode","params":{"threadId":"THREAD_ID","enabled":true,"trigger":"manual"}}
{"jsonrpc":"2.0","id":33,"method":"thread/goal/set","params":{"threadId":"THREAD_ID","objective":"Ship docs parity","tokenBudget":20000}}

When a turn requires approval, user input, or plan-mode exit, the runtime emits events and the client answers through the server. Durable goals usethread/goal/get, thread/goal/set, andthread/goal/clear; updates emit thread/goal/updated orthread/goal/cleared.

{"jsonrpc":"2.0","id":33,"method":"thread/resolve_approval","params":{"approvalId":"APPROVAL_ID","approved":true}}
{"jsonrpc":"2.0","id":34,"method":"thread/resolve_user_input","params":{"requestId":"REQUEST_ID","answers":{"choice":"yes"}}}
{"jsonrpc":"2.0","id":35,"method":"thread/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. Auth methods expose Codex, SuperGrok, and Kimi Code sign-in state. 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/configurepersists 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 aprocessId. The response then contains empty stdout/stderr and the server emits command/exec/outputDelta 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 selected tools directly, discover lazy catalog entries, 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":"discovery/search","params":{"groupId":"tools","query":"grep","limit":10}}
{"jsonrpc":"2.0","id":64,"method":"tools/call","params":{"threadId":"THREAD_ID","toolName":"get_goal","arguments":{}}}
{"jsonrpc":"2.0","id":65,"method":"tasks/list"}

Direct tools/call is intentionally limited to workflow-safe control tools such as get_goal, create_goal, and update_goal. General tool execution still goes through a turn so policy, transcript, and result routing stay intact.

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 optionalthreadId, turnId, and workspace context.

Teams and subagents

Team methods expose long-lived teammate orchestration over the same control plane as model-facing agent control. Use team/start to create a team,team/member/start to add members, team/member/message to start or steer a member turn, and team/member/interrupt to stop only that member's active turn. team/read mailbox entries includeMESSAGE, NEW_TASK, or FINAL_ANSWER kind, and completion notifications can include both finalMessage anderror. Split-pane methods currently return an unsupported error in the app server.

Subagent role and lane compatibility is validated before child work starts. Lane-as-role, incompatible-tool, and native full-history fork conflicts are rejected early.

{"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":"team/read","params":{"teamId":"TEAM_ID"}}
{"jsonrpc":"2.0","id":73,"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. First-party embedding providers include OpenAI, Google, and ZeroEntropy.
{"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 andadditions: 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, andplugins/uninstall.

Method reference by area

  • Handshake: initialize.
  • Discovery: extensions/list, providers/list, model/list, tools/list, agents/list, discovery/refresh, discovery/groups, and discovery/search.
  • 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, auth/kimi-code/login, auth/kimi-code/status, auth/kimi-code/logout, settings/get, settings/set_web_search, settings/set_default_mode.
  • Threads/turns/goals: thread/start, thread/list, thread/read, thread/archive, thread/goal/get, thread/goal/set, thread/goal/clear, turn/start, turn/steer, turn/interrupt, thread/state, thread/set_mode, thread/set_agent_swarm_mode, thread/exit_plan, thread/resolve_approval, and thread/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, runners/pause, runners/resume, runners/detach, runners/rejoin, tasks/submit, tasks/list, tasks/get, tasks/cancel, tasks/subscribe. The lifecycle methods are included in the checked-in schema and generated TypeScript and Python client types.
  • 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 thread/resolve_approval and thread/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 node note. Remote WebSocket mode is the same JSON-RPC surface over bearer-authenticated WebSocket transport. Sprites can now run a managedremote-roder app-server service inside a forked sandbox workspace.