Vinculaciones persistentes de ACP para canales de Discord y temas de Telegram

Estado: Borrador

Resumen

Introducir vinculaciones ACP persistentes que mapeen:

  • Canales de Discord (e hilos existentes, donde sea necesario), y
  • Temas de foro de Telegram en grupos/supergrupos (chatId:topic:topicId)

a sesiones ACP de larga duración, con el estado de vinculación almacenado en entradas de nivel superior bindings[] usando tipos de vinculación explícitos.

Esto hace que el uso de ACP en canales de mensajería de alto tráfico sea predecible y duradero, para que los usuarios puedan crear canales/temas dedicados como codex, claude-1 o claude-myrepo.

Por qué

El comportamiento actual de ACP vinculado a hilos está optimizado para flujos de trabajo efímeros de hilos de Discord. Telegram no tiene el mismo modelo de hilos; tiene temas de foro en grupos/supergrupos. Los usuarios quieren “espacios de trabajo” ACP estables y siempre activos en superficies de chat, no solo sesiones temporales de hilos.

Objetivos

  • Soportar vinculación ACP duradera para:
    • Canales/hilos de Discord
    • Temas de foro de Telegram (grupos/supergrupos)
  • Hacer que la fuente de verdad de vinculación sea dirigida por configuración.
  • Mantener el comportamiento de /acp, /new, /reset, /focus y entrega consistente entre Discord y Telegram.
  • Preservar los flujos de vinculación temporal existentes para uso ad-hoc.

No objetivos

  • Rediseño completo de los internos de runtime/sesión ACP.
  • Eliminar los flujos de vinculación efímera existentes.
  • Expandir a todos los canales en la primera iteración.
  • Implementar temas de mensajes directos de Telegram (direct_messages_topic_id) en esta fase.
  • Implementar variantes de temas de chat privado de Telegram en esta fase.

Dirección de UX

1) Dos tipos de vinculación

  • Vinculación persistente: guardada en la configuración, reconciliada al inicio, pensada para canales/temas de “workspace con nombre”.
  • Vinculación temporal: solo en runtime, expira por política de inactividad/edad máxima.

2) Comportamiento de comandos

  • /acp spawn ... --thread here|auto|off sigue disponible.
  • Agregar controles explícitos de ciclo de vida de vinculación:
    • /acp bind [session|agent] [--persist]
    • /acp unbind [--persist]
    • /acp status incluye si la vinculación es persistent o temporary.
  • En conversaciones vinculadas, /new y /reset reinician la sesión ACP vinculada y mantienen la vinculación adjunta.

3) Identidad de conversación

  • Usar IDs de conversación canónicos:
    • Discord: ID de canal/hilo.
    • Tema de Telegram: chatId:topic:topicId.
  • Nunca indexar vinculaciones de Telegram solo por ID de tema.

Modelo de configuración (Propuesto)

Unificar la configuración de enrutamiento y vinculación ACP persistente en bindings[] de nivel superior con discriminador type explícito:

{
  "agents": {
    "list": [
      {
        "id": "main",
        "default": true,
        "workspace": "~/.openclaw/workspace-main",
        "runtime": { "type": "embedded" },
      },
      {
        "id": "codex",
        "workspace": "~/.openclaw/workspace-codex",
        "runtime": {
          "type": "acp",
          "acp": {
            "agent": "codex",
            "backend": "acpx",
            "mode": "persistent",
            "cwd": "/workspace/repo-a",
          },
        },
      },
      {
        "id": "claude",
        "workspace": "~/.openclaw/workspace-claude",
        "runtime": {
          "type": "acp",
          "acp": {
            "agent": "claude",
            "backend": "acpx",
            "mode": "persistent",
            "cwd": "/workspace/repo-b",
          },
        },
      },
    ],
  },
  "acp": {
    "enabled": true,
    "backend": "acpx",
    "allowedAgents": ["codex", "claude"],
  },
  "bindings": [
    // Vinculaciones de ruta (comportamiento existente)
    {
      "type": "route",
      "agentId": "main",
      "match": { "channel": "discord", "accountId": "default" },
    },
    {
      "type": "route",
      "agentId": "main",
      "match": { "channel": "telegram", "accountId": "default" },
    },
    // Vinculaciones ACP persistentes de conversación
    {
      "type": "acp",
      "agentId": "codex",
      "match": {
        "channel": "discord",
        "accountId": "default",
        "peer": { "kind": "channel", "id": "222222222222222222" },
      },
      "acp": {
        "label": "codex-main",
        "mode": "persistent",
        "cwd": "/workspace/repo-a",
        "backend": "acpx",
      },
    },
    {
      "type": "acp",
      "agentId": "claude",
      "match": {
        "channel": "discord",
        "accountId": "default",
        "peer": { "kind": "channel", "id": "333333333333333333" },
      },
      "acp": {
        "label": "claude-repo-b",
        "mode": "persistent",
        "cwd": "/workspace/repo-b",
      },
    },
    {
      "type": "acp",
      "agentId": "codex",
      "match": {
        "channel": "telegram",
        "accountId": "default",
        "peer": { "kind": "group", "id": "-1001234567890:topic:42" },
      },
      "acp": {
        "label": "tg-codex-42",
        "mode": "persistent",
      },
    },
  ],
  "channels": {
    "discord": {
      "guilds": {
        "111111111111111111": {
          "channels": {
            "222222222222222222": {
              "enabled": true,
              "requireMention": false,
            },
            "333333333333333333": {
              "enabled": true,
              "requireMention": false,
            },
          },
        },
      },
    },
    "telegram": {
      "groups": {
        "-1001234567890": {
          "topics": {
            "42": {
              "requireMention": false,
            },
          },
        },
      },
    },
  },
}

Ejemplo mínimo (sin sobreescrituras ACP por vinculación)

{
  "agents": {
    "list": [
      { "id": "main", "default": true, "runtime": { "type": "embedded" } },
      {
        "id": "codex",
        "runtime": {
          "type": "acp",
          "acp": { "agent": "codex", "backend": "acpx", "mode": "persistent" },
        },
      },
      {
        "id": "claude",
        "runtime": {
          "type": "acp",
          "acp": { "agent": "claude", "backend": "acpx", "mode": "persistent" },
        },
      },
    ],
  },
  "acp": { "enabled": true, "backend": "acpx" },
  "bindings": [
    {
      "type": "route",
      "agentId": "main",
      "match": { "channel": "discord", "accountId": "default" },
    },
    {
      "type": "route",
      "agentId": "main",
      "match": { "channel": "telegram", "accountId": "default" },
    },

    {
      "type": "acp",
      "agentId": "codex",
      "match": {
        "channel": "discord",
        "accountId": "default",
        "peer": { "kind": "channel", "id": "222222222222222222" },
      },
    },
    {
      "type": "acp",
      "agentId": "claude",
      "match": {
        "channel": "discord",
        "accountId": "default",
        "peer": { "kind": "channel", "id": "333333333333333333" },
      },
    },
    {
      "type": "acp",
      "agentId": "codex",
      "match": {
        "channel": "telegram",
        "accountId": "default",
        "peer": { "kind": "group", "id": "-1009876543210:topic:5" },
      },
    },
  ],
}

Notas:

  • bindings[].type es explícito:
    • route: enrutamiento normal del agente.
    • acp: vinculación persistente del harness ACP para una conversación coincidente.
  • Para type: "acp", match.peer.id es la clave de conversación canónica:
    • Canal/hilo de Discord: ID de canal/hilo sin procesar.
    • Tema de Telegram: chatId:topic:topicId.
  • bindings[].acp.backend es opcional. Orden de fallback del backend:
    1. bindings[].acp.backend
    2. agents.list[].runtime.acp.backend
    3. acp.backend global
  • mode, cwd y label siguen el mismo patrón de sobreescritura (sobreescritura de vinculación -> valor por defecto del runtime del agente -> comportamiento global/por defecto).
  • Mantener los existentes session.threadBindings.* y channels.discord.threadBindings.* para políticas de vinculación temporal.
  • Las entradas persistentes declaran el estado deseado; el runtime reconcilia con las sesiones/vinculaciones ACP reales.
  • Una vinculación ACP activa por nodo de conversación es el modelo previsto.
  • Compatibilidad hacia atrás: type ausente se interpreta como route para entradas legacy.

Selección de backend

  • La inicialización de sesión ACP ya usa la selección de backend configurada durante el spawn (acp.backend hoy).
  • Esta propuesta extiende la lógica de spawn/reconciliación para preferir sobreescrituras de vinculación ACP tipadas:
    • bindings[].acp.backend para sobreescritura local de conversación.
    • agents.list[].runtime.acp.backend para valores por defecto por agente.
  • Si no existe sobreescritura, mantener el comportamiento actual (valor por defecto acp.backend).

Ajuste de arquitectura en el sistema actual

Reutilizar componentes existentes

  • SessionBindingService ya soporta referencias de conversación agnósticas al canal.
  • Los flujos de spawn/bind ACP ya soportan vinculación mediante APIs de servicio.
  • Telegram ya transporta contexto de tema/hilo mediante MessageThreadId y chatId.

Componentes nuevos/extendidos

  • Adaptador de vinculación de Telegram (paralelo al adaptador de Discord):
    • Registrar adaptador por cuenta de Telegram,
    • resolver/listar/vincular/desvincular/touch por ID de conversación canónico.
  • Resolvedor/índice de vinculación tipado:
    • Dividir bindings[] en vistas route y acp,
    • mantener resolveAgentRoute solo en vinculaciones route,
    • resolver intención ACP persistente solo desde vinculaciones acp.
  • Resolución de vinculación entrante para Telegram:
    • Resolver sesión vinculada antes de la finalización de ruta (Discord ya hace esto).
  • Reconciliador de vinculaciones persistentes:
    • Al inicio: cargar vinculaciones type: "acp" configuradas de nivel superior, asegurar que las sesiones ACP existen, asegurar que las vinculaciones existen.
    • En cambio de configuración: aplicar deltas de forma segura.
  • Modelo de transición:
    • No se lee fallback de vinculación ACP local al canal,
    • Las vinculaciones ACP persistentes se obtienen solo de entradas bindings[].type="acp" de nivel superior.

Entrega por fases

Fase 1: Base del esquema de vinculación tipada

  • Extender el esquema de configuración para soportar el discriminador bindings[].type:
    • route,
    • acp con objeto de sobreescritura acp opcional (mode, backend, cwd, label).
  • Extender el esquema de agentes con descriptor de runtime para marcar agentes nativos ACP (agents.list[].runtime.type).
  • Agregar división parser/indexador para vinculaciones route vs ACP.

Fase 2: Resolución en runtime + paridad Discord/Telegram

  • Resolver vinculaciones ACP persistentes desde entradas type: "acp" de nivel superior para:
    • Canales/hilos de Discord,
    • Temas de foro de Telegram (IDs canónicos chatId:topic:topicId).
  • Implementar adaptador de vinculación de Telegram y paridad de sobreescritura de sesión vinculada entrante con Discord.
  • No incluir variantes de temas directos/privados de Telegram en esta fase.

Fase 3: Paridad de comandos y resets

  • Alinear el comportamiento de /acp, /new, /reset y /focus en conversaciones vinculadas de Telegram/Discord.
  • Asegurar que la vinculación sobrevive a los flujos de reset según la configuración.

Fase 4: Endurecimiento

  • Mejores diagnósticos (/acp status, logs de reconciliación al inicio).
  • Manejo de conflictos y chequeos de salud.

Barreras y políticas

  • Respetar la habilitación de ACP y restricciones de sandbox exactamente como hoy.
  • Mantener el alcance explícito de cuenta (accountId) para evitar filtraciones entre cuentas.
  • Fallar cerrado ante enrutamiento ambiguo.
  • Mantener el comportamiento de política de mención/acceso explícito por configuración de canal.

Plan de testing

  • Unitario:
    • Normalización de ID de conversación (especialmente IDs de temas de Telegram),
    • Rutas de crear/actualizar/eliminar del reconciliador,
    • Flujos de /acp bind --persist y unbind.
  • Integración:
    • Tema entrante de Telegram -> resolución de sesión ACP vinculada,
    • Canal/hilo entrante de Discord -> precedencia de vinculación persistente.
  • Regresión:
    • Las vinculaciones temporales siguen funcionando,
    • Los canales/temas no vinculados mantienen el comportamiento de enrutamiento actual.

Preguntas abiertas

  • ¿Debería /acp spawn --thread auto en temas de Telegram usar here por defecto?
  • ¿Las vinculaciones persistentes deberían siempre omitir la restricción de mención en conversaciones vinculadas, o requerir requireMention=false explícito?
  • ¿Debería /focus ganar --persist como alias de /acp bind --persist?

Despliegue

  • Publicar como opt-in por conversación (entrada bindings[].type="acp" presente).
  • Empezar solo con Discord + Telegram.
  • Agregar documentación con ejemplos para:
    • “un canal/tema por agente”
    • “múltiples canales/temas por el mismo agente con diferente cwd
    • “patrones de nombres de equipo (codex-1, claude-repo-x)”.