OpenHands / OpenHands/software-agent-sdk
[Bug]: /pause reports PAUSED while an ACP agent keeps executing tool calls
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.1k
- Forks
- 539
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 137
Description
Summary
POST /api/conversations/{id}/pause flips execution_status to paused immediately, but an ACP-driven agent keeps executing tool calls for the rest of its turn. The reported state and the actual state of the workspace diverge for as long as the in-flight prompt() runs, which can be minutes or hours.
POST /interrupt stops the work as documented. The problem is specific to /pause.
Environment
openhands-agent-server/openhands-sdk/openhands-tools1.47.0, installed from PyPI- Python 3.12.13, macOS (Apple Silicon), server bound to loopback
- Agent:
ACPAgent,acp_server: claude-code,acp_command: ["npx","-y","--prefer-offline","@agentclientprotocol/claude-agent-acp@0.63.0"],acp_model: sonnet
Reproduction
Start a conversation whose task is a long sequence of separate tool calls, so progress is observable on disk independently of the API:
Do this strictly one step at a time, never in a loop, never batched.
For i from 1 to 40: runsh -c 'sleep 3; echo step-$i >> steps2.txt'as its own separate tool call, then continue to the next i.
Then, while it is running:
POST /api/conversations/{id}/pause- Poll
GET /api/conversations/{id}andwc -l steps2.txtevery 5 s for 40 s POST /api/conversations/{id}/interrupt, poll again for 40 s
Observed
[ 16.1s] before pause: status=running lines=2
[ 16.1s] POST /pause -> 200; status now paused
[ 56.4s] [paused] 40s window: lines 2 -> 10 (grew=True)
samples (status, lines): paused/3 paused/4 paused/5 paused/6
paused/7 paused/8 paused/9 paused/10
[ 56.4s] POST /interrupt -> 200; status now paused
[ 96.7s] [interrupted] 40s window: lines 10 -> 10 (grew=False)
[ 96.7s] POST /run -> 200; status now running
[127.0s] [resumed] 30s window: lines 10 -> 15 (grew=True)
Eight more shell commands ran, each writing to the workspace, during 40 consecutive seconds in which the API reported paused.
In a separate run, POST /run against a conversation in this "paused but still working" state returned 409, so the server itself does not consider it stopped either.
Expected
Either of:
/pauseactually stops the ACP turn (e.g. by routing to ACPsession/cancel, which OpenHands/OpenHands#15912 already assumes it does), orexecution_statusdoes not becomepauseduntil the in-flight prompt has actually stopped — a distinct transitional value such aspausingwould make the divergence visible to clients.
Root cause
LocalConversation.pause() is cooperative at the run-loop step boundary, and says so:
The pause will take effect at the next iteration of the run loop (between agent steps).
Note: If called during an LLM completion, the pause will not take effect until the current LLM call completes.
For an LLM-direct agent, a "step" is one completion, so the window is short and bounded. For ACPAgent, one step is an entire prompt() call, inside which the external CLI may run an unbounded number of tool calls. pause() still sets execution_status = PAUSED and emits PauseEvent synchronously, so the status flips while the subprocess keeps going for the rest of the turn.
Impact
The reported paused state is not a safety property for ACP agents. Any orchestrator that gates on it — "pause the agent, then take an approval decision / touch a shared resource / inspect the workspace" — is operating on a workspace that is still being modified. We hit this while building an external scheduler on top of the Agent Server and had to map our Pause operation onto /interrupt instead.
Related
- OpenHands/OpenHands#15912 — Canvas-side pause/resume/cancel UI; states that
/pauseroutes to ACPsession/cancel, which does not match the behaviour measured here. - #4829 — interrupt / steer / queue control plane for user messages. Related area, different problem (message delivery semantics rather than the state reported by
/pause). - #3327 — ACP lifecycle primitives (closed).
Happy to supply the full driver script or test other versions if useful.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with LocalConversation.pause() and the ACPAgent prompt() run-loop boundary, then reproduce the issue using the documented pause, status polling, and interrupt sequence. Trace when execution_status changes relative to the in-flight prompt; done means /pause no longer reports a settled paused state while ACP tool calls continue, with behavior covered by an appropriate regression test.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100