Grupos de pares
Un peer group declara de quién acepta un agente mensajes entrantes en un canal, y con qué otros agentes puede intercambiar mensajes allí. Es la puerta de entrada para los canales de chat y la primitiva de enrutamiento para el despacho entre agentes. En la configuración se encuentra en [peer_groups.<name>]. Para saber cómo encajan los peer groups en el cableado de un agente, consulte Agents.
Los remitentes entrantes se validan contra el conjunto de pares resuelto para la instancia del canal, tomado de todos los bloques [peer_groups.<name>] cuyo channel coincida con el tipo de canal o con su alias con punto. La comparación elimina el @ inicial y no distingue mayúsculas de minúsculas frente al identificador nativo del remitente en el canal. Un conjunto vacío deniega a todos; un conjunto que contiene "*" acepta a cualquiera; en caso contrario, solo se aceptan los external_peers enumerados (y los agentes pares). Esto es independiente del emparejamiento de la puerta de enlace ([gateway] require_pairing), que autentica a los clientes HTTP/WebSocket, no a los remitentes de canales de chat.
Campos
Un bloque [peer_groups.<name>] contiene:
| Campo | Significado |
|---|---|
channel | Un tipo de canal ("telegram", se aplica a todos los alias de ese tipo) o un alias con punto ("telegram.work", se limita a esa única instancia). |
agents | Agentes miembro por alias. Dos agentes son pares solo cuando ambos aparecen en el mismo grupo; la membresía es mutua. |
external_peers | Miembros no agentes según el nombre de usuario/ID nativo del canal. ["*"] acepta a cualquiera; vacío no acepta a nadie. |
ignore | Lista de bloqueo por grupo; viaja con el conjunto de pares resuelto y anula cualquier concesión, incluida una comodín. La aplica el canal, según las reglas de identidad de ese canal. |
output_modality | Modalidad de respuesta preferida para el grupo: mirror (basada en la entrada, predeterminada), voice (responder siempre y entregar mensajes proactivos como notas TTS en canales con capacidad de audio) o text (siempre texto). |
admin_for_agent_scope | Cuando es true, los external_peers del grupo están autorizados a emitir /model --agent <model> en el agente vinculado. Valor predeterminado false (denegar por defecto). Véase Autorización del alcance del agente de administración. |
Resolución
La autorización de remitentes externos está limitada por canal: el runtime une external_peers de todos los grupos que coinciden con el tipo de canal o con el alias con puntos, y transporta junto a ella la lista ignore de cada grupo coincidente como entradas reservadas !name. Ambas mitades llegan al canal, que aplica las denegaciones antes que las concesiones.
La denegación la aplica el canal, no el resolutor. Determinar si dos entradas nombran la misma cuenta es algo que solo el canal puede responder: Reddit interpreta u/alice y alice como un mismo usuario, WhatsApp interpreta +1555… y 1555… como un mismo número, y Nostr convierte un npub a hex. Un resolutor que eliminara por sí mismo las entradas ignoradas tenía que adivinar eso, y una suposición errónea descartaba la denegación antes de que el canal pudiera aplicarla, lo que admitía a un remitente que el operador había anotado como bloqueado. Por eso ignore no se resta en ningún otro lugar salvo en el canal que entiende la identidad.
ignore tiene prioridad sobre cualquier concesión, incluido un comodín. Con external_peers = ["*"] en un grupo que coincide y ignore = ["alice"] en otro, todos excepto alice quedan autorizados. ignore = ["*"] es su imagen especular y deniega a todos los remitentes. Una denegación coincide con todo lo que la misma cadena habría concedido en ese canal y, además, ignora mayúsculas y minúsculas y un @ inicial, porque una lista de bloqueo debe inclinarse hacia la denegación. En un canal que acepta varios identificadores para una misma cuenta (Bluesky acepta un handle o un DID), ignorar cualquiera de ellos deniega la cuenta.
A se reconoce un comodín (wildcard) eliminando los espacios en blanco que lo rodean, por lo que [" * "] y ["*"] significan lo mismo para ambas partes de la regla.
! abre, por tanto, un marcador de denegación en la lista resuelta. Un identificador puede comenzar legalmente con !, ya que RFC 5322 lo permite en la parte local de una dirección de correo electrónico, de modo que el recuento de marcadores iniciales determina la decisión y no solo su presencia. Un identificador que comienza con k marcadores se emite con 2k como concesión y 2k + 1 como denegación, de modo que una secuencia par es una concesión, una secuencia impar es una denegación, y dividir la secuencia entre dos recupera el identificador:
| identidad | como external_peers | como ignore |
|---|---|---|
alice | alice | !alice |
!user@example.com | !!user@example.com | !!!user@example.com |
Una identidad que no comienza con ! se codifica exactamente como siempre, por lo que ninguna configuración existente se interpreta de forma diferente. Nada de lo que un operador puede escribir se invierte silenciosamente en ninguna de las dos direcciones. Escribes la dirección tal como es; la duplicación es interna a la lista resuelta.
Un canal escrito de más de una forma en channel (WeCom WebSocket responde tanto a wecom-ws como a wecom_ws) se resuelve como un único conjunto. Resolver cada grafía por separado y unir los resultados dejaría un comodín bajo una grafía y un ignore bajo la otra sin saber nada el uno del otro. Todas las rutas de arranque deben usar la misma resolución: la construcción normal de canales del daemon y la ruta de un solo canal de ejecución única pasan por un mismo helper con nombre, precisamente por esa razón.
Como la denegación viaja dentro de la lista resuelta, un canal autoriza a un remitente mediante allowlist::is_user_allowed, is_user_allowed_by o, cuando un remitente tiene varios identificadores, is_identity_allowed. Comprobar la lista directamente en busca de "*", o consultar un identificador a la vez, admite a un remitente que el operador ignoró.
El listado resuelto es una entrada de autorización, no una lista de direcciones: contiene comodines y marcadores de denegación. El código que necesita un destino al que enviar un mensaje consulta Config::channel_addressable_peers, que devuelve únicamente las entradas que nombran una cuenta accesible.
Por la misma razón, «la lista está vacía» no es lo mismo que «alguien ha sido autorizado». Un grupo con ignore y sin external_peers se resuelve en una lista no vacía que no concede acceso a nadie, así que un canal que decide si todavía no está emparejado consulta allowlist::grants_anyone en lugar de comprobar si la lista está vacía. Una concesión que su propio ignore cancela tampoco cuenta: external_peers = ["alice"] con ignore = ["alice"], o ["*"] con ignore = ["*"], no autoriza a nadie, así que los canales que condicionan el emparejamiento a esa respuesta siguen ofreciendo su código de vinculación.
Después, el emparejamiento escribe en external_peers, por lo que el mismo ignore puede anular lo que el emparejamiento acaba de guardar. En lugar de informar de un enlace que no puede funcionar, la escritura se rechaza y se indica al operador que primero elimine la entrada ignore. Una entrada explícita de lista de bloqueo sigue siendo autoritativa frente a un intento de emparejamiento. Esto se aplica a todos los escritores: un intercambio de /bind dentro del canal, zeroclaw channel bind-telegram y POST /api/channels/bind rechazan todos ellos esta escritura, y el rechazo también abarca una identidad que ya figura como concesión, porque una concesión que su propio ignore anula tampoco es un enlace utilizable.
El enrutamiento entre agentes tiene alcance por agente: para un agente dado, el runtime recorre todos los grupos en los que aparece el agente, une los alias de los demás miembros en el canal del grupo y, después, resta ignore. El alias propio del agente se elimina por precaución para evitar un bucle reflexivo. Un agente que no pertenece a ningún grupo de pares se ejecuta en solitario, sin despacho entre agentes.
El identificador de remitente con el que cada canal realiza la coincidencia difiere según la plataforma (un ID de usuario de Telegram, un @user:server de Matrix, un número E.164, un UUID, …). Cada página de canal indica la forma del identificador que espera.
Ejemplo
Un grupo de pares de Discord llamado p. ej. my_discord_group establece channel = "discord", permite 111111111111111111 en external_peers, nombra a los agentes pares researcher, summariser, y bloquea 222222222222222222 mediante ignore. Configúralo a través del panel de control del gateway, zerocode o zeroclaw config set.
Cada página de canal muestra la forma directiva con la estructura del identificador de remitente de ese canal.
Autorización de ámbito de agente de administrador
admin_for_agent_scope = true amplía el límite de privilegios del grupo: además de ser un peer enrutable, cada miembro de external_peers tiene अनुमति para emitir /model --agent <model> en el agente vinculado, es decir, cambiar el binding del agente a un modelo distinto del predeterminado. La bandera es de denegación por defecto: todo grupo que no la tenga establecida explícitamente en true deniega esta capacidad, incluidos los grupos con external_peers no vacío. /model --user <model> (anulación solo para la sesión, sin rebinding del agente) no está restringido por esta bandera y está disponible para todo peer aceptado.
El orquestador resuelve en vivo el conjunto de administradores autorizados desde Config::channel_agent_scope_admins, pero la puerta de despacho lee a través de la instantánea de configuración con la que se inició el tiempo de ejecución. Por lo tanto, las ediciones del operador en admin_for_agent_scope (o en external_peers / channel / agents de un grupo) surten efecto en el siguiente reinicio del daemon, no en el proceso en ejecución. Cambiar la bandera en un daemon en vivo no autorizará, por sí solo, a ningún nuevo remitente dentro de la sesión actual.
Esto es deliberado: la autorización se calcula contra la configuración inmutable al arrancar, así que un par que se añade a un grupo de administradores a mitad de ejecución no puede escalar privilegios dentro de la vida útil del proceso actual. El conjunto solo se vuelve a resolver en una recarga completa de la configuración, que en sí misma es una ruta de reinicio. Si una invocación de /model --agent informa “not authorized” para un emisor que esperabas que estuviera dentro del ámbito, reinicia el daemon después de editar admin_for_agent_scope y vuelve a emitir el comando desde una nueva sesión de cliente antes de sacar más conclusiones.