Skip to content

pi: seed settings.json instead of clobbering the user's model choice#206

Draft
MarkAngler wants to merge 2 commits into
databricks:mainfrom
MarkAngler:fix/pi-preserve-model-choice
Draft

pi: seed settings.json instead of clobbering the user's model choice#206
MarkAngler wants to merge 2 commits into
databricks:mainfrom
MarkAngler:fix/pi-preserve-model-choice

Conversation

@MarkAngler

Copy link
Copy Markdown

Part of #204.

Stacked on #205. Review after that lands; its commit appears in this diff until then.

Problem

Pi persists the model picked in its own selector to settings.json (via setDefaultModelAndProvider). ucode overwrote defaultProvider/defaultModel on every launch and every 30 minutes from the token-refresh thread, so the user's choice could never survive to the next ucode pi — the "if you try to modify the providers manually, ucode just overwrites them on next launch" part of #204.

Fix

_write_settings now only writes when there is nothing to preserve or when what is there is ucode-authored residue that no longer resolves:

  • nothing pinned yet — seed it, so Pi's findInitialModel can't fall through to an env-key-backed provider (e.g. HF_TOKEN exposing huggingface). Unchanged behavior.
  • pinned to a ucode provider we no longer register (databricks-openai on a workspace with no Responses models, or a legacy provider name) — repair.
  • pinned to a ucode provider we register, but a model it no longer offers — repair.

A pin naming a provider ucode does not manage is left alone (Pi writes defaultProvider only on an explicit user selection).

The refresh thread now passes update_settings=False: it still rewrites models.json every 30 minutes to rotate the baked-in bearer token, but no longer resets the model.

🤖 Generated with Claude Code

MarkAngler and others added 2 commits July 10, 2026 10:44
Model discovery bucketed `system.ai.*` ids into claude/codex/gemini/oss by
matching substrings in the model name. Names don't say which API dialect a
model speaks, so the guess misfired three ways:

- `gpt-oss-120b`/`gpt-oss-20b` matched `"gpt-" in id` and were bucketed as
  OpenAI Responses models. They only speak `mlflow/v1/chat/completions`, so pi
  registered them under `databricks-openai` pointing at /ai-gateway/codex/v1 —
  a route that rejects them. Claude's web_search MCP aimed at the same route.
- `claude-fable-5` matched none of `claude-{opus,sonnet,haiku}-` and vanished.
- `claude_models` is a `{family: id}` map, so only the newest model per family
  reached agents that render a full model list. A workspace serving 11 Claude
  models showed pi 3.

Every model-service entry already carries `supported_api_types`; the listing
fetched it and threw it away. Bucket by that instead, with precedence — a
Claude model also advertises mlflow chat-completions and would otherwise land
in two buckets. Workspaces whose payload predates the field fall back to the
name rules, with the codex rule tightened to require a version digit after
`gpt-` so gpt-oss is excluded there too.

Also:
- Sort every bucket with `model_version_sort_key`, replacing a lexicographic
  reverse sort that ranked `opus-4-8` above `opus-4-10`, and leaving
  `codex_models[0]` as the newest rather than the alphabetically first.
- Return `claude_ids` (every Anthropic-dialect id) alongside the family map;
  persist it as `claude_model_ids` and register it in pi's models.json.
  opencode deliberately keeps reading the family map — it takes `anthropic[0]`
  as its default, which widening would silently move onto claude-fable-5.
- `check_gateway_endpoint("codex")` now delegates to `codex.default_model`.
  A non-empty list wasn't enough: codex drops ids that don't parse as
  `gpt-<version>`, so a gpt-oss-only workspace reported codex available and
  then failed at launch with "No models available for codex".

State keys are additive; no STATE_VERSION bump (a mismatch discards every
workspace's state, including managed_configs and provider_services).

Refs databricks#204

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pi persists the model picked in its own selector to settings.json (via
`setDefaultModelAndProvider`). ucode overwrote `defaultProvider`/`defaultModel`
on every launch *and* every 30 minutes from the token-refresh thread, so the
choice could never survive a relaunch — the "ucode just overwrites them" half
of databricks#204.

`_write_settings` now only writes when there is nothing to preserve or when
what is there is ucode-authored residue that no longer resolves:

- nothing pinned yet — seed it, so Pi's `findInitialModel` can't fall through
  to an env-key-backed provider (e.g. HF_TOKEN exposing huggingface). This is
  the behavior the pin existed for, and it is unchanged.
- pinned to a ucode provider we no longer register (`databricks-openai` on a
  workspace with no Responses models, or a legacy provider name) — repair.
- pinned to a ucode provider we do register, but to a model it no longer
  offers — repair.

A pin naming a provider ucode does not manage is left alone: Pi writes
defaultProvider only on an explicit user selection, so it is a deliberate
choice, not fall-through.

The refresh thread now passes `update_settings=False`. It still rewrites
models.json every 30 minutes — that rotates the bearer token baked into the
file, which is load-bearing — but no longer resets the model.

Refs databricks#204

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant