Skip to main content

Agent state

Track and respond to agent state changes in your frontend.

Overview

Agents move through a lifecycle of states: connecting, listening, thinking, speaking, and eventually disconnecting or failing. Your frontend can read that state to show the right UI -- for example, when a user can talk, when an agent is busy, or if something has gone wrong. The agent publishes its state as the lk.agent.state participant attribute.

For most UI decisions you should use state getters, which are booleans like canListen and isFinished, rather than raw state, so your UI stays correct as the SDK evolves.

Agent states

State comes from two places. The agent SDK reports what the agent is doing (for example, listening or speaking), and the client reports connection and terminal outcomes. The following table defines each state.

StateDescription
connectingThe client is connecting to the room. The agent is not yet in the loop.
pre-connect-bufferingThe app is connecting and may buffer user input before the connection is ready. Used for instant connect. Can be disabled so the client goes straight to the active states.
initializingThe agent is connecting and setting up.
idleThe agent is connected but idle (waiting for user input).
listeningThe agent is actively listening to the user.
thinkingThe agent is processing input or performing actions.
speakingThe agent is producing audio output.
disconnectedThe user disconnected cleanly. This is the normal success outcome after ending a session.
failedThe session entered an error state. Check the platform's failure API such as failureReasons for details.

Lifecycles

The following diagrams show the lifecycle of an agent with or without instant connect enabled. The flow can end in failed from any state, such as if the agent never connects or leaves the room unexpectedly during the conversation.

With instant connect

In this flow, the agent connects, buffers input, initializes, and then can listen, think, speak, and eventually disconnect.

Loading diagram…

Without instant-connect

In this flow, the agent connects, initializes, and then can listen, think, speak, and eventually disconnect. It doesn't buffer input before the connection is ready.

Loading diagram…

Disconnected vs failed

The disconnected state is the successful status of a call ending. It means the user connected, then disconnected, and everything shut down correctly.

The failed state is the error status of a call ending. It means the state machine hit an error, such as connection or agent errors. When state is failed, use the platform's failure property like failureReasons to surface or handle the issues.

Using state getters

Use state getters for UI decisions to accommodate new states as they're added over time. State getters are booleans over raw state so your UI stays correct as the SDK evolves.

Tip

Only use raw states when you need to show a specific state in the UI, such as "Pre-connect buffering…" or "Connecting…" messages.

State getters and their associated descriptions and states can be found in the following table.

GetterDescriptionAssociated states
canListenThe user can speak. Input is accepted (including while the agent is thinking or speaking).pre-connect-buffering, listening, thinking, speaking
isConnectedThe session is connected to the room.listening, thinking, speaking
isPendingThe agent is in a transitional phase (connecting or setting up).connecting, initializing, idle
isFinishedThe session has reached a terminal outcome.disconnected, failed

Accessing agent state

The following examples show how to read state and getters for each platform.

Use the useAgent hook to access the agent's state and getters:

import { useAgent } from '@livekit/components-react';
function AgentStatus() {
const agent = useAgent();
return (
<>
{agent.canListen && (
<div>
<p>Agent ready!</p>
<p>Agent is in state {agent.state}</p>
{/* Show chat panel or other agent specific ui elements here */}
</div>
)}
{agent.isFinished && (
agent.failureReasons?.length > 0 ? (
<p>Agent failed: {agent.failureReasons.join(', ')}</p>
) : (
<p>Agent disconnected.</p>
)
)}
</>
);
}

Access the agent from the session and use its state getters. The session's agent property exposes canListen, isFinished, agentState, and error:

@EnvironmentObject var session: Session
var body: some View {
let agent = session.agent
if agent.canListen {
VStack {
Text("Agent ready!")
Text("Agent is in state \(String(describing: agent.agentState ?? .initializing))")
// Show chat panel or other agent-specific UI here
}
}
if agent.isFinished {
if let error = agent.error {
Text("Agent failed: \(error.localizedDescription)")
} else {
Text("Agent disconnected.")
}
}
}

Use the rememberAgent composable to access the agent's state and getters.

@Composable
fun AgentStatus() {
val agent = rememberAgent()
if (agent.canListen) {
Column {
Text("Agent ready!")
Text("Agent is in state ${agent.agentState.name}")
// Show chat panel or other agent-specific UI here
}
}
if (agent.isFinished) {
if (agent.failureReasons.isNotEmpty()) {
Text("Agent failed: ${agent.failureReasons.joinToString(", ")}")
} else {
Text("Agent disconnected.")
}
}
}

Custom state

The built-in agent states cover the agent's lifecycle, but your application may need to share additional state between the frontend and agent. For example, you might want to display which tool the agent is currently using, show a list of items the agent has found, or let the user toggle agent behavior from the UI.

For this kind of application-specific state, use LiveKit's state synchronization and RPC features. State synchronization keeps key-value data in sync across participants, while RPC lets you call methods on the agent or frontend from the other side.