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
priorityorder (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. interpretationis 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 lightweightfeed_direct_captionpath.
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:
| type | How it is mounted |
|---|---|
glossary / i18n / theme | Mounted as data (glossary / dictionary / style tokens), no Python import |
asr / mt / tts / middleware / assistant / interpretation / tool | importlib loads the Python class, instantiated on demand |
caption_bridge / caption_extractor | Wired 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=toolplugin is pre-loaded with an instance during the scan phase (because it has to register hotkeys). So atoolplugin'sinitialize()runs at startup — don't do heavy work in there.
3. Lifecycle
discover → parse mod.json → (on enable) instantiate + initialize(config) → use → disable shutdown()| Phase | What the host does | Your responsibility |
|---|---|---|
| Scan | Read mod.json, run dependency gates, restore the previous enabled state | Get the manifest right |
| Enable | Create the instance, call initialize(config) | Prepare only: parse config, check files; do not start services, do not load weights |
| Use | The host calls ensure_loaded() / engine interfaces / action_* on demand | The request entry point handles "make sure it's ready" itself |
| Config change | Persist + call update_config(delta) + broadcast config.changed | Hot-sync internal state |
| Disable / exit / release VRAM | Call shutdown() → unregister_plugin_hotkeys / close_plugin_windows / cancel event subscriptions | You must kill your own child processes |
| Unload | Reclaim libs/ vendor/ from sys.path | Don't leave state anywhere else |
An external (non-built-in) plugin's
initialize()runs on a background thread; before it is needed the host callswait_async_init()to wait for it to settle. So blocking for a few seconds insideinitializewill 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:
- Zero changes for plugin authors:
asr/mt/ttsplugins only follow the standard Provider interfaces and need no special logic for the two tracks. - The host routes intelligently: the pipeline dispatches by data flow direction (
source="peer"/"self") to the matching active engine. - 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. - 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(returnFalseif 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:
| type | on enable | on disable |
|---|---|---|
glossary | registers into the global glossary manager | unregisters |
i18n | switches to that language; mutually exclusive with other third-party language packs | falls back to zh-CN if it was current |
theme (full) | applies the theme; mutually exclusive with other full themes | reverts to the default theme |
| Same-slot UI extensions | occupy the slot and disable peers on it | removes the slot component |
asr / mt / tts | can be set as the active engine | falls back to another enabled engine if it was active |
interpretation | enters SI mode; bypasses standard ASR+MT+TTS | leaves SI and reverts to the standard pipeline |
middleware | mounts into the middleware manager | unmounts |
caption_bridge | drives the web caption service | drives the service off |
| Built-in default theme | cannot be disabled | — |
General rules:
- Enabling takes effect immediately / disabling reverts immediately.
- Same-kind engines are mutually exclusive:
asr/mt/tts/interpretationeach 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_viewtakes 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:
| Moment | Behaviour |
|---|---|
| Startup scan | Dependency not in place (not installed / not enabled / version too low / runtime missing) → the plugin is force-disabled |
| Flipping the enable switch | Enabling is refused + overlay notice (plugin name, what's missing, what to do) |
| Selecting it as the ASR/MT/TTS engine | Refused (the same gate as enabling) |
| Starting the interpreter | The 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.
