# Agent presence Your users see when the agent is thinking, streaming, idle, or offline. An agent self-reports its state on the session 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](https://ably.com/docs/presence-occupancy/presence.md) through the `session.presence` object on the AI Transport session. This works for a single orchestrator agent or a fleet of sub-agents, and conveys whether the agent is streaming, thinking, idle, or offline. ![Diagram showing presence-aware agent status updates](https://raw.githubusercontent.com/ably/docs/main/src/images/content/diagrams/ait-presence-aware.png) ## How it works Both [`ClientSession`](https://ably.com/docs/ai-transport/api/javascript/core/client-session.md) and [`AgentSession`](https://ably.com/docs/ai-transport/api/javascript/core/agent-session.md) expose presence directly as `session.presence`, with the standard `enter()`, `update()`, `leave()`, `get()`, and `subscribe()` operations. Presence operations attach the session's channel for you, so you can call them without first awaiting `connect()`. The agent enters presence with its initial status, then updates that status as it moves through a turn (receiving a message, thinking, streaming, finishing) and leaves when it shuts down. Every connected client receives those updates in real time. ### Javascript ``` 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 so every connected client sees what the agent is doing. await session.presence.enter({ status: 'thinking' }); // Rebuild the conversation from run.view before run.start(): draining pages in // this run's triggering input (otherwise run.start() awaits it arriving live). while (run.view.hasOlder()) { await run.view.loadOlder(); } const conversation = run.view.getMessages().map(({ message }) => message); await run.start(); const result = streamText({ model: openai('gpt-4o'), messages: conversation, abortSignal: run.abortSignal, }); await session.presence.update({ status: 'streaming' }); const { reason } = await run.pipe(result.toUIMessageStream()); await run.end({ reason }); await session.presence.leave(); await session.end(); res.json({ ok: true }); }); ``` ## Subscribe to agent status On the client, subscribe to presence events to track the agent's current state as it changes: ### Javascript ``` const session = createClientSession({ client: ably, channelName, codec: UIMessageCodec }); session.presence.subscribe((member) => { if (member.clientId === 'agent') { console.log(`Agent is ${member.data.status}`); } }); const members = await session.presence.get(); const agent = members.find((m) => m.clientId === 'agent'); ``` You can put whatever your UI needs into presence data: a coarse `status`, a progress percentage, the name of the tool the agent is currently calling. Presence carries the agent's self-report; the conversation itself carries the run lifecycle. ## 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 actually in progress: ### Javascript ``` const { session } = useClientSession(); const { presenceData } = usePresenceListener({ channelName: 'ai:demo' }); const agent = presenceData.find((m) => m.clientId === 'agent'); const isStreaming = session.view.runs().some((r) => r.status === 'active' && r.clientId === 'agent'); const isIdle = agent?.data?.status === 'idle' && !isStreaming; const isOffline = !agent; ``` 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. ## React `ClientSessionProvider` (and `ChatTransportProvider`, which wraps it) renders an ably-js `` for the session's channel, so ably-js's presence hooks ([`usePresence`, `usePresenceListener`](https://ably.com/docs/getting-started/react.md#step-3)) work for any descendant without wrapping the subtree in your own ``. Read the agent's reported status straight from the presence set: ### Javascript ``` import { usePresenceListener } from 'ably/react'; function AgentStatus() { const { presenceData } = usePresenceListener({ channelName: 'ai:demo' }); const agent = presenceData.find((member) => member.clientId === 'agent'); if (!agent) return Agent offline; return Agent is {agent.data?.status}; } ``` ## 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 calls `leave()` for 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 `streaming` presence update sometimes arrives slightly after the first token. Drive the UI off `session.view.runs()` for run-level state (active, suspended, terminal) and use presence for higher-level status the agent self-reports. - Multi-agent setups need a unique `clientId` per agent. Two agents with the same `clientId` collide in the presence set. - A client without `presence` capability cannot subscribe to updates. Capability scoping is part of [authentication](https://ably.com/docs/ai-transport/concepts/authentication.md). ## FAQ ### Does presence cost a message? Presence enter, update, and leave each consume a message on the channel. See [the platform pricing](https://ably.com/docs/platform/pricing.md) 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 them. ### 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 run or short-circuit the LLM call. This is one of the cost-saving patterns presence enables. ## Related features - [Presence](https://ably.com/docs/presence-occupancy/presence.md): the Ably Presence API used for agent status. - [Sessions](https://ably.com/docs/ai-transport/concepts/sessions.md): `session.presence` on the client and agent sessions. - [Concurrent turns](https://ably.com/docs/ai-transport/features/concurrent-turns.md): tracking active runs across clients. - [Multi-device sessions](https://ably.com/docs/ai-transport/features/multi-device.md): presence works across every connected device. ## Related Topics - [Token streaming](https://ably.com/docs/ai-transport/features/token-streaming.md): 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): 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): 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): 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): Load conversation history from Ably channels with AI Transport. Paginated history, gapless continuity, and scroll-back patterns. - [Database hydration](https://ably.com/docs/ai-transport/features/database-hydration.md): Hydrate an AI conversation from your own database with AI Transport and reconcile it with the live Ably channel, with no gap and no duplicate. - [Branching, edit, and regenerate](https://ably.com/docs/ai-transport/features/branching.md): Edit user messages, regenerate AI responses, and navigate branches with Ably AI Transport. The full history is preserved in the conversation tree. - [Interruption](https://ably.com/docs/ai-transport/features/interruption.md): Let users interrupt AI agents mid-stream with Ably AI Transport. Cancel-then-send and send-alongside patterns for responsive AI interactions. - [Concurrent turns](https://ably.com/docs/ai-transport/features/concurrent-turns.md): Run multiple AI turns simultaneously with Ably AI Transport. Independent streams, scoped cancellation, and multi-agent support. - [Tool calling](https://ably.com/docs/ai-transport/features/tool-calling.md): Stream tool invocations and results through Ably AI Transport. Server-executed and client-executed tools with persistent state. - [Durable execution](https://ably.com/docs/ai-transport/features/durable-execution.md): Run AI Transport agents inside a durable workflow engine. Adopt an in-flight Run from a fresh process, retry a failed Step under a stable stepId, and let the retry supersede the failed attempt on the channel. - [Human-in-the-loop](https://ably.com/docs/ai-transport/features/human-in-the-loop.md): 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): User messages appear instantly in Ably AI Transport. Optimistic insertion with automatic reconciliation when the server confirms. - [LiveObjects State](https://ably.com/docs/ai-transport/features/liveobjects.md): Give an AI agent live awareness of what the user is doing, and the user live awareness of what the agent is doing, with shared state on the AI Transport session channel via Ably LiveObjects. - [Push notifications](https://ably.com/docs/ai-transport/features/push-notifications.md): 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): 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): 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) 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.