OBLETHdocs

Model lifecycle

Try a new model before callers can find it, then take an old one out of service with a date and a replacement, so nobody is cut off without warning.

3 min readUpdated for v2.4.0Edit on GitHub

Set itThe model's page › Lifecycle
StatusesStaged, active, deprecated, retired
Callers seeDeprecation headers, then a 410 or the replacement

Stage a new model

Add model has a Start staged switch, on by default, and a model launched from a recipe starts staged too. A staged model answers anyone who sends its name, but nothing advertises it: it is left out of /v1/models and /model/info, GET /v1/models/<name> returns 404, and auto never picks it. Health checks and capacity discovery run as usual, so you can watch it work, and the Playground can call it.

When it is ready, open the model's page, set Lifecycle › Status to Active and choose Save lifecycle. Before a staged model goes live, the dashboard asks you to confirm and says whether auto may start sending it requests.

Retire a model

  1. 1

    Deprecate it

    On the model's page, set Lifecycle › Status to Deprecated. Pick the Replacement (a model of the same type that is still served and listed), the Retire on date (midnight UTC that day) and, if you want, a Note to callers of up to 500 characters, such as where to ask. What callers see previews the headers or the error, and Who calls it lists the keys that used the model in the last 30 days, linked to their requests.

  2. 2

    Save it

    Choose Save lifecycle. The section saves on its own, not with the save bar, and applies within a few seconds. From then on the model is still served, with headers saying it is going away, and auto stops picking it.

  3. 3

    Let it retire

    On the retirement date the model retires itself: requests naming it are refused with 410 Gone. To retire it at once, set Retired instead; the dashboard asks first. With Once retired › Answer with the replacement on, the replacement answers instead of the 410.

Or through the Management API:

curl -X PUT http://localhost:9180/api/v1/models/$MODEL_ID/status \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "deprecated",
    "replacement": "new-model",
    "retire_at": "2026-12-01T00:00:00Z",
    "note": "Questions? Ask the platform team.",
    "redirect": false
  }'

The body sets the whole lifecycle: status (staged, active, deprecated or retired), replacement, retire_at (RFC 3339), note and redirect. POST /api/v1/models takes "status": "staged" to create a model staged; a new model can't start deprecated or retired.

What callers see

StatusRequests/v1/models, /model/infoauto
StagedServed as usualLeft out; GET /v1/models/{id} is 404Never picks it
ActiveServed as usualListedMay pick it
DeprecatedServed, with lifecycle headersListed with status, and replacement, retire_at and note when setNever picks it
Retired410, or the replacement answersLeft out; GET /v1/models/{id} still answers, so a caller can find the replacementNever picks it

Every response from a deprecated model carries:

x-obleth-model-status: deprecated
Deprecation: @1790812800
Sunset: Tue, 01 Dec 2026 00:00:00 GMT
Link: </v1/models/new-model>; rel="successor-version"
x-obleth-model-replacement: new-model

Deprecation (RFC 9745) is when the model was deprecated, Sunset (RFC 8594) is sent once a retirement date is set, and Link and x-obleth-model-replacement once a replacement is named. Client libraries and proxies that know the standard headers can warn without obleth-specific code.

A request to a retired model gets the same headers and this body:

{
  "error": {
    "message": "The model `old-model` was retired on 2026-12-01. Use `new-model` instead. Questions? Ask the platform team.",
    "type": "invalid_request_error",
    "param": "model",
    "code": "model_retired",
    "replacement": "new-model",
    "retired_at": "2026-12-01T00:00:00+00:00"
  }
}

Refusals are written to the usage ledger with status 410, so Request logs shows who still calls a retired model.

With redirect on, the replacement answers and the response adds x-obleth-redirected-from: <retired model>. The replacement must be on, of the same type, and active or deprecated, so a redirect never chains; if it can't stand in, the request gets the 410.

Where the check runs

The lifecycle is checked on every request that names a model, verdicts and searches included, after an alias or variant is resolved and before the tenant's allowlist and admission. A variant shares its model's lifecycle. A deprecated model whose retirement date has passed counts as retired without another write.

Scheduled and bulk health checks and capacity discovery skip retired models, so a backend you have scaled down raises no alerts. The tenant portal's Models page leaves out staged and retired models, and the Models list marks the others with a badge.

Rules for a status write
  • replacement must be a registered model of the same type that is neither retired nor staged, and not the model itself. An alias is stored as the model's name.
  • redirect needs a replacement.
  • active and staged clear every other field.
  • changed_at is set by the gateway and moves only when the status changes.
  • The model's view has lifecycle, as stored, and effective_status, the status in force now.
  • Backups carry the lifecycle. A backup taken before v2.1.0 restores every model as active.