Gepeto : Orchestrer des Agents Claude Code avec Tmux et Python

Gepeto : Orchestrer des Agents Claude Code avec Tmux et Python

Gepeto : Orchestrer des Agents Claude Code avec Tmux et Python

Combien de temps votre équipe perd-elle à jongler entre des tâches de code répétitives, des revues interminables et des pipelines de qualité à surveiller manuellement ? Selon GitHub, les développeurs passent en moyenne 45 % de leur temps sur des tâches qui pourraient être automatisées. C’est précisément ce constat qui nous a amenés à construire Gepeto — un orchestrateur open-source pour piloter plusieurs agents Claude Code en parallèle depuis une interface TUI Python.


L’Architecture Hub-and-Spoke : Un Cerveau, Des Bras

Le principe central de Gepeto est une architecture en étoile : un orchestrateur central (le hub) distribue les tâches à des agents enfants, chacun isolé dans sa propre session tmux. Les agents ne se parlent jamais directement — toute communication transite par l’orchestrateur.

           ┌──────────────┐
           │ Orchestrateur│  (session tmux gepeto-dashboard)
           │   (hub)      │
           └──┬───┬───┬───┘
              │   │   │
              ▼   ▼   ▼
           agent agent agent
        (tmux sessions isolées)

Chaque projet enfant reçoit :

  • Une session tmux dédiée
  • Un fichier .mcp.json restreint (filesystem limité au dossier projet, sans accès à /opt/workspace/gepeto/ ni aux répertoires home)
  • Un .claude/settings.json avec des règles deny explicites : gepeto/**, rm -rf, sudo, kill, git push, git reset --hard

Cette isolation garantit qu’un agent buggé ou mal configuré ne peut pas casser l’environnement parent. Les ports applicatifs sont alloués dynamiquement par blocs de 10 à partir de 7450, référencés dans registry.json. L’API centrale tourne sur le port 7499 (GEPETO_PORT), et le système supporte jusqu’à 20 agents simultanés (MAX_AGENTS = 20).


PatternDetector : Savoir Si Claude Travaille par Hash MD5

Le premier défi technique a été de détecter l’état d’un agent Claude Code sans modifier Claude lui-même. Notre solution : lire la sortie brute de tmux capture-pane et la hacher.

class PatternDetector:
    IDLE_AFTER_S = 3.0  # secondes sans changement → IDLE

    _WORKING_RE = re.compile(
        r"[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]|Running...",
        re.IGNORECASE
    )

    def analyze(self, terminal_text: str) -> DetectorResult:
        clean = _ANSI_RE.sub("", terminal_text)
        normalized = " ".join(clean.split())
        current_hash = hashlib.md5(
            normalized.encode(), usedforsecurity=False
        ).hexdigest()

        if current_hash != self._last_hash:
            self._last_hash = current_hash
            self._last_change_time = time.time()

        # Spinner braille actif → outil en cours d'exécution
        if self._WORKING_RE.search(terminal_text[-30:]):
            return DetectorResult(action=Action.WAIT,
                                  state="WORKING",
                                  reason="Tool en cours")

        idle_duration = time.time() - self._last_change_time
        if idle_duration >= self.IDLE_AFTER_S:
            return DetectorResult(action=Action.WAIT,
                                  state="IDLE",
                                  reason=f"Aucun changement depuis {idle_duration:.1f}s")

        return DetectorResult(action=Action.WAIT, state="WORKING",
                              reason="Terminal actif")

L’astuce clé : normaliser les espaces avant de hacher (" ".join(clean.split())). Un resize tmux peut modifier les sauts de ligne sans changer le contenu réel — sans cette normalisation, le detector signalerait faussement un changement à chaque redimensionnement de fenêtre.

Les spinners braille (⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏) indiquent que Claude exécute un outil. Quand ils apparaissent, le terminal peut être “gelé” du point de vue du hash, mais l’agent travaille effectivement — le regex _WORKING_RE capture ce cas.


Auto-Approve : 8 Règles pour Zéro Intervention Manuelle

Claude Code demande régulièrement des confirmations : “Voulez-vous lire ce fichier ?”, “Trust MCP tools ?”, “Y/n ?“. En mode production avec plusieurs agents, répondre manuellement à chacune est impossible. Gepeto embarque 8 règles d’auto-approve :

_AUTO_APPROVE_RULES: list[tuple[re.Pattern, str, str]] = [
    (re.compile(r"❯s*d+.s*(Yes|Oui)", re.MULTILINE), "Enter",
     "Menu TUI — accepter option par défaut"),
    (re.compile(r"[Y/n]", re.IGNORECASE), "y",
     "Confirmation Y/n"),
    (re.compile(
        r"Allow .* to (read|write|edit|create|execute|run|access|list|delete)",
        re.IGNORECASE), "Enter",
     "Permission lecture/écriture"),
    (re.compile(r"Trust all tools from MCP", re.IGNORECASE), "Enter",
     "Trust MCP tools"),
    (re.compile(r"Resume Session (d+ of d+)", re.IGNORECASE), "Enter",
     "Resume Session picker — sélectionner la première"),
    # ... 3 autres règles
]

Chaque règle est un triplet (pattern, touche à envoyer, raison). Le detector scanne les 30 dernières lignes du terminal — suffisant pour capturer tout dialogue interactif sans traiter l’intégralité du buffer. Quand une règle correspond, l’action SEND est retournée avec la touche appropriée, et le watcher l’injecte via tmux send-keys.


JsonlDetector : Lire les Vrais Événements Structurés

La détection par hash MD5 fonctionne, mais elle est aveugle au contenu. Pour savoir ce que fait Claude — quel fichier il lit, quel outil il appelle, combien de tokens il a consommés — nous avons développé JsonlDetector.

Claude Code écrit en continu un transcript JSONL dans ~/.claude/projects/. Notre detector le suit en temps réel :

@dataclass
class ActivityInfo:
    tool_name: str = ""
    tool_input: str = ""    # résumé tronqué 80 chars
    file_path: str = ""     # chemin extrait si Read/Edit/Write

    def __str__(self) -> str:
        if self.file_path:
            return f"{self.tool_name} {Path(self.file_path).name}"
        return self.tool_input or self.tool_name


@dataclass
class TokenUsage:
    input_tokens: int = 0
    output_tokens: int = 0
    cache_creation_tokens: int = 0
    cache_read_tokens: int = 0

    @property
    def cost_usd(self) -> float:
        # $3/M input, $15/M output, $3.75/M cache creation, $0.30/M cache read
        return (
            self.input_tokens * 3.0 / 1_000_000
            + self.output_tokens * 15.0 / 1_000_000
            + self.cache_creation_tokens * 3.75 / 1_000_000
            + self.cache_read_tokens * 0.30 / 1_000_000
        )

Les outils Task, Agent et AskUserQuestion sont exclus du calcul d’activité (_EXEMPT_TOOLS) car ils correspondent à de l’attente, pas à du travail effectif. Un outil sans tool_result depuis plus de 300 secondes est considéré comme un zombie et purgé (TOOL_TIMEOUT_S = 300.0).


Trois Modes de Fonctionnement

Chaque projet peut fonctionner dans l’un des trois modes stockés dans registry.json :

ModeSur IDLESur ASKQui pilote
manualRienDashboard afficheUtilisateur
orchestratedNotifie gepeto_mainNotifie gepeto_mainOrchestrateur Claude
autoEnchaîne next taskBloque + afficheTask tracker

Le mode orchestrated est le plus puissant : quand un agent termine une tâche (IDLE détecté), le watcher envoie automatiquement une notification à la session tmux orchestrateur, qui peut alors décider de la suite. Les agents ne se connaissent pas — ils travaillent chacun dans leur coin pendant que l’orchestrateur coordonne.


Dashboard Textual : Tout Contrôler depuis un Terminal

L’interface TUI est construite avec Textual, le framework Python pour terminaux riches. Elle affiche en temps réel l’état de tous les agents (WORKING / IDLE / ASK / DEAD), l’activité courante extraite du JSONL (Read watcher.py, Edit constants.py…), et le coût cumulé en dollars.

Les raccourcis principaux :

  • j/k : naviguer entre les projets
  • s : envoyer un message à l’agent sélectionné
  • n : créer un nouveau projet
  • o : toggle orchestrateur
  • p : toggle watcher
  • l : logs du watcher en live

L’API REST (port 7499) expose ces mêmes données en JSON pour intégrer Gepeto dans des dashboards externes ou des scripts de monitoring :

curl -s http://127.0.0.1:7499/api/v1/summary
# {"total":3,"working":2,"idle":1,"cost_usd":40.54,"top_agent":"demo-agent-2"}

Ce Que Gepeto Nous a Appris

Construire un orchestrateur d’agents révèle des problèmes qu’on ne voit pas avec un seul agent :

  1. La détection d’état est non-triviale : un terminal tmux “stable” ne signifie pas que Claude est inactif — il peut attendre une réponse réseau.
  2. L’isolation est indispensable : sans .mcp.json restreint, un agent peut modifier les fichiers d’un autre ou consommer des ressources système critiques.
  3. Le coût est visible : le suivi USD par agent change radicalement les décisions de conception — on évite les appels LLM inutiles quand on voit $0.30 disparaître à chaque exécution.

Gepeto est aujourd’hui utilisé en production pour orchestrer des pipelines de qualité (lint + tests + review), des agents de génération de contenu, et des workers d’annotation de datasets.


Conclusion

L’orchestration d’agents IA n’est plus réservée aux grandes entreprises avec des équipes MLOps dédiées. Avec des outils comme tmux, Python et Textual, il est possible de construire un système robuste qui pilote plusieurs instances Claude Code en parallèle, avec isolation, monitoring en temps réel et calcul de coût automatique.

Vous souhaitez mettre en place une infrastructure similaire pour votre équipe ? Contactez-nous sur contact@brio-novia.eu — nous aidons les entreprises à concevoir et déployer des architectures multi-agents adaptées à leurs besoins réels.