The four hooks
Each hook can be
async def or plain def, and a model declares at most one of each.
Ordering
For a session serving two clients in sequence, the hooks fire in this order:self.connected is set before the first @connected hook runs and cleared before the
@disconnected hook for the last client to leave, so a run() loop gating on it always agrees with
the hooks.
When a session ends, its connections are torn down wholesale.
@session_ended fires, but the
per-client @disconnected hooks do not. Anything that must happen for every client, however the
session ends, belongs in @session_ended.Choosing a hook
Put work in the scope whose lifetime it matches.@session_started / @session_ended for anything shared across everyone the session serves: a
scene the viewers all observe, a scoreboard, a conversation history, a cache you want warm before
the first client arrives.
@connected / @disconnected for anything belonging to one viewer: their camera pose, their
cursor, their entry in a roster.
Anything that costs real money or memory — loading a checkpoint, allocating GPU buffers — belongs in
load(), which runs once at startup, well before either hook.
The client handle
Add aclient: ClientInfo parameter to any hook or @event handler and the runtime injects the
client it concerns. It never appears in your schema, and clients never send it.
ClientInfo carries the connection id, the joined_at timestamp, and a send() coroutine that
messages that one client:
@disconnected hook, so you can say goodbye before it goes.
Serving several clients
self.connected tracks occupancy, not identity — it is set while any client is connected, and
clears only when the last one leaves. A single run() loop produces one stream that every connected
client receives.
That makes the common multi-client pattern a counter:
@connected would yank the scene out from under everyone already
watching whenever somebody new joined. Guarding on the counter keeps the second viewer from
disturbing the first.
If you need to address clients individually, keep the handles:
self.send() broadcasts to everyone and client.send() reaches exactly one. See
Per-client messages.
Next
Events & Messages
Commands, replies, and outbound messages.
Managing State
One event loop, shared by your loop and your handlers.