Files
agents_and_robots/dev/issues/0041-livekit-videocall.md
T
egutierrez 52d5632d89 docs: crear issues 0036-0041 — nuevas features del sistema
Issues planificados:
- 0036: Claude Code streaming de progreso en Matrix
- 0037: Agente que crea otros agentes/bots via Matrix
- 0038: Webapps y dashboards embebidos en Element via widgets
- 0039: Recordatorios dinámicos y crons que invocan agentes
- 0040: Soporte para mensajes de voz (audio → STT)
- 0041: Videollamadas con agentes via LiveKit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 21:19:09 +00:00

16 KiB

0041 — Videollamadas con agentes via LiveKit (Element Call)

Estado: pendiente

Objetivo

Permitir que los agentes se unan a llamadas de voz y video iniciadas desde Element, participando como interlocutores conversacionales en tiempo real. El agente captura el audio de la llamada, lo transcribe en tiempo real (streaming STT), genera respuestas via LLM, y habla de vuelta usando TTS — creando una experiencia de IA interactiva por voz dentro de las llamadas de Element.

Contexto

  • Element usa LiveKit como backend para llamadas de voz/video via MatrixRTC (Element Call)
  • El proyecto ya usa github.com/sashabaranov/go-openai que incluye soporte para Whisper (STT) y TTS APIs
  • Existe un issue planificado (0040) para soporte de mensajes de voz con STT — este issue reutiliza la interfaz Transcriber definida alli
  • LiveKit tiene un SDK oficial para Go: github.com/livekit/server-sdk-go para interaccion server-side con rooms y tracks de audio/video
  • No existe actualmente ninguna forma de que los agentes participen en llamadas — solo responden a mensajes de texto
  • El flujo MatrixRTC funciona asi: Element crea un estado MatrixRTC en la room Matrix (events tipo m.call.member), lo que genera una sesion en el servidor LiveKit donde los participantes se conectan via WebRTC
  • Esta feature es compleja y multi-faceted — se recomienda implementar en sub-issues independientes

Arquitectura

Flujo principal

Usuario inicia llamada en Element (1:1 o grupo)
  → Element crea estado MatrixRTC en la room
  → Event m.call.member llega al listener del agente
  → Agent detecta llamada activa → obtiene credenciales LiveKit
  → shell/livekit/ conecta al LiveKit room como participante
  → Audio pipeline:
      Audio track entrante → Buffer/VAD → STT (Transcriber)
        → Texto transcrito → LLM del agente → Respuesta texto
          → TTS (Synthesizer) → Audio track saliente → LiveKit room
  → Usuario escucha la respuesta del agente
  → Ciclo continua hasta que se cuelga la llamada

Pure core / impure shell

pkg/tts/types.go          → PURO: interfaz Synthesizer, tipos de audio
shell/livekit/client.go    → IMPURO: conexion LiveKit, join/leave rooms
shell/livekit/audio.go     → IMPURO: captura y publicacion de audio tracks
shell/livekit/pipeline.go  → IMPURO: orquestacion STT → LLM → TTS
shell/tts/openai.go        → IMPURO: cliente OpenAI TTS API
shell/matrix/listener.go   → IMPURO (MOD): deteccion de eventos de llamada
internal/config/schema.go  → PURO (MOD): tipos LiveKitCfg, TTSCfg
devagents/runtime.go          → COMPOSICION (MOD): inicializar LiveKit, wiring

La logica pura se limita a tipos e interfaces. Todo el I/O real (LiveKit, STT, TTS, LLM) vive en shell/. Las reglas del agente no cambian — la decision de unirse a una llamada es un comportamiento del runtime, no de las reglas de decision.

Archivos afectados

shell/livekit/              NEW — paquete LiveKit
shell/livekit/client.go     NEW — cliente LiveKit: connect, join room, leave, lifecycle
shell/livekit/audio.go      NEW — captura audio track entrante, publish audio track saliente
shell/livekit/pipeline.go   NEW — orquestacion del pipeline STT → LLM → TTS
shell/tts/                  NEW — paquete TTS
shell/tts/openai.go         NEW — implementacion OpenAI TTS API (tts-1, voces)
pkg/tts/                    NEW — tipos puros de TTS
pkg/tts/types.go            NEW — interfaz Synthesizer, AudioFormat, VoiceConfig
shell/matrix/listener.go    MOD — detectar eventos m.call.member / MatrixRTC
internal/config/schema.go   MOD — anadir LiveKitCfg, TTSCfg al schema de config
devagents/runtime.go           MOD — inicializar cliente LiveKit, conectar call handling

Tareas

Nota: este es un feature multi-issue. Cada fase deberia convertirse en un sub-issue independiente (ver seccion "Desglose multi-issue" mas abajo).

Fase 1 — Cliente LiveKit + deteccion de llamadas

  • 1.1 Anadir dependencia github.com/livekit/server-sdk-go al modulo Go
  • 1.2 Crear shell/livekit/client.go: conexion al servidor LiveKit, join room como participante, leave room, manejo de reconexion
  • 1.3 Anadir LiveKitCfg a internal/config/schema.go: ServerURL, APIKeyEnv, APISecretEnv, Enabled, AutoJoinCalls
  • 1.4 Modificar shell/matrix/listener.go para detectar eventos MatrixRTC (m.call.member state events) y notificar al runtime
  • 1.5 Implementar auto-join: cuando se detecta una llamada activa en una room donde el agente esta presente, obtener token LiveKit y unirse como participante de audio
  • 1.6 Tests: conexion y join de room con servidor LiveKit mock o de prueba

Fase 2 — Captura de audio + STT

  • 2.1 Implementar captura de audio track desde el LiveKit room participant en shell/livekit/audio.go
  • 2.2 Buffer de chunks de audio para procesamiento STT (formato Opus → PCM si es necesario)
  • 2.3 Integrar con STT del issue 0040 — reutilizar interfaz Transcriber para transcribir audio capturado
  • 2.4 Implementar Voice Activity Detection (VAD) para detectar cuando el usuario deja de hablar (silencio > umbral configurable)
  • 2.5 Tests: pipeline de captura de audio con datos de prueba

Fase 3 — TTS

  • 3.1 Definir pkg/tts/types.go: interfaz Synthesizer con Synthesize(ctx context.Context, text string) ([]byte, error), tipos AudioFormat, VoiceConfig
  • 3.2 Implementar shell/tts/openai.go: cliente OpenAI TTS API (modelo tts-1, voces: alloy, echo, fable, onyx, nova, shimmer)
  • 3.3 Anadir TTSCfg a internal/config/schema.go: Enabled, Provider, Model, Voice, Speed, APIKeyEnv
  • 3.4 Convertir output de TTS al formato que LiveKit espera (PCM/Opus) si es necesario
  • 3.5 Publicar audio track con la voz sintetizada al LiveKit room
  • 3.6 Tests: TTS con mock del API de OpenAI

Fase 4 — Pipeline completo

  • 4.1 Orquestar pipeline en shell/livekit/pipeline.go: audio entrante → STT → LLM → TTS → audio saliente
  • 4.2 Manejar flujo conversacional: usuario habla → pausa (VAD) → agente responde → vuelve a escuchar
  • 4.3 Manejo de interrupciones: si el usuario habla mientras el agente esta hablando, detener TTS y escuchar
  • 4.4 Optimizacion de latencia: iniciar TTS conforme los tokens del LLM van llegando (streaming TTS)
  • 4.5 Conectar pipeline al runtime del agente en devagents/runtime.go
  • 4.6 Tests: pipeline end-to-end con mocks de STT, LLM y TTS

Fase 5 — Polish y opcionales

  • 5.1 Gestion del ciclo de vida de llamadas: join, active, hangup, error recovery, timeout por inactividad
  • 5.2 Opcional: publicar video track con avatar/estado del agente (estatico o animado)
  • 5.3 Indicadores en la room Matrix durante la llamada (typing indicators, mensajes de estado)
  • 5.4 Documentacion de config y ejemplos en config de agentes de referencia
  • 5.5 Verificacion de permisos: solo aceptar llamadas de usuarios autorizados (ACL check via security/)

Desglose multi-issue

Este issue es demasiado grande para completarse en una sola rama corta. Se recomienda desglosar en los siguientes sub-issues, cada uno autocontenido, compilable y testeable:

Sub-issue Rama Alcance Fases cubiertas Estado
0041a — LiveKit client + deteccion de llamadas issue/0041a-livekit-client Paquete shell/livekit/, config LiveKitCfg, deteccion de eventos MatrixRTC en listener, auto-join basico Fase 1 pendiente
0041b — TTS package + publicacion de audio issue/0041b-tts-audio-publish Paquete pkg/tts/, shell/tts/, config TTSCfg, publicar audio track al LiveKit room Fase 3 pendiente
0041c — Pipeline completo STT → LLM → TTS issue/0041c-call-pipeline Orquestacion en shell/livekit/pipeline.go, captura audio, VAD, integracion STT (issue 0040), flujo conversacional, wiring en runtime Fases 2 y 4 pendiente
0041d — Polish, video track y lifecycle issue/0041d-call-polish Lifecycle management, interrupciones, video track opcional, indicadores, ACL, docs Fase 5 pendiente

Nota sobre feature flags

Se recomienda usar un feature flag livekit-calls en dev/feature_flags.json (desactivado) para las sub-issues 0041a-0041c. La sub-issue 0041d activa el flag y cierra el feature. Esto permite mergear codigo completo y testeado a master sin activar el comportamiento hasta que todo el pipeline este listo.

{
  "flags": {
    "livekit-calls": {
      "enabled": false,
      "issue": "0041",
      "description": "Agentes pueden unirse a llamadas de voz/video via LiveKit + MatrixRTC",
      "added": "2026-04-09"
    }
  }
}

Progreso por tarea

Fase 1: Cliente LiveKit + deteccion — sub-issue 0041a

  • 1.1 Dependencia livekit/server-sdk-go
  • 1.2 shell/livekit/client.go
  • 1.3 LiveKitCfg en config schema
  • 1.4 Deteccion MatrixRTC en listener
  • 1.5 Auto-join a llamadas
  • 1.6 Tests

Fase 2: Captura audio + STT — sub-issue 0041c

  • 2.1 Captura audio track
  • 2.2 Buffer audio chunks
  • 2.3 Integracion STT (issue 0040)
  • 2.4 Voice Activity Detection
  • 2.5 Tests

Fase 3: TTS — sub-issue 0041b

  • 3.1 pkg/tts/types.go
  • 3.2 shell/tts/openai.go
  • 3.3 TTSCfg en config schema
  • 3.4 Conversion formato audio
  • 3.5 Publicar audio track
  • 3.6 Tests

Fase 4: Pipeline completo — sub-issue 0041c

  • 4.1 Orquestacion pipeline
  • 4.2 Flujo conversacional
  • 4.3 Manejo de interrupciones
  • 4.4 Optimizacion latencia (streaming TTS)
  • 4.5 Wiring en runtime
  • 4.6 Tests E2E del pipeline

Fase 5: Polish — sub-issue 0041d

  • 5.1 Lifecycle management
  • 5.2 Video track opcional
  • 5.3 Indicadores en Matrix
  • 5.4 Documentacion
  • 5.5 ACL check

Ejemplo de uso

Llamada 1:1 con agente

1. Usuario abre DM con el agente en Element
2. Usuario hace clic en el boton "Call" (icono de telefono)
3. Element crea sesion MatrixRTC → LiveKit room
4. El agente detecta el evento m.call.member en la room
5. El agente se une a la llamada como participante de audio
6. Conversacion:

   Usuario (hablando): "Hola, como estan los servidores?"
   [VAD detecta fin de habla → STT transcribe → LLM procesa → TTS genera audio]
   Agente (hablando): "Hola! Todos los servidores estan operativos.
                        El uso de CPU promedio es del 23% y hay 4.2 GB
                        de memoria disponible."

   Usuario: "Y el servicio de base de datos?"
   [Pipeline se repite]
   Agente: "PostgreSQL esta corriendo normalmente. La ultima replica
            se sincronizo hace 3 minutos sin errores."

7. Usuario cuelga la llamada
8. El agente detecta el hangup y se desconecta del LiveKit room

Config del agente

# agents/<agent-id>/config.yaml

livekit:
  enabled: true
  server_url: "wss://livekit.myserver.com"
  api_key_env: LIVEKIT_API_KEY
  api_secret_env: LIVEKIT_API_SECRET
  auto_join_calls: true           # unirse automaticamente cuando se detecta llamada

tts:
  enabled: true
  provider: openai                # openai | elevenlabs | local
  model: tts-1                    # tts-1 (rapido) | tts-1-hd (calidad)
  voice: nova                     # alloy, echo, fable, onyx, nova, shimmer
  speed: 1.0                      # 0.25 - 4.0
  api_key_env: OPENAI_API_KEY     # reutiliza la misma key del LLM

Env vars nuevas

# .env
LIVEKIT_API_KEY="APIxxxxxxxx"
LIVEKIT_API_SECRET="secretxxxxxxxx"
# OPENAI_API_KEY ya existe — se reutiliza para TTS

Decisiones de diseno

  1. LiveKit server-sdk-go: SDK oficial de LiveKit para Go, permite integracion nativa sin bridges ni proxies. El agente se conecta como participante server-side al LiveKit room.

  2. OpenAI TTS como provider primario: consistente con la dependencia existente de github.com/sashabaranov/go-openai. El modelo tts-1 ofrece buen balance entre calidad y latencia (~1s). Se puede extender a ElevenLabs o TTS local en el futuro.

  3. MVP solo audio, video opcional: la interaccion por voz es el valor principal. El video track (avatar, estado) es un nice-to-have que se puede agregar despues sin cambiar la arquitectura.

  4. Reutilizar interfaz Transcriber del issue 0040: evita duplicar logica de STT. El issue 0040 define la interfaz y la implementacion; este issue la consume para el pipeline de llamadas.

  5. Voice Activity Detection (VAD): critico para saber cuando el usuario termina de hablar. Sin VAD, el agente no sabe cuando empezar a procesar. Se puede empezar con un umbral simple de silencio (ej: 1.5s sin audio) y mejorar despues con VAD basado en WebRTC o silero-vad.

  6. Considerar OpenAI Realtime API como optimizacion futura: la Realtime API de OpenAI permite audio-in → audio-out directamente, eliminando la necesidad de STT y TTS separados. Reduciria la latencia significativamente (~500ms vs ~4s). Sin embargo, introduce acoplamiento fuerte con OpenAI y no permite usar otros LLMs. Se deja como optimizacion futura.

  7. Feature flag para merge incremental: dado que son 4+ sub-issues, cada uno mergea codigo funcional y testeado a master protegido por el flag livekit-calls. Esto sigue el patron TBD del proyecto y evita ramas largas.

Prerequisitos

  • Issue 0040 (STT) completado: este issue depende de la interfaz Transcriber y la implementacion de STT para transcribir el audio de la llamada
  • Servidor LiveKit desplegado: se necesita un servidor LiveKit accesible (self-hosted via livekit-server o LiveKit Cloud), configurado para funcionar con el homeserver Matrix
  • Integracion MatrixRTC en el homeserver: el homeserver Synapse necesita estar configurado para MatrixRTC/LiveKit (configuracion de SFU en .well-known o en el config de Synapse)
  • Element Web/Desktop con soporte de Element Call: las versiones recientes de Element incluyen Element Call integrado

Seguridad

  • Credenciales LiveKit via env vars: LIVEKIT_API_KEY y LIVEKIT_API_SECRET nunca se hardcodean, se cargan desde .env via api_key_env/api_secret_env
  • Solo aceptar llamadas de usuarios autorizados: verificar permisos del usuario que inicia la llamada contra las ACLs del agente (security/permissions.yaml) antes de unirse
  • Audio procesado en memoria, no persistido: el audio de la llamada se procesa en streaming y no se guarda en disco. Los buffers se liberan despues de la transcripcion
  • Llamadas TTS/STT via HTTPS: todas las llamadas a APIs externas (OpenAI Whisper, OpenAI TTS) usan HTTPS
  • Timeout por inactividad: si no se detecta audio por un periodo configurable (ej: 5 minutos), el agente se desconecta automaticamente para liberar recursos
  • Rate limiting: aplicar rate limiting a las llamadas por usuario/room para prevenir abuso de recursos (STT/TTS tienen costo por uso)

Riesgos

Riesgo Probabilidad Impacto Mitigacion
MatrixRTC spec en evolucion — la integracion LiveKit/Matrix puede cambiar entre versiones Alta Alto Fijar versiones de Element y livekit-server; encapsular la deteccion de eventos en una capa de abstraccion que se pueda actualizar sin reescribir el pipeline
Latencia total del pipeline: STT (~1s) + LLM (~2s) + TTS (~1s) = ~4s minimo de respuesta Alta Medio Aceptable para MVP; optimizar con streaming TTS (iniciar antes de completar la respuesta LLM); considerar OpenAI Realtime API como mejora futura
Codec Opus: conversion entre formato LiveKit (Opus/WebRTC) y APIs de STT/TTS (PCM/MP3) Media Medio Usar librerias Go para decode Opus → PCM (gopkg.in/hraban/opus.v2 o pion/opus); puede requerir CGO dependiendo de la libreria
Hosting y costo del servidor LiveKit Media Medio LiveKit se puede self-host (binario unico); el costo de APIs de STT/TTS es proporcional al uso. Documentar estimaciones de costo
Compatibilidad Element Web vs Mobile vs Desktop Media Bajo Element Call funciona diferente en cada plataforma. Priorizar Element Web/Desktop que usan MatrixRTC directamente; mobile puede tener limitaciones
CGO dependency para codec Opus Media Medio El proyecto usa CGO_ENABLED=0. Si las librerias Opus requieren CGO, evaluar alternativas pure-Go o pre-compilar bindings. pion/opus ofrece decode pure-Go
LiveKit server-sdk-go compatibility con Go 1.23.5 Baja Bajo Verificar compatibilidad antes de empezar; el SDK de LiveKit suele soportar versiones recientes de Go