Skip to main content
The core layer connects configuration, storage, and middleware so the CLI, HTTP server, and MCP runtime share one source of truth. attach() and guard() do not go through this container: each builds its own lightweight CostTracker/RateLimiter per call. See Architecture Overview for how the two paths relate.

Gateway class

File: src/voicegateway/core/gateway.py Gateway is an internal container, not part of the public Python SDK (from voicegateway import attach, guard is the public surface). The CLI, HTTP server, and MCP runtime each construct one via core/gateway_factory.py’s process-wide singleton and own its lifecycle.

Initialization

Config file search order (when no path is given):
  1. VOICEGW_CONFIG environment variable.
  2. ./voicegw.yaml
  3. ~/.config/voicegateway/voicegw.yaml
  4. /etc/voicegateway/voicegw.yaml

Startup sequence

The database is only enabled when cost_tracking.enabled: true is set in YAML, or VOICEGW_DB_PATH / VOICEGW_DB_URL is set in the environment. When enabled, the path resolves as: VOICEGW_DB_PATH env > cost_tracking.db_path in YAML > default ~/.config/voicegateway/voicegw.db.

What Gateway exposes internally

Config refresh

After the dashboard or MCP server writes to a managed table, the caller reloads the Gateway’s merged config:

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 resolver finds them via GatewayConfig.get_provider_config_for_project. YAML always wins on conflict. See Configuration Layers for the full merge rules.

Registry

File: src/voicegateway/core/registry.py Maps provider names to their implementation classes in inference/providers/, via lazy import: no provider module is imported until create_provider() is called for it.
create_provider(name, config) imports the module and instantiates the class. If the import fails (the plugin wheel is missing), it raises an ImportError pointing at the upstream wheel, not a VoiceGateway extra:
create_provider() has exactly three callers in production: the /v1/providers/{id}/test and /v1/providers/test HTTP routes, voicegw doctor’s legacy key-validation check, and the MCP server’s provider-test tools. All three exist to run a provider’s health_check(), not to build an instance for inference. See Provider Abstraction for why the registry never sits on the attach()/guard() request path.
core/model_resolution.py’s resolve_model("provider/model") (parsing a string into a provider name and validating it against this registry) is not called from anywhere except its own test file. It is not part of the attach()/guard() path: those build provider/model from the plugin instance’s own attributes instead of parsing one. See Models and stacks for the format attach()/guard() actually produce.