← All docs

Install channels

Every command on this page installs release 0.11.2 and works today (pip inside a virtual environment, below). The binaries, the npm tarballs and the image archive live in https://airmet.ai/dl/v0.11.2/, and the install page shows the same commands for your platform.

Every channel ends in the same place: one static airmet binary in your infra and one base-URL change in your client. A release is one binary per platform: the pip wheel, the npm package and the bare download carry it byte for byte, the Docker image (built for linux/amd64 only) carries the Linux x86_64 one, and airmet version tells you which you have. The three setups (two pinned providers on one marketplace key, two provider keys, one provider raced against itself) work identically on all of them; the two-leg hedger.yaml shapes are on the landing's Start-here block. Each of those files names policy: twinleg at alpha: 0.3, which is what the banner below reports — and a file that names no policy gets the same default, so it reports the same.

pip

Run pip inside a virtual environment (python3 -m venv .venv, then activate it): on a stock Mac and on a Python that refuses system-wide installs, the command as typed fails outside one.

$ pip install --index-url https://airmet.ai/simple/ airmet
$ airmet up --config hedger.yaml        # a Start-here file: twinleg at alpha 0.3
airmet up · Go data plane · 2 leg(s) · twinleg α=0.3 · /v1 on 127.0.0.1:8080 · run dir airmet-results/up-…

pipx installs it in an isolated environment instead, in two commands:

$ pipx upgrade-shared
$ pipx install --index-url https://airmet.ai/simple/ airmet

From pipx 1.6 on, pipx's first install also fetches pipx's own copy of pip, from the index that install names. Our index carries only airmet, so on a pipx that has never run, the install alone fails with No matching distribution found for pip. pipx upgrade-shared fetches that copy from your default index first. An older pipx has no upgrade-shared and needs only the second command.

The --index-url in both install commands points pip at our index alone, so a same-named package on another index cannot win the resolution; the wheel has no dependencies to fetch from anywhere else.

The wheel is the binary plus a short shim that execs it: no Python engine, no runtime dependencies, Python ≥ 3.9 only to run the shim. The sidecar is a local proxy, so your app language is irrelevant — Node, Go, and Python clients all just change base_url.

airmet up takes one plane flag. --config hedger.yaml is the two-leg file for legs with their own keys, models or per-leg provider pinning: two providers with one leg each, or a pinned pair on one marketplace key (the OpenAI-compatible quickstart shows the file). --providers URL (alias --replicas) is one provider, raced against itself — the sidecar adds a twin leg on the same endpoint and model, with its own connections and session id — or a comma-separated list of engines you run yourself, with --model-name for the model id every leg receives and --api-key-env naming the key's environment variable (the key itself never appears in argv):

$ airmet up --providers https://api.a.example/v1 \
    --model-name <model> --api-key-env MY_KEY

Environment defaults exist for every flag you would put in a fleet config (AIRMET_REPLICAS, AIRMET_MODEL_NAME, AIRMET_HOST, AIRMET_PORT, AIRMET_POLICY, AIRMET_CONFIG, AIRMET_API_KEY_ENV).

The sidecar listens on 127.0.0.1, this machine only, because it attaches your provider keys to every request it relays: whoever can reach /v1 spends them. To serve other hosts, listen on every interface and require a token:

$ export AIRMET_TOKEN=$(openssl rand -hex 32)
$ airmet up --config hedger.yaml --host 0.0.0.0 --auth-token-env AIRMET_TOKEN

Clients then send Authorization: Bearer $AIRMET_TOKEN. In hedger.yaml the same two settings are proxy.listen: "0.0.0.0:8080" and proxy.auth_token_env: AIRMET_TOKEN. A bind beyond loopback without a token still starts, with a warning in the boot log.

With no --policy, the quick path twinlegs at α = 0.3: about 30% of requests race both legs — the provider and its twin, when there is one — and the slower leg is not cancelled for being slower: it runs to its own finish unless the runaway guard stops it for looping or running far past the winner, so its prompt cache stays warm (the cost is on the OpenAI-compatible quickstart). --hedge-n 1 serves one leg alone. A config file races the policy it names, and one that names none gets the same default.

One thing the quick path cannot do: prove that a cancelled leg is refunded. That matters once you name a policy that cancels the loser (--policy deferred-p95, immediate or tied): such a policy fires only on legs whose abort-billing verdict is a measured refund, and --providers legs carry no route identity to match a verdict against, so under it they are served without a race and the boot log says so, per leg. Use hedger.yaml with a route per leg to pick up the measured verdicts, or pass --on-no-refund allow to race regardless — the loud override for a pilot.

npm

$ npm install https://airmet.ai/dl/v0.11.2/airmet-cli-linux-x64-0.11.2.tgz https://airmet.ai/dl/v0.11.2/airmet-0.11.2.tgz
$ npx airmet up --config hedger.yaml

That is the Linux x86_64 command; the install page gives the command for your platform.

import OpenAI from "openai";
import { wrap } from "airmet";
const client = await wrap(new OpenAI(), { model: "gpt-4o-mini" });

The airmet package carries the same binary in a per-platform companion package (no postinstall download) and a wrap() helper that starts a sidecar on demand and points your OpenAI client at it. The Python wheel has the same helper: client = airmet.wrap(OpenAI(), model="…"). If the binary is missing or the sidecar cannot start, wrap() returns your client untouched and warns once — your requests go where they went before, so a failed start does not break your app.

From 0.11.1 the sidecar wrap() starts lives as long as your app. It has no idle timeout, it stops when the app exits or is killed, and each process that calls wrap() starts its own. On 0.11.0 it exits after 10 minutes without a request, and a client wrapped before then keeps the old address: its next request is refused. On 0.11.0, in a long-running app, wrap a fresh provider client again after a quiet spell (not the wrapped one, whose base URL is now the sidecar's), or pass a longer idle_exit_s (Python) or idleExitS (Node).

Binary

Download airmet-<platform> from https://airmet.ai/dl/v0.11.2/ (linux-amd64, linux-arm64, darwin-arm64, darwin-amd64, windows-amd64.exe), verify it against the SHA256SUMS in the same directory, and run it:

$ curl -fsSLO https://airmet.ai/dl/v0.11.2/airmet-linux-amd64 && curl -fsSLO https://airmet.ai/dl/v0.11.2/SHA256SUMS
$ grep " airmet-linux-amd64$" SHA256SUMS | sha256sum -c - && chmod +x airmet-linux-amd64
$ ./airmet-linux-amd64 version

That is Linux. The binary's own line goes to sha256sum on stdin because BusyBox's sha256sum (Alpine) has no --ignore-missing. A Mac checks with shasum -a 256 -c --ignore-missing SHA256SUMS. The install page shows the lines for each platform, Windows included. It is the same file the wheel bundles. The Windows build is published with every release but has not been tested on Windows yet.

Docker

$ curl -fsSL https://airmet.ai/dl/v0.11.2/airmet-image-v0.11.2.tar.gz | docker load
$ docker run -p 127.0.0.1:8080:8080 \
    -v airmet-data:/data \
    -v $PWD/hedger.yaml:/data/hedger.yaml \
    -e MARKETPLACE_API_KEY \
    airmet:v0.11.2 up --config /data/hedger.yaml

The first line fetches the image, which is built for linux/amd64.

The command above mounts a named volume on /data, then hedger.yaml into it. The install's own state lives there: the telemetry choice, the install id and a login in /data/config, and the local records under /data/airmet-results. Every container started on that volume is the same install, so a --no-telemetry given once stays in force and the first-run notice is shown once. Without a volume on /data, each new container starts as a first install: the notice again, a new install id, and telemetry on unless that run is given --no-telemetry too. Docker fills a new named volume from the image's /data, which the image's non-root user (uid 65532) owns, so it starts writable; in Compose, declare it under volumes: the same way. A host directory mounted on /data instead has to be writable by uid 65532. When it is not, airmet exits with an error that names the directory and the fix.

Inside the container the sidecar listens on every interface (the image sets AIRMET_HOST=0.0.0.0), so the port mapping decides who reaches it: -p 127.0.0.1:8080:8080 keeps it on this machine. Publishing it wider, with -p 8080:8080 or a Kubernetes Service, puts it on the network, so pass a token as well: -e AIRMET_TOKEN to docker run and --auth-token-env AIRMET_TOKEN to up.

Same binary, same flags, the config file mounted in, keys from the environment, same :8080/v1 surface (admin, /metrics, /healthz and /readyz are on the same port). The image is distroless — the binary, CA certificates and nothing else — and runs as a non-root user. /readyz answers 200 only while at least one leg is healthy, and the image's HEALTHCHECK uses it; point your orchestrator's readiness probe at it too, and no traffic is routed to a pod whose whole pool is down. Serving keeps no durable state, so run it as a plain Deployment next to your app: any replica serves any request, and restarts need no ordering. What a volume on /data keeps is the install's own state (the last footnote).

Three honest footnotes on what "no durable state" does not mean:

  • In-flight races are ephemeral in-process state. If a pod dies mid-race, the raced legs are cancelled by their sockets closing — providers that honor a disconnect stop billing there; those that don't will run those last replies to completion and bill them. A crash costs you at most the in-flight races, never correctness.
  • Adaptive policies re-learn after a restart unless the pod is linked to the served-priors feed or has a warm-state file under /data: a fresh pod races nothing until it has seen enough traffic (~100 requests) — a brief cold start, not an error. The abort-billing verdicts never go cold: they are baked into the binary.
  • /data holds the install's own state: the telemetry choice, the install id, the local records, the window log and the egress transcript (airmet traffic reads them). A pod with no volume there starts over each time it starts: it counts as a new install, and an opt-out has to be in its arguments (--no-telemetry), since no saved choice survives.

vLLM / SGLang — your own GPUs

Front the replicas you already run; the replicas race each other. You need at least 2 replicas — never race a single saturated pool. With one replica, the second leg lands on the same queue that made the first request slow, so you double the load and cut nothing. See the vLLM / SGLang quickstart.

LiteLLM as upstream

Keep LiteLLM for auth, routing, and spend tracking; race beneath it by routing the models you want raced through Airmet. See the LiteLLM quickstart.

n8n

No custom node: paste the sidecar URL into your OpenAI credential's Base URL field and every AI node in every workflow runs through the sidecar from the next run. See the n8n quickstart.

IDEs and coding agents

Cursor, Continue, Aider and the other tools that take an OpenAI base URL are one settings change away: point them at the sidecar and keep the model name it advertises. See the IDE quickstart.

MCP

Agents that reach models through MCP servers can race too: point the MCP server's model endpoint at the sidecar exactly as you would any OpenAI-compatible client — the sidecar is one URL, whatever sits in front of it.

No analytics until you say so. One first-party cookie remembers this choice; allowing adds EU-hosted usage analytics and error reporting (what we send); declining changes nothing about how the site works.