Note
Grok responding on behalf of Julius.
Triage
Thanks @snnbotchway. The repro, the two-host cache comparison, and the code pointers all line up. I confirmed this on current main (31a9da179). It's a real catalog bug in the ACP registry snapshot, and Devin makes it visible because its thought_level choices change with the current model.
What the menu is reading
The composer looks up the selected model's capabilities (getProviderModelCapabilities). modelsFromDiscovery builds one capabilities object from the session configOptions and assigns that same object, including currentValue, to every model. The comment there says those options ride on every model so the composer can drive them per thread. applyAcpRegistryLiveConfiguration rebuilds the catalog the same way from the latest live session.
ACP configOptions describe the current session. The disposable discovery probe (probeAcpRegistryConfiguration → acpRegistryProbeResult) never switches models, so the list it stores is whatever model that test session happened to be on, and a later config_option_update replaces it. Live configuration is one entry per provider instance (liveConfigurationByInstance), so the newest Devin session wins for every model and every thread on that instance. That entry stays until the driver instance is recreated or disposed, or until logout or auth loss.
A provider refresh probes again, and then withLiveRuntimeState paints the stored live configuration back on top, which is why a refresh keeps the short list. A server restart drops the in-memory overlay, but the next probe stamps a single session's list onto the catalog again. The cache file is that published snapshot, so identical thought_level arrays on all 108 models come from the same write.
The in-repo ACP mock already varies thought_level with currentModelId (apps/server/scripts/acp-mock-agent.ts), but the registry snapshot doesn't keep those lists apart.
What still works at send time
On session open, AcpAdapterV2 skips a stored select value the live session doesn't offer and logs an agent rejection instead of failing the turn. That only runs once a session exists, though. It doesn't restore Medium or Extra High on the Opus menu, and it doesn't stop the GLM list from showing for Opus beforehand. Your workaround of sending on a model with the wider list just changes which single list every model shares.
Likely fix area
Your suggestion fits this failure: keep the last thought_level list seen for each model (from probes and live sessions) and attach session options only to the current model, instead of copying one set to all models. A maintainer will decide on the fix direction.