Skip to content

Model dropdown stays empty for ACP harnesses that report models via session/newmodels.availableModels (e.g. Hermes Agent) #3934

Description

@lazo99

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:

{
  "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:

{
  "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:

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions