Architecture
Miravo's internal architecture — the engine, asset graph, event bus, tick scheduler, generator pipeline, model compiler, and protocol adapter interface.
Miravo follows a one-way data flow: Configuration goes in, protocol output comes out.
Data Flow
Section titled “Data Flow”Config (.twin.yaml + .miravo.yaml) -> Twin Runtime (compile models, spawn instances) -> Tick Scheduler (fixed-interval loop) -> Generators + Lifecycle + Faults (evaluate members) -> Asset Graph (source of truth) -> Event Bus (typed events) -> Protocol Adapters (MQTT, OPC UA, and more)Key Components
Section titled “Key Components”Engine
Section titled “Engine”The createEngine() factory wires everything together and returns the public API: start, stop, execute commands, register adapters, get metrics/state. The engine owns the lifecycle of all subsystems.
Twin Runtime
Section titled “Twin Runtime”Registers compiled models and spawns asset instances. Each instance gets:
- A forked RNG (deterministic per-instance randomness)
- Varied parameters (based on
variationsetting) - Per-instance generator instances (stateful generators like
random-walkare independent) - Writable member overrides (for methods like
SetSpeed)
Scheduler
Section titled “Scheduler”Runs the tick loop at a fixed interval (default 1000ms). Each tick:
- Snapshot each instance’s parameters (tick-consistent view)
- Evaluate all members in declaration order
- Apply lifecycle effects (
value * multiplier + offset) - Apply fault effects (spike, then multiplier, then offset)
- Write values to the asset graph (skip unchanged values)
- Emit
tick:completewith the graph snapshot and delta
The scheduler uses writeMemberValue() to skip writes when both value and quality are unchanged, preserving timestamps and avoiding unnecessary delta entries.
Asset Graph
Section titled “Asset Graph”The AssetGraph is the runtime source of truth. It holds all active AssetNode instances with their current member values, parameters, lifecycle state, and active faults. Snapshots are immutable by contract — protocol adapters receive a consistent point-in-time view.
Event Bus
Section titled “Event Bus”A typed mitt bus carries all internal communication. Components never reference each other directly. Key events:
| Event | Payload |
|---|---|
tick:complete | { graph: AssetGraphSnapshot, delta: AssetGraphDelta } |
instance:created / instance:removed | { id } |
lifecycle:changed | { instanceId, stage } |
fault:triggered / fault:cleared | { instanceId, fault } |
engine:state-changed | { from, to } (idle, running, paused, stopped) |
adapter:enabled / adapter:disabled | { name, endpoint? } |
Model Compiler
Section titled “Model Compiler”compileModel() validates a raw model definition:
- Topological ordering of member dependencies
$param.Xreference validation (parameter must exist and be numeric)input.memberreferences (must be declared earlier)- Method argument types and writable targets
- Lifecycle/fault effect targets
Invalid models fail at compile time, before the simulation starts.
Protocol Adapter Interface
Section titled “Protocol Adapter Interface”All protocol adapters implement the ProtocolAdapter interface:
interface ProtocolAdapter { name: string; start(config: unknown): Promise<void>; stop(): Promise<void>; onTick(graph: AssetGraphSnapshot): void; getMetrics(): AdapterMetrics;}Adapters receive the graph snapshot each tick. They never compute member values — they only project what the engine provides. This interface is how MQTT and OPC UA are implemented today, and how upcoming protocols (Modbus TCP, Sparkplug B, and others) will be added.
All current adapters use delta-driven updates. onTick() receives the full graph snapshot each tick. Structural changes (instance creation and removal) are processed as they occur so the address space stays consistent between ticks.
Determinism
Section titled “Determinism”Miravo uses a seeded xoshiro128** PRNG. Each instance gets a forked RNG from the parent seed. Same seed + same config = bit-identical output. Math.random() and Date.now() are never used.
Content Catalog
Section titled “Content Catalog”The ContentCatalog resolves models and templates through three layers:
- Current working directory
- Local registry (
~/.miravo/registry/local/) - Built-in content (
@miravo/contentpackage)
Higher-priority layers shadow lower ones by name.
Persistence
Section titled “Persistence”State persistence saves structural snapshots to $MIRAVO_HOME/state/<name>.json. On startup, the engine restores from the snapshot if one exists. Saves are debounced and serialized to prevent write races.