Cancellation of an agent invocation is supported in AI Transport. Cancellation is a turn-level operation. The client publishes a cancel signal on the Ably channel, the server receives it and aborts the matching turns, but the session remains intact, other turns continue, and both sides handle cleanup gracefully. Unlike closing an HTTP connection, cancellation is an explicit signal.
How it works
Since sessions are bidirectional, cancel is just a signal on the channel. The client publishes a cancel message with a filter specifying which turns to cancel. The server's transport matches the filter against active turns and fires their abort signals. The LLM stream stops, the turn ends with reason 'cancelled', and all subscribers are notified.
1
2
3
4
5
6
7
// Client: cancel the current turn
await turn.cancel()
// Server: abort signal fires automatically
const result = streamText({
abortSignal: turn.abortSignal, // wired to cancel
})Cancel filters
Cancel signals are scoped. You control exactly what gets cancelled:
| Filter | Effect | Use case |
|---|---|---|
{ own: true } (default) | Cancel all turns started by this client | Stop button |
{ turnId: 'abc' } | Cancel one specific turn | Cancel a specific generation |
{ clientId: 'user-1' } | Cancel all turns by a specific client | Admin cancellation |
{ all: true } | Cancel all turns on the channel | Emergency stop |
1
2
3
4
5
6
7
8
// Cancel your own turns (default)
await transport.cancel()
// Cancel a specific turn
await transport.cancel({ turnId: activeTurn.turnId })
// Cancel all turns on the channel
await transport.cancel({ all: true })Server-side handling
Abort signal
Every turn has an abortSignal that fires when the turn is cancelled. Pass it to your LLM call:
1
2
3
4
5
6
7
8
9
10
11
const turn = transport.newTurn({ turnId, clientId })
await turn.start()
const result = streamText({
model: anthropic('claude-sonnet-4-20250514'),
messages: history,
abortSignal: turn.abortSignal,
})
const { reason } = await turn.streamResponse(result.toUIMessageStream())
await turn.end(reason) // reason is 'cancelled' if abort firedCancel authorization
The onCancel hook lets you authorize or reject cancel requests:
1
2
3
4
5
6
7
8
9
const turn = transport.newTurn({
turnId,
clientId,
onCancel: async (request) => {
// Only allow the turn owner to cancel
const owner = request.turnOwners.get(request.filter.turnId)
return owner === request.message.clientId
},
})The CancelRequest includes message (the raw cancel message with clientId), filter (parsed scope), matchedTurnIds, and turnOwners (map of turn ID to owner client ID). Return false to reject.
Abort hook
The onAbort hook runs after the abort signal fires, giving you a chance to publish final events before the stream closes:
1
2
3
4
5
6
7
const turn = transport.newTurn({
turnId,
clientId,
onAbort: async (write) => {
await write({ type: 'text-delta', textDelta: '\n[Response cancelled]' })
},
})Cancel on close
When a client transport closes, it can optionally cancel its own turns:
1
await transport.close({ cancel: { own: true } })Related features
- Interruption and barge-in - cancel and immediately send a new message
- Concurrent turns - multiple turns with independent cancel handles
- Token streaming - what gets cancelled
- Client transport API - reference for
canceland other client methods. - Sessions and turns - how turns and cancel signals interact within a session.
- Get started - build your first AI Transport application.