ReactorModel subclass. You declare the media it sends and receives, load your weights
once, and write the loop that produces frames. The runtime does the rest.
The full model
Here is a complete model:model.py
Output tracks
Video carries frames; Audio carries samples. Defining
the class registers the tracks, and the model sends media by emitting instances of it.
A model can send several tracks at once:
main_video, main_audio) are the track identifiers clients subscribe to, so pick
names your frontend will read well.
To receive media from the client, declare an Input the same way and annotate it on the model. See
Media Input.
The model class
AReactorModel subclass holds four kinds of member, and the rest of this page takes them in turn:
Loading
config_path is the path to the file named by runtime.config in reactor.yaml, or None when
none is configured. The runtime hands you the path and stays out of the way — read it however you
like:
get_weights_path() so the same code
runs locally and in production; see Weights.
load() runs once for the life of the process, and one process serves one session after another.
Anything that should start fresh for each session — a step counter, a prompt, a cache of what this
audience has seen — belongs in @session_started instead, which is why the example splits them.
The run loop
load() and keeps running
for the model’s lifetime:
await self.connected.wait()blocks until a client connects, so an idle model burns no GPU.- The inner loop runs your forward pass and calls
emit()to hand each result to the transport. - When the last client leaves,
self.connectedclears, the inner loop exits, and the outer loop parks until someone else arrives.
Commands
@event declares one command a client can send. The handler’s parameters define the payload,
and InputField attaches the constraints the runtime enforces before your handler runs — a prompt
over 500 characters is rejected, not truncated.
Returning a ModelMessage makes it that command’s correlated reply, so a client awaiting
set_prompt resolves with the state that actually took effect rather than assuming its own value
was applied. Return None when there is nothing to say back, and raise CommandError when the
command cannot be honoured.
See Events & Messages.
Lifecycle hooks
@session_started and @session_ended bracket the session as a whole and fire once each, however
many clients come and go inside it. @connected and @disconnected fire once per client.
That difference decides where a piece of setup belongs. Resetting the prompt and the step counter is
session work: it happens once, before anyone is watching, and a second viewer joining leaves the
scene as the first one left it. Greeting an arrival with the current prompt is per-client work,
because every client needs it on the way in.
Adding a client: ClientInfo parameter gets you a handle to the client that triggered the hook, and
client.send() reaches that one client rather than broadcasting. That is what lets a viewer who
joins halfway through render the right UI immediately. The runtime injects the handle, so the schema
describes only the parameters a client actually sends.
See Sessions & Clients.
The manifest
reactor.yaml
model.name: the model’s identifier, used for registration and routing.model.version: the release tag, bumped on every shipped change.runtime.import: the Python import path to your class, inmodule:ClassNameform.model:MyModelmeans the classMyModelinmodel.py.runtime.config: the file whose path is handed toload(). Omit it andload()receivesNone.
Next
The Run Loop
Emitting frames, batches, and frame rates.
Sessions & Clients
Session hooks, multiple clients, and what resets when.