Skip to content

[Bug]: Docker conversation runtime cannot resolve configured title LLM profile because per-runtime profile store is empty #5310

Description

@hectormhr

Is there an existing issue for the same bug?

  • I have searched existing issues and this is not a duplicate.

Bug Description

When Agent Canvas uses the Docker conversation runtime, a configured
title_llm_profile is passed into the conversation, but the inner Agent Server
cannot resolve it because its per-conversation LLM profile store is empty.

The outer Agent Canvas / Agent Server has a valid LLM profile named
LLMStudioAux, configured as the default/title-generation profile.

The Docker conversation runtime starts with:

OH_PERSISTENCE_DIR=/var/openhands/.openhands

and its profile directory is:

/var/openhands/.openhands/profiles

but that directory contains only:

.profiles.lock

No LLM profile JSON files are propagated into the runtime.

As a result, automatic title generation logs that LLMStudioAux cannot be
found and falls back to another LLM path. For an ACP Codex conversation this
then produces an unrelated LiteLLM/OpenAI authentication error because the
ACP agent itself is not a directly callable LiteLLM model.

The underlying coding agent continues to work; only the auxiliary title-
generation path is broken.

Expected Behavior

A Docker-backed conversation should be able to resolve the LLM profile selected
for title generation by Agent Canvas.

If title_llm_profile="LLMStudioAux" is passed when creating the conversation,
the inner Agent Server should have enough runtime configuration to load that
profile.

The solution should not require exposing the entire host OpenHands persistence
directory to the conversation container.

Actual Behavior

The inner Agent Server receives the title LLM profile name but cannot resolve it.

Inside the conversation container:

docker exec <conversation-container> sh -lc '
  echo "OH_PERSISTENCE_DIR=$OH_PERSISTENCE_DIR"
  ls -la /var/openhands/.openhands/profiles
'

produces:

OH_PERSISTENCE_DIR=/var/openhands/.openhands

total 0
drwxr-xr-x ...
drwx------ ...
-rw-r--r-- ... .profiles.lock

The runtime then logs that the configured title profile LLMStudioAux does not
exist and that there are no available profiles.

The model endpoint itself is reachable from the same container:

docker exec <conversation-container> \
  curl -s http://host.docker.internal:1234/v1/models

and returns, among others:

qwen/qwen3.6-35b-a3b

Therefore this is not an LM Studio networking failure. The missing piece is
the LLM profile definition inside the per-conversation runtime.

Steps to Reproduce

  1. Run Agent Canvas with Docker conversation runtimes:

    OH_CONVERSATION_RUNTIME=docker \
    OH_CONVERSATION_IMAGE=ghcr.io/openhands/agent-server:1.49.5-python \
    agent-canvas
    
  2. Create an LLM profile in Agent Canvas, for example:

    name: LLMStudioAux
    model: openai/qwen/qwen3.6-35b-a3b
    base URL: http://127.0.0.1:1234/v1
    
  3. Configure Agent Canvas to use LLMStudioAux for conversation title generation.

  4. Start a new ACP/Codex conversation.

  5. The outer Agent Server passes the title profile name into the conversation.

  6. Inspect the conversation container:

    docker exec <container> \
      ls -la /var/openhands/.openhands/profiles
    
  7. Observe that the directory contains only .profiles.lock.

  8. Observe the inner Agent Server log reporting that LLMStudioAux cannot be
    found / that no LLM profiles are available.

  9. Verify separately that the container can reach the local model using:

    curl http://host.docker.internal:1234/v1/models
    

    which succeeds.

Acceptance Criteria

  • A Docker conversation can resolve the configured title_llm_profile.
  • The required LLM profile definition is made available to the inner Agent Server.
  • ACP conversations can use an auxiliary LLM profile for title generation without falling back to the ACP agent or an unrelated OpenAI API-key path.
  • The fix preserves per-conversation runtime isolation and does not require mounting the entire host OpenHands persistence directory.
  • A regression test covers Docker conversation runtime + configured title_llm_profile.

Installation Method

Agent Canvas with Docker conversation runtime on macOS.

If you selected "Other", please specify

No response

SDK Version

openhands-agent-server / openhands-sdk 1.49.5 Agent Canvas 1.22.0

Version Confirmation

  • I have confirmed this bug exists on the LATEST version of OpenHands SDK

Python Version

No response

Model Name (if applicable)

openai/qwen/qwen3.6-35b-a3b via LM Studio

Operating System

MacOS

Logs and Error Messages

Failed to load title LLM profile 'LLMStudioAux':
Profile LLMStudioAux not found.
Available profiles: none.
Falling back to the agent's LLM.

Minimal Code Sample

No response

Screenshots and Additional Context

The fallback subsequently produced a LiteLLM/OpenAI invalid_api_key error,
but that is secondary. The primary failure is that the Docker runtime's
LLM profile store is empty.
This appears related to the title-generation/profile work tracked in #4199,
which was completed before the current per-conversation Docker runtime topology.

The configured profile exists in the outer Agent Canvas environment, and the
inner container can reach the model endpoint successfully via
host.docker.internal:1234.

The failure is specifically that the profile reference crosses the runtime
boundary, but the profile definition does not.


OpenHands AI triage

The following comments and acceptance criteria were added by the OpenHands AI agent.

Triage

This is a real, narrowly scoped bug in the per-conversation Docker conversation runtime. The outer agent-server resolves title_llm_profile only as a name: AutoTitleSubscriber._load_title_llm calls get_llm_profile_store().load(profile_name, ...), and in Docker mode that store resolves under the container's OH_PERSISTENCE_DIR (/var/openhands/.openhands/profiles), which is the bind-mounted per-conversation runtime persistence dir and is seeded with nothing but .profiles.lock. title_llm_profile is carried across the boundary verbatim (docker_runtime/mediation.py::prepare_start/serialize_start), unlike agent_profile_id, which is resolved outer-side before serialization. So the profile definition never reaches the inner runtime.

Smallest coherent scope: at Docker container provisioning (docker_runtime/registry.py::_build_container), seed the per-conversation runtime profile store with the profile named by title_llm_profile (and only that profile), encrypted with the conversation's per-runtime key, so the existing inner LLMProfileStore loads it with no new wire field or endpoint. Reuse the authoritative LLMProfileStore / get_llm_profile_store() mechanism and the existing pydantic_secrets/Cipher handling; do not add a parallel store or redaction path. The public title_llm_profile request-contract semantics and the existing fallback precedence (title_llm_profile → agent.llm → truncation) stay unchanged.

Explicit non-goals:

  • Do not change title-generation precedence or fallback behavior for non-Docker/local runtimes.
  • Do not bind-mount the host OpenHands persistence directory into the conversation container, and do not copy unrelated host profiles.
  • Do not add a new REST endpoint or a new title-generation API surface.
  • The secondary LiteLLM invalid_api_key symptom is out of scope; the defect is the empty inner profile store.

Acceptance Criteria

  • With OH_CONVERSATION_RUNTIME=docker, creating a conversation whose title_llm_profile names an existing profile causes the inner agent-server to resolve that profile (no Profile <name> not found. Available profiles: none log entry), and the auto-generated conversation title is produced by that profile's LLM.
  • While such a conversation runtime is running, <OH_PERSISTENCE_DIR>/profiles inside the container contains the referenced profile's JSON file and no other host profile files; the container mounts remain limited to the per-conversation conversation, persistence, and workspace directories (the host OpenHands persistence directory is not mounted).
  • The propagated profile's API key is stored encrypted at rest under the per-runtime OH_SECRET_KEY and is decryptable by the inner LLMProfileStore; no plaintext key material is written to the runtime persistence directory.
  • An ACP conversation with a configured title_llm_profile generates its title via the auxiliary profile and does not fall back to the ACP agent's LLM or emit a LiteLLM invalid_api_key error.
  • Failure behavior is preserved: when title_llm_profile names a missing or unloadable profile, the inner server logs the existing warning and falls back to the agent's LLM (then truncation) without failing conversation creation.
  • A test under tests/agent_server/docker_runtime/ covers the Docker conversation runtime with a configured title_llm_profile, asserting the profile is made available to the inner runtime and is used for title generation.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority:normalStandard priority issueready-for-devIssue meets development readiness criteria

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions