WebSocket and WebUI
Corresponding source
| Subject | Source |
|---|---|
| WebSocket Channel and protocol | nanobot/channels/websocket.py |
| WebUI session coordination | nanobot/session/webui_turns.py |
| WebUI backend routes and runtime data | nanobot/webui/ |
| Frontend client and message rendering | webui/src/lib/nanobot-client.ts, webui/src/hooks/useNanobotStream.ts, webui/src/components/ |
| WebSocket/WebUI tests | tests/channels/test_websocket_*.py, tests/webui/ |
Learning objectives
After this lesson, you should be able to:
- explain why WebUI depends on the WebSocket Channel and Gateway;
- distinguish WebSocket frames from runtime events;
- understand multi-chat multiplexing, stream segments, and authentication boundaries;
- locate the Python-side entry point for UI state coordination.
Prerequisites
- Understand ChannelManager.
- Understand RuntimeEventBus.
- Know that WebSocket is a bidirectional, long-lived connection.
1. WebUI is not the Gateway health page
Default ports:
| Surface | Default port |
|---|---|
| WebSocket Channel / WebUI | 8765 |
| Gateway health | 18790 |
| OpenAI-compatible API | 8900 |
WebUI static assets are served by HTTP services associated with the WebSocket Channel. Port 18790 is primarily Gateway health, not the browser workspace.
2. Structure
flowchart TD UI["React/TypeScript WebUI"] --> HTTP["Static assets and WebUI HTTP API"] UI <--> WS["WebSocket Channel"] WS <--> BUS["MessageBus"] BUS <--> AL["AgentLoop"] AL --> RE["RuntimeEventBus"] RE --> WTC["WebuiTurnCoordinator"] WTC --> BUS HTTP --> GS["Gateway services"] GS --> SM["Session / Cron / Skills / Settings"]
WebUI uses:
- WebSocket conversation and real-time events;
- HTTP routes for settings, media, Skills, Apps, Cron, and related management;
- Session and workspace metadata.
3. WebSocket connection
Connection URL:
ws://<host>:<port><path>?client_id=<id>&token=<token>Authentication may use:
- a static token;
- a short-lived issued token;
tokenIssueSecret;websocketRequiresToken.
allowFrom still controls the client ID.
When binding publicly to 0.0.0.0, Gateway requires a token or token issue secret to avoid unauthenticated exposure.
4. Multi-chat multiplexing
One WebSocket connection can subscribe to multiple chat IDs. Client envelopes include:
new_chatattachmessage
The server confirms with:
attached
Consequences:
- connection ID is not chat ID;
- one browser connection can carry several Sessions;
- the client can attach again after a page reload;
- stream buffers must be separated by both chat ID and stream ID.
5. Primary server events
| Event | Purpose |
|---|---|
ready | connection established |
message | complete response |
delta | text increment |
stream_end | current text segment ended |
reasoning_delta | reasoning increment |
reasoning_end | reasoning segment ended |
runtime_model_updated | runtime model changed |
coordination events such as goal_state | synchronize UI state |
error | soft protocol error; connection may remain open |
Verify fields in the WebSocket documentation and nanobot/channels/websocket.py.
6. Stream ID
One chat can contain several stream segments:
Model emits text
→ calls a Tool
→ continues emitting textThe UI therefore cannot concatenate every delta forever by chat ID alone. _stream_id distinguishes segments, and stream_end closes the current segment.
7. Runtime event and WebSocket frame
RuntimeEventBus publishes general Python events:
- Turn started;
- run status changed;
- Turn completed;
- Goal changed;
- runtime model changed.
WebuiTurnCoordinator or the WebSocket adapter converts them into UI frames.
Runtime event: core internal semantics
WebSocket frame: client wire protocol
React state: final presentation stateKeep these three layers separate.
8. Python-side WebUI services
nanobot/webui/ contains:
- settings routes/API;
- Skills API;
- MCP presets;
- CLI Apps;
- media;
- file preview;
- Session list/index;
- automations;
- Gateway token handling;
- workspaces;
- token usage;
- thread disk/display state.
Not every operation passes through AgentLoop. Changing settings or listing Skills is a management action, not a user Agent Turn.
9. React frontend
Its source is under webui/. Study it in depth only when changing browser presentation:
- Vite;
- React;
- TypeScript;
- WebSocket client;
- state and components.
Debug in this order:
- Did the core Turn complete?
- Was the runtime event published?
- Was the WebSocket frame delivered?
- Did the browser receive it?
- Did React update correctly?
Do not begin with CSS or a component when the unknown is backend state.
10. WebUI capability is not security authorization
WebUI can select a workspace and local access mode, but it cannot bypass:
- Gateway configuration;
- Tool workspace restrictions;
- Shell sandbox;
- operating-system permissions;
- Provider permissions;
- SSRF controls;
- token/allowFrom.
A UI control is a capability-selection surface, not a new security root.
Source and tests
nanobot/channels/websocket.pynanobot/session/webui_turns.pynanobot/webui/webui/tests/channels/test_websocket_integration.pytests/channels/test_websocket_http_routes.pytests/channels/test_websocket_envelope_media.pytests/webui/
Self-check
- Why is WebUI on 8765 while health is on 18790?
- How do connection ID, client ID, and chat ID differ?
- Why can a runtime event not be sent directly as WebSocket JSON?
- Why can one chat need several stream IDs?
- Why can WebUI access mode not bypass Tool security boundaries?