Agent presence
Your users see when the agent is thinking, streaming, idle, or offline. AI Transport channels carry Ably Presence, so an agent self-reports its state and every client sees it in real time.
Agent presence gives session participants a real-time view of which agents are active and what they are doing. Agent presence uses Ably's native Presence API on the AI Transport session channel. This works for a single orchestrator agent or a fleet of sub-agents, and conveys whether the agent is streaming, thinking, idle, or offline.
How it works
The agent enters presence on the AI Transport session channel with status data. As the agent moves through its turn lifecycle (receiving a message, thinking, streaming, finishing), it updates its presence data. Every connected client receives these updates in real time.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
app.post('/api/chat', async (req, res) => {
const invocation = Invocation.fromJSON(await req.json());
const session = createAgentSession({ client: ably, channelName: invocation.sessionName, codec: UIMessageCodec });
await session.connect();
const run = session.createRun(invocation, { signal: req.signal });
// Enter presence on this invocation's channel so every subscriber sees the agent.
const channel = ably.channels.get(invocation.sessionName);
await channel.presence.enter({ status: 'thinking' });
await run.start();
await run.loadConversation();
const result = streamText({
model: openai('gpt-4o'),
messages: run.messages,
abortSignal: run.abortSignal,
});
await channel.presence.update({ status: 'streaming' });
const { reason } = await run.pipe(result.toUIMessageStream());
await run.end(reason);
await channel.presence.leave();
session.close();
res.json({ ok: true });
});Subscribe to agent status
On the client, subscribe to presence events to track the agent's current state:
1
2
3
4
5
6
7
8
9
10
const channel = ably.channels.get(sessionChannelName);
channel.presence.subscribe((member) => {
if (member.clientId === 'agent') {
console.log(`Agent is ${member.data.status}`);
}
});
const members = await channel.presence.get();
const agent = members.find((m) => m.clientId === 'agent');Combine presence with active runs
For richer status indicators, combine presence data with the active Runs on the view. Presence tells you the agent's self-reported state. session.view.runs() tells you which Runs are in progress:
1
2
3
4
5
6
const { session } = useClientSession();
const agentStatus = useAgentPresence(channel); // your custom hook
const isStreaming = session.view.runs().some((r) => r.status === 'active' && r.clientId === 'agent');
const isIdle = agentStatus === 'idle' && !isStreaming;
const isOffline = agentStatus === null;This is enough information for the UI to show a typing indicator while the agent thinks, a streaming animation while tokens arrive, and an offline badge when the agent disconnects.
Edge cases and unhappy paths
- An agent that exits without calling
presence.leave()(for example, a crashed process) is automatically removed from presence after a timeout. The agent is treated as present until the timeout fires. Wire a graceful shutdown that callsleavefor the best user experience. - A serverless agent that comes up for one turn and tears down should enter and leave presence per turn; entering once and leaving once at the end is fine for a long-running agent.
- Presence updates do not guarantee strict ordering with channel messages. A
streamingpresence update sometimes arrives slightly after the first token. Drive the UI offsession.view.runs()for run-level state (active, suspended, terminal) and use presence for higher-level status the agent self-reports. - Multi-agent setups need unique
clientIdper agent. Two agents with the sameclientIdcollide in the presence set. - A client without
presencecapability cannot subscribe to updates. Capability scoping is part of authentication.
FAQ
Does presence cost a message?
Presence enter, update, and leave each consume a message on the channel. See the platform pricing for current rates.
Can clients enter presence too?
Yes. Presence is symmetric. A client that enters presence shows up alongside agents in the presence set. Use the clientId to distinguish.
How long does presence persist after a disconnect?
Until Ably's presence timeout fires (currently around 15 seconds). Active connections are not affected; this is for ungraceful disconnects.
What is the difference between presence and the view's active runs?
Presence is self-reported by the agent. session.view.runs() is observable from the channel by inspecting run lifecycle events. Presence reports intent; active runs report fact. Both together produce richer status.
Can I pause inference when no users are connected?
Yes. Subscribe to presence and check whether any non-agent participants are present. If none, end the turn or short-circuit the LLM call. This is one of the cost-saving patterns presence enables.
Related features
- Presence: the Ably Presence API used for agent status.
- Concurrent turns: tracking active turns across clients.
- Multi-device sessions: presence works across every connected device.