fleetcom has two kinds of state with different lifetimes: the daemon owns live processes, while session files store repeatable launch recipes. Confusing those boundaries makes shutdown, reconnect, and session behavior difficult to reason about. This guide documents the paths, the lifecycle, and a complete first run.
- Commands: every key and launch flag, including the routing mechanics
- How it works: the PTY emulation, input routing, and activity grouping
- Sessions: the task recipe format and where it lives
- Agent session resume: when
fleetcomcan save resumableclaude,codex, andgrokcommands - Storage paths: the socket, the lock, and the session paths
- First-run walkthrough: a first run, start to finish
- Operational constraints: process and protocol boundaries
fleetcom is Unix-only: it relies on PTYs and process-group signals (killpg).
From a repository clone:
cargo test: confirm the suite passescargo build --release: compile totarget/release/fleetcomcargo install --path .: putfleetcomonPATH
Runtime state contains the daemon socket and lock. Configuration contains durable session recipes. The paths resolve independently.
The runtime directory holds default.sock, the mode-0600 client↔daemon socket, and daemon.lock, the single-instance flock. The daemon records its PID in the lock file; --kill uses that PID rather than waiting for the socket. fleetcom creates the directory with mode 0700. An existing path must be a real directory owned by the current user, so symlinks and directories owned by another user are rejected.
Resolved in this order:
| Order | Condition | Path |
|---|---|---|
| 1 | FLEETCOM_RUNTIME_DIR is set |
$FLEETCOM_RUNTIME_DIR (verbatim) |
| 2 | $XDG_RUNTIME_DIR is set and non-empty |
$XDG_RUNTIME_DIR/fleetcom |
| 3 | otherwise | $TMPDIR/fleetcom-$uid |
$XDG_RUNTIME_DIR is honored on every supported platform. On macOS, $TMPDIR is already per-user. The $uid suffix also separates users when the fallback resolves beneath a shared /tmp.
Holds saved sessions under a sessions/ subdirectory: one sanitized-name .json file per session. See Sessions for the format.
| Order | Condition | Path |
|---|---|---|
| 1 | FLEETCOM_CONFIG_DIR is set |
$FLEETCOM_CONFIG_DIR/sessions |
| 2 | Linux | ${XDG_CONFIG_HOME:-~/.config}/fleetcom/sessions |
| 2 | macOS | ~/Library/Application Support/fleetcom/sessions |
The platform default is dirs::config_dir() joined with fleetcom. The first save creates missing session directories with mode 0700; recipe files use mode 0600.
The following walkthrough moves from an empty dashboard to a saved fleet. The frames show layout, not captured terminal output.
Run fleetcom. The first invocation starts the daemon and opens an empty dashboard:
fleetcom 0 running · 0 idle · 0 done by state · dir · custom
❯ n run · @ dir · s sort · w save · o load
↑↓ select · enter attach · space peek · m tag · g group · R rename · r rerun · X kill · q detach · Q quit
Press n, enter a command, and press Enter. The command runs in its own PTY and appears under Running. Repeat the process for a second command:
fleetcom 2 running · 0 idle · 0 done by state · dir · custom
Running
✻ cargo watch -x test test result: ok. 42 passed 9s
✻ npm run dev VITE v5.0 ready in 312 ms 4s
❯ n run · @ dir · s sort · w save · o load
↑↓ select · enter attach · space peek · m tag · g group · R rename · r rerun · X kill · q detach · Q quit
Each row is glyph · tag · command · latest output · age. The age counts from the task's last meaningful edge: launch while running, last output once idle, exit once completed. Space peeks: a read-only box of the selected task's live screen, without leaving the dashboard:
┌─ cargo watch -x test ───────────────────────────────┐
│ running 3 tests │
│ test result: ok. 42 passed; 0 failed │
│ │
└ space/esc close · enter attach · preview: floor ────┘
Enter attaches to the task. Keystrokes then go to its PTY, except for the reserved background chord shown in the status bar:
[attached] npm run dev Ctrl-\ background
Ctrl-\ returns to the dashboard. m tags the selected task "in use," adding ◆ and moving it to the first section:
fleetcom 2 running · 0 idle · 0 done by state · dir · custom
In use
✻ ◆cargo watch -x test test result: ok. 42 passed 1m
Running
✻ npm run dev VITE v5.0 ready in 312 ms 1m
s cycles through state, directory, and custom grouping. The header renders the active mode in bold. In custom mode, g assigns the selected task to a named group. Named sections sort alphabetically; Unassigned appears last when at least one task has no group:
fleetcom 2 running · 0 idle · 0 done by state · dir · custom
api
✻ ◆cargo watch -x test test result: ok. 42 passed 2m
Unassigned
✻ npm run dev VITE v5.0 ready in 312 ms 2m
In custom mode, a new command inherits the selected task's group. The spawn prompt makes that destination explicit (❯ api ▸ cargo run). Group assignments belong to task state, so detach and rerun preserve them. The command reference documents the picker mechanics.
R renames the selected task. The prompt opens with the current name; Enter saves, an empty field restores the command as the display label, and Esc cancels. Rows and peek titles then show the name in place of the command:
fleetcom 2 running · 0 idle · 0 done by state · dir · custom
api
✻ ◆api tests test result: ok. 42 passed 3m
Unassigned
✻ npm run dev VITE v5.0 ready in 312 ms 3m
The attached status bar shows both: [attached] api tests · cargo watch -x test. Names are daemon state, survive detach and rerun, and persist in saved sessions.
w, a name, and Enter save the fleet as a session. q then disconnects while the daemon and both tasks continue running. A subsequent fleetcom invocation reconstructs the dashboard from the daemon's current task state. Q or fleetcom --kill stops the tasks (TERM, then KILL after a two-second grace period) and exits the daemon.
Because the daemon holds each PTY master, daemon termination closes the terminals and the kernel sends SIGHUP to every task's process group. A clean shutdown sends SIGTERM before SIGKILL; a crash or direct SIGKILL provides no grace period. HUP-immune processes (nohup, trap '' HUP) can survive, but the next daemon neither owns nor displays them. A panic while serving one client only drops that connection.
($SHELL -c, or /bin/sh when SHELL is unset), so functions and aliases from ~/.zshrc are unavailable.
Each launch uses the launching client's environment and working directory, sent once per connection during the hello handshake. Connect from a venv terminal and your spawns, reruns, and session loads all see that venv, whichever client originally autostarted the daemon. Environment is never written to disk; session files store only directories, commands, group assignments, and display names.
Each task's terminal keeps a scrollback history whose depth is resolved once, when the owning supervisor starts:
| Order | Condition | Depth |
|---|---|---|
| 1 | --scrollback <lines> was passed |
that value, clamped to 100,000 |
| 2 | FLEETCOM_SCROLLBACK parses as a whole number |
that value, clamped to 100,000 |
| 3 | otherwise | 2,000 |
0 disables scrollback. An unparseable FLEETCOM_SCROLLBACK falls back to 2,000 rather than failing daemon startup. The daemon resolves the depth at startup from its inherited environment, so a changed value reaches only the tasks of a new daemon; stop the current one with fleetcom --kill first. --foreground resolves the depth in-process for each invocation.
The daemon rejects a mismatch during the handshake. Stop an incompatible daemon with fleetcom --kill, which also terminates every running task, then start a new client.
A second fleetcom prints a waiting notice, then attaches when the active client disconnects (q). Ctrl-C while waiting aborts without touching the daemon.
X, Q, --kill, and daemon shutdown signals send SIGTERM to each task's process group, then escalate to SIGKILL after two seconds. Removing one task (X) keeps an exited leader unreaped through escalation, reserving the process-group ID so background children remain signalable. During full shutdown (Q/--kill), checking whether a group is empty reaps its exited leader. A TERM-ignoring member that outlives the leader can then become unsafe to signal by group ID and survive daemon shutdown. A child created by cmd & in a non-interactive shell normally remains in its parent's group. A process that calls setsid or otherwise leaves the group is outside the sweep and must be terminated separately.
It runs the core in-process with no daemon, so the tasks die when you quit and there is nothing to reattach to.
SIGTERM/SIGINT/SIGHUP to the daemon group-kill every task, remove the socket, and exit. This is the same teardown as Q or fleetcom --kill.