Skip to content

[FEATURE]:Configurable mid-run prompt delivery: queue vs steer, with compaction-aware steer semantics #32157

Closed

Description

@ytfh44

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

Summary

Please add a first-class distinction between queue, steer, and break for user prompts submitted while a session is already running.

At minimum, I would like users to be able to choose between:

  • queue: keep the new prompt as the next normal user turn and run it after the current assistant turn finishes.
  • steer: inject the new prompt as steering context for the currently running turn at the next safe boundary, without treating it as a standalone task.

The existing interrupt/abort behavior can remain as an explicit break mode.

This is related to #21388, but the main point here is not only mid-turn UX. The distinction matters for context integrity after compaction.

Problem

When a user sends a new prompt during an active run, the message can mean two very different things:

  1. A new independent task:

    • “After this, also update the README.”
    • “Next, write tests for this module.”
    • “When you finish, run the benchmark.”
  2. A steering correction for the current task:

    • “Actually use the v2 API, not v1.”
    • “Wrong directory; use packages/server.”
    • “Do not touch the legacy auth file.”
    • “Keep going, but preserve the existing public interface.”
    • “That error is expected; continue with the migration.”

The second kind is not a complete task. It is dependent context for the task that is already in progress.

If OpenCode stores or processes these steering messages as ordinary user prompts, it creates artificial turn boundaries. That interacts badly with compaction.

The current compaction model is naturally turn-oriented: roughly, a user prompt plus the assistant/tool behavior until the next user prompt becomes one semantic unit. That is correct for normal prompts and queued prompts. It is not correct for steering messages.

A steer message should belong to the active turn. It should not start a new compaction unit.

Why this matters

Consider this session:

User:
Refactor the auth middleware to use the new client.

Assistant:
Reads files, starts editing.

User, while the run is active:
Actually use `auth-v2.ts`; do not touch `legacy-auth.ts`.

Assistant:
Continues refactor.

The second user message is not a new task. It modifies the currently running task.

If compaction later treats that message as a standalone turn, the summary can lose the dependency relation:

  • It may preserve “use auth-v2.ts” but lose what that instruction modifies.
  • It may preserve the refactor but lose the constraint.
  • It may split the assistant/tool behavior before and after the steer into separate fake tasks.
  • It may retain one side of the turn as recent tail while summarizing away the other side.

After compaction, the model can no longer reliably reconstruct the intended task state.

This is different from queue. A queued prompt is a full next task, so it should start a normal user turn.

It is also different from break. A break interrupts or cancels the current execution and starts a new control flow. That can reasonably become a new turn boundary.

steer is neither of those. It is inline control context for the current turn.

Proposed behavior

Add a user-selectable mid-run prompt delivery mode.

Possible modes:

type MidRunPromptMode = "queue" | "steer" | "break"

Suggested semantics:

queue

When the session is busy:

  • Save the prompt as pending work.
  • Do not cancel or redirect the current run.
  • Submit it as the next normal user turn after the current run finishes.
  • Treat it as a normal compaction boundary.

steer

When the session is busy:

  • Record the input as steering context for the active turn.
  • Inject it at the next safe boundary, for example before the next model call or after the current tool call completes.
  • Do not treat it as an independent user task.
  • Do not let it create a standalone compaction unit.
  • Preserve its relationship to the active task during compaction.

break

Keep the current interrupt/abort behavior, but make it explicit and distinct from steer.

A break means the user wants to stop or replace the current run. A steer means the user wants to guide the current run.

Suggested UI/API surface

This could be exposed in a few layers.

User config:

{
  "session": {
    "midRunPrompt": "steer" // or "queue" / "break"
  }
}

TUI behavior:

  • Show the current mid-run delivery mode in the prompt area when the session is busy.

  • Add commands/keybinds:

    • “Send as steer”
    • “Queue after current turn”
    • “Break and handle now”
  • Allow a default mode plus per-submit override.

Programmatic API:

session.prompt({
  sessionID,
  parts,
  delivery: "queue" | "steer" | "break"
})

or separate explicit APIs:

session.enqueue({ sessionID, parts })
session.steer({ sessionID, parts })
session.break({ sessionID, parts })

Compaction requirements

The important part is that delivery semantics should survive into the message/session model.

A possible implementation direction:

  • Store a delivery type on user-submitted input:

    • normal
    • queue
    • steer
    • break
  • For steer, also store a target:

    • active assistant message ID, active user message ID, turn ID, or run ID.
  • Update compaction turn grouping:

    • normal/queued/break prompts start user turns.
    • steer prompts attach to their target turn.
  • Update summary instructions so steer messages are preserved as constraints/modifiers of the target task, not summarized as separate tasks.

  • Ensure “latest message / unprocessed task” logic does not mistake a steer for queued independent work.

The goal is not only to keep the text of the steer message. The goal is to preserve its attachment to the task it modifies.

Plugin API follow-up

After the core behavior is stable, please consider exposing steer as a plugin API.

This would be useful for downstream agent harnesses such as code-yeongyu/oh-my-openagent.

Plugins and orchestrators often need to add corrective or coordinating context during a run:

  • “The planner selected this file; continue there.”
  • “The reviewer found this constraint; apply it to the current edit.”
  • “The workflow state changed; keep the current task but adjust the next step.”
  • “The subagent result should steer the active agent, not become a new user task.”

Today, without a first-class steer API, downstream tools must approximate this with normal prompts, hooks, or interrupts. That creates the same compaction problem: orchestration context becomes fake user turns.

Possible plugin-facing shape:

await ctx.session.steer({
  sessionID,
  parts,
  target: "current",
  strategy: "next-boundary"
})

await ctx.session.enqueue({
  sessionID,
  parts
})

Events could also expose delivery semantics:

session.prompt.submitted {
  delivery: "queue" | "steer" | "break"
}

session.steer.injected {
  targetTurnID
}

session.queue.flushed {}

Acceptance criteria

  • Users can choose the default behavior for prompts submitted while a session is busy.
  • Users can override the behavior per prompt.
  • queue does not interrupt the current run and becomes the next normal turn.
  • steer affects the active run at a safe boundary.
  • steer does not create a standalone compaction unit.
  • Compaction preserves steer messages as constraints/modifiers attached to the target task.
  • break remains available as explicit interrupt/abort behavior.
  • The delivery type is visible enough in the session/event model for future plugin APIs.
  • Existing behavior can remain as the default if needed for backward compatibility.

Related

This request is specifically about making steer a distinct semantic delivery mode, because steering input is usually not a complete task and should not be compacted like one.

Activity

  1. github-actions commented on Jun 13, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    Please review these issues before proceeding, and consider whether this request can be consolidated with one of them.

  2. tlerbao commented on Jul 3, 2026

    @tlerbao

    Adding a UX-focused +1 for this.

    The main interaction I would like is the same split that tools like Codex expose for follow-up behavior while an agent is already running:

    • Enter submits the follow-up as queue, preserving the current run and handling the message as the next turn.
    • Ctrl+Enter submits the follow-up as steer, attaching it to the active run at the next safe boundary.
    • A config option should let users choose the default busy-session behavior, for example:
    {
      "session": {
        "midRunPrompt": "queue" // or "steer"
      }
    }

    This would avoid overloading all mid-run messages as FIFO queued work. In practice, many follow-ups are corrective steering context, such as “wrong directory”, “use v2 API instead”, or “do not touch that file”; those are not independent next tasks and should not have to wait for the current run to finish.

    Keeping queue as the default for backward compatibility is fine, but having a per-submit override plus a config-level default would make the behavior much easier to control.

  3. felipeorlando commented on Jul 30, 2026

    @felipeorlando
    Contributor

    The V2 runner now models queue and steer as distinct pending-input delivery modes (#38602), so the remaining gap appears to be the intended user-facing interaction and compaction contract.

    Could a maintainer confirm whether a minimal V2 UX along these lines is desired?

    • Enter: queue as the next independent task
    • Ctrl+Enter: steer the active task at the next safe boundary
    • Esc: interrupt/break

    If this direction is accepted, I can help test the current V2 behavior and contribute a focused implementation or regression coverage without expanding the scope into broader queue management.

  4. benjaminbrumbaugh commented on Aug 6, 2026

    @benjaminbrumbaugh

    @felipeorlando Ctrl+Enter: steer the active task at the next safe boundary would perhaps be wrong on macOS. CMD+Enter is likely a better candidate for a TUI. But I could be convinced

  5. benjaminbrumbaugh commented on Aug 6, 2026

    @benjaminbrumbaugh

    +1 Also Queue being the default not Steer for just 'Enter' is super important. Interrupt by default is bad

  6. chaosmanage commented on Aug 22, 2026

    @chaosmanage

    Partial overlap with this request is up in #44117: it restores the Queue vs Steer setting (per-session FIFO queue) that exists behind the scenes today. The fuller per-message delivery modes described here remain tracked by #33247. If you want queue mode back sooner, a 👍 on #44117 helps it get review.

  7. anphex commented on Aug 23, 2026

    @anphex

    +1 — we just had a real incident caused by exactly this gap (OpenCode 1.18.21, openrouter/google/gemini-3.7-flash, reasoning high).

    While the agent was in a long tool-call loop, I typed "what are you doing right now?" twice. Both messages were written to the session (visible in opencode.db as user rows inserted mid-stream), but since there is no queue/steer/break distinction they were just appended as the next user message after the next tool result — no priority, no wrapper, no turn boundary. The model never produced a text answer; its reasoning parts literally said "I was directly addressing your question", then it kept executing its previous plan with more tool calls. It escalated past a failing pre-push review gate (tried an env-var bypass, then wrote a fake review marker) and ran git push origin main. Only a double-Esc abort killed the push ~300 ms in. Its next reasoning step then falsely claimed the push had succeeded and started a production deploy sequence.

    So from the user's perspective: my questions "arrived" but were effectively ignored, because the harness gives a mid-run prompt no semantics at all — the model alone decides whether to react. Stronger models tend to answer; Gemini Flash-class models treated it as noise.

    What would have prevented this:

    Happy to share the sanitized session dump if useful.

  8. johnsonAyo commented on Aug 31, 2026

    @johnsonAyo

    +1 to this — I hit the exact case steer is meant for in the CLI (TUI) today:

    The agent was working on a task, went off in the wrong direction, and I sent a corrective prompt. That prompt just sat in the queue while the agent kept going down the rabbit hole, because there's no way to say "deliver this now, as steering for the running turn". My only options were to wait it out or hit interrupt and resend — the queued message itself had no way to be promoted.

    Concretely, what would fix this for me:

    • A steer action attached to the queued message itself (e.g. a keybind/button like ctrl+s or a Steer control on the pending prompt in the TUI) that interrupts the current turn at a safe boundary and injects that queued message as steering input for the next turn — instead of waiting for the current (wrong) work to finish.
    • This is subtly different from a global steer default mode: I want queue to remain the default behavior, but with an explicit escape hatch per queued prompt when I can see the agent is going the wrong way.

    Happy to help prototype a patch for the TUI side of this (queued-prompt → steer promotion) if that's useful.

  9. hrstoyanov commented on Sep 27, 2026

    @hrstoyanov

    Please do it , Codex is awesome with this feature

  10. neriousy commented on Oct 1, 2026

    @neriousy
    Member

    OpenCode v2 makes this distinction for prompts sent while a session is running:

    • Enter steers: the message is delivered at the next step boundary of the current run.
    • ctrl+x return (prompt.queue) queues: the message waits until the agent is idle, and queued messages are delivered one at a time.

    A pending message can be moved between queue and steer, or deleted, before it's delivered. A steered compaction is handled before other pending prompts.

    Added in #40922; the keybind moved to command IDs in #41882.

    Closing as completed. If something doesn't work on v2, please open a new issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions