Scaling real-time AI agents with session-aware load balancing LiveKit, a real-time AI infrastructure provider, reports that traditional load balancing metrics like QPS and CPU utilization fail for real-time AI agents, which require session-aware balancing that tracks active, stateful streaming sessions. The company recommends application-level reporting of active session counts, as demonstrated in a Kotlin code snippet, to accurately manage long-lived bidirectional streams over gRPC or WebSockets. Building real-time AI agents isn’t quite like working with the standard web APIs we’re used to. With a typical API, there’s a predictable lifecycle: the client sends a request, the server processes it, returns a response, and then moves on. This ephemeral model is great because it’s easy to track - you can measure performance through familiar metrics like latency, QPS, and CPU usage. Real-time AI systems change that model. Instead of handling isolated requests, the backend has to manage a continuous, live bidirectional stream. You’re dealing with a constant stream of audio chunks, transcripts, model outputs, and synthesized speech flowing back and forth simultaneously. Things get even more complex when a user interrupts. The server has to immediately halt its current speech generation, pivot to update the context, maybe trigger a new tool, and start drafting a different response; this must be done without dropping the connection. This forces us to rethink our infrastructure. In the world of real-time AI, we aren’t just optimizing for the single request anymore; we’re managing the complexities of a living conversation. Traditional load balancing strategies often prioritize request throughput and current CPU utilization. These approaches assume that each incoming request consumes a predictable amount of resources and that finishing a task clears capacity. However, for long-lived, stateful AI streams, these assumptions fall apart. Consider two backend tasks: Task A handles 100 short requests, each finishing in 50 milliseconds. Task B accepts just 5 requests, but each turns into a 20-minute session. If you only judge these by request arrival rates, Task B appears less busy by request rate, yet it may actually be shouldering a significantly heavier, more committed workload. QPS tracks arrival volume, but fails to capture the number of live conversations a server is already managing. Similarly, CPU utilization can be deceptive. A voice runtime, for example, might host 20 silent sessions; because there’s no active speech processing or model inference happening, the server looks underutilized. But as soon as those 20 users start speaking simultaneously, CPU usage can spike suddenly. While CPU metrics reflect the immediate processing load, active session counts reveal the work the backend has already promised to handle. For real-time AI, you need to balance both signals. Real-time agents typically rely on bidirectional streaming protocols like gRPC or WebSockets. While specific implementations differ, they all face the same infrastructure hurdle: maintaining an open, long-running connection where data flows constantly in both directions. While a network observer sees a simple connection, the application treats it as a complex, stateful session. Inside a single real-time agent session, you might have audio buffers, partial transcripts, active tool calls, model context, and user-specific metrics all living within the runtime memory. Standard load balancers struggle with this. They see the stream, but they can't distinguish between a genuinely active user conversation, an idle listener, or background noise like retries and health checks. Because the infrastructure lacks visibility into these internal states, you can't rely on generic connection metrics alone. Instead, you need application-level reporting. The backend service itself is the only component with enough context to accurately track when a session is truly active versus when it has failed, finished, or been canceled. A simple pattern is to track active sessions at the point where the streaming session lifecycle begins and ends. suspend fun handleAudioSession audioStream: Flow