Hi folks, and congrats on successfully transitioni...
# questions
f
Hi folks, and congrats on successfully transitioning to software-agent-sdk as the agent backend! Now that OpenHands is using software-agent-sdk under the hood, does this make it easier to integrate a custom agent (built with software-agent-sdk) into the local GUI? Are there any plans to add a guide or docs for this use case?
c
Hey, I think it should
Let me try to get OH to help us, let’s see if it wants to cooperate
@nutritious-balloon-65098 In OpenHands/OpenHands repo, we have a bit of chaos you need to be attentive to: we have both V0 (legacy version, 0.62 or so) code and V1 code, 1.0.0. We want to work only with V1 code. Find the app_server module and understand that all in app_server is V1 and all on its execution paths. (V1_enabled) Clone yourself docs/ repo from the same org, and software-agent-sdk repo. Goal: write a guide on 1) running OpenHands app-server on a totally local agent-sdk agent-server (no docker, no remote api) 2) how to build a custom agent in the sdk and integrate it with the app-server running locally Open a docs PR with the guide. Investigate and think deeply, and remember to keep the execution paths to/from app-server so you know you use V1. In the guide, use markdown links with text, for the source files from github, e.g. [process_sandbox_service](https://github.com…/process_sandbox_service.py#L127) , for the main source locations.
n
I'm on it! can track my progress here.
Summary of work completed What I changed • Created a new guide in the OpenHands/docs repo: ◦ File: openhands/usage/developers/v1-local-agent-server.mdx ◦ Branch: docs/v1-local-agent-server-guide ◦ PR (draft): https://github.com/OpenHands/docs/pull/194 What the guide covers • Running the OpenHands V1 app-server with a purely local software-agent-sdk agent-server (no Docker, no remote API): ◦ Set RUNTIME=local (or process) to select the process-based sandbox which spawns python -m openhands.agent_server ◦ Start the app and on V1 conversation creation, the process sandbox starts the local agent-server and probes /alive ◦ Verify health endpoints (/alive, /server_info) and understand the wiring • Building a custom agent with the SDK and integrating it locally: ◦ Zero-code customization via Skills and MCP server configuration (user/org/repo skills, default MCP servers, optional Tavily) ◦ How V1 composes an SDK Agent (DEFAULT or PLAN) and sends StartConversationRequest to the agent-server ◦ Advanced option to add a new AgentType to OpenHands to introduce a custom agent profile How I ensured this is V1 and correct • Verified execution paths are V1-enabled: ◦ v1_router exposed under /api/v1 and included when ENABLE_V1 != '0' ◦ RUNTIME=local/process selects ProcessSandboxService + ProcessSandboxSpecService which spawn python -m openhands.agent_server ◦ Agent-server health endpoints and API are from software-agent-sdk ◦ App-server uses AsyncRemoteWorkspace to communicate with agent-server and builds StartConversationRequest in LiveStatusAppConversationService • Included markdown links (with text) directly to the relevant source files and line ranges in GitHub throughout the guide. Checklist against your request • Work only with V1 code (app_server is V1, execution paths checked): Done • Clone docs and software-agent-sdk repos: Done • Write a guide on: ◦ Running OpenHands app-server on a totally local agent-sdk agent-server (no docker, no remote api): Done ◦ How to build a custom agent in the sdk and integrate it locally: Done • Use markdown links with text for source files from GitHub: Done • Open a docs PR with the guide: Done (draft PR #194) Diff review • Only one new file added: openhands/usage/developers/v1-local-agent-server.mdx • No extraneous changes. Next steps • Do you want me to mark the PR ready for review? • Should I add a link to this page from a higher-level index (e.g., Developers or Runtimes overview)? <sub>View full conversation</sub>
f
oh thank you so much for the docs update. I'll look into the process_sandbox_service.py code.❤️
c
I haven’t read it yet but at a quick look it seems ok @flaky-engineer-19286
f
cool, thank you so much, I'll try this guide and provide some feedback if anything goes wrong.🙌
❤️ 1
n
Final summary of changes (since previous summary) What changed just now • Fixed the MDX parsing error flagged by your link checker on the new guide: ◦ Wrapped JSON and path examples in code spans to prevent MDX from parsing braces: ▪︎ GET
{agent_server_url}/alive
→
{"status":"ok"}
▪︎ GET
{agent_server_url}/server_info
• Committed and pushed the fix to the same branch and updated the draft PR. PR and branch • File: openhands/usage/developers/v1-local-agent-server.mdx • Branch: docs/v1-local-agent-server-guide • PR (draft): https://github.com/OpenHands/docs/pull/194 • Latest commit: “docs: fix MDX code span for alive/server_info to satisfy link checker” Checklist (complete) • Use only V1 execution paths (app_server) and verify end-to-end flow to agent-server: Done • Add guide covering: ◦ Running app-server with a purely local software-agent-sdk agent-server (no Docker, no remote API): Done ◦ Building a custom agent with the SDK and integrating it locally: Done • Include GitHub source links with link text to the main source locations: Done • Open a docs PR with the guide: Done (draft PR) • Changes concise: Yes (one new MDX file, one minimal fix commit). No extraneous changes. Open item (naming) • Your note about removing “v1” from the filename is acknowledged. I can rename the file to openhands/usage/developers/local-agent-server.mdx and update the PR. Would you like me to proceed with the rename and re-run link checks? <sub>View full conversation</sub>
f
Hi Engel. I think there is some issues with the current guide for starting a local GUI. I followed the guide generated by the OpenHands bot and used
Copy code
export INSTALL_DOCKER=0         # No Docker for this guide
export RUNTIME=local            # Force process-based sandbox (spawns local agent-server)
# optional: export ENABLE_V1=1  # V1 is enabled by default; set explicitly if you disabled it elsewhere

make build
to install the OpenHands latest repo. But when I ran
Copy code
make run FRONTEND_PORT=12000 FRONTEND_HOST=0.0.0.0 BACKEND_HOST=0.0.0.0
It throws an error like this:
Copy code
Running the app...
Starting backend server...
Waiting for the backend to start...
18:18:59 - openhands:INFO: utils.py:152 - config.toml not found: [Errno 2] No such file or directory: 'config.toml'. Toml values have not been applied.
18:18:59 - openhands:INFO: server_config.py:55 - Using config class None
/home/wsy0227/.cache/pypoetry/virtualenvs/openhands-ai-XTtcVfrc-py3.12/lib/python3.12/site-packages/fastmcp/server/server.py:255: DeprecationWarning: Providing `stateless_http` when creating a server is deprecated. Provide it when calling `run` or as a global setting instead.
  self._handle_deprecated_settings(
INFO:     Started server process [1967469]
INFO:     Waiting for application startup.
/home/wsy0227/debugging_repo/OpenHands/openhands/runtime/impl/local/local_runtime.py:111: DeprecationWarning: Session.attached_pane() is deprecated in favor of Session.active_pane()
  pane = session.attached_pane
/home/wsy0227/debugging_repo/OpenHands/openhands/runtime/impl/local/local_runtime.py:114: DeprecationWarning: Session.kill_session() is deprecated in favor of Session.kill()
  session.kill_session()
Process Process-1:
Traceback (most recent call last):
  File "/mnt/disk1/conda_envs/openhands-localgui/lib/python3.12/multiprocessing/process.py", line 314, in _bootstrap
    self.run()
  File "/mnt/disk1/conda_envs/openhands-localgui/lib/python3.12/multiprocessing/process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File "/home/wsy0227/debugging_repo/OpenHands/openhands/runtime/browser/browser_env.py", line 101, in browser_process
    obs, info = env.reset()
                ^^^^^^^^^^^
  File "/home/wsy0227/.cache/pypoetry/virtualenvs/openhands-ai-XTtcVfrc-py3.12/lib/python3.12/site-packages/gymnasium/wrappers/common.py", line 400, in reset
    return super().reset(seed=seed, options=options)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wsy0227/.cache/pypoetry/virtualenvs/openhands-ai-XTtcVfrc-py3.12/lib/python3.12/site-packages/gymnasium/core.py", line 333, in reset
    return self.env.reset(seed=seed, options=options)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wsy0227/.cache/pypoetry/virtualenvs/openhands-ai-XTtcVfrc-py3.12/lib/python3.12/site-packages/browsergym/core/env.py", line 232, in reset
    self.browser = pw.chromium.launch(
                   ^^^^^^^^^^^^^^^^^^^
  File "/home/wsy0227/.cache/pypoetry/virtualenvs/openhands-ai-XTtcVfrc-py3.12/lib/python3.12/site-packages/playwright/sync_api/_generated.py", line 14502, in launch
    self._sync(
  File "/home/wsy0227/.cache/pypoetry/virtualenvs/openhands-ai-XTtcVfrc-py3.12/lib/python3.12/site-packages/playwright/_impl/_sync_base.py", line 115, in _sync
    return task.result()
           ^^^^^^^^^^^^^
  File "/home/wsy0227/.cache/pypoetry/virtualenvs/openhands-ai-XTtcVfrc-py3.12/lib/python3.12/site-packages/playwright/_impl/_browser_type.py", line 98, in launch
    await self._channel.send(
  File "/home/wsy0227/.cache/pypoetry/virtualenvs/openhands-ai-XTtcVfrc-py3.12/lib/python3.12/site-packages/playwright/_impl/_connection.py", line 69, in send
    return await self._connection.wrap_api_call(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wsy0227/.cache/pypoetry/virtualenvs/openhands-ai-XTtcVfrc-py3.12/lib/python3.12/site-packages/playwright/_impl/_connection.py", line 558, in wrap_api_call
    raise rewrite_error(error, f"{parsed_st['apiName']}: {error}") from None
playwright._impl._errors.Error: BrowserType.launch: EACCES: permission denied, mkdir '/workspace'
I think this is an issue with the V0 version where the agent runs are managed by runtime instead of sandboxes. When the runtime is not run on dockers, /workspace is not accessible to the BrowserGym for cache downloading. I have an issue and PR on this regarding V0 runtime: https://github.com/OpenHands/OpenHands/pull/12015 . My question is: since OpenHands now uses the sandbox-based approach to manage agent execution, shouldn’t this kind of error go away? My understanding is that EACCES: permission denied, mkdir '/workspace' is mainly a V0 local-runtime issue (where runs are managed by the runtime directly rather than sandboxes), as described in my previous issue/PR. So I’m wondering which of the following is true: 1. My startup/config is still inadvertently triggering the V0 runtime path instead of the sandbox-based (V1) path; or 2. V1 still relies on the same BrowserGym/Playwright setup that tries to use /workspace (e.g., for downloads/cache), so the error can still happen even under V1, in this case I think my previous PR still applies
c
Right! I thought it should not be using this from V0
f
Oh now I found the problem
c
what was it?
f
v0 and v1 co-exists when the service started instead when ENABLE_V1!=0 then v1 is exclusive
this caused v0 to start from the beginning and failed immediately since it has this bug in the local runtime
c
aha, I do think only V1 should start now
f
oh then I don't know what is the problem, but from the error itself, I think it's still from the V0 side.
c
I'm not able to look into why it’s doing that, do you think you can disable it so that it works? Maybe ask OH on a side, it is able to debug ! 🙏
or apply your patch
f
oh yes i'll dig into it more later, thank you so much.
Hi Engel — I think I’ve narrowed down why I still hit
BrowserGym/Playwright
(and
/workspace
) errors even though the project is intended to be V1-only now. What I expected If the app is truly “V1-only” (software-agent-sdk agent-server + sandbox-managed execution), then the legacy runtime BrowserGym code (
openhands/runtime/browser/browser_env.py
) shouldn’t be executed during normal server startup / V1 conversation start. What I’m observing When I export
RUNTIME=local
and start the server,
BrowserEnv()
is initialized and
openhands/runtime/browser/browser_env.py
runs (which previously led to
EACCES: permission denied, mkdir '/workspace'
on non-docker local setups). Why this seems to happen (code path) It looks like
RUNTIME=local
still affects the legacy runtime initialization at server startup: 1.
RUNTIME=local
→
OpenHandsConfig.runtime = "local"
via env loading 2. On server startup, the FastAPI lifespan always enters the conversation manager: -
openhands/server/app.py:56
(
async with conversation_manager:
) 3. The conversation manager unconditionally initializes the selected runtime: -
openhands/server/conversation_manager/standalone_conversation_manager.py:82
(
get_runtime_cls(self.config.runtime).setup(self.config)
) 4. If
runtime == "local"
, this calls
LocalRuntime.setup()
, which runs dependency checks and instantiates `BrowserEnv()`: -
openhands/runtime/impl/local/local_runtime.py:398
(
LocalRuntime.setup
) - `openhands/runtime/impl/local/local_runtime.py:119`–`123` (
browser = BrowserEnv()
) → which executes
openhands/runtime/browser/browser_env.py
So even if V1 is enabled/used for conversations, setting
RUNTIME=local
appears to pull the process into legacy runtime setup, which then triggers BrowserGym.
c
wait…
standalone_conversation_manager
< this one I thought it shouldn’t be active
f
is it because I have
export RUNTIME=local
, cuz when I have
unset RUNTIME
I don't see any errors from BrowserGym
c
could be!
but you do want to use local mode (process “sandbox”) I think
f
yes I want the local mode, but the problem is that v0 and v1 both checks for
RUNTIME
and perhaps it falsely triggered v0
so it tries to work with both RUNTIME old and new
f
I think the root cause is the
RUNTIME
env var name collision. - In OSS server startup, we still go through
StandaloneConversationManager.__aenter__
, which calls
get_runtime_cls(config.runtime).setup(...)
. So if I set
RUNTIME=local
, it sets
OpenHandsConfig.runtime=local
and triggers the legacy
LocalRuntime.setup()
(and its BrowserEnv/BrowserGym dependency checks). - Separately, the v1 app_server sandbox fallback also checks
os.getenv('RUNTIME')
to decide between docker/process/remote sandbox. So when I set
RUNTIME=local
intending “v1 process sandbox”, it also unintentionally triggers the legacy runtime path and BrowserGym. When I unset
RUNTIME
, that legacy path doesn’t get forced to
LocalRuntime
, so the BrowserGym errors disappear.
c
how about
RUNTIME = ‘process’
?
f
Yeah so I guess this bug(perhaps a bug) is causing v1 and v0 to co-exist?
I'll try RUNTIME=process now
c
process seems like it should work with V1
definitely, it’s a bit messy, thank you and sorry
f
yeah I don't see any browsergym errors from
RUNTIME=process
No worries at all, happy to contribute❤️
Given the
RUNTIME
collision, what direction do you prefer for a PR? 1. document that V1 local mode is
RUNTIME=process
and add a warning when
RUNTIME=local
is used (since it triggers legacy LocalRuntime/BrowserGym setup). 2. in V1 server mode, treat
RUNTIME=local
as process sandbox and prevent it from selecting legacy LocalRuntime (or at least warn + ignore). 3. split env vars (e.g.
OH_SANDBOX_MODE
for v1,
OH_LEGACY_RUNTIME
for legacy) and deprecate the old
RUNTIME
usage. Also: do you expect
StandaloneConversationManager
to be inactive in OSS when V1 is enabled? If so, we can add a V1-only server mode switch that skips the legacy lifespan/routes.