Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Groupes de pairs

Un peer group déclare de qui un agent accepte les messages entrants sur un canal, et avec quels autres agents il peut y échanger des messages. C’est la porte d’entrée pour les canaux de discussion et la primitive de routage pour la distribution inter-agents. Dans la configuration, il se trouve à [peer_groups.<name>]. Pour comprendre comment les peer groups s’intègrent dans le câblage d’un agent, consultez Agents.

Les expéditeurs entrants sont filtrés selon l’ensemble de pairs résolu pour l’instance de canal, issu de chaque bloc [peer_groups.<name>] dont channel correspond soit au type de canal, soit à son alias pointé. La correspondance supprime un @ initial et ne tient pas compte de la casse, par rapport à l’identifiant natif de l’expéditeur sur le canal. Un ensemble vide refuse tout le monde ; un ensemble contenant "*" accepte tout le monde ; sinon, seuls les external_peers listés (et les agents pairs) sont acceptés. Ceci est distinct de l’appairage de la passerelle ([gateway] require_pairing), qui authentifie les clients HTTP/WebSocket, et non les expéditeurs des canaux de discussion.

Champs

Un bloc [peer_groups.<name>] contient :

ChampSignification
channelUn type de canal ("telegram", s’applique à tous les alias de ce type) ou un alias avec notation pointée ("telegram.work", limite la portée à cette seule instance).
agentsAgents membres par alias. Deux agents ne sont pairs que lorsqu’ils apparaissent tous les deux dans le même groupe ; l’appartenance est mutuelle.
external_peersMembres non-agents identifiés par le nom d’utilisateur/ID natif du canal. ["*"] accepte tout le monde ; une valeur vide n’accepte personne.
ignoreListe de blocage par groupe ; elle suit l’ensemble de pairs résolu et prime sur toute autorisation, y compris un joker. Appliquée par le canal, selon les règles d’identité de ce canal.
output_modalityModalité de réponse préférée pour le groupe : mirror (déterminée par l’entrée, par défaut), voice (toujours répondre et envoyer les messages proactifs sous forme de notes TTS sur les canaux compatibles audio) ou text (toujours en texte).
admin_for_agent_scopeLorsque true, les external_peers du groupe sont autorisés à exécuter /model --agent <model> sur l’agent lié. Par défaut false (refus par défaut). Voir Autorisation d’administration de portée agent.

Résolution

L’autorisation des expéditeurs externes est limitée au canal : le runtime fusionne les external_peers de chaque groupe correspondant au type de canal ou à l’alias pointé, et transmet avec elles la liste ignore de chaque groupe correspondant, sous forme d’entrées !name réservées. Les deux éléments atteignent le canal, qui applique les refus avant les autorisations.

The deny is applied by the channel, not by the resolver. Whether two entries name the same account is a question only the channel can answer: Reddit reads u/alice and alice as one user, WhatsApp reads +1555… and 1555… as one number, Nostr rewrites an npub to hex. A resolver that removed ignored entries itself had to guess at that, and a guess that came out wrong dropped the deny before the channel could apply it, admitting a sender the operator had written down. So ignore is not subtracted anywhere except in the channel that understands the identity.

ignore l’emporte sur une autorisation, y compris un joker. Avec external_peers = ["*"] sur un groupe correspondant et ignore = ["alice"] sur un autre, tous les expéditeurs sauf alice sont autorisés. ignore = ["*"] en est l’image inverse et refuse tous les expéditeurs. Un refus correspond à tout ce que la même chaîne aurait autorisé sur ce canal, et ignore en plus la casse et un @ initial, car une liste de blocage doit pencher vers le refus. Sur un canal qui accepte plusieurs identifiants pour un même compte (Bluesky accepte un handle ou un DID), ignorer l’un d’eux refuse le compte.

Un caractère générique est reconnu après suppression des espaces qui l’entourent, donc [" * "] et ["*"] ont la même signification pour les deux parties de la règle.

! ouvre donc un marqueur de refus dans la liste résolue. Une identité peut légitimement commencer par !, car la RFC 5322 l’autorise dans la partie locale d’une adresse e-mail. C’est donc le nombre de marqueurs initiaux qui détermine la décision, et non simplement leur présence. Une identité commençant par k marqueurs est émise avec 2k pour une autorisation et 2k + 1 pour un refus. Ainsi, une série paire correspond à une autorisation, une série impaire à un refus, et diviser la série par deux permet de retrouver l’identité :

identitécomme external_peerscomme ignore
alicealice!alice
!user@example.com!!user@example.com!!!user@example.com

Une identité qui ne commence pas par ! s’encode exactement comme auparavant, de sorte qu’aucune configuration existante n’est interprétée différemment. Rien de ce qu’un opérateur peut écrire n’est silencieusement inversé, dans un sens comme dans l’autre. Vous écrivez l’adresse telle quelle ; le doublement est interne à la liste résolue.

Un canal écrit de plusieurs façons dans channel (WeCom WebSocket répond à la fois à wecom-ws et à wecom_ws) est résolu comme un seul ensemble. Résoudre chaque graphie séparément puis joindre les résultats laisserait un joker sous une graphie et un ignore sous l’autre sans savoir l’un de l’autre. Chaque chemin de démarrage doit utiliser la même résolution : la construction normale des canaux du démon et le chemin mono-canal ponctuel passent tous deux par une même fonction auxiliaire nommée, précisément pour cette raison.

Parce que le refus voyage à l’intérieur de la liste résolue, un canal autorise un expéditeur via allowlist::is_user_allowed, is_user_allowed_by ou, lorsqu’un expéditeur possède plusieurs identifiants, is_identity_allowed. Tester la liste à la recherche de “*” directement, ou interroger un identifiant à la fois, admet un expéditeur que l’opérateur avait ignoré.

La liste résolue est une entrée d’autorisation, et non une liste d’adresses : elle contient des jokers et des marqueurs de refus. Le code qui a besoin d’un destinataire pour envoyer un message interroge Config::channel_addressable_peers, qui ne renvoie que les entrées désignant un compte joignable.

Pour la même raison, « la liste est-elle vide ? » n’est pas équivalent à « quelqu’un a-t-il été autorisé ? ». Un groupe disposant de ignore mais sans external_peers se résout en une liste non vide qui n’accorde l’accès à personne ; un canal qui détermine s’il est encore non appairé interroge donc allowlist::grants_anyone au lieu de tester si la liste est vide. Une autorisation annulée par son propre ignore ne compte pas non plus : external_peers = ["alice"] avec ignore = ["alice"], ou ["*"] avec ignore = ["*"], n’autorise personne, si bien que les canaux qui conditionnent l’appairage à cette réponse continuent de proposer leur code de liaison.

Le jumelage écrit ensuite dans external_peers, ce qui signifie que le même ignore peut masquer ce que le jumelage vient d’enregistrer. Plutôt que de signaler une liaison qui ne peut pas fonctionner, l’écriture est refusée et l’opérateur est invité à supprimer d’abord l’entrée ignore. Une entrée explicite de liste de blocage reste prioritaire sur une tentative de jumelage. Ceci vaut pour chaque auteur d’écriture : un échange /bind dans le canal, zeroclaw channel bind-telegram et POST /api/channels/bind le refusent tous, et ce refus couvre une identité déjà listée comme autorisation, car une autorisation que son propre ignore masque n’est pas non plus une liaison utilisable.

Le routage inter-agents est délimité par agent : pour un agent donné, le runtime parcourt chaque groupe dont l’agent fait partie, fusionne les alias des autres membres sur le canal du groupe, puis retire ignore. L’alias propre de l’agent est retiré par précaution afin d’éviter une boucle sur soi-même. Un agent qui n’appartient à aucun groupe de pairs s’exécute en solo, sans dispatch inter-agents.

L’identifiant d’expéditeur que chaque canal utilise pour la correspondance diffère selon la plateforme (un ID utilisateur Telegram, un @user:server Matrix, un numéro E.164, un UUID, …). Chaque page de canal précise le format d’identifiant attendu.

Exemple

Un groupe de pairs Discord nommé par exemple my_discord_group définit channel = "discord", autorise 111111111111111111 dans external_peers, nomme les agents pairs researcher, summariser, et bloque 222222222222222222 via ignore. Configurez-le via le tableau de bord de la passerelle, zerocode ou zeroclaw config set.

Chaque page de canal affiche le formulaire de directive avec la forme d’identifiant d’expéditeur propre à ce canal.

Autorisation de portée de l’agent administrateur

admin_for_agent_scope = true étend le périmètre de privilèges du groupe : en plus d’être un pair routable, chaque membre de external_peers est autorisé à exécuter /model --agent <model> sur l’agent lié, c.-à-d. basculer le binding de l’agent vers un modèle autre que celui par défaut. L’indicateur est en refus par défaut : tout groupe pour lequel il n’est pas explicitement défini à true refuse la capacité, y compris les groupes avec des external_peers non vides. /model --user <model> (surcharge limitée à la session, sans re-liaison d’agent) n’est pas soumis à cet indicateur et est disponible pour chaque pair accepté.

L’orchestrateur résout l’ensemble des administrateurs autorisés en direct depuis Config::channel_agent_scope_admins, mais la porte de dispatch lit via l’instantané de configuration avec lequel le runtime a été démarré. Les modifications de l’opérateur apportées à admin_for_agent_scope (ou aux external_peers / channel / agents d’un groupe) prennent donc effet au prochain redémarrage du démon, et non sur le processus en cours d’exécution. Basculer le flag sur un démon en cours d’exécution n’autorisera pas, à lui seul, de nouveaux expéditeurs au sein de la session courante.

C’est délibéré : l’autorisation est calculée par rapport à la configuration immuable au démarrage, de sorte qu’un pair qui est ajouté à un groupe d’administration en cours d’exécution ne peut pas escalader au sein de la durée de vie du processus actuel. L’ensemble n’est re-résolu que lors d’un rechargement complet de la configuration, qui est lui-même un chemin de redémarrage. Si une invocation /model --agent signale « not authorized » pour un expéditeur que vous attendiez dans le scope, redémarrez le démon après avoir modifié admin_for_agent_scope et réémettez la commande depuis une session client fraîche avant d’en tirer d’autres conclusions.