Skip to content
Robotadocs

WebSocket Transport

Drive an InteractiveSession over a WebSocket connection with @robota-sdk/agent-transport/ws. The client sends JSON messages (submit a prompt, run a command, answer a permission prompt) and receives the session's events as they happen.

Basic setup

createWsTransport() connects one session to one socket you already have. It adds no authentication: whoever can open a socket on your server can drive the session, so bind the server to loopback or authenticate the upgrade yourself.

import { InteractiveSession } from '@robota-sdk/agent-framework';
import { createNodeFrameworkHost } from '@robota-sdk/agent-framework/node';
import { createDefaultTools } from '@robota-sdk/agent-tools/defaults';
import { AnthropicProvider } from '@robota-sdk/agent-provider/anthropic';
import { createWsTransport } from '@robota-sdk/agent-transport/ws';
import { WebSocketServer } from 'ws';
 
const wss = new WebSocketServer({ host: '127.0.0.1', port: 8080 });
const provider = new AnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY });
 
wss.on('connection', async (ws) => {
  const session = new InteractiveSession({
    cwd: process.cwd(),
    host: createNodeFrameworkHost({ createDefaultTools }),
    provider,
  });
  const transport = createWsTransport({
    send: (msg) => ws.send(JSON.stringify(msg)),
    // Called once if a message cannot be delivered; the transport stops forwarding after it.
    onDeliveryError: () => ws.close(1011, 'Outbound delivery failed'),
  });
 
  session.attachTransport(transport);
  await transport.start();
 
  ws.on('message', (data) => transport.onMessage?.(String(data)));
  ws.on('close', () => {
    void transport.stop();
    void session.shutdown();
  });
});

transport.onMessage is set by start() and cleared by stop() or a delivery failure, hence the ?..

A connection drives the session unless you grant less: pass role: 'observe' to createWsTransport() for a read-only connection that follows the session's conversation and state but cannot submit, answer prompts or control the session.

Message protocol

The main messages are below. The full set is the TClientMessage and TServerMessage types exported by @robota-sdk/agent-transport.

Client → server

{ "type": "submit", "prompt": "Fix the bug" }
{ "type": "command", "name": "compact", "args": "focus on the API" }
{ "type": "abort" }
{ "type": "cancel-queue" }
{ "type": "get-messages" }
{ "type": "get-context" }
{ "type": "permission-response", "id": "<request id>", "result": true }

command runs one of the session's commands. An InteractiveSession has only the commands passed in its commandModules option; the CLI composes its slash commands this way.

Server → client

{ "type": "text_delta", "delta": "Here is..." }
{ "type": "tool_start", "state": { "toolName": "Read", "isRunning": true } }
{ "type": "complete", "result": { "response": "Done." } }
{ "type": "command_result", "name": "compact", "message": "Context compacted.", "success": true }
{ "type": "permission_request", "event": { "id": "<request id>", "toolName": "Bash", "toolArgs": { "command": "pnpm test" } } }

A permission_request waits for a permission-response with the same id. result is true (allow once), false (deny), "allow-session" or "allow-project".

Advanced: direct handler

createWsTransport() is a thin wrapper. To control the connection yourself, pair the carrier-neutral session handler with an outbound delivery boundary:

import {
  createOutboundDelivery,
  createSessionMessageHandler,
  type IProtocolSession,
} from '@robota-sdk/agent-transport';
import type { WebSocketServer } from 'ws';
 
declare const wss: WebSocketServer;
declare const session: IProtocolSession; // for example an InteractiveSession
 
wss.on('connection', (ws) => {
  const deliver = createOutboundDelivery(
    (message) => ws.send(JSON.stringify(message)),
    () => ws.close(1011, 'Outbound delivery failed'),
  );
  const { onMessage, cleanup } = createSessionMessageHandler({ session, deliver });
 
  ws.on('message', (data) => onMessage(String(data)));
  ws.on('close', cleanup);
});

For a ready-made server with token admission, use the package's WsTransport class; examples/capabilities/multi-surface-deploy serves one session over WebSocket and HTTP together. examples/websocket-chat is a complete chat server that defines its own small message protocol instead.