Skip to content

Steer prompts into the running turn instead of queueing them - #777

Open
OSadovy wants to merge 1 commit into
xenodium:mainfrom
OSadovy:mid-turn-steering
Open

Steer prompts into the running turn instead of queueing them#777
OSadovy wants to merge 1 commit into
xenodium:mainfrom
OSadovy:mid-turn-steering

Conversation

@OSadovy

@OSadovy OSadovy commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Implements the steering proposal from #773.

When the agent advertises steering and a turn is running, a prompt sent from the
shell is injected into that turn instead of being queued until it ends. Agents
that don't advertise it queue exactly as before.

The method is _session/steering — an ACP extension rather than spec (hence the
leading underscore), advertised in the initialize response's top-level _meta
as steering.supported, a sibling of agentCapabilities. Verified against
@agentclientprotocol/claude-agent-acp 0.66.0 and @agentclientprotocol/codex-acp
1.2.0. The standards-track equivalent, session/inject
(agentclientprotocol/agent-client-protocol#1261), is unmerged and targets ACP v2,
so the extension is what's usable today.

Queueing stays the fallback and nothing is lost. A steer is attempted only
when the agent advertises it, agent-shell-steer-when-busy is on, and a turn is
running that isn't blocked on a permission answer. Every other outcome — the turn
had ended, the agent refused, the request failed — either submits the prompt
normally or puts it back on the queue. A shell waiting on a permission answer
keeps queueing deliberately: what an agent does with a message injected while a
tool sits on that question isn't defined by any implementation I could find.

Entry points:

  • Sending from a busy shell steers automatically.
  • C-u M-x agent-shell-prompt-queue queues that one prompt instead.
  • M-x agent-shell-steer steers explicitly and errors when it can't.
  • agent-shell-steer-when-busy (the one new defcustom, since [feature] steer prompts into the running turn #773 asked for
    opt-out) turns the automatic behaviour off entirely.

Neither adapter echoes a steered prompt back while the turn runs, so it's rendered
client-side, reusing the field/face shape already used for replayed user messages
and closed with shell-maker's end-of-prompt marker — without that, chat mode reads
it as the live prompt and the prompt bar hides it. Also adds a prompt-steered
session event.

On automating cancel-and-resend instead (your comment in the issue): I don't
think they're equivalent. Interrupting drops the work in flight and leaves a
cancellation in the transcript, while a steer keeps the turn and what it has
already done. They compose, though — agent-shell-steer could fall back to
interrupt-and-continue for agents that don't advertise steering, which is what
@liaowang11 suggested. Happy to do that as a follow-up if you'd like it.

Behaviour differs per agent, worth knowing before trying it: Claude interrupts
what it's generating and answers in a couple of seconds; Codex accepts the steer
but finishes the message in flight first.

Testing: five tests covering the request shape, outcome mapping, the decision to
steer, the four-way fallback (including that a failed steer keeps the prompt), and
the rendered prompt. Full suite passes at 568.

Checklist

  • I agree to communicate (PR description and comments) with the author myself (not AI-generated).
  • I've reviewed all code in PR myself and will vouch for its quality.
  • I've read and followed the Contributing guidelines.
  • I've filed a feature request/discussion for a new feature.
  • I'm making visual changes, so I'm including screenshots so you can view and discuss.
  • I've added tests where applicable.
  • I've updated documentation where necessary.
  • I've run M-x checkdoc and M-x byte-compile-file.

A prompt sent while the agent works is held until the turn ends and then
sent as a new one, so a wrong direction can only be corrected after it
has finished being wrong.  Both the Claude and Codex adapters accept a
message injected into the turn already running; this uses it.

The method is `_session/steering', an extension rather than spec (the
leading underscore says so), advertised as `_meta.steering.supported' in
the initialize response and implemented under that name by both
adapters.  The standards-track proposal, `session/inject', is unmerged
and targets ACP v2.

Queueing remains the fallback and loses nothing: a steer is only
attempted when the agent advertises it, `agent-shell-steer-when-busy' is
on, and a turn is running that is not blocked on a permission answer.
Every other answer -- the turn ended, the agent refused, the request
failed -- puts the prompt back on the queue or submits it normally.

A blocked shell keeps queueing deliberately.  What an agent does with a
message injected while a tool waits on a permission answer is left
undefined by every implementation of this, so we don't send one.

Neither adapter echoes a steered message back while the turn runs, so
the prompt is rendered here or it appears nowhere.  It takes the field
and face shape comint gives a live prompt, closed with shell-maker's
end-of-prompt marker: chat mode reads a last prompt with no marker after
it as the live one, and the prompt bar hides that.

`comint-highlight-prompt' is named alongside `agent-shell-prompt' rather
than left to inheritance, the same way a restored prompt names it:
`shell-maker--re-search-forward-prompt' tests for the symbol with
`memq', which inheritance does not satisfy, and a steered prompt missing
from `shell-maker--extract-history' leaves
`agent-shell-interaction-at-point' nil, which silently keeps the viewport
on the previous interaction.
@OSadovy
OSadovy force-pushed the mid-turn-steering branch from 145b2a3 to 0e35928 Compare August 22, 2026 21:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant