Contributing#
The repository’s CONTRIBUTING.md is authoritative — this page mirrors it.
Thank you for your interest in contributing to mononet! This guide covers the development workflow.
License of contributions#
mononet is licensed under the Apache License 2.0. Contributions are
accepted inbound=outbound under section 5 of that license: unless you state
otherwise, any contribution you intentionally submit for inclusion is
licensed under Apache-2.0, with no additional terms. No CLA is required. See
NOTICE.md.
Development environments#
The repo ships four devcontainer flavors. Pick the one matching your hardware:
Flavor |
When to use |
|---|---|
|
CPU work: writing code, running unit tests, building docs. |
|
GPU benchmarks against the paper’s PyTorch baseline. |
|
GPU work with JAX (Flax NNX). |
|
GPU work with Keras 3 (backed by JAX with CUDA 12 by default). |
In VS Code, Ctrl/Cmd+Shift+P → Dev Containers: Reopen in Container,
then pick the flavor by name.
Outside devcontainers, you need Python ≥3.11, uv, and git.
The devcontainers also auto-provision your tooling on build: pre-commit hooks are installed, Claude Code plugins are set up, and this repo’s Claude sessions are shared with your host (see Claude Code). Working locally, you do those steps yourself.
The devcontainer
.venvis container-private. Each flavor mounts the project virtualenv (/workspaces/mononet/.venv) as its own named Docker volume, isolated from any.venvon your host — so a host-sideuvrun and the container never clobber each other’s environment (their interpreters live at different paths and can’t be shared). It persists across rebuilds; if it goes stale, reset it withdocker volume rm <compose-project>_mononet-venv(find the name viadocker volume ls | grep mononet-venv) and rebuild.
GPU flavors: host driver preflight#
The three gpu-* flavors pass --gpus=all, so they cannot start unless the
host NVIDIA driver is loaded and the NVIDIA Container Toolkit is installed.
Docker’s native failure for this is opaque:
error running prestart hook #0: exit status 1 …
nvidia-container-cli: initialization error: nvml error: driver not loaded
To turn that into something actionable, the GPU flavors run
.devcontainer/shared/gpu-preflight.sh
as their initializeCommand (on the host, before the container starts). It
checks for an NVIDIA GPU, a working nvidia-smi, and nvidia-container-cli,
and on failure prints the specific cause plus the fix.
The most common cause is a kernel upgrade without a matching NVIDIA module
build: the running kernel has no nvidia.ko, so NVML is dead. apt upgrade
holds the module package back whenever it needs a driver-version bump, which
makes this recur silently after routine updates. On Ubuntu:
sudo apt update
sudo apt install -y linux-modules-nvidia-<branch>-open-generic-hwe-24.04 nvidia-driver-<branch>-open
sudo modprobe nvidia_uvm && nvidia-smi # no reboot needed if no module is loaded yet
docker run --rm --gpus all ubuntu:24.04 nvidia-smi -L # verify the container path
Set MONONET_SKIP_GPU_PREFLIGHT=1 to bypass the check (e.g. a remote Docker
context whose GPUs aren’t visible from your machine).
Claude Code (plugins & sessions)#
The Claude Code plugins this repo uses are declared in
.devcontainer/claude-plugins.txt and
installed by .devcontainer/shared/provision-claude-plugins.sh (idempotent,
user scope). That one script is the source of truth for both environments:
In a devcontainer — nothing to do.
post-createruns the script (plugins) and installs the pre-commit hooks, and this repo’s session transcripts are bind-mounted to/from your host, so a conversation started on the host continues in the container and vice-versa. You log in inside the container (auth is not shared).Locally (host) — run it once:
bash .devcontainer/shared/provision-claude-plugins.sh # install the repo's Claude plugins
Your host
~/.claude(settings, auth, other plugins) stays otherwise independent from the container’s.
To add a plugin, append a <marketplace-source> <plugin@marketplace> line to
.devcontainer/claude-plugins.txt, then re-run the script (host) or rebuild the
container.
Host and container keep independent plugin/config state; only this repo’s sessions are shared. Avoid running Claude Code on the host and in the container for this project at the same time — concurrent writes can interleave the shared transcript.
Setup#
git clone https://github.com/davorrunje/mononet.git
cd mononet
uv sync # install runtime + dev + docs + lint + bench
uv run pre-commit install # install git hooks (devcontainers do this for you)
If you skip the hooks, run the checks manually before pushing (see Lint, format, static analysis).
Running tests#
uv run pytest # full suite (skips backends not installed)
uv run pytest tests/core # framework-agnostic tests only
uv run pytest tests/torch # PyTorch-only tests
uv run pytest tests/jax # JAX-only tests
uv run pytest tests/keras # Keras-only tests
uv run pytest tests/equivalence # cross-backend numerical equivalence
Set the active backend with MONONET_TEST_BACKEND={torch|jax|keras} when
running the equivalence suite to mirror what a single CI matrix cell
does.
Lint, format, static analysis#
uv run ruff check --exit-non-zero-on-fix # lint
uv run ruff format # format
uv run mypy # strict type check, this env's Python
./tools/typecheck-all.sh # strict type check, every supported Python
uv run bandit -c pyproject.toml -r mononet # security scan
uv run semgrep scan --config auto --error # semgrep
uv run pre-commit run --all-files # everything pre-commit runs
pre-commit is the full gate for everything above except the sweep: on
every commit it runs ruff, mypy (ambient), bandit, semgrep, plus the
docs build, codespell, secret detection, and file-hygiene hooks — so a
clean git commit means the change already passes almost everything CI
enforces. The exception is type checking across versions: the typecheck hook
checks only the interpreter in your environment, while CI runs mypy against
every version in requires-python (one status check each). Run
./tools/typecheck-all.sh before pushing if you touched anything
typing-sensitive. Each isolated leg installs the CPU backends (torch, jax,
keras) so those layers are actually type-checked instead of resolving to
Any — the first run downloads a few GB of wheels per Python version, and
each version takes roughly half a minute once its environment is warm. The
same checks run on demand whether or not the hooks are installed: uv run pre-commit run --all-files for the lot, or the individual commands above
piecemeal.
Building docs#
./tools/build-docs.sh # one-shot build
./tools/serve-docs.sh # live preview
Benchmark notebooks under docs/docs/benchmarks/ are committed with
their outputs and are not re-executed during a docs build. To
re-execute them before a release, see “Release process” below.
Release process#
The full maintainer runbook — one-time Trusted-Publishing setup, the Bump
Version action, TestPyPI rehearsal, and the GitHub-Release-triggered publish —
lives in docs/about/releasing.md.
Commit messages#
We use Conventional Commits:
feat:, fix:, chore:, docs:, refactor:, test:, ci:, build:.
Pull requests#
See PULL_REQUEST_GUIDE.md for repo-specific
PR conventions. New issues go to the project’s GitHub Issues tab.
Coding conventions#
Python 3.11+, line length 88 (ruff).
MyST field-list docstrings on all public functions and classes (
:param x: ...,:returns: ...,:raises X: ...). Types come from signature annotations, never:type:/:rtype:. See the spec for the canonical format.Strict mypy throughout. Type hints on every function and method.
Stdlib
dataclassesfor simple value objects; avoid adding new runtime dependencies without discussion.Tests use
pytest. Per-backend tests live undertests/<backend>/and usepytest.importorskip("<framework>")so they skip cleanly when the backend is not installed.
Reporting security issues#
See SECURITY.md.