Skip to content

feat: add local_agent_server_mode to OpenHandsCloudWorkspace#2490

Merged
xingyaoww merged 17 commits intomainfrom
feat/saas-runtime-mode
Mar 26, 2026
Merged

feat: add local_agent_server_mode to OpenHandsCloudWorkspace#2490
xingyaoww merged 17 commits intomainfrom
feat/saas-runtime-mode

Conversation

@xingyaoww
Copy link
Copy Markdown
Collaborator

@xingyaoww xingyaoww commented Mar 18, 2026

Summary

Add local_agent_server_mode to OpenHandsCloudWorkspace to support the automation service architecture described in RFC #13275.

When local_agent_server_mode=True, the workspace assumes it is already running inside an OpenHands Cloud Runtime sandbox. Instead of creating or managing a sandbox via the Cloud API, it connects directly to the local agent-server at http://localhost:<port> (default port 60000). Cloud API credentials are still required for get_llms() / get_secrets() calls.

New fields:

  • local_agent_server_mode: bool = False — enables local agent-server mode
  • agent_server_port: int = 60000 — configurable local port

Behavioral changes:

  • model_post_init skips sandbox creation/wait in local agent-server mode
  • cleanup() only closes the HTTP client in local agent-server mode (no sandbox deletion)
  • automation_callback_url and automation_run_id are read from env vars (AUTOMATION_CALLBACK_URL, AUTOMATION_RUN_ID) rather than constructor args

Usage:

workspace = OpenHandsCloudWorkspace(
    local_agent_server_mode=True,
    cloud_api_url="https://app.all-hands.dev",
    cloud_api_key=os.environ["OPENHANDS_API_KEY"],
)

Checklist

  • If the PR is changing/adding functionality, are there tests to reflect this?
  • If there is an example, have you run the example to make sure that it works?
  • If there are instructions on how to run the code, have you followed the instructions and made sure that it works?
  • If the feature is significant enough to require documentation, is there a PR open on the OpenHands/docs repository with the same branch name?
  • Is the github CI passing?

Agent Server images for this PR

GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22 Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:33e1694-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-33e1694-python \
  ghcr.io/openhands/agent-server:33e1694-python

All tags pushed for this build

ghcr.io/openhands/agent-server:33e1694-golang-amd64
ghcr.io/openhands/agent-server:33e1694-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:33e1694-golang-arm64
ghcr.io/openhands/agent-server:33e1694-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:33e1694-java-amd64
ghcr.io/openhands/agent-server:33e1694-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:33e1694-java-arm64
ghcr.io/openhands/agent-server:33e1694-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:33e1694-python-amd64
ghcr.io/openhands/agent-server:33e1694-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-amd64
ghcr.io/openhands/agent-server:33e1694-python-arm64
ghcr.io/openhands/agent-server:33e1694-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-arm64
ghcr.io/openhands/agent-server:33e1694-golang
ghcr.io/openhands/agent-server:33e1694-java
ghcr.io/openhands/agent-server:33e1694-python

About Multi-Architecture Support

  • Each variant tag (e.g., 33e1694-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., 33e1694-python-amd64) are also available if needed

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 18, 2026

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 18, 2026

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

Copy link
Copy Markdown
Collaborator Author

Added a commit to expose the SDK packages (openhands-sdk, openhands-workspace, openhands-tools) to the system Python in the source and source-minimal Docker targets via PYTHONPATH.

This is needed for the automations service dispatcher (deploy PR #3456) — when an automation script runs inside a sandbox with saas_runtime_mode=True, it needs to import openhands.workspace etc. Without this, those packages are only available inside the agent-server's venv, not to the system Python that user scripts run under.

Once this PR's image finishes building, we'll reference the new image tag in the deploy PR to enable full SDK automation scripts.

When saas_runtime_mode=True, the workspace assumes it is already running
inside an OpenHands Cloud Runtime sandbox and connects directly to the
local agent-server at localhost instead of provisioning a sandbox via the
Cloud API.

This supports the automation service architecture (ADR-0002) where SDK
scripts execute inside pre-existing Cloud Runtimes. The workspace skips
sandbox creation/deletion and points the HTTP client at localhost:<port>.

Changes:
- Add saas_runtime_mode and agent_server_port fields
- Make cloud_api_url/cloud_api_key optional (with validator enforcing
  them when saas_runtime_mode=False)
- Add _init_saas_runtime_mode() for the local init path
- Skip sandbox cleanup in saas_runtime_mode
- Add 8 new tests covering the feature

Co-authored-by: openhands <openhands@all-hands.dev>
cloud_api_url and cloud_api_key are needed even in saas_runtime_mode
for get_llms() and get_secrets() calls to the Cloud API. Reverts them
to required str fields and removes the conditional model_validator.

Co-authored-by: openhands <openhands@all-hands.dev>
Add PYTHONPATH pointing to the agent-server venv's site-packages in
both source and source-minimal Docker targets. This allows user scripts
running inside the sandbox (e.g. automation entrypoints using
OpenHandsCloudWorkspace with saas_runtime_mode=True) to import
openhands-sdk, openhands-workspace, and openhands-tools directly
without needing to activate the venv.

Co-authored-by: openhands <openhands@all-hands.dev>
When automation_callback_url is set, __exit__ POSTs completion status
(COMPLETED or FAILED with error detail) to the automation service before
cleanup(). Best-effort — callback failures are logged, not raised.

New fields:
- automation_callback_url: URL to POST to on exit
- automation_run_id: included in callback payload

Co-authored-by: openhands <openhands@all-hands.dev>
@xingyaoww xingyaoww force-pushed the feat/saas-runtime-mode branch from 62d08aa to b3841c4 Compare March 23, 2026 11:50
Default to AGENT_SERVER_PORT environment variable for the agent server
port, falling back to 60000 if not set. Explicit kwarg still takes
precedence over the env var.

Co-authored-by: openhands <openhands@all-hands.dev>
_init_saas_runtime_mode() now reads sandbox identity from env vars
so get_llm() and get_secrets() work when the SDK runs inside a
Cloud sandbox (automation dispatch path).

Env vars (injected by the automation dispatcher):
  SANDBOX_ID       — sandbox's Cloud API identifier
  SESSION_API_KEY  — session key for sandbox settings auth
  (falls back to OH_SESSION_API_KEYS_0 set by the runtime)

Also reads AGENT_SERVER_PORT env var for the local agent-server port.
Constructor param sandbox_id= takes precedence over env var.

Also fixes pre-existing test URL mismatch in test_cleanup_deletes_sandbox.

Co-authored-by: openhands <openhands@all-hands.dev>
@xingyaoww
Copy link
Copy Markdown
Collaborator Author

@OpenHands please fix https://github.com/OpenHands/software-agent-sdk/actions/runs/23438124731/job/68181392308?pr=2490

@openhands-ai
Copy link
Copy Markdown

openhands-ai bot commented Mar 23, 2026

I'm on it! xingyaoww can track my progress at all-hands.dev

Co-authored-by: openhands <openhands@all-hands.dev>
@openhands-ai
Copy link
Copy Markdown

openhands-ai bot commented Mar 23, 2026

Summary

The CI failure in PR #2490 (job 68181392308 from workflow run 23438124731) has been fully fixed and pushed.

What was wrong

The pre-commit job failed because ruff-format detected formatting issues in code introduced by the PR.

What was fixed (commit 71f66416)

Three formatting corrections across two files:

  1. openhands-workspace/openhands/workspace/cloud/workspace.py:

    • Collapsed an unnecessarily multi-line parenthesized description string for automation_run_id
    • Collapsed an unnecessarily multi-line logger.info() call into a single line
  2. tests/workspace/test_cloud_workspace.py:

    • Fixed tuple unpacking syntax: url, = ...(url,) = ...

Checklist

  • Identified the root cause from the CI logs (ruff-format reformatting)
  • Applied exactly the formatting changes ruff-format expected — no extraneous changes
  • Verified all pre-commit hooks pass locally before pushing
  • Committed and pushed to feat/saas-runtime-mode to update PR feat: add local_agent_server_mode to OpenHandsCloudWorkspace #2490

@xingyaoww xingyaoww requested a review from all-hands-bot March 23, 2026 14:54
@xingyaoww xingyaoww marked this pull request as ready for review March 23, 2026 14:54
Copy link
Copy Markdown
Collaborator

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Good taste - Clean implementation with solid engineering fundamentals.

What's good:
• Simple data structures eliminate complexity
• Low nesting (max 2 levels), proper early returns
• Comprehensive test coverage for new behavior
• Backward compatible (all new fields default to existing behavior)
• Fixes test bug for cleanup endpoint
• Best-effort callback design won't break on errors

Minor note:
The test fix for test_cleanup_deletes_sandbox (line 167) corrects a pre-existing mismatch between test expectation and implementation. Worth mentioning in the PR description as a drive-by fix.

_init_saas_runtime_mode() was setting self._session_api_key but not
propagating it to self.api_key (the RemoteWorkspaceMixin field).
The shared HTTP client (used by RemoteConversation) builds its headers
from self.api_key via _headers, so conversations got no
X-Session-API-Key header → 401 from the local agent-server.

Mirrors what _start_sandbox() already does at line 268.

Co-authored-by: openhands <openhands@all-hands.dev>
@xingyaoww xingyaoww force-pushed the feat/saas-runtime-mode branch from a4d2797 to 13a4aa6 Compare March 23, 2026 15:10
@xingyaoww xingyaoww requested a review from all-hands-bot March 23, 2026 15:18
Copy link
Copy Markdown
Collaborator

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Good taste - Clean implementation that solves a real problem.

What works:
• Simple data structures, no unnecessary complexity
• Low nesting (max 2-3 levels), clean control flow
• Backward compatible - all defaults preserve existing behavior
• Comprehensive test coverage for new paths
• Pragmatic error handling (best-effort callback, defensive cleanup guards)
• Fixes pre-existing test bug for cleanup endpoint URL

KEY INSIGHT: The dual-mode design (normal vs SaaS runtime) is handled elegantly through early branching in model_post_init, eliminating special cases throughout the rest of the code.

@xingyaoww xingyaoww marked this pull request as draft March 23, 2026 15:32
openhands-agent and others added 2 commits March 23, 2026 17:53
Prevent LLM-driven agents from accessing SESSION_API_KEY via terminal
commands. This credential grants access to user secrets via the SaaS API
and must remain isolated to the SDK's Python process.

- Add SESSION_API_KEY to _SENSITIVE_ENV_VARS in sanitized_env()
- Add security tests verifying terminal tool cannot access the key

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 23, 2026

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-sdk/openhands/sdk/utils
   command.py593737%61–64, 66–68, 70, 80–81, 84–87, 89–97, 100, 103, 107–108, 110–116, 123–124, 126
openhands-workspace/openhands/workspace/cloud
   workspace.py28722521%36–38, 152–154, 160, 163–164, 172, 176, 178–179, 181–186, 204–206, 211–212, 217–218, 220–221, 226–227, 234, 236, 238, 246–247, 249, 252, 255–257, 261, 264–265, 268, 271–272, 276, 279–281, 284, 290, 292–294, 303–306, 316, 319, 324, 326–327, 329–331, 333, 335–336, 339–342, 344–345, 347–348, 350, 352–354, 357–358, 362–373, 377–378, 380–381, 389–390, 392–394, 396, 407, 420–421, 423–426, 430, 433–434, 437–439, 441–449, 451, 461–464, 466–472, 474–475, 477–480, 482–483, 489–491, 493–500, 509, 514, 547, 549–550, 552, 558, 560–566, 569, 571, 604, 606–607, 609–610, 612–617, 623, 638–639, 641–643, 645–653, 655, 658, 661, 664–665, 675–678, 680–681, 683–688, 690–696
TOTAL21141527175% 

malhotra5 pushed a commit to OpenHands/OpenHands that referenced this pull request Mar 23, 2026
Pin SDK packages to commit 8e797ec3 which includes the security fix
that strips SESSION_API_KEY from subprocess environments, preventing
LLM-driven agents from accessing user secrets via terminal commands.

SDK PR: OpenHands/software-agent-sdk#2490
Agent-server image: 9c0a58d-python (merge-commit SHA from CI)

Co-authored-by: openhands <openhands@all-hands.dev>
@jpshackelford
Copy link
Copy Markdown
Contributor

@xingyaoww @malhotra5 I was trying to make sure I understood this PR and in the process created some additional documentation at OpenHands/docs#414. Would your feedback.

Co-authored-by: openhands <openhands@all-hands.dev>
The automation service's completion callback endpoint requires authentication
via Bearer token. Without this header, the callback returns 401 Unauthorized.

The cloud_api_key is already available on the workspace instance (set from
OPENHANDS_API_KEY env var), so we just need to include it in the request.

Co-authored-by: openhands <openhands@all-hands.dev>
Copy link
Copy Markdown
Collaborator

@malhotra5 malhotra5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

…utomation fields from env vars

- Rename saas_runtime_mode to local_agent_server_mode for clarity
- Move automation_callback_url and automation_run_id from constructor
  fields to private attributes read from env vars (AUTOMATION_CALLBACK_URL,
  AUTOMATION_RUN_ID) during local_agent_server_mode init
- Update all tests accordingly

Co-authored-by: openhands <openhands@all-hands.dev>
@xingyaoww xingyaoww changed the title feat: add saas_runtime_mode to OpenHandsCloudWorkspace feat: add local_agent_server_mode to OpenHandsCloudWorkspace Mar 25, 2026
@xingyaoww xingyaoww marked this pull request as ready for review March 26, 2026 15:42
@xingyaoww xingyaoww merged commit d772e3d into main Mar 26, 2026
29 of 30 checks passed
@xingyaoww xingyaoww deleted the feat/saas-runtime-mode branch March 26, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants