Skip to content

Docker conversation runtime: built-in sub-agents are not registered (task tool fails with "none registered") #5380

Open

Description

@simonrosenberg

Summary

With the docker conversation runtime (OH_CONVERSATION_RUNTIME=docker), task_tool_set can't delegate to any built-in sub-agent. Every task call fails:

Failed to execute task: Unknown agent 'general-purpose'. Available types: none registered. Use register_agent() to add custom agent types.

The same happens for web-researcher, code-explorer and bash-runner. With the local runtime, the same profile and prompt delegate fine.

Reproduction

  • Host: Agent Canvas with OH_CONVERSATION_RUNTIME=docker.
  • Start a conversation whose agent has tools [terminal, task_tool_set].
  • Ask it to delegate to general-purpose (or web-researcher).
  • The TaskObservation has is_error: true, status: error, and the message above.

Evidence

Reproduced on the shared OSS automation VM, 2026-09-29:

conversation image host agent-server result
ghcr.io/openhands/agent-server:rel-1.49.6-python SDK #5151 head b3eb043 none registered
ghcr.io/openhands/agent-server:b3eb043-python SDK #5151 head b3eb043 none registered
local runtime (no container) SDK #5151 head b3eb043 (uvx) delegation works; the host logs Registered file-based agent 'general-purpose' … for all four built-ins

So this happens with a released image too, and is not introduced by #5151.

Relevant pieces:

  • tool_router.py calls register_builtins_agents() when it is imported.
  • agent-server.spec bundles openhands.tools.preset/subagents/*.md (fix(agent-server): bundle built-in subagent .md files in PyInstaller binary #3290).
  • docker_runtime/mediation.py (prepare_start / serialize_start) forwards no sub-agent definitions to the container, so the container relies entirely on its own registration.
  • In the frozen container the sub-agent registry is empty when TaskManager looks the agent up.

The root cause inside the container isn't confirmed yet. Candidates: the PyInstaller data path for subagents/, or the import-time registration not running in the process that serves conversations.

Impact

#5151 makes task_tool_set user-selectable in the Canvas tool catalog. On a docker-runtime deployment, users can pick it and it silently does nothing useful.

Suggested fix

  • Make the container register the built-in sub-agents, and add a smoke test that a docker-runtime conversation can list or launch general-purpose.
  • Alternatively (or as well), have the host forward its registered definitions through agent_definitions in serialize_start.

OpenHands AI triage

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

Triage

With OH_CONVERSATION_RUNTIME=docker, each conversation is served by its own agent-server container (docker_runtime/registry.py), and serialize_start (docker_runtime/mediation.py) forwards no sub-agent definitions, so the container relies on its own startup registration. The agent-server registers built-ins at import time (register_builtins_agents() in tool_router.py), and the .md definitions are already bundled in the PyInstaller build (agent-server.spec, #3290) and shipped as package data. The observed failure is that this registration is not in effect in the frozen container process that serves the conversation, so the registry is empty when TaskManager looks up general-purpose.

Scope: make the built-in sub-agents resolvable inside the docker conversation runtime so task_tool_set delegates to general-purpose, web-researcher, code-explorer, and bash-runner exactly as it does on the local runtime. The repository already has one authoritative registration path (agent-server startup registration of openhands.tools.preset/subagents/*.md); reuse it rather than adding a parallel mechanism.

Non-goals: no new built-in sub-agents; no change to local-runtime registration or precedence; no change to the agent_definitions request field or the agent-server REST/OpenAPI contract; no task-tool redesign.

Actual Behavior

Every task call in a docker-runtime conversation fails:

Failed to execute task: Unknown agent 'general-purpose'. Available types: none registered. Use register_agent() to add custom agent types.

The TaskObservation has is_error: true, status: error. The same happens for web-researcher, code-explorer, and bash-runner. Reproduced on ghcr.io/openhands/agent-server:rel-1.49.6-python and on a SHA-tagged image, so it is not introduced by #5151.

Steps to Reproduce

  1. Run Agent Canvas with OH_CONVERSATION_RUNTIME=docker.
  2. Start a conversation whose agent has tools [terminal, task_tool_set].
  3. Ask it to delegate to general-purpose (or web-researcher).
  4. Observe the error above. With the local runtime and the same profile and prompt, delegation succeeds.

Acceptance Criteria

  • With OH_CONVERSATION_RUNTIME=docker, a conversation whose agent includes task_tool_set can delegate to general-purpose, web-researcher, code-explorer, and bash-runner, and each call returns a TaskObservation with is_error: false / status: completed (no "none registered" error).
  • The container serving the conversation reports the built-in sub-agents as available (for example POST /api/sub-agents and/or the task tool description lists the built-ins), rather than an empty registry.
  • Delegating to a genuinely unknown sub-agent still returns the existing unknown-agent error; registry lookup failure behavior is unchanged.
  • web-researcher remains gated on browser availability (register_builtins_agents(enable_browser=...)): absent when browser is disabled, present when enabled, with the other three always present.
  • Local-runtime behavior is unchanged: the same profile and prompt delegate successfully as before.
  • The packaged conversation-container artifact ships the built-in sub-agent definitions (PyInstaller bundle and package data for openhands.tools.preset/subagents/*.md), verified by a packaging/build check rather than an in-process unit test alone.
  • A docker-runtime smoke/integration test starts a conversation and asserts a built-in sub-agent can be listed or launched; it fails against the pre-fix behavior.
  • The host-to-container agent_definitions forwarding and the agent-server REST/OpenAPI contract are unchanged (no breaking change).

Activity

  1. added
    priority:mediumFor bugs, a serious source of annoyance, but not blocking a large number of users.
    ready-for-devIssue meets development readiness criteria
    on Sep 29, 2026
  2. simonrosenberg commented on Oct 22, 2026

    @simonrosenberg
    MemberAuthor

    The same failure happens on OHE (Replicated), where each automation run starts its conversation in a cloud sandbox built from ghcr.io/openhands/agent-server:1.56.0-python. So it's not specific to the Canvas docker runtime: it's the agent-server image.

    What the image is missing. PyInstaller doesn't bundle any of openhands-tools' data files. I checked 1.53.0-python and 1.56.0-python locally (arm64; the OHE sandbox below is amd64 and behaves the same):

    • Inside the container, POST /api/sub-agents with {"load_user": false, "load_project": false, "load_builtin": true} returns {"agents": []}. That endpoint calls discover_builtin_agents() directly, which reads Path(__file__).parent / "subagents". So this isn't about when registration runs: the server can't find the .md files at all.
    • The extraction dir /tmp/_MEI*/openhands/ has sdk/context/condenser/prompts/ and sdk/context/prompts/templates/ (2 files each) and no tools/ dir.
    • So none of the collect_data_files("openhands.tools.*", …) entries in agent-server.spec adds anything. preset/subagents/*.md (4 files in the source tree) is missing, and so is browser_use/js/*.js (6 files). The openhands.sdk entries are bundled.

    That matches the first candidate in the issue, the PyInstaller data path. It also means the fix should cover every openhands.tools data entry, not only the sub-agents.

    On OHE (2026-10-20; OHE 0.85.0, enterprise 1.74.0, agent-server 1.56.0):

    • The run started its conversation with RemoteConversation.create(StartConversationRequest(agent_profile_id=…)), with no agent_definitions. The profile's tools were terminal, file_editor, task_tracker, task_tool_set.
    • Both of the model's task calls failed with Unknown agent 'general-purpose'. Available types: none registered.
    • The task tool's description said None: every registered agent uses tools this agent does not have, which points the reader at tool scoping, while in fact no agents were registered at all (openhands/tools/task/definition.py, the else branch when scope.restricts).
    • POST /api/sub-agents in that sandbox returned {"agents": []}, as above.
    • A conversation started in the same sandbox with agent_definitions=discover_builtin_agents(), computed on the client, delegated to general-purpose and got its answer back.

    Enterprise forwards agent_definitions when it starts a conversation itself, so this only shows when a client starts the conversation directly on the sandbox's agent server. OpenHands/automation#614 does the same forwarding as a workaround until the image ships the files.


    I'm an AI agent (Claude Code) helping @simonrosenberg.

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

    priority:mediumFor bugs, a serious source of annoyance, but not blocking a large number of users.ready-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