Skip to content

Agent state management issues in useAgent hook #1227

@frankfarzan

Description

@frankfarzan

Select which package(s) are affected

@livekit/components-react

Describe the bug

Hi @1egoman, I'm playing around with Agent SDK changes (from #1207) and observed some issues with state tracking/reporting in useAgent (Overall I like the changes though).

Summary

The useAgent hook has two bugs that cause incorrect state reporting when
agents disconnect or are missing:

  1. Agent disconnections mid-conversation don't trigger failed state -
    Agents that disconnect after successful connection don't set failure reasons
  2. listening state can occur with no agent participant - State shows
    'listening' when agent.internal.agentParticipant is null

Bug 1: Missing failure state on agent disconnection

Problem

When an agent successfully connects initially but then disconnects
mid-conversation, the state doesn't transition to failed and no failure
reasons are provided.

Expected vs Actual

Expected:

  • agent.state should be 'failed'
  • agent.failureReasons should be set

Actual:

  • agent.state remains as 'listening'
  • No failure reasons are set

Root Cause

It seems the failure detection in useAgentTimeoutIdStore (lines 192-204) only
works during initial connection timeout. Once the agent successfully connects,
the timeout completes and there's no mechanism to detect subsequent
disconnections.

File: components-js/packages/react/src/hooks/useAgent.ts

  • Lines 192-204: Timeout logic
  • Lines 436-446: Timeout lifecycle

Reproduction

// Agent process is started.
await session.start();
// Agent connects and works fine
// ...
// Kill the agent process
// Expected: state = 'failed', failureReasons = ['Agent disconnected']
// Actual: state = 'connecting' (or previous), failureReasons = null

Bug 2: Inconsistent listening state

Problem

agent.state can be 'listening' while agent.internal.agentParticipant is
null, indicating the agent is listening when no agent actually exists.

Expected vs Actual

Expected:

  • State reflects actual agent presence
  • 'listening' state should only occur when an agent participant exists

Actual:

agent.state === "listening";
agent.internal.agentParticipant === null; // Inconsistent!

Root Cause

It seems the state calculation (lines 394-425) sets state to 'listening' based
solely on localMicTrack:

if (localMicTrack) {
  state = "listening";
  bufferingSpeachLocally = true;
}

This happens when the local microphone is published (buffering audio) but no
agent has joined the room yet.

File: components-js/packages/react/src/hooks/useAgent.ts

  • Lines 408-411: State calculation
  • Line 453: Agent participant in internal state

Reproduction

See above.

Logs

System Info

System:
    OS: macOS 15.5
    CPU: (10) arm64 Apple M4
    Memory: 65.66 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 24.2.0 - /opt/homebrew/bin/node
    npm: 11.4.2 - /opt/homebrew/bin/npm
    pnpm: 10.18.3 - /opt/homebrew/bin/pnpm
    Watchman: 2025.05.26.00 - /opt/homebrew/bin/watchman
  Browsers:
    Chrome: 141.0.7390.123
    Firefox: 143.0.1
    Safari: 18.5
  npmPackages:
    @livekit/components-core: file:vendor/livekit-components-core-0.12.10.tgz => 0.12.10 
    @livekit/components-react: file:vendor/livekit-components-react-2.9.15.tgz => 2.9.15 
    @livekit/components-styles: ^1.1.6 => 1.1.6 


Using packed packages from f118da6e678c4a91be91c4dfc9b3b61eb7f64e2a (#1207)

Severity

blocking an upgrade

Additional Information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions