Reconnection and recovery

Your users get the whole response, even when their connection drops. The agent keeps publishing to the channel, and a client that drops receives what it missed when it reconnects.

Responses survive connection drops. The agent publishes to the Ably channel, not to a client's connection, so a dropped client does not stop the response. When the client reconnects, it receives the messages published while it was away.

Diagram showing a client disconnecting mid-stream and resuming from the exact point of disconnection on reconnect
JavaScript

1

2

3

4

5

6

7

// Client code.
import { fromSerial } from '@ably/ai-transport';

transport.on('discontinuity', () => {
  unsubscribe();
  unsubscribe = transport.subscribe(apply, { history: { replay: fromSerial(lastSeen) } });
});

How it works

The channel outlives every connection attached to it. When a client's connection drops:

  1. The agent keeps publishing to the channel, so the response carries on unchanged.
  2. The Ably client reconnects by itself.
  3. After a brief drop, the channel resumes, and the client receives the messages published during the gap, in order.
  4. After a longer gap, the channel cannot guarantee that the client received every message. The transport reports a discontinuity, and the client subscribes again from the last message it received.

Recover after a discontinuity

A discontinuity is a channel state change after which messages may have been missed: the channel entered FAILED, SUSPENDED or DETACHED, or attached again with resumed set to false. The transport reports it on on('discontinuity').

To recover, the client keeps the serial of the newest message it has received, and subscribes again from that serial with fromSerial:

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

// Client code.
import { fromSerial } from '@ably/ai-transport';

let lastSeen = stored.serial;

const apply = (delivery) => {
  render(delivery);
  if (delivery.message.serial > lastSeen) lastSeen = delivery.message.serial;
};

let unsubscribe = transport.subscribe(apply, { history: { replay: fromSerial(lastSeen) } });

transport.on('discontinuity', () => {
  unsubscribe();
  unsubscribe = transport.subscribe(apply, { history: { replay: fromSerial(lastSeen) } });
});

The new subscription replays the messages after lastSeen from history, in channel order, then delivers live messages.

A response that was streaming when the gap started repairs itself. Its next append arrives as an update that carries the full content, and the codec delivers only the text the client has not seen.

In React, useTransportStatus reports that a discontinuity happened. To subscribe again on every discontinuity, listen with transport.on('discontinuity') in an effect, as the quickstart does.

Repair failed appends on the agent

On the agent, pipe() recovers from an append that fails, for example on a network blip between the agent and Ably:

  1. The pipe sends each append without waiting for it, so the response streams at the speed of the connection.
  2. If an append fails, the pipe carries on.
  3. When that stream ends, the pipe replaces the message with an update that carries the full content, before it sends the message that ends the stream.

Every subscribed client receives the whole response. A live client reads the missing text from the update, and history holds the whole message. The pipe rejects with PipeFailed only if the repair fails too.

Join a response mid-stream

A client that subscribes while a response is streaming has not seen the start of that response's message. With a replay, it receives the response's text so far in one delivery, then the remaining appends live. A second tab opened mid-response therefore shows the response so far, then the rest of it as it streams.

Know when to replay

Most reconnects need nothing from you. Subscribe with a replay in two cases:

  • The transport reported a discontinuity, so the client may have missed messages.
  • The client is loading the conversation for the first time, or after a reload. It renders what your store holds and replays the channel from the stored serial, as history and replay describes.

Edge cases and unhappy paths

  • A replay reads history from the point where the channel attached again. History reaches back as far as the channel's retention period, so a client offline for longer than that can miss messages.
  • If the replay fails, the subscription's replayed rejects and the handler receives live messages with a gap. Show the user that earlier messages may be missing, or subscribe again.
  • A client without the history capability cannot replay. Capability scoping is part of authentication.
  • If the agent process crashes mid-response, the response's message stays on the channel with the text it reached, and no end event follows. A client merging the response does not receive the event that finishes it.
  • Closing the Ably client detaches the channel, which the transport reports as a discontinuity. Check the connection state before you replay, or remove the listener before you close the client.

FAQ

How long can a client be offline before it needs to replay?

Ably resumes a connection that drops for up to about two minutes, and delivers the messages published in that time. After a longer gap the channel attaches again without resuming, and the transport reports a discontinuity.

Does the user see the response pause when they reconnect?

The client receives the text of the response so far, then the remaining appends. Rendering stays continuous when your merge appends each delivery's text to the response it belongs to.

What if the agent process dies before the response finishes?

The partial response stays on the channel, and the conversation is intact. AI Transport does not retry the model call. To finish the turn after a process restart, run the agent inside a durable workflow engine.

Does the client need special code to handle reconnection?

A brief drop needs none, because the Ably connection resumes. For a longer gap, the client listens for a discontinuity and subscribes again from the last message it received.