1
14 Fleet Manager
Dai Ha edited this page 2026-08-31 10:49:14 +07:00

Fleet Manager

fleet-manager gives one host a status view of several fleetd fleets. A fleet entry has a name, a REST address, and an optional project path. The manager is a separate Python package. It uses only REST requests and does not import or run inside fleetd (README.md:3-12, fleet_manager/probe.py:10-13, pyproject.toml:1-9).

This is not multi-host federation. Entries marked remote are not probed. The manager reports them as remote instead (fleet_manager/probe.py:117-121, tests/test_probe.py:31-34).

Install and run

The package needs Python 3.11 or later. Its runtime dependencies are Python standard-library modules only (pyproject.toml:3-6).

For development, create a virtual environment and install the package with its test and lint tools:

python -m venv .venv && .venv/bin/pip install -e '.[dev]'

This command is the documented development setup (README.md:54-59).

The package registers the fleet-status command (pyproject.toml:8-9). It also runs as a Python module:

fleet-status
python -m fleet_manager.cli
python -m fleet_manager.cli path/to/fleets.json

The module command accepts at most the first command-line argument as the registry path. Without an argument, it reads fleets.json beside the package source, not the current directory (fleet_manager/cli.py:54-57). If that file does not exist, it prints setup help to standard error and exits with status 2 (fleet_manager/cli.py:58-62).

fleets.json registry

The registry is JSON with a required top-level fleets array. The command reads reg["fleets"], so a missing fleets key stops the command with a key lookup error (fleet_manager/cli.py:49-51, fleet_manager/cli.py:63-65).

Each array item is one fleet. The following example uses only the sample values:

{
  "fleets": [
    {
      "name": "fleetd",
      "rest": "http://127.0.0.1:8765",
      "project": "/path/to/the/project/this/fleet/works/on"
    },
    {
      "name": "other-host-fleet",
      "remote": true,
      "project": "/path/on/that/host",
      "note": "REST is loopback-only, not reachable from here yet"
    }
  ]
}

The sample shape is from fleets.example.json:1-16.

Field Required Meaning and missing-field result
name Yes The displayed fleet name. A missing value stops probing that entry with a key lookup error (fleet_manager/probe.py:114-115).
rest Yes for a local fleet The base REST URL. A missing value stops a local probe with a key lookup error. A remote entry does not read it (fleet_manager/probe.py:114-115, fleet_manager/probe.py:117-124).
project No An optional project path shown in the status output. When absent, no project line is shown (fleet_manager/probe.py:114-115, fleet_manager/cli.py:32-33).
remote No When true, the manager does not call the fleet and reports remote. When absent or false, it treats the entry as local (fleet_manager/probe.py:117-123).
note No An optional message for a remote fleet. If absent, the manager uses on another host; REST is loopback-only, not probed from here (fleet_manager/probe.py:117-121).

fleet-status

fleet-status is the installed status command. It loads every entry in fleets, probes each one, and prints the result (pyproject.toml:8-9, fleet_manager/cli.py:63-66).

The output has a time-stamped FLEET STATUS heading. For each fleet, it shows the name, status, REST URL, optional project and note, a verdict when the fleet is reachable, and each returned member's role, profile, state, live status, branch, and progress result (fleet_manager/cli.py:27-46).

python -m fleet_manager.cli

This module command runs the same main function as fleet-status (fleet_manager/cli.py:54-70, pyproject.toml:8-9). It uses the package-side default registry with no path, or the first supplied path with one (fleet_manager/cli.py:54-57).

How a probe works

For a local fleet, the manager requests /healthz first. If the request fails, it reports down, adds an /healthz unreachable note, and does not request members (fleet_manager/probe.py:123-129, tests/test_probe.py:37-42). A successful health response supplies the displayed status and optional herdr version (fleet_manager/probe.py:130-131).

It then requests /members. It reads the workers array when present. Each worker can supply profile, role, state, liveStatus, worktree, and branch (fleet_manager/probe.py:133-145).

The manager does not trust a busy liveStatus by itself. For members whose live status is working or busy, it records the newest modification time in the worktree, waits four seconds by default, then records it again (fleet_manager/probe.py:36-55, fleet_manager/probe.py:81, fleet_manager/probe.py:136-150). A later time means working. No change means stalled, with the newest file age. An unavailable worktree gives unknown, not stalled (fleet_manager/probe.py:64-78, tests/test_probe.py:86-92).

A fleet is WORKING when any watched member changed files. It is STALLED when a watched member did not change files. It is busy (no worktree progress signal) when a busy member has no usable worktree. A fleet with no members is idle (no members); otherwise it can be idle (fleet_manager/probe.py:156-166, tests/test_probe.py:45-57).

Limits of the probe

The manager handles several fleet entries on one host. It does not probe a remote entry, so it is not a cross-host status or federation tool (README.md:3-7, fleet_manager/probe.py:117-121, tests/test_probe.py:31-34).

The probe cannot prove that a busy member is making useful progress. It only compares worktree modification times. A member can be busy without a usable worktree, and the result is unknown rather than a progress decision (fleet_manager/probe.py:64-78, fleet_manager/probe.py:143-150, tests/test_probe.py:86-92).

Two limits that come from fleetd, not from this tool

Neither limit is visible in the fleet-manager source, because neither belongs to it. Both were measured against running daemons, and both shape how the manager can be used.

Two fleetd daemons must not share one herdr session. When they do, each daemon's reaper treats the other's panes as orphans and tears them down, so the two fleets kill each other's members. Give each daemon its own herdr session.

The manager must not run inside a herdr pane. fleetd resolves a caller's role from the connection, and it identifies its lead by a tab label. A Claude session sitting in a pane that some other daemon does not name as its lead is resolved as a worker by that daemon, and every orchestration call is then refused. A process outside any pane is resolved as the primary, which is why the manager sits outside and speaks REST.

Together these are the reason the manager is a separate REST client rather than a second daemon or an in-pane session.

What this page could not confirm

The fleet-manager source alone does not describe either limit above, and it was the only source read for the rest of this page. Issues #160 and #156 carry the measurements behind them.