Skip to main content
Every real-time model needs a way for clients to change inputs mid-generation. A prompt, a style setting, a slider value. In Reactor, you declare these as fields on an InputState dataclass, and the runtime does the rest.

Declaring state

from dataclasses import dataclass
from reactor_runtime.interface import InputState, InputField

@dataclass
class GameState(InputState):
    prompt: str = InputField(default="a sunny meadow")
    action: str = InputField(default="idle", choices=["idle", "left", "right", "jump"])
    brightness: float = InputField(default=1.0, ge=0.0, le=2.0)
Each public field becomes a client-facing parameter. The runtime auto-generates a set_<field> command for each one. From a client application using the official Reactor Client SDK:
await reactor.sendCommand("set_prompt", { prompt: "a dark forest" });
await reactor.sendCommand("set_action", { action: "jump" });
await reactor.sendCommand("set_brightness", { brightness: 0.5 });

Validation with InputField

InputField defines constraints that the runtime enforces automatically. Invalid values from clients are silently rejected and the field keeps its current value.
# Default value (any type)
score: float = InputField(default=1.0)

# Numeric range (min/max inclusive)
brightness: float = InputField(default=1.0, ge=0.0, le=1.0)

# String / sequence length
prompt: str = InputField(default="hello", min_length=1, max_length=500)

# Exhaustive allowed values
style: str = InputField(default="none", choices=["none", "oil_paint", "sketch"])

# Description (shown in schema)
seed: int = InputField(default=42, description="Random seed for generation")

Reading state in inference

Access self.state inside inference(). It’s guaranteed to be updated in real-time.
class GameModel(ReactorPipeline):
    state: GameState

    def inference(self):
        while True:
            # ✅ Read live state each iteration
            frame = self.pipe.forward(
                prompt=self.state.prompt,
                action=self.state.action,
            )
            frame *= self.state.brightness
            yield MyOutput(main_video=frame)

Session lifecycle

A fresh InputState instance (with all defaults) is created when a client connects. It’s destroyed when they disconnect. No state bleeds between sessions.
  • Client connects: self.state is a fresh instance with all defaults.
  • During session: fields are mutated by client commands.
  • Client disconnects: self.state is set to None.

Overriding auto-generated events

If you need custom logic when a field changes (e.g. encoding a prompt), define an @event handler with the same name. It replaces the auto-generated one:
from reactor_runtime.interface import event

@dataclass
class GameState(InputState):
    prompt: str = InputField(default="a sunny meadow")
    _embedding: Any = None

class GameModel(ReactorPipeline):
    state: GameState

    # ✅ Override the auto-generated set_prompt handler
    @event(name="set_prompt", description="Scene prompt with encoding")
    def set_prompt(self, prompt: str = InputField(default="")):
        self.state.prompt = prompt
        self.state._embedding = self.encoder.encode(prompt)
When you override a state handler, you need to manually set self.state.prompt = prompt to ensure the state gets updated.

Private fields

Prefix a field with _ to hide it from clients. No event is generated, and it doesn’t appear in the schema. Use them to store derived values that should be cleaned up automatically when the session ends.
@dataclass
class GameState(InputState):
    prompt: str = InputField(default="a sunny meadow")
    # ✅ Private — hidden from clients, no set_ event generated
    _embedding: Any = None
The _embedding from the override example above is a good use case: the event handler computes it, inference() reads it, and the runtime destroys the entire state on disconnect — no manual cleanup needed.

Next

Events & Messages

Custom events, lifecycle hooks, and outbound messages.

Video Input

Read webcam frames from the client.