Skip to content

feat(sdk): extend existing conversation and workspace APIs for automation - #5010

Merged
neubig merged 25 commits into
mainfrom
feat/agent-server-python-client
Sep 14, 2026
Merged

feat(sdk): extend existing conversation and workspace APIs for automation#5010
neubig merged 25 commits into
mainfrom
feat/agent-server-python-client

Conversation

@neubig

@neubig neubig commented Sep 12, 2026

Copy link
Copy Markdown
Member

HUMAN:

This stack of PRs is working in my "software factory" prototype, so I have tested that it's working properly.


AGENT:

Why

This PR enables scheduled Automation workers to use conversations created from saved agent profiles and to run in conversation-owned local or Docker workspaces. A trusted Automation controller creates the conversation, while the isolated worker receives only the resulting conversation ID and runtime-scoped credential. The worker must then attach to the server-resolved agent without reconstructing its profile or receiving the controller's broader credentials. Creation and attachment therefore need distinct operations: creation submits the existing StartConversationRequest, while attachment reads an existing conversation and must fail rather than silently create one when its ID is missing or inaccessible.

The Agent Server already had host-level Bash, file, and Git APIs, and Python RemoteWorkspace already used them. Merged #4966 added conversation-scoped versions of those routes and taught the TypeScript clients to select them, but it did not complete the equivalent Python RemoteWorkspace routing. This PR supplies that missing Python client half: a controller addressing one runtime among many binds the workspace to an immutable conversation ID, while standalone servers and workers already running inside a single isolated runtime retain the existing unscoped behavior. This is route selection in the existing workspace abstraction, not a second runtime client. Automated behavioral-parity enforcement would have caught this omission in #4966; #5043 tracks adding that preferable repository-wide guard.

An Automation run also has to outlive an individual scheduler request. The controller starts a worker command, persists the returned command ID, and polls that exact command on later passes; the existing blocking execute_command() contains these operations internally but cannot provide that durable handoff. For Docker execution, the controller must obtain the conversation runtime's restricted session credential and release the container after verification without deleting its retained conversation history. #3403 supplies those Docker lifecycle endpoints; this PR exposes them through the same Python workspace interface. In-memory upload and async cleanup support the existing Automation execution path without adding another transport.

The result is one Python SDK path for scheduled Automation in local and Docker environments: explicit profile-based creation, read-only attachment, conversation-scoped controller operations, durable command polling, scoped credential delivery, and runtime cleanup. Existing unscoped workspace operations remain valid for standalone Agent Servers and code already isolated inside a runtime, so they are not deprecated.

Summary

  • Add explicit RemoteConversation.create(workspace, request) and RemoteConversation.attach(workspace, conversation_id). Creation submits the existing SDK StartConversationRequest, including a concrete agent or server profile. Attachment only reads an existing conversation; a missing or inaccessible conversation raises instead of creating one. Neither method falls back to the other.
  • Restore the constructor's required AgentBase parameter. All entry points share the extracted connection initializer, callbacks, event synchronization, and WebSocket lifecycle. Use one canonical conversation route, removing the obsolete legacy name and duplicate private route state. Profile resolution and request validation remain in existing server/request machinery. Reuse messaging, run(blocking=False), state/events, and interruption; add set_title for persisted display names.
  • Add an immutable runtime_conversation_id to existing remote workspaces. File, command, and Git requests use that scope; omitted scope preserves existing host behavior.
  • Expose start_command and get_command_output by extracting the request generators already used by execute_command. Add scoped runtime credential/release methods, in-memory upload support, and async workspace context cleanup.
  • Remove the draft-only AgentServerClient/RuntimeClient classes, their sync/async variants, and the separate request module. Existing released constructor behavior remains compatible; no compatibility shim is needed for unpublished draft APIs.

The server capabilities are already implemented or supplied by the runtime PRs. This PR fills Python interface gaps; it does not introduce another execution layer.

REST API contract changes

Compared with base OpenAPI b5c8ab950401 for public /api/** paths.

--- base public OpenAPI
+++ head public OpenAPI
@@ -2353,0 +2354 @@
+schema RemoteWorkspace property runtime_conversation_id optional schema=anyOf=[type="string" format="uuid",type="null"]

Issue Number

Closes #5009

How to Test

After merging main’s #4966 at e9392e7cf, 71 scoped-runtime, RemoteConversation, and RemoteWorkspace tests passed; this refresh changes no #5010 feature code. On source de5bc17e6: 283 conversation/workspace regressions passed after the internal route cleanup. The preceding functional change also passed 288 conversation/workspace/profile regressions. All changed-file hooks passed, including Ruff, Pyright, dynamic-attribute checks, and import boundaries.

Coverage verifies profile request options, use of the returned agent, read-only attachment, failed creation/attachment with no fallback, and the unchanged scoped workspace operations. Automation and extension workers are migrated to the explicit methods.

Fresh live Canvas evidence (30-second GIF) uses SDK source 4bbab2dd0 plus the recorded runtime prerequisites. The identical bundle completed real DeepSeek tasks locally and in Docker. Missing attachment made one GET, returned 404, and created no persisted conversation. Scoped file/Git operations, byte upload, detached commands, exact Docs #793 examples, and automatic runtime release passed. All private test services and workers stopped.

The recording preserves the initial explicit-create failure: serializing an unset agent as null caused HTTP 500. Canonical exclude_none=True serialization fixed it; the same fixture and docs then passed, and a regression validates the emitted request. Earlier constructor-based recordings are historical. The recording predates de5bc17e6, which only renames the canonical route constant and removes redundant private route fields; endpoint URLs and public behavior are unchanged. Cleanup head de5bc17e6 has 47 passing CI checks and an all-hands-bot verdict on that head of Worth merging, with no material findings. Its review is recorded as DISMISSED, so this is not claimed as a formal approval. The required HUMAN note and runtime release prerequisites remain. This validation does not replace the separate secret-scope/queue matrices or upstream release gates.

Dependencies and review order

This PR targets main independently. Conversation-scoped operations use SDK #4966, now merged into main; Docker credential/release endpoints require SDK #3403. These are runtime/release prerequisites rather than native stack parents.

Release the SDK with these capabilities before merging the migrated Automation #449/#453 and extensions #570–#573. Documentation: OpenHands/docs #793.

Type

  • Feature
  • Refactor

🐳 Agent Server images for this PR — GHCR package, pull/run commands, and all pushed tags (click to expand)

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-slim amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22-slim Link
python amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22-slim 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:e9392e7-python

Run

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

All tags pushed for this build

ghcr.io/openhands/agent-server:e9392e7-golang-amd64
ghcr.io/openhands/agent-server:e9392e7cf35fcb011acf2eca825a80f9a22774ea-golang-amd64
ghcr.io/openhands/agent-server:feat-agent-server-python-client-golang-amd64
ghcr.io/openhands/agent-server:e9392e7-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:e9392e7-golang-arm64
ghcr.io/openhands/agent-server:e9392e7cf35fcb011acf2eca825a80f9a22774ea-golang-arm64
ghcr.io/openhands/agent-server:feat-agent-server-python-client-golang-arm64
ghcr.io/openhands/agent-server:e9392e7-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:e9392e7-java-amd64
ghcr.io/openhands/agent-server:e9392e7cf35fcb011acf2eca825a80f9a22774ea-java-amd64
ghcr.io/openhands/agent-server:feat-agent-server-python-client-java-amd64
ghcr.io/openhands/agent-server:e9392e7-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:e9392e7-java-arm64
ghcr.io/openhands/agent-server:e9392e7cf35fcb011acf2eca825a80f9a22774ea-java-arm64
ghcr.io/openhands/agent-server:feat-agent-server-python-client-java-arm64
ghcr.io/openhands/agent-server:e9392e7-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:e9392e7-python-amd64
ghcr.io/openhands/agent-server:e9392e7cf35fcb011acf2eca825a80f9a22774ea-python-amd64
ghcr.io/openhands/agent-server:feat-agent-server-python-client-python-amd64
ghcr.io/openhands/agent-server:e9392e7-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-amd64
ghcr.io/openhands/agent-server:e9392e7-python-arm64
ghcr.io/openhands/agent-server:e9392e7cf35fcb011acf2eca825a80f9a22774ea-python-arm64
ghcr.io/openhands/agent-server:feat-agent-server-python-client-python-arm64
ghcr.io/openhands/agent-server:e9392e7-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-arm64
ghcr.io/openhands/agent-server:e9392e7-python-slim-amd64
ghcr.io/openhands/agent-server:e9392e7cf35fcb011acf2eca825a80f9a22774ea-python-slim-amd64
ghcr.io/openhands/agent-server:feat-agent-server-python-client-python-slim-amd64
ghcr.io/openhands/agent-server:e9392e7-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-slim-amd64
ghcr.io/openhands/agent-server:e9392e7-python-slim-arm64
ghcr.io/openhands/agent-server:e9392e7cf35fcb011acf2eca825a80f9a22774ea-python-slim-arm64
ghcr.io/openhands/agent-server:feat-agent-server-python-client-python-slim-arm64
ghcr.io/openhands/agent-server:e9392e7-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-slim-arm64
ghcr.io/openhands/agent-server:e9392e7-golang
ghcr.io/openhands/agent-server:e9392e7cf35fcb011acf2eca825a80f9a22774ea-golang
ghcr.io/openhands/agent-server:feat-agent-server-python-client-golang
ghcr.io/openhands/agent-server:e9392e7-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:e9392e7-java
ghcr.io/openhands/agent-server:e9392e7cf35fcb011acf2eca825a80f9a22774ea-java
ghcr.io/openhands/agent-server:feat-agent-server-python-client-java
ghcr.io/openhands/agent-server:e9392e7-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:e9392e7-python-slim
ghcr.io/openhands/agent-server:e9392e7cf35fcb011acf2eca825a80f9a22774ea-python-slim
ghcr.io/openhands/agent-server:feat-agent-server-python-client-python-slim
ghcr.io/openhands/agent-server:e9392e7-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-slim
ghcr.io/openhands/agent-server:e9392e7-python
ghcr.io/openhands/agent-server:e9392e7cf35fcb011acf2eca825a80f9a22774ea-python
ghcr.io/openhands/agent-server:feat-agent-server-python-client-python
ghcr.io/openhands/agent-server:e9392e7-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim

About Multi-Architecture Support

  • Each variant tag (e.g., e9392e7-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., e9392e7-python-amd64) are also available if needed

@github-actions

github-actions Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor
  ✅ **PR Artifacts Cleaned Up**

  The `.pr/` directory is no longer present.

@github-actions

github-actions Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@github-actions

github-actions Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-sdk/openhands/sdk/conversation/impl
   remote_conversation.py7768389%92, 167, 194, 207, 209–212, 238, 256–257, 268–271, 278–279, 284–287, 371, 381–383, 389, 473–474, 609–612, 614, 640–644, 649–652, 655, 671, 795, 851–852, 856–857, 871, 1023–1026, 1075–1076, 1100–1101, 1141–1142, 1160, 1170, 1174, 1183–1184, 1223, 1377–1378, 1477–1478, 1482, 1487–1491, 1515, 1520, 1567, 1817–1818, 1826–1827
openhands-sdk/openhands/sdk/workspace/remote
   async_remote_workspace.py80594%35–37, 48–49
   base.py3034087%84–85, 127–131, 290–300, 304, 387, 438, 547, 596, 598–600, 656–657, 665, 746–751, 789, 834–836, 857
   remote_workspace_mixin.py167299%100, 158
TOTAL425481180072% 

@all-hands-bot

Copy link
Copy Markdown
Collaborator

🤖 OpenHands is reviewing this PR.

Head commit: 3509fa71ae0100e20917dfd2d644805a547c5a2b
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/0a99bbd5-3032-45fc-9998-160b4c151fb4

This comment was posted by an AI agent (OpenHands).

@all-hands-bot

Copy link
Copy Markdown
Collaborator

🤖 OpenHands is reviewing this PR.

Head commit: df0f25d312403b18194c56025f3f62d379f00c5d
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/3af8154a-0881-471f-af22-747f2eed41fb

This comment was posted by an AI agent (OpenHands).

all-hands-bot
all-hands-bot previously approved these changes Sep 12, 2026

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review was created by an AI agent (OpenHands) on behalf of the repository maintainers.

Verdict: APPROVE — 🟢 Low risk

Clean, well-structured additive client. The shared _requests.py module eliminates sync/async duplication, UUID validation prevents path injection on all conversation-scoped operations, the credential accessor fails closed on empty/non-string values, and release correctly treats 404 as idempotent while propagating other failures. Tests cover the full wire contract, scope validation, release idempotency, legacy runtime restrictions, and missing-credential edge cases.

One observation (non-blocking): The sync AgentServerClient is missing runtime_for_api_prefix() — only the async AsyncAgentServerClient exposes it (line 223). If sync consumers ever need to migrate from a stored API prefix, they would have no public method. This may be intentional if all migration consumers are async, but worth confirming.

Server endpoint coordination note: The client targets conversation-scoped bash/file/runtime endpoints that do not exist on the current main server. The PR description acknowledges this — the server-side changes are in #4966/#3403, #4998/#5005, and #5008. The client is correctly additive and the MockTransport tests validate wire shape without needing live endpoints. No issue with the client code itself; just flagging the cross-PR dependency.

Comment thread openhands-sdk/openhands/sdk/client/agent_server.py Outdated
@all-hands-bot
all-hands-bot dismissed their stale review September 12, 2026 21:29

Automated review used the wrong decision (APPROVED instead of COMMENT) and is dismissed. Findings are reposted as a comment.

all-hands-bot
all-hands-bot previously approved these changes Sep 12, 2026

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review was created by an AI agent (OpenHands) on behalf of the repository maintainers.

🟡 Acceptable - Works well; two actionable findings around merge-gate completeness and error handling.

Summary

This PR adds a repository-scoped GitHub gateway that exposes role-specific operations (triage, developer, reviewer, watchdog) to Docker automation workers while keeping upstream GitHub credentials in the control plane. The authorization model is well-designed: permitted() uses strict re.fullmatch patterns, path sanitization blocks traversal characters, secrets.compare_digest prevents timing attacks on role tokens, and the merge gate is fail-closed across head SHA, base ancestry, mergeability, CI statuses, and check-runs completeness. Repository placement is correct - this is a skill reference script, not SDK or agent-server behavior.

Findings

1. Statuses pagination not checked for completeness (merge gate asymmetry)

The merge gate carefully verifies check-runs pagination completeness via check_page.get("total_count", len(checks)) == len(checks), but latest_statuses() fetches only the first 100 entries from /commits/{sha}/statuses?per_page=100 with no equivalent completeness check. If a commit has >100 status entries, a failed third-party CI status beyond page 1 would be silently absent from statuses.values(), and the all(s["state"] == "success" for s in statuses.values()) check would pass without seeing it. The required factory contexts would likely be in the first page, but the broader "all statuses must pass" invariant could be violated. Consider adding a total_count comparison for statuses as well, or documenting why only the first page is sufficient.

2. URLError not handled in request handler

The exception handling in do_POST catches HTTPError and (ValueError, KeyError, TypeError) but not urllib.error.URLError. Non-HTTP network failures (DNS resolution failure, connection refused, socket timeout exceeding the 60s/90s urlopen timeout) would propagate as unhandled exceptions in the handler thread. While ThreadingHTTPServer keeps the server alive, the client receives a connection reset with no JSON error response. For a merge service where the watchdog may retry on ambiguous failures, returning a structured 502/504 error would make failure semantics cleaner.

Risk Assessment

  • [Overall PR] ⚠️ Risk Assessment: 🟡 MEDIUM
    The gateway implements a security-critical permission boundary with a well-structured authorization model and thorough fail-closed merge gate. The two findings above are completeness gaps rather than privilege escalation paths - the core security properties (no main writes, no force push, no admin operations, SHA-guarded merge) hold. Tests are real and exercise actual code paths rather than mock-only assertions.

VERDICT: ✅ Worth merging - core logic is sound, address the two findings as follow-ups.

KEY INSIGHT: The authorization and merge-gate design is solid; the gaps are in edge-case completeness (statuses pagination) and error-path robustness (URLError), not in the permission model itself.


Improve this review? If any feedback above seems incorrect or irrelevant to this repository, you can teach the reviewer to do better:

  1. Add a .agents/skills/custom-codereview-guide.md file to your branch (or edit it if one already exists) with the /codereview trigger and the context the reviewer is missing (e.g., "Security concerns about X do not apply here because Y"). See the customization docs for the required frontmatter format.
  2. Re-request a review - the reviewer reads guidelines from the PR branch, so your changes take effect immediately.
  3. When your PR is merged, the guideline file goes through normal code review by repository maintainers.

Resolve with AI? Install the iterate skill in your agent and run /iterate to automatically drive this PR through CI, review, and QA until it's merge-ready.

Was this review helpful? React with 👍 or 👎 to give feedback.

Comment thread openhands-sdk/openhands/sdk/client/agent_server.py Outdated
@all-hands-bot

Copy link
Copy Markdown
Collaborator

🤖 OpenHands is reviewing this PR.

Head commit: d41ab13dce3fa7dba3f32c1d5d9e730234be6753
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/1b71ea0b-55fd-4485-91e6-9203277253a2

This comment was posted by an AI agent (OpenHands).

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review was created by an AI agent (OpenHands) on behalf of the repository maintainers.

Summary

Clean, well-structured additive client module. The shared _requests.py operation layer eliminates wire-contract duplication between sync/async, UUID validation on conversation IDs prevents path traversal, and the test suite covers the key contracts (scoped wire paths, invalid scope rejection, idempotent release, missing credential fail-closed, caller-owned transport lifecycle).

The existing server endpoints (/server_info, /api/conversations, /api/conversations/{id}/events, /api/conversations/{id}/interrupt, /api/conversations/{id}/bash/*, /api/conversations/{id}/file/upload) all match the current agent-server router registrations. The /runtime and /runtime/credentials endpoints are not yet in the server — the PR description acknowledges this and references pending PRs (#4966/#3403, #4998/#5005, #5008). Since this is a draft, that coordination is expected.

No material code issues found. One minor observation below.

Minor Observation

Async lazy client init race (agent_server.py:167-168): In AsyncAgentServerClient._send, if self._http is None, a new httpx.AsyncClient is created inline. Two concurrent coroutines hitting _send before the first assignment completes could each create a client, leaking one. In practice this only happens on first use when no http_client is injected, and the leaked client is GC'd eventually — low impact. Consumers following the recommended pattern (injecting an http_client) are unaffected. Worth a note for awareness but not blocking.

Risk Assessment

🟢 LOW — Purely additive new module with no changes to existing SDK or server code. No agent behavior, prompt, or tool changes. The module is exported via openhands.sdk.client.__all__ but not yet via the top-level openhands.sdk.__all__, so it doesn't enter the SDK's tracked public API surface yet. The server-side runtime endpoints it targets are pending in separate PRs, which is appropriate for a draft.

Verdict

Code is clean and well-tested. No material findings. Leaving COMMENT (not APPROVE) because the PR is draft and the target server endpoints (/runtime, /runtime/credentials) haven't landed yet — the author's plan to validate against those PRs first is the right call.

Comment thread openhands-sdk/openhands/sdk/client/agent_server.py Outdated
@all-hands-bot

Copy link
Copy Markdown
Collaborator

The review was successfully posted to GitHub as an APPROVE review (ID: 5192223095) against commit b56bd0073b49a43e9e04ac670f351cd3f0cc5827.

GITHUB_REVIEW_POSTED

Summary: Reviewed PR #5010 which extends RemoteConversation and remote workspace APIs for automation. The PR:

  • Widens agent parameter to AgentBase | None (backward-compatible), adds agent_profile_id for profile-based creation
  • Extracts shared request generators from _execute_command_generator, reducing duplication
  • Adds runtime_conversation_id (frozen) for scoped file/command/git operations
  • Adds start_command/get_command_output for detached command polling
  • Adds scoped runtime credential/release methods, bytes upload support, async context manager

No material issues found. The code is clean, well-tested (287 tests passing), and doesn't touch agent behavior or eval-sensitive paths. Risk: 🟢 LOW. Approved.

This comment was posted by an AI agent (OpenHands).

Reuse StartConversationRequest and a shared connection initializer instead of selecting behavior from agent=None.

Co-authored-by: openhands <openhands@all-hands.dev>
@all-hands-bot

Copy link
Copy Markdown
Collaborator

🤖 OpenHands is reviewing this PR.

Head commit: 70c3afeb0d7ccc6101ef103973def1e39966df9f
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/b950516f-db8c-4871-b9ef-475308d50c8f

This comment was posted by an AI agent (OpenHands).

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review was created by an AI agent (OpenHands) on behalf of the repository maintainers.

Summary

This PR extends the SDK's remote conversation and workspace APIs to support automation use cases: explicit create()/attach() classmethods, set_title(), scoped runtime operations (start_command/get_command_output/get_runtime_session_key/release_runtime), in-memory bytes upload, async context manager support, and a runtime_conversation_id field that scopes file/command/git operations to a conversation's isolated runtime.

Assessment

Good taste — The refactoring is clean and well-motivated. Extracting _initialize_connection() eliminates duplicated setup across the constructor, create(), and attach(). The generator extraction in remote_workspace_mixin.py (splitting _start_command_generator, _search_command_output_generator, etc. out of _execute_command_generator) reduces duplication without adding abstraction layers. The api_prefix property is a simple, elegant way to route operations to either the host workspace or a conversation-scoped runtime.

Findings

No material issues found. A few things I verified:

  • create() vs __init__() payload parity: create() sends StartConversationRequest.model_dump() which includes all fields the server needs (agent, workspace, max_iterations, hook_config, plugins, client_tools, tags, observability, etc.). The constructor's manual payload construction is the older path; create() correctly uses the canonical request model. The one field only on the constructor path (stuck_detection_thresholds) is not on StartConversationRequest, which is by design — the request model is the canonical surface.
  • _from_info() bypasses __init__ via cls.__new__: This skips Pydantic validation but _initialize_connection() explicitly sets every required attribute (agent, workspace, _id, _state, _ws_client, _callbacks, etc.), and delete_on_close defaults to False as a class attribute. This is safe.
  • URL inconsistency in _file_download_generator: The download/git URLs use f"{self.api_prefix}/..." (relative, resolved against the httpx client's base_url), while upload uses f"{self.host}{self.api_prefix}/..." (absolute). Both work correctly, and this is a pre-existing pattern — the PR only changed the hardcoded /api to self.api_prefix.
  • get_command_output returns only the latest event (limit=1, sort DESC): This is by design for polling completion status, as documented in the docstring.
  • No version bumps, no dependency changes, no event type modifications.

Risk Assessment

LOW — This PR adds new API surface without changing existing agent behavior, prompt templates, or tool execution logic. It is not in the eval-risk category. The refactoring preserves existing constructor behavior. Tests cover create/attach/no-fallback scenarios and scoped workspace operations across sync/async paths.

Validate the outgoing profile request with the canonical schema to prevent the live null-agent discriminator failure.

Co-authored-by: openhands <openhands@all-hands.dev>
@all-hands-bot

Copy link
Copy Markdown
Collaborator

🤖 OpenHands is reviewing this PR.

Head commit: 4bbab2dd0ccfabb935ff42f48a9e1024f6e957b1
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/87530c8f-a3e2-4dfe-bc76-57de37e98433

This comment was posted by an AI agent (OpenHands).

all-hands-bot
all-hands-bot previously approved these changes Sep 13, 2026

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review was created by an AI agent (OpenHands) on behalf of the repository maintainers.

Summary

This PR extends the SDK'''s conversation and workspace APIs for automation use cases: explicit create/attach classmethods on RemoteConversation, scoped runtime operations on remote workspaces, detached command lifecycle, and in-memory file upload. The refactoring of __init__ into a shared _initialize_connection is clean — all three entry points (constructor, create, attach) share the same connection setup, WebSocket lifecycle, and callback composition.

Findings

No material issues found. The design is sound:

  • create() / attach() separation: create submits a StartConversationRequest (with profile or agent), attach is read-only and raises on missing conversations. Neither falls back to the other — verified by test_failed_explicit_operation_does_not_try_the_other_operation.
  • exclude_none=True on the create payload correctly omits a null agent when using agent_profile_id, avoiding the server-side discriminator failure described in the PR.
  • runtime_conversation_id field is frozen (immutable) and defaults to None, preserving existing /api behavior when unset. The api_prefix property cleanly routes to /api/conversations/{id} when scoped.
  • Runtime lifecycle guards: get_runtime_session_key/release_runtime raise ValueError when no conversation scope is set. Release handles 404 idempotently.
  • start_command/get_command_output extract the generators already used by execute_command, eliminating duplication.
  • file_upload accepting bytes is backward compatible — existing str | Path callers are unaffected.
  • Tests cover profile requests, agent type preservation on attach, no-fallback on failure, scoped vs unscoped operations across sync/async, runtime credential validation, and async context manager cleanup.

Risk Assessment

🟢 LOW — This is purely API surface extension for automation. No changes to agent behavior, prompt templates, tool execution, planning logic, memory/condenser behavior, or terminal handling. No eval-risk. No version bumps or dependency changes.

@all-hands-bot
all-hands-bot dismissed their stale review September 13, 2026 22:19

Automated review used the wrong decision (APPROVED instead of COMMENT) and is dismissed. Findings are reposted as a comment.

@all-hands-bot

Copy link
Copy Markdown
Collaborator

Code review for PR #5010 has been posted to GitHub as an APPROVED review (ID: 5192427387).

Summary of findings: No material issues found. The PR cleanly extends the SDK's conversation and workspace APIs for automation:

  • RemoteConversation.create() and attach() classmethods share a well-extracted _initialize_connection with the existing constructor
  • runtime_conversation_id field with api_prefix property provides clean scoped routing, backward compatible when unset
  • start_command/get_command_output eliminate duplication by extracting generators already used by execute_command
  • Runtime lifecycle methods properly guard against missing scope and handle 404 idempotently
  • file_upload accepting bytes is backward compatible
  • Tests comprehensively cover profile-based creation, read-only attach, no-fallback on failure, sync/async scoped operations, and credential validation

Risk: 🟢 LOW — purely API surface extension, no changes to agent behavior or eval-relevant logic.

This comment was posted by an AI agent (OpenHands).

Co-authored-by: openhands <openhands@all-hands.dev>
@all-hands-bot

Copy link
Copy Markdown
Collaborator

🤖 OpenHands is reviewing this PR.

Head commit: de5bc17e68aebe3125c6832c8096422a430e770e
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/81034c9c-14e6-48b3-a173-baa1619c88b8

This comment was posted by an AI agent (OpenHands).

all-hands-bot
all-hands-bot previously approved these changes Sep 13, 2026

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review was created by an AI agent (OpenHands) on behalf of the repository maintainers.

Summary

Clean refactor that extracts shared connection initialization into _initialize_connection, adds explicit RemoteConversation.create() / attach() classmethods for automation, scopes workspace operations via an immutable runtime_conversation_id, and extracts reusable command generators. No agent behavior, prompt, or tool-logic changes — purely API surface extension.

Assessment

No material findings. The implementation is sound:

  • _initialize_connection correctly centralizes all shared setup (callbacks, WebSocket lifecycle, event sync, visualizer) used by both the legacy constructor and the new create/attach paths.
  • create serializes with exclude_none=True + expose_secrets: True — the right pattern for the request payload.
  • attach is read-only: a single GET, no fallback to create, raises on missing/inaccessible conversations.
  • runtime_conversation_id is frozen=True (immutable after construction) — correct for a scope identifier.
  • _runtime_lifecycle_generator gracefully handles 404 on release (already-gone runtime is safe).
  • The LEGACY_CONVERSATIONS_PATHCONVERSATIONS_PATH rename and removal of redundant private fields are internal-only with no public API impact.
  • Tests cover profile-based creation, read-only attachment, failed operations with no fallback, scoped/unscoped workspace operations across sync/async, and edge cases (empty credential, immutability).

Not in eval-risk category — no prompt templates, tool calling, planning loop, or condenser changes.

[RISK ASSESSMENT]

  • Overall PR: 🟢 LOW — pure API/interface extension with no changes to agent behavior or execution paths.

VERDICT: ✅ Worth merging.

@all-hands-bot
all-hands-bot dismissed their stale review September 13, 2026 23:10

Automated review used the wrong decision (APPROVED instead of COMMENT) and is dismissed. Findings are reposted as a comment.

@all-hands-bot

Copy link
Copy Markdown
Collaborator

Review posted successfully to PR #5010 with APPROVE event.

The PR extends existing conversation and workspace APIs for automation by:

  • Adding RemoteConversation.create() and attach() classmethods
  • Extracting shared initialization into _initialize_connection
  • Adding immutable runtime_conversation_id for scoped workspace operations
  • Extracting reusable command generators (start_command, get_command_output)
  • Adding runtime credential/release methods and async workspace context manager
  • Adding set_title method
  • Renaming LEGACY_CONVERSATIONS_PATH to CONVERSATIONS_PATH and removing redundant private fields

No material findings — the implementation is clean, well-tested, and not in the eval-risk category. Risk assessment: 🟢 LOW.

This comment was posted by an AI agent (OpenHands).

Co-authored-by: openhands <openhands@all-hands.dev>
@all-hands-bot

Copy link
Copy Markdown
Collaborator

🤖 OpenHands is reviewing this PR.

Head commit: e9392e7cf35fcb011acf2eca825a80f9a22774ea
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/a2b4ae6c-7233-4e36-a951-81ea882123c4

This comment was posted by an AI agent (OpenHands).

all-hands-bot
all-hands-bot previously approved these changes Sep 14, 2026

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review was created by an AI agent (OpenHands) on behalf of the repository maintainers.

Summary

This PR extends the SDK conversation and workspace APIs to support automation use cases: explicit create()/attach() class methods on RemoteConversation, runtime-scoped workspace operations via runtime_conversation_id, detached command lifecycle (start_command/get_command_output), runtime credential/release methods, in-memory byte uploads, and async workspace context cleanup.

The refactoring is clean: _initialize_connection() extracts the shared connection setup (agent, callbacks, WebSocket lifecycle, event sync) used by all three entry points (init, create, attach). The create() method correctly uses exclude_none=True to avoid serializing an unset agent field as null when agent_profile_id is used instead. The attach() method is read-only -- it GETs the conversation and never creates or updates -- and missing conversations raise rather than silently creating. Both failed operations are verified to not fall back to the other.

Runtime scoping is implemented via an immutable runtime_conversation_id field (frozen, UUID | None) and an api_prefix property that returns /api or /api/conversations/{id}. File, command, and Git request generators consistently use self.api_prefix, preserving existing host behavior when the scope is None.

The set_title method adds a simple PATCH endpoint. The AsyncRemoteWorkspace.aexit closes the HTTP client without releasing the runtime, which is the right default -- callers can explicitly call release_runtime() if needed.

Tests cover: profile-based creation with request validation, read-only attachment for both Agent and ACPAgent kinds, failed create/attach with no fallback, scoped vs unscoped file/command/git/runtime operations (sync and async), byte uploads, async context cleanup, frozen field validation, and missing runtime credential rejection.

Risk Assessment

LOW -- This PR extends API surface without changing agent behavior, prompt templates, tool calling, or planning logic. No eval-risk concern. No security issues (secrets are serialized with context expose_secrets True and exclude_none True). No breaking changes to existing constructor behavior. No dependency changes.

[RISK ASSESSMENT]

  • Overall PR: LOW
  • No material bugs, security problems, or design flaws found.
  • The pre-existing URL inconsistency (download/git generators omit self.host while upload/command/runtime include it) is not introduced by this PR and works correctly because httpx joins relative URLs with base_url.

VERDICT: Worth merging.

@all-hands-bot
all-hands-bot dismissed their stale review September 14, 2026 12:14

Automated review used the wrong decision (APPROVED instead of COMMENT) and is dismissed. Findings are reposted as a comment.

@all-hands-bot

Copy link
Copy Markdown
Collaborator

Successfully reviewed and posted an APPROVE review for PR #5010 (feat(sdk): extend existing conversation and workspace APIs for automation).

Review Summary

I examined all 6 changed files in detail:

  • remote_conversation.py: Refactored to extract _initialize_connection() shared by __init__, create(), and attach(). Verified exclude_none=True prevents null-agent serialization bug. Confirmed attach() is read-only and failed operations don't fall back.
  • remote_workspace_mixin.py: Added runtime_conversation_id (frozen) and api_prefix property for scoped operations. Extracted _start_command_generator, _search_command_output_generator, _get_command_output_generator, and _runtime_lifecycle_generator. Verified byte upload support.
  • base.py / async_remote_workspace.py: Added sync/async start_command, get_command_output, get_runtime_session_key, release_runtime. Added async context manager (__aenter__/__aexit__).
  • Tests: Verified coverage for profile-based creation, read-only attachment, failed operations with no fallback, scoped/unscoped operations, byte uploads, async cleanup, and frozen field validation.

Key Findings

  • No material bugs, security issues, or design flaws found
  • Not in eval-risk category (API surface extension only, no agent behavior changes)
  • Pre-existing URL inconsistency (download/git omit self.host) works correctly via httpx base_url and is not introduced by this PR
  • Risk: 🟢 LOW

The review was posted as APPROVE at commit e9392e7cf35fcb011acf2eca825a80f9a22774ea (review ID: 5196914771).

This comment was posted by an AI agent (OpenHands).

@neubig
neubig marked this pull request as ready for review September 14, 2026 15:26
@neubig
neubig merged commit e2889cd into main Sep 14, 2026
67 checks passed
@neubig
neubig deleted the feat/agent-server-python-client branch September 14, 2026 16:10
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.

Fill automation gaps in existing conversation and workspace APIs

4 participants