Files
VoiceStudio/backend/runtime_adapter

VoiceStudio runtime adapter

A local gRPC server implementing the vssaas GPU-node runtime contract voicestudio.runtime.v1.RuntimeAdapterService, so a vssaas GPU Gateway can drive this VoiceStudio backend as its inference runtime.

Boundary (deliberate non-capabilities)

  • Binds only a Unix-domain socket (default /run/voicestudio/runtime.sock, override with VOICE_STUDIO_RUNTIME_SOCKET). No HTTP listener, no TCP.
  • Never reaches PostgreSQL, customer credentials, or arbitrary network URLs. Execute accepts local file handles only — absolute paths generated by the Gateway; any URL-shaped or relative handle is rejected as invalid input.
  • The Gateway owns leases, artifact transfer, retries, and billing. This adapter owns approved model loading and inference only.

Running

# serve (production socket):
VOICE_STUDIO_RUNTIME_SOCKET=/run/voicestudio/runtime.sock \
  python -m backend.runtime_adapter

# self-check: starts the server on a private temp socket and validates the
# same expectations the Go preflight (cmd/runtime-adapter-preflight) enforces:
python -m backend.runtime_adapter --selfcheck

Environment:

Variable Default Meaning
VOICE_STUDIO_RUNTIME_SOCKET /run/voicestudio/runtime.sock Unix socket path (must be absolute; parent dir must exist and not be world-writable).
VOICE_STUDIO_RUNTIME_SLOTS 1 Concurrent execution slots per device.

Wire contract and generated stubs

runtime_adapter.proto is a byte-identical vendored copy of the vssaas contract api/proto/voicestudio/runtime/v1/runtime_adapter.proto. Do not edit it here; re-vendor from vssaas when the contract changes, then regenerate.

The gen/ stubs are committed (same policy as backend/worker/protocol/gen/). Regenerate with:

uv run python scripts/gen_runtime_adapter_protocol.py

tests/test_runtime_adapter_gen.py fails if the committed stubs drift from the proto.

Preflight expectations honoured

The Go preflight (internal/gateway/preflight.go) fails closed unless:

  • the socket path is absolute, a real Unix socket (not a symlink), and its parent directory is not world-writable — server.prepare_socket enforces the same rules at bind time;
  • Health returns SERVING_STATE_READY with nonempty runtime + adapter versions, and GetCapabilities returns identical versions — both handlers read the same constants, so they cannot disagree;
  • at least one device with nonempty id/hardware class, nonzero VRAM and slots, free_slots <= total_slots, unique ids;
  • at least one model explicitly READY with catalog_model_id, model_version, model_digest, and ≥1 precision. A loading, installed, or failed model is reported with its true state and never as READY.

Model identity

  • catalog_model_id — the VoiceStudio TTS engine id (omnivoice, voxcpm2, …) from services.tts_backend's registry.
  • model_version — an immutable catalog version comprising the installed Hugging Face revision (40-char commit SHA) and the first 16 hex characters of the attested snapshot digest. This creates a new catalog identity when snapshot bytes change; it never rewrites an identity retained by a Job.
  • model_digest — sha256:<hex> computed over the installed snapshot files (sorted relative path + per-file SHA-256), cached next to the repo cache keyed by (revision, file list, sizes, mtimes) so multi-GB weights are hashed once. See digest.py.

Failure taxonomy

Stable codes (prefix RTA_) map onto the proto's RuntimeFailureClass: invalid input (RTA_INPUT_*), model load (RTA_MODEL_LOAD_FAILED), inference (RTA_INFERENCE_*), GPU resource (RTA_GPU_*), local storage (RTA_STORAGE_*), cancellation (terminal ExecutionCanceled), and adapter crash (RTA_RUNTIME_CRASH). See codes.py.

Tests

uv run pytest backend/tests/test_runtime_adapter_capabilities.py \
              backend/tests/test_runtime_adapter_execute.py \
              tests/test_runtime_adapter_gen.py