Skip to content

feat(workspace): Add completion callback support to RemoteWorkspace#3110

Merged
malhotra5 merged 3 commits intomainfrom
feat/api-remote-workspace-callback
May 7, 2026
Merged

feat(workspace): Add completion callback support to RemoteWorkspace#3110
malhotra5 merged 3 commits intomainfrom
feat/api-remote-workspace-callback

Conversation

@malhotra5
Copy link
Copy Markdown
Collaborator

@malhotra5 malhotra5 commented May 7, 2026

  • A human has tested these changes.

Why

OpenHands Cloud workspace (OpenHandsCloudWorkspace) supports sending completion callbacks on exit for automations via AUTOMATION_CALLBACK_URL. This adds similar functionality to the RemoteWorkspace base class so all remote workspace implementations can send completion callbacks when configured via environment variables.

Summary

  • Add completion callback support to RemoteWorkspace base class via _init_callback_settings() and _send_completion_callback() methods
  • Update register_conversation() to store the conversation ID (was a no-op)
  • Update APIRemoteWorkspace to call the callback methods during init and exit

Issue Number

N/A - User request

How to Test

uv run pytest tests/sdk/workspace/remote/test_remote_workspace.py -v -k callback
uv run pytest tests/workspace/test_api_remote_workspace.py -v -k callback

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

Environment variables:

  • AUTOMATION_CALLBACK_URL — URL to POST completion status to
  • AUTOMATION_CALLBACK_API_KEY — Bearer token for callback auth (optional)
  • AUTOMATION_RUN_ID — Run ID to include in callback payload (optional)

This PR was created by an AI agent (OpenHands) on behalf of the user.


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-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:87af8d7-python

Run

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

All tags pushed for this build

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

About Multi-Architecture Support

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-sdk/openhands/sdk/workspace/remote
   base.py2803687%79–84, 125–129, 206–208, 222–224, 510, 562, 564–566, 622–623, 631, 713–718, 756, 801–803, 824
openhands-workspace/openhands/workspace/remote_api
   workspace.py2179257%128, 137–139, 143–144, 146–150, 152, 154–155, 159–160, 165–167, 169–182, 189, 212, 237, 290, 294, 300–302, 306, 308, 313–315, 318–325, 327–330, 333, 335–348, 350–352, 354–360, 363, 366–367, 383, 387, 396, 415–416, 422
TOTAL25499681173% 

…ase class

Move callback functionality to RemoteWorkspace base class so all subclasses
(APIRemoteWorkspace, OpenHandsCloudWorkspace) can use it. On exit, the
workspace can POST completion status to a configured callback URL.

Environment variables (read via _init_callback_settings):
- AUTOMATION_CALLBACK_URL: URL to POST completion status to
- AUTOMATION_CALLBACK_API_KEY: Bearer token for callback auth (optional)
- AUTOMATION_RUN_ID: Run ID to include in callback payload (optional)

Base class changes:
- Added _init_callback_settings() method for subclasses to call
- Added _send_completion_callback() method for __exit__ to call
- Updated register_conversation() to actually store the conversation_id
- Updated conversation_id property to return stored value

APIRemoteWorkspace changes:
- Call _init_callback_settings() in model_post_init()
- Call _send_completion_callback() in __exit__()

Co-authored-by: openhands <openhands@all-hands.dev>
@malhotra5 malhotra5 force-pushed the feat/api-remote-workspace-callback branch from e6c660e to 756071e Compare May 7, 2026 20:35
@malhotra5 malhotra5 changed the title feat(workspace): Add completion callback support to APIRemoteWorkspace feat(workspace): Add completion callback support to RemoteWorkspace May 7, 2026
Remove private attributes for automation callback settings and read
environment variables directly in _send_completion_callback() instead.
This keeps the callback logic self-contained and avoids leaking into
other implementations that extend RemoteWorkspace.

Co-authored-by: openhands <openhands@all-hands.dev>
@malhotra5 malhotra5 marked this pull request as ready for review May 7, 2026 21:07
Add __exit__ override to RemoteWorkspace base class that calls
_send_completion_callback() so all subclasses get the behavior
automatically. APIRemoteWorkspace now calls super().__exit__()
instead of calling the callback directly.

Co-authored-by: openhands <openhands@all-hands.dev>
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 follows existing patterns from OpenHandsCloudWorkspace.

Key strengths:

  • Backward compatible (opt-in via env vars)
  • Follows the same pattern as OpenHandsCloudWorkspace callback implementation
  • Comprehensive test coverage
  • Best-effort error handling prevents callback failures from breaking cleanup
  • Properly stores conversation_id for inclusion in callback payload

RISK ASSESSMENT
🟢 LOW - Purely additive feature with no impact on existing behavior unless explicitly enabled via environment variables.

VERDICT:Worth merging - Extends callback functionality to all RemoteWorkspace implementations in a clean, testable way.

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.

✅ QA Report: PASS

Completion callback functionality works end-to-end as specified — all environment variables, success/failure cases, and optional fields verified through actual HTTP requests.

Does this PR achieve its stated goal?

Yes. The PR successfully adds completion callback support to the RemoteWorkspace base class, enabling all remote workspace implementations to send completion status via HTTP POST on exit. I verified the implementation by:

  1. Starting an HTTP server to receive callbacks
  2. Creating APIRemoteWorkspace instances with various callback configurations
  3. Exercising exit paths (success and failure)
  4. Inspecting the actual HTTP requests sent

The callback correctly handles all documented environment variables (AUTOMATION_CALLBACK_URL, AUTOMATION_CALLBACK_API_KEY, AUTOMATION_RUN_ID), includes conversation IDs when registered, and gracefully no-ops when disabled.

Phase Result
Environment Setup ✅ Dependencies installed, build succeeded
CI Status ✅ All checks passing (sdk-tests, tools-tests, pre-commit, etc.)
Functional Verification ✅ 5 scenarios verified with actual HTTP requests
Functional Verification

Test 1: Successful Workspace Exit with Full Configuration

Step 1 — Set up callback receiver and environment:
Started HTTP server on localhost:8888 to receive callbacks.

Configured environment:

export AUTOMATION_CALLBACK_URL='http://localhost:8888/callback'
export AUTOMATION_CALLBACK_API_KEY='test-api-key-12345'
export AUTOMATION_RUN_ID='run-qa-test-001'

Step 2 — Exercise workspace with successful exit:
Ran test script that creates APIRemoteWorkspace, registers conversation ID 'conv-qa-test-123', and exits normally:

workspace.register_conversation('conv-qa-test-123')
workspace.__enter__()
workspace.__exit__(None, None, None)  # Successful exit

Step 3 — Verify callback received:
Server logs showed:

{
  "status": "COMPLETED",
  "run_id": "run-qa-test-001",
  "conversation_id": "conv-qa-test-123"
}

SDK log confirmed:

Completion callback sent (COMPLETED): 200

This confirms the callback is sent on successful exit with all expected fields.


Test 2: Failed Workspace Exit with Error

Step 1 — Configure environment without API key:

export AUTOMATION_CALLBACK_URL='http://localhost:8888/callback'
export AUTOMATION_RUN_ID='run-qa-test-002'
# No AUTOMATION_CALLBACK_API_KEY set

Step 2 — Exercise workspace with exception:
Simulated workspace failure:

exc = RuntimeError("Simulated workspace failure for QA testing")
workspace.__exit__(type(exc), exc, None)

Step 3 — Verify FAILED callback:
Server received:

{
  "status": "FAILED",
  "run_id": "run-qa-test-002",
  "error": "Simulated workspace failure for QA testing"
}

This confirms:

  • Status changes to "FAILED" on exception
  • Error message is included in payload
  • No conversation_id (none was registered)
  • No Authorization header (API key not set)

Test 3: Callback Disabled (No URL)

Step 1 — Remove all callback environment variables:

unset AUTOMATION_CALLBACK_URL
unset AUTOMATION_CALLBACK_API_KEY
unset AUTOMATION_RUN_ID

Step 2 — Exercise workspace:

workspace.__enter__()
workspace.__exit__(None, None, None)

Step 3 — Verify no callback sent:
No "Completion callback sent" log message appeared. Server logs confirmed no new callback was received.

This confirms the callback is a no-op when AUTOMATION_CALLBACK_URL is not set.


Test 4: Authorization Header Verification

Step 1 — Set up with API key:

export AUTOMATION_CALLBACK_URL='http://localhost:8888/callback'
export AUTOMATION_CALLBACK_API_KEY='secret-key-abc123'

Step 2 — Capture HTTP request details:
Used mock to intercept the actual httpx.Client.post() call and inspect headers.

Step 3 — Verify Authorization header:
Captured request:

Headers: {'Authorization': 'Bearer secret-key-abc123'}

This confirms the Authorization header is correctly formatted as Bearer {api_key}.


Test 5: RemoteWorkspace Base Class Behavior

Step 1 — Test initial state:

workspace = RemoteWorkspace(host='http://localhost:8000', working_dir='/workspace')
assert workspace.conversation_id is None

✅ conversation_id returns None initially

Step 2 — Register conversation:

workspace.register_conversation('conv-test-456')
assert workspace.conversation_id == 'conv-test-456'

✅ conversation_id is stored and returned correctly

Step 3 — Verify persistence:

assert workspace.conversation_id == 'conv-test-456'

✅ Value persists across property accesses

This confirms the base class changes (store conversation ID, return it via property) work as expected.

Issues Found

None.

@malhotra5 malhotra5 merged commit 2d36319 into main May 7, 2026
35 checks passed
@malhotra5 malhotra5 deleted the feat/api-remote-workspace-callback branch May 7, 2026 22:38
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.

3 participants