Files
agents_and_robots/dev/issues/0015-multi-platform-telegram.md
T
egutierrez 2756557498 chore: renombrar issues a formato 4 dígitos (NNNN)
Se estandariza la numeración de todos los issues de 3 dígitos a 4 dígitos
(e.g. 005 → 0005, 010 → 0010) para mantener consistencia con la convención
definida en create_issue.md. Se actualiza el README con los nuevos nombres
y links. No hay cambios de contenido en los issues, solo renombrado.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:39:33 +00:00

10 KiB

015 — Soporte multi-plataforma: Telegram como segunda plataforma

Objetivo

Desacoplar el runtime de agentes de Matrix e introducir abstracciones de plataforma que permitan conectar un mismo agente a multiples servicios de mensajeria. Implementar Telegram como primera plataforma adicional para validar el diseno.

Contexto

  • Actualmente agents/runtime.go depende directamente de *matrix.Client y *matrix.Listener
  • shell/effects/runner.go ya define MatrixSender como interfaz, pero con nombre acoplado
  • decision.MessageContext es generico — no tiene nada de Matrix
  • Las reglas, LLM, tools y memoria son independientes de la plataforma
  • El acoplamiento esta en: runtime.go, effects/runner.go, listener, y algunos tools (matrix_send)

Prerequisitos

  • Ninguno estricto. Se puede hacer de forma incremental sin romper Matrix.

Tareas

Fase 1: Abstracciones de plataforma en pkg/platform/

  • 1.1 Crear pkg/platform/types.go con las interfaces puras:

    // Messenger envía mensajes a una plataforma de chat.
    type Messenger interface {
        SendText(ctx context.Context, roomID, text string) error
        SendMarkdown(ctx context.Context, roomID, markdown string) error
        SendReplyMarkdown(ctx context.Context, roomID, inReplyTo, markdown string) error
        SendTyping(ctx context.Context, roomID string, typing bool) error
    }
    
    // EventSource escucha eventos de una plataforma y los entrega como MessageContext.
    type EventSource interface {
        Run(ctx context.Context) error
    }
    
    // Platform agrupa Messenger + EventSource para una plataforma concreta.
    type Platform interface {
        Messenger
        EventSource
        Name() string // "matrix", "telegram", "slack", etc.
    }
    

    Nota: estas interfaces van en pkg/ porque son tipos puros (no ejecutan I/O, solo los definen).

  • 1.2 Definir PlatformID como prefijo para room IDs multi-plataforma:

    • Formato: matrix:!abc123:server.com, telegram:chat_456
    • Crear helpers PrefixRoomID(platform, rawID) string y ParseRoomID(prefixed) (platform, rawID)
    • Esto permite que la memoria y windows no mezclen contextos entre plataformas

Fase 2: Adaptar shell/matrix/ a las interfaces

  • 2.1 Verificar que shell/matrix/Client ya satisface platform.Messenger (deberia, con los metodos actuales). Anadir metodo Name() string que retorne "matrix".

  • 2.2 Refactorizar shell/matrix/Listener para que implemente platform.EventSource:

    • El EventHandler callback ya recibe decision.MessageContext — solo necesita ajustar la firma de Run(ctx) si difiere
    • Internamente sigue usando mautrix syncer, pero externamente expone la interfaz generica
  • 2.3 Crear wrapper shell/matrix/Platform que componga Client + Listener e implemente platform.Platform

Fase 3: Desacoplar runtime.go

  • 3.1 Cambiar el campo matrix *matrix.Client en Agent struct por messenger platform.Messenger

  • 3.2 Cambiar listener *matrix.Listener por sources []platform.EventSource

  • 3.3 Actualizar Run() para arrancar multiples EventSources en goroutines:

    for _, src := range a.sources {
        go src.Run(ctx)
    }
    
  • 3.4 Actualizar handleEvent para que no reciba *event.Event — actualmente solo usa evt.RoomID que ya esta en MessageContext.RoomID. Eliminar la dependencia de mautrix/event.

  • 3.5 Actualizar todas las llamadas directas a a.matrix.SendXxx() y a.matrix.SendTyping() para usar a.messenger.SendXxx(). Puntos clave:

    • handleEvent — typing indicator, command replies, unknown command
    • executeActions — ya pasa por el runner, OK
    • handleTaskEvent — typing indicator, send reply
    • runLLM — tool call notices
  • 3.6 Actualizar shell/effects/runner.go:

    • Renombrar interfaz MatrixSender a Messenger (o importar platform.Messenger)
    • El Runner ya recibe la interfaz, solo cambia el nombre
  • 3.7 Actualizar New() constructor para recibir []platform.Platform en vez de construir matrix.Client internamente. Mover la creacion de clientes de plataforma al launcher.

Fase 4: Implementar shell/telegram/

  • 4.1 Elegir libreria de Telegram Bot API para Go. Opciones:

    • (A) github.com/go-telegram-bot-api/telegram-bot-api/v5 — la mas popular, estable
    • (B) github.com/gotd/td — cliente completo (MTProto), mas complejo
    • (C) HTTP directo contra Bot API — minimo, sin dependencias extra
    • Recomendacion: opcion (A) por madurez y simplicidad
  • 4.2 Crear shell/telegram/client.go:

    • Struct Client con el bot API client interno
    • Constructor New(cfg config.TelegramCfg) (*Client, error)
    • Implementar platform.Messenger:
      • SendTexttgbotapi.NewMessage(chatID, text)
      • SendMarkdowntgbotapi.NewMessage con ParseMode: "MarkdownV2"
      • SendReplyMarkdownReplyToMessageID en el message config
      • SendTypingtgbotapi.NewChatAction(chatID, "typing")
  • 4.3 Crear shell/telegram/listener.go:

    • Implementar platform.EventSource
    • Modo long-polling con GetUpdatesChan() (webhook es mas complejo y requiere dominio publico)
    • Convertir cada tgbotapi.Update a decision.MessageContext:
      • SenderID = user ID de Telegram (string)
      • SenderName = username o first_name
      • RoomID = telegram:<chat_id> (con prefijo de plataforma)
      • Content = texto del mensaje
      • IsDirectMsg = true si chat.Type == "private"
      • IsMention = true si el mensaje contiene @botname
      • Command = parsear si empieza con ! (o / que es la convencion Telegram)
    • Llamar al mismo handleEvent(ctx, msgCtx) del Agent
  • 4.4 Crear shell/telegram/platform.go que componga Client + Listener e implemente platform.Platform

Fase 5: Configuracion

  • 5.1 Anadir tipos de config en internal/config/schema.go:

    telegram:
      enabled: false
      bot_token_env: "TELEGRAM_TOKEN_BOT"
      allowed_chats: []       # lista de chat IDs permitidos (vacio = todos)
      command_prefix: "/"     # convencion Telegram, ademas de "!"
    

    Struct: TelegramCfg con campos Enabled, BotTokenEnv, AllowedChats, CommandPrefix

  • 5.2 Anadir TelegramCfg al config principal del agente (junto a MatrixCfg)

  • 5.3 Actualizar internal/config/loader.go para parsear la nueva seccion

  • 5.4 Actualizar cmd/launcher/main.go para instanciar plataformas segun config:

    var platforms []platform.Platform
    if cfg.Matrix.Enabled { platforms = append(platforms, matrixPlatform) }
    if cfg.Telegram.Enabled { platforms = append(platforms, telegramPlatform) }
    
  • 5.5 Anadir TELEGRAM_TOKEN_<BOT> a .env.example

Fase 6: Tool matrix_send → platform_send

  • 6.1 Evaluar si matrix_send tool debe ser generico o especifico:

    • Opcion A: renombrar a send_message con parametro platform — mas flexible
    • Opcion B: mantener matrix_send y anadir telegram_send — mas simple
    • Recomendacion: opcion A a largo plazo, pero para esta task basta con que el LLM responda por la misma plataforma que recibio el mensaje (ya lo hace via handleEvent → runner)
    • matrix_send como tool explícita solo se usa para enviar a rooms arbitrarios; si no se necesita eso en Telegram, no hace falta telegram_send ahora
  • 6.2 Si se opta por generalizar: crear tools/send.go con NewPlatformSend(messenger platform.Messenger) que el LLM pueda usar para enviar a cualquier plataforma

Fase 7: Tests

  • 7.1 Unit tests para pkg/platform/types.go — verificar que las interfaces compilan y los helpers de PlatformID funcionan

  • 7.2 Unit tests para shell/telegram/client.go — mock del bot API, verificar conversion de mensajes

  • 7.3 Unit tests para shell/telegram/listener.go — mock de updates, verificar conversion a MessageContext

  • 7.4 Integration test: verificar que un Agent con dos plataformas (matrix mock + telegram mock) recibe y responde correctamente por ambas

  • 7.5 Verificar que todos los agentes existentes siguen funcionando solo con Matrix (backward compat)

Fase 8: Documentacion

  • 8.1 Actualizar CLAUDE.md — anadir shell/telegram/ a la estructura de directorios, actualizar diagrama de flujo

  • 8.2 Actualizar docs/creating-agents.md con la seccion de configuracion multi-plataforma

  • 8.3 Actualizar .claude/rules/create_agent.md para mencionar la seccion telegram: en config

  • 8.4 Anadir a README.md la seccion de soporte Telegram


Orden de ejecucion recomendado

  1. Fase 1 (interfaces) — base para todo lo demas
  2. Fase 2 (adaptar matrix) — asegurar que Matrix sigue funcionando con las nuevas interfaces
  3. Fase 3 (desacoplar runtime) — el refactor central; debe compilar y pasar tests con solo Matrix
  4. Fase 5 (config) — preparar el config antes de implementar Telegram
  5. Fase 4 (implementar telegram) — el codigo nuevo
  6. Fase 6 (tools) — ajustar si es necesario
  7. Fase 7 (tests) — validar todo
  8. Fase 8 (docs) — ultima, cuando todo este estable

Decisiones de diseno pendientes

  • Memoria compartida vs separada: Si un usuario habla por Matrix y por Telegram, son windows separadas (por el prefijo de plataforma en roomID). Podria unificarse en el futuro con un "user identity" cross-platform, pero no es necesario ahora.
  • Comandos / vs !: Telegram usa / como convencion para comandos de bot. Soportar ambos prefijos (/ y !) en el parser de comandos, configurable por plataforma.
  • Webhooks vs long-polling: Empezar con long-polling por simplicidad. Webhook requiere HTTPS publico y es una optimizacion posterior.
  • Presence: Matrix tiene presence (online/offline). Telegram no tiene equivalente nativo para bots. Abstraer como opcional en la interfaz.
  • Reactions: Matrix tiene reactions (m.reaction). Telegram tiene limitaciones. No incluir en Messenger por ahora; dejarlo como extension especifica de plataforma.

Dependencias nuevas

github.com/go-telegram-bot-api/telegram-bot-api/v5  # Telegram Bot API client

Riesgos

  • El refactor de runtime.go (Fase 3) es el mas delicado — cambia el corazon del sistema. Hacer commits atomicos despues de cada sub-tarea.
  • Asegurar backward compatibility: un agente sin telegram: en su config debe funcionar exactamente como antes.