Skip to content

fix(sdk): override RemoteConversation.interrupt() to POST /interrupt (B4 of #3341)#3398

Merged
malhotra5 merged 1 commit into
mainfrom
fix/remote-conversation-interrupt
May 27, 2026
Merged

fix(sdk): override RemoteConversation.interrupt() to POST /interrupt (B4 of #3341)#3398
malhotra5 merged 1 commit into
mainfrom
fix/remote-conversation-interrupt

Conversation

@VascoSch92
Copy link
Copy Markdown
Member

@VascoSch92 VascoSch92 commented May 27, 2026

What

Addresses B4 of #3341. RemoteConversation didn't override interrupt(), so it inherited BaseConversation.interrupt(), whose default falls back to pause(). A remote caller invoking interrupt() therefore got pause semantics — wait for the in-flight LLM request to finish — from a method named interrupt().

The server already exposes POST /conversations/{id}/interrupt (conversation_router.py:219), so this adds the missing override, mirroring pause():

def interrupt(self) -> None:
    _send_request(
        self._client,
        "POST",
        f"{self._conversation_action_base_path}/{self._id}/interrupt",
    )

Test

test_remote_conversation_interrupt asserts the call POSTs to /interrupt and not to /pause — i.e. it no longer silently degrades to the inherited fallback.

Verification

  • ruff format --check and ruff check clean
  • full test_remote_conversation.py suite passes (32 tests)

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:30e52a4-python

Run

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

All tags pushed for this build

ghcr.io/openhands/agent-server:30e52a4-golang-amd64
ghcr.io/openhands/agent-server:30e52a4bf7567936e4ba5593482cfca7f53e7dc0-golang-amd64
ghcr.io/openhands/agent-server:fix-remote-conversation-interrupt-golang-amd64
ghcr.io/openhands/agent-server:30e52a4-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:30e52a4-golang-arm64
ghcr.io/openhands/agent-server:30e52a4bf7567936e4ba5593482cfca7f53e7dc0-golang-arm64
ghcr.io/openhands/agent-server:fix-remote-conversation-interrupt-golang-arm64
ghcr.io/openhands/agent-server:30e52a4-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:30e52a4-java-amd64
ghcr.io/openhands/agent-server:30e52a4bf7567936e4ba5593482cfca7f53e7dc0-java-amd64
ghcr.io/openhands/agent-server:fix-remote-conversation-interrupt-java-amd64
ghcr.io/openhands/agent-server:30e52a4-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:30e52a4-java-arm64
ghcr.io/openhands/agent-server:30e52a4bf7567936e4ba5593482cfca7f53e7dc0-java-arm64
ghcr.io/openhands/agent-server:fix-remote-conversation-interrupt-java-arm64
ghcr.io/openhands/agent-server:30e52a4-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:30e52a4-python-amd64
ghcr.io/openhands/agent-server:30e52a4bf7567936e4ba5593482cfca7f53e7dc0-python-amd64
ghcr.io/openhands/agent-server:fix-remote-conversation-interrupt-python-amd64
ghcr.io/openhands/agent-server:30e52a4-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-amd64
ghcr.io/openhands/agent-server:30e52a4-python-arm64
ghcr.io/openhands/agent-server:30e52a4bf7567936e4ba5593482cfca7f53e7dc0-python-arm64
ghcr.io/openhands/agent-server:fix-remote-conversation-interrupt-python-arm64
ghcr.io/openhands/agent-server:30e52a4-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-arm64
ghcr.io/openhands/agent-server:30e52a4-golang
ghcr.io/openhands/agent-server:30e52a4bf7567936e4ba5593482cfca7f53e7dc0-golang
ghcr.io/openhands/agent-server:fix-remote-conversation-interrupt-golang
ghcr.io/openhands/agent-server:30e52a4-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:30e52a4-java
ghcr.io/openhands/agent-server:30e52a4bf7567936e4ba5593482cfca7f53e7dc0-java
ghcr.io/openhands/agent-server:fix-remote-conversation-interrupt-java
ghcr.io/openhands/agent-server:30e52a4-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:30e52a4-python
ghcr.io/openhands/agent-server:30e52a4bf7567936e4ba5593482cfca7f53e7dc0-python
ghcr.io/openhands/agent-server:fix-remote-conversation-interrupt-python
ghcr.io/openhands/agent-server:30e52a4-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim

About Multi-Architecture Support

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

RemoteConversation did not override interrupt(), so it inherited BaseConversation.interrupt(), whose default falls back to pause(). Remote callers invoking interrupt() therefore got pause semantics (wait for the in-flight LLM call) instead of an immediate cancel. The server already exposes POST /conversations/{id}/interrupt, so override interrupt() to POST to it, mirroring pause().
@github-actions
Copy link
Copy Markdown
Contributor

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions
Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

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.

Taste Rating: 🟢 Good taste

LGTM — straightforward override maps RemoteConversation.interrupt() to the existing /interrupt endpoint, with focused regression coverage that ensures it no longer falls back to /pause.

[RISK ASSESSMENT]

  • [Overall PR] ⚠️ Risk Assessment: 🟢 LOW. Narrow client-side routing fix with direct test coverage; no dependency, persistence, or agent-loop changes.

VERDICT: ✅ Worth merging.

This review was generated by an AI agent (OpenHands) on behalf of the user.


Was this automated review useful? React with 👍 or 👎 to this review to help us measure review quality.
Workflow run: https://github.com/OpenHands/software-agent-sdk/actions/runs/26503239077

@github-actions
Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-sdk/openhands/sdk/conversation/impl
   remote_conversation.py6597788%143, 170, 183, 185–188, 198, 220–221, 226–229, 313, 323–325, 331, 405, 552–555, 557, 583–587, 592–595, 598, 614, 767–768, 772–773, 787, 833, 844–845, 865–868, 870–871, 897, 907, 911, 920–921, 960, 1091, 1163–1164, 1168, 1173–1177, 1183–1189, 1202, 1207, 1249, 1462–1463
TOTAL27849818770% 

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

RemoteConversation.interrupt() now reaches the remote /interrupt API instead of silently falling back to /pause; no functional issues found.

Does this PR achieve its stated goal?

Yes. I exercised the SDK as a client would by creating a RemoteConversation against a local HTTP/WebSocket-compatible server and calling conversation.interrupt(). On origin/main, the same call POSTed to /pause; on PR commit 30e52a4b, it POSTed to /interrupt, while an explicit conversation.pause() still POSTed to /pause.

Phase Result
Environment Setup make build completed successfully
CI Status 🟡 Latest GitHub checks: 23 success, 4 in progress, 5 skipped, no failures observed
Functional Verification ✅ Before/after SDK execution confirms endpoint behavior changed as intended
Functional Verification

Test 1: RemoteConversation.interrupt() endpoint semantics

Step 1 — Reproduce baseline without the fix:
Checked out origin/main and ran OPENHANDS_SUPPRESS_BANNER=1 uv run python -u /tmp/qa_remote_interrupt_aiohttp.py, which creates a real RemoteConversation against a local aiohttp HTTP/WebSocket server and calls conversation.interrupt():

CONVERSATION CREATED
SERVER POST /api/conversations/9b4c173e-a81c-41cd-b32e-deab4359e49e/pause
AFTER INTERRUPT
REQUESTS_SEEN
POST /api/conversations
GET /api/conversations/9b4c173e-a81c-41cd-b32e-deab4359e49e/events/search?limit=100
GET /sockets/events/9b4c173e-a81c-41cd-b32e-deab4359e49e
GET /api/conversations/9b4c173e-a81c-41cd-b32e-deab4359e49e/events/search?limit=100
POST /api/conversations/9b4c173e-a81c-41cd-b32e-deab4359e49e/pause

This confirms the reported bug: the public interrupt() call used pause semantics on the remote client.

Step 2 — Apply the PR's changes:
Returned to fix/remote-conversation-interrupt at 30e52a4b.

Step 3 — Re-run with the fix in place:
Ran OPENHANDS_SUPPRESS_BANNER=1 uv run python -u /tmp/qa_remote_interrupt_aiohttp.py:

30e52a4b
CONVERSATION CREATED
SERVER POST /api/conversations/d79d16f3-44a9-4d42-a848-e7eef0f01537/interrupt
AFTER INTERRUPT
REQUESTS_SEEN
POST /api/conversations
GET /api/conversations/d79d16f3-44a9-4d42-a848-e7eef0f01537/events/search?limit=100
GET /sockets/events/d79d16f3-44a9-4d42-a848-e7eef0f01537
GET /api/conversations/d79d16f3-44a9-4d42-a848-e7eef0f01537/events/search?limit=100
POST /api/conversations/d79d16f3-44a9-4d42-a848-e7eef0f01537/interrupt

This shows the fix works: interrupt() now POSTs to the dedicated /interrupt endpoint.

Test 2: Related pause() behavior still works

Ran QA_METHOD=pause OPENHANDS_SUPPRESS_BANNER=1 uv run python -u /tmp/qa_remote_interrupt_aiohttp.py on the PR branch:

CONVERSATION CREATED
SERVER POST /api/conversations/c0b2688a-ff30-49af-bca1-c41309f9a0fd/pause
AFTER PAUSE
REQUESTS_SEEN
POST /api/conversations
GET /api/conversations/c0b2688a-ff30-49af-bca1-c41309f9a0fd/events/search?limit=100
GET /sockets/events/c0b2688a-ff30-49af-bca1-c41309f9a0fd
GET /api/conversations/c0b2688a-ff30-49af-bca1-c41309f9a0fd/events/search?limit=100
POST /api/conversations/c0b2688a-ff30-49af-bca1-c41309f9a0fd/pause

This confirms the PR separates interrupt from pause without breaking explicit pause calls.

Issues Found

None.

Verdict: PASS.

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

Copy link
Copy Markdown
Member

@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, thank you!

@malhotra5 malhotra5 merged commit 3e2a77e into main May 27, 2026
42 checks passed
@malhotra5 malhotra5 deleted the fix/remote-conversation-interrupt branch May 27, 2026 14:58
@simonrosenberg simonrosenberg mentioned this pull request May 27, 2026
7 tasks
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