Thank you for considering a contribution! PeakVox is a Universal Voice Runtime — a source-available, self-hostable, model-agnostic platform for speech generation. It grows through community contributions, and it is built in public.
Before you start, please read this guide, the Code of Conduct, the Governance model, and the Voice Usage Policy. By contributing, you agree your contributions are licensed under the project's LICENSE.
Quick links: Architecture overview · Runtime architecture · Constitution (invariants) · ADR index · Roadmap · Security Policy
- Orientation: read this first
- Ways to Contribute
- How architecture decisions are made (ADRs)
- Proposing a new runtime or model family
- How the Runtime Registry & Runtime Variants evolve
- Development Workflow
- Branch Strategy
- Commit Conventions
- Coding Standards
- Pull Request Requirements
- Code Review Expectations
- Issue Reporting
- Feature & Proposal Submissions
- Documentation Requirements
- Contributor License & Sign-off
PeakVox is not a frontend for one model. It is a runtime above many models. The single rule that governs everything: a voice belongs to PeakVox, not to a model. Two minutes of reading will save you a rejected PR:
- Vision — the north star (Universal Voice Runtime).
- Constitution — the invariants you must never violate.
- Product Principles — model-agnostic UI/API rules.
The invariants that most often catch new contributors:
- Address voices by
public_voice_id. It is a permanent public contract. Never assume "a voice's model." - Never surface model internals (embeddings, checkpoints, variant formats) in the UI, types, or public API. The public surface speaks only Voice + Model.
- Capabilities are declared, not inferred. Render model-specific controls only when the selected model declares the capability — never branch on a model id or name.
- Generation is
voice + model + text. Switching models must not change the voice or the integration shape. - Commercial surfaces (marketplace, creators, billing, auth) are Cloud-only and feature-flag gated — hidden in Community Edition.
- 🐛 Report bugs with clear reproduction steps.
- 🧪 Validate providers — help move a model from architecture-validated to provider-validated by running it end to end and reporting results.
- 🧩 Propose or add a runtime / model family — see the dedicated section.
- 🛠️ Submit code — fixes, features, performance and accessibility improvements.
- 📖 Improve documentation — guides, examples, clarifications.
- 🌍 Help with language coverage and runtime testing across hardware.
If you plan a large or architectural change, open an issue to discuss it first so we can align before you invest significant effort. Structural changes go through an ADR (below).
PeakVox is ADR-driven. Decisions that are expensive to reverse are recorded as
Architecture Decision Records in docs/.agents/DECISIONS/.
The authority chain is explicit:
ADRs define decisions. Architecture defines structure. Specs define intent. Code defines reality. Implementation status defines truth.
When your change alters structure or is costly to reverse:
- Open an issue describing the problem (not just your solution).
- Draft an ADR from the template:
docs/.agents/DECISIONS/adr-NNNN-short-title.md. Capture context, the decision, alternatives considered, and consequences. - Check it against the Constitution. If your proposal conflicts with an article, either the proposal changes, or the ADR must amend the article explicitly (naming it). Articles are never edited silently.
- Accepted ADRs are immutable. A changed decision is a new ADR that supersedes the old one. Never rewrite history; supersede it.
See GOVERNANCE.md for the full decision process.
Adding a model is a first-class, documented path — and it must never change the public API, Voice IDs, the Voice Library, or existing integrations. A new provider is *wiring a new adapter
- runtime*, not a redesign.
The path:
- Open a proposal issue. Describe the model: provider, license, capabilities (cloning, voice design, singing, streaming, languages), hardware requirements (GPU/CPU, VRAM), and how it maps to the Model Capability Contract.
- Integrate through the
ModelAdaptercontract. Nothing above the adapter line imports a model implementation. Read Runtime architecture and the existing adapters underbackend/app/.../model_adapters/for the shape. - Add a runtime descriptor under
runtime-registry/(see existingomnivoice-base/,f5-tts-base/,kokoro-82m/). Prefer a Runtime Variant when your model shares a runtime image with an existing one — don't duplicate a multi-GB image to change a checkpoint (ADR-0018). - Declare capabilities, never infer them. The capability contract is how the UI and API decide what controls to show. Do not hard-code per-model conditionals anywhere.
- Validate honestly. Provide an architecture-validation (tests) and, where you have the hardware, a provider-validation report (real audio, end to end). See VALIDATION/ for the report format and the 8-gate process.
- Respect edition scoping & licensing. A model's editions and license are declared properties, not code branches (ADR-0005). Record attributions in NOTICE.
Community-imported runtime variants carry trust/provenance metadata (ADR-0019); imports are validated before they are trusted. See the security notes in SECURITY.md.
- The Runtime Registry installs runtimes (environment + service contract), not bespoke per-model builds. Governed by ADR-0016 and ADR-0017.
- A Runtime Variant reuses one runtime image for many model variations (different weights, config, tokenizer) — governed by ADR-0018.
- Registry shape changes (descriptor schema, trust tiers, lifecycle) are additive and migration-planned. Breaking changes (e.g. an id cutover) are release-gated and documented before they land.
When you touch the registry, include the migration step and keep existing runtimes working.
- Fork the repository and clone your fork.
- Create a feature branch from
main(see Branch Strategy). - Use development mode (hot reload, no image rebuilds):
Editing
scripts/start-dev.sh # backend + MinIO in Docker (uvicorn --reload); frontend on host scripts/start-dev.sh --build # only when backend deps/Dockerfile changed
backend/app/reloads the API in seconds; editingfrontend/src/hot-reloads the UI. Do not rundocker compose buildper change — that is the expensive path this workflow replaces. Runtime images (runtime-registry/*/server.py) are intentionally immutable in dev; rebuild a specific runtime from the Models page (Remove + Install). - Make your change, with tests and documentation where applicable.
- Run the local checks (see Coding Standards).
- Commit using Conventional Commits.
- Push and open a Pull Request against
main.
main— always releasable. Protected; no direct pushes.- Feature branches —
feat/<short-description> - Fix branches —
fix/<short-description> - Docs branches —
docs/<short-description> - Refactor branches —
refactor/<short-description>
Keep branches focused and short-lived. Rebase on the latest main before requesting review.
We follow Conventional Commits. This keeps history readable and powers the CHANGELOG.
<type>(<scope>): <subject>
Types: feat, fix, docs, refactor, perf, test, build, ci, chore, revert.
Examples (note the model-agnostic scopes):
feat(runtime-registry): add Kokoro CPU runtime descriptor
feat(runtime-variants): support fine-tuned checkpoint variants on a shared image
fix(runtime): resolve VoiceVariant rebuild on stale artifact
docs(decisions): add ADR-0020 for <decision>
refactor(adapters): tighten the ModelAdapter capability surface
- Use the imperative mood ("add", not "added").
- Keep the subject ≤ 72 characters.
- Reference issues in the body or footer (
Closes #123). - Breaking changes: add a
!(feat(api)!: ...) and aBREAKING CHANGE:footer. The/api/v1contract andpublic_voice_idare stable — breaking them requires a new version and a deprecation policy (Constitution, Art. VIII).
- TypeScript, Next.js 15 App Router, React 19, Tailwind, shadcn/ui.
- Read the Next.js docs in
node_modules/next/dist/docs/before Next.js work — they are the source of truth over training data (seefrontend/AGENTS.md). - Stay model-agnostic. Address voices by
public_voice_id; never surface model internals; render capability-driven controls only when the active model declares the capability. - Centralize controlled vocabularies/config — don't hardcode lists in components.
- Keep server cache in TanStack Query and UI/session state in Zustand; don't duplicate server state.
- Run before pushing:
cd frontend npm run lint # must pass, 0 errors npm run build # must succeed; type-checks the project
- FastAPI, async SQLAlchemy 2, Pydantic 2, Python 3.11+.
- All generation routes through
PeakVoxRuntime. Nothing bypasses the runtime; nothing above the adapter line imports a model implementation. - Models integrate only through
ModelAdapter. Capabilities are read fromModelCapabilities, never inferred from a model id/name. - Keep request/response shapes in
schemas/. Honor the fire-and-forget job model — long work runs in async tasks and never blocks the request. - Migrations are additive and idempotent (the SQLite-safe runner in
app/core/migrations.py— not Alembic in CE). Add nullable columns and backfill; never destructive changes (Constitution, Art. VI). - Derive all paths from
DATA_DIR/config; never hardcode absolute paths.
- Match the surrounding code's style, naming, and comment density.
- Small, focused PRs over large mixed ones.
- No secrets, credentials, or large binaries (including generated audio or model weights) in commits.
A PR is ready for review when:
- It targets
mainand is rebased on the latestmain. - Scope is focused and the title follows Conventional Commits.
-
npm run lintandnpm run buildpass (frontend changes). - The backend starts and affected endpoints work; tests pass (backend changes).
- It upholds the Constitution — model-agnostic, runtime routes generation, no model internals leaked, capabilities declared.
- Structural changes reference the relevant ADR (or add a new one).
- Documentation is updated (see Documentation Requirements).
- CHANGELOG.md
Unreleasedsection is updated for user-facing changes. - No secrets, generated audio, model weights, or large binaries are committed.
- The change complies with the Voice Usage Policy (no features designed to enable impersonation, consent circumvention, or other prohibited uses).
Fill in the PR description: what changed, why, how to test, and screenshots/recordings for UI changes.
- Maintainers aim to respond within a reasonable timeframe; please be patient.
- Reviews focus on correctness, scope, security, accessibility, performance, model-agnostic integrity, and consistency with the architecture.
- Address feedback with follow-up commits; avoid force-pushing mid-review unless asked (rebasing before merge is fine).
- Approval from a maintainer and green checks are required before merge.
- Be respectful and assume good intent — as author and reviewer. See the Code of Conduct.
Open a Bug Report with:
- A clear title and description.
- Steps to reproduce, expected vs. actual behavior.
- Environment: OS, GPU/CPU and VRAM, Docker vs. local, browser, and which runtime/model was active.
- Relevant logs (
docker compose logs backend), screenshots, and the runtime/job status. - Whether it reproduces on a clean
.env.
Security vulnerabilities must NOT be filed as public issues — follow SECURITY.md.
Open a Feature Request or Proposal describing:
- The problem/use case (not just the solution).
- How it fits the Roadmap and the Community-vs-Cloud split.
- Whether it touches structure (if so, it needs an ADR).
- For a new runtime/model family, follow Proposing a new runtime or model family.
Maintainers may label requests by roadmap horizon or decline those out of scope for the Community Edition.
Documentation is part of "done" — a change is not complete until the affected docs are updated (Constitution, Art. VII §24):
- User-facing changes → update the README and relevant docs.
- Architectural changes → add/update an ADR and the relevant architecture suite doc (including Mermaid diagrams).
- New runtimes/variants → update the
runtime-registry/descriptors and the registry docs. - New env vars → update
.env.exampleand the README env table. - Behavior users will notice → add an entry under
Unreleasedin CHANGELOG.md.
- By submitting a contribution, you agree it is licensed under the project's LICENSE (PeakVox Community License, based on the Elastic License 2.0), and that you have the right to contribute it.
- Ensure any third-party code or model you add is license-compatible and recorded in NOTICE.
- We use a Developer Certificate of Origin model — sign off your commits:
git commit -s -m "feat(runtime-registry): ..."
Thank you for helping build PeakVox — a universal, model-agnostic runtime for voice. 🎙️
Copyright © 2026 Bruno Silva and the PeakVox contributors.