OpenHands / OpenHands/software-agent-sdk
Introduce an explicit interrupt / steer / queue control plane for user messages (ACP parity)
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.1k
- Forks
- 539
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 137
Description
Note: this issue was opened by an AI agent and its terminology was initially inverted (it had OpenHands native "queueing" by default). Corrected by @enyst to match the terminology agreed in the #proj-agent-canvas discussion. The three verbs and the support matrix below are the authoritative version.
Context
There is currently no first-class, user-facing decision for what happens when a user sends a message while an agent turn is already running. The behavior is implicit and inconsistent across surfaces.
Terminology (Codex App terms)
Three distinct behaviors, ordered by how disruptive they are:
- interrupt — interrupt the current tool call and post the message now. The in-flight action is cancelled and the new message becomes the head.
- steer — wait for the current tool call to finish, then inject the message into the same running turn. Non-destructive: it does not kill in-flight work; it just gets the message to the agent as soon as the current step ends.
- queue — hold the message until the agent is FINISHED / IDLE (the whole turn is done and the agent has yielded control back to the user), then send it as the prompt for the next turn.
The key distinction between steer and queue is when the held message is delivered: steer = at the next step boundary, still inside the current turn; queue = only once the turn has fully finished.
Current support
| Surface | interrupt | steer | queue |
|---|---|---|---|
OpenHands native (LocalConversation) |
yes | yes | no |
OpenHands ACP (ACPAgent) |
yes (kill-by-default) | no | no |
| Codex (reference) | yes | yes (CLI default) | yes |
- OpenHands native supports interrupt and steer, but not queue.
send_message()appends aMessageEventand rewindsFINISHED/STUCK -> IDLE, so the run loop picks the message up at the next step boundary (steer). There is no mode that holds a message until the turn is FINISHED and only then starts a fresh turn (queue). - OpenHands ACP effectively interrupts by default, routing intake through
session/canceland killing in-flight work. It supports neither steer nor queue. - Codex supports all three; in the Codex CLI steer is the default (configurable), and it also offers queue.
The gap / feature request
The behavior users are asking for (see the originating request) is queue — keep the current run intact, hold follow-ups, and deliver them one by one when the agent reaches FINISHED/IDLE, with interrupt kept as an explicit action rather than the default. OpenHands native has no queue mode today.
The end-of-turn queue is what enables the "give the agent a large task, line up several continue messages, walk away, and let each one fire when the agent yields plain text" workflow (self-driving long-running tasks). It became genuinely useful with GPT-5.x-class models. See the earlier write-up in OpenHands/OpenHands-CLI#226 ("Queue prompt at the end of turn?").
Proposal
Make the choice explicit rather than an emergent side effect of the lock/status race, and route all agent types (native, ACP) through one shared control plane exposing the three verbs above:
- Add a queue primitive to OpenHands native (hold until FINISHED/IDLE, then start the next turn), alongside the existing steer and interrupt.
- Bring ACP onto the same control plane so it can steer and queue instead of only interrupting.
- Let the surface (or the user, per message) choose the mode; keep interrupt as an explicit action, not the default.
Related / adjacent
- OpenHands/OpenHands-CLI#226 — "Queue prompt at the end of turn?" (the original request for the queue behavior).
- OpenHands/software-agent-sdk#270 — "Enable concurrent message handling in agent conversations" (root feature).
- OpenHands/software-agent-sdk#336 — "queue user messages in agent conversation" (earlier queue implementation attempt).
- OpenHands/OpenHands#15432 — "messages sent during streaming are not always seen by the agent" (message-loss bug; makes the existing steer path unreliable in the async loop).
- #4194 —
fix(sdk): pick up a user message that arrives during an async step(closes #15432; correctness of the steer path, not a new mode). - #4674 —
Split ConversationState's single FIFOLock by responsibility(structural prerequisite for intake-while-running). - OpenHands/OpenHands#15912 — ACP lifecycle control (pause / resume / cancel); to be expressed in interrupt / steer / queue terms.
- OpenHands/OpenHands#16119 — deferred / pending user-message UI projection (queue rendering).
Scope note
This is a control-plane / conversation-semantics concern, filed here (rather than OpenHands/OpenHands) with the same routing rationale as #4674: no canvas-side code is required for the core model, though the frontend will need to expose the choice.
Originally created by an AI agent (OpenHands); terminology corrected by @enyst.
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 by locating LocalConversation, ACPAgent, send_message(), ConversationState, and the ACP session/cancel path; read the related issues for the existing intake and lifecycle behavior. Map how messages arrive during a running turn before proposing the shared control plane. Done means interrupt, steer, and queue have the stated delivery semantics for native and ACP agents, with the choice exposed to the relevant surface.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend-api-design
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100