Skip to content

Runtime architecture & lifecycle ​

How the host discovers your plugin, when it instantiates it, when it refuses to enable it, and who is mutually exclusive with whom.


1. Pipeline & plugin insertion points ​

The host is pipeline-centric: audio/captions come in → recognition → translation → display/speech. Plugins hook into explicit extension points:

[audio capture / browser captions]
        │
        ▼
 process_audio          ← middleware
        │
        ▼
      VAD segmentation
        │
        ▼
      ASR              ← asr Provider
        │
        ▼
 process_transcript     ← middleware
        │
        ▼
 glossary prompt + MT   ← mt Provider + glossary
        │
        ▼
 process_translation    ← middleware
        │
        ├──► UI subtitles
        └──► TTS speech    ← tts Provider
                │
                ▼
        process_tts_audio  ← middleware
                │
                ▼
            sound card
  • Middleware runs in ascending priority order (lower number runs first, default 100). An exception in one middleware is isolated and does not break the chain.
  • Glossaries only affect enabled packs: filter entries by the current source_lang→target_lang → inject into the prompt / correct the source side before translation → mt.translate(...) → middleware rewrites the translation → bidirectional glossary substitution. Cloud Qwen ASR also turns the word list into live hotwords.
  • interpretation is a bypass: once the host enters SI mode it no longer mounts/drives the standard ASR → MT → TTS pipeline; audio frames go straight to the SI provider (see interpretation).
  • Browser captions (caption_bridge) take the lightweight feed_direct_caption path.

2. Discovery & loading ​

local extensions/               ──┐
%APPDATA%/transbox/extensions/  ─┼─► PluginRegistry scans mod.json ─► mounts by type
Steam Workshop content/<AppID>/ ─┤
<data dir>/workshop/<id>/       ─┘   (self-published build only)

Dispatch by type:

typeHow it is mounted
glossary / i18n / themeMounted as data (glossary / dictionary / style tokens), no Python import
asr / mt / tts / middleware / assistant / interpretation / toolimportlib loads the Python class, instantiated on demand
caption_bridge / caption_extractorWired into the caption bridge chain (see caption_extractor)

Same-id priority: local > steam > web. A bundled plugin shipped with the app can be overridden by a workshop version — only when the workshop version has a higher semver.

An already-enabled type=tool plugin is pre-loaded with an instance during the scan phase (because it has to register hotkeys). So a tool plugin's initialize() runs at startup — don't do heavy work in there.


3. Lifecycle ​

discover → parse mod.json → (on enable) instantiate + initialize(config) → use → disable shutdown()
PhaseWhat the host doesYour responsibility
ScanRead mod.json, run dependency gates, restore the previous enabled stateGet the manifest right
EnableCreate the instance, call initialize(config)Prepare only: parse config, check files; do not start services, do not load weights
UseThe host calls ensure_loaded() / engine interfaces / action_* on demandThe request entry point handles "make sure it's ready" itself
Config changePersist + call update_config(delta) + broadcast config.changedHot-sync internal state
Disable / exit / release VRAMCall shutdown() → unregister_plugin_hotkeys / close_plugin_windows / cancel event subscriptionsYou must kill your own child processes
UnloadReclaim libs/ vendor/ from sys.pathDon't leave state anywhere else

An external (non-built-in) plugin's initialize() runs on a background thread; before it is needed the host calls wait_async_init() to wait for it to settle. So blocking for a few seconds inside initialize will not stall the UI — but don't use it to start services either.


4. Dual-track routing & singleton reuse ​

Since v0.2, the inbound track (🎧 peer / system audio) and the outbound track (🎙️ our own microphone) are fully decoupled across all three layers: ASR / MT / TTS:

  1. Zero changes for plugin authors: asr / mt / tts plugins only follow the standard Provider interfaces and need no special logic for the two tracks.
  2. The host routes intelligently: the pipeline dispatches by data flow direction (source="peer" / "self") to the matching active engine.
  3. VRAM-safe singleton reuse: when both tracks select the same model the host strictly reuses the same instance reference (outbound is inbound), keeping VRAM cost at 1x; concurrent calls are protected by the engine's internal lock.
  4. Silent bypass: when the outbound track is unassigned (none), the outbound audio stream and inference are fully short-circuited — zero CPU/GPU/network cost.

There are only two actual requirements for a plugin:

  • implement the standard Provider interface (see Provider signatures);
  • a streaming-capable engine that wants to serve both tracks at once must return supports_concurrent_streams = True (return False if the underlying handle is single-session, and the host will automatically fall back for the other track).

5. Enabling and mutual exclusion rules ​

Enable / disable action per type:

typeon enableon disable
glossaryregisters into the global glossary managerunregisters
i18nswitches to that language; mutually exclusive with other third-party language packsfalls back to zh-CN if it was current
theme (full)applies the theme; mutually exclusive with other full themesreverts to the default theme
Same-slot UI extensionsoccupy the slot and disable peers on itremoves the slot component
asr / mt / ttscan be set as the active enginefalls back to another enabled engine if it was active
interpretationenters SI mode; bypasses standard ASR+MT+TTSleaves SI and reverts to the standard pipeline
middlewaremounts into the middleware managerunmounts
caption_bridgedrives the web caption servicedrives the service off
Built-in default themecannot be disabled—

General rules:

  • Enabling takes effect immediately / disabling reverts immediately.
  • Same-kind engines are mutually exclusive: asr / mt / tts / interpretation each have exactly one "active engine", chosen by the user on the settings page (set_active_provider). Setting the active engine is only a config change, it never pre-warms or loads a model.
  • UI slots are mutually exclusive (these slots only, among peers of the same slot): overlay_background, caption_view, audio_level, alert_capsule, overlay_decorations, settings_background, history_background, dialog_background, window_background, copilot_window.
  • A full theme (style_layer=full) and partial UI slot extensions can coexist — enabling a VU meter will not kick the user's theme offline.
  • settings_view takes part in no mutual exclusion at all: every plugin has its own settings form.
  • i18n full language packs are mutually exclusive (only one UI language at a time).
  • Display / presentation plugins can be enabled in multiples: glossary, middleware, tool, assistant.

6. Dependency gates (three places) ​

The host checks requires_plugins / requires_runtime at three gates:

MomentBehaviour
Startup scanDependency not in place (not installed / not enabled / version too low / runtime missing) → the plugin is force-disabled
Flipping the enable switchEnabling is refused + overlay notice (plugin name, what's missing, what to do)
Selecting it as the ASR/MT/TTS engineRefused (the same gate as enabling)
Starting the interpreterThe readiness check returns missing_dependency → blocked with a plain-language reason

The host does not silently install prerequisite plugins (plugins that contain code are never installed silently); the card offers "install / enable / update / download runtime" entry points that the user clicks.


TransBox — empowering borderless cross-language communication for everyone