fix(sdk): override RemoteConversation.interrupt() to POST /interrupt (B4 of #3341)#3398
Conversation
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().
Python API breakage checks — ✅ PASSEDResult: ✅ PASSED |
REST API breakage checks (OpenAPI) — ✅ PASSEDResult: ✅ PASSED |
all-hands-bot
left a comment
There was a problem hiding this comment.
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
Coverage Report •
|
||||||||||||||||||||
all-hands-bot
left a comment
There was a problem hiding this comment.
✅ 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.
What
Addresses B4 of #3341.
RemoteConversationdidn't overrideinterrupt(), so it inheritedBaseConversation.interrupt(), whose default falls back topause(). A remote caller invokinginterrupt()therefore got pause semantics — wait for the in-flight LLM request to finish — from a method namedinterrupt().The server already exposes
POST /conversations/{id}/interrupt(conversation_router.py:219), so this adds the missing override, mirroringpause():Test
test_remote_conversation_interruptasserts the call POSTs to/interruptand not to/pause— i.e. it no longer silently degrades to the inherited fallback.Verification
ruff format --checkandruff checkcleantest_remote_conversation.pysuite passes (32 tests)Agent Server images for this PR
• GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server
Variants & Base Images
eclipse-temurin:17-jdknikolaik/python-nodejs:python3.13-nodejs22-slimgolang:1.21-bookwormPull (multi-arch manifest)
# Each variant is a multi-arch manifest supporting both amd64 and arm64 docker pull ghcr.io/openhands/agent-server:30e52a4-pythonRun
All tags pushed for this build
About Multi-Architecture Support
30e52a4-python) is a multi-arch manifest supporting both amd64 and arm6430e52a4-python-amd64) are also available if needed