Pi custom / variant model
When to Use
Pi's saved default only loads if the exact provider/id exists in its model registry. Pi ships a static bundled list per provider — so OpenRouter routing-shortcut variants (:nitro = sort by throughput, :floor = cheapest, :exacto = quality tool-use) and any brand-new slug are NOT in it. When the default doesn't resolve, Pi silently falls through to its built-in per-provider default (for openrouter that's moonshotai/kimi-k2.6) — looking like Pi "reset" your model. Fix = register the slug as a custom model so find(provider, id) matches.
Files (global)
~/.pi/agent/settings.json—defaultProvider,defaultModel,defaultThinkingLevel~/.pi/agent/models.json— custom models, keyed by provider~/.pi/agent/auth.json— provider credentials (check the provider key exists)
Steps
- Confirm the slug is real before adding it (e.g. check the OpenRouter model/variant exists). A typo'd id also silently falls back.
- Confirm auth. The provider must have a key in
auth.json(or an env var likeOPENROUTER_API_KEY). No auth → the model is registered but unavailable → still falls back. - Add the model to
models.jsonunderproviders.<provider>.models. For a built-in provider (openrouter, anthropic, etc.) you only supply metadata —api,baseUrl, and auth are inherited from the bundled defaults. Example:
Copy{ "providers": { "openrouter": { "models": [ { "id": "z-ai/glm-5.2:nitro", "name": "Z.ai: GLM 5.2 (nitro)", "reasoning": true, "thinkingLevelMap": { "xhigh": "xhigh" }, "input": ["text"], "cost": { "input": 0.95, "output": 3, "cacheRead": 0.18, "cacheWrite": 0 }, "contextWindow": 1048576, "maxTokens": 32768, "compat": { "supportsDeveloperRole": false, "thinkingFormat": "openrouter" } } ] } } }cost/contextWindow/compatfrom the base model (the variant shares them) — find the bundled entry in<pi-pkg>/node_modules/@earendil-works/pi-ai/dist/providers/<provider>.models.js. Don't hardcode generic 128k/16k if the real model is bigger. - Set the default in
settings.json:defaultProvider+defaultModel= the exact id. LeavedefaultThinkingLevelas the user has it. - Verify:
pi --list-models | grep <id>shows it, and JSON parses. Optionally smoke-test:pi --provider <p> --model "<id>" "which model are you?".
Quirks
- Exact match only.
find()is exactprovider+id— no fuzzy/colon-stripping for the saved default path. The slug insettings.jsonandmodels.jsonmust be byte-identical. - Silent fallback. Pi prints no error when the default doesn't resolve; it just shows a different model in the footer. That's the tell.
- Don't edit
settings.jsonalone. SettingdefaultModelto an unregistered slug does nothing —models.jsonis the actual fix. enabledModels(optional) pins the model picker so Ctrl+P cycling can't drift back:"enabledModels": ["<provider>/<id>:<thinking>"].- Project override. A repo's
.pi/settings.jsonoverrides global. If a default reverts only inside one project, check that file first. - Restart Pi fully — the registry loads at startup.
Limitations
- Adapted from
davidondrej/skills; verify local paths, tools, credentials, and agent features before acting. - For commands, remote access, scheduling, browser automation, or file-changing workflows, get explicit user approval and confirm the target environment first.