nanobot event and protocol index

Corresponding source

LayerSource
Business messages and queuesnanobot/bus/events.py, nanobot/bus/queue.py
Turn and Runtime Eventsnanobot/agent/loop.py, nanobot/bus/runtime_events.py
Hooks, progress, and streamingnanobot/agent/hook.py, nanobot/agent/progress_hook.py, nanobot/sdk/streaming.py
HTTP, WebSocket, and Channel protocolsnanobot/api/server.py, nanobot/channels/websocket.py, nanobot/channels/manager.py
WebUI wire/clientnanobot/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

FieldMeaning
channelSource Channel
sender_idUser identifier
chat_idConversation or channel identifier
contentText content
timestampReceipt time
mediaList of media URLs or paths
metadataChannel or internal control metadata
session_key_overrideOverrides the default Session key
session_keyProperty that returns the override or channel:chat_id

OutboundMessage

Definition: nanobot/bus/events.py::OutboundMessage

FieldMeaning
channel, chat_idDelivery target
contentText
reply_toOptional reply target
mediaMedia list
metadataRouting, progress, and rich-UI information
buttonsOptional 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.

FieldDirection and purposePrimary consumer
_wants_streamInbound: requests streamed outputAgentLoop.run()
_stream_deltaOutbound: this message is a text-stream fragmentChannelManager, concrete Channel
_stream_endOutbound: ends the current stream segmentSame as above
_stream_idDistinguishes stream segments within one TurnWebSocket, Telegram, and others
_progressOutbound: progress rather than an ordinary final replyChannelManager
_tool_hintMarks progress content as a Tool hintChannel rendering
_reasoning_deltaReasoning incrementChannel reasoning adapter
_reasoning_endEnds a reasoning segmentSame as above
_retry_waitSignals that a Provider is waiting before retryChannelManager
_runtime_controlInbound: in-process runtime control that does not enter an ordinary user Sessionnanobot/agent/context.py, MCP reload
_agent_uiOutbound: optional serializable rich-UI payloadRich 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

DataclassKey fieldsSemantics
SessionTurnStartedcontextSession is loaded; the Turn is about to build context
TurnRunStatusChangedcontext, status, started_atUser-visible run status changed
TurnCompletedcontext, latency_ms, runtimeThe final user-visible response has been delivered
GoalStateChangedcontext, session_metadataPersistent Goal state changed
RuntimeModelChangedmodel, model_presetThe 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’s text.delta for a RuntimeEventBus event.

5. SDK StreamEvent

Definition: nanobot/sdk/types.py

Event typeMeaning
run.startedOne SDK run began
text.delta / text.completedText fragment / text segment completed
reasoning.delta / reasoning.completedReasoning fragment / reasoning segment completed
tool.startedTool started; may carry name, arguments, and call ID
tool.completed / tool.failedTool succeeded / failed
run.completedReturns the final RunResult
run.failedRun 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:

RouteBehavior
POST /v1/chat/completionsOpenAI-compatible chat completions with JSON and multipart support
GET /v1/modelsLists available models
GET /healthReturns 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:

For learning purposes, remember:

RuntimeEvent dataclass ≠ OutboundMessage metadata ≠ SDK StreamEvent ≠ WebSocket frame

They may describe related actions, but they serve different consumers and compatibility boundaries.

8. Debugging a missing event

Check in order:

  1. Did the core action actually occur?
  2. Does it use a Runtime Event or a hook/progress callback?
  3. Did the producer invoke it?
  4. Did the adapter subscribe or receive the callback?
  5. Did Channel configuration filter progress, Tool hints, or reasoning?
  6. Did the wire frame arrive?
  7. Did the UI merge by stream ID and sequence correctly?

Return to Quick reference.