Skip to main content
VoiceGateway enforces consistent code style through automated tooling. This page documents the rules and conventions.

Tooling overview

Ruff

Ruff handles both linting and formatting. It is configured in pyproject.toml:

Running ruff

Import sorting

Ruff’s I rule handles import sorting (replacing isort). Imports are grouped in this order:
  1. Standard library (import os, from typing import ...)
  2. Third-party (import pytest, from fastapi import ...)
  3. Local (from voicegateway.core import ...)
Each group is separated by a blank line. Within a group, import statements come before from ... import.

mypy

Static type checking catches bugs before runtime. VoiceGateway’s mypy config:
Run mypy:
CI pins mypy below 2. mypy 2.x stops resolving the livekit namespace package’s subpackages and reports Module "livekit" has no attribute "api"/"rtc" on a tree that is otherwise clean, so install the same cap locally or your results will not match CI:

Type annotation guidelines

  • All public functions must have type annotations
  • Use from __future__ import annotations at the top of every module (enables PEP 604 X | Y syntax)
  • Use dict, list, tuple (lowercase) instead of Dict, List, Tuple from typing
  • Use X | None instead of Optional[X]
  • Use TYPE_CHECKING guards for import-only types to avoid circular imports:

Docstrings

Use Google-style docstrings for all public classes, methods, and functions:
Rules:
  • First line is a concise imperative summary (no period for one-liners)
  • Blank line between summary and Args/Returns/Raises sections
  • Args, Returns, Raises sections as needed
  • Private methods (_foo) may use shorter docstrings

Conventional Commits

All commit messages must follow Conventional Commits:

Types

Scopes

Use the module or area affected:
  • core, inference, middleware, services, repository, billing
  • dashboard, mcp, cli, server, fleet
  • config, docker
  • Provider names: openai, deepgram, etc.

Examples

Multi-scope commits

If a change spans multiple scopes, list the primary scope and mention others in the body:

File organization

  • One class per file for providers (openai_provider.py, not providers.py).
  • Group related functions in a module (middleware/cost_tracker.py).
  • Keep __init__.py files minimal — a docstring, re-exports of the subpackage’s public API, and an __all__ declaration. Nothing else.
  • Use from __future__ import annotations in every module.

Internal modules

Files whose names start with a leading underscore are internal implementation details and not part of the public import surface:
  • src/voicegateway/_version.py — hatch-vcs generated, do not edit.
  • src/voicegateway/tests/fixtures/streaming/_loader.py — private test helper.
A future ruff rule could enforce that nothing under src/voicegateway/ imports a leading-underscore module from a different subpackage; for now the convention is documentation-only.

Public API contract

Every package and subpackage __init__.py declares an explicit __all__ list. This is the public surface:
When __all__ is the empty list (__all__: list[str] = []), the subpackage exposes nothing at its top level and callers reach into submodules directly:
Names not in __all__, and any leading-underscore module, are internal. They may be renamed or removed in any minor release without a deprecation cycle.

Module-level patterns

The codebase converged on a small set of patterns. New code should follow them unless there is a concrete reason not to.

typing.Protocol vs ABC

Prefer typing.Protocol for structural typing where multiple implementations need to satisfy an interface without sharing helper code (see src/voicegateway/cli/daemon/base_daemon.py for a real example — the DaemonBackend Protocol is satisfied by MacOSBackend, LinuxBackend, and WindowsBackend without inheritance). Use an abstract base class only when the base genuinely supplies shared behaviour (src/voicegateway/inference/providers/base_provider.py’s BaseProvider is the canonical example: every concrete provider inherits the _unsupported() helper).

Pydantic for config

Anything parsed from YAML or environment variables is a Pydantic model. See src/voicegateway/core/config.py and src/voicegateway/schemas/config_schema.py for the project-wide config shape; the validators there are the single source of truth for what voicegw.yaml accepts.

Async throughout

Every I/O path uses async / await. Storage reads, provider calls, HTTP handlers, MCP tools — all async. Synchronous helpers exist only for pure data transformation (parsing, formatting). When in doubt, make it async; mixing sync and async boundaries is the most common source of subtle bugs in this codebase.

Exception handling

Catch specific exception types where possible. except Exception is acceptable at top-level boundaries (provider call sites, MCP tool dispatch, guard()’s fallback dispatch) where the catch is paired with structured logging and a controlled fallback. Avoid broad excepts in narrow code paths — they hide real bugs and bypass the type system. See Testing for fixtures, async patterns, and coverage expectations.

Naming conventions