Production features that matter

Your agent works. Now comes the gap between working and good. Production polish is the small stuff nobody notices until it is missing. Personalised greetings, a visible thinking indicator, and graceful handling when the microphone fails or the agent stalls.

restaurant_agent.py
python
import json
import logging
from datetime import datetime
from livekit.agents import get_job_context

class RestaurantAgent(Agent):
    async def emit_thought(self, category: str, content: str) -> None:
        """Publishes an agent thinking event to the frontend via LiveKit data channel."""
        try:
            ctx = get_job_context()
            room = ctx.room if ctx else None
            if not room:
                return
            thought = {
                "category": category,
                "content": content,
                "timestamp": datetime.now().isoformat(),
            }
            await room.local_participant.publish_data(
                json.dumps({"thinking": thought}).encode("utf-8")
            )
        except Exception as e:
            logging.error(f"Error publishing thought: {e}")

LiveKit data channels let the agent send structured messages to every participant alongside the audio stream. The frontend subscribes and renders a thinking indicator while the agent reasons.

Personalisation is even simpler. When the frontend mints a token, it passes the customer name. The agent reads that name from the room participant attributes and uses it naturally in greetings. Hearing your own name is a tiny thing that changes the whole feel of the conversation.

app/ThoughtIndicator.tsx
tsx
'use client';
import { useDataChannel } from '@livekit/components-react';
import { useState } from 'react';

export function ThoughtIndicator() {
  const [thought, setThought] = useState<string | null>(null);

  useDataChannel((msg) => {
    const text = new TextDecoder().decode(msg.payload);
    const parsed = JSON.parse(text);
    if (parsed.thinking) {
      setThought(parsed.thinking.content);
      setTimeout(() => setThought(null), 3000);
    }
  });

  if (!thought) return null;
  return <div className="thought-pill">{thought}</div>;
}

useDataChannel subscribes to the same stream the agent publishes on. Parse the JSON payload, show a little pill that fades after three seconds, and suddenly the agent feels thoughtful instead of slow.

Quiz: Quiz

Loading practice…

AI prompt: Try it: design your own tool

Loading practice…