Documentación

Lo necesario para ejecutar una flota en tu propia máquina. Cada comando de abajo está copiado sin cambios del README del proyecto.

Python 3.14 redis-server cmux ≥ 0.64.25

Inicio rápido

Requisitos

fleetmux maneja CLI reales y un Redis privado, así que la máquina tiene que proporcionarlos.

Lo que tiene que aportar la máquina.
QuéMínimo o nota
macOS con cmux cmux ≥ 0.64.25 (new-surface --command). Ejecuta start dentro de un terminal cmux, o cambia el automation → socket control mode de cmux para que no sea cmuxOnly; por defecto cmux solo acepta comandos de procesos que él mismo ha lanzado.
Codex CLI (para agentes Codex) ≥ 0.156 (perfiles por archivo, check_for_update_on_startup). El daemon analiza los archivos rollout .jsonl de Codex (token_count, task_complete, metadatos de subagentes); si una versión de Codex cambia ese formato, fija la versión que te funcione.
redis-server en el PATH brew install redis. Cada ejecución arranca su propia instancia privada en .runtime/redis/<token>/ del checkout (socket, PID, log y datos); TCP está desactivado y nunca toca localhost:6379. El binario sigue siendo una dependencia externa, no viene incluido.
Python 3.14 + requirements.txt 3.14 es la versión sobre la que se ejecuta la suite de pruebas; deps.yaml enumera cada programa externo y su versión mínima.
Un modelo con el que Codex pueda hablar La API de OpenAI, un inicio de sesión de ChatGPT o un servidor local compatible con OpenAI. El daemon lee la ventana de contexto del rollout, así que no hay que configurar ningún tamaño de ventana.

Los agentes se ejecutan en un home de Codex aislado (--codex-home, por defecto ~/.fleetmux/codex-home): auth.json y el archivo de perfil se enlazan desde tu ~/.codex, así que el inicio de sesión se comparte, pero tus hooks, servidores MCP, plugins y skills no se ejecutan dentro de los agentes (skills.include_instructions=false). Las entradas de «confiar en esta carpeta» de cada agente se escriben ahí automáticamente.

Instalación

Un solo comando: fleetmux <subcommand> (por ejemplo fleetmux start, fleetmux status, fleetmux channel). El binario ejecuta por sí mismo sus subcomandos de terminal (server, attach, reap, web, gui --runtime D) y pasa los subcomandos de flota a la CLI de Python de la misma versión. Las instalaciones hechas con el nombre anterior del proyecto siguen funcionando: su comando, sus variables de entorno y sus directorios de versiones se siguen aceptando como alias.

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

Añade un perfil de Codex para los agentes. Codex ≥ 0.156 guarda cada perfil en su propio archivo (consulta codex.config.example.toml):

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

Nada más: la flota crea su propio home de Codex para los agentes y responde ella misma, para cada directorio de agente, a la pregunta de Codex «¿confiar en esta carpeta?».

El código fuente, las versiones y las incidencias están en GitHub: github.com/justindbca/fleetmux.

Arrancar una flota

Abre un terminal de cmux, configura tu proveedor y modelo de Pi, y sustituye local/my-model más abajo. Los arranques nuevos requieren Redis v2 y una autorización explícita del modo de desarrollo. Sin --supervisor-cli / --worker-cli, start toma el par por defecto de fleetmux/config/fleet.yaml#cli_pairs: un supervisor Pi con openai-codex/gpt-6-astra (thinking high) y workers oficiales de Claude Code. Esa tabla enumera los pares admitidos y su estado; un par que no figure en ella se rechaza. start también exige --lifecycle <mode> (service, dev_task, program, scheduled, review_loop, smoke); un par unverified solo arranca con --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 - entrega el objetivo al supervisor.
  • fleetmux status muestra la tarea, el estado de cada agente, el uso de contexto y la profundidad de la bandeja de entrada.
  • fleetmux stop cancela la ejecución y confirma la retirada del runtime antes de apagar Redis.

El JSON del objetivo declara los entregables que verifica el daemon: path, min_chars y las cadenas contains. La tarea solo se cierra cuando cada entregable declarado existe, es lo bastante largo y contiene el texto requerido; tres declaraciones COMPLETED falsas la bloquean.

Dos formas de ejecución: task y service

Cada start indica un --lifecycle, que decide qué significa «terminado» y cómo se cierra la ejecución (fleetmux/config/fleet.yaml#lifecycle_modes). Los valores son service, dev_task, program, scheduled, review_loop y smoke. Dos formas cubren el primer día.

Una ejecución de tipo task

Entregas un único objetivo y la ejecución termina. task pone el objetivo en la bandeja de entrada del supervisor; el daemon lo pega en la pestaña del supervisor; el supervisor responde con un único objeto JSON (status, completion, reason, dispatch, artifacts); cada envío va a la bandeja de entrada de ese worker y se entrega cuando el worker está libre; la respuesta del worker vuelve marcada [worker-1 report]. COMPLETED se comprueba contra los entregables declarados y los fallos vuelven como «Independent verification failed»; tres devoluciones bloquean la tarea; BLOCKED detiene la tarea y deja el motivo en status para una persona. Tres JSON inválidos seguidos bloquean la tarea.

Una ejecución de tipo service

--lifecycle service mantiene la flota en marcha sin objetivo ni final: responde a un flujo de peticiones. El supervisor solo enruta; cada worker puede tener su propio prompt; las respuestas son JSON comprobados por el daemon, no por un modelo.

modo servicio
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 es el directorio de roles de ejemplo del README; apunta al tuyo.

  • --worker-names: ranuras de worker con nombre por rol (minúsculas, sin puntos; los puntos marcan generaciones). Cada nombre termina en -<number> (proto-1, no proto).
  • --roles DIR: DIR/<agent>.md se añade al AGENTS.md de ese agente como «Your role». Un prompt por worker.
  • --max-busy N: como máximo N workers trabajan a la vez; el supervisor no cuenta.
  • Una petición: id (de un solo uso), payload (cualquier JSON, ≤ 200 000 caracteres), reply.required, reply.allowed opcional, to opcional (enruta directamente a ese worker, sin turno del supervisor), instruction opcional, timeout_s opcional. La respuesta debe ser un único objeto JSON; una respuesta mala tiene una corrección; una segunda respuesta mala se publica como invalid. Estados del resultado: ok, invalid, skipped, timeout, stalled.
  • pause detiene la entrega y la rotación; las peticiones siguen en cola. Los calendarios (horario de mercado, festivos) son cosa de quien llama: pause y resume desde fuera.

Las reglas de nombres y cómo añadir workers sin reiniciar están en Roster YAML.

Roster YAML (añadir / quitar en caliente)

start --roster roster.yaml (en lugar de --workers / --worker-names) toma los workers de un archivo, y el daemon lo vuelve a leer mientras la flota está en marcha.

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
  • Añadir: un nombre nuevo se escribe en config.json (y latest.json si apunta a esta ejecución) y luego se lanza con el prompt READY como pestaña al final de la columna de workers. El supervisor ve la lista nueva añadida a su siguiente mensaje ([Roster change] Workers you can dispatch to now: ...).
  • Quitar: el worker no acepta trabajo nuevo (los envíos hacia él se rechazan), termina lo que ya tiene y, cuando está inactivo con la cola vacía, su pantalla pasa a la página de historial y su pestaña se cierra. Su directorio de trabajo se queda. Volver a ponerlo en el archivo antes de eso cancela la retirada.
  • Cambio de rol de un worker existente: se aplica en su siguiente generación; no se reinicia.
  • El supervisor también lo edita: cada prompt del supervisor incluye una sección «Adding and retiring workers (the roster)» que nombra el archivo de roster de esta ejecución. Añade una fila - {name: <role>-<n>, role: <path>} cuando hay más trabajo listo que workers libres, borra una cuando un worker ya no tiene trabajo, dentro de los límites de su brief de tarea.
  • Rechazado en bloque (el roster se conserva, motivos en la pestaña daemon como roster.rejected): YAML inválido, claves desconocidas, nombres inválidos o duplicados, cero workers, un archivo de rol que falta o un nombre ya usado en esta ejecución. Los cambios se registran como roster.changed.
  • Umbral de traspaso por modelo: tabla handoff: opcional y window / handoff_ratio por worker; los cambios se aplican en la siguiente comprobación de entrega, sin reinicio.

Necesita PyYAML en el Python que ejecuta el daemon.

Reanudar una ejecución interrumpida

fleetmux start --resume <run-dir> vuelve a engancharse a una ejecución Redis v2 sin terminar en vez de crear una nueva; --resume auto toma la ejecución de ~/.fleetmux/latest.json y arranca una ejecución nueva cuando esa ya ha terminado (existe <run>/result.json) o falta. Con un daemon vivo la orden no hace nada.

terminal
fleetmux start --resume <run-dir>
  • Redis arranca otra vez desde el directorio y el socket propios de la ejecución; el AOF recupera lo escrito hasta el último fsync (alrededor de 1 s). Un Redis que sigue vivo se reutiliza.
  • Los workers oficiales de Claude cuya pestaña aún ejecuta la CLI se vuelven a enganchar; el resto de agentes (el supervisor de la tarea, los agentes Pi, las pestañas cerradas) arrancan como su generación siguiente (worker-1 -> worker-1.1) en el sitio.
  • Tarea: los entregables verificados y los informes aceptados se conservan; los envíos que estaban en cola o en vuelo los vuelve a encolar el daemon, y el nuevo supervisor recibe un resumen (objetivo, entregables verificados/faltantes, envíos terminados con extractos del informe, envíos reencolados). Servicio: las peticiones aceptadas sin resultado se vuelven a encaminar; una petición con "replay": false se publica como timeout.
  • --max-attempts N (2 por defecto, guardado en la config, modificable al reanudar): un envío / una petición se ejecuta como máximo N veces en total; por encima del tope la tarea queda blocked / la petición queda timeout, nombrando el elemento.
  • Una tarea terminada (cualquier estado terminal) o un stop explícito escribe <run>/result.json; una ejecución así nunca se reanuda. Sin --resume, un daemon rearrancado sigue con trabajo incierto y se pausa (sin replay).

Ventana y ratio de traspaso por modelo

La sesión se renueva cuando used >= ratio × window; --handoff-ratio vale 0.6 por defecto, y cada agente resuelve window y ratio por separado: gana la primera coincidencia.

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

Backend de terminal: tres opciones

fleet habla con el terminal a través de un único backend intercambiable. start --backend cmux|wezterm|fleetmux lo elige, y esa elección se escribe en el config.json de la ejecución: el daemon, los actores, start --resume y cada subflota siguen ese valor, y una ejecución arrancada antes de que existiera esta opción se queda en cmux. Sin la opción, macOS no cambia — cmux cuando cmux está instalado, WezTerm en un Mac sin cmux — y Linux (WSL2 incluido) usa fleetmux por defecto desde M3; --backend wezterm (o --backend cmux) sigue seleccionando los otros backends de forma explícita. Un backend elegido a mano que no puede funcionar en la máquina se rechaza antes de provisionar nada, diciendo qué falta.

Los tres backends.
BackendPor defecto enDisposiciónNecesita
cmux macOS, cuando cmux está instalado Supervisor arriba a la izquierda, navegador de historial debajo, workers como pestañas a la derecha. cmux ≥ 0.64.25
wezterm Linux / WSL2 cuando se elige a propósito; por defecto en un Mac sin cmux Una ventana, pestaña 1 con el supervisor a la izquierda y daemon debajo, una pestaña por worker; sin superficie de navegador ni pestañas dentro de un split, así que la página de historial no se integra — fleet imprime <run>/history.html en daemon y la abre con open / xdg-open cuando hay escritorio disponible. wezterm y wezterm-mux-server ≥ 20240203 en el PATH; cada ejecución arranca su propio servidor mux privado, nunca tu WezTerm.
fleetmux Linux (WSL2 incluido) desde M3; en macOS solo con --backend fleetmux el terminal de disposición fija de fleet, escrito en Rust (rust/, MIT); un fleetmux server privado es dueño del PTY y de la pantalla de cada agente, y tú lo miras con fleetmux attach — ábrelo y ciérralo cuando quieras, varios a la vez, también por ssh; cerrar una vista nunca detiene a un agente. fleetmux en el PATH (o $FLEETMUX_BIN); fleetmux --version debe imprimir fleetmux <x.y.z> (protocol 1) con una versión al menos igual al mínimo 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

Compila el backend fleetmux desde este repositorio (cd rust && cargo install --path crates/app), o ejecuta scripts/install_fleetmux.sh [--dest DIR] para instalar el binario precompilado que fija deps.yaml.

Limitaciones conocidas

  • En macOS el backend por defecto es cmux (WezTerm sin cmux); en Linux es el terminal propio de fleetmux. En macOS el daemon usa caffeinate cuando está disponible para que la máquina no se duerma.
  • Un espacio de trabajo cmux nuevo roba el foco un instante al crearse (cmux ignora --focus false para new-workspace); el daemon te devuelve a donde estabas.
  • Codex no guarda un registro en flujo de una pestaña; cuando una pestaña se retira, su pantalla final se guarda junto a la nota de traspaso. La transcripción completa es el archivo rollout de Codex en ~/.codex/sessions.
  • A veces un modelo hace más de un paso despachado en un turno. La comprobación de entregables atrapa el resultado, no el exceso.