Overview
When an AI agent is executing tool calls, users may want to send a message that steers the agent mid-execution — adding context, correcting course, or refining the request without waiting for the response to finish. By default (withoutpendingMessages), a message sent while the agent is responding never interrupts the in-flight response: it’s buffered and processed as its own turn once the current turn completes, with multiple messages running sequentially in arrival order.
The pendingMessages option enables steering instead, injecting user messages between tool-call steps via the AI SDK’s prepareStep. Messages that arrive during streaming are queued and injected at the next step boundary. A message that is not injected becomes the next turn instead, whether that is because shouldInject returned false or because there were no more step boundaries (single-step response or final text generation). The backend handles that, so no client-side re-send is involved.
Use the streamText passed to your agent’s run callback. It wires up pending-message injection automatically. If you import streamText directly from ai, spread chat.toStreamTextOptions() into its options to connect injection.
How it works
- User sends a message while the agent is streaming
- The message is sent to the backend via input stream (
transport.sendPendingMessage) - The backend queues it in the steering queue
- At the next
prepareStepboundary (between tool-call steps),shouldInjectis called - If it returns
true, the message is injected into the LLM’s context - A
data-pending-message-injectedstream chunk confirms injection to the frontend - If
shouldInjectreturnsfalse, orprepareStepnever fires (no tool calls), the message stays queued on the backend and is answered as the next turn
Backend: chat.agent
AddpendingMessages to your chat.agent configuration:
streamText composes your prepareStep callback after its own. You can add step-specific settings without disconnecting steering. With the manual chat.toStreamTextOptions() spread, a later prepareStep property replaces the spread’s callback.
To try it, ask the agent to inspect a document and summarize it in English. While the tool runs, send a steering message asking for French. The next model step receives that instruction, and the stream includes data-pending-message-injected.
Options
shouldInject
Called once per step boundary with the full batch of pending messages. Returntrue to inject all of them, false to skip (they’ll be available at the next boundary or become the next turn).
prepare
Transform the batch of pending messages before they’re injected into the LLM’s context. By default, each UIMessage is converted to ModelMessages individually. Useprepare to combine multiple messages or add context:
Stream chunk
When messages are injected, the SDK automatically writes adata-pending-message-injected stream chunk containing the message IDs and text. The frontend uses this to:
- Confirm which messages were injected
- Remove them from the pending overlay
- Render them inline at the injection point in the assistant response
Backend: chat.createSession
PasspendingMessages to the session options:
turn.prepareStep() to get a prepareStep function that handles both injection and compaction. Users who spread chat.toStreamTextOptions() get it automatically.
Backend: MessageAccumulator (raw task)
PasspendingMessages to the constructor and wire up the message listener manually:
MessageAccumulator methods
Frontend: usePendingMessages hook
TheusePendingMessages hook manages all the frontend complexity — tracking pending messages, detecting injections, and handling the turn lifecycle.
Hook API
PendingMessage
Message lifecycle
-
Steering messages are sent via
transport.sendPendingMessage()immediately. They appear as purple pending bubbles. If injected, they disappear from the overlay and render inline at the injection point. If not injected, the backend answers them as the next turn once the response finishes; the client does not need to re-send them. -
Queued messages stay client-side until the turn completes, then auto-send as the next turn via
sendMessage(). They can be promoted to steering mid-stream by clicking “Steer instead”. - Promoted messages are queued messages that were converted to steering. They get sent via input stream immediately and follow the steering lifecycle from that point.
Transport: sendPendingMessage
TheTriggerChatTransport exposes a sendPendingMessage method for sending messages via input stream without disrupting the active stream subscription:
sendMessage() from useChat, this does NOT:
- Add the message to useChat’s local state
- Cancel the active stream subscription
- Start a new response stream
usePendingMessages hook calls this internally — you typically don’t need to use it directly.
Coexistence with compaction
Pending message injection and compaction both useprepareStep. When both are configured, the auto-injected prepareStep handles them in order:
- Compaction runs first — checks threshold, generates summary if needed
- Injection runs second — pending messages are appended to either the compacted or original messages

