Tooling overview
Ruff
Ruff handles both linting and formatting. It is configured inpyproject.toml:
Running ruff
Import sorting
Ruff’sI rule handles import sorting (replacing isort). Imports are grouped in this order:
- Standard library (
import os,from typing import ...) - Third-party (
import pytest,from fastapi import ...) - Local (
from voicegateway.core import ...)
import statements come before from ... import.
mypy
Static type checking catches bugs before runtime. VoiceGateway’s mypy config: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 annotationsat the top of every module (enables PEP 604X | Ysyntax) - Use
dict,list,tuple(lowercase) instead ofDict,List,Tuplefromtyping - Use
X | Noneinstead ofOptional[X] - Use
TYPE_CHECKINGguards for import-only types to avoid circular imports:
Docstrings
Use Google-style docstrings for all public classes, methods, and functions:- First line is a concise imperative summary (no period for one-liners)
- Blank line between summary and
Args/Returns/Raisessections Args,Returns,Raisessections 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,billingdashboard,mcp,cli,server,fleetconfig,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, notproviders.py). - Group related functions in a module (
middleware/cost_tracker.py). - Keep
__init__.pyfiles minimal — a docstring, re-exports of the subpackage’s public API, and an__all__declaration. Nothing else. - Use
from __future__ import annotationsin 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.
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:
__all__ is the empty list (__all__: list[str] = []), the subpackage exposes nothing at its top level and callers reach into submodules directly:
__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. Seesrc/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 usesasync / 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.