Skip to main content
A model is a 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
That is the whole spine: media out, state that resets per session, a greeting for each arriving client, one command with a typed reply, and the loop that produces frames. Let’s break it down.

Output tracks

This declares what media the model sends to clients. Each field is a track, a named media channel, and the annotation is its kind. 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:
The field names (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

A ReactorModel subclass holds four kinds of member, and the rest of this page takes them in turn:
The runtime reads the class to build the model’s schema, which is what generates typed SDKs and docs for your clients. Everything a client can see comes from what you declare here.

Loading

Called once at startup, before any client can connect. This is where you load checkpoints, allocate GPU memory, and warm up your pipeline. 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:
Checkpoints are not baked into the image. Resolve them with 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

This is the heart of the model — an async method the runtime starts after load() and keeps running for the model’s lifetime:
  1. await self.connected.wait() blocks until a client connects, so an idle model burns no GPU.
  2. The inner loop runs your forward pass and calls emit() to hand each result to the transport.
  3. When the last client leaves, self.connected clears, the inner loop exits, and the outer loop parks until someone else arrives.
The Run Loop covers emitting, batches, and frame rates in detail.

Commands

Each @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, in module:ClassName form. model:MyModel means the class MyModel in model.py.
  • runtime.config: the file whose path is handed to load(). Omit it and load() receives None.

Next

The Run Loop

Emitting frames, batches, and frame rates.

Sessions & Clients

Session hooks, multiple clients, and what resets when.