OpenHands / OpenHands/software-agent-sdk
[Feature]: Injectable WebSocket client factory for RemoteConversation
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.1k
- Forks
- 539
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 137
Description
Is there an existing feature request for this?
- I have searched existing issues and feature requests, and this is not a duplicate.
Problem or Use Case
RemoteConversation constructs WebSocketCallbackClient directly at a single
point in __init__. There is no supported way for a caller to supply a
transport-specific callback client.
The concrete use case is running the controller with --network none, talking to
an agent server over a Unix-domain socket via a relay, so a WebSocket-over-UDS
client is needed in place of the default TCP one.
Today the only way to achieve this is to replace the module global
openhands.sdk.conversation.impl.remote_conversation.WebSocketCallbackClient,
which is process-wide. That prevents two conversations in the same process from
using different transports, and it depends on a private module attribute that is
not part of the public API.
Desired Behavior
Add an optional, keyword-only websocket_client_factory argument to
RemoteConversation, plus a typed WebSocketClientFactory protocol describing
the construction contract (host, conversation_id, callback, api_key,
on_reconnect).
When the argument is omitted, behavior is unchanged: the default
WebSocketCallbackClient is used. The existing client keeps ownership of
startup, readiness waiting, initial reconciliation, failure cleanup, and
shutdown — the factory only decides which client is constructed.
A Protocol is preferred over a bare Callable[..., Any] so an incompatible
factory is rejected at type-check time rather than at WebSocket-connect time.
The default must stay late-bound (resolved when __init__ runs, not captured as
a default argument value), because the existing test suite patches the module
attribute in 87 patch() sites across 10 test files.
Acceptance Criteria
-
RemoteConversationaccepts an optionalwebsocket_client_factorykeyword argument - A
WebSocketClientFactoryprotocol types the contract withhost,conversation_id,callback,api_key, andon_reconnect - An injected factory is used instead of the default, and receives all five keyword arguments
- Omitting the argument still constructs
WebSocketCallbackClient, resolved at call time so existing module-level patching keeps working -
WebSocketCallbackClientsatisfies the protocol's keyword contract -
uv run pytest tests/sdk/conversationpasses with no new failures
Alternatives Considered
Replacing the module global WebSocketCallbackClient at runtime. This works, but
it is process-wide, so conversations cannot use independent transports, and it
relies on a private attribute with no compatibility guarantee.
Subclassing RemoteConversation to override client construction. The
construction is inline in __init__ rather than in an overridable method, so
this would mean copying the whole __init__ body.
Priority / Severity
Medium - Would improve experience
Estimated Scope
Small - API addition, config option, or minor change
Feature Area
- Agent API / Core functionality
- Tools / Tool system
- Skills / Plugins
- Agent Server
- Workspace management
- Configuration / Settings
- Examples / Templates
- Documentation
- Testing / Development tools
- Performance / Optimization
- Integrations (GitHub, APIs, etc.)
- Other
Technical Implementation Ideas (Optional)
No response
Additional Context
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in openhands.sdk.conversation.impl.remote_conversation, focusing on RemoteConversation.init and the existing WebSocketCallbackClient construction. Read the tests under tests/sdk/conversation, then run uv run pytest tests/sdk/conversation while checking the acceptance criteria: the injected factory receives all five keyword arguments, and the default remains late-bound and compatible with existing patches.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, backend
- Issue type
- Feature
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100