Double texting

Open in

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.

Default behavior

By default, sending a message while the agent is streaming starts a new concurrent turn. Both the existing response and the new turn run in parallel:

JavaScript

1

2

3

4

5

6

7

8

9

// User sends a message
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

If you want to prevent concurrent turns and instead queue messages, detect active turns and show a queued indicator in the UI:

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

const activeTurns = useActiveTurns(transport)

const handleSend = async (text) => {
  if (activeTurns.size > 0) {
    // Show a queued indicator in the UI
    addToQueue(text)
    return
  }

  await send([{ id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text }] }])
}

// When the active turn finishes, send the next queued message
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-then-send

An alternative is to cancel the current turn before sending the new message. This is the interruption pattern:

JavaScript

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. This works well when the user's new message supersedes the previous one.

Choose a pattern

PatternBehaviorBest for
Concurrent (default)Both turns run in parallelMulti-topic conversations, research tasks
Queue-while-streamingNew message waits for current turn to finishSequential conversations, chatbots
Cancel-then-sendCurrent turn is aborted, new turn startsCorrections, redirections

The right pattern depends on your application. Concurrent turns work well for exploratory conversations. Queuing is better for sequential interactions where order matters. Cancel-then-send is best when the new message replaces the previous one.