nanobot event and protocol index
Corresponding source
| Layer | Source |
|---|---|
| Business messages and queues | nanobot/bus/events.py, nanobot/bus/queue.py |
| Turn and Runtime Events | nanobot/agent/loop.py, nanobot/bus/runtime_events.py |
| Hooks, progress, and streaming | nanobot/agent/hook.py, nanobot/agent/progress_hook.py, nanobot/sdk/streaming.py |
| HTTP, WebSocket, and Channel protocols | nanobot/api/server.py, nanobot/channels/websocket.py, nanobot/channels/manager.py |
| WebUI wire/client | nanobot/webui/, webui/src/lib/, webui/src/hooks/ |
1. Identify the layer first
flowchart TD CH["Channel / API / SDK"] --> IM["InboundMessage or direct call"] IM --> AL["AgentLoop / AgentRunner"] AL --> OM["OutboundMessage"] OM --> CH AL --> RE["RuntimeEventBus<br/>high-level runtime state"] AL --> HK["Agent hooks / progress callbacks<br/>text, reasoning, and Tool progress"] HK --> AD["SDK / Channel / WebSocket adapter"] AD --> WP["Surface-specific public or wire protocol"]
The most important boundaries are:
- MessageBus carries business messages.
- RuntimeEventBus carries only five kinds of high-level runtime state.
- Text deltas, reasoning deltas, and Tool progress come from Agent-hook and progress-callback paths.
- SDK and WebSocket translate those internal signals into their own event formats.
2. MessageBus data objects
InboundMessage
Definition: nanobot/bus/events.py::InboundMessage
| Field | Meaning |
|---|---|
channel | Source Channel |
sender_id | User identifier |
chat_id | Conversation or channel identifier |
content | Text content |
timestamp | Receipt time |
media | List of media URLs or paths |
metadata | Channel or internal control metadata |
session_key_override | Overrides the default Session key |
session_key | Property that returns the override or channel:chat_id |
OutboundMessage
Definition: nanobot/bus/events.py::OutboundMessage
| Field | Meaning |
|---|---|
channel, chat_id | Delivery target |
content | Text |
reply_to | Optional reply target |
media | Media list |
metadata | Routing, progress, and rich-UI information |
buttons | Optional rows of buttons |
3. Internal metadata signals
These underscore-prefixed fields coordinate nanobot’s internal layers. External applications should not assume that they form a stable, long-term public protocol.
| Field | Direction and purpose | Primary consumer |
|---|---|---|
_wants_stream | Inbound: requests streamed output | AgentLoop.run() |
_stream_delta | Outbound: this message is a text-stream fragment | ChannelManager, concrete Channel |
_stream_end | Outbound: ends the current stream segment | Same as above |
_stream_id | Distinguishes stream segments within one Turn | WebSocket, Telegram, and others |
_progress | Outbound: progress rather than an ordinary final reply | ChannelManager |
_tool_hint | Marks progress content as a Tool hint | Channel rendering |
_reasoning_delta | Reasoning increment | Channel reasoning adapter |
_reasoning_end | Ends a reasoning segment | Same as above |
_retry_wait | Signals that a Provider is waiting before retry | ChannelManager |
_runtime_control | Inbound: in-process runtime control that does not enter an ordinary user Session | nanobot/agent/context.py, MCP reload |
_agent_ui | Outbound: optional serializable rich-UI payload | Rich clients; ordinary Channels may ignore it |
Metadata is not one flat public protocol
The same dictionary carries Channel routing, internal streaming flags, and optional UI data. Before adding a field, identify its producer, consumer, persistence behavior, and compatibility across entry points.
4. Exact RuntimeEventBus event set
Definition: nanobot/bus/runtime_events.py
| Dataclass | Key fields | Semantics |
|---|---|---|
SessionTurnStarted | context | Session is loaded; the Turn is about to build context |
TurnRunStatusChanged | context, status, started_at | User-visible run status changed |
TurnCompleted | context, latency_ms, runtime | The final user-visible response has been delivered |
GoalStateChanged | context, session_metadata | Persistent Goal state changed |
RuntimeModelChanged | model, model_preset | The runtime’s active model changed |
RuntimeEventContext carries common channel, chat_id, session_key, and metadata fields.
RuntimeEventBus.publish() invokes subscribers in registration order and awaits asynchronous handlers. If one handler raises, the failure is logged without propagating to the publisher. publish_nowait() requires a running event loop; otherwise, the event is dropped and a debug message is logged.
RuntimeEventBus has no text- or Tool-delta dataclass
Fine-grained text, reasoning, and Tool progress travels through
nanobot/agent/hook.py,progress_hook.py, and corresponding callbacks. Do not mistake SDK’stext.deltafor a RuntimeEventBus event.
5. SDK StreamEvent
Definition: nanobot/sdk/types.py
| Event type | Meaning |
|---|---|
run.started | One SDK run began |
text.delta / text.completed | Text fragment / text segment completed |
reasoning.delta / reasoning.completed | Reasoning fragment / reasoning segment completed |
tool.started | Tool started; may carry name, arguments, and call ID |
tool.completed / tool.failed | Tool succeeded / failed |
run.completed | Returns the final RunResult |
run.failed | Run failed |
StreamEvent may carry delta, content, result, Tool fields, usage, error, and metadata. See nanobot/sdk/streaming.py for exact generation logic.
6. HTTP API
nanobot/api/server.py currently registers:
| Route | Behavior |
|---|---|
POST /v1/chat/completions | OpenAI-compatible chat completions with JSON and multipart support |
GET /v1/models | Lists available models |
GET /health | Returns API service health |
Streaming responses use SSE semantics constructed by this API layer. An API chunk is not a MessageBus _stream_delta, although both may originate from the same core callback.
See the official OpenAI API documentation for the complete format.
7. WebSocket and WebUI
A WebSocket frame contains the envelope, sequence, replay, stream ID, and UI state required by the client. Treat the following as authoritative for exact wire fields:
- Official WebSocket documentation
nanobot/channels/websocket.pytests/channels/test_websocket_integration.py
For learning purposes, remember:
RuntimeEvent dataclass ≠ OutboundMessage metadata ≠ SDK StreamEvent ≠ WebSocket frameThey may describe related actions, but they serve different consumers and compatibility boundaries.
8. Debugging a missing event
Check in order:
- Did the core action actually occur?
- Does it use a Runtime Event or a hook/progress callback?
- Did the producer invoke it?
- Did the adapter subscribe or receive the callback?
- Did Channel configuration filter progress, Tool hints, or reasoning?
- Did the wire frame arrive?
- Did the UI merge by stream ID and sequence correctly?
Return to Quick reference.