> ## Documentation Index
> Fetch the complete documentation index at: https://docs.airmux.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Development

> Set up airmux, make and verify a change, open a pull request, and publish a release.

## Clone and install

Install Python 3.13+, [uv](https://docs.astral.sh/uv/), Bun, and Docker with Compose. The development stack uses ports 5432, 8000, 8080, and 5000; stop any service already using them. Clone the repository and install its dependencies:

```bash theme={null}
git clone https://github.com/michel-tricot/airmux.git
cd airmux
uv sync --all-packages --frozen
bun install --frozen-lockfile
cp .env.example .env
docker compose -f docker-compose.dev.yml up -d --wait
```

## Start the development environment

Load the development catalog:

```bash theme={null}
uv run airmux control-plane taxonomy --file taxonomy/taxonomy.yml
```

Start each service in its own terminal:

| Service       | Command                                                 | Address                 |
| ------------- | ------------------------------------------------------- | ----------------------- |
| Control plane | `uv run airmux control-plane serve --dev`               | `http://127.0.0.1:8000` |
| Gateway       | `uv run airmux gateway serve --config airmux.yml --dev` | `http://127.0.0.1:8080` |
| Webapp        | `bun run dev`                                           | `http://127.0.0.1:5000` |

Finish local setup through the webapp origin:

```bash theme={null}
uv run --package airmux airmux quickstart --url http://127.0.0.1:5000
```

The development control plane applies migrations and creates `.airmux/dataplane.key` when needed. Keep that key with its local database.

## Develop and verify

Create a branch for your change. Add a behavior test, make the change, and run the checks for the affected area. For a full local check:

```bash theme={null}
uv run ruff format .
uv run ruff check .
uv run ty check .
uv run lint-imports
uv run pytest -n auto
bun run format:check
bun run lint
bun run typecheck
```

Control-plane unit tests can run without Docker using `uv run pytest apps/control-plane/tests/unit`. Integration tests use the Postgres service started above. For inference request-path changes, run `uv run pytest tests/acceptance/full_stack/scenarios` and verify a real request through the local gateway.

## Contribute a change

Before editing, read [CONTRIBUTING.md](https://github.com/michel-tricot/airmux/blob/main/CONTRIBUTING.md), `AGENTS.md`, and any relevant design record. Update generated contracts when their source changes, and commit generated outputs with the change.

Open a pull request against `main` with a concise summary of user impact and the checks you ran. Draft and ready pull requests run the same fast checks. Main CI runs the broader checks after merge. Address review feedback and merge after the required CI and security checks pass.

## Publish a release

The root `VERSION` file is the public package version. Publish Release requires the full SHA of the version pull request's merge commit on `main`. It checks Main CI and Security for that exact commit. The other workspace projects stay at `0.0.0`.

### Checklist

1. Run **Actions → Prepare Release** on `main`, then merge its pull request
2. Wait for **Main CI** and **Security** to pass
3. Run **Actions → Publish Release** on `main` with the version PR's merge commit SHA; wait for it to finish
4. Check the GitHub release, PyPI package, and GHCR version and `latest` tags

Prepare Release opens a pull request that changes `VERSION`. Publish Release confirms that the supplied commit changed `VERSION` and has not already been tagged. It uses the validated Python and container artifacts from that commit's successful Main CI run; it does not rebuild them. It checks installation and live providers before creating the tag, then publishes and verifies the package and image. The `release` environment supplies provider credentials and `RELEASE_GITHUB_TOKEN`; PyPI uses its trusted publisher for `release.yml`.

### If a release fails

| Failed stage                                      | Action                                                                                                                                                                                                                           |
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Main CI or Security                               | Fix the failure on a PR. If the release source changed, prepare a new version, wait for its checks, then dispatch **Publish Release**                                                                                            |
| `prepare`                                         | Confirm the full SHA is the version pull request's merge commit on `main`, the version tag is unused, and both main checks passed; then dispatch **Publish Release** again                                                       |
| `installation` or `live-providers` before the tag | Retry transient failures. If the release source must change, fix it on a PR and prepare a new version before dispatching again                                                                                                   |
| After the tag or during publication               | Rerun failed jobs on the original **Publish Release** run while its Main CI artifacts are available. Keep the tag on its original commit                                                                                         |
| After PyPI or GHCR publication                    | Inspect the published version and digest, then rerun failed verification or announcement jobs on the original run. If the source must change, prepare a new version; published package bytes and release tags cannot be replaced |

### Install a local build

Build and install an unreleased checkout, then run its portable installation smoke test outside the repository:

```bash theme={null}
(
set -e
./scripts/build-python-distribution.sh
uv tool install --reinstall dist/airmux-*.whl
smoke_dir="$(mktemp -d)"
export UV_TOOL_DIR="$smoke_dir/tools"
export UV_TOOL_BIN_DIR="$smoke_dir/bin"
uv tool install --python 3.13 dist/airmux-*.whl
uv venv --python 3.13 "$smoke_dir/runner"
uv pip install --python "$smoke_dir/runner/bin/python" pytest httpx pyyaml
mkdir -p "$smoke_dir/tests/installation/portable" "$smoke_dir/tests/acceptance"
cp tests/installation/portable/*.py "$smoke_dir/tests/installation/portable/"
cp tests/installation/installation_support.py "$smoke_dir/tests/installation/"
cp tests/acceptance/process_harness.py "$smoke_dir/tests/acceptance/"
cd "$smoke_dir"
AIRMUX_INSTALL_BIN="$UV_TOOL_BIN_DIR/airmux" AIRMUX_INSTALL_PYTHON=3.13 \
  "$smoke_dir/runner/bin/python" -I -m pytest -o pythonpath=. tests/installation/portable
)
```

The `airmux` command is then available outside the repository.
