Model List Must Be Derived
Consolidated. This note's findings now live in
../MIN-STUDIES.md§6. The original is kept for its measurements; do not re-derive from it.
The model list changes constantly — so it must be derived
Young, 2026-10-10: "Updated the list with your find, the GO/ZEN free models keep update all the time."
That observation is a design requirement, not a note. A list that changes all the time is exactly the class of fact V3 exists to handle: if it is written down in two places, it drifts. And I found the drift live while checking.
1. 🔴 The defect, measured — our config already has it
Our opencode.json declares a model under the wrong endpoint. Verified by call,
not by reading:
# space-bunny-free, sent to the GO base — where our config puts it
POST https://opencode.ai/zen/go/v1/chat/completions
→ {"error":{"type":"server_error","message":"Upstream request failed: Model is unavailable."}}
# the same model, sent to the ZEN base — where it actually lives
POST https://opencode.ai/zen/v1/chat/completions
→ {"id":"07193e23...","model":"space-bunny-free","usage":{...}} ✅
The config declares:
| provider | baseURL | models | verdict |
|---|---|---|---|
lab-go | …/zen/go/v1 | glm-5.3-flash · longcat-2.0 · qwen3.8-flash | ✅ all live on GO |
lab-go | …/zen/go/v1 | space-bunny-free | ❌ lives on ZEN, not GO |
So a model we list is silently unreachable — and the failure is a
server_error at call time, not at config time. This is the V2 pattern exactly: a
fact restated in a second place, drifting, with the failure surfacing far from the
cause.
Why it happened is the interesting part: space-bunny-free IS a real ZEN model.
Whoever added it had the right name and the wrong endpoint. A saved list cannot
tell you that; a derived one cannot get it wrong.
2. The provider agrees the list is unstable
Measured on both endpoints:
cache-control: no-store
The provider deliberately does not let the list be cached. That is a design statement: do not hold this. And the docs say it in words:
"The list of models may change as we test and add new ones." —
opencode.ai/docs/go
Counts change too. GO read 38 models and ZEN 14 in this session; the free sets were:
| Endpoint | Free models live 2026-10-10 |
|---|---|
| GO | longcat-2.5-preview-free · step-5-preview-free |
| ZEN | jev-1.13-free · longcat-2.5-preview-free · mimo-v2.6-flash-free · muse-spark-1.2-contributor-free · muse-spark-1.3-contributor-free · nemotron-3.5-lightning-free · space-bunny-free · step-5-preview-free |
8 free on ZEN, 2 on GO — and both lists move.
3. The rule this produces
A model list is DERIVED, never saved. It joins the registry's rule:
| Fact | Home | Derived? |
|---|---|---|
| agent identity, port, lane | cortex/registry/agents.yaml | ✅ everything derives |
| which models exist, and where | GET /zen/go/v1/models · GET /zen/v1/models | ✅ must derive — NEW |
| which models are free | the same endpoint ('free' in id) | ✅ derive |
| a model's price and monthly limit | the published table | ⚠️ docs — see below |
And the endpoint is part of the model's identity. space-bunny-free is not a
model name alone — it is (zen, space-bunny-free). A model without its
endpoint is half a fact, which is precisely how the defect above happened.
4. What this means for the plan
| Stage | Change |
|---|---|
| 1 — registry | the registry declares which endpoint each model belongs to, and a drift probe refreshes the live list and fails on any model that is not served there |
| 6 — cost | the per-model limit is joined at read time: the live list gives the ids, the published table gives price and limit — never a single saved table |
| 8 — observability | the readout shows when the model list was last refreshed, and flags a model that has stopped being served |
Stage 1's drift probe now has a second subject, and it is a real one: mutate
the registry's model, and the probe must fail because the endpoint does not serve
it. That is the same shape as the port-drift probe, and it would have caught
space-bunny-free on the day it was added.
5. The honest limit of the derivation
The live list gives ids, not prices. /models returns ids; the price and
monthly limit come from the published docs table, which is not an API.
So the split is:
| Fact | Source | Stability |
|---|---|---|
| which models exist, and on which endpoint | /models API | derived — authoritative |
| whether a model is free | /models — 'free' in id | derived |
| price, monthly limit | the docs table | documented — re-checked, not derived |
| GO window usage | /zen/go/v1/usage | derived |
| ZEN credit balance | console only | not derivable — unknown |
That is four derivable facts and two that are not — and the ledger should say which is which rather than blending them into one confident number.
6. Not established
- The
/modelsendpoints were read, not schema-verified. The shape is{"data":[{"id": …}]}; other fields exist but were not enumerated. - The published price table was not cross-checked against a live call. Prices
are from
opencode.ai/docs/go; a real call'susageobject reports tokens, not dollars, so the join is by model id and the docs' table. - The
space-bunny-freemismatch was proven on one model. Whether other saved models are similarly misplaced was checked — the other three onlab-goare correct — but theopenrouter-freeprovider's 8 models were not verified against OpenRouter's own list. - The list will have changed by the time this is read. That is the point.
V3 — what we still need to look into
Young's question: "We are designing at this stage, what else do we need to look into?"
muse-spark is a training model, and putting it on the zero-retention list would publish our confidential tasks
Young, 2026-10-11: "muse-spark retention — it's wired in, and it trains on request data — approved and enabled."

