Repository navigation
Docker conversation runtime: built-in sub-agents are not registered (task tool fails with "none registered") #5380
Description
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(orweb-researcher). - The
TaskObservationhasis_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.pycallsregister_builtins_agents()when it is imported.agent-server.specbundlesopenhands.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
TaskManagerlooks 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_definitionsinserialize_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
- Run 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(orweb-researcher). - 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 includestask_tool_setcan delegate togeneral-purpose,web-researcher,code-explorer, andbash-runner, and each call returns aTaskObservationwithis_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-agentsand/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-researcherremains 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_definitionsforwarding and the agent-server REST/OpenAPI contract are unchanged (no breaking change).
Activity
fix: forward the built-in sub-agents when a plugin-spec run starts it…
fix: forward the built-in sub-agents when a plugin-spec run starts it…
simonrosenberg commentedon Oct 22, 2026
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 checked1.53.0-pythonand1.56.0-pythonlocally (arm64; the OHE sandbox below is amd64 and behaves the same):POST /api/sub-agentswith{"load_user": false, "load_project": false, "load_builtin": true}returns{"agents": []}. That endpoint callsdiscover_builtin_agents()directly, which readsPath(__file__).parent / "subagents". So this isn't about when registration runs: the server can't find the.mdfiles at all./tmp/_MEI*/openhands/hassdk/context/condenser/prompts/andsdk/context/prompts/templates/(2 files each) and notools/dir.collect_data_files("openhands.tools.*", …)entries inagent-server.specadds anything.preset/subagents/*.md(4 files in the source tree) is missing, and so isbrowser_use/js/*.js(6 files). Theopenhands.sdkentries are bundled.That matches the first candidate in the issue, the PyInstaller data path. It also means the fix should cover every
openhands.toolsdata entry, not only the sub-agents.On OHE (2026-10-20; OHE 0.85.0, enterprise 1.74.0, agent-server 1.56.0):
RemoteConversation.create(StartConversationRequest(agent_profile_id=…)), with noagent_definitions. The profile's tools wereterminal, file_editor, task_tracker, task_tool_set.taskcalls failed withUnknown agent 'general-purpose'. Available types: none registered.tasktool's description saidNone: 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, theelsebranch whenscope.restricts).POST /api/sub-agentsin that sandbox returned{"agents": []}, as above.agent_definitions=discover_builtin_agents(), computed on the client, delegated togeneral-purposeand got its answer back.Enterprise forwards
agent_definitionswhen 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.