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
410 or the replacementStage 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
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
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
autostops picking it. - 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 the410.
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
| Status | Requests | /v1/models, /model/info | auto |
|---|---|---|---|
| Staged | Served as usual | Left out; GET /v1/models/{id} is 404 | Never picks it |
| Active | Served as usual | Listed | May pick it |
| Deprecated | Served, with lifecycle headers | Listed with status, and replacement, retire_at and note when set | Never picks it |
| Retired | 410, or the replacement answers | Left out; GET /v1/models/{id} still answers, so a caller can find the replacement | Never 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
replacementmust 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.redirectneeds a replacement.activeandstagedclear every other field.changed_atis set by the gateway and moves only when the status changes.- The model's view has
lifecycle, as stored, andeffective_status, the status in force now. - Backups carry the lifecycle. A backup taken before v2.1.0 restores every model as active.
