enough-match-43171
12/17/2025, 9:11 PMinitializing - agent is starting up. this should be brief.
• listening - agent is waiting for user input
• thinking - agent is processing user input
• speaking - agent is speaking
How would i listen this events on client sidetall-airline-16756
12/17/2025, 9:11 PMtall-airline-16756
12/17/2025, 9:11 PMlk.agent.state
• The agent's current state is synchronized to the participant attribute lk.agent.state.
• Subscribe to participant attribute updates and read that attribute when it changes.
• Example (React with LiveKit SDK):
import { useRoom } from '@livekit/components-react';
import { useEffect, useState } from 'react';
function useAgentState() {
const room = useRoom();
const [agentState, setAgentState] = useState<string>('');
useEffect(() => {
const onUpdated = (participant: any) => {
const state = participant?.attributes?.['lk.agent.state'];
if (state) setAgentState(state);
};
room.on('participant_attributes_updated', onUpdated);
return () => { room.off('participant_attributes_updated', onUpdated); };
}, [room]);
return agentState;
}
2) Use built-in voice/agent UI hooks/components
• LiveKit provides frontend components/hooks (React, SwiftUI, Android, Flutter) that already consume lk.agent.state and audio tracks to show visual state (e.g., bars, waveforms).
• Use the appropriate component/hook for your platform to get state updates and audio visualization with minimal wiring.
If you want, tell me which frontend platform/library you're using (React, plain JS, Flutter, iOS, Android) and I’ll give a tailored code example.
Sources: Web and mobile frontends | LiveKit docs | Agent session | LiveKit docs | Events and error handling | LiveKit docsenough-match-43171
12/17/2025, 9:14 PMtall-airline-16756
12/17/2025, 9:14 PMlk.agent.state and a small React hook using LiveKit's JS SDK/components.
1) Python agent — emit agent state changes (example)
# assume you're using livekit.agents and have an AgentSession `session`
from livekit.agents import AgentStateChangedEvent
@session.on("agent_state_changed")
def on_agent_state_changed(ev: AgentStateChangedEvent):
# ev.old_state / ev.new_state contain: "initializing", "listening", "thinking", "speaking"
print(f"Agent state changed: {ev.old_state} -> {ev.new_state}")
# When you set the agent state in your agent logic, the AgentSession will emit this event
# and LiveKit will keep participant attribute `lk.agent.state` in sync for clients.
2) Next.js (React) — listen for lk.agent.state updates
• Use LiveKit's JS SDK or @livekit/components-react. Listen to participant attribute updates and read lk.agent.state.
Example hook using LiveKit JS (works in Next.js pages/components):
import { useEffect, useState } from "react";
import { Room, RemoteParticipant } from "livekit-client"; // or use @livekit/components-react useRoom()
export function useAgentState(room: Room | null) {
const [agentState, setAgentState] = useState<string>("");
useEffect(() => {
if (!room) return;
const updateFromParticipant = (participant: RemoteParticipant) => {
const state = participant.attributes?.["lk.agent.state"];
if (state) setAgentState(state);
};
// handle all current remote participants (in case agent is already present)
room.participants.forEach((p) => updateFromParticipant(p));
// listen for attribute updates on any participant
const onAttributesUpdated = (participant: RemoteParticipant) => updateFromParticipant(participant);
room.on("participant_attributes_updated", onAttributesUpdated);
// also update when a participant joins
const onParticipantConnected = (participant: RemoteParticipant) => updateFromParticipant(participant);
room.on("participant_connected", onParticipantConnected);
return () => {
room.off("participant_attributes_updated", onAttributesUpdated);
room.off("participant_connected", onParticipantConnected);
};
}, [room]);
return agentState;
}
3) Using LiveKit React Components (simpler)
• If you're using @livekit/components-react, use useRoom() to get the room and apply the same hook, or use prebuilt voice assistant components that read lk.agent.state and audio tracks to show visualization.
If you want, I can:
• Provide a Next.js page example showing connecting to the room and rendering state.
• Show how to set the agent state programmatically in your Python agent logic (specific code for transitions). Which would you like?enough-match-43171
12/17/2025, 9:19 PM<RoomContext.Provider value={room}>
<VideoConference />
</RoomContext.Provider>tall-airline-16756
12/17/2025, 9:19 PMuseAgentState hook (from earlier), consume it inside VideoConference, and render the state UI. Place the UI where it fits in your layout.
1) Hook (if you haven't already)
// hooks/useAgentState.ts
import { useEffect, useState } from "react";
import type { Room, RemoteParticipant } from "livekit-client";
export function useAgentState(room: Room | null) {
const [agentState, setAgentState] = useState<string>("");
useEffect(() => {
if (!room) return;
const updateFromParticipant = (p: RemoteParticipant) => {
const s = p.attributes?.["lk.agent.state"];
if (s) setAgentState(s);
};
// initial scan
room.participants.forEach(updateFromParticipant);
const onAttributesUpdated = (p: RemoteParticipant) => updateFromParticipant(p);
const onParticipantConnected = (p: RemoteParticipant) => updateFromParticipant(p);
room.on("participant_attributes_updated", onAttributesUpdated);
room.on("participant_connected", onParticipantConnected);
return () => {
room.off("participant_attributes_updated", onAttributesUpdated);
room.off("participant_connected", onParticipantConnected);
};
}, [room]);
return agentState;
}
2) Consume in VideoConference
// components/VideoConference.tsx
import { useContext } from "react";
import { RoomContext } from "../path/to/RoomContext"; // your provider
import { useAgentState } from "../hooks/useAgentState";
export default function VideoConference() {
const room = useContext(RoomContext);
const agentState = useAgentState(room);
return (
<div className="video-conference">
{/* your existing video grid, controls, etc. */}
<div className="agent-state">
<strong>Agent:</strong>{" "}
<span className={`agent-state-pill agent-state-${agentState || "unknown"}`}>
{agentState || "offline"}
</span>
</div>
</div>
);
}
3) Simple CSS for clarity
.agent-state-pill {
padding: 6px 10px;
border-radius: 12px;
color: white;
font-weight: 600;
}
.agent-state-initializing { background: gray; }
.agent-state-listening { background: green; }
.agent-state-thinking { background: orange; }
.agent-state-speaking { background: blue; }
.agent-state-unknown { background: darkslategray; }
Notes:
• The hook reads lk.agent.state from participant attributes; update will reflect in real time.
• If multiple agents may appear, filter participants by role/identity before reading the attribute.
• Tell me if you want the example adapted to @livekit/components-react hooks or to include an audio-visualizer component.enough-match-43171
12/17/2025, 9:21 PMtall-airline-16756
12/17/2025, 9:21 PMuseAgent (or the useRoom + participant attributes) to display agent state alongside the imported VideoConference component.
1) Simple component using useAgent (preferred if available)
// components/AgentStatusOverlay.tsx
import React from "react";
import { useAgent } from "@livekit/components-react";
export default function AgentStatusOverlay() {
const agent = useAgent(); // returns agent participant or null
const state = agent?.attributes?.["lk.agent.state"] ?? "offline";
return (
<div style={{ position: "absolute", top: 12, right: 12 }}>
<strong>Agent:</strong>{" "}
<span className={`agent-state-pill agent-state-${state}`}>
{state}
</span>
</div>
);
}
2) If useAgent isn't available, use useRoom to find the agent participant
import React from "react";
import { useRoom } from "@livekit/components-react";
export default function AgentStatusOverlay() {
const room = useRoom();
// replace the filter below if you identify agent by role, name, or metadata
const agent = Array.from(room.participants.values()).find(p => p.attributes?.["lk.agent.state"]);
const state = agent?.attributes?.["lk.agent.state"] ?? "offline";
return (
<div style={{ position: "absolute", top: 12, right: 12 }}>
<strong>Agent:</strong>{" "}
<span className={`agent-state-pill agent-state-${state}`}>
{state}
</span>
</div>
);
}
3) Use it together with your provider and imported VideoConference
import { RoomContext, VideoConference } from "@livekit/components-react";
import AgentStatusOverlay from "./AgentStatusOverlay";
function Meeting({ room }) {
return (
<RoomContext.Provider value={room}>
<div style={{ position: "relative" }}>
<VideoConference />
<AgentStatusOverlay />
</div>
</RoomContext.Provider>
);
}
4) Optional CSS
.agent-state-pill { padding: 6px 10px; border-radius: 12px; color: white; font-weight: 600; }
.agent-state-initializing { background: gray; }
.agent-state-listening { background: green; }
.agent-state-thinking { background: orange; }
.agent-state-speaking { background: blue; }
.agent-state-offline { background: darkslategray; }
Notes:
• The code reads lk.agent.state from participant attributes; updates are real-time.
• If you have multiple agents, filter participants by a specific attribute (e.g., role or identity) instead of the simple presence check.
• Tell me if you want the overlay to show an icon, animated visualizer, or to be embedded inside VideoConference controls and I’ll provide that snippet.