average-processor-75195
09/02/2026, 1:12 PMlimited-student-10747
09/02/2026, 1:14 PMlimited-student-10747
09/02/2026, 1:14 PMfresh-machine-37269
09/02/2026, 1:15 PMcalm-artist-7206
09/02/2026, 1:15 PMcalm-artist-7206
09/02/2026, 1:15 PMfresh-machine-37269
09/02/2026, 1:16 PMOpenHands/OpenHands#15432 — "messages sent during streaming are not always seen by the agent" (open). This is the canonical issue for "send a message mid-run and it's never acted on."
• OpenHands/software-agent-sdk#4194 — "pick up a user message that arrives during an async step" (open PR). Directly fixes #15432: arun() now re-scans for a newer user message before honoring FINISHED instead of silently dropping it into the next turn.
• OpenHands/software-agent-sdk#4674 — "Split ConversationState's single FIFOLock by responsibility" (open, labeled architecture, ready-for-dev). This is the real architectural fix: the single FIFOLock currently guards six independent concerns (event log, agent, execution_status, secrets, stats, HEAD), so message intake and the run loop contend at every step boundary. It proposes per-concern locks so intake can append while the agent runs. Explicitly notes it "closes #15432 by itself, with no inbox, no queue and no protocol work."
Frontend / UI ordering
• OpenHands/OpenHands#15433 — "user messages can split streaming text" (open).
• OpenHands/OpenHands#16119 — "keep queued messages below streaming responses" (closed). Adds the "deferred/pending user message" UI projection so a message sent mid-stream sits visually at the tail until all crossing streams finalize.
ACP alignment (the "always steer" side of the report)
• OpenHands/OpenHands#15912 — "ACP: expose pause / resume / cancel for conversations driven by Claude Code, Codex, Gemini CLI" (open). Notes ACP kills the action immediately while OpenHands queues. This is the issue most directly matching the "bring ACP in line with OpenHands" comment.
• OpenHands/software-agent-sdk#4108 — "propagate interruptions to active subagents" (open).
• OpenHands/software-agent-sdk#3557 — _"don't bump explicit_interrupt_generation on no-op pause/interrupt"_ (open).
• OpenHands/software-agent-sdk#4329 — "ACPAgent.close() races with agent.step()" (open).
So the answer to "are there existing issues?" is yes — substantially, and active work is already in flight.
Architectural proposal The cleanest framing: separate message intake from turn control, and make "steer vs queue" an explicit mode rather than an implicit side effect of the lock/status race.
1. Introduce an explicit intake inbox per conversation (SDK level).
Instead of send_message racing with the run loop for the FIFO lock, every incoming message lands in a small inbox/queue keyed by conversation. The run loop owns a single "should I keep going?" gate that drains the inbox only at defined points (step boundaries / FINISHED). This is essentially formalizing what #4194 patches ad-hoc and what #4674 wants structurally: intake needs only the event-log lock; the run loop needs only agent + status.
2. Split the lock, don't widen it (build on #4674).
Per-concern locks with a documented acquisition order. The run loop holds agent + `execution_status`; intake holds the event log. This is the prerequisite for any queuing semantics — you can't reliably enqueue while the agent runs if appending and stepping contend on one lock.
3. Model the "steer vs queue" decision as an explicit control-plane action.
Right now "queue" in OpenHands and "kill" in ACP are emergent behaviors. Propose a first-class per-message (or per-session-default) flag:
• queue (default): append to inbox; run loop picks it up at the next boundary, preserving the in-flight turn and long-running monitors.
• `steer`/`interrupt`: emit an explicit InterruptEvent (bumping explicit_interrupt_generation), cancel the in-flight action/LLM call, and make the new message the head of the queue.
• `pause`/`resume`/`cancel` as separate lifecycle verbs (already partially exposed for ACP in #15912).
4. Unify ACP with the built-in agent on the same intake path.
ACP currently bypasses the queue and cancels via session/cancel immediately. Route ACP message intake through the same inbox + control-plane layer, translating queue → buffered `session/load`+message and steer → session/cancel + immediate message. This brings ACP in line with OpenHands (matching the comment that ACP is the outlier), and #4329's close/step race fix is a prerequisite for doing this safely.
5. Keep the UI "deferred message" projection (already in #16119) as the presentation layer.
The inbox produces pending user message entries that the frontend renders at the tail until crossing streams finalize, so a queued message never visually splits an assistant response.
Sequencing I'd suggest:
1. Land #4674 (lock split) → makes intake-while-running correct.
2. Land #4194 (re-scan before FINISHED) → closes #15432.
3. Add the explicit inbox + control-plane verbs (`queue`/`steer`/`pause`/`resume`) on the SDK, then the agent-server.
4. Port ACP to that intake path (#15912 + #4329 fixes as prerequisites).
5. Default the UI to queue, expose `steer`/`stop` as explicit actions.
Want me to dig into any of the specific PRs (e.g. #4674's lock-splitting details or #15912's pause/resume API surface) more deeply?limited-student-10747
09/02/2026, 1:18 PMcalm-artist-7206
09/02/2026, 1:18 PMfresh-machine-37269
09/02/2026, 1:19 PMarun() re-scans for a newer user message before honoring FINISHED (and leaves the conversation IDLE on the last iteration). It closes #15432 with no inbox, queue, or protocol changes. The bigger lock-split (#4674) and ACP work can follow.limited-student-10747
09/02/2026, 1:19 PMfresh-machine-37269
09/02/2026, 1:19 PMFINISHED — it actually mirrors logic the ACP branch already had. So it closes the message-loss gap in the native agent, but it does not change the "queue vs kill" semantics: ACP still cancels the in-flight action immediately, while OpenHands queues. Bringing ACP in line (queue instead of immediate cancel) still needs the intake/control-plane work (#15912 + routing ACP through the same inbox).limited-student-10747
09/02/2026, 1:20 PMfresh-machine-37269
09/02/2026, 1:20 PMEventService → ACPAgent), when status is RUNNING, don't emit session/cancel. Instead persist the incoming message as a pending/queued message.
2. Drain the queue on idle. When the ACP step finishes and the conversation goes `FINISHED`/idle, check for queued messages and issue session/prompt (or session/load + message) to resume and process the next one — mirroring the run:true follow-up path already available in the server.
3. Keep explicit "stop" as the only cancel path. The Stop button continues to map to `session/cancel`/interrupt; it just stops being the default for ordinary messages.
That's the core. Supporting bits: rely on the existing deferred-message UI projection (#16119) so the queued message renders at the tail without splitting the stream, and land the `ACPAgent.close()`/step race fix (#4329) so cancel-on-stop is safe. Everything else (lock split #4674, general inbox) is optional hardening, not required for parity.bland-bear-604
09/02/2026, 1:24 PMlimited-student-10747
09/02/2026, 1:26 PMfresh-machine-37269
09/02/2026, 1:30 PMprotocol/src/turn_input.rs defines TurnInputMode::StartOrSteer | StartIfIdle | Steer { expected_turn_id } — three explicit routing modes.
• start_if_idle(...) returns NotSubmittedReason::NotIdle when a turn is active — i.e. it does not kill the running turn.
• The TUI keeps a real queue (`chatwidget/input_queue.rs`: queued_user_messages, pending_steers, rejected_steers_queue), and `turn_runtime.rs`'s maybe_send_next_queued_input() sends exactly one queued message when the turn completes. Steer is a deliberate user action, not the default.
OpenHands (software-agent-sdk) — also queue-by-default:
• local_conversation.py send_message() just appends a MessageEvent and rewinds FINISHED/STUCK → IDLE so the run loop picks it up at the next boundary. It does not cancel the in-flight step.
• The run loop (`run`/`arun`) re-scans for a newer user message before honoring FINISHED. Stop/interrupt is an explicit action.
So the honest picture is: Codex and OpenHands-native already match — both queue. The outlier is OpenHands' ACP integration (driven by the ACP session/cancel path), which steers by default and kills the action.
Does this change my assessment? Yes, in a good way — it simplifies it. There's no need to invent a new "queue vs steer" decision layer; that model already exists in Codex and roughly in OpenHands-native. The minimal fix is unchanged and even narrower: make ACP's intake match OpenHands-native (queue, don't cancel), with explicit "stop" as the only cancel path. Everything else (#4674 lock split, general inbox) remains optional hardening.
One caveat worth flagging: OpenHands-native's queue still had the async message-loss bug (#15432), and #4194 is what makes that queue reliable in the async path. So for parity to actually hold end-to-end, #4194 should still land — but it's about correctness of the queue, not about matching Codex's model.calm-artist-7206
09/02/2026, 1:33 PMlimited-student-10747
09/02/2026, 1:34 PMIt’s the same question, and it names the distinction precisely — the axis is when a queued message is injected, not whether messages get dropped:
• steer (Claude Code, and OpenHands today): inject the message mid-turn, in the current agent turn.
• queue (Codex): hold it and inject only when the agent yields control — the LLM stops with a plain-text message and no tool calls (end of turn).
limited-student-10747
09/02/2026, 1:34 PMmodern-fireman-99755
09/02/2026, 1:35 PMlimited-student-10747
09/02/2026, 1:36 PMlimited-student-10747
09/02/2026, 1:36 PMlimited-student-10747
09/02/2026, 1:38 PMcalm-artist-7206
09/02/2026, 1:40 PMlimited-student-10747
09/02/2026, 1:41 PMcalm-artist-7206
09/02/2026, 1:42 PMlimited-student-10747
09/02/2026, 1:43 PMlimited-student-10747
09/02/2026, 1:44 PMcalm-artist-7206
09/02/2026, 1:44 PMcalm-artist-7206
09/02/2026, 1:44 PMcalm-artist-7206
09/02/2026, 1:44 PMcalm-artist-7206
09/02/2026, 1:45 PMlimited-student-10747
09/02/2026, 1:45 PMcalm-artist-7206
09/02/2026, 1:46 PMaverage-processor-75195
09/02/2026, 1:49 PMfresh-machine-37269
09/02/2026, 1:50 PMprotocol/src/turn_input.rs defines TurnInputMode::StartOrSteer | StartIfIdle | Steer — and StartOrSteer is the default (start_or_steer_turn).
• The TUI submits immediately when a turn is running (<http://input_flow.rs|input_flow.rs>): should_submit_now = … || agent_turn_running, then <http://thread_routing.rs|thread_routing.rs> calls turn_steer(...) when there's an active turn id, falling back to turn_start only on NoActiveTurn.
• steer_input() (<http://turn_input.rs|turn_input.rs>) injects the message into the running turn's mailbox — it does not cancel the in-flight action. Interrupt is a separate explicit op (AppCommand::Interrupt → turn/interrupt).
• Queueing is the fallback, not the default: it kicks in for non-steerable turns (Review/Compact), plan streaming (is_plan_streaming_in_tui()), or before the session is configured. I found no user-facing config toggle for queue-vs-steer in the TUI.
OpenHands native — queue by default. send_message() only appends a MessageEvent and rewinds `FINISHED/STUCK → IDLE`; the run loop picks it up at the next step boundary. There's no "steer into a live step" concept, and interrupt/stop is explicit.
OpenHands ACP — kill by default. It routes intake through session/cancel, killing the in-flight action; it has neither queue nor steer.
So smolpaws was essentially right — Codex steers (and offers queue as a mode), while OpenHands queues and only offers "stop." The two products genuinely diverge on the default.
Does this change the assessment? Yes. My previous "they already match, only ACP is the outlier" was incorrect. The real gap is two-fold:
1. ACP is the odd one out — it kills where both Codex and OpenHands-native are non-destructive (steer/queue).
2. OpenHands-native has no "steer" mode at all, so it can't match Codex's default without new capability.
That reframes the target: if the goal is Codex parity, the right end-state is to add a steer (non-destructive inject) primitive to OpenHands, make it the default like Codex, keep queue as fallback for non-steerable states, keep explicit "stop" as interrupt — and route ACP onto that same three-way control plane instead of its current always-kill behavior. If the goal is just internal consistency (bring ACP to OpenHands-native), the earlier minimal fix (ACP queues instead of cancels) still stands. Which of those two end-states are you actually aiming for?, with interactive elementscalm-artist-7206
09/02/2026, 1:51 PMcalm-artist-7206
09/02/2026, 1:51 PMlimited-student-10747
09/02/2026, 1:51 PMlimited-student-10747
09/02/2026, 1:52 PMlimited-student-10747
09/02/2026, 1:52 PMcalm-artist-7206
09/02/2026, 1:52 PMcalm-artist-7206
09/02/2026, 1:53 PMlimited-student-10747
09/02/2026, 1:54 PMcalm-artist-7206
09/02/2026, 1:55 PMlimited-student-10747
09/02/2026, 1:55 PMlimited-student-10747
09/02/2026, 1:57 PMlimited-student-10747
09/02/2026, 1:59 PMcalm-artist-7206
09/02/2026, 2:00 PMlimited-student-10747
09/02/2026, 2:01 PMfresh-machine-37269
09/02/2026, 2:02 PMOpenHands/OpenHands has a bunch of closed issues/PRs with "queue" in the title (e.g. #12279, #13373, #14181, #14598, #16119 "keep queued messages below streaming responses"). These are about delivering messages over the wire (offline/reconnect/startup buffering, and UI ordering while streaming). None of them implement a user-facing choice to queue vs. steer vs. interrupt.
2. The message-loss bug — OpenHands/OpenHands#15432 and the SDK fixes #4194 (re-scan before FINISHED) and #4674 (split the FIFOLock). These make OpenHands-native's existing queue reliable; they do not add a queue option.
So the actual feature the user described — "keep the current run, enqueue follow-ups, process when idle; make interrupting an explicit action" — has no open issue tracking it in either repo. What exists is adjacent groundwork (the native queue already behaves this way, and #4194/#4674 fix its race), plus the ACP-consistency angle (#15912 covers ACP pause/resume/cancel, but not queue-vs-steer semantics).
Bottom line: the queue-vs-steer-vs-interrupt decision model doesn't have an issue yet. If we want it, the right move is to file one — likely on software-agent-sdk (it's the conversation/control-plane concern) with a routing note like #4674 carries, describing the three-way behavior (queue by default, steer as Codex-style inject, interrupt as explicit "stop"), and ACP as the primary driver for parity.
Want me to draft that issue?limited-student-10747
09/02/2026, 2:07 PMfresh-machine-37269
09/02/2026, 2:09 PMlimited-student-10747
09/02/2026, 2:09 PMcalm-artist-7206
09/02/2026, 2:10 PMlimited-student-10747
09/02/2026, 2:10 PMfresh-machine-37269
09/02/2026, 2:10 PMgpt-5.6-sol option you're seeing only applies to the image-inspection tool (inspect_image_with_vision), not to the model driving this conversation. So I can't literally "re-think as a different model."
What I can do is re-review anything you think I got wrong. If there's a specific part of the analysis or the two issue changes you want me to redo, tell me which and I'll re-examine the code and re-verify:
• whether "no open queue-option issue exists" is accurate,
• whether the interrupt/steer/queue default mapping (native=queue, Codex=steer, ACP=interrupt) is correct,
• whether #15912 vs #15728 was the right "ACP consistency" issue to touch.
Which one should I re-check?calm-artist-7206
09/02/2026, 2:11 PMidle a lot. People - and myself, were doing some silly stuff like:
• send a prompt for a complex task
• write a bunch of 10x “continue” messages
• go to bed 😅
The harness stored all those “continue”, and was sending them one by one, every time the agent FINISHED or IDLEd. 😅
Though I guess this is what a “conversation finished” hook can do these days… idk.limited-student-10747
09/02/2026, 2:12 PMcalm-artist-7206
09/02/2026, 2:13 PMlimited-student-10747
09/02/2026, 2:13 PMcalm-artist-7206
09/02/2026, 2:15 PMbland-bear-604
09/02/2026, 2:21 PMcalm-artist-7206
09/02/2026, 2:27 PMlimited-student-10747
09/02/2026, 2:28 PMcalm-artist-7206
09/02/2026, 2:39 PM