Routing — Message Dispatch & Session Management

Traffic Controller for OpenClaw

The Routing subsystem dispatches authenticated RPC requests to the correct handler, manages session lifecycles across 32+ fields of state, resolves assistant identities, and coordinates broadcast events to connected clients via WebSocket.

32+
Session Fields
5
Session Statuses
80+
RPC Methods
4
Session Kinds

TypeScript RPC WebSocket Stateful

What Is This & Why Does It Exist?

Once a client is authenticated, its requests need to reach the right handler with the right session context. The Routing subsystem is the dispatcher that makes this happen.

💡 Real-world analogy

Think of Routing as a hotel concierge desk. The GatewayRequestHandlers map is the service directory — each RPC method name is a service (room service, spa, restaurant) and each GatewayRequestHandler is the staff member who fulfills it. GatewaySessionRow is the guest’s folio tracking everything from check-in time to room charges. resolveAssistantIdentity() assigns the personal butler (assistant) with the right name badge and uniform.

Table of Contents

Architecture

The Routing layer sits after authentication and protocol validation. It maps RPC method names to handlers, manages session state, and coordinates broadcasts.

flowchart TB
    subgraph Input
        REQ["Validated RequestFrame
method + params"] end subgraph Routing["Routing Layer"] DISPATCH["GatewayRequestHandlers
Record of string to GatewayRequestHandler"] OPTS["GatewayRequestOptions
req, client, respond, context"] subgraph Session["Session Manager"] CREATE["Create Session"] RESUME["Resume Session"] PATCH["Patch Session"] LIFE["Lifecycle Events"] end subgraph Identity["Identity Resolver"] RESOLVE["resolveAssistantIdentity()"] DEFAULT["DEFAULT_ASSISTANT_IDENTITY"] end end subgraph Targets["Target Systems"] AGENT["Agent Runtime"] WIZARD["Wizard Sessions"] CONFIG["Config Handlers"] PRESENCE["Presence System"] end REQ --> DISPATCH DISPATCH --> OPTS OPTS --> Session OPTS --> Identity OPTS --> AGENT OPTS --> WIZARD OPTS --> CONFIG OPTS --> PRESENCE Session --> LIFE LIFE --> BROADCAST["broadcast()"]

Data Flow

sequenceDiagram
    participant C as Client
    participant D as Dispatcher
    participant H as Handler
    participant S as Session Manager
    participant I as Identity Resolver
    participant A as Agent Runtime
    participant B as Broadcast

    C->>D: RequestFrame {method, params, id}
    D->>D: lookup GatewayRequestHandlers[method]
    D->>H: handler(GatewayRequestHandlerOptions)
    H->>I: resolveAssistantIdentity(cfg, agentId)
    I-->>H: AssistantIdentity {name, avatar, emoji}
    H->>S: create/resume GatewaySessionRow
    S-->>H: session {key, kind, status}
    H->>A: forward to agent runtime
    A-->>H: response payload
    H->>S: persistGatewaySessionLifecycleEvent()
    S->>B: broadcast lifecycle event
    H-->>C: ResponseFrame {ok, payload}

Key Types & Functions

NameFileDescription
GatewayRequestHandlerserver-methods/types.tsFunction: (opts: GatewayRequestHandlerOptions) => Promise<void> | void
GatewayRequestHandlersserver-methods/types.tsRecord<string, GatewayRequestHandler> — RPC method dispatch map
GatewayRequestOptionsserver-methods/types.tsContains req, client, respond, context, isWebchatConnect
GatewayClientserver-methods/types.tsClient info: connect, connId, clientIp, canvasCapability
RespondFnserver-methods/types.ts(ok, payload?, error?, meta?) => void
GatewaySessionRowsession-utils.types.ts32+ field session: key, kind, status, tokens, cost, displayName
SessionRunStatussession-utils.types.tsUnion: "running" | "done" | "failed" | "killed" | "timeout"
GatewaySessionsDefaultssession-utils.types.tsDefaults: modelProvider, model, contextTokens
SessionsListResultsession-utils.types.tsList result with defaults and session rows
SessionsPatchResultsession-utils.types.tsPatch result: entry, resolved model info
AssistantIdentityassistant-identity.tsIdentity: agentId, name, avatar, emoji
resolveAssistantIdentity()assistant-identity.tsResolve identity from config + workspace files
DEFAULT_ASSISTANT_IDENTITYassistant-identity.tsDefault: {agentId:"main", name:"Assistant", avatar:"A"}
GatewaySessionLifecycleSnapshotsession-lifecycle-state.tsPartial lifecycle shape for event snapshots
persistGatewaySessionLifecycleEvent()session-lifecycle-state.tsPersist lifecycle event to storage

Session Kinds

KindDescriptionUse Case
"direct"Direct 1:1 conversation sessionCLI or Web UI single chat
"group"Group/multi-agent sessionMulti-agent collaboration
"global"Global broadcast sessionSystem-wide announcements
"unknown"Fallback for unrecognized session typesLegacy or custom sessions

Session Row Key Fields

FieldTypeDescription
keystringUnique session identifier
kind"direct" | "group" | "global" | "unknown"Session type
statusSessionRunStatusCurrent run status
spawnedBystring?Parent session that spawned this one
displayNamestring?User-facing session name
derivedTitlestring?Auto-generated title from conversation
lastMessagePreviewstring?Preview of most recent message
inputTokensnumber?Total input tokens consumed
outputTokensnumber?Total output tokens generated
estimatedCostUsdnumber?Estimated cost in USD
startedAtnumber?Session start timestamp
endedAtnumber?Session end timestamp
runtimeMsnumber?Total runtime in milliseconds

Code Examples

Defining a Request Handler

import { GatewayRequestHandler } from './server-methods/types';

const handleSessionsList: GatewayRequestHandler = async (opts) => {
  const { req, client, respond, context } = opts;
  const params = req.params as SessionsListParams;

  const result: SessionsListResult = await context.deps.sessionStore.list({
    limit: params.limit ?? 50,
    offset: params.offset ?? 0,
  });

  respond(true, result);
};

// Register in the handler map
const handlers: GatewayRequestHandlers = {
  'sessions.list': handleSessionsList,
  'sessions.send': handleSessionsSend,
  'config.get': handleConfigGet,
  // ... 80+ more methods
};

Resolving Assistant Identity

import {
  resolveAssistantIdentity,
  AssistantIdentity,
  DEFAULT_ASSISTANT_IDENTITY,
} from './assistant-identity';

const identity: AssistantIdentity = resolveAssistantIdentity({
  cfg: openClawConfig,
  agentId: 'main',
  workspaceDir: '/home/user/.openclaw',
});

// identity.name => "Assistant" (or custom name, max 50 chars)
// identity.avatar => "A" (or URL, max 200 chars)
// identity.emoji => undefined (or custom, max 16 chars)

Persisting Session Lifecycle Events

import {
  persistGatewaySessionLifecycleEvent,
  deriveGatewaySessionLifecycleSnapshot,
} from './session-lifecycle-state';

const snapshot = deriveGatewaySessionLifecycleSnapshot({
  session: currentSession,
  event: { ts: Date.now(), data: { phase: 'end', status: 'done' } },
});

await persistGatewaySessionLifecycleEvent({
  sessionKey: 'sess_abc123',
  event: { ts: Date.now(), data: { phase: 'end' } },
});

Configuration

ParameterTypeDefaultDescription
assistant.namestring"Assistant"Display name (max 50 chars)
assistant.avatarstring"A"Avatar char or URL (max 200)
assistant.emojistring—Emoji badge (max 16 chars)
session.defaultModelstring—Default model for new sessions
session.defaultProviderstring—Default provider for new sessions
session.contextTokensnumber—Default context window size
protocol.versionnumberPROTOCOL_VERSIONCurrent protocol version

Deep-Dive Focus Pages

📤 Message Dispatch Focus

How RPC method names are mapped to handlers, request validation, and the respond/error pattern.

📋 Session Management Focus

The full session lifecycle: creation, status transitions, token tracking, cost estimation, and cleanup.

🤖 Assistant Identity Focus

How assistant names, avatars, and emojis are resolved from config, workspace files, and defaults.

Directory Map

Source files for Routing
src/gateway/
├── assistant-identity.ts           ← AssistantIdentity, resolveAssistantIdentity
├── session-utils.types.ts          ← GatewaySessionRow (32+), SessionRunStatus
├── session-lifecycle-state.ts      ← Lifecycle snapshots, persistence
├── server-methods/
│   └── types.ts                    ← GatewayRequestHandler, GatewayRequestContext
├── server/
│   └── ws-types.ts                 ← GatewayWsClient (WebSocket client)
└── protocol/
    └── index.ts                    ← 80+ validators, ConnectParams, RequestFrame