Two models
A session runs on one model, and there are two ways to bring a cheaper one into the work. They answer different questions, and only one of them lets the cheap model do anything.
Naming a model
A model is named the same way everywhere: in agent.model, after --model in the message that starts a session, and after !model in a thread.
musecringe a model of the provider the session is on
ajamxhacker/musecringe a model of another provider you have defined
ajamxhacker/musecringe:max the same, thinking as hard as it can
muse whatever `aliases` says that stands forNaming a provider settles which one the session runs on, so it beats the standing agent.provider. A leading segment counts as a provider only when it is one your configuration defines, which leaves a model id holding a slash of its own, meta/muse-spark-1.3, whole.
A bare name is looked for on the provider the session is on first. When that provider does not serve it, it is looked for on every provider you defined, and a name more than one of them serves is refused rather than guessed at: the refusal names each provider/model that answers to it. Two providers sharing an id serve two different models, and the one meant is the one you had in mind.
A provider the agent has built in needs only its credential to be switched to. The daemon reads where it is served from the host's model store, so it does not have to be the provider a session started on.
Asking a provider for its models
A provider can be asked which models it serves instead of having them listed by hand. Set discover on it, and errand reads the listing under its baseUrl when the daemon starts:
{
"agent": {
"providers": {
"gateway": {
"baseUrl": "https://gateway.example/v1",
"kind": "openai",
"credential": "...",
"discover": { "defaults": { "reasoning": true } },
"models": [{ "id": "glm-5.3-flash", "contextWindow": 256000 }]
}
}
}
}true reads the usual /models listing, with the sizes under context_window and max_output_tokens. A provider speaking the Anthropic protocol instead of OpenAI's names "kind": "anthropic", the same word kage's own configuration uses; omitted means "openai". A provider shaped otherwise says where things are:
"discover": {
"path": "/api/models",
"list": "result.models",
"fields": { "id": "slug", "contextWindow": "top_provider.context_length" }
}What is found goes underneath what models says. An entry for a model that was found overrides only the fields it names, so the example above keeps the listed maxTokens and replaces the window. defaults fills fields no listing carries, such as reasoning, for every model found. A provider that cannot be asked keeps the models it names, and the log says why.
An operator can ask again without a restart with !models refresh. Every session's !model sees the new list at once. A running sandbox keeps what it was launched with until its next launch. Editing the models list in the configuration file does not need the refresh: a change to the file is read out of it again, so the edited list is what !model lists. The refresh is for asking the providers themselves over the network.
Reading a provider's usage window
!usage and the bot's status say what is left of each provider's usage window, and a session is refused up front when its provider's window is spent. A provider is read only when its definition says how, with usage:
"zai-coding-cn": { "credential": "...", "usage": "zai" },
"ajamxhacker": { "baseUrl": "https://meta.example/v1", "credential": "...", "usage": "gateway" }"zai" asks z.ai's own quota endpoint. "gateway" asks /usage under the baseUrl, the shape a gateway serving one beside its API answers with. Any other provider says where its numbers are:
"usage": {
"path": "/v1/usage",
"auth": "bearer",
"percent": "limiting.remainingPercent",
"percentIs": "left",
"resets": "limiting.resetsAt",
"resetsAs": "iso"
}auth is bearer or raw, for a key sent without Bearer. percentIs has no default, because reading what is left as what is used turns the window upside down. resetsAs is iso, ms, or s. A provider that cannot be read shows as unknown and never stops a session.
A path is dotted keys, and a key can pick one entry out of a list by a field it carries, since a provider does not promise the order of a list. A provider that keeps several windows lists them, the one to show first:
"usage": {
"path": "/usage",
"windows": [
{ "percent": "windows[id=window-share:300m].usedPercent", "percentIs": "used",
"resets": "windows[id=window-share:300m].resetsAt", "resetsAs": "iso" },
{ "percent": "windows[id=window-share:1w].usedPercent", "percentIs": "used",
"resets": "windows[id=window-share:1w].resetsAt", "resetsAs": "iso" }
]
}The first window is shown. One missing from the answer, or already past its reset, gives way to the next. A spent window outranks them all: a spent weekly allowance leaves nothing for the five hour window to give, so the provider reads as spent until the last spent window resets.
Some plans keep serving once a window is spent and bill the rest to the account's credit balance, as OpenCode Go does. "spentRefuses": false on such a provider's usage keeps its windows in !usage and the bot's status, so the spend stays in sight, while a session starts whatever they say:
"usage": {
"path": "/zen/go/v1/usage",
"spentRefuses": false,
"windows": [
{ "percent": "usage.rolling.percent", "percentIs": "used",
"resets": "usage.rolling.resetsAt", "resetsAs": "iso" },
{ "percent": "usage.monthly.percent", "percentIs": "used",
"resets": "usage.monthly.resetsAt", "resetsAs": "iso" }
]
}When a window is spent
A session asked for while its provider's window is spent can start somewhere else instead of being turned away. agent.fallback lists models to try, in order, named as !model names them:
{ "agent": { "fallback": ["glmcringe", "zai-coding-cn/glm-5.3-flash", "free"] } }The first whose provider has room is used, and the thread says which window was spent and what the session runs on instead. Only when every fallback is spent too is the session turned away.
Switching the model a session runs on
!model lists every model this host can run, each named provider/model, with the session's own provider first and the model it is on marked. That is the name to type back: it resolves whatever else serves the same id. !model <name> moves the session to that one, keeping the conversation: what was said stays said, and the next turn is answered by the model named.
That is the useful shape for "plan on the capable model, carry it out on the cheap one". The cheap model works in the same thread, with the same tools, in the same sandbox, and you switch back to review. A switch asked for while a turn is running waits for it to end, because changing the model underneath a turn answers half a question with each. The thread is told it is waiting, and a later switch replaces an earlier one that is still waiting.
How hard a model thinks
A thinking level is written onto a model name with a colon, as musecringe:max, and typing one is always the last word. Saying it on every switch is tedious, so a level can be settled once in the configuration:
{
"agent": {
"providers": {
"ajamxhacker": {
"credential": "...",
"defaultThinkingLevel": "high",
"models": [{ "id": "musecringe", "defaultThinkingLevel": "max" }]
}
}
}
}The model's own level wins over the provider's, and the provider's applies to every model it serves. !model says which level a model will think at when nobody asks, so the list is enough to know what a switch will do.
Naming a model the host's store already lists does not add a second copy of it. It says something about the one that is there, which is how a level is set for a model of the provider the session starts on.
Asking a cheaper model about one thing
A delegation is a question about one artefact that already exists. The agent runs a command:
delegate --file src/parse.ts "which functions does this export?"
delegate --call <tool call id> "what failed, and on which line?"
delegate --attachment screenshot.png "transcribe the error"The daemon reads what was named, sends it with the question, and hands the answer back labelled with the model that produced it.
Turn it on by naming a model:
{
"agent": {
"delegate": { "model": "glm-5.3-flash", "perTurn": 8, "deadlineMs": 60000 }
}
}Absent means no delegation at all, and the agent is not told about a command it does not have.
What the cheap model can do
Nothing. It is sent one message holding the question and the artefact, with no tools, so it cannot read another file, run a command, or change anything. It is not told what the session is trying to achieve. What comes back is text.
This is what makes it safe to ask freely: a question phrased as a decision produces an opinion about an artefact, which is as harmless as a summary of one, and the answer can always be checked against the same artefact.
What it is for
Keeping material out of the session's own context. A log read into the conversation is paid for again on every later turn, because each turn replays what came before. Asking about it instead costs one cheap request and returns a paragraph.
!status reports what a session has delegated: how many were asked, what they cost, and how much was kept out of the conversation.
When it does not work
A refusal is ordinary and never fails a turn: the work stays with the session's own model, and the thread says so once. A delegation is refused when it names nothing, names more than one thing, names a path outside the project, when the turn has used its allowance, or when the provider is being backed off.
What is not here
There is no automatic handoff: nothing writes a plan and hands it to a second agent to implement unattended. That needs a second agent with its own sandbox, its own turn loop, and a way to review and stop it, and the failure mode is the expensive one, a cheap model producing plausible wrong work that the capable one then has to diagnose and redo.