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
| Method | Path | Auth | Summary |
|---|---|---|---|
| POST | /admin/models/{model_id}/adapters | Bearer | Drain in-flight requests and swap to adapter_id. |
| GET | /admin/models/{model_id}/active | Bearer | Return the active adapter id (or null). |
| POST | /admin/models/load | Bearer | Load model_id into the engine; idempotent. |
| GET | /admin/models/loaded | Bearer | List every model currently registered. |
| DELETE | /admin/models/{model_id} | Bearer | Unload model_id and drop its replica. |
POST /admin/models/{model_id}/adapters
Body ActivateAdapterRequest:
{ "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:
{
"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 ofadapter_id.409— drain timeout (in-flight requests did not complete).422— eval gate blocked the activation (see Evals).500—engine.loadfailed; 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/AdapterRegistrynot initialised onapp.state.
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/adaptersGET /admin/models/{model_id}/active
Returns {"model_id": "...", "adapter_id": "adp_..." | null}.
POST /admin/models/load
Body LoadModelRequest: {"model_id": "..."}.
Returns LoadedModelResponse:
{
"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.
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/loadGET /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_MODELand every entry inBIGWAVE_FALLBACK_MODELSload 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
422with the gate's failure detail. - Audit.
POST /{model_id}/adapterswritesadapter.activateaudit rows. See Audit. - Related runbook.feedback-loop.md walks the end-to-end chat → feedback → DPO → train → activate cycle.