OpenHands / OpenHands/software-agent-sdk
Support Unix domain socket binding in openhands-agent-server
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.1k
- Forks
- 542
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 137
Description
Problem
openhands-agent-server currently exposes only TCP binding through --host and --port. There is no supported way to bind the server to a Unix domain socket.
This matters for local container deployments where each isolated task has its own Agent Server. A Unix socket can provide filesystem-scoped connectivity without publishing a per-task TCP port or introducing another container network/proxy.
Reproduction
Tested with OpenHands SDK 1.35.0, using the binary shipped in Agent Canvas 1.3.0:
$ openhands-agent-server --help
usage: openhands-agent-server [-h] [--host HOST] [--port PORT] [--reload]
[--check-browser]
[--import-modules IMPORT_MODULES]
[--extra-python-path EXTRA_PYTHON_PATH]
$ openhands-agent-server --uds /tmp/agent-server.sock
openhands-agent-server: error: unrecognized arguments: --uds /tmp/agent-server.sock
$ echo $?
2
I searched existing open and closed issues for "unix socket", "unix domain socket", and "UDS" and did not find a duplicate.
Requested contract
- Add
--uds PATH, mutually exclusive with explicitly supplied--host/--port. - Preserve HTTP and WebSocket behavior.
- Make relative
LookupSecretURLs use the same UDS transport; simply passinguds=to Uvicorn is insufficient becauseOH_INTERNAL_SERVER_URLcurrently points internal secret lookups back to TCP. - Fail clearly for unsupported platforms or unsafe/unusable paths.
- Document socket ownership, permissions, stale-socket cleanup, and reload behavior.
- Add CLI/config and relative-secret integration coverage.
If there is already a supported public ASGI entry point that can be run under Uvicorn with --uds without depending on private module internals, documenting it would also solve the immediate problem.
Proposed patch
The following is a concrete minimal direction against current main (current files: openhands-agent-server/openhands/agent_server/__main__.py, openhands-sdk/openhands/sdk/secret/secrets.py, and tests/agent_server/test_preload_modules.py):
diff --git a/openhands-agent-server/openhands/agent_server/__main__.py b/openhands-agent-server/openhands/agent_server/__main__.py
--- a/openhands-agent-server/openhands/agent_server/__main__.py
+++ b/openhands-agent-server/openhands/agent_server/__main__.py
@@
_INTERNAL_SERVER_URL_ENV = "OH_INTERNAL_SERVER_URL"
+_INTERNAL_SERVER_UDS_ENV = "OH_INTERNAL_SERVER_UDS"
_EXTRA_PYTHON_PATH_ENV = "OH_EXTRA_PYTHON_PATH"
@@
parser.add_argument(
"--port", type=int, default=8000, help="Port to bind to (default: 8000)"
)
+ parser.add_argument(
+ "--uds",
+ default=None,
+ help="Unix domain socket to bind instead of TCP",
+ )
@@
args = parser.parse_args()
+ if args.uds and any(flag in sys.argv[1:] for flag in ("--host", "--port")):
+ parser.error("--uds cannot be combined with --host or --port")
+ if args.uds and os.name == "nt":
+ parser.error("--uds is not supported on Windows")
@@
- os.environ[_INTERNAL_SERVER_URL_ENV] = _get_internal_server_url(
- args.host, args.port
- )
+ if args.uds:
+ os.environ[_INTERNAL_SERVER_URL_ENV] = "http://localhost"
+ os.environ[_INTERNAL_SERVER_UDS_ENV] = args.uds
+ else:
+ os.environ.pop(_INTERNAL_SERVER_UDS_ENV, None)
+ os.environ[_INTERNAL_SERVER_URL_ENV] = _get_internal_server_url(
+ args.host, args.port
+ )
@@
- config = Config(
- "openhands.agent_server.api:api",
- host=args.host,
- port=args.port,
+ config_kwargs = dict(
reload=args.reload,
reload_includes=[
"openhands-agent-server",
"openhands-sdk",
"openhands-tools",
],
log_level=log_level,
log_config=LOGGING_CONFIG,
ws="wsproto",
)
+ if args.uds:
+ config = Config(
+ "openhands.agent_server.api:api", uds=args.uds, **config_kwargs
+ )
+ else:
+ config = Config(
+ "openhands.agent_server.api:api",
+ host=args.host,
+ port=args.port,
+ **config_kwargs,
+ )
diff --git a/openhands-sdk/openhands/sdk/secret/secrets.py b/openhands-sdk/openhands/sdk/secret/secrets.py
--- a/openhands-sdk/openhands/sdk/secret/secrets.py
+++ b/openhands-sdk/openhands/sdk/secret/secrets.py
@@
_INTERNAL_SERVER_URL_ENV = "OH_INTERNAL_SERVER_URL"
+_INTERNAL_SERVER_UDS_ENV = "OH_INTERNAL_SERVER_UDS"
_DEFAULT_INTERNAL_SERVER_URL = "http://127.0.0.1:8000"
@@
def get_value(self) -> str:
- response = httpx.get(self.url, headers=self.headers, timeout=30.0)
+ uds = os.getenv(_INTERNAL_SERVER_UDS_ENV)
+ if uds:
+ transport = httpx.HTTPTransport(uds=uds)
+ with httpx.Client(transport=transport, timeout=30.0) as client:
+ response = client.get(self.url, headers=self.headers)
+ else:
+ response = httpx.get(self.url, headers=self.headers, timeout=30.0)
response.raise_for_status()
return response.text
diff --git a/tests/agent_server/test_preload_modules.py b/tests/agent_server/test_preload_modules.py
--- a/tests/agent_server/test_preload_modules.py
+++ b/tests/agent_server/test_preload_modules.py
@@
class TestMainCheckBrowserOrdering:
+ def test_main_configures_unix_socket(self, monkeypatch, tmp_path):
+ socket_path = tmp_path / "agent-server.sock"
+ monkeypatch.delenv("OH_INTERNAL_SERVER_UDS", raising=False)
+
+ with (
+ patch("sys.argv", ["prog", "--uds", str(socket_path)]),
+ patch("openhands.agent_server.__main__.preload_modules"),
+ patch("openhands.agent_server.__main__.LoggingServer") as server_cls,
+ patch("openhands.agent_server.__main__.Config") as config,
+ ):
+ server_cls.return_value.run.side_effect = SystemExit(0)
+ from openhands.agent_server.__main__ import main
+ with pytest.raises(SystemExit):
+ main()
+
+ assert os.environ["OH_INTERNAL_SERVER_URL"] == "http://localhost"
+ assert os.environ["OH_INTERNAL_SERVER_UDS"] == str(socket_path)
+ assert config.call_args.kwargs["uds"] == str(socket_path)
+ assert "host" not in config.call_args.kwargs
+ assert "port" not in config.call_args.kwargs
The config_kwargs helper may need a small typed-dict annotation to satisfy the repository's type checker. The important part is treating Uvicorn binding and internal LookupSecret transport as one contract. I would also add a focused LookupSecret test that asserts httpx.HTTPTransport(uds=...) is selected for a relative URL.
Context
The goal is to avoid a custom ingress/proxy when all consumers are local and filesystem permissions can define the transport boundary. This is a feature request, not a claim that the current TCP interface is broken.
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 at openhands-agent-server/openhands/agent_server/main.py and trace how the CLI builds its server configuration and internal URL. Then read openhands-sdk/openhands/sdk/secret/secrets.py and tests/agent_server/test_preload_modules.py, adding focused coverage for the relative-secret transport. Done means the UDS CLI/config path, secret lookups, validation, documentation, and integration tests cover the requested contract.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, backend, cli
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100