Quand un sous-agent terminait, il s'emparait du chat

Dans Telegram, tu demandes à Hermes d’envoyer deux subagent faire des recherches, puis tu vas te faire un café. À ton retour, le rapport du subagent n’est jamais arrivé — mais ta conversation d’origine est « terminée », et chaque message que tu envoies dans cette même fenêtre reçoit désormais une réponse du subagent, comme si quelque chose avait discrètement pris ta place.
Ce n’est pas ton imagination. L’enfant a bel et bien pris le chat.
Le symptôme étrange
Un subagent lancé avec delegate_task qui démarre un processus d’arrière-plan — ou une longue commande au premier plan promue en arrière-plan dès qu’elle dure assez longtemps — signale une complétion quand il se termine. C’est à ce moment-là que tout a mal tourné :
- Ta vraie conversation a été close en tant que
session_switch, un événement de user boundary ; - La route du chat a été déplacée sur le transcript d’exécution propre à l’enfant ;
- Le résultat de délégation du parent a donc été abandonné comme terminal à son arrivée — tu n’as jamais vu le rapport ;
- Ton message suivant dans cette fenêtre a reçu une réponse de l’enfant au lieu de ta session.
Le même bug a été signalé sur Discord (MP, fils et salons, sous macOS et Windows), Telegram (MP et groupes), Slack, Zulip et Feishu.
Comment une complétion a pris le chat
En bref : le système a classé la complétion sous le mauvais propriétaire.
Un processus d’arrière-plan démarré dans un enfant de delegate_task enregistre l’id de session de l’enfant lui-même comme pin de complétion. Quand il se termine, le completion resolver du gateway voit un pin actif pointant vers une session et considère cette session comme propriétaire du chat. L’étape suivante est switch_session : la route du chat se déplace sur le transcript d’exécution de l’enfant, et la vraie conversation se termine en user boundary. Un user boundary est terminal — à ce stade, le résultat de délégation du parent, encore en vol, est jugé appartenir à une conversation close, et il est abandonné.
Autrement dit : l’avis de processus de l’enfant devait atteindre la personne qui avait délégué le travail, et il est allé chercher l’enfant à qui le travail avait été délégué. Un lien erroné, et toute la chaîne a basculé du mauvais côté.
Le correctif : une complétion appartient à la personne, pas à l’enfant
La règle qu’établit le correctif est simple : une complétion épinglée appartient à la session tournée vers l’humain — celle qu’on atteint via model_config._delegate_from. La route du chat ne se déplace jamais sur un enfant.
Mécaniquement :
- La propriété est prouvée une seule fois avant la livraison, puis remise au resolver. Le resolver ne relit la propriété que si la route a bougé après cette preuve ;
- Une propriété incertaine est retentée plutôt que devinée : avant l’admission, une nouvelle tentative ne coûte aucune tentative de livraison ; après l’admission, la complétion retourne dans la delivery queue ;
- Les user boundaries restent terminaux à chaque saut, si bien qu’une nouvelle tentative ne peut jamais ressusciter une conversation close.
Les avis de processus des enfants ont eux aussi été ajustés : ils suivent désormais delegation.surface_child_process_notifications (désactivé par défaut, comme les docs de délégation l’ont toujours dit) et sont livrés dans le profile propriétaire de la session, y compris la session brute api_server d’un profile servi. Un handoff process_manage en cours de route redonne l’avis au parent.
Pourquoi le correctif vit dans le gateway, pas dans une plateforme
Chaque platform adapter fait exactement un travail : traduire entre son service et le format de message commun du gateway. Quelle session possède un chat se décide ensuite dans le code partagé du gateway — un seul session store indexé par la routing key du chat, un seul completion resolver, un seul switch_session.
La complétion d’un enfant emprunte ce chemin sur chaque plateforme, donc chaque plateforme a la prise de contrôle, et la réparation appartient au code partagé plutôt qu’à un adapter isolé. C’est pourquoi ce changement ne contient aucun patch spécifique à une plateforme.
Ce qui n’a pas changé
- Les événements de user boundary restent terminaux ; leur sémantique n’a pas été assouplie ;
- Les notifications de processus des enfants restent supprimées par défaut : le résultat consolidé de l’enfant est ce qui se livre, le commentaire en direct non. Active
delegation.surface_child_process_notifications: truepour les recevoir, avec l’attribution du subagent ; - Les résultats de délégation eux-mêmes n’ont jamais été supprimés — ce travail porte sur leur capacité réelle à être livrés ;
- Le changement est un salvage rebuild de #132827, qui conserve les commits des contributeurs d’origine avec leur paternité intacte.
Statut et mise à niveau
Ce correctif (PR #136225, étiqueté P1) a été fusionné dans main le 2026-10-11. Le tag actuel, v0.21.6 du 8 octobre, ne le contient pas ; la prochaine release le contiendra.
Comment vérifier après la mise à niveau — sur Discord ou Telegram, délègue un subagent qui exécute une commande devenant un processus d’arrière-plan, puis observe ce qui se passe à la fin :
- Le résultat de l’enfant doit atterrir dans ta conversation d’origine ;
- Ton message suivant doit recevoir une réponse de ta propre session, pas de l’enfant ;
- Avec
delegation.surface_child_process_notificationsactivé, l’avis de processus apparaît dans cette même conversation avec l’attribution du subagent.
Pour conclure
C’est le bug classique du genre « on croit que le modèle a perdu la tête, alors que c’est une erreur de comptabilité » : l’avis de complétion s’adressait au mauvais destinataire, la propriété du chat a été transférée, et chaque tour suivant a déraillé. Une fois corrigé, un job d’arrière-plan de longue durée ne peut plus remplacer la conversation qui l’a lancé — une frontière qui compte surtout dans les déploiements multiplateformes et multi-utilisateurs faisant tourner plusieurs subagent en même temps.
Lectures associées : l’orchestration de subagent en direct traite du pilotage des enfants pendant leur exécution, le guide des frontières de session explique où une conversation commence et se termine, et l’auto-réparation des plateformes du gateway est l’autre voie de récupération quand une connexion de plateforme déraille.