@event. Your model sends
messages, declared as ModelMessage subclasses. Both are typed, and both end up in the schema
that generates your clients’ SDKs.
Commands
@event marks a method as the handler for a named command. The method’s parameters become the
command’s payload:
{"type": "set_style", "data": {"style": "anime"}} and the runtime validates the
payload against the signature before calling you. Handlers can be async def or plain def.
A command taking no parameters is fine, and useful for actions rather than settings:
Constraining a parameter
InputField carries the constraints the runtime checks before your handler runs:
invalid_command. The
runtime checks your defaults too, when the class is declared, so a default that violates its own
constraint fails at startup rather than on the first request.
Constraints also travel into the schema, so a generated SDK shows a client the range before it
sends anything.
Replying to a command
A handler that returns aModelMessage sends it back as that command’s correlated reply, so a
client awaiting the command resolves with the result:
None when the client needs no answer. Use a reply when the client should confirm what
actually took effect — clamped values, resolved defaults, or the full state after a change.
The return annotation must be a single ModelMessage subclass or None. A union — including
StyleChanged | None — is rejected when the class is declared, because the schema publishes one
response shape and a client generated from it would expect no body and receive one.
Reporting a failure
RaiseCommandError to answer a command with a failure the client can act on. The code and message
cross the wire unchanged, correlated with the command, so an awaiting caller rejects with a reason
instead of waiting for a reply that never comes:
internal_error, keeping
the detail — which can name paths, queries, or credentials — out of the reply.
The runtime sends a few codes of its own on the same channel: invalid_command when a payload
fails the model’s contract and no handler runs, and unresolved_upload when a command references
an upload that cannot be fetched.
Outbound messages
AModelMessage subclass declares a typed payload your model can send at any time. Fields are
declared like a dataclass:
{"type": "progress", "data": {"step": 3, "total": 100}}. The wire type is
the snake_case form of the class name.
MessageField is optional — it exists to attach a default or a description that shows up in the
generated schema. The class docstring documents the message itself.
Send one with self.send():
GenerationComplete.
Per-client messages
self.send() broadcasts to every client in the session. To reach one client, take a
client: ClientInfo parameter and use client.send(). The runtime injects the client that
triggered the handler, so it never appears in your schema.
client is a handle you can keep. Store it in @connected and message that client later from
anywhere:
self.send() covers everything. Reach for client.send() when a session
has an audience and the message is meant for one of them.
File uploads
To receive a binary file with a command, annotate a parameter asUploadedFile. The runtime
resolves the upload and hands your handler the bytes:
UploadedFile carries name, mime_type, data (the raw bytes), and size (their length). Check
mime_type before decoding — a client can upload anything.
A file that arrives with a command reaches that handler alone. The runtime resolves it first, so a
handler runs with the bytes in hand, and an upload it cannot fetch answers the client with an
unresolved_upload failure.
Uploads outside a command
A client can also send a file on its own.@file_uploaded receives those:
uploaded_file, plus the optional client. The
runtime fetches an upload’s bytes only when the model declares this hook, so it is what turns a
bare notification into a file.
Reach for it when a file has no natural command to ride along with — a drag-and-drop surface, or a
client that uploads first and decides what to do with the result later. When the file belongs to an
action, put it on that action’s @event instead, so the schema shows a client what the file is for.
Putting it together
- accepts
set_promptand replies with the appliedprompt_changed, - accepts
get_promptand answers the asking client alone, - broadcasts a
progressmessage on every frame.
Next
Managing State
One event loop, shared by your loop and your handlers.
Media Input
Read the client’s camera and microphone.