contenu
Introduction
Claude Code (v2.1.47) intègre une fonctionnalité expérimentale appelée Agent Teams : plusieurs sessions Claude Code se coordonnent sur un travail commun selon une topologie avec un responsable et des coéquipiers. Je développe Hive, un orchestrateur multi-agent pour la programmation aux objectifs similaires, mais à l’architecture très différente. Je voulais donc comprendre comment fonctionne l’approche d’Anthropic sous le capot.
Cet article présente ce que j’ai découvert grâce à :
- La lecture de la documentation officielle
- L’examen des artefacts réels laissés sur le disque par de précédentes sessions en équipe
- Une analyse du binaire de Claude Code (v2.1.47) que j’ai confiée à Claude pour obtenir des détails d’implémentation (ha !)
Sommaire
- 1. Vue d’ensemble de l’architecture
- 2. La liste de tâches partagée
- 3. Communication entre agents
- 4. Lancement et cycle de vie des agents
- 5. Contrôles qualité et hooks
- 6. Coût des tokens
- 7. Synthèse de l’architecture
- Sources
1. Vue d’ensemble de l’architecture
Une équipe d’agents se compose de quatre éléments :
| Composant | Rôle |
|---|---|
| Responsable d’équipe | La session principale de Claude Code, qui crée l’équipe et lance les coéquipiers |
| Coéquipiers | Des instances distinctes de Claude Code, chacune avec sa propre fenêtre de contexte |
| Liste de tâches | Des éléments de travail partagés, stockés dans des fichiers JSON individuels sur le disque |
| Boîte aux lettres | Des fichiers de boîte de réception propres à chaque agent pour acheminer les messages |
Toute la couche de coordination est fondée sur des fichiers. Le système de fichiers à l’emplacement ~/.claude/ est l’unique support de coordination :
~/.claude/
├── teams/{team-name}/
│ ├── config.json # team membership registry
│ └── inboxes/{agent-name}.json # per-agent mailbox
└── tasks/{team-name}/
├── .lock # flock() for concurrent task claiming
├── .highwatermark # auto-increment counter
├── 1.json # individual task files
├── 2.json
└── ...
Cette conception est fondamentalement décentralisée. Le responsable est simplement une autre session Claude dotée d’outils supplémentaires (TeamCreate, TeamDelete, SendMessage). Il n’y a aucun processus en arrière-plan. La coordination découle de l’accès partagé aux fichiers.
Dans une session active, si vous demandez à Claude de lancer une équipe pour accomplir une tâche, puis exécutez ce qui suit dans une autre fenêtre, vous pouvez observer les modifications du système de fichiers en temps réel.
watch -n 0.5 'tree ~/.claude/teams/ 2>/dev/null; echo "---"; tree ~/.claude/tasks/ 2>/dev/null'
Par exemple, avec le prompt suivant :
can you spawn an agent team to examine this codebase?
- have one look for bugs
- have one look for complexity
- have one look for good things to call out and play devil's advocate against the other two agents
J’ai observé ceci :
teams
└── code-review
├── config.json
└── inboxes
├── bug-hunter.json
├── complexity-analyst.json
├── devils-advocate.json
└── team-lead.json
Configuration de l’équipe
La configuration de l’équipe à l’emplacement ~/.claude/teams/{team-name}/config.json contient un tableau members que les coéquipiers lisent pour découvrir les autres membres :
{
"members": [
{ "name": "team-lead", "agentId": "abc-123", "agentType": "leader" },
{
"name": "researcher",
"agentId": "def-456",
"agentType": "general-purpose"
}
]
}
Les noms constituent le principal mécanisme d’adressage (les UUID existent, mais ne sont pas utilisés pour le routage). Tous les échanges de messages et toutes les attributions de tâches utilisent le champ name.
2. La liste de tâches partagée
Format des fichiers
Chaque tâche est stockée dans un fichier JSON individuel dans ~/.claude/tasks/{team-name}/. Voici un exemple réel tiré d’une session précédente :
{
"id": "1",
"subject": "Hunt for bugs across the codebase",
"description": "...",
"activeForm": "Hunting for bugs",
"owner": "bug-hunter",
"status": "completed",
"blocks": [],
"blockedBy": []
}
Schéma des tâches :
| Champ | Type | Description |
|---|---|---|
id |
string | Identifiant numérique, incrémenté automatiquement via .highwatermark |
subject |
string | Titre à l’impératif (p. ex., "Exécutez les tests") |
description |
string | Exigences détaillées et critères d’acceptation |
activeForm |
string | Formulation au présent progressif pour l’indicateur d’activité ("Tests en cours d’exécution") |
status |
string | pending → in_progress → completed (ou deleted) |
blocks |
string[] | Identifiants des tâches que cette tâche bloque |
blockedBy |
string[] | Identifiants des tâches qui doivent être terminées avant que cette tâche puisse commencer |
Gestion de la concurrence
Deux fichiers spéciaux assurent la coordination :
.lock: un fichier de 0 octet utilisé pour assurer l’exclusion mutuelle au niveau du système de fichiers (flock()). Présent dans les 42 répertoires de tâches observés sur ma machine..highwatermark: contient un seul entier (p. ex.,"3","13"). Il s’agit du prochain identifiant de tâche disponible pour l’incrémentation automatique.
Prise en charge des tâches
La prise en charge des tâches utilise le verrouillage de fichiers pour éviter les conditions de concurrence. Les coéquipiers privilégient les tâches dont l’identifiant est le plus petit. Une tâche dont le tableau blockedBy n’est pas vide ne peut pas être prise en charge tant que toutes les tâches bloquantes n’ont pas atteint un état final.
Observation : la plupart des répertoires de tâches sont vides
Sur les 42 répertoires de tâches présents sur ma machine, seuls 5 contenaient effectivement des fichiers JSON de tâches. Les 37 autres ne contenaient que .lock et .highwatermark. Cela signifie probablement que les tâches sont supprimées une fois terminées, ou qu’il s’agissait de sessions où Claude utilisait la liste de tâches interne (disponible depuis le lancement de la fonctionnalité de liste de tâches), sans décomposition en fichiers de sous-tâches.
3. Communication entre agents
Modèle de boîte aux lettres
Chaque agent dispose d’un fichier contenant un tableau JSON à l’emplacement ~/.claude/teams/{team-name}/inboxes/{agent-name}.json. Voici une véritable boîte de réception issue d’une session précédente, où un responsable d’équipe avait confié du travail à un controlplane-agent :
[
{
"from": "team-lead",
"text": "{\"type\":\"task_assignment\",\"taskId\":\"1\",\"subject\":\"Phase 2: Control-plane - remove participants/presence\",\"description\":\"Remove multiplayer code from the control-plane package...\",\"assignedBy\":\"team-lead\",\"timestamp\":\"2026-02-18T02:37:16.890Z\"}",
"timestamp": "2026-02-18T02:37:16.890Z",
"read": false
}
]
Remarquez l’encodage JSON dans du JSON : le champ text est une chaîne JSON contenant un objet de message sérialisé. L’enveloppe externe comporte les champs from, text, timestamp et read.
Types de messages
Le champ type dans la charge utile text prend en charge les types suivants :
| Type | Sens | Fonction |
|---|---|---|
task_assignment |
responsable → coéquipier | Attribuer une tâche avec tous ses détails |
message |
n’importe quel agent → n’importe quel agent | Envoyer un message direct à un destinataire |
broadcast |
responsable → tous | Envoyer le même message à chaque coéquipier |
shutdown_request |
responsable → coéquipier | Demander un arrêt propre |
shutdown_response |
coéquipier → responsable | Accepter ou refuser l’arrêt |
plan_approval_request |
coéquipier → responsable | Soumettre un plan pour examen |
plan_approval_response |
responsable → coéquipier | Approuver ou refuser avec des commentaires |
idle_notification |
coéquipier → responsable | Envoyé automatiquement à la fin du tour du coéquipier |
Mécanisme de remise des messages
Chemin d’écriture : l’expéditeur ajoute une entrée au fichier contenant le tableau JSON de la boîte de réception du destinataire.
Chemin de lecture : le destinataire interroge régulièrement son propre fichier de boîte de réception. Les nouveaux messages sont injectés sous forme de tours de conversation synthétiques (ils apparaissent comme s’ils avaient été envoyés par un utilisateur).
Diffusion : écrit littéralement le même message dans le fichier de boîte de réception de chaque coéquipier. Le coût en tokens augmente linéairement avec la taille de l’équipe.
La communication se résume à un ajout dans un fichier + une lecture de fichier. La latence entre l’envoi et la réception dépend de l’intervalle d’interrogation du destinataire.
Visibilité des messages privés entre coéquipiers
Lorsqu’un coéquipier envoie un message privé à un autre coéquipier, un bref résumé est inclus dans la notification d’inactivité adressée au responsable. Cela permet au responsable de suivre la collaboration entre pairs sans recevoir le contenu intégral du message.
4. Lancement et cycle de vie des agents
Création des coéquipiers
Chaque coéquipier est un processus en ligne de commande claude distinct. Le responsable les lance avec l’outil Task et les paramètres team_name et name. Des variables d’environnement sont définies pour le processus lancé :
CLAUDE_CODE_TEAM_NAME: définie automatiquement pour les coéquipiers lancésCLAUDE_CODE_PLAN_MODE_REQUIRED: définie àtruesi l’approbation du plan est requise
Initialisation du contexte
Les coéquipiers chargent le même contexte de projet que toute nouvelle session :
- Les fichiers
CLAUDE.mddu répertoire de travail - Les serveurs MCP
- Les skills
- Le prompt de lancement fourni par le responsable
L’historique de conversation du responsable n’est PAS transmis. Chaque coéquipier démarre de zéro, avec pour seul contexte le prompt de lancement.
Implémentation interne
D’après l’analyse du binaire de Claude Code v2.1.47, le contexte des coéquipiers est géré via AsyncLocalStorage avec les champs suivants :
agentId,agentName,teamNameparentSessionId,colorplanModeRequired
Principales fonctions internes :
isTeammate()/isTeamLead(): détection du rôlewaitForTeammatesToBecomeIdle(): primitive de synchronisation pour le responsablegetTeammateContext()/setDynamicTeamContext(): gestion du contexte d’exécution
Détection de l’inactivité
Après chaque tour du LLM, un coéquipier passe automatiquement à l’état inactif et envoie un idle_notification au responsable. Il s’agit de son état de repos normal, plutôt que d’une erreur ou d’un état périmé. L’envoi d’un message à un coéquipier inactif le réveille (le cycle d’interrogation suivant récupère le message de sa boîte de réception).
Protocole d’arrêt
- Le responsable envoie
shutdown_requestà un coéquipier - Le coéquipier peut accepter (il s’arrête proprement) ou refuser (il continue à travailler en fournissant une explication)
- Le nettoyage de l’équipe via
TeamDeletesupprime~/.claude/teams/{team-name}/et~/.claude/tasks/{team-name}/ - Le nettoyage échoue si des coéquipiers sont encore actifs ; ils doivent d’abord être arrêtés
Héritage des autorisations
Les coéquipiers héritent du mode d’autorisation du responsable au moment de leur lancement. Si le responsable utilise --dangerously-skip-permissions, tous les coéquipiers l’utilisent aussi. Les modes individuels peuvent être modifiés après le lancement, mais ne peuvent pas être configurés séparément pour chaque coéquipier au moment du lancement.
5. Contrôles qualité et hooks
Agent Teams s’intègre au système de hooks de Claude Code pour faire respecter les exigences de qualité :
Hook TeammateIdle
Se déclenche lorsqu’un coéquipier s’apprête à devenir inactif. Le code de sortie 2 transmet le contenu de stderr en guise de retour et empêche le passage à l’état inactif, ce qui maintient le coéquipier au travail.
{
"hook_event_name": "TeammateIdle",
"teammate_name": "researcher",
"team_name": "my-project"
}
Hook TaskCompleted
Se déclenche lorsqu’une tâche est sur le point d’être marquée comme terminée. Le code de sortie 2 empêche cette validation et renvoie le contenu de stderr en guise de retour.
{
"hook_event_name": "TaskCompleted",
"task_id": "task-001",
"task_subject": "Implement user authentication",
"task_description": "Add login and signup endpoints",
"teammate_name": "implementer",
"team_name": "my-project"
}
Ce hook se déclenche dans deux situations : (1) lorsqu’un agent marque explicitement une tâche comme terminée via TaskUpdate, ou (2) lorsqu’un coéquipier d’une équipe d’agents termine son tour avec des tâches en cours.
Types de gestionnaires de hooks
| Type | Description |
|---|---|
command |
Script shell. JSON sur stdin, codes de sortie pour les décisions. |
prompt |
Évaluation par un LLM en un seul tour. Renvoie {ok, reason}. |
agent |
Sous-agent sur plusieurs tours avec des outils de lecture. Jusqu’à 50 tours. |
6. Coût des tokens
Les équipes d’agents utilisent environ 7 fois plus de tokens que les sessions standard lorsque les coéquipiers fonctionnent en mode planification. Chaque coéquipier conserve sa propre fenêtre de contexte complète en tant qu’instance Claude distincte.
Valeurs de référence
- Utilisation moyenne de Claude Code : environ 6 $/développeur/jour
- Équipes d’agents : surcoût approximativement proportionnel à la taille de l’équipe, en plus du coût de base
7. Synthèse de l’architecture
| Dimension | Claude Code Agent Teams |
|---|---|
| Support de coordination | Fichiers plats (~/.claude/tasks/, ~/.claude/teams/) |
| Format des tâches | Un fichier JSON par tâche + .lock pour la prise en charge |
| Messagerie | Fichiers de boîte de réception JSON (ajout + interrogation périodique) |
| Cycle de vie des agents | Processus en ligne de commande qui gèrent leur propre cycle de vie |
| Isolation du travail | Répertoire de travail partagé |
| Stratégie de fusion | Aucune (les agents modifient directement les fichiers) |
| Nouvelle tentative/escalade | Manuelle (le responsable décide, ou l’utilisateur intervient) |
| Topologie | Responsable + pairs sur un même niveau, messagerie entre pairs |
| Ordonnancement | Prise en charge autonome (les coéquipiers prennent la tâche suivante) |
| Persistance de l’état | Fichiers uniquement ; pas de reprise des coéquipiers au sein du processus |
| Contrôles qualité | Hooks shell (TeammateIdle, TaskCompleted) |
| Suivi des tokens | Par session uniquement, sans agrégation entre agents |
| Détection des blocages | Manuelle (l’utilisateur remarque qu’un coéquipier s’est arrêté) |
| Gestion de la concurrence | Implicite (taille de l’équipe = nombre de coéquipiers) |
| Modèle de dépendances | blocks/blockedBy dans les fichiers de tâches |
Sources
Documentation officielle
- Équipes de sessions Claude Code : architecture
- Mode interactif — Liste de tâches
- Équipes d’agents — Attribuer et prendre en charge des tâches
- Équipes d’agents — Contexte et communication
- Équipes d’agents — Arrêter les coéquipiers
- Équipes d’agents — Nettoyer l’équipe
- Équipes d’agents — Autorisations
- Équipes d’agents — Limites
- Équipes d’agents — Bonnes pratiques
- Hooks
- Coûts — Coûts en tokens des équipes d’agents
- Paramètres
- Sous-agents
Artefacts sur disque (Claude Code v2.1.47)
Observés à l’emplacement /Users/tau/.claude/ :
- Répertoires d’équipes contenant des fichiers
config.jsonetinboxes/{agent-name}.json - Répertoires de tâches contenant
.lock,.highwatermarket des fichiers JSON de tâches individuels - Exemple de message d’attribution de tâche de
team-leadàcp-agent, horodaté2026-02-18T02:37:16.890Z
Analyse du binaire
Binaire de Claude Code v2.1.47. Fonctions internes identifiées par analyse des chaînes de caractères : getTeamName, getAgentName, getAgentId, isTeammate, isTeamLead, waitForTeammatesToBecomeIdle, getTeammateContext, setDynamicTeamContext, createTeammateContext. Champs du contexte AsyncLocalStorage : agentId, agentName, teamName, parentSessionId, color, planModeRequired.