Documentation

Tout ce qu'il faut pour faire tourner une flotte sur votre propre machine. Toutes les commandes ci-dessous sont reprises telles quelles du README du projet.

Python 3.14 redis-server cmux ≥ 0.64.25

Démarrage rapide

Prérequis

fleetmux pilote de vrais CLI et un Redis privé : c'est donc à la machine de les fournir.

Ce que la machine doit fournir.
QuoiMinimum ou remarque
macOS avec cmux cmux ≥ 0.64.25 (new-surface --command). Lancez start dans un terminal cmux, ou réglez le automation → socket control mode de cmux sur autre chose que cmuxOnly ; par défaut, cmux n'accepte que les commandes des processus qu'il a lui-même lancés.
Codex CLI (pour les agents Codex) ≥ 0.156 (profils par fichier, check_for_update_on_startup). Le daemon analyse les fichiers rollout .jsonl de Codex (token_count, task_complete, métadonnées des sous-agents) ; si une version de Codex change ce format, figez la version qui vous convient.
redis-server dans le PATH brew install redis. Chaque exécution démarre sa propre instance privée sous .runtime/redis/<token>/ du dépôt (socket, PID, journal et données) ; TCP est désactivé et elle ne touche jamais localhost:6379. Le binaire reste une dépendance externe, il n'est pas embarqué.
Python 3.14 + requirements.txt 3.14 est la version sur laquelle tourne la suite de tests ; deps.yaml liste chaque programme externe et sa version minimale.
Un modèle avec lequel Codex peut dialoguer L'API OpenAI, une connexion ChatGPT ou un serveur local compatible OpenAI. Le daemon lit la fenêtre de contexte dans le rollout, aucune taille de fenêtre n'est donc à configurer.

Les agents tournent dans un home Codex isolé (--codex-home, par défaut ~/.fleetmux/codex-home) : auth.json et le fichier de profil sont liés symboliquement depuis votre ~/.codex, la connexion est donc partagée, mais vos hooks, serveurs MCP, plugins et skills ne s'exécutent pas dans les agents (skills.include_instructions=false). Les entrées « faire confiance à ce dossier » de chaque agent y sont écrites automatiquement.

Installation

Une seule commande : fleetmux <subcommand> (par exemple fleetmux start, fleetmux status, fleetmux channel). Le binaire exécute lui-même ses sous-commandes de terminal (server, attach, reap, web, gui --runtime D) et transmet toutes les sous-commandes de flotte au CLI Python de la même version. Les installations faites sous l'ancien nom du projet continuent de fonctionner : sa commande, ses variables d'environnement et ses répertoires de versions restent acceptés comme alias.

terminal
git clone https://github.com/justindbca/fleetmux && cd fleetmux
pip install -e .

Ajoutez un profil Codex pour les agents. Codex ≥ 0.156 range chaque profil dans son propre fichier (voir codex.config.example.toml) :

~/.codex/fleet.config.toml
# ~/.codex/fleet.config.toml   (selected with `codex --profile fleet`)
model = "gpt-5.5"

Rien d'autre : la flotte crée son propre home Codex pour les agents et répond elle-même, pour chaque répertoire d'agent, à la question de Codex « faire confiance à ce dossier ? ».

Le code source, les versions et les tickets sont sur GitHub : github.com/justindbca/fleetmux.

Démarrer une flotte

Ouvrez un terminal cmux, configurez votre fournisseur et votre modèle Pi, et remplacez local/my-model ci-dessous. Les nouveaux démarrages exigent Redis v2 et une activation explicite du mode développement. Sans --supervisor-cli / --worker-cli, start prend la paire par défaut dans fleetmux/config/fleet.yaml#cli_pairs : un supervisor Pi sur openai-codex/gpt-6-astra (thinking high) avec des workers Claude Code officiels. Cette table liste chaque paire prise en charge et son statut ; une paire absente de la table est refusée. start exige aussi --lifecycle <mode> (service, dev_task, program, scheduled, review_loop, smoke) ; une paire unverified ne démarre qu'avec --lifecycle smoke.

terminal
fleetmux start --workers 2 --supervisor-cli pi --worker-cli pi --lifecycle <mode> \
  --pi-model local/my-model --allow-unsandboxed-pi --allow-development-v2
echo '{"goal": "Survey the three most-cited papers on X and write a comparison",
       "hours": 4,
       "artifacts": [{"path": "worker-1/report.md", "min_chars": 800, "contains": ["Conclusion"]}]}' \
  | fleetmux task -                           # hand the goal to the supervisor
fleetmux status                               # task + per-agent state, context usage, inbox depth
fleetmux stop                                 # cancel; confirm runtime retirement before Redis shutdown
  • fleetmux task - confie l'objectif au supervisor.
  • fleetmux status affiche la tâche, l'état de chaque agent, l'occupation du contexte et la profondeur de la boîte de réception.
  • fleetmux stop annule l'exécution et confirme le retrait du runtime avant l'arrêt de Redis.

Le JSON d'objectif déclare les livrables que le daemon vérifie : path, min_chars et les chaînes contains. Une tâche ne se termine que lorsque chaque livrable déclaré existe, est assez long et contient le texte requis ; trois fausses déclarations COMPLETED bloquent la tâche.

Deux formes d'exécution : task et service

Chaque start indique un --lifecycle ; il détermine ce que « terminé » veut dire et la façon dont l'exécution se clôt (fleetmux/config/fleet.yaml#lifecycle_modes). Les valeurs sont service, dev_task, program, scheduled, review_loop et smoke. Deux formes couvrent le premier jour.

Une exécution de type task

Vous confiez un seul objectif et l'exécution se termine. task place l'objectif dans la boîte de réception du supervisor ; le daemon le colle dans l'onglet du supervisor ; le supervisor répond par un seul objet JSON (status, completion, reason, dispatch, artifacts) ; chaque envoi part dans la boîte de réception du worker concerné et est remis quand ce worker est libre ; la réponse du worker revient marquée [worker-1 report]. COMPLETED est contrôlé au regard des livrables déclarés et un échec revient sous la forme « Independent verification failed » ; trois renvois bloquent la tâche ; BLOCKED arrête la tâche et laisse la raison dans status pour une personne. Trois JSON invalides d'affilée bloquent la tâche.

Une exécution de type service

--lifecycle service maintient la flotte en marche sans objectif ni fin : elle répond à un flux de requêtes. Le supervisor ne fait qu'aiguiller ; chaque worker peut avoir son propre prompt ; les réponses sont des JSON contrôlés par le daemon, pas par un modèle.

mode service
fleetmux start --lifecycle service --worker-names exit-1,reentry-1,rung-1 --roles ~/my-roles \
    --supervisor-cli pi --worker-cli pi --pi-model local/my-model \
    --allow-unsandboxed-pi --allow-development-v2 --max-busy 2
echo '{"id": "cu-1:r7", "to": "exit-1", "payload": {...},
       "reply": {"required": ["verdict", "param", "evidence"], "allowed": ["verdict", "param", "evidence", "note"]}}' \
  | fleetmux request -
fleetmux results --drain        # one JSON line per finished request
fleetmux pause --reason "market closed"; fleetmux resume

~/my-roles est le répertoire de rôles d'exemple du README ; indiquez le vôtre.

  • --worker-names : des emplacements worker nommés par rôle (minuscules, sans points ; les points marquent les générations). Chaque nom se termine par -<number> (proto-1, pas proto).
  • --roles DIR : DIR/<agent>.md est ajouté à l'AGENTS.md de cet agent comme « Your role ». Un prompt par worker.
  • --max-busy N : au plus N workers travaillent en même temps ; le supervisor n'est pas compté.
  • Une requête : id (usage unique), payload (n'importe quel JSON, ≤ 200 000 caractères), reply.required, reply.allowed en option, to en option (aiguille directement vers ce worker, sans tour du supervisor), instruction en option, timeout_s en option. La réponse doit être un seul objet JSON ; une mauvaise réponse a droit à une correction ; une deuxième mauvaise réponse est publiée comme invalid. Statuts de résultat : ok, invalid, skipped, timeout, stalled.
  • pause arrête la distribution et la rotation ; les requêtes continuent de s'accumuler. Les calendriers (heures de marché, jours fériés) regardent l'appelant : pause et resume depuis l'extérieur.

Les règles de nommage et l'ajout de workers sans redémarrage sont dans Roster YAML.

Roster YAML (ajout / retrait à chaud)

start --roster roster.yaml (au lieu de --workers / --worker-names) prend les workers dans un fichier, et le daemon le relit pendant que la flotte tourne.

roster.yaml
workers:
  - name: exit-1            # same rules as --worker-names (ends with -<number>)
    role: roles/exit.md     # optional, relative to the yaml; overrides --roles DIR/exit-1.md
  - name: reentry-1
  • Ajout : un nouveau nom est écrit dans config.json (et latest.json s'il pointe vers cette exécution), puis lancé avec le prompt READY comme onglet à la fin de la colonne des workers. Le supervisor voit la nouvelle liste ajoutée à son message suivant ([Roster change] Workers you can dispatch to now: ...).
  • Retrait : le worker n'accepte plus de nouveau travail (tout envoi vers lui est refusé), termine ce qu'il a déjà, et une fois inactif avec une file vide son écran passe dans la page d'historique et son onglet est fermé. Son répertoire de travail reste. Le remettre dans le fichier avant cela annule le retrait.
  • Changement de rôle d'un worker existant : il s'applique à sa génération suivante ; le worker n'est pas redémarré.
  • Le supervisor le modifie aussi : chaque prompt du supervisor contient une section « Adding and retiring workers (the roster) » qui nomme le fichier de roster de cette exécution. Il ajoute une ligne - {name: <role>-<n>, role: <path>} quand il y a plus de travail prêt que de workers libres, en supprime une quand un worker n'a plus de travail, dans les limites de son brief de tâche.
  • Rejeté en bloc (roster conservé, raisons dans l'onglet daemon sous roster.rejected) : YAML invalide, clés inconnues, noms invalides ou en double, aucun worker, fichier de rôle manquant, ou nom déjà utilisé dans cette exécution. Les changements sont journalisés sous roster.changed.
  • Seuil de passation par modèle : table handoff: facultative et window / handoff_ratio par worker ; les modifications s'appliquent au prochain contrôle de livraison, sans redémarrage.

Nécessite PyYAML dans le Python qui exécute le daemon.

Reprendre une exécution interrompue

fleetmux start --resume <run-dir> rattache une exécution Redis v2 inachevée au lieu d'en créer une ; --resume auto prend l'exécution indiquée dans ~/.fleetmux/latest.json et démarre une nouvelle exécution quand celle-là est terminée (<run>/result.json existe) ou absente. Un daemon actif rend l'opération sans effet.

terminal
fleetmux start --resume <run-dir>
  • Redis redémarre depuis le répertoire et le socket de l'exécution ; l'AOF ramène tout jusqu'au dernier fsync (environ 1 s). Un Redis encore actif est réutilisé.
  • Les workers Claude officiels dont l'onglet fait encore tourner le CLI sont rattachés ; tous les autres agents (le supervisor de la tâche, les agents Pi, les onglets fermés) repartent à leur génération suivante (worker-1 -> worker-1.1) sur place.
  • Tâche : les livrables vérifiés et les rapports acceptés sont conservés ; les envois qui étaient en file ou en vol sont remis en file par le daemon, et le nouveau supervisor reçoit un résumé (objectif, livrables vérifiés/manquants, envois terminés avec extraits de rapport, envois remis en file). Service : les requêtes acceptées sans résultat sont aiguillées de nouveau ; une requête avec "replay": false est publiée comme timeout.
  • --max-attempts N (2 par défaut, stocké dans la config, modifiable à la reprise) : un envoi / une requête s'exécute au plus N fois au total ; au-delà du plafond la tâche passe à blocked / la requête à timeout, en nommant l'élément.
  • Une tâche terminée (tout statut terminal) ou un stop explicite écrit <run>/result.json ; une telle exécution n'est jamais reprise. Sans --resume, un daemon redémarré conserve le travail incertain et se met en pause (aucun replay).

Fenêtre et ratio de passation par modèle

La session est renouvelée quand used >= ratio × window ; --handoff-ratio vaut 0.6 par défaut, et chaque agent résout window et ratio séparément, la première correspondance l'emportant.

handoff
handoff:
  claude:opus: {window: 1000000}
  claude: {window: 200000, ratio: 0.6}
  codex: {ratio: 0.6}

Backend de terminal : trois choix

fleet parle au terminal via un seul backend interchangeable. start --backend cmux|wezterm|fleetmux le choisit, et ce choix est écrit dans le config.json de l'exécution : le daemon, les acteurs, start --resume et chaque sous-flotte suivent cette valeur, et une exécution démarrée avant l'existence de cette option reste sur cmux. Sans l'option, macOS ne change pas — cmux quand cmux est installé, WezTerm sur un Mac sans cmux — et Linux (WSL2 compris) utilise fleetmux par défaut depuis M3 ; --backend wezterm (ou --backend cmux) sélectionne toujours explicitement les autres backends. Un backend choisi explicitement qui ne peut pas tourner sur la machine est refusé avant tout provisionnement, en nommant ce qui manque.

Les trois backends.
BackendPar défaut surDispositionNécessite
cmux macOS, quand cmux est installé Supervisor en haut à gauche, navigateur d'historique en dessous, workers en onglets à droite. cmux ≥ 0.64.25
wezterm Linux / WSL2 quand il est choisi explicitement ; par défaut sur un Mac sans cmux Une fenêtre, onglet 1 avec le supervisor à gauche et daemon en dessous, un onglet par worker ; pas de surface de navigateur ni d'onglets dans un split, la page d'historique n'est donc pas intégrée — fleet affiche <run>/history.html dans daemon et l'ouvre avec open / xdg-open quand un bureau est disponible. wezterm et wezterm-mux-server ≥ 20240203 dans le PATH ; chaque exécution démarre son propre serveur mux privé, jamais votre WezTerm.
fleetmux Linux (WSL2 compris) depuis M3 ; sur macOS seulement avec --backend fleetmux le terminal à disposition fixe de fleet, écrit en Rust (rust/, MIT) ; un fleetmux server privé possède le PTY et l'écran de chaque agent, et vous le regardez avec fleetmux attach — ouvrez-le et fermez-le quand vous voulez, plusieurs à la fois, même par ssh ; fermer une vue n'arrête jamais un agent. fleetmux dans le PATH (ou $FLEETMUX_BIN) ; fleetmux --version doit afficher fleetmux <x.y.z> (protocol 1) avec une version au moins égale au minimum de deps.yaml.
wezterm
fleetmux start --backend wezterm --workers 2 --supervisor-cli pi --worker-cli pi \
  --lifecycle <mode> --pi-model local/my-model --allow-unsandboxed-pi --allow-development-v2
fleetmux backend
fleetmux start --backend fleetmux --workers 2 ...    # prints: Watch it with: fleetmux attach --runtime <dir>
fleetmux start --backend fleetmux --attach ...       # same, then becomes that `fleetmux attach`
fleetmux attach --runtime <dir> [--no-mouse]           # from any terminal, as many times as you like

Compilez le backend fleetmux depuis ce dépôt (cd rust && cargo install --path crates/app), ou lancez scripts/install_fleetmux.sh [--dest DIR] pour installer le binaire précompilé figé par deps.yaml.

Limites connues

  • Sur macOS, le backend par défaut est cmux (WezTerm sans cmux) ; sur Linux, c'est le terminal propre à fleetmux. Sur macOS, le daemon utilise caffeinate quand il est disponible pour que la machine ne se mette pas en veille.
  • Un nouvel espace de travail cmux vole brièvement le focus à sa création (cmux ignore --focus false pour new-workspace) ; le daemon vous remet dessus.
  • Codex ne tient pas de journal en flux d'un onglet ; quand un onglet est retiré, son écran final est enregistré à côté de la note de passation. La transcription complète est le fichier rollout de Codex sous ~/.codex/sessions.
  • Il arrive qu'un modèle fasse plus d'une étape dispatchée en un tour. Le contrôle des livrables attrape le résultat, pas le débordement.