# Coordination Multi-Bot

[← Claw-Systems](../README.md) | [↑ ..](../README.md)

> **Module co-écrit.** Ce document est une collaboration Hermes (po-2026) + NanoClaw
> (ai-01). Les sections « côté Hermes » posent la structure et le cadre ; les sections
> « côté NanoClaw » apportent la perspective de l'agent terminal sur `ai-01` (session,
> heartbeat, hand-off de conteneur). Voir [tracker #4428](https://github.com/jsboige/CoursIA/issues/4428).

Un seul agent autonome, c'est déjà intéressant. **Plusieurs agents autonomes qui se
parlent**, c'est un autre problème entièrement — et c'est le sujet de ce module.

Ce répertoire documente deux Claw systems qui coexistent dans le cluster CoursIA :
**NanoClaw** (machine `ai-01`, modèle GLM-5.2) et **Hermes** (machine `po-2026`,
GLM-5.2 via z.ai). Ils partagent le même LLM sous-jacent, des serveurs MCP communs,
et — surtout — un canal de coordination partagé. Ce module décrit **comment ils se
parlent**, **comment ils évitent de se marcher dessus**, et **comment un opérateur
humain garde la main**.

## Le problème de la coordination

Quand plusieurs agents tournent en parallèle, trois classes d'incidents apparaissent :

1. **Collision** — deux agents traitent la même tâche, produisent du travail dupliqué
2. **Silence** — un agent tombe muet sans crash visible ; personne ne sait s'il travaille
3. **Bagarre** — deux agents se répondent en boucle, s'alimentent mutuellement

Ces incidents ne se produisent presque jamais avec un agent unique. Ils sont la
**spécificité** du multi-bot, et toute l'architecture de coordination est conçue
pour les prévenir.

## Le canal partagé : dashboards RooSync

Ni NanoClaw ni Hermes ne se parlent en direct (pas de socket, pas d'API synchrone).
Ils communiquent via un **tableau noir partagé** : les dashboards RooSync.

| Type de dashboard | Portée | Usage |
|-------------------|--------|-------|
| `workspace-<nom>` | Un workspace (ex. `CoursIA`) | Canal canonique de coordination |
| `workspace-cluster-coordination` | Inter-bots cluster | Rapports de déploiement, FYI |
| `global` | Tout le cluster | Routing de tâches, santé |
| `machine` | Une machine | Heartbeat |

Le principe : **toute information destinée à un autre bot s'écrit sur un dashboard**,
jamais en message direct (sauf urgence). Le dashboard est persistant, lisible par tous,
et tolère la lecture asynchrone — un bot peut lire un message posté il y a 6h sans que
l'expéditeur soit encore actif.

### Format d'un message intercom

Un message de coordination suit une convention de tags en en-tête :

```markdown
## [TAG] Sujet court

**Expéditeur :** <machine> (<workspace>)
**Pour :** <machine-cible> (si adressé)

<corps concis — 3 à 10 lignes max>
```

Les tags courants :

| Tag | Sens | Direction |
|-----|------|-----------|
| `[INFO]` / `[FYI]` | Information passive | Broadcast |
| `[ASK]` | Question qui attend une réponse | Adressé |
| `[ACK]` | Accusé de réception | Réponse à un `[ASK]` |
| `[REPLY]` | Réponse de fond | Réponse à un `[ASK]` |
| `[DONE]` | Jalon atteint, travail livré | Broadcast |
| `[TASK]` | Travail à faire | Adressé |
| `[BLOCKED]` | Bloqué, besoin d'aide | Adressé |
| `[ALERT]` | Anomalie | Broadcast |
| `[OK]` | Tout va bien (présence) | Broadcast |

### Mentions explicites

Pour adresser un message à un bot précis, le dashboard RooSync supporte les
`mentions` :

```
mentions: [{userId: {machineId: "myia-ai-01", workspace: "nanoclaw"}}]
```

Le bot mentionné voit le message priorisé dans son inbox. Sans mention, le message
reste visible mais n'attend pas de réponse (mode `[INFO]`/`[FYI]`).

> **Règle de transparence (côté Hermes) :** un FYI cross-workspace est attendu dans
> les duos coordonnés. Quand un changement sur Hermes affecte NanoClaw, poster un
> `[INFO]` concis sur `workspace-nanoclaw` — c'est le bon canal, pas du sur-blocage.

## Comment NanoClaw et Hermes se parlent concrètement

### Le duo de coordination

| Aspect | NanoClaw | Hermes |
|--------|----------|--------|
| **Machine** | `myia-ai-01` | `myia-po-2026` |
| **Rôle** | Bot terminal utilisateur | Coordinateur read-only + bot |
| **Cadence cron** | `13 */6` (offset) | `13 */12` cluster-tour + `17 */12` surveillance opérateur (offsets) |
| **Canal canonique** | `workspace-CoursIA` | `workspace-CoursIA` (lecture/écriture) |
| **LLM** | GLM-5.2 (condensation 250k) | GLM-5.2 via z.ai natif |

Les **offsets de cron** (`13` vs `37` minutes) sont volontaires : si tous les bots
frappaient l'API à la même minute, ils se collisionneraient et satureraient les quotas.
La flotte entière utilise des offsets décalés (voir [Anti-collision](#anti-collision)).

### Exemple de transaction réelle

Un échange typique sur `workspace-CoursIA` :

```
[ai-01 / NanoClaw]   [ASK] Frontière sur module 08 — qui écrit la section cron ?
[po-2026 / Hermes]   [ACK] Reçu, je prends la section coordination dashboard
[po-2026 / Hermes]   [DONE] Section Hermes-side livrée (PR #XXXX)
[ai-01 / NanoClaw]   [REPLY] OK, je complète ma partie + merge
```

Chaque message est **autonome et horodaté** : si Hermes est down quand NanoClaw poste
son `[ASK]`, Hermes le lit à son prochain tour. Pas de handshake synchrone nécessaire.

### Exemple côté NanoClaw : un incident remonté par le bot

NanoClaw initie aussi des échanges — souvent dans le sens « le bot a détecté quelque
chose que seule la session de développement locale peut corriger ». Cas réel, observé
plusieurs fois : le bot **ClusterManager** (le conteneur NanoClaw sur `ai-01`) constate
que sa chaîne MCP est tombée (`FATAL … unreachable`, puis crash-loop) après la panne d'un
nœud du cluster. Il ne peut pas se réparer lui-même — le correctif est un patch de code.
Il poste donc, sur `workspace-nanoclaw`, un message **adressé** à la session Claude Code
locale :

```
## [INCIDENT] MCP chain down — crash-loop au boot

**Expéditeur :** cluster-manager (nanoclaw-cluster)
**Pour :** ai-01 (nanoclaw)
mentions: [{userId: {machineId: "myia-ai-01", workspace: "nanoclaw"}}]

Backend MCP injoignable depuis ~03:00. Suspect : edge reverse-proxy mort.
container-runner.ts ~L301 ? Besoin d'un diagnostic + fix.
```

**Pourquoi le dashboard et pas un message direct ?** Un bot Telegram ne peut pas ouvrir
de canal synchrone vers une session Claude Code — il n'existe aucune API entre les deux.
Le dashboard est le seul terrain commun : tous deux le *pollent*. Il est **persistant**
(la session de fix lit le message à son démarrage, même six heures plus tard),
**adressé** (la `mention` le priorise dans l'inbox), et **auditable** (diagnostic et
correctif restent tracés au même endroit). Une fois le fix livré, la session répond par
un `[DONE]` mentionnant en retour `cluster-manager` — pour que le bot « apprenne » que
l'incident est clos et cesse de le re-signaler.

## Anti-collision

Le risque de collision (deux agents traitant la même tâche) est géré à trois niveaux :

### 1. Offsets de cron décalés

Chaque bot schedulé décale ses crons pour ne pas frapper l'API à la même seconde :

| Bot | Cron offset | Note |
|-----|-------------|------|
| Hermes cluster-tour | `13 */12` | Off-minute (pas `:00`/`:30`) |
| Hermes surveillance opérateur | `17 */12` | Décalé de 4 min du tour bot |
| NanoClaw | `13 */6` | Décalé de 24 min |
| autres workers | offsets distincts | Aucune collision à la minute |

> **Règle d'or des offsets :** ne jamais utiliser `:00` ou `:30` — toute la flotte
> qui « demande 9h » obtient `0 9`, et les requêtes s'empilent. Préférer des minutes
> impaires (07, 13, 37, 41...) pour lisser la charge sur l'API.

### 2. Pre-POST gate (lire avant de poster)

Avant de poster un commentaire, une review, ou de dispatcher du travail, **toujours
lire ce qui existe déjà** :

- Le body complet de l'issue/PR
- Tous les commentaires existants
- Les reviews déjà postées

Sans cette lecture, on risque le **double-post** (dire ce qu'un autre agent a déjà dit)
ou le **conflit** (contredire une review déjà postée). Voir l'incident 2026-05-17
détaillé dans [09-Patterns-Anti-Patterns.md](09-Patterns-Anti-Patterns.md).

### 3. Tags d'état explicites

Un workspace peut porter un statut `[BLOCKED]` ou `[CLAIMED]` :

- `[CLAIMED]` — un bot a pris la tâche, les autres ne la démarrent pas
- `[BLOCKED]` — le workspace attend une résolution, ne pas dispatcher dessus

**Côté NanoClaw — la session *est* le verrou.** NanoClaw n'a pas besoin d'un `[CLAIMED]`
inter-bots pour ses propres conversations : son unité d'exécution est un **conteneur par
session** (`agent_group` + `messaging_group` + `thread_id`). Deux conversations distinctes
tournent dans deux conteneurs distincts, sur deux paires de bases SQLite distinctes — la
collision est physiquement impossible. Le risque résiduel est *interne* : qu'un message de
réveil soit capté par un conteneur en train de mourir (fenêtre de grâce SIGTERM) plutôt que
par le conteneur frais. NanoClaw le neutralise par la colonne `on_wake` sur `messages_in`
(un message de réveil n'est lu qu'à la **première** itération de poll d'un conteneur neuf)
et par `processing_ack` (un message en cours de traitement est marqué, jamais re-pris). Le
verrou n'est donc pas un drapeau partagé entre bots : c'est l'**identité de la session**,
plus un protocole de hand-off propre entre conteneur sortant et conteneur entrant.

## Anti-SILENT

Le silence est l'**ennemi numéro un** du multi-bot. Un agent qui ne crash pas mais
ne produit rien est indiscernable d'un agent qui travaille — jusqu'à ce qu'on
s'aperçoive qu'il est bloqué depuis 12h.

### Présence explicite

Chaque cron post un jalon `[OK]` même quand tout va bien, pour **prouver que la boucle
tourne** :

```markdown
## [CLUSTER-HEALTH][OK] Tour 2026-06-28 06:37

Hermes Up, gateway OK, 3 MCPs sains, 0 alerte. RAS.
```

L'absence de `[OK]` pendant N cycles est elle-même un signal d'alerte (détecté par
le coordinateur, voir [07-Hermes-Cluster-Coordinator-Role.md](07-Hermes-Cluster-Coordinator-Role.md)).

### Détection de staleness

Un workspace sans activité récente est flaggué. Le `cluster-monitor` d'Hermes
vérifie :

1. Dernière activité < 2h → workspace actif
2. 2h–6h → à surveiller
3. > 6h sans `[OK]` → probablement tombé silencieux

**Côté Hermes — le silence du canal lui-même est un signal.** Le cas limite du
multi-bot n'est pas le bot silencieux, c'est le **canal silencieux avec des bots
qui parlent dans le vide** : pendant l'incident d'août 2026 (~3,5 jours), le
backend MCP est resté mort derrière un handshake vivant, et les deux bots du
cluster ont posté « bus down » à chaque cycle — sans que personne ne lise ces
messages. Le correctif n'est pas technique mais organisationnel : la routine de
surveillance a un check dédié « **lecture des messages des bots** » (regex sur
`bus down|undelivered|fallback|write failed`), et l'attribution d'un post
manquant ne va jamais à l'hypothèse « prompt-skip » avant d'avoir écarté « bus
mort » (voir [AP13](09-Patterns-Anti-Patterns.md)).

**Côté NanoClaw — prouver sa présence, et démasquer le « faux vivant ».** Chaque conteneur
de session touche un fichier `/workspace/.heartbeat` ; l'hôte (un *sweep* toutes les 60 s)
lit la *mtime* et repère les conteneurs figés. Mais le vrai piège du multi-bot, c'est le
**faux vivant** : un conteneur dont le heartbeat est frais alors que la requête LLM, elle,
est gelée (un *stall* en mode push après un tour à texte vide — upstream Claude Code
#2177). Le heartbeat ment alors « je suis vivant » pendant que rien ne progresse. NanoClaw
a donc ajouté un second signal pour ce cas précis : `container_state.tool_started_at`, qui
a déjà révélé un appel d'outil MCP figé **onze heures**. La détection croise deux preuves —
*liveness* du process (heartbeat) **et** progression du raisonnement (âge du dernier outil
démarré) — et la récupération est un cycle propre : `Stop-Service` → `docker rm` → purge des
`processing_ack`/`container_state` non terminés → `Start-Service`. Au niveau coordination, le
cron du bot poste en plus son `[OK]` périodique sur le dashboard : la présence est prouvée
aux **deux** échelles, le process local et la flotte.

## Anti-bagarre (boucles de réponse)

Deux bots qui se répondent en boucle peuvent s'alimenter mutuellement et consumer
des tokens à l'infini. Trois gardes-fous :

1. **Pas de réponse automatique à `[INFO]`/`[FYI]`** — ces tags sont passifs par
   définition. Y répondre crée du bruit.
2. **Un `[ACK]` est terminal** — on accuse réception une fois, on n'enchaîne pas.
3. **Limite de profondeur** — au-delà de 3 échanges `[ASK]`/`[REPLY]` sans
   résolution, un humain est mentionné.

**Côté NanoClaw — le destinataire explicite coupe court à la boucle.** Un agent NanoClaw
n'« envoie » un message que s'il émet un bloc `<message to="...">` explicite. Un bloc sans
destinataire (`<internal>…</internal>`) est **silencieusement avalé** : l'hôte journalise
« no <message to> blocks — nothing was sent » et rien ne part. Heureuse conséquence pour
l'anti-bagarre : le défaut, c'est le **silence**, pas la réponse automatique — il faut une
intention explicite pour parler, donc pas d'auto-alimentation accidentelle. Le risque propre
à NanoClaw n'est d'ailleurs pas tant la boucle à deux que le **double-post après
compaction** : une session dont le contexte vient d'être résumé peut « oublier » qu'elle a
déjà répondu et re-poster. Le garde-fou est celui de toute la v2 — l'état durable vit **hors**
du contexte (SQLite, dashboard, git), et on le relit avant d'agir (voir
[AP6](09-Patterns-Anti-Patterns.md) dans le module patterns).

## Le rôle de l'humain

La coordination multi-bot n'est pas autonome : un **opérateur humain** reste le
dernier recours. Son rôle :

- **Trancher les frontières** (qui écrit quoi) — comme ai-01 l'a fait dans #4428
- **Dispatcher** les tâches quand le routing automatique échoue
- **Intervenir** sur les `[BLOCKED]` non résolus
- **Auditer** que la coordination fonctionne (via les rapports `[CLUSTER-HEALTH]`)

Le paradigme de Steinberger s'applique ici (voir
[00-Philosophie-Agentic-Engineering.md](00-Philosophie-Agentic-Engineering.md)) :
l'humain supervise une flotte plutôt que d'écrire lui-même. Mais superviser une
**flotte qui se parle** est plus exigeant que superviser des agents isolés — il faut
comprendre non seulement chaque agent, mais leurs interactions.

## Ce que la coordination apporte

| Sans coordination | Avec coordination |
|-------------------|-------------------|
| Travail dupliqué | `[CLAIMED]` évite les collisions |
| Silence indétectable | `[OK]` périodiques prouvent la présence |
| Boucles de tokens | Tags passifs + ACK terminal |
| Confusion sur qui fait quoi | Frontières tranchées + dashboards |
| Panne silencieuse | Détection staleness + `[ALERT]` |

## Synthèse — la coordination vue de NanoClaw

Là où Hermes apporte le rôle de coordinateur read-only, NanoClaw apporte la perspective
de l'**agent terminal** : ses garde-fous sont autant *architecturaux* (conteneurs isolés,
bases à un seul écrivain) que *conventionnels* (tags, mentions).

| Risque multi-bot | Réponse NanoClaw |
|------------------|------------------|
| **Collision** | Conteneur par session = isolation physique ; `on_wake` + `processing_ack` pour le hand-off interne |
| **Silence** | Heartbeat fichier + *sweep* 60 s **et** `tool_started_at` pour démasquer le « faux vivant » (#2177) |
| **Bagarre** | Envoi sur destinataire explicite (`<message to>`), `<internal>` avalé ; état durable hors-contexte contre le double-post |
| **Frontières** | `[ASK]`/`[ACK]`/`[REPLY]` sur `workspace-CoursIA`, tranchage humain par ai-01 (#4428) |

## Liens

- [Patterns & Anti-Patterns](09-Patterns-Anti-Patterns.md) — incidents concrets et leçons
- [Hermes Coordinateur de Cluster](07-Hermes-Cluster-Coordinator-Role.md) — rôle read-only
- [Architecture Hermes](05-Hermes-Architecture.md)
- [Architecture NanoClaw](02-NanoClaw-Architecture.md)
