Model dropdown stays empty for ACP harnesses that report models via `session/new` → `models.availableModels` (e.g. Hermes Agent)
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Summary
Buzz discovers a harness's models by reading `configOptions` from the `session/new`
result. ACP agents that instead report models via the `models` field
(`result.models.availableModels`) — and emit no `configOptions` — are invisible to
that code path. The UI falls back to "Using built-in model options. Could not load
live models for this provider."
Hermes Agent (a bundled preset harness, `hermes-acp`) is affected: it returns **154
models** in `result.models.availableModels` and **no `configOptions` at all**, so
Buzz shows zero live models for it.
Both mechanisms are legitimate ACP. `configOptions` is the generic
session-configuration mechanism; `models` / `availableModels` is the dedicated model
field. Buzz currently supports only the former.
## Environment
| | |
|---|---|
| Buzz Desktop | v0.5.2 (Linux, extracted AppImage) |
| OS | Fedora Linux 44.1.7 (Sway Atomic), Wayland |
| Hermes Agent | v0.19.0 (2026.7.20) |
| OpenCode | 1.18.10 |
| Claude Code | 2.1.220 via `@agentclientprotocol/claude-agent-acp` 0.64.0 |
| ACP protocolVersion | 1 (all three agents) |
## Expected vs actual
**Expected:** With Default harness = Hermes Agent, the Model dropdown lists Hermes's
available models.
**Actual:** Dropdown is empty except built-in options, with:
> Using built-in model options. Could not load live models for this provider.
Reached via Settings → Agents → Agent defaults → Default harness = "Hermes Agent".
## Root cause
Diffing the `session/new` response across the three harnesses installed here:
| Harness | `session/new` result keys | `models.availableModels` | `configOptions` | Dropdown populates |
|---|---|---|---|---|
| OpenCode (`opencode acp`) | `sessionId`, `configOptions` | 0 | ✅ present (`id: "model"`, `category: "model"`) | ✅ yes |
| Claude Code (`claude-agent-acp`) | `sessionId`, `modes`, `configOptions` | 0 | ✅ present | ✅ yes |
| **Hermes (`hermes-acp`)** | `_meta`, `models`, `modes`, `sessionId` | **154** | ❌ **absent** | ❌ **no** |
The two harnesses that work both expose models through `configOptions`. The one that
fails exposes them through `models.availableModels`. This lines up with
`resolve_model_switch_method()` reading the `configId` key from each `session/new`
`configOptions` entry (see commit 925a9a7, "fix(buzz-acp): accept id-keyed config
options when resolving model switch") — there appears to be no corresponding reader
for the `models` field.
### Raw responses
OpenCode — models under `configOptions`:
```json
{
"sessionId": "…",
"configOptions": [
{
"id": "model",
"name": "Model",
"category": "model",
"type": "select",
"currentValue": "opencode/big-pickle",
"options": [
{ "value": "opencode-go/deepseek-v4-flash", "name": "OpenCode Go/DeepSeek V4 Flash (New)" },
{ "value": "opencode-go/deepseek-v4-pro", "name": "OpenCode Go/DeepSeek V4 Pro" }
]
}
]
}
```
Hermes — models under `models`, no `configOptions`:
```json
{
"sessionId": "…",
"_meta": { "hermes": { "sessionProvenance": { … } } },
"modes": { … },
"models": {
"currentModelId": "nous:tencent/hy3:free",
"availableModels": [
{ "modelId": "nous:anthropic/claude-opus-5", "name": "Nous Portal · anthropic/claude-opus-5", "description": "Provider: Nous Portal" },
{ "modelId": "nous:anthropic/claude-sonnet-5", "name": "Nous Portal · anthropic/claude-sonnet-5", "description": "Provider: Nous Portal" }
]
}
}
```
## Reproduction
Minimal ACP stdio client — no Buzz required:
```python
import json, subprocess, time
p = subprocess.Popen(["hermes-acp"], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
text=True, bufsize=1)
def send(o): p.stdin.write(json.dumps(o) + "\n"); p.stdin.flush()
def rd(i):
while True:
m = json.loads(p.stdout.readline())
if m.get("id") == i: return m
send({"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":1,"clientCapabilities":{}}}); rd(1)
send({"jsonrpc":"2.0","id":2,"method":"session/new",
"params":{"cwd":".","mcpServers":[]}})
r = rd(2)["result"]
print("keys: ", list(r.keys()))
print("availableModels:", len(r.get("models", {}).get("availableModels", [])))
print("configOptions: ", r.get("configOptions", "ABSENT"))
```
Output here:
```
keys: ['_meta', 'models', 'modes', 'sessionId']
availableModels: 154
configOptions: ABSENT
```
Swap `hermes-acp` for `opencode acp` to see the inverse.
## Ruled out
These were each tested and are **not** the cause:
- **Discovery timeout.** Hermes's ACP `initialize` was slow (6.1s) because it connects
MCP servers during init. Reducing that to **0.65s init / 2.0s total** — faster than
OpenCode's 2.6s, which works — did **not** fix the dropdown.
- **Network/DNS.** All model endpoints resolve and respond normally.
- **`authMethods` gating.** OpenCode also advertises a non-empty `authMethods` and
populates fine, so a non-empty `authMethods` is not what blocks Hermes.
- **Binary resolution.** `hermes-acp` resolves on the login-shell PATH and the
handshake succeeds; Buzz spawns it without error.
## Suggested fix
In the model-discovery path, fall back to `result.models` when `configOptions` has no
`category: "model"` entry:
- Read `result.models.availableModels[].modelId` / `.name` for the option list.
- Read `result.models.currentModelId` for the current selection.
- For switching, ACP defines `session/set_model` (Buzz already logs
`session/set_model: model overridden model_id=…` for its own `buzz-agent` runtime),
so the switch path may largely exist already.
Supporting both would fix Hermes and any other agent using the dedicated `models`
field, without affecting the `configOptions` harnesses.
## Workaround
The "Custom model…" option plus a hand-typed model ID (e.g.
`nous:anthropic/claude-opus-5`) does work — Buzz passes it through even though it
can't enumerate it. So this is a discovery/enumeration gap only, not a plumbing one.
Contributor guide
Research direction
Start in Buzz’s model-discovery path, especially the code that reads session/new configOptions and resolve_model_switch_method(); compare that flow with the reproduced Hermes response.models.availableModels shape. Done means the model dropdown lists modelId/name values and uses currentModelId while preserving the existing configOptions behavior; verify with Hermes and a configOptions-based harness.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100