Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

7.4 KiB

Architecture

mirage is a single Cargo workspace that builds one unified mirage executable. The binary bundles three roles — the control plane (ctl), the per-session host (host), and the optional web dashboard (daemon) — plus a set of pluggable emulator backends. Which role runs is decided by the subcommand:

  • mirage <ctl-command> — every user-facing verb (profile, topology, agent, emulators, session, exec, run, attach, logs, state, paths, about).
  • mirage host --session <id> — the per-session host (spawned internally).
  • mirage webui — the cross-session HTTP/WebSocket dashboard.

Crates

Crate Role
mirage (root) The unified binary: parses argv, owns the Tokio runtime, dispatches to a role.
mirage_ctl The control plane: the CtlCmd subcommand tree and dispatch.
mirage_core Shared types (ProfileDef, SessionDef, ExecDef, …), XDG path resolution, atomic state I/O, FileCtl, the attach stream, and the emulator/registry traits.
mirage_host The per-session host: watches exec/, runs commands, publishes health.
mirage_container Container provider abstraction (podman/docker) for containerised sessions.
mirage_builtin Embedded builtin agents, topologies, and profiles, plus their unpackers.
mirage_daemon The web UI server (HTTP API + WebSocket), built only with the daemon/webui features.
mirage_dashboard The embedded React/Vite single-page app served by mirage_daemon.
mirage_noop The noop backend: runs commands directly with no emulation.
mirage_rocjitsu The rocjitsu (and rocjitsu-dbt) backend and its asset embedding.
mirage_hotswap The hotswap load-time ISA-rewriting backend.
rocjitsu_sys Low-level FFI bindings used by the rocjitsu backend.

Emulator backends are link-only dependencies: each registers itself into the emulator registry via inventory at link time. The binary never names a backend directly, so enabling or disabling a backend's Cargo feature simply adds or removes it from mirage emulators.

Data flow

┌──────────────────────────────────────────────────────────────────┐
│ mirage <ctl-command>            (mirage_ctl::dispatch)            │
│  • parses argv (clap)                                             │
│  • owns the only Tokio runtime in the CLI process                │
│  • renders text / JSON                                           │
│  • drives a MirageCtl implementation (FileCtl by default)        │
└───────────────┬──────────────────────────────────────────────────┘
                │ trait MirageCtl  (read/write on-disk state)
                ▼
┌──────────────────────────────────────────────────────────────────┐
│ Filesystem under the mirage config / runtime / state directories │
│  profiles, agents, topologies (config)                           │
│  session/<id>/...           (runtime)                            │
└───────────────▲──────────────────────────────────────────────────┘
                │ writes def.json + exec defs; reads health/status/stdout
                │
┌───────────────┴──────────────────────────────────────────────────┐
│ mirage host --session <id>      (mirage_host)                    │
│  • one detached process per session                              │
│  • spawned by `session start` / `run`                           │
│  • watches exec/ for new defs, runs them through the emulator    │
│  • writes health.json continuously                              │
└──────────────────────────────────────────────────────────────────┘

The CLI and the host never talk over a socket or network — only through the session directory. See state-layout.md for the exact files and formats.

Why file-backed?

  • Inspectability. Every interesting state can be read with ls, cat, and jq. Debugging is far easier than tracing through an IPC daemon.
  • Recoverability. If any process crashes, the others keep going. Restarting the CLI is free; restarting a host is safe.
  • No daemon required. Read-only commands (list, show, paths) work with no host running at all.
  • Simple lifetime. $XDG_RUNTIME_DIR is cleared on logout, so sessions do not leak across reboots.

Why a per-session host?

  • Isolation. A buggy emulator in one session cannot affect another.
  • Containerisation. A host can be the entry point of a container while the CLI on the outside still drives it through the bind-mounted session directory.
  • Clean shutdown. SIGTERM to a host means exactly "stop this session"; nothing else needs to know.

Concurrency model

The CLI process runs a single Tokio runtime created in main. Each host process runs its own runtime.

  • Attach streams output via tokio_stream's ReceiverStream over a tail task that polls each node's stdout and exit_code. Interactive attaches put the terminal into raw mode and forward stdin to the exec's node-0 FIFO.
  • The host scans exec/ on a fixed tick, runs each discovered exec in its own task, and shares a tokio::sync::Notify between its signal handler and main loop for clean shutdown.

Adding an emulator backend

  1. Create a crate that implements the mirage_core::emulator::Emulator trait.
  2. Register it into the registry with an inventory::submit! in its lib.rs.
  3. Add it as an optional, feature-gated dependency of the root mirage crate and reference it with extern crate … as _ so the linker keeps the registration.

No code in the binary needs to name the backend; it appears automatically in mirage emulators and becomes selectable via --emulator.

Testing strategy

  • Unit tests live in each crate's *::tests modules and cover id validation, path resolution, atomic state I/O, option parsing, profile overrides, and the FileCtl surface. Tests that mutate process-wide env vars serialize through a shared lock.
  • End-to-end tests under tests/ exercise the full stack through the public CLI (assert_cmd) and the HTTP/WebSocket surface. Each runs in a fresh tempdir with overridden mirage directories, so they are independent and parallel-safe even though they spawn real host processes. The rocjitsu-backed suites require a working rocjitsu runtime; the noop-backed ones do not.