# Concurrent turns
Your application runs multiple AI request-response cycles on one session at the same time. AI Transport multiplexes turns over a single channel; each turn has its own stream, cancel handle, and lifecycle.
Concurrent turns let multiple request-response cycles run at the same time on one session. Each turn has its own stream, cancel handle, and lifecycle. This is what makes interruption, multi-user sessions, and multi-agent architectures possible.
## How it works
Turns are multiplexed on the Ably channel via `turnId`. Every message published during a turn (text deltas, tool calls, lifecycle events) carries a header identifying its turn. The client transport reads these headers and routes each message to the correct turn's stream.
### Javascript
```
const turn1 = await view.send([
{ id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: 'Summarize the report' }] },
]);
const turn2 = await view.send([
{ id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: 'What are the key risks?' }] },
]);
for await (const chunk of turn1.stream) {
renderToPanel('summary', chunk);
}
for await (const chunk of turn2.stream) {
renderToPanel('risks', chunk);
}
```
On the server, each turn is handled independently. The server transport creates a separate turn object per request, each with its own abort signal and lifecycle:
### Javascript
```
app.post('/api/chat', async (req, res) => {
const { turnId, clientId, messages } = req.body;
const turn = transport.newTurn({ turnId, clientId });
await turn.start();
const result = streamText({
model: anthropic('claude-sonnet-4-20250514'),
messages,
abortSignal: turn.abortSignal,
});
await turn.streamResponse(result.toUIMessageStream());
await turn.end('complete');
res.json({ ok: true });
});
```
## Track active turns
`useActiveTurns` returns a map of which clients have which turns in progress:
### Javascript
```
const activeTurns = useActiveTurns({ transport });
for (const [clientId, turnIds] of activeTurns) {
console.log(`${clientId} has ${turnIds.size} active turn(s)`);
}
const isAgentStreaming = activeTurns.has('agent-1');
```
The map updates in real time across every connected client. A turn that starts or ends anywhere on the channel updates every subscriber's `useActiveTurns` immediately.
## Scope cancellation to a turn
Cancel one turn without affecting others by passing a `turnId` filter:
### Javascript
```
await transport.cancel({ turnId: turn1.turnId });
// turn2 continues streaming
```
The default cancel (`{ own: true }`) cancels every turn started by this client. For concurrent turns, scoped cancellation is essential.
| Filter | Effect |
| --- | --- |
| `{ turnId }` | Cancel one specific turn. |
| `{ own: true }` | Cancel every turn started by this client. |
| `{ clientId }` | Cancel every turn by a specific client. |
| `{ all: true }` | Cancel every active turn on the channel. |
See [cancellation](https://ably.com/docs/ai-transport/features/cancellation.md?source=llms.txt) for the full cancel API, including server-side authorisation and abort hooks.
## Wait for turns to complete
`transport.waitForTurn()` returns a promise that resolves when matching turns complete:
### Javascript
```
await transport.waitForTurn({ turnId: turn1.turnId });
await transport.waitForTurn({ own: true });
await transport.waitForTurn({ all: true });
```
Use this to sequence work: send a follow-up only after the first response completes, or disable a submit button until pending turns resolve.
## Use cases
### Interruption
Cancel the current turn and immediately start a new one:
#### Javascript
```
await transport.cancel({ own: true });
const newTurn = await view.send([
{ id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: 'Actually, focus on the budget instead' }] },
]);
```
See [Interruption](https://ably.com/docs/ai-transport/features/interruption.md?source=llms.txt) for the full pattern.
### Multi-user sessions
Two users prompting the same session at the same time. Each user's turn is independent:
#### Javascript
```
const turnA = await viewA.send([
{ id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: 'What does section 3 mean?' }] },
]);
const turnB = await viewB.send([
{ id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: 'Summarize section 5' }] },
]);
```
Both turns stream concurrently on the shared channel.
### Multi-agent
An orchestrator dispatches work to multiple sub-agents, each streaming concurrently on the same channel:
#### Javascript
```
app.post('/api/chat', async (req, res) => {
const { messages } = req.body;
const researchTurn = transport.newTurn({ turnId: 'research', clientId: 'researcher' });
const analysisTurn = transport.newTurn({ turnId: 'analysis', clientId: 'analyst' });
await Promise.all([
runResearchAgent(researchTurn, messages),
runAnalysisAgent(analysisTurn, messages),
]);
res.json({ ok: true });
});
```
The client sees both agent responses arriving in parallel, each tagged with its own turn ID and client ID.
## Edge cases and unhappy paths
- Concurrent turns share the channel's message rate. A burst of parallel streams approaches the per-connection rate limit faster than a single stream. See [token streaming](https://ably.com/docs/ai-transport/features/token-streaming.md?source=llms.txt#rollup) for rollup behaviour.
- A scoped cancel against a `turnId` that no longer matches an active turn is a no-op. Cancellation does not error on absence.
- `waitForTurn({ all: true })` includes turns started by other clients. If you only want to wait for your own, use `{ own: true }`.
- A multi-agent setup with the same `turnId` across sub-agents collides on the channel. Generate a unique `turnId` per sub-agent.
- Two clients sending at the same time produce two turns. The conversation tree shows both as siblings of the same parent message.
## FAQ
### How many turns run concurrently?
There is no hard limit on the channel side. Practical limits come from your application's concurrency (server compute, model rate limits) and the channel's message rate. Plan for the publish rate, not the turn count.
### Does the client need to track turn IDs?
The transport tracks turn IDs internally. Hold onto the `turn` handle returned by `send()` only if you need to cancel it specifically or wait for it specifically.
### How do I tell which turn a message belongs to?
Each message carries its `turnId` in the header. `useView`'s tree exposes the relationship; you do not parse headers manually.
### Can two turns run on behalf of the same user?
Yes. The `clientId` does not constrain how many turns a client has open. Scoped cancellation lets you target one of them.
### Why use turn IDs instead of message IDs?
A turn is one unit of agent work that produces multiple messages. The turn ID groups every message the agent publishes for that work, so cancel and wait operate on the unit a user understands.
## Related features
- [Cancellation](https://ably.com/docs/ai-transport/features/cancellation.md?source=llms.txt): scoped cancel signals and server-side abort handling.
- [Interruption](https://ably.com/docs/ai-transport/features/interruption.md?source=llms.txt): cancel and immediately send a new message.
- [Multi-device sessions](https://ably.com/docs/ai-transport/features/multi-device.md?source=llms.txt): concurrent turns across devices.
## Related Topics
- [Token streaming](https://ably.com/docs/ai-transport/features/token-streaming.md?source=llms.txt): Stream AI-generated tokens to clients in realtime using AI Transport, with support for message-per-response and message-per-token patterns.
- [Cancellation](https://ably.com/docs/ai-transport/features/cancellation.md?source=llms.txt): Cancel AI responses mid-stream with Ably AI Transport. Scoped cancel signals, server-side authorization, and graceful abort handling.
- [Reconnection and recovery](https://ably.com/docs/ai-transport/features/reconnection-and-recovery.md?source=llms.txt): AI Transport streams survive connection drops automatically. Clients reconnect and resume from where they left off with no lost tokens.
- [Multi-device sessions](https://ably.com/docs/ai-transport/features/multi-device.md?source=llms.txt): Share AI conversations across tabs, phones, and laptops with Ably AI Transport. All devices see the same session in real time.
- [History and replay](https://ably.com/docs/ai-transport/features/history.md?source=llms.txt): Load conversation history from Ably channels with AI Transport. Paginated history, gapless continuity, and scroll-back patterns.
- [Conversation branching](https://ably.com/docs/ai-transport/features/branching.md?source=llms.txt): Edit user messages, regenerate AI responses, and navigate branches with Ably AI Transport. The full history is preserved as a tree.
- [Interruption](https://ably.com/docs/ai-transport/features/interruption.md?source=llms.txt): Let users interrupt AI agents mid-stream with Ably AI Transport. Cancel-then-send and send-alongside patterns for responsive AI interactions.
- [Tool calling](https://ably.com/docs/ai-transport/features/tool-calling.md?source=llms.txt): Stream tool invocations and results through Ably AI Transport. Server-executed and client-executed tools with persistent state.
- [Human-in-the-loop](https://ably.com/docs/ai-transport/features/human-in-the-loop.md?source=llms.txt): Add human approval gates to AI agent workflows with Ably AI Transport. Approve tool executions and provide input across devices.
- [Optimistic updates](https://ably.com/docs/ai-transport/features/optimistic-updates.md?source=llms.txt): User messages appear instantly in Ably AI Transport. Optimistic insertion with automatic reconciliation when the server confirms.
- [Agent presence](https://ably.com/docs/ai-transport/features/agent-presence.md?source=llms.txt): Show agent status in your AI application with Ably Presence. Display streaming, thinking, idle, and offline states in real time.
- [Push notifications](https://ably.com/docs/ai-transport/features/push-notifications.md?source=llms.txt): Notify users when AI agents complete background tasks with Ably Push Notifications. Reach users even when they're offline.
- [Chain of thought](https://ably.com/docs/ai-transport/features/chain-of-thought.md?source=llms.txt): Stream reasoning and thinking content alongside responses with Ably AI Transport. Display chain-of-thought in real time.
- [Double texting](https://ably.com/docs/ai-transport/features/double-texting.md?source=llms.txt): Handle users sending multiple messages while the AI is streaming with Ably AI Transport. Queue or run messages concurrently.
## Documentation Index
To discover additional Ably documentation:
1. Fetch [llms.txt](https://ably.com/llms.txt?source=llms.txt) for the canonical list of available pages.
2. Identify relevant URLs from that index.
3. Fetch target pages as needed.
Avoid using assumed or outdated documentation paths.