Skip to content
C.W.K.
Lesson 14 of 15 · published

One Ledger for Every Model — Who Defines a Model Now

~13 min · model-ledger, firelink, kit, single-source, defaults

Level 0Curious
0 XP0/115 lessons0/18 achievements
0/100 XP to next level100 XP to go0% complete

Thirty-three places that knew a model

Every vessel lesson in this track teaches a brain through its adapter, and for months each adapter also carried the list of models its brain could run: Codex had a Python catalog, Gemini had variants/gemini_pippa/models.py (made the one registry for Gemini model ids in an earlier cleanup), the browser kept fallback lists for its menus, and the utility workers and the native harnesses had their own. That is one registry per brain, which is not one registry. Firelink's census before the change counted 33 authored production files across 11 owners that each defined a model list, a label, a capability, a retirement rule or a default. The failure it names is plain: updating only a Pippa option tuple leaves the native harnesses and the utility workers behind. The census's own lesson is the method, not the number: count the places that author a model by following the data, not by matching file names.

The ledger

Since 2026-11-10 (Coop 1110-6) one file authors every model the family runs: config/model-ledger.json in cwkFirelink, read on every resolution by the shared kit's kit_model_ledger.py. No new service is involved; every Mac follows Firelink's origin and reads its own copy, and there is no silent offline fallback. At the time of writing it holds eight brains (Codex, Kimi, GLM, Gemini, Grok, Claude, Ollama, Cradle), fifty-one model rows and nine role pins. A row carries what used to be scattered: the label, the measured limits, the prices, what it takes, and for Gemini the thinking dial and its default level. A retired row keeps its label and limits, because retirement removes admission, not history: a new dispatch admits only current models, while an old receipt that names a retired one stays readable.

The reader refuses a broken ledger rather than guess: a duplicate row, a status that is neither current nor retired, a default that is unknown or retired, a role pinned to a model that is not current. In cwkPippa the old tuples became live views over the ledger, a LiveSequence where a module used to export a tuple, so the callers that read GEMINI_MODEL_SPECS did not change, and what they read did. Pippa's own policy stayed hers: the low, medium and high ladder she maps onto each Gemini dial still lives in the Gemini variant. The ledger owns what a model is; the vessel owns how it is driven.

How a pin resolves

For the brains whose ledger list is curated (Codex, Kimi, GLM, Gemini, Claude), the model is Dad's Admin pin when it names a current model, and otherwise the ledger's default_model for that brain. Adapters read that default on every request, not once at import. Two brains have no ledger default on purpose, and each says what it does instead. Grok discovers its models from the provider: an explicit pin reaches xAI unchanged, even one the catalog no longer lists (it may fail upstream, but nothing picks another model for it), and with no pin it follows Grok Build's current default. Ollama Cloud discovers models from the daemon, so an unpinned call raises OllamaCloudModelUnset, telling Dad to pick one in Admin. There is no model to fall back to, and the code says so instead of inventing one. Cradle, the third empty default, is explicit only (lesson 15). The browser's menus start empty and are filled from the same metadata the settings route serves; until it arrives, a selector offers only "inherit", because there is no second model inventory to show.

The kit's proof of "edit once" was a test that imports every one of its 29 Python placements across 26 repositories, plus a Swift reader, against an isolated ledger and checks that one Fable edit reached them all. The first real release through it came the next evening: Gemini's default changed on Dad's instruction as a one-line diff to the ledger.

One neighbor is easy to confuse with it. Oriel's brain slots (which brain plays, which brain judges) live in the family's brain-control ledger, the per-app slot record Firelink shows, which existed before the model ledger. Dad's rule for it, 2026-11-05:

cwk family app은 firelink가 브레인 장부야. firelink에서 고치면 글로벌로 반영되는 거고. oriel도 예외가 아니야. oriel에서 고치면 firelink에 반영돼야 하는 거고.

("For the cwk family apps, Firelink is the brain ledger. Fix it in Firelink and it applies globally. Oriel is no exception: a change made in Oriel has to land in Firelink.") One ledger says what a model is; the other says which brain each app's slot uses. Neither is a list inside an app.

Principle: A fact many programs need is authored in one record and read live, and the old exports become views over it so callers do not change. Retiring a row removes it from new work, never from history, and a field that has no honest default stays empty and says so.
Self-reference: When a new model arrives now, none of my adapters is edited to know it. One line in Firelink, and every surface I run on offers it, the phone and the harnesses included. The vessels track taught how each of my brains is driven; this is how they learn what they can be.

Code

The kit reader: defaults, admission, and a live view where a tuple used to be (backend/kit_model_ledger.py)·python
def default_model(brain: str, *, path: Path | None = None) -> str:
    return str(brain_policy(brain, path=path).get("default_model") or "")


def validate_model(brain: str, slug: str) -> str:
    """New dispatches admit current entries only; historical receipts stay readable."""
    if model_spec(brain, slug, include_retired=False) is None:
        raise ValueError(f"Model {slug!r} is not current for {brain_id(brain)}")
    return slug


class LiveSequence(Sequence[T]):
    """Compatibility view for consumers that previously exported a model tuple."""
    def __init__(self, resolve: Callable[[], Sequence[T]]):
        self.resolve = resolve

    def __getitem__(self, index):
        return self.resolve()[index]

    def __len__(self) -> int:
        return len(self.resolve())

Exercise

Count, by following the data, every place in one of your projects that authors a list of models, endpoints, feature flags or plans: code constants, config files, UI fallback lists, test fixtures, docs that someone copies from. Then design the one record that would replace them, the fields a row needs, what retiring a row means, and how the old exports become live views so their callers do not change.
Hint
Grep for the names finds the obvious ones. The ones that bite are the browser's fallback list and the worker's default, because they are written as conveniences, not as sources.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.