Skip to main content

Overview

Multi-Gateway support lets you connect Mission Control to multiple OpenClaw gateway instances simultaneously. Monitor connection health, switch between gateways, and manage direct CLI connections—all from a unified interface.
Connect to gateways on different hosts, ports, or networks to support distributed agent fleets, dev/staging/prod environments, or high-availability configurations.

Architecture

Mission Control supports two connection types:
WebSocket connections to OpenClaw gateway servers. Each gateway can manage multiple agents.
interface Gateway {
  id: number
  name: string              // Human-readable identifier
  host: string              // IP or hostname
  port: number              // WebSocket port (default: 18789)
  token_set: boolean        // Whether auth token configured
  is_primary: number        // 1 if primary, 0 otherwise
  status: string            // 'online' | 'offline' | 'timeout'
  last_seen: number | null  // Last successful probe timestamp
  latency: number | null    // Round-trip time in ms
  sessions_count: number    // Active agent sessions
  agents_count: number      // Registered agents
  created_at: number
  updated_at: number
}

Gateway Configuration

Database Schema

Gateways are stored in the gateways table:
CREATE TABLE gateways (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL,
  host TEXT NOT NULL,
  port INTEGER NOT NULL,
  token TEXT,                    -- Encrypted gateway auth token
  is_primary INTEGER DEFAULT 0,  -- Only one can be primary
  status TEXT DEFAULT 'unknown', -- online, offline, timeout
  last_seen INTEGER,
  latency INTEGER,               -- ms
  sessions_count INTEGER DEFAULT 0,
  agents_count INTEGER DEFAULT 0,
  created_at INTEGER NOT NULL,
  updated_at INTEGER NOT NULL
)

Adding a Gateway

Use the UI form or API:
curl -X POST "http://localhost:3000/api/gateways" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production Gateway",
    "host": "10.0.1.50",
    "port": 18789,
    "token": "your-gateway-token",
    "is_primary": false
  }'

Setting Primary Gateway

The primary gateway auto-connects on page load:
curl -X PUT "http://localhost:3000/api/gateways" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "id": 2,
    "is_primary": 1
  }'

WebSocket Connection

Connecting to a gateway establishes a WebSocket:
const connectTo = (gw: Gateway) => {
  const proto = window.location.protocol === 'https:' ? 'wss' : 'ws'
  const wsUrl = `${proto}://${gw.host}:${gw.port}`
  
  // Token is fetched from database by gateway ID
  connect(wsUrl, '')
}

Connection State

The Zustand store tracks active connection:
interface ConnectionState {
  isConnected: boolean
  url: string | null
  latency: number | null
  lastPing: number | null
  error: string | null
}

const { connection } = useMissionControl()

<div className="flex items-center gap-3">
  <span className={`w-2.5 h-2.5 rounded-full ${
    connection.isConnected ? 'bg-green-500' : 'bg-red-500 animate-pulse'
  }`} />
  <div>
    <div className="text-sm font-medium">
      {connection.isConnected ? 'Connected' : 'Disconnected'}
    </div>
    <div className="text-xs text-muted-foreground">
      {connection.url || 'No active connection'}
      {connection.latency != null && ` (${connection.latency}ms)`}
    </div>
  </div>
</div>

Health Monitoring

Gateway Probing

Probe all gateways to check status and latency:
curl -X POST "http://localhost:3000/api/gateways/health" \
  -H "x-api-key: YOUR_API_KEY"

Status Indicators

Gateway cards show real-time status:
function GatewayCard({ gateway, isCurrentlyConnected }) {
  const statusColors: Record<string, string> = {
    online: 'bg-green-500',
    offline: 'bg-red-500',
    timeout: 'bg-amber-500',
    unknown: 'bg-muted-foreground/30',
  }

  const lastSeen = gateway.last_seen
    ? new Date(gateway.last_seen * 1000).toLocaleString()
    : 'Never probed'

  return (
    <div className={`bg-card border rounded-lg p-4 ${
      isCurrentlyConnected ? 'border-green-500/30 bg-green-500/5' : 'border-border'
    }`}>
      <div className="flex items-center gap-2">
        <span className={`w-2 h-2 rounded-full ${
          statusColors[gateway.status] || statusColors.unknown
        }`} />
        <h3 className="text-sm font-semibold">{gateway.name}</h3>
        {gateway.is_primary && (
          <span className="text-2xs px-1.5 py-0.5 rounded bg-primary/20 text-primary border border-primary/30">
            PRIMARY
          </span>
        )}
        {isCurrentlyConnected && (
          <span className="text-2xs px-1.5 py-0.5 rounded bg-green-500/20 text-green-400">
            CONNECTED
          </span>
        )}
      </div>
      <div className="flex items-center gap-4 mt-1.5 text-xs text-muted-foreground">
        <span className="font-mono">{gateway.host}:{gateway.port}</span>
        <span>Token: {gateway.token_set ? 'Set' : 'None'}</span>
        {gateway.latency != null && <span>Latency: {gateway.latency}ms</span>}
        <span>Last: {lastSeen}</span>
      </div>
    </div>
  )
}

Direct CLI Connections

CLI tools connect via the /api/connect endpoint:

Registration

curl -X POST "http://localhost:3000/api/connect" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_name": "codex-session-01",
    "tool_name": "codex",
    "tool_version": "2.1.0",
    "metadata": {
      "workspace": "/home/user/project",
      "model": "claude-3-5-sonnet-20241022"
    }
  }'

Heartbeat

CLI tools send periodic heartbeats:
curl -X POST "http://localhost:3000/api/agents/7/heartbeat" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "connection_id": "conn-abc123def456",
    "tokens": {
      "input": 1500,
      "output": 800,
      "model": "claude-3-5-sonnet-20241022"
    }
  }'

Disconnection

curl -X DELETE "http://localhost:3000/api/connect" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "connection_id": "conn-abc123def456"
  }'

UI Components

The Multi-Gateway Panel provides full management:
export function MultiGatewayPanel() {
  const [gateways, setGateways] = useState<Gateway[]>([])
  const [directConnections, setDirectConnections] = useState<DirectConnection[]>([])
  const { connection } = useMissionControl()
  const { connect } = useWebSocket()

  return (
    <div className="p-4 md:p-6 max-w-5xl mx-auto space-y-6">
      {/* Header */}
      <div className="flex items-center justify-between">
        <div>
          <h2 className="text-lg font-semibold">Gateway Manager</h2>
          <p className="text-xs text-muted-foreground mt-0.5">
            Manage multiple OpenClaw gateway connections
          </p>
        </div>
        <div className="flex items-center gap-2">
          <button onClick={probeAll}>Probe All</button>
          <button onClick={() => setShowAdd(!showAdd)}>+ Add Gateway</button>
        </div>
      </div>

      {/* Current Connection Info */}
      <div className="bg-card border rounded-lg p-4">
        <ConnectionStatus connection={connection} />
      </div>

      {/* Add Form */}
      {showAdd && <AddGatewayForm onAdded={...} onCancel={...} />}

      {/* Gateway List */}
      <div className="space-y-2">
        {gateways.map(gw => (
          <GatewayCard
            key={gw.id}
            gateway={gw}
            isCurrentlyConnected={connection.url?.includes(`:${gw.port}`) ?? false}
            onSetPrimary={() => setPrimary(gw)}
            onDelete={() => deleteGateway(gw.id)}
            onConnect={() => connectTo(gw)}
            onProbe={() => probeGateway(gw)}
          />
        ))}
      </div>

      {/* Direct CLI Connections */}
      <div>
        <h3 className="text-sm font-semibold mb-3">Direct CLI Connections</h3>
        {directConnections.map(conn => (
          <DirectConnectionCard
            key={conn.id}
            connection={conn}
            onDisconnect={() => disconnectCli(conn.connection_id)}
          />
        ))}
      </div>
    </div>
  )
}

Best Practices

  • Name gateways descriptively: “Production”, “Staging”, “Dev”, not “Gateway 1”
  • Set primary for auto-connect: The primary gateway connects on page load
  • Use secure tokens: Generate strong tokens for production gateways
  • Document network topology: Keep a map of gateway hosts and ports

Troubleshooting

1

Gateway shows 'offline' status

  • Verify gateway is running: ps aux | grep openclaw
  • Check firewall rules allow WebSocket port (default: 18789)
  • Test connectivity: telnet <host> <port>
  • Review gateway logs for connection errors
2

High latency or timeouts

  • Check network congestion between Mission Control and gateway
  • Verify gateway host resources (CPU, memory)
  • Consider geographic distribution—use local gateways when possible
  • Review gateway configuration for performance tuning
3

Direct connection not appearing

  • Verify agent_name matches an existing agent in database
  • Check API key has operator role or higher
  • Ensure heartbeat is sent within 60 seconds of registration
  • Review Mission Control logs for auth or validation errors