Double texting
Your users send a follow-up before the previous response finishes. AI Transport gives you three patterns: run concurrently by default, queue the new message, or cancel and re-prompt.
Double texting is when a user sends a new message while the agent is still streaming a response. AI Transport supports this through concurrent turns. The new message starts a new turn that runs alongside the existing one. Your application chooses between three patterns: concurrent, queued, or cancel-then-send.
Default behaviour: concurrent
Sending a message while the agent is streaming starts a new concurrent turn. Both the existing response and the new turn run in parallel:
1
2
3
4
5
6
7
8
await send([{ id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: 'Explain quantum computing' }] }]);
// Agent starts streaming a response.
// User sends another message before the response finishes.
await send([{ id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: 'Also, what is superposition?' }] }]);
// Both turns stream concurrently.Each turn has its own stream and cancel handle. The conversation tree contains both exchanges.
Queue while streaming
To prevent concurrent turns and queue messages instead, detect active turns and gate the send:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const activeTurns = useActiveTurns({ transport });
const handleSend = async (text) => {
if (activeTurns.size > 0) {
addToQueue(text);
return;
}
await send([{ id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text }] }]);
};
useEffect(() => {
if (activeTurns.size === 0 && queue.length > 0) {
const next = queue.shift();
send([{ id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: next }] }]);
}
}, [activeTurns.size]);This gives users feedback that their message was received and will be processed next, without interrupting the current response.
Cancel and re-prompt
To cancel the current turn before sending the new message, use the interruption pattern:
1
2
3
4
5
6
7
const handleSend = async (text) => {
if (activeTurns.size > 0) {
await transport.cancel();
}
await send([{ id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text }] }]);
};The current response stops, and the new message starts a fresh turn. Use this when the new message supersedes the previous one.
Choose a pattern
| Pattern | Behaviour | Best for |
|---|---|---|
| Concurrent (default) | Both turns run in parallel. | Multi-topic conversations, research tasks. |
| Queue while streaming | New message waits for the current turn to finish. | Sequential conversations, chatbots. |
| Cancel and re-prompt | The current turn is aborted, the new turn starts. | Corrections, redirections. |
Concurrent turns work well for exploratory conversations. Queuing is better for sequential interactions where order matters. Cancel-then-send is the right pattern when the new message replaces the previous one.
Edge cases and unhappy paths
- Queueing without a visible indicator confuses users who expect instant send. Show a queued state in the UI.
- A queue that grows without bound during a long-running turn pressures channel and server limits. Cap the queue at a reasonable size.
- Cancel-then-send races with the in-flight stream. A few tokens from the cancelled turn arrive after the cancel returns; treat them as part of the cancelled turn.
- Concurrent turns share the connection's message rate. Many parallel streams approach the rate limit faster than one. See token streaming.
- A pattern choice that varies across devices in the same session leads to inconsistent behaviour. Decide the pattern at the application layer, not per device.
FAQ
Can I mix patterns within one conversation?
Yes. Decide per send. A user that hits stop and re-prompts uses cancel-then-send; a user that types fast and triggers queueing uses queue-while-streaming.
Does the agent know it received a follow-up while streaming?
Concurrent turns are independent on the server. If the agent should be aware of a follow-up, surface it through your own protocol; AI Transport does not inject messages into an in-flight turn.
What if the LLM hits a rate limit during a concurrent burst?
The corresponding turn fails with reason 'error'. Other turns are unaffected.
How do I show typing-paused indicators while queued?
Track queue.length and activeTurns.size in your UI state. Show a queued chip while the queue is non-empty and the active turn is still streaming.
Does double texting break the conversation tree?
No. Each user message is a sibling under the same parent. The tree records both exchanges; the view's branch selection determines what renders.
Related features
- Concurrent turns: the underlying mechanism for parallel turns.
- Interruption: the cancel-then-send pattern in detail.
- Cancellation: cancel signals and scoped cancellation.