Last week we introduced AI Transport v0.2.0 and made one idea the centre of the design: the session is the channel. Every input, output, and lifecycle event for an AI conversation is just a message published to an Ably channel, which is what makes a session durable, multi-party, and resumable.
In v0.3.0, we added first-class support for presence and LiveObjects to AI sessions, allowing you and your agent to see who's online and update shared state in real time.
We made the codec interface declarative, moved event ordering and de-duplication into the transport. Now you can write a codec as a simple description of the wire format, and the transport will handle reordering, de-duplication, and replay for you.
See who's online with presence
The presence object is just the same presence API you know from Ably Pub/Sub. The client and agent session objects now expose it as session.presence, so you can enter, leave, and subscribe to presence events on the session's channel:
const session = createClientSession({ client: ably, channelName });
await session.presence.enter({ role: 'user' });
session.presence.subscribe((member) => {
console.log(member.clientId, member.action); // 'enter' | 'leave' | ...
});
Agents and users enter the same set, distinguished by clientId, so "is the agent online?" and "who else is watching this conversation?" are all answered by the same API. You can also use presence data to update per-participant state which other clients can see in realtime, like a "typing..." indicator or a cursor position in a shared document.
Our ably-js presence React hooks work out-of-the-box in a SessionProvider. The session providers now mount an ably-js <ChannelProvider> for the session's channel, so ably-js's own usePresence and usePresenceListener hooks just work for any component underneath:
function OnlineList() {
usePresence({ channelName: 'ai:demo' });
const { presenceData } = usePresenceListener({ channelName: 'ai:demo' });
return (
<ul>
{presenceData.map((m) => <li key={m.clientId}>{m.clientId}</li>)}
</ul>
);
}
LiveObjects for the state that lives next to the chat
Some state belongs in the conversation; some lives beside it. A document the agent is editing, task progress, votes, settings. LiveObjects enables synchronized shared state on the same channel as the conversation. Sessions now expose it as session.object.
type SessionState = { status: string };
const root = await session.object.get<SessionState>();
await root.set('status', 'reviewing');
root.subscribe(() => {
console.log(root.get('status').value());
});
The API is symmetric, an agent can publish structured state mid-run (progress, a todo list, a shared document, client selection state) and every client on the session sees it update live or can update it themselves, with late joiners synced on attachment.
Unlike presence, LiveObjects isn't enabled by default. Object operations need channel modes that aren't in Ably's default set, and the LiveObjects plugin needs to be loaded, see Enable LiveObjects.
Codecs are now declarative
A codec teaches the transport your wire format: how to encode and decode events. In v0.2.0 that meant writing a reducer that also had to de-duplicate against the stream's high-water-mark and reason about event ordering. That's exactly the kind of thing the transport should own, not you.
So we moved it. Event ordering, de-duplication, and replay now live in the transport, and codecs are written with a new declarative defineCodec API plus a small set of header-field helpers. You describe the inputs, outputs, and headers; the transport folds each node in canonical serial order behind the scenes:
import { defineCodec, strField, enumField } from '@ably/ai-transport';
const codec = defineCodec<MyInput, MyOutput>()({
reducer: { init, fold, getMessages },
// Outputs the agent streams back: a chunked text family, then a discrete "finish".
output: (b) => [
b.stream('text', { start: 'text-start', delta: 'text-delta', end: 'text-end' }),
b.event('finish', { fields: { reason: enumField('reason', 'stop', 'length') } }),
],
// Inputs the client publishes.
input: (b) => [
b.event('user-message', { fields: { role: strField('role') } }),
],
});
See our codec interface docs for further information.
Get started
AI Transport v0.3.0 is available now:
npm install @ably/ai-transport
- Read the docs — Presence, LiveObjects, and the codec interface.
- See the source — ably/ably-ai-transport-js.
- Sign up free — 6M messages a month on the free tier.
We're excited to see what you build.



