[Bug]: ACP registry shows the current model's options on every model#15177

openedOct 3, 2026bySsnnbotchway

Area

apps/server

Summary

ACP registry providers show the option list of one model on every model. When the current ACP model has fewer thought_level choices, all models lose the missing choices.

With Devin, a thread on glm-5-3-flash-max (which offers low/high/max) makes Claude Opus 5.5 show only Low / High / Max. Opus 5.5 itself offers low/medium/high/xhigh/max.

Steps to reproduce

  1. Add Devin through the ACP registry (acpRegistry, agent devin).
  2. Start a Devin thread on glm-5-3-flash-max and send a message.
  3. Start a new Devin thread and select Claude Opus 5.5 (claude-opus-5-5-medium).
  4. Open the mode / thinking menu.

Expected behavior

Each model shows its own options. Opus 5.5 shows Low, Medium, High, Extra High, Max.

Actual behavior

Opus 5.5 (and all 108 Devin models) shows only Low, High, Max: the list of the GLM model.

The provider cache confirms it. ~/.t3/caches/acpRegistry_devin.json gives every model the same thought_level options:

  • Host A, live/probed model claude-opus-5-5-medium: 108 models × low/medium/high/xhigh/max
  • Host B, live/probed model glm-5-3-flash-max: 108 models × low/high/max

Both hosts use the same Devin CLI version, account, and T3 provider settings. Refreshing the provider does not help, because the live configuration from the last session overrides the probe.

Cause

modelsFromDiscovery builds one capabilities object from discovery.configOptions and assigns it to every model. ACP configOptions describe the current session, so their values are valid only for the current model. applyAcpRegistryLiveConfiguration does the same with the last live session's configuration, which stays in memory until the provider instance is recreated.

A possible fix: attach session option descriptors only to currentModelId, and keep the descriptors last seen for each model (from the probe and from live sessions) instead of copying one set to all models.

Impact

Minor bug or occasional failure

Version or commit

T3 Code nightly 0.0.46-nightly.20261003.2610 (server). Code references are at 31a9da1. Devin CLI 3000.11.3.

Environment

Ubuntu 26.04 server, Windows desktop client over Tailscale. Also seen with a WSL2 server.

Workaround

Send one message in a Devin thread on a model with the full option list (for example Opus 5.5). The live configuration then gives the full list to all models, until another model becomes current.

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.