Development
Everything runs through make, and almost everything runs inside a throwaway dev container, so the host needs only Docker, make and git. make help lists every target.
Layout
| Path | What |
|---|---|
| src/audiolla/ | The server: server.py (routes), mcp_server.py (MCP tools), engines/, audio.py, schema/ and the support modules described in architecture. |
| openapi.yaml | The REST API contract. |
| engines.json, engines-cpu.json | Engine registries for the CUDA and CPU images. |
| presets/ | Shipped preset workflows. |
| tests/ | Offline unit tests, image_smoke.py, and the Docker-based integration/ suite. |
| scripts/ | Model generation, dependency lock compilation and the exclude-newer bump. |
Dockerfile, Dockerfile.cuda, Dockerfile.dev | CPU image, CUDA image, and the light dev image the Makefile runs tools in. |
| docs/ | This site. |
Everyday commands
make dev-image # build the dev container (Python 3.12, uv, flake8, black, isort, mypy, pytest)
make shell # interactive shell in it
make lint # flake8 src, then mypy src
make format # isort src, then black src
make test # unit tests (same as make test-unit)
make check # lint, then test
The dev image has the web stack (FastAPI, uvicorn, pydantic, httpx, mcp) but none of the ML libraries. Unit tests replace engines with mocks and never download a model, so they run offline and without a GPU.
Tests
| Target | What it runs | Needs |
|---|---|---|
make test-unit | pytest tests/ --ignore=tests/integration with coverage | Docker |
make test-unit-cov-gate | The same, failing under 80% line coverage on audio, auth, files, config and engines.base | Docker |
make test-retention | File retention and config tests with a 90% coverage gate on file_retention | Docker |
make test-image | tests/image_smoke.py inside the built CPU image: real Torch tensors, DSP, jobs and cleanup. DEVICE=cuda runs it in the CUDA image. | make build first; a GPU for CUDA |
make test-integration | pytest tests/integration/ on the host. It builds the CPU image, starts one container with the engines the selected tests need, and calls the real API. | Python with pytest on the host, Docker, disk for models |
The integration suite reads these variables: HARNESS_GPU=1 for the CUDA tests, HARNESS_IMAGE to pick an image, HF_TOKEN or HUGGINGFACE_TOKEN for gated models, AUDIOLLA_ENABLE_NONCOMMERCIAL=1 for MusicGen, HARNESS_KEEP=1 to leave the container running and HARNESS_SKIP_BUILD=1 to skip the build. Select tests with pytest’s own options, for example pytest tests/integration/ -k generate or -m "not gpu". Model downloads are cached in .e2e-cache/.
Changing the API
The REST contract is openapi.yaml. Request and response models are generated from it, never written by hand:
- Edit the path and its schemas in
openapi.yaml, reusing shared components such asBaseAudioInputRequestandBaseAudioOutputRequestwith$ref. - Run
make generate. It runsscripts/generate_models.sh(datamodel-code-generator) in the dev container and rewritessrc/audiolla/schema/_generated.py. - Write the handler in
src/audiolla/server.py, taking the generated model fromaudiolla.schemaas its one body parameter. - Commit
openapi.yamland the regenerated_generated.pytogether.
src/audiolla/schema/__init__.py is hand-written: it re-exports the generated classes, turns type aliases the generator emits into real subclasses, and holds the either-or validators (validate_input_xor, validate_output_xor). The server serves openapi.yaml at /openapi.json, so the spec you edit is the spec clients see.
Dependencies
There are two dependency sets, both hash-locked:
- Light runtime dependencies (FastAPI, uvicorn, pydantic, httpx, mcp, mido, mutagen) are in
pyproject.tomlanduv.lock. The images install them withuv export --frozenanduv pip install --require-hashes, so a stale lock fails the build. - The ML and DSP stack (Torch, Demucs, matchering, pedalboard, librosa and the rest) is specified in
scripts/heavy-deps-cpu.inandscripts/heavy-deps-cuda.inand compiled torequirements-heavy-cpu.txtandrequirements-heavy-cuda.txt, because the Torch wheels differ per image.
make pkg-lock # refresh uv.lock at the current cutoff
make pkg-add PKG=name==1.2.3 # add a dependency
make pkg-update PKG=name # upgrade one dependency
make pkg-upgrade # upgrade everything
make pkg-remove PKG=name # remove a dependency
make pkg-compile-heavy # recompile both heavy locks, keeping versions
make pkg-compile-heavy VARIANT=cuda # recompile only the CUDA lock
pkg-add, pkg-remove, pkg-update and pkg-upgrade first move [tool.uv] exclude-newer in pyproject.toml to UTC midnight seven days ago, so no package version younger than a week gets in. The heavy locks use their own cutoff, HEAVY_EXCLUDE_NEWER in scripts/compile_heavy_deps.sh, and keep existing pins. The dev image pins its own copies of the web stack in Dockerfile.dev, separate from uv.lock.
Images
make build # CPU image: psyb0t/audiolla:local, :v<version>, :latest
make build-cuda # CUDA image: psyb0t/audiolla:local-cuda, :v<version>-cuda, :latest-cuda
make build-all # both
make version # print v<version> from pyproject.toml
Both build on Torchbase images pinned by digest; override with TORCHBASE_CPU_IMAGE or TORCHBASE_CUDA_IMAGE. Application source is copied after the dependencies, so editing Python code does not reinstall Torch. The Docker build context leaves out docs/, tests/, scripts/, .agents/, README.md, Makefile and Dockerfile.dev.
CI and releases
.github/workflows/pipeline.yml runs on every push:
- every branch:
make lint,make test, andmake generate, which fails the run if the committedsrc/audiolla/schema/_generated.pydoes not matchopenapi.yaml; main: also builds both images, scans them with Grype, and pushes:latest,:latest-cuda,:sha-<commit>and:sha-<commit>-cuda;- a
v*tag: waits for that commit’smainrun to pass, re-tags the same image digests asvX.Y.ZandvX.Y.Z-cudawithout rebuilding, creates the GitHub release, then publishes the skill and plugin to ClawHub andserver.jsonto the MCP Registry.
The version lives in pyproject.toml. The CHANGELOG states the versioning policy.
Docs
This site is built by GitHub Pages from docs/ on main. Build it locally with the same Jekyll image:
make docs # output in .testing/docs-site