Skip to content

Models ​

Operator surface for the running ReplicaManager — load and unload models against the live serving engine, list what's currently resident, and trigger drain-and-swap LoRA adapter activations. This is the wider sibling of Adapters: the adapter-registry routes manage the catalog of trained adapters, these routes manage the running replicas.

The router lives at packages/serving/admin_routes.py. Wire schemas: ActivateAdapterRequest, ActivateAdapterResponse, LoadModelRequest, LoadedModelResponse.

Endpoints ​

MethodPathAuthSummary
POST/admin/models/{model_id}/adaptersBearerDrain in-flight requests and swap to adapter_id.
GET/admin/models/{model_id}/activeBearerReturn the active adapter id (or null).
POST/admin/models/loadBearerLoad model_id into the engine; idempotent.
GET/admin/models/loadedBearerList every model currently registered.
DELETE/admin/models/{model_id}BearerUnload model_id and drop its replica.

POST /admin/models/{model_id}/adapters ​

Body ActivateAdapterRequest:

json
{ "adapter_id": "adp_01HF…" }

Drains in-flight requests on the named replica and swaps to the target adapter (PRD 04 user story #1). Returns ActivateAdapterResponse:

json
{
  "model_id": "llama3.2:3b",
  "adapter_id": "adp_01HF…",
  "old_handle_id": "rep_old…",
  "new_handle_id": "rep_new…",
  "swap_ms": 412.3
}

Errors:

  • 404 — registry has no record of adapter_id.
  • 409 — drain timeout (in-flight requests did not complete).
  • 422 — eval gate blocked the activation (see Evals).
  • 500 — engine.load failed; the OLD handle remains live (rollback). The response carries an opaque correlation id; the underlying error is in the API process logs only.
  • 503 — ReplicaManager / AdapterRegistry not initialised on app.state.
bash
curl -X POST -H "Authorization: Bearer $BIGWAVE_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"adapter_id":"adp_01HF..."}' \
  http://localhost:8000/admin/models/llama3.2:3b/adapters

GET /admin/models/{model_id}/active ​

Returns {"model_id": "...", "adapter_id": "adp_..." | null}.

POST /admin/models/load ​

Body LoadModelRequest: {"model_id": "..."}.

Returns LoadedModelResponse:

json
{
  "model_id": "qwen2.5:7b",
  "replica_id": "rep_…",
  "active_adapter": null
}

Idempotent — re-loading an already-loaded model returns the existing replica without a second engine round-trip. Errors: 400 on empty model_id, 500 on engine.load failure (model id unknown to backend, OOM, …), 503 when ReplicaManager is not wired.

The bw model load <id> CLI wraps this route.

bash
curl -X POST -H "Authorization: Bearer $BIGWAVE_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"model_id":"qwen2.5:7b"}' \
  http://localhost:8000/admin/models/load

GET /admin/models/loaded ​

Returns a JSON array of LoadedModelResponse, one per running replica. This is the admin-shaped sibling of /v1/models — it reports replica id and active adapter, not the OpenAI catalog shape. The bw model loaded CLI wraps this route.

DELETE /admin/models/{model_id} ​

Unloads the named model and drops its replica from the manager. Returns 404 when the model is not currently loaded; 500 when engine.unload fails (the deregister still happened — the response carries engine_unload_failed so the operator can clean up out-of-band). The bw model unload <id> CLI wraps this route.

Operator notes ​

  • Workspace fence. These routes are operator-level — replica state is process-wide, not per-tenant. The bearer token is the fence; cross-tenant chat traffic is scoped via the principal at the /v1/* layer, not here. See workspace fence.
  • Lifespan vs runtime load. BIGWAVE_MODEL and every entry in BIGWAVE_FALLBACK_MODELS load at lifespan startup; this route exists for runtime additions without a process restart.
  • Adapter activation safety. The drain-and-swap protects in-flight requests but does not freeze the queue — new requests arriving during drain land on the OLD handle until the swap completes, then on the NEW handle. Callers therefore see the before-OR-after adapter, never an interleaved one.
  • Eval gate. Activation can be gated on a passing eval — see eval-as-a-gate. A blocked activation returns 422 with the gate's failure detail.
  • Audit. POST /{model_id}/adapters writes adapter.activate audit rows. See Audit.
  • Related runbook.feedback-loop.md walks the end-to-end chat → feedback → DPO → train → activate cycle.

Dual-licensed: MIT OR Apache-2.0