Gateway Core
The core layer wires configuration, storage, and middleware together so thevoicegateway.inference factories and the operations endpoints (CLI, HTTP, MCP, dashboard) all share one source of truth.
Gateway Class
File:src/voicegateway/core/gateway.py
Gateway is an internal container; it is not part of the public Python SDK. The inference module holds a process-wide singleton via voicegateway.inference._factory.get_gateway(). The CLI, HTTP server, and MCP runtime each instantiate it directly because they own their own process lifecycle.
Initialization
VOICEGW_CONFIGenvironment variable../voicegw.yaml~/.config/voicegateway/voicegw.yaml/etc/voicegateway/voicegw.yaml
What happens at Gateway.__init__
The database path is resolved as: VOICEGW_DB_PATH env > cost_tracking.db_path in YAML > default ~/.config/voicegateway/voicegw.db.
What Gateway exposes
| Surface | Purpose |
|---|---|
gw.config | The merged GatewayConfig object (read-only). |
gw.storage | SQLiteStorage or None when cost tracking is disabled. |
gw.cost_tracker | The CostTracker middleware used by the inference wrappers. |
gw.costs(period, project=...) | Cost summary helper used by the CLI and HTTP API. |
gw.list_projects() | Project list for the CLI / dashboard / MCP. |
await gw.refresh_config() | Re-merges YAML and SQLite after a managed_* write. |
voicegateway.inference.STT/LLM/TTS,
which constructs the Gateway singleton internally and reads the same
merged config. There is no separate Gateway.stt() / llm() /
tts() method on the Gateway object.
Config Refresh
After the dashboard or MCP server writes to managed tables, the Gateway reloads its merged config:ConfigManager.load_merged() and rebuilds the BudgetEnforcer so it sees newly-added projects.
ConfigManager
File:src/voicegateway/core/config_manager.py
ConfigManager.load_merged() deep-copies the YAML config and layers in managed_providers, managed_models, and managed_projects rows from SQLite. Per-project provider rows (those with a non-null project column) merge into merged.projects[<id>].providers[<provider_type>] so the inference resolver finds them via GatewayConfig.get_provider_config_for_project. YAML always wins on conflict.
inference resolution
File:src/voicegateway/inference/_resolution.py
The inference factories parse "provider/model" strings inline and validate the provider against the registry. The variant suffix (language for STT, voice for TTS) is parsed in the modality-specific factory file (_stt.py, _tts.py) before resolution; LLM strings keep their trailing colon segments verbatim so Ollama tags survive.
| Exception | When |
|---|---|
ModelResolutionError | Empty string, missing slash, empty halves, or unknown provider name. |
voicegateway.core.registry.create_provider(provider_name, config) to instantiate the matching livekit.plugins.<provider> wrapper.
Registry
File:src/voicegateway/core/registry.py
The Registry maps provider names to their implementation classes via lazy import. No provider module is imported until it is actually needed.
create_provider(name, config) calls importlib.import_module() to load the module, then instantiates the class with the provider config dict. If the import fails (missing SDK), it raises an ImportError with an install hint: