Real-time flow and control
A real-time agent does more than pass A's output to B. It must decide what happens when a consumer slows down, how an interruption stops an old answer, and which messages belong to the business pipeline versus observability.
Muxiva separates the data plane from the control plane:
flowchart LR
N1["Upstream Node"] -->|"Frame over a typed Edge"| N2["Downstream Node"]
N1 -.->|"Signal · explicit Graph Edge"| R["Rust Runtime"]
N1 -.->|"Notification · process-local observation"| B["NotificationBus"]
R -.->|"on_signal"| N2
B -.-> UI["Studio · logs · metrics · application"]
Frames carry business data
Audio, video, text, and byte Frames travel over Graph Edges. Port types, queue capacity,
overflow policy, and topology govern their delivery. Receiving a Frame causes the downstream
Node's on_process callback to run.
Signals change runtime state
Signals express interruption, cancellation, cache flushes, and other cross-Node control. A Node
calls ctx.emit_signal(...); the Runtime routes it only to receivers connected by outgoing Graph
Edges and invokes their on_signal. Core does not interpret Signal names or execute voice-product
policy. A Signal is not a process-global broadcast.
A common example is barge-in. VAD emits observational speech.started/stopped Events and ASR
final text enters builtin.voice_turn_controller. After filtering fillers and admitting a turn,
the controller exclusively emits muxiva.turn.cancelled; Agent/TTS reject old generations and
the Audio Sink clears stale playback. Runtime only delivers it over explicit Edges.
NotificationBus lets observers see what happened
Notifications are process-local observations such as a completed transcript, first-token
arrival, Node reconnection, or excessive latency. A Node calls ctx.publish_notification(...).
Studio, logs, metrics, or application subscribers can observe them, but a NotificationBus
notification does not replace an EventFrame or other business data flowing through the Graph.
| Requirement | Use |
|---|---|
| Send audio to ASR | Frame + Edge |
| Tell relevant Nodes to stop an old answer | Signal |
| Show local operational telemetry in Studio | NotificationBus notification |
| Deliver transcript or speech state to a remote client | Frame + Transport Node |
| Send LLM text to TTS | Frame + Edge |
Bounded queues and backpressure
Every Edge queue has a fixed capacity. A full queue follows an explicit policy:
| Policy | Behavior | Typical use |
|---|---|---|
block |
Wait for downstream capacity | Text or commands that must stay complete |
drop_oldest |
Remove the oldest Frame to stay current | Live audio or video preview |
drop_newest |
Preserve already queued data | Stable batch processing |
abort |
Fail and begin shutdown | Protocols where loss is unacceptable |
An unlimited queue appears lossless but turns a short slowdown into high latency and unbounded memory use. Muxiva makes capacity and policy explicit so latency, completeness, and failure behavior remain predictable.
Application turns and interruption
Voice turns are owned by the framework-provided but explicit and configurable
builtin.voice_turn_controller, not hard-coded in the Runtime scheduler or duplicated across Providers.
On interruption, relevant Nodes normally:
- cancel the current remote model request;
- discard late chunks from that request;
- clear audio that has not played yet;
- publish local operational state through NotificationBus and client state through a Transport Node; and
- keep subsequent input flowing through the Graph.
Policy stays in Nodes and mechanism stays in Core. This coordinates model generation, playback, and observation without coupling the generic Runtime to one model or voice protocol.
Lifecycle and shutdown
A normal run follows prepare → process → finish; errors, timeouts, or cancellation enter
abort. The Runtime uses bounded waits for workers and foreign execution domains so a process
cannot report completion while a background thread still owns a microphone, connection, or
model stream.
Next: Node extensibility and the end-to-end voice path.