fleetmux
English · 中文 · Français · Español
Run AI agent teams the way you run containers. Run a team, not a single assistant: fleetmux runs one supervisor agent and N worker agents as visible
terminal sessions. The supervisor only judges and dispatches; workers only execute; a daemon, not the models,
decides when the work is done. Sessions near their context limit hand over to a fresh session with a written note,
so one task can run for hours across many model sessions. Official Claude Code, official Codex CLI, Pi
(Codex models and local models) and opencode mix in one fleet, in any supervisor × worker pairing that has
passed a real smoke run (fleetmux/config/fleet.yaml#cli_pairs).
In daily use. Since October 2026 it has run resident service fleets and scheduled task fleets on three Macs for a live trading operation: an ops team that starts and stops the other teams, a trading desk, a strategy review team that answers price and indicator triggers, and a scheduled research team. Each team has an AI lead and AI workers; the ops team runs the trading desk and the review team as its sub-fleets.
A project manager, not a secretary
Personal-assistant agents can already spawn sub-agents, run scheduled jobs and fail over between models, but a sub-agent's result still goes back to a model to judge, long tasks rely on context compaction, and the model is whatever the caller or the config names. fleetmux adds what a project manager does:
- Delivery accepted by the program. Whether the outputs exist, whether they contain what they must and whether the tests pass is decided by the daemon, not taken from the model's own report.
- Sessions that continue without end. Near the context limit a session writes a handoff note and a fresh session takes its place; a frozen session restarts in place; daemon and supervisor watch each other.
- Dispatch by a capability matrix. Before every dispatch the advisor picks the model and effort from a capability matrix (domain × task type × model, each cell with evidence), and the pick must pass a check against that matrix; a usage wall moves the slot to the next usable model on its role's chain, or pauses.
- Visible throughout. Every agent is a visible terminal session you can watch and step into at any time.
- Official CLIs, your own login. Each agent runs in its vendor's own CLI under your own login; no token is extracted, no client is impersonated and no API call goes through a proxy.
- A budget per task. A budget card caps calls, rounds, hours and per-account quota; a usage-wall table drops a model tier or pauses the run so it can resume later.
- Teams of teams. A worker can become the lead of its own sub-fleet, so one resident fleet can run several business lines, each started and stopped on its own clock.
We learned why this matters from our own setup: an assistant gateway we ran lost its chat channels for months and nothing outside it noticed, because nothing outside it was checking. In a fleet, the daemon checks every delivery.
Built by Justin Wang (Toronto), AI systems engineer: LinkedIn · X. Available for freelance work: AI setup from zero, agent harness tuning, LLM capability elicitation and multi-agent orchestration. DM me on X.
Highlights
- Literal acceptance. A task ends only when declared artifacts exist and contain what they must;
false
COMPLETEDclaims are counted and block the task. - Context rotation. At a configurable share of the context window the session writes a handoff note
and a successor (
worker-1→worker-1.1) continues in the same tab. - Sleep recovery. Close the laptop lid, move, open it and reconnect: the run continues. The daemon tells a sleeping machine from a lost link, retries a startup that failed on the network with a backoff, and restarts no session until the network answers.
- Private Redis per run (Unix socket only, no TCP) carrying a bounded message bus with generation-checked deliveries, so a late callback from a retired session can never act.
- Watchdog and brake. Five-state stall detection per slot; a per-task budget card (calls, rounds,
hours, per-account quota) and a usage-wall table that moves a walled slot down its model chain or pauses resumably.
Accounts live in the
accountsblock of your private copy of the config file, never in code. - One config file. Every table (CLI pairs, lifecycle modes, walls, watchdog dialogs, model cards,
profiles, accounts) is a block of
fleetmux/config/fleet.yaml; a private~/.fleetmux/fleet.yamlin the same format overrides the blocks an operator may change. - Official CLIs, your own login. Claude Code always runs as the official
claudeTUI with your own login, and Pi is never given Claude credentials; Codex models run in the official Codex CLI or through Pi's own OpenAI login. fleetmux does not extract or forward tokens, impersonate a client or proxy API calls. - Sub-fleets. A worker can become the supervisor of its own child fleet.
- 2,000+ tests (in-memory Redis, fake cmux, synthetic transcripts; real Redis where it matters).
cmux workspace "Fleet agents" (one page)
┌───────────────────────────────┬────────────────────────┐
│ supervisor-1.2 │ [worker-1.1] [worker-2] │
│ supervisor TUI (selected) │ worker tabs │
│ │ │
├───────────────────────────────┤ selected worker's TUI │
│ [history] [daemon] │ │
│ retired screens + handoffs │ │
└───────────────────────────────┴────────────────────────┘
daemon: the fleet daemon, private Redis socket, routing, verification and watchdog.
Use cases
Every start names a --lifecycle; it decides what "done" means and how the run closes
(fleetmux/config/fleet.yaml#lifecycle_modes). These are the shapes it is built for; the scheduled report, the resident service and feature development run in daily use.
- Operations that may only use local models. Some environments allow only models running on your own
machines, and local models are poor at working together: they lose track of who is doing what, report work as
done when it is not, overflow a small context window, or freeze mid-turn. Part of that is the model, part is the
harness around it. fleetmux takes the collaboration out of the models and gives it to the daemon. The supervisor
only picks the next step and a worker only carries one out; the daemon decides when a turn has ended, whether the
result exists and says what it must (files checked literally, service replies against
reply.required; three falseCOMPLETEDclaims block the task), when a session hands over to a fresh one (window and ratio per model), and when a frozen session is restarted. Supervisor and workers can all be local models through Pi (--supervisor-cli pi --worker-cli pi --pi-model <provider>/<model>);--max-busykeeps concurrent prompts under the inference server's memory limit; the fleet's private Redis listens on a Unix socket only and opens no port. - Trading watch: strategies handed to AI, reviewed on a trigger. Your own program watches prices and
indicators; the model is not polled all day. When a price level or an indicator parameter is crossed, the program
sends one request, e.g.
{"to": "exit-1", "payload": {strategy, position, trigger, data}, "reply": {"required": ["verdict", "param", "evidence"]}}, and the worker that owns that role (one prompt per role through--roles, its tools and skills through--worker-kit) runs a full review: it reads the position and the market data, rechecks the strategy's assumptions and answers in JSON. A request that namestoskips the supervisor's turn; triggers that arrive together queue under--max-busy; the daemon checks the reply's fields before your program reads it (fleetmux results --drain). Requests have no global deadline: one that expires holds only its own slot, and a frozen Claude Code worker resumes its own session and gets the same request again. The fleet places no orders and knows no calendar: your program decides what to do with the verdict, sends any order through its own checks, andpause/resumes the fleet around market hours. - One mid-strength model leading a crowd of weak free models on routine work. Translation, drafting,
summarising and sorting are mostly volume. A mid-strength supervisor (through Pi or Codex) cuts the job into small
pieces, one chapter, one batch of documents, one topic, and declares the output files the task must produce, how
long each must be and what each must contain. Workers on free models through Pi do the pieces (one model per roster
row,
pi_model:). A weak model only ever sees one small piece:"fresh": trueon a dispatch item starts a clean session in the same slot so the last piece does not leak into the next, and a long piece hands over to a new session before a small window overflows. What a weak model gets wrong is caught by the program instead of trusted: the task closes only when every declared output exists, is long enough and contains the required text, and three falseCOMPLETEDclaims block it. Free endpoints are flaky and capped: rate limits and usage walls go through the wall table, which moves the slot down its model chain or pauses the run resumably;--max-busystays under the free tier's concurrency; workers are added or removed by editing the roster YAML while the fleet runs. For a quality pass, give one roster row a strongerpi_model:and use it as the reviewer. - Scheduled research report (
scheduled). The caller's own scheduler starts a fleet at fixed times of the business day (withclock_gateon, the fleet itself refuses to launch on a closed market day): a supervisor plus two Claude Code workers that carry research skills and a web-search MCP through--worker-kit. The goal declares the report file and the sections it must contain; the daemon accepts it literally, gives the run a hard time budget and closes the workspace when it ends. - Resident judgment service (
service). Another program sends JSON requests withfleetmux request; a request that namestogoes straight to that role's worker (--worker-names exit-1,reentry-1,..., one prompt per role through--roles). The daemon checks each reply againstreply.required/reply.allowed, and the caller holds the fleet outside its working hours withpause/resume. See Service mode. - Feature development with independent review (
dev_task). The supervisor splits the feature, workers build it, and separate reviewer workers judge it; review is capped at three rounds and leftover P0 / P1 go to you instead of a fourth round. The task is done only when the delivery gate'stest_cmdhas passed and the work is merged and handed to the release queue. Interface contracts ("contracts": true) freeze shared interfaces before work starts. - Multi-phase program (
program). Each phase runs as its own childdev_taskfleet; a ledger records the phases, and the program ends when the last phase is merged and reviewed. - Review until it passes (
review_loop). Review → fix → review, at most three rounds, then a report. - A task that outgrows one worker (development). When a worker keeps a flowing workflow busy past a
configurable time, it is asked to propose a split; on the supervisor's approval (with chosen models) the worker
hands off and its successor becomes the supervisor of a child fleet, shown live in the parent's worker area.
A worker can also ask for this itself. See Sub-fleets and
docs/design.md§44. - One resident operations fleet for several lines of work (development). A main supervisor in service mode
owns one worker per business line; each of those workers supervises a named child fleet declared in the roster,
which an outside program can start and stop on its own clock (
docs/design.md§29). - Day and night models.
start --roster day.yaml --night-roster night.yamlandfleetmux night on|offswap each worker whose profile differs to its night profile (e.g. a free or local model) at an idle boundary, same slot names, and tell the supervisor what is done and what is left (a full day → night → day run is not yet accepted end to end).start --resumebrings an interrupted run back the same way. - Run anywhere, watch from anywhere. On Linux the
fleetmuxbackend replaces cmux;fleetmux attach(also overssh -t) orfleetmux guishows the live page from another terminal, including a phone terminal app.
Chat channels (Telegram, Feishu, WeChat, email, Slack) are a second keyboard for the supervisor, with results and
"waiting for a human" pushed back to the same chat: fleetmux channel, contracts §8.
Why
Running agents as interactive TUIs instead of headless codex exec calls keeps them visible: you can
watch every tab, type into one if you must, and close any of them with the ✕. The daemon never scrapes
screens. Codex's notify hook calls back into the daemon at the end of every turn, and the daemon pastes
the next message only when a session is idle.
What the daemon enforces, independently of what the models claim:
- Acceptance is literal. A task ends only when every declared artifact exists, is long enough and
contains the required text. Three false
COMPLETEDclaims block the task. - No host commands from models. The supervisor can only declare files to check, never commands to
run. Codex agents are sandboxed (
workspace-write, network on); Pi and the optional Claude workers are not. - Context rotation before overflow. Before each delivery the daemon reads the session's token usage
from the selected CLI's rollout/transcript or Pi context API. At
--handoff-ratio(default 60 %) it asks the session to write a three-section handoff note, merges it into a machine skeleton, saves the old screen to the history page, and starts a successor in the same place (worker-1→worker-1.1→worker-1.2) that reads the note. If the model does not write, the skeleton is used; missing model-written notes alone do not block rotation. Window/process failures still follow the normal failure path. - Stall watchdog. A busy agent whose rollout file has not changed for
--stall-timeoutseconds (default 20 min; transport v2 also has the 5-minute multi-signal--stall-window, docs/design.md §24) is aborted and reported to the supervisor; a stalled supervisor gets one retry, then the task is blocked for a human. In transport v2 a frozen official Claude worker first resumes its own session (claude --resume, same tab) and gets its work again; only a resumed session that freezes again starts fresh (§43). - Stale callbacks are dropped. Every spawn bakes a generation number into the notify hook; a late callback from a retired session is ignored.
Requirements
| Item | Version | Notes |
|---|---|---|
| OS | Linux or macOS, x86_64 or ARM64 | The Linux fleetmux binary is static. The desktop window fleetmux-web needs glibc >= 2.39 and WebKitGTK 4.1 (Ubuntu 24.04 desktop has both). |
| Python | 3.14 | scripts/install.sh provides it through uv. The system Python is not used. |
redis-server |
>= 7.0 | Each run starts its own private instance under the release's .runtime/redis/<token>/ (socket, PID, log, data). TCP is off; it never touches localhost:6379. |
tmux |
>= 3.4 | Sub-fleets show one session in two tabs. |
git |
>= 2.30 | The delivery gate fast-forwards branches. |
| One agent CLI | see deps.yaml |
Claude Code, Codex, pi or opencode, logged in. Install only the ones your roster uses. pi also needs Node.js >= 22. |
| cmux (macOS only) | >= 0.65.0 | The default terminal on macOS. Run start inside a cmux terminal, or set cmux's automation -> socket control mode away from cmuxOnly. Without cmux, macOS uses WezTerm or --backend fleetmux. |
scripts/check_deps.py checks all of these against deps.yaml and prints one line per item.
Notes for Codex agents:
- The daemon parses Codex's rollout
.jsonlfiles (token_count,task_complete, subagent metadata). If a Codex release changes that format, pin the version that works for you. - Any model Codex can talk to works: OpenAI API, ChatGPT login, or a local OpenAI-compatible server. The daemon reads the context window from the rollout.
- Agents run in an isolated Codex home (
--codex-home, default~/.fleetmux/codex-home).auth.jsonand the profile file are symlinked from your~/.codex, so the login is shared, but your hooks, MCP servers, plugins and skills do not run inside agents (skills.include_instructions=false). Per-agent "trust this folder" entries are written there automatically.
Install
One command: fleetmux <subcommand> (e.g. fleetmux start, fleetmux status, fleetmux channel). The binary runs
its own terminal subcommands (server, attach, reap, web, gui --runtime D) and hands every fleet subcommand
to the Python CLI of the same release (docs/design.md §50). A machine set up under the project's earlier name
moves its data over once with fleetmux home migrate (docs/design.md §50.1).
Quick install (Linux and macOS)
-
Install the system packages.
sudo apt install redis-server tmux git # Debian / Ubuntu brew install redis tmux git # macOS
-
Run the installer. It needs only
curlandtar. It installs the latest release into~/fleetmux-releases/v<version>, gets Python 3.14 through uv, downloads the prebuilt binaries (sha256-checked), linksfleetmuxinto~/.local/binand runs the dependency check. No root, no compiler.curl -fsSL https://raw.githubusercontent.com/justindbca/fleetmux/main/scripts/install.sh | bash
-
Install and log in to one agent CLI, for example Claude Code:
curl -fsSL https://claude.ai/install.sh | bash && claude
-
Check.
fleetmux start --helpprints the options. Run~/fleetmux-releases/v<version>/.venv/bin/python ~/fleetmux-releases/v<version>/scripts/check_deps.pyagain after step 3: every line must sayok.
Options: FLEETMUX_VERSION=0.2.1 installs a given release; FLEETMUX_RELEASES and FLEETMUX_BIN_DIR move the
install and the links (see the header of scripts/install.sh).
From PyPI or crates.io
uv tool install fleetmux # or: pip install fleetmux
The PyPI wheels (macOS 11+ on Apple silicon, macOS 10.12+ on Intel, Linux with glibc 2.39+) carry the same
prebuilt fleetmux and fleetmux-web binaries as the GitHub release, so fleetmux works right after the install.
Install the system packages and pi as in steps 1 and 3 above. cargo install fleetmux builds only the terminal
binary; fleet commands also need the Python package of the same version.
From source (development)
git clone https://github.com/justindbca/fleetmux && cd fleetmux
pip install -e .
Add a Codex profile for the agents. Codex ≥ 0.156 keeps each profile in its own file
(see codex.config.example.toml):
# ~/.codex/fleet.config.toml (selected with `codex --profile fleet`)
model = "gpt-5.5"
Nothing else: the fleet creates its own Codex home for the agents and answers Codex's "trust this folder?" prompt for each agent directory itself.
Release install on a target machine (macOS or Linux)
scripts/install_release.sh <sha> <tarball> [--bin-dir DIR] [--tool-sha SHA] [--test] [--activate]
One application release per full source SHA: the tarball (a git archive <sha>) is unpacked into
$FLEETMUX_RELEASES/<sha> (default ~/fleetmux-releases), the release's .venv is built from the pinned
requirements, an already-built --bin-dir (fleetmux, fleetmux-web, SHA256SUMS, manifest.json) is verified
and both binaries land in <release>/bin, and scripts/check_deps.py runs with <release>/bin first on PATH.
An existing release is never joined or overwritten (the directory is created with a bare mkdir), a failed
install removes only the directory that run created, and <sha> must be 7-40 hex characters because it is the
release directory name. --test runs the release's own suite and keeps the complete log outside the release
dir, at <releases>/.logs/<sha>-<timestamp>.log, printing that path (so a failed run stays diagnosable);
--activate is the only step that moves ~/fleetmux-current. On success the installer prints one explicit
start command, and records it as start_command in <release>/bin/installed.json, so fleetmux resolves to
this release instead of a stale ~/.local/bin build:
PATH=<release>/bin:$PATH <release>/.venv/bin/fleetmux-py --help
--tool-sha records the installer-tool commit separately from the application <sha>. An installed release is
not a native PASS and not an acceptance receipt.
Use
Open a cmux terminal, configure your Pi provider/model and replace local/my-model below.
New starts require Redis v2 and an explicit development opt-in. Without --supervisor-cli / --worker-cli, start
takes the default pair from fleetmux/config/fleet.yaml#cli_pairs: a Pi supervisor on openai-codex/gpt-6-astra (thinking
high) with official Claude Code workers. That table lists every supported pair and its status
(see "Redis v2 development runtime"); a pair it does not list is refused. start also requires --lifecycle <mode>
(service, dev_task, program, scheduled, review_loop, smoke; see fleetmux/config/fleet.yaml#lifecycle_modes); an
unverified pair starts only with --lifecycle smoke.
The Codex-specific setup and launch examples below are retained only as legacy v1 reference.
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
Terminal backend: cmux, WezTerm or fleetmux
fleet talks to the terminal through one pluggable backend. start --backend cmux|wezterm|fleetmux picks it, and
the choice is written into the run's config.json: the daemon, the actors, start --resume and every
sub-fleet follow that value, and a run started before this flag existed stays on cmux. Without the flag
macOS is unchanged — cmux when cmux is installed, WezTerm on a Mac without cmux — and Linux (including
WSL2) defaults to fleetmux since M3; --backend wezterm (or --backend cmux) still selects the other
backends explicitly. An explicitly chosen backend that cannot run on the machine is refused before anything
is provisioned, naming what is missing.
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
- cmux (default on macOS, the layout described above): supervisor top-left, history browser below, workers as tabs on the right.
- WezTerm (
--backend weztermon Linux / WSL2, or the default on a Mac without cmux; needsweztermandwezterm-mux-server≥ 20240203 onPATH): each run starts its own private mux server (its own socket, never your WezTerm). There is no browser surface and no tabs inside a split, so the layout degrades: one window, tab 1 with the supervisor on the left anddaemonsplit below it, one tab per worker. The history page is not embedded — fleet prints<run>/history.htmlindaemonand opens it withopen/xdg-openwhen a desktop is available. Named sub-fleets still work through tmux. - fleetmux (the default off macOS since M3; on macOS only with
--backend fleetmux): fleet's own fixed-layout terminal written in Rust (rust/, MIT) — see the subsection below.
fleetmux backend (Linux default since M3)
Each run gets one private fleetmux server that owns every agent's PTY and screen. You look at it with
fleetmux attach, which is a separate client: open and close it whenever you like, open several at once, run it
over ssh. Closing a viewer never stops an agent.
-
Install (one of):
- build from this checkout:
cd rust && cargo install --path crates/app, orcd rust && cargo build --releaseand puttarget/release/fleetmuxonPATH. Run cargo insiderust/so that the toolchain pinned byrust/rust-toolchain.tomlis used. On Linux, building the workspace also needslibwebkit2gtk-4.1-dev(for thefleetmux-webwindow, M2.5;apt-get install libwebkit2gtk-4.1-dev); scripts/install_fleetmux.sh [--dest DIR]installs the prebuilt binary pinned bydeps.yaml(programs.fleetmux.min) into~/.local/binby default and refuses it unless its sha256 matches. It downloads from the GitHub releasev<min>(.github/workflows/release.yml). If that release has not been published yet, the script fails and installs nothing; build from source instead.$FLEETMUX_BINpoints fleet at a specific binary; otherwisefleetmuxonPATHis used.fleetmux --versionmust printfleetmux <x.y.z> (protocol 1)with a version of at leastdeps.yaml's minimum, or--backend fleetmuxis refused before anything starts (scripts/check_deps.py --backend fleetmuxreports the same).
- build from this checkout:
-
Start and watch:
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
--attachis refused on other backends. The runtime directory is<state dir>/fleetmux/<session token>/. It holdsctl.sock(0600, the directory 0700, same-uid peers only),server.json,panes.json,layout.json(your ratios survive a server restart),start.lockandserver.log. fleet derives the path from the run, so every fleet process finds the same server. -
Watching a fleet on another machine (fix-m25-2):
ssh -t <host> fleetmux attach --runtime <dir>shows the terminal, but that viewer runs on<host>, soC-b ocan only open a window there. To get the history window on your own desktop, run the viewer on your machine and forward the server's socket with your own SSH:# On your machine (the one with the desktop). Replace every <...> placeholder. <dir> = the runtime directory on # <host>, an absolute path: `fleetmux start` printed it there ("Watch it with: fleetmux attach --runtime <dir>"). # Use a NEW short private directory every time, and the same $L in both terminals (set it in each of them). L=$HOME/.local/state/fmr/<host>-<session token>-<n> # <n>: a number you have not used before (1, 2, ...) mkdir -p -m 0700 "$(dirname "$L")" # terminal 1: the parent (already there is fine) mkdir -m 0700 "$L" # terminal 1: fails if $L exists -> stop, pick a new <n> ssh -N -o ExitOnForwardFailure=yes -o StreamLocalBindUnlink=no \ -L "$L/ctl.sock:<dir>/ctl.sock" <user>@<host> # terminal 1: keep it open while you watch fleetmux attach --runtime "$L" # terminal 2, with the same L
- Only
ctl.sockis forwarded. The controlhello, the view connection and the web commands all use it, and the history page comes over it too (pushed to your machine and kept at<$XDG_RUNTIME_DIR or ~/.local/state>/fleetmux-web/<first 24 chars of the last part of $L>-<pid>/page/history.html, 0600). The window, itsweb.sockand the clipboard are local. No TCP port is opened. - Install
fleetmux(and, for the window,fleetmux-webof the same version) on your machine as well; both ends speak protocol 1. - Log in as the user the fleet runs as on
<host>: the server accepts only peers with its own uid, andsshdconnects as the login user. The remotesshdmust allow unix-socket forwarding (AllowStreamLocalForwarding, on by default; OpenSSH 6.7 or later). $L/ctl.sockmust fit a unix socket path (at most 103 bytes on macOS, 107 on Linux), so keep$Lshort.- Identity:
fleetmux attachchecks that the socket it connected to is held by your own uid (the kernel's peer credentials; yoursshprocess here) and refuses when it cannot tell. It does not compare the remote server's uid with yours: two machines number users differently. It trusts whatever your own SSH forward reaches, so forward only to hosts you trust. On one machine nothing changes: the peer is the server itself. - If
attachcannot connect it prints$L/server.log, which does not exist locally; read the server's log withssh <host> tail -n 20 <dir>/server.log. - Nothing is ever replaced: with
StreamLocalBindUnlink=noandExitOnForwardFailure=yes,sshexits instead of removing a$L/ctl.sockthat already exists. Ifmkdirorsshsays the directory or socket is in use, stop there and start again with a new<n>; do not delete or overwrite what is there. - When you are done, stop that
sshwith Ctrl-C in terminal 1. The socket file stays in$L; leave it, and use a new$Lnext time.
- Only
-
Watching in a window:
fleetmux gui(M2.6): on a desktop you can watch the fleet in a native window instead of a terminal. The window shows the same screen asfleetmux attach(same layout, tabs, keys and mouse), and while the lower-lefthistorytab is selected (or zoomed withC-b z) the history page is drawn right in that cell, so there is no separate window. Switch todaemonor another lower-left tab and the page hides, showing the terminalhistoryview under it.fleetmux gui --runtime <dir> [--devtools] # needs fleetmux-web of the same version fleetmux start --backend fleetmux --gui ... # on a desktop: start, then becomes that `fleetmux gui`
- On a desktop,
fleetmux startprintsWatch it in a window: fleetmux gui --runtime <dir>before the usualWatch it with: … attach …line.--guiand--attachcannot be combined, and--guiis refused without a desktop. - Click into the page to use it (the lower-left group gets the focus); the prefix key still works inside the page
and gives the keyboard back to the terminal.
C-b oselects the history tab and focuses the page.C-b dor closing the window detaches (exit 0); the agents keep running.browser_goto/browser_url/reload_browserfrom the fleet act on the embedded page. - Copy and paste are handled by the page itself: Cmd-C / Cmd-V on macOS, Ctrl-Shift-C / Ctrl-Shift-V on Linux
(Linux needs
wl-copy/wl-pasteon Wayland orxclipon X11); an Option-click selection is also copied as soon as the mouse button comes up. Cmd/Ctrl=-0change the font size. - Exit codes:
0detached, window closed or server gone;2usage error;3cannot connect (asattach);4cannot open the window: no graphical session (Linux withoutDISPLAY/WAYLAND_DISPLAY, e.g. underssh),fleetmux-webmissing or of another version, WebKitGTK missing, the web runtime path too long, or the window not ready within 3 s. It prints one line with the reason plususe: fleetmux attach --runtime <dir>, and never turns into a terminal attach by itself. - Another machine: forward
ctl.sockexactly as above (same$L,mkdir,ssh -N -L …in terminal 1), then runfleetmux gui --runtime "$L"in terminal 2 instead offleetmux attach. The history page comes over the same socket into the localpage/history.html(0600); no TCP port is opened.
- On a desktop,
-
Layout: fixed. Supervisor tabs top left;
history(a terminal view of the run history,python -m fleetmux.history_view) anddaemonbottom left; worker tabs on the right. A successor takes its predecessor's tab position. -
Tabs and separators: each tab is a padded, clickable text block sitting on its group's border row, with a thin
│between neighbours; a click selects a tab only inside its block (not in a gap or on the border after the last tab). The selected tab of the focused group is a bright bold label on a dark-grey block, the selected tab of an unfocused group a grey label on the same block, other tabs plain grey text with no background, and•marks a tab with unseen output. Tab colours are the 16-colour ANSI palette, so each terminal (and the gui's theme) shows them in its own shades; on a light terminal theme they are faint but readable. All three pane borders stay visible (the focused pane's border is highlighted). Drag the vertical separator to change the left/right ratio and the left column's horizontal separator — it shows a visible grip — to change the upper/lower ratio (both 10 %–90 %, kept inlayout.json). -
Sidebar: while the fleet daemon runs, a 34-column sidebar on the left lists this fleet's cards (state dot, bold title, two grey lines). Cards that do not fit are scrolled to, like cmux: the wheel over the cards moves the list one card per step, a grey
┃thumb on the sidebar's inner edge shows the visible share and where it is, and the selected card (a click,←/→, a new bash page) is always scrolled into view. Drag its right edge to resize it: 24 up tomin(60, columns − 60)columns (a window too narrow for 24 cannot drag), the drag paints that border column Yellow bold, and the release saves the width to kvsidebar_width; hiding and showing the sidebar again keeps it, while a restarted server returns to 34, and the gui cannot drag the edge (one xterm.js canvas, it does not know the border column). Row 0 is a two-segment topbar across the screen, split at that drag line: the left segment holds◫ + ↺ ← →and follows the sidebar's width, the right segment starts two columns after the│divider and shows the current fleet's title plus the daemon's one-line state (fleetmux-demo · 运行中 · …, "running"), cut with…at the right edge.◫collapses the sidebar to 0 columns — the panes fill the screen from column 0, nothing is drawn below row 0, and the buttons keep exactly their columns —◫orC-b sshows it again at the width it had;+opens a bash page (see the next bullet),↺(orC-b r) restores the newest closed fleet from kvrestorable: the server spawns the recordedstart --resumecommand in aresumepane, zooms it and the status line says正在恢复…("restoring…"); with nothing to restore it says没有可恢复的 fleet("no fleet to restore"), and while the sidebar still lists arunning/paused/stoppingcard it says先关闭正在运行的 fleet 再恢复("close the running fleet before restoring") and spawns nothing (one session holds one fleet). A closed fleet's card keeps a grey dot and已关闭 · 可恢复("closed · restorable"; a run a lifecycle timeout stopped withresumablesays到点停 · 可恢复, "stopped at the time limit · restorable"), draws↺on its first row (it restores that card's run by id) instead of×, and the pane area shows the closed-fleet placeholder; the server exits 10 s after the last pane and viewer are gone, and the run can still be resumed withstart --resumeafterwards. While no fleet runs,fleetmux gui(or./start.sh gui: the Pythonguisubcommand,fleet.py gui) is the one-command window on that history: it scans this HOME'sruns/once, writes the grey cards newest-closed-first (at most 16, only runs whose runtime is this HOME's) and execsfleetmux gui --runtime <that runtime>; with no resumable run it prints这个 HOME 没有可恢复的 fleet;用 start 新起,或 start --resume <run>("this HOME has no fleet to restore; start a new one with start, or start --resume ") and exits 1 without starting a service. A window that lists only grey cards — norunning/paused/stoppingcard — shows没有在跑的 fleet;点侧栏卡片上的 ↺ 恢复("no fleet running; click ↺ on a sidebar card to restore") in the pane area, and such a server exits 10 s after the last pane and viewer too; a server without kvsidebarnever exits by itself.←/→(orC-b (/C-b )) step the card selection through the fleet and bash cards as one list (see the next bullet). Sidebar S3 (M4, onefleetmux serverper fleet tree, one page per fleet): a card that carriespageswitches every viewer to that fleet's page — click it, or step onto it with←/→/C-b (/C-b )— and the highlighted card and the topbar follow the page shown; a reclaimed child fleet's page is a read-only record (已收回 · 只读, "reclaimed · read-only", over its empty groups, itshistory_viewtab below), and a child card has no×(child fleets start and stop through their roster andsubfleet.up/down). A card withoutpage(an older daemon) still only says切换 fleet:S3 开放("switch fleet: available in S3"). Infleetmux guiand the M2.5 window a sub-fleet link ofhistory.htmlswitches the page the same way (the server does it, so it works with the child daemon gone). With a sidebar the cells start on row 1 (their tab row) and the cards on row 2; the topbar has no background of its own. A card's×(running / paused fleets only) orC-b Xopens the close confirmation box:yconfirms, any other key or a click elsewhere cancels. Confirming writes kvstop_request=requestedand the status line shows已请求关闭,等 daemon…("close requested, waiting for the daemon…") — the daemon only starts acting on it in the next milestone, so a request made today stops nothing. The panes' PTYs, the tabs and the gui's history overlay all move right while the sidebar is shown; without the kv keysidebarthere is no sidebar and nothing changes. Shown/hidden is shared by every viewer and not saved inlayout.json. With a gui window attached (macOS) the topbar's left segment (the toolbar) shifts right by the columns its traffic lights take: the window has a transparent, title-less titlebar, xterm.js row 0 sits on the traffic-light line, and every viewer — terminals too — starts its toolbar after them; Linux keeps the native titlebar and no shift. In the gui, blank and─cells of row 0 still drag the window (double click zooms it); the│divider and the title text are neither, so the page leaves them to the terminal instead of dragging the window. -
Bash pages (S1c): three entry points open one: the sidebar's
+(row 0, column 4), a double click on its empty card area (two left presses on the same cell within 300 ms, from row 2 down to the row above the status line, in the columns left of the card gap) andC-b c. A bash is$SHELL -l(or/bin/bash -l) started in$HOME, a pane of typeshellthat the server spawns as the last tab of the root page'sworkersgroup, so every new bash is mapped into the main fleet's worker area and managed by the main fleet. The root fleet takes it into the fleet's registry (<session>.fleetmux.json,type: shell; the daemon's tick andarrange): the daemon keeps it in the worker area behind the workers and lists it on the history page, and a bash opened while no fleet runs is taken in by the next root fleet. It is no worker: no slot, no work, not counted by--max-busy, not in the roster; a sub-fleet never takes one in. Each bash gets abash-<n>card after the fleet cards, in creation order (green dot,终端 · 运行中("terminal · running"),~,×on its first row); a shell that exits takes its card and its tab with it. Opening a bash or clicking its card shows the root page, cancels a zoom of another pane, selects the tab and focuses its group — the topbar readsbash-<n> · 终端 · 运行中and the fleet card's blue bar gives way — and clicking the fleet's card focuses the supervisor group again (the bash stays a tab of the worker area). A bash card's×closes the bash right away while the shell sits at its prompt; while a program runs in the foreground (the PTY's foreground process group is not the shell's own) the same confirmation box asks first —bash-<n> 里还有程序在跑,关掉会结束它。("a program is still running in bash-; closing will end it.") with[ 关闭 bash 页 ]("close bash page") — and onlyycloses it; closing a bash also ends the job in its foreground, even one that ignores SIGHUP (pane.kill, PROTOCOL.md §4.13). Closing the root fleet (Python'sclose_panesandclose_all: the sidebar×/↺path and the lifecycle close) also closes every bash whose shell is idle at its prompt (the pane record'sfg_pgidequals itspgid) and keeps the ones with a program in the foreground — or withoutfg_pgid(an older server) — naming them in the close outcome;close_allends the server once no bash is left and keeps it while a busy one lives — the closer then takes the run's cards out of the live states itself (a resumable timeout run reads到点停 · 可恢复with↺, any other runstopped/已停止("stopped"), its sub-fleet cards已收回 · 随根关闭, "reclaimed · closed with the root"), so the server exits by itself 10 s after the last pane and viewer, as before. A sub-fleet's close never touches a bash.session_existsskipstype: shellpanes, so a server holding only bashes is not a live fleet. The closed-fleet / no-fleet placeholder is drawn on the fleet's page only while no live card is up, no pane other thanshellexists there, nothing is zoomed and no bash card is selected. Switching to another fleet is S3 (M4): a card withpageswitches the page; a card withoutpageonly says切换 fleet:S3 开放. -
Keys (prefix
C-b, server option--prefix):1–9/Tab/BTabpick a tab; the arrow keys move focus between groups;zzooms;[enters page mode (PgUp/PgDn,/search,qquit);<>+-change the ratios and=resets them;oopenshistory.htmlon the machine you are attaching from (or shows its path);ddetaches. Mouse: click tabs and panes, drag the separators, wheel to scroll back. Shift+drag leaves selection to your outer terminal. In a pane whose program does not take the mouse, a drag selects (highlighted), double click selects a word, triple click the whole line; each copies at once via OSC 52 pluspbcopy/wl-copy/xclipon the server host, and the highlight stays until your next click or key. Word boundaries are cmux's (Ghostty's default): whitespace and'"│`|:;,()[]{}<>$. -
Copy (OSC 52) by outer terminal — what was actually checked during the M1 acceptance:
Outer terminal OSC 52 copy cmux (macOS) works WezTerm (macOS and Linux) works Terminal.app (macOS) not accepted gnome-terminal (VTE, Linux) not accepted iTerm2 not tested Where OSC 52 is not accepted, the server-host clipboard tools (
pbcopy/wl-copy/xclip) are the only copy path; on a headless host without them nothing reaches a clipboard. -
Crash recovery order: if a
` first** and only then `fleetmux start --resume`. The first spawn of a new server rewrites `panes.json`, after which `reap` can no longer find the old process groups. Try this only on a run you started for testing — never `kill` / `reap` the server of a run you are using or someone else's fleet.fleetmux serverdies (for examplekill -9), run **`fleetmux reap --runtime -
Limits (today):
- Sub-fleets are refused, as on WezTerm (
subfleet.backend_problem). - There is no embedded browser; the history is a terminal tab.
- The M0–M2 Mac and Linux-VM acceptance was passed by the operator on the fixed commit
64f7be96; the reworked tab style above (fix-m1-tabs) passed the operator's real-mouse check (7 items) in cmux and WezTerm on the release built fromb1de5637. - In that acceptance the performance targets (server CPU while 8 panes flood, idle CPU with no wakeups, memory
for 10,000 lines of scrollback) were met on both machines, and the Linux-side checks (
htopend-to-end, the logindKillUserProcesseswarning) ran green on the VM. - Replay snapshots: 9 real Claude Code / Codex / pi sessions were redacted under the operator's conditions and
imported (
rust/fixtures/recordings/); the targetedreplaytests pass. The fullm3gate and the final review are not done yet. - Design: docs/design.md §33.6.
- Sub-fleets are refused, as on WezTerm (
start options: --profile (Codex profile, default fleet), --backend (cmux / wezterm /
fleetmux, see above), --attach (fleetmux only), --codex / --redis-server (binaries,
default: first on PATH), --redis-runtime-dir (default: checkout .runtime/redis),
--runs-dir (default ~/.fleetmux/runs), --codex-home (the agents'
isolated Codex home, default ~/.fleetmux/codex-home), --handoff-ratio,
--handoff-timeout, --stall-timeout, --window (fallback context size until the rollout reports one),
--compact-limit (Codex auto-compaction token limit; by default Codex decides), --chrome-script
(see below).
Each run writes to ~/.fleetmux/runs/fleet-<time>-<token>/: config.json, workspace/<worker>/
(worker cwd, all deliverables land here), agents/supervisor/, handoffs/, and prompts/.
~/.fleetmux/latest.json points at the newest run and pins redis_runtime_root,
redis_runtime_dir, redis_instance_id and redis_socket for callbacks and control.
All per-user data lives under ~/.fleetmux/: the fleet home above, state/ (window registries), config/
(channel credentials, whitelists, leases), launchers.env, board/ and fleets/<name>/ (a second fleet's
home). A machine set up under the project's earlier name runs fleetmux home migrate [--dry-run] once, while no
fleet is running: it moves the data here, leaves a symlink at each old path and updates the run records, so old
runs can be resumed (design §50.1); until then fleetmux start stops with one line saying so.
Private Redis files live separately in .runtime/redis/<token>/: redis.sock, redis.pid,
redis.log, fleet.rdb, and v2's appendonlydir/. The short token avoids Unix socket path-length
limits; directories are owner-only (700). Overlong paths fail before Redis starts, with no /tmp
or TCP fallback. Use an explicit short, writable --redis-runtime-dir for a read-only/non-editable
installation. Older configs pinned to an unmanaged /tmp/fleetmux-*.sock remain supported; existing
fleets are not restarted. Stop removes the socket through Redis shutdown, but retains logs/data.
Keep .runtime/ out of Git (a local .git/info/exclude entry is sufficient); these are runtime
files, not source. See the design baseline.
Layout and the history page
Everything lives on one cmux page: the supervisor top-left, the history browser pane under it, the
workers as tabs of one pane on the right (worker-1, worker-2, …). After startup the daemon tab's
surface is docked as an inactive tab beside history in the lower-left pane; history stays selected.
Its now-empty startup workspace closes
without killing the daemon. Click daemon only when you need logs; it is not another AI agent. When a session rotates, its successor opens in the same spot and the
retired session's whole screen (with scrollback) and handoff note go to <run>/history.html, which the
history pane reloads. On that page you pick an agent (supervisor, worker-1, …), pick a retired
generation (supervisor-1, supervisor-1.1, …, worker-1, worker-1.1, …), switch between its screen
and its handoff note, or search every generation of every agent at once (Enter jumps to the next match).
Successors are named <slot>.<generation> for the supervisor and the workers alike: supervisor-1 → supervisor-1.1 → supervisor-1.2, worker-1 → worker-1.1 → worker-1.2.
Service mode (resident fleet)
start --mode service keeps the fleet running with no goal and no end: it answers a stream of
requests. The supervisor only routes; each worker can have its own prompt; answers are JSON checked by
the daemon, not by a model.
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
--worker-names: worker slots named by role (lowercase, no dots; dots mark generations). Each name ends with-<number>(proto-1, notproto) so its generations readproto-1→proto-1.1→proto-1.2, likeworker-1→worker-1.1; a new run refuses other names. A run started before this rule keeps its names on--resume.--roles DIR:DIR/<agent>.mdis appended to that agent'sAGENTS.mdas "Your role". One prompt per worker.--supervisor-profile/--worker-profile: separate Codex profiles, e.g. a local model for one side.--max-busy N: at most N workers work at once (a local model server may reject concurrent large requests). The supervisor is not counted.- A request:
id(single use,[A-Za-z0-9][A-Za-z0-9:._-]*),payload(any JSON, ≤ 200 000 chars),reply.required(keys the answer must have), optionalreply.allowed(no other keys accepted), optionalto(a worker: Python routes it, no supervisor turn), optionalinstruction, optionaltimeout_s. Withouttothe supervisor picks a worker or skips the request. - Deadlines: a request without
timeout_snever expires by wall clock (health is the watchdog's: stalls, lost links, API errors);--request-timeout Nsets a default deadline for the run (none by default). A request whose deadline passes is published astimeout; only the worker executing it is held (its late answer is audited asresult.late, never applied), the supervisor and the rest of the fleet keep working (docs/design.md §43). - The answer must be one JSON object. A bad answer gets one correction; a second bad answer is published
as
invalid. Result statuses:ok,invalid,skipped,timeout,stalled. pausestops delivery and rotation; requests keep queuing, and those with a deadline still time out (sendtimeout_sif you must never wait longer). A supervisorBLOCKED, a closed tab or a failed delivery pauses the fleet instead of ending it. Calendars (market hours, holidays) are the caller's business: pause and resume from outside.- Callers on the same machine can skip the CLI: push
{"kind": "request", "request_id": ..., "spec": ...}tofx:eventson the private socket named in~/.fleetmux/latest.json, then readfx:result:<id>.
Worker roster YAML (hot add / remove)
start --roster roster.yaml (instead of --workers / --worker-names) takes the workers from a file,
and the daemon re-reads it while the fleet runs:
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
start --worker-kit KIT --kit-fleet NAME takes the roster template from KIT/fleets/NAME/ instead: the
whole directory is copied to <run>/fleet-config/ and the run follows that copy exactly like
--roster <run>/fleet-config/roster.yaml; --resume keeps the copy (docs/design.md §30).
- Add: a new name is written to
config.json(andlatest.jsonif it points at this run), then spawned with the READY prompt as a tab at the end of the worker column. The supervisor sees the new list appended to its next message ([Roster change] Workers you can dispatch to now: ...). - Remove: the worker takes no new work (dispatch to it is rejected), finishes what it already has, and when idle with an empty queue its screen goes to the history page and its tab is closed. Its workspace directory stays. Putting it back in the file before that cancels the removal.
- Role change for an existing worker applies at its next generation; it is not restarted.
- The supervisor edits it too: every supervisor prompt has an "Adding and retiring workers (the roster)"
section naming this run's roster file (
{{ROSTER}}; runs started with--workers/--worker-namessay it is not available). It adds a row- {name: <role>-<n>, role: <path>}when more work is ready than free workers, deletes one when a worker has no more work, within the limits of its task brief, and dispatches to a new worker only once it is in a[Roster change]list. A rejected edit reaches it as[Roster rejected] <errors>; the roster in effect is unchanged: <workers>(once per rejected file). A Codex supervisor's sandbox gets the roster's directory as a writable root. - Rejected as a whole (roster kept, reasons in the daemon tab as
roster.rejected): invalid YAML, unknown keys, bad or duplicate names, zero workers, a missing role file, or a name already used in this run (a removed worker's name cannot come back). Changes are logged asroster.changed. - Handoff threshold per model (docs/design.md §22): optional
handoff:table and per-workerwindow/handoff_ratio; edits apply at the next delivery check, no restart. - Advisor switch: optional top-level
advisor: on|off, default off. While on, the daemon consults the advisor (official Claude CLI, fable/high) before every supervisor dispatch; it picks the worker model / effort / domains and authorizes the respawns its sheets need. Off: dispatches go out with the slot defaults, exactly as without the advisor. Flipped while running without a restart, by editing the roster, byfleetmux advisor on|off [--config <run>/config.json](only edits the roster; a run started without--rostergets<run>/roster.yamlfrom its current workers), or by the supervisor's decision key"advisor": "on"|"off".start --advisoronly writesadvisor: oninto the roster (a--rosterfile is edited in place). On at start: Claude workers open their pane but launch the CLI only when the first dispatch's sheet arrives. Turned off: workers still waiting for a sheet start with the defaults; running workers keep their generation. Logged asadvisor.on/advisor.off(who), and the daemon tab'sSTATEline showsadv=on|off. - Companion tabs (docs/design.md §31): optional top-level
tabs: [{name, command, cwd?, env?}](name^[a-z][a-z0-9-]*$, unique; cwd relative to the roster,~ok; env strings) - terminal programs (not agents) opened as tabs after the control tab of the loading fleet's workspace,--focus false. On every fleet start (start,--resume, a sub-fleet'sdaemon --resume) each running process whose command line exactly equals the tab's program is SIGTERMed (SIGKILL after ~5 s) before the tab opens; matching rule in §31. Closed bystop/close_all; a tab closed while the daemon runs is not reopened until the next start. Programs run as the fleet's user: the roster is the authorization. - Needs PyYAML in the Python that runs the daemon.
Handoff window and ratio per model
The session is rotated when used >= ratio × window. Each agent resolves window and ratio
separately, first hit wins, using its current runtime CLI and model (after a profile switch too):
- the roster worker entry:
{name: exit-1, window: 1000000, handoff_ratio: 0.7}; - the roster
handoff:table, key<cli>:<model>(exact model string) then<cli>; - the same
handoff:table inconfig.json(static); - built-in: Claude models whose name contains
[1m],opus,fableorsonnet→ 1,000,000; other Claude models →--claude-window(default 200,000); Codex / Pi → the window they report themselves; --handoff-ratio(default 0.6).
handoff:
claude:opus: {window: 1000000}
claude: {window: 200000, ratio: 0.6}
codex: {ratio: 0.6}
A bad value (ratio not in (0, 1), window not a positive integer, unknown key or CLI) rejects the
whole roster change. status shows context.handoff_window / handoff_ratio / handoff_source
per agent; the daemon tab prints handoff=<ratio>@<window>.
Resume an interrupted run (start --resume)
fleetmux start --resume <run-dir> reattaches an unfinished Redis v2 run instead of creating one;
--resume auto takes the run in ~/.fleetmux/latest.json and starts a new run when that one is
finished (<run>/result.json exists) or missing. A live daemon makes it a no-op. Keeping the daemon
alive is the supervisor's job inside the fleet (design §36), not an external timer's.
- Redis restarts from the run's own directory and socket; AOF brings back everything up to the last fsync (about 1 s). A Redis that is still up is reused.
- Official Claude workers whose tab still runs the CLI are reattached; every other agent (the task
supervisor, Pi agents, closed tabs) starts as its next generation (
worker-1->worker-1.1) in place. - Task: verified artifacts and accepted reports are kept; dispatches that were queued or in flight are
re-queued by the daemon, and the new supervisor gets a summary (goal, artifacts verified/missing,
completed dispatches with report excerpts, re-queued dispatches). Service: accepted requests without a
result are routed again; a request with
"replay": falseis published astimeout. --max-attempts N(default 2, stored in config, can be changed on resume): one dispatch / request runs at most N times in total; over the cap the task isblocked/ the request istimeout, naming the item.- A finished task (any terminal status) or an explicit
stopwrites<run>/result.json; such a run is never resumed. Without--resume, a restarted daemon still holds uncertain work and pauses (no replay). - The fleet looks after itself (docs/design.md §36): a Pi supervisor's fleet extension checks the daemon
heartbeat every 2 minutes (
fleet.yaml#daemon_watch); when it is stale and no daemon holds the run, the supervisor gets one[Daemon watchdog]turn with the facts from the logs and runs the exactstart --resumecommand its AGENTS.md names for this run. The daemon in turn restarts a frozen supervisor or one whose CLI exited, and briefs the next generation from the logs (supervisor.revived). No cron or launchd job is needed.
Interface contracts (task spec "contracts": true)
Opt-in per task; without the key nothing changes. Rules: docs/design.md §18.
- P0 – interfaces. The supervisor's first reply is
phase: "interfaces"with a plan (M0…Mn) and every interface (path in the producer's worker directory,contains/json_keys,writers,decisions, a self-passingsample), and no dispatch. The daemon rejects each gap by name. - P1 – skeleton run. The daemon sends
[skeleton]toskeleton_worker(task spec, default the first worker);<worker>/skeleton.mdneeds one PASS line per interface and no FAIL. Pass → interfaces frozen at v1; fail → back to P0; a second fail blocks the task. - P2 – parallel. Dispatch items carry
id,provides,consumes(id@version),edits,depends_on+serial_reason; the daemon holds dependent items until their prerequisites are done. A worker'sInterfaces: IF-x@n provided: readyline is checked literally before the interface isreadyand consumers get[contract] ready. A trailing{"interface_change": ...}object is a proposal the supervisor approves or rejects; breaking changes deprecate the old version and mark its consumers stale. - COMPLETED also needs every non-deprecated interface
ready. Contract tasks may addartifacts[].not_containsandartifacts[].cmd_exit(argv, no shell, run in the shared output directory,timeout_sdefault 120 / max 600; output written topath).
The program writes <run>/contracts.json (copied to <workspace>/contracts.json) and
<run>/contracts.log.jsonl; agents only read them. Each agent directory gets CONTRACTS.md.
Peer notices (contract tasks only; docs/design.md §21): a producer tells the consumers of an
interface it changes (UNAPPROVED) and finishes (READY) directly. Two Claude Code workers use
Claude Code's own SendMessage (session names f<root token>-…-worker-<n>, listed per consumer in
contracts.json consumers_runtime); any pair with a Pi / Codex end uses the broker
fleet.py peer --from <me> --to <worker|consumers:IF-x> --iface IF-x --text-file <own file>, which
validates the fixed first line, 600 chars, READY-by-producer and 20/hour, puts [peer] … in the
target's inbox and copies the supervisor ([peer CC], appended to its next business message). A Claude
turn woken by a peer message answers no delivery: logged as unsolicited, never a report.
Sub-fleets (a worker becomes a sub-fleet supervisor; development)
A busy worker can be turned into the supervisor of its own fleet for one numbered piece of work
(M0, M1, ...; a sub-fleet of M0 numbers M0.0, M0.1, ...). Details: docs/design.md §19.
- Each fleet has its own cmux registry (
fleetmux,fleetmux:M0, ...), daemon, private Redis and run directory (<main run>/sub/M0). Only the main fleet is written tolatest.json. - The sub-fleet is a separate workspace right after its parent in the sidebar, titled
<main fleet name> › worker-1 › 子 fleet M0("sub-fleet M0";start --namesets the name; default: the task goal or the run token). Its supervisor is the worker's live tab (same Claude session, moved top-left); the parent keeps a placeholder tab in its place. When the sub-fleet finishes, the tab moves back and the worker continues asworker-1of the main fleet. - Limits (program-enforced, reasons returned):
max_depth2,max_children3,max_agents_total12,child_workers_max3; override in configlimitsor in the roster YAMLlimits:(read on each check). - Only official Claude Code worker tabs can be handed over (a Pi bridge cannot reconnect to another Redis).
statusshows the whole tree;stopon the main fleet stops sub-fleets first;start --resumeon the main fleet restarts sub-fleets that were running.- Test / operator entry (the owning daemon executes it):
fleetmux subfleet spawn --slot worker-1 --task-id M0 --goal "..." --workers 2 [--output /abs/path]. - Normal path (§20): a worker whose task is too big ends its report with
{"escalate": "<why>", "proposal": [{"id": "M0.0", "instruction": "...", "output": "m0.0.md"}], "workers": 2}; the supervisor answers"escalations": [{"task": "M0", "decision": "approve|revise|reject", "workers": 2, "note": "..."}]; on approve the program starts the sub-fleet. The task needs an id: contract tasks always have one, plain tasks need"id": "M0"on the dispatch item. In contract tasks the sub-fleet runs its own P0 / P1; its interfaces take effect at once and are reported to the top supervisor, which mayvetothem.
Claude Code workers (optional)
On Redis v2 the default supervisor is Pi on openai-codex/gpt-6-astra with official Claude Code workers (see "Redis v2
development runtime"); the official Codex supervisor stays available with --supervisor-cli codex. This section describes the
legacy v1 Claude worker path. Workers can be official Claude Code CLI sessions instead:
fleetmux start --workers 2 --worker-cli claude --worker-model sonnet --worker-kit ~/my-kit \
--lifecycle <mode> --allow-unsandboxed-pi --allow-development-v2
With no --supervisor-cli this now gets the default Pi supervisor (openai-codex/gpt-6-astra), hence
--allow-unsandboxed-pi.
- Each worker runs
claude --dangerously-skip-permissions --model <model> --settings <hook> --add-dir <workspace> [--mcp-config mcp.json] -- <prompt>. - Turn ends come from a Claude Code Stop hook (
fleet.py claude-stop) instead of Codexnotify. A Stop that still listsbackground_tasks(subagents running in the background) is ignored; the session stops again when they finish. - Context usage and the stall watchdog read the session transcript (plus its
subagents/transcripts). - Multi-line messages are written to
<worker>/.inbox/msg-*.mdand the tab gets a one-line pointer: Claude Code would submit a pasted newline as Enter. - The "trust this folder?" prompt is pre-answered in
~/.claude.json; the scope file is also written asCLAUDE.md. --worker-kit DIRis copied into every worker cwd:DIR/.claude/{agents,skills}andDIR/mcp.json.
Pi agents (v1 compatibility by default, Pi >= 1.1.0)
Pi can run the supervisor, workers, or both. Claude workers still run the official claude CLI
as a separate process started by fleet. Pi does not proxy Claude requests or consume Claude Code's
subscription credentials. This separation is not a guarantee against account limits or enforcement;
follow the providers' terms.
Example: a local-model Pi supervisor with official Claude Code workers:
fleetmux start --lifecycle service --workers 2 \
--supervisor-cli pi --pi-model local/my-model --pi-thinking off \
--worker-cli claude --worker-model sonnet \
--allow-unsandboxed-pi --allow-development-v2
The explicit --supervisor-cli pi takes its model from --pi-model (the table default is not read).
Configure local/my-model in Pi's models.json first. --pi-model is required (no implicit
fallback to a personal/cloud default) for every Pi agent that has no model of its own. To use the same Pi model for workers instead, select
--worker-cli pi. An all-Pi fleet needs neither a Codex binary nor a Codex profile.
-
Per-agent Pi models. Model, highest first:
- supervisor:
--supervisor-pi-model(or the model an old profile maps to,--supervisor-profile fleet-astra→openai-codex/gpt-6-astra, only when--supervisor-cliis not given); then an explicit--pi-model; then, only when--supervisor-cliis not given (pi taken from thefleet.yaml#cli_pairsdefault),default.supervisor_pi_model. A flag you typed is never overridden by the table default. An explicit--supervisor-cli pidoes not read the table default: give--supervisor-pi-modelor--pi-model. Sostart --pi-model Xwith no other CLI/model flag runs the default Pi supervisor on X; to keep Astra while workers use X, also pass--supervisor-pi-model. - worker: a live profile card's
model; then its roster rowpi_model:(Pi workers only, not together withprofile:); then--pi-model. - Thinking is resolved separately in the same way: supervisor =
--supervisor-pi-thinking(or the mapped profile's)explicit
--pi-thinking>default.supervisor_pi_thinking(defaulted supervisor only) >off; worker = card > rosterpi_thinking:>--pi-thinking>off. E.g.--pi-model Xalone gives the default supervisor X with thinkinghigh; adding--pi-thinking lowgives X / low. - A Pi agent left without a model refuses the start:
--pi-model provider/model is required for pi agents (or --supervisor-pi-model / roster pi_model); no model for: .... --supervisor-pi-model/--supervisor-pi-thinkingwith a non-Pi supervisor (--supervisor-cli codex|claude) are refused, not ignored. With an explicit--supervisor-cli pi,--supervisor-profile Xis refused (use--supervisor-pi-model); old-profile mapping applies only to a defaulted supervisor.- Changing a row's
pi_model/pi_thinkingin the roster file moves that worker to its next generation on the new model once idle (same slot name).
- supervisor:
-
--pi PATH: Pi CLI binary, >= 1.1.0 (theagent_settledlifecycle is required). -
--pi-agent-dir DIR: source Pi directory, default$PI_CODING_AGENT_DIRor~/.pi/agent. Only itsauth.jsonandmodels.jsonare linked into a fresh<run>/pi-home. Credentials are not printed. No Claude configuration/credentials are copied. Model-file credential commands are executable configuration: use a trusted source. -
Discovered extensions, skills, MCP, prompt templates, themes, project settings and context files are disabled. Only the bundled bridge extension loads, and the generated
AGENTS.mdis explicitly appended to the system prompt. Pi's read/bash/edit/write tools remain enabled. -
Pi has no OS sandbox.
--allow-unsandboxed-piis an explicit acknowledgement, not a sandbox switch. Role instructions are not a security boundary. Use a dedicated account/VM for untrusted tasks. -
Pi session names and terminal titles use the fleet's existing label (
supervisor-1.1,worker-1.1, etc.), not the cwd or a separate naming scheme. The original slot/generation/history rules stay unchanged. -
In the supervisor TUI, ordinary typed text enters the daemon's existing request/task queue instead of bypassing it. In service mode Python assigns a real request ID, the worker answers in a
replyJSON field, and the result is queued back to the supervisor for reporting. In task mode text starts a task or supplies a follow-up to the current task. Slash commands and daemon inbox pointers are not re-enqueued; images are rejected with a message (supply a file path instead). -
Pi reports completion only on
agent_settled, after tools, retries and automatic continuations. Generation plus inbox delivery ID reject stale/duplicate completions, including after Escape. The ID comes only from the final daemon pointer in this agent's own.inbox, never an inbox path quoted in the instruction text (e.g. a supervisor's path forwarded to a worker). An unsuccessful startup never counts as READY; error/aborted partial JSON is never accepted as a result. -
Context usage comes from Pi's
getContextUsage(); each generation writes its session under<run>/pi-sessions/<slot>/g<N>/. Fleet still handles idle-boundary rotation and history; Pi's automatic compaction remains a safety net within a long turn.--compact-limitis Codex-only. -
The Pi bridge callback runs through the fleet Python interpreter and only its private Redis Unix socket, not
localhost:6379. A callback failure is shown by Pi; the fleet watchdog eventually handles a missing completion.
Optional live adapter smoke (uses a temporary private Redis, fake cmux, real Pi/model; no Claude calls):
python tests/pi_smoke.py --model local/my-model
This tests READY, JSON request/result and handoff to a new generation. It is not a real cmux TUI test.
Redis v2 development runtime
V2 now connects the existing Fleet dispatcher to persistent Pi bridges. Select it explicitly
on a fresh isolated development run with --transport-version 2 --allow-development-v2
and --supervisor-cli pi --worker-cli pi, plus the existing explicit model/permission options.
Default pair: Pi supervisor + official Claude Code workers. Codex models and FlashNext
run through Pi (ChatGPT login via Pi's openai-codex provider); Claude agents only ever run the official claude CLI, and
Pi never uses Claude subscription credentials. With no --supervisor-cli the supervisor is Pi on
openai-codex/gpt-6-astra, thinking high (fleet.yaml#cli_pairs default.supervisor_pi_model / supervisor_pi_thinking;
--supervisor-pi-model / --supervisor-pi-thinking, or an explicit --pi-model / --pi-thinking, override them;
an explicit --supervisor-cli pi skips the table default and needs --supervisor-pi-model or --pi-model; full order
under "Pi agents"). No --pi-model is needed while
the workers are Claude. --allow-unsandboxed-pi is still required:
fleetmux start --worker-model sonnet --workers 2 --lifecycle <mode> \
--allow-unsandboxed-pi --allow-development-v2
The pi+claude task/service rows are verified (real scripts/smoke_pair.sh pi claude task|service, 4/4 checks,
runs fleet-20261013-115145-828661b9 / fleet-20261013-115347-7e61f9af, 2026-10-13, uncommitted branch tree). Night pair: Pi Astra supervisor + Pi FlashNext workers
(the verified pi+pi rows, per-agent models; --supervisor-pi-model is required here, otherwise --pi-model would put the
supervisor on FlashNext too):
fleetmux start --supervisor-cli pi --worker-cli pi \
--supervisor-pi-model openai-codex/gpt-6-astra --supervisor-pi-thinking high \
--pi-model flashnext/<local-model> --workers 2 --lifecycle <mode> \
--allow-unsandboxed-pi --allow-development-v2
--supervisor-profile fleet-astra (or --profile fleet-astra while the workers are not Codex) without
--supervisor-cli is the old Codex-profile spelling: it now starts the Pi supervisor on openai-codex/gpt-6-astra
(thinking high) and prints a notice to stderr; combined with a different --supervisor-pi-model it is refused. Any other
profile name there is refused with the two new usages (--supervisor-cli codex --supervisor-profile X, or
--supervisor-pi-model provider/model). It never silently starts the official Codex TUI.
Optional: official Codex supervisor + official Claude Code workers (task and service; the previous default)
also run on v2.
The daemon receives each delivery from Redis and types only its business text into the official
TUI (Claude panes get a bracketed paste); no .inbox file and no transport ID reach the model.
The Codex notify / Claude Stop hooks bind the session on READY and then settle only the delivery
this generation received, only when the typed text started that turn; the daemon validates it as for Pi.
Agents keep the original AGENTS.md reply formats. Six parallel Claude workers with an official Codex Astra supervisor:
# once: a Codex profile file for the supervisor model (Codex >= 0.156 reads ~/.codex/<name>.config.toml)
printf 'model = "gpt-6-astra"\nmodel_reasoning_effort = "high"\n' > ~/.codex/fleet-astra.config.toml
fleetmux start --supervisor-cli codex --supervisor-profile fleet-astra \
--worker-cli claude --worker-model sonnet --workers 6 --max-busy 0 \
--lifecycle <mode> --allow-development-v2
fleetmux task goal.json
Validated locally with --claude pointing at the real Claude Code binary; the cmux claude shim found
first on PATH inside cmux was not exercised. Pairs and their status (verified / unverified = --lifecycle smoke
only / refused) live in fleetmux/config/fleet.yaml#cli_pairs; Codex workers (codex+codex) are still unverified, and any
pair the table does not list is refused with a reason before provisioning or profile retirement.
New start commands now require Redis v2 (the default); --transport-version 1 is rejected. Existing v1 configs remain readable for draining/status/stop only: do not edit their transport version in place. The file-delivery and native-Codex/Claude startup descriptions above are legacy behavior, not a supported new v2 launch path. Unsupported CLI combinations fail rather than silently falling back to files. The development opt-in is still required.
V2 deliveries use run-scoped Redis Streams and full business context rather than message files
or cmux paste. Structured fleet_decision / fleet_result tools collect candidates; settled plus
daemon validation commits them. UI cards are non-context session entries. Simple replies and
mechanical failures cause no supervisor reporting turn. Task reports still require supervisor
judgment and independent artifact acceptance. External request/result JSON remains compatible.
Timeout, interruption or disconnection holds the slot/PEL until reliable retirement confirms stop.
In task mode a Claude worker's API error (StopFailure) or a lease the daemon could not renew (e.g. system
sleep) holds only that slot, never the whole fleet; the API-error turn is retried on the same slot after a
backoff, within --max-attempts (docs/design.md §24). In service mode the same StopFailure fails the request at
once (never re-sent by the fleet) and holds only that slot for a backoff; a model refusal also gives the slot a fresh
session, and service_stopfail_max (default 3) consecutive failures keep it held and tell the supervisor.
A same-run daemon restart retains sessions and pauses uncertain work; it never starts a second set
of agents or shuts Redis down as crash cleanup. Explicit v2 fleet stop retires runtimes before
shutting down Redis. New v2 Redis uses AOF everysec, with about a one-second host-crash loss window.
fleetmux logs --config <isolated-config> --request <id> --slot worker-1 --generation 0 --event result --level error --json --follow reads audit detail through the independent control
reader; it never ACKs work. Control failure does not determine scheduler commit.
This is a development integration, not production acceptance. The local merge candidate passed the full regression suite, independent real-Redis race probes and installed Pi SDK/TUI contracts. An earlier frozen candidate passed a real Redis + Fleet + Pi/FlashNext TUI smoke on owned PTYs (cmux layout simulated); the final error-audit rerun was refused by the model server's memory guard. Prepared effect intents can replay; ambiguous Redis script failures hold the run for inspection. Full crash/slow-UI coverage, 1000-message end-to-end accounting, performance, capacity/AOF and mixed-CLI validation remain gates. V2 browser-broker execution currently rejects explicitly. See the design baseline.
Run the isolated acceptance suite on a parent that permits temporary Redis sockets:
python3 -m pytest -q tests/test_runtime_v2.py tests/test_message_bus_v2.py
python3 -m pytest -q tests/test_pi_v2_contract.py tests/test_runtime_v2_planning.py
python3 -m pytest -q tests
These tests use freshly created /tmp/fleetmux-<token>.sock instances with port=0, fake cmux
and no model/GUI/production registry. The installed Pi packages are required by the TS harness.
How a task flows
taskputs the goal in the supervisor's inbox. The daemon pastes it into the supervisor tab.- The supervisor replies with one JSON object:
{"status": "RUNNING", "completion": 30, "reason": "...", "dispatch": [{"to": "worker-1", "instruction": "..."}], "artifacts": [...]}. Bad JSON three times in a row blocks the task. - Each
dispatchitem goes to that worker's inbox and is delivered when the worker is idle. - A worker's reply comes back to the supervisor tagged
[worker-1 report]. COMPLETEDis checked against the declared artifacts (user-declared ones count). Failures bounce back as "Independent verification failed"; three bounces block the task.BLOCKEDstops the task and leaves the reason instatusfor a human.
Agent scope files
fleetmux/agents/supervisor.md and worker.md are copied into each agent's cwd as AGENTS.md at
spawn ({{WORKSPACE}} and {{FLEET_CHROME}} are filled in). They contain only prohibitions, a command
table and the reply format; no methodology. Models follow prohibitions and tables far more reliably than
prose about "how to think".
Optional: browser broker
Sandboxed agents cannot drive the user's browser (and should never be able to kill it). If you have a
script that implements the verbs check, tabs, newtab <url>, go <tab> <url>, url <tab> and
js <tab> <file>, pass it with start --chrome-script /path/to/broker. Workers then call
fleet.py chrome <verb> ... (the exact command is written into their AGENTS.md), the daemon validates
the verb, confines js files to the run directory, paces page actions (--chrome-min-interval, default
20 s) and runs the script on the host. Without --chrome-script the verbs are refused.
Redis keys (private instance only)
| Key | Type | Content |
|---|---|---|
fx:events |
list | daemon BLPOP: turn (CLI callback), pi_session; task / request / operator_input / pause / resume / stop / chrome (with request_id) |
fx:reply:<request_id> |
string | request reply (24 h) |
fx:agent:<name> |
hash | role, pane, thread, generation, state (starting/idle/busy/handoff/dead), ctx_used, ctx_window, busy_since, stalls, handoff_* |
fx:inbox:<name> |
list | pending messages; delivered only when the agent is idle |
fx:replies:<name> |
list | last 5 replies (used for the handoff skeleton) |
fx:task |
hash | id, goal, status, completion, reason, deadline, artifacts, bad_replies, bounces |
fx:contracts |
string | contract tasks only: JSON phase / plan / registry / proposals / task table / per-worker report owners |
fx:log |
list | event log, last 2000 |
fx:seen:<sha256> |
string | notify de-duplication (SET NX, 24 h) |
fx:service |
hash | paused, reason, history_pane, bad_replies |
fx:req:<id> |
hash | an open request: spec, status (routing/dispatched), worker, created, attempts |
fx:reqq |
list | ids of open requests (timeout scan) |
fx:result:<id> |
string | finished request result JSON (7 days) |
fx:results |
list | finished results in order, last 1000 (results --drain pops them) |
Test status
- 2,000+ tests on macOS (Python 3.14):
python -m pytest -q teststakes about 13 minutes. Tests that needredis-server,node,tmuxor an installed Pi skip themselves when those are missing. - Every supervisor × worker CLI pairing marked
verifiedinfleetmux/config/fleet.yaml#cli_pairspassed a real smoke run (scripts/smoke_pair.sh: 1 supervisor + 2 workers, 4 checks); the run id is recorded in the row. - The fleet has been run as resident service fleets and scheduled task fleets on three Macs since October 2026; the incidents those runs found are pinned as regression tests (search the tests for the dated comments).
Module map
| Module | Role |
|---|---|
fleet.py |
CLI entry point and the daemon: dispatch, acceptance, rotation, brake hooks (large; split by concern is planned) |
runtime_v2.py, message_bus.py, agent_bridge.py |
Transport v2: private Redis message bus and the per-agent bridges |
cmux_manager.py |
cmux tabs and splits; every agent can be closed with the ✕ |
watchdog.py |
Five-state stall detection per slot |
fleet_config.py |
The one config file: shipped blocks plus the private override |
brake.py |
Budget cards, private account pools, usage-wall table |
contracts.py |
Interface contracts between parallel workers |
subfleet.py, named_subfleet.py |
Child fleets supervised by a worker |
roster.py, router.py, agent_registry.py, model_usage.py |
Worker roster, profile routing, model cards |
codex_queue.py, opencode_adapter.py, pi_extension_v2.ts |
CLI-specific delivery adapters |
control_view.py, history.py, presentation.py |
Read-only control page and the history of retired sessions |
lifecycle.py |
Lifecycle modes: what "done" means for a run |
lifecycle_deliver.py, lifecycle_close.py |
dev_task delivery gate (review / tests / merge / enqueue) and run closing |
session_board.py |
Cross-session board: independent Claude Code / pi sessions record what they touch and get one collision notice per side when two edit the same repository file or merge into the same branch — first come keeps going, the later one waits for the fleet supervisor's release (session_board.py list / note); hook session_board.py hook claude, pi extensions/session-board.ts; contracts §7 |
Every dev_task gate writes its test_cmd run to <run>/gate/<task_id>-tests.log: a header with the
command, cwd, start time and exit code (or timeout / start failure), then the full stdout+stderr,
capped at 20 MiB as the first 1 MiB plus the last 19 MiB with a truncation marker. A failed or
timed-out gate names that path in its problem (tests failed (exit N); log: <path>).
What the live run taught us (already fixed)
- Typing a ~1.5 kB command into a fresh login shell truncates it (macOS tty line limit before the
line editor is up). Spawns now go through a short launcher script and cmux's
--command. - Codex asks "Trust this folder?" per exact directory and does not accept the trust table as a
-coverride. The fleet runs agents in its ownCODEX_HOMEand writes the trust entry per agent. - Running agents in your personal Codex home also runs your hooks, MCP servers and plugins inside them; a failing Stop hook blocked the READY turn. The isolated home fixes this too, and the hook-trust bypass flag is gone.
- A fresh session whose baseline usage already exceeds
--handoff-ratiowould be rotated forever (observed: 21 generations in ten minutes at a 1 % test ratio). Fresh sessions are never rotated; the daemon logsrotate.impossibleand delivers anyway.
Known limitations
- On macOS the default backend is cmux (WezTerm without cmux); on Linux it is fleetmux's own terminal (see
Terminal backend). On macOS the daemon uses
caffeinatewhen available so the machine does not sleep. - A new cmux workspace briefly steals focus when it is created (cmux ignores
--focus falsefornew-workspace); the daemon switches you back. - Codex keeps no streaming log of a tab; when a tab is retired its final screen is saved next to the
handoff note. The full transcript is Codex's rollout file under
~/.codex/sessions. - Models occasionally do more than one dispatched step in a turn. The artifact check catches the outcome, not the overreach.
Development
pip install -e '.[dev]'
pytest
Most tests use in-memory Redis, fake cmux and synthetic transcripts. Redis v2 integration tests start only owned temporary Redis processes; the TS contract harness starts a JSONL fixture child. They do not start a model or a real cmux fleet.
License
MIT
Metadata
Release files for fleetmux 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Actions Reset | |||
|---|---|---|---|---|
| fleetmux-0.2.2-py3-none-manylinux_2_39_x86_64.whl | Python 3 | none | Linux glibc 2.39+ x86-64 | Details for fleetmux-0.2.2-py3-none-manylinux_2_39_x86_64.whl |
| fleetmux-0.2.2-py3-none-manylinux_2_39_aarch64.whl | Python 3 | none | Linux glibc 2.39+ ARM64 | Details for fleetmux-0.2.2-py3-none-manylinux_2_39_aarch64.whl |
| fleetmux-0.2.2-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details for fleetmux-0.2.2-py3-none-macosx_11_0_arm64.whl |
| fleetmux-0.2.2-py3-none-macosx_10_12_x86_64.whl | Python 3 | none | macOS 10.12+ x86-64 | Details for fleetmux-0.2.2-py3-none-macosx_10_12_x86_64.whl |
Total release size: 14.1 MB
Release files / fleetmux-0.2.2-py3-none-manylinux_2_39_x86_64.whl
| Download URL | fleetmux-0.2.2-py3-none-manylinux_2_39_x86_64.whl |
|---|---|
| Size | 3.6 MB |
| Tags | Linux glibc 2.39+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
bbadbb79b2c3d7002ae95da381c9c9c2a7b27cfa105454651258f0c167ad9ce1
|
|
BLAKE2b-256 checksum How to use checksums |
6bbcc93c2cc407a9f216ee3fb86b97f7f146aa2430b447a378b7be39e63faa26
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / fleetmux-0.2.2-py3-none-manylinux_2_39_aarch64.whl
| Download URL | fleetmux-0.2.2-py3-none-manylinux_2_39_aarch64.whl |
|---|---|
| Size | 3.6 MB |
| Tags | Linux glibc 2.39+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
03fe00c0cee8ce17db38da17a3c50d7266cb6902f928f13407d992880f6f3689
|
|
BLAKE2b-256 checksum How to use checksums |
c68b3d6a87e3b0476805bb3b098bce2a875a8d747588a26db87aa0051cc1f592
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / fleetmux-0.2.2-py3-none-macosx_11_0_arm64.whl
| Download URL | fleetmux-0.2.2-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 3.4 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
980505fd93a3ec1aa156458280711f2a2b0a035dc27196bf855dc03ea0fb2220
|
|
BLAKE2b-256 checksum How to use checksums |
34ec65f3ba3effaa5805acbfb9f81bafe9128d9f6ff7eb132d72c936f08b1b3c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / fleetmux-0.2.2-py3-none-macosx_10_12_x86_64.whl
| Download URL | fleetmux-0.2.2-py3-none-macosx_10_12_x86_64.whl |
|---|---|
| Size | 3.5 MB |
| Tags | Python 3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
1a8d6261d623d312db00db03f2e67ecdc7ee16e0b420da18d3c746cb09f415c7
|
|
BLAKE2b-256 checksum How to use checksums |
9ad472a18107671b2b236db6b71af9d26940b1ad4a47e884a1de93d3cb157547
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for fleetmux 0.2.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.