amplihack fleet — CLI Reference¶
Full reference for the fleet subcommand introduced in v0.5.0.
amplihack fleet has two operating modes:
| Mode | How to invoke | Purpose |
|---|---|---|
| Local dashboard | amplihack fleet (no subcommand) |
Interactive TUI for sessions on the local machine |
| Azure VM orchestration | amplihack fleet <subcommand> |
Discover, reason about, and act on sessions across Azure VMs via azlin |
The local dashboard requires a real TTY. The Azure VM subcommands
(scout, advance, dry-run, start) run non-interactively and are
safe to call from scripts and CI pipelines.
Local mode and process restart — The local dashboard tracks UI state (selected row, active filters, editor contents, proposal notices) using
AtomicBoolandArc<Mutex<…>>in-memory flags. None of this state survives process exit. Only thefleet_dashboard.jsonfile (tracked project list) is persisted to disk. If the dashboard exits unexpectedly, the project list is preserved but any in-progress editor content or uncommitted proposals are lost.
Prerequisites¶
| Dependency | Required? | Effect when absent |
|---|---|---|
tmux |
Optional | Capture-pane preview disabled; slow-refresh thread exits silently. Dashboard remains fully functional — session table, project management, and new-session launch all work. |
Synopsis¶
Description¶
amplihack fleet opens a full-screen terminal dashboard that displays all
active Claude sessions on the local machine, lets you launch new sessions,
adopt existing ones, and manage a persistent list of tracked projects.
The command requires a real TTY. It exits immediately with code 0 when
--help is passed, making it safe to call in existence checks.
Options¶
| Flag | Type | Default | Description |
|---|---|---|---|
--no-color |
bool | false | Disable ANSI colour output in the session table |
--refresh-fast |
ms | 500 | Poll interval for the session-list refresh thread |
--refresh-slow |
ms | 5000 | Poll interval for the tmux capture-pane thread |
-h, --help |
— | — | Print help and exit 0 |
Azure VM Subcommands¶
All Azure VM subcommands require azlin to be installed and reachable. Set
AZLIN_PATH if azlin is not on your PATH.
fleet dry-run¶
Analyse sessions on one or more VMs without taking any action. Prints the fleet admiral reasoning report and exits.
| Flag | Type | Default | Description |
|---|---|---|---|
--vm |
string | (all) | Restrict discovery to this VM. Repeat for multiple VMs. |
--priorities |
string | (none) | Free-text priority guidance passed to the reasoner |
--backend |
string | auto |
Reasoner backend: auto, claude, or none |
Example output
Fleet Admiral Dry Run -- 3 sessions analyzed
Summary:
send_input: 2
wait: 1
amplihack-vm-01/work-session-1 [running] -> send_input (87%)
Reason: Tests failing; agent needs a nudge to retry.
Input: "Run the failing tests again and fix any new errors."
amplihack-vm-01/work-session-2 [idle] -> wait (70%)
Reason: Session appears idle awaiting external review.
amplihack-vm-02/work-session-3 [running] -> send_input (82%)
Reason: PR is ready; agent should push and open it.
Input: "Push your branch and open a pull request now."
fleet scout¶
A three-phase operation: discover sessions → adopt them into the task queue → reason about each one with the LLM backend → print a scout report.
amplihack fleet scout [--vm <VM>] [--session <SESSION>] [--skip-adopt] [--incremental] [--save <PATH>]
| Flag | Type | Default | Description |
|---|---|---|---|
--vm |
string | (all) | Restrict to sessions on this VM |
--session |
string | (all) | Restrict to a single session name |
--skip-adopt |
bool | false | Skip Phase 2 adoption; report on already-adopted sessions only |
--incremental |
bool | false | Skip sessions whose status hasn't changed since the last scout run |
--save |
path | (none) | Write the full JSON scout report to this path in addition to stdout |
Phases
| Phase | Label in output | What it does |
|---|---|---|
| 1 | Discovering fleet sessions |
Calls azlin list to enumerate running VMs and tmux sessions |
| 2 | Adopting sessions |
Registers discovered sessions in the local task queue |
| 3 | Reasoning about sessions |
Calls the LLM reasoner for each session; prints the scout report |
Example output
Phase 1: Discovering fleet sessions...
Phase 2: Adopting sessions...
amplihack-vm-01: adopted 2 sessions
Total adopted: 2
Phase 3: Reasoning about sessions...
Reasoning: amplihack-vm-01/work-session-1...
Reasoning: amplihack-vm-01/work-session-2...
============================================================
FLEET SCOUT REPORT
============================================================
VMs discovered: 2
Running VMs: 1
Sessions analyzed: 2
Adopted sessions: 2
Actions:
send_input: 1
wait: 1
amplihack-vm-01/work-session-1 [running] -> send_input (87%)
Branch: feature/auth-refactor
PR: https://github.com/org/repo/pull/42
Project: amplihack-rs
Reason: Agent is idle; tests are passing; PR should be opened.
Input: "Open a pull request for your current branch."
amplihack-vm-01/work-session-2 [idle] -> wait (70%)
Branch: main
Reason: No active task; waiting for assignment.
Incremental mode
On the second and later runs, pass --incremental to skip sessions whose
status matches the cached result from ~/.claude/runtime/fleet/last_scout.json:
amplihack fleet scout --incremental
# Incremental mode: loaded 3 previous statuses
# Skipping (unchanged): amplihack-vm-01/work-session-2 [idle]
fleet advance¶
Reason about all sessions and execute the recommended actions, with interactive confirmation for destructive operations.
| Flag | Type | Default | Description |
|---|---|---|---|
--vm |
string | (all) | Restrict to sessions on this VM |
--session |
string | (all) | Restrict to a single session name |
--force |
bool | false | Skip interactive confirmation prompts (suitable for automation) |
--save |
path | (none) | Write the full JSON advance report to this path |
Confirmation behaviour
Without --force, advance prompts before executing send_input (default
Y) and restart (default N) actions. wait, escalate, and
mark_complete are no-ops and never prompt.
Example output
============================================================
FLEET ADVANCE REPORT
============================================================
Sessions analyzed: 3
send_input: 2
wait: 1
[OK] amplihack-vm-01/work-session-1 -> send_input
[SKIPPED] amplihack-vm-01/work-session-2 -> wait
[ERROR] amplihack-vm-02/work-session-3 -> send_input: tmux send-keys failed
Exit codes — advance exits 0 even when individual sessions fail. Check
the [ERROR] lines in the report or inspect the JSON output to identify
failures.
fleet start¶
Run the full fleet admiral orchestration loop. The admiral repeatedly
discovers sessions, reasons about them, and executes recommended actions until
Ctrl-C or --max-cycles is reached.
amplihack fleet start [--max-cycles <N>] [--interval <SECS>] [--adopt] [--stuck-threshold <SECS>] [--max-agents-per-vm <N>] [--capture-lines <N>]
| Flag | Type | Default | Description |
|---|---|---|---|
--max-cycles |
u32 | 0 | Stop after N cycles. 0 means run indefinitely until Ctrl-C. |
--interval |
secs | 60 | Seconds to sleep between cycles |
--adopt |
bool | false | Adopt all existing sessions on startup before the first cycle |
--stuck-threshold |
secs | 300 | Seconds of no-output before a session is classified as Stuck |
--max-agents-per-vm |
usize | 3 | Maximum simultaneous sessions the admiral will manage per VM |
--capture-lines |
usize | 50 | Lines of tmux output to capture per session during reasoning |
Example
# Run for 10 cycles, adopting existing sessions first:
amplihack fleet start --max-cycles 10 --adopt
# Run indefinitely, polling every 2 minutes:
amplihack fleet start --interval 120
Set AMPLIHACK_FLEET_EXISTING_VMS (comma-separated VM names) to tell the
admiral which VMs already existed before this run, so it excludes them from
newly-allocated session slots.
fleet run-once¶
Execute exactly one admiral cycle and exit. Useful for testing and debugging the orchestration logic without running a full loop.
Output:
Cycle completed: 2 actions taken
send_input: Sent nudge to work-session-1
send_input: Sent PR-open prompt to work-session-3
fleet auth <VM_NAME>¶
Authenticate the specified VM against GitHub, Azure, and Claude services.
| Arg | Type | Default | Description |
|---|---|---|---|
VM_NAME |
string | (required) | The azlin-registered VM to authenticate |
--services |
string[] | github,azure,claude |
Comma-separated list of services |
fleet adopt <VM_NAME>¶
Manually adopt named sessions from a VM into the task queue without running the full scout reasoning phase.
fleet observe <VM_NAME>¶
Print a live tail of all session captures from the specified VM. Exits when
Ctrl-C is pressed.
fleet watch <VM_NAME> <SESSION_NAME>¶
Stream the terminal output from a single named session.
| Arg | Type | Default | Description |
|---|---|---|---|
VM_NAME |
string | — | VM hosting the session |
SESSION_NAME |
string | — | Tmux session name to watch |
--lines |
u32 | 30 | Number of terminal lines to capture |
fleet add-task¶
Enqueue a new task into the fleet task queue for dispatch by the admiral.
amplihack fleet add-task <PROMPT> [--repo <URL>] [--priority high|medium|low] [--agent claude|copilot|codex] [--mode auto|code|ask] [--max-turns <N>] [--protected]
| Flag | Type | Default | Description |
|---|---|---|---|
PROMPT |
string | (required) | Task description given to the agent |
--repo |
URL | (none) | Repository to clone on the VM before starting the session |
--priority |
enum | medium |
Queue priority: high, medium, or low |
--agent |
enum | claude |
Agent binary: claude, copilot, or codex |
--mode |
enum | auto |
Launch mode: auto, code, or ask |
--max-turns |
u32 | 20 | Maximum agent turns before the admiral considers escalation |
--protected |
bool | false | Mark task as protected (admiral will not auto-restart it) |
Example
amplihack fleet add-task \
"Fix the failing CI tests in the auth module" \
--repo https://github.com/org/amplihack-rs \
--priority high \
--agent claude \
--max-turns 30
fleet setup¶
Initialise the fleet home directory (~/.claude/runtime/fleet/) and create the
default task queue file. Safe to run repeatedly (idempotent).
fleet status¶
Print a one-line summary of the current fleet state: number of running VMs, active sessions, and pending tasks.
fleet snapshot¶
Write the current fleet state to ~/.claude/runtime/fleet/snapshot.json.
Useful before a maintenance window or before running start for the first time.
fleet report¶
Print the contents of ~/.claude/runtime/fleet/last_scout.json in a
human-readable form.
fleet queue¶
Print the current task queue, including pending, running, and completed tasks.
Key Bindings¶
Navigation¶
| Key | Action |
|---|---|
↑ / ↓ |
Move selection up / down in session table |
PgUp / PgDn |
Scroll session table by one screen |
Tab |
Switch focus between panels |
Session management¶
| Key | Action |
|---|---|
n |
Open inline editor to start a new Claude session |
N |
Open the New Session tab |
a |
Adopt (attach to) the currently selected session |
k |
Send SIGTERM to the selected session (requires UID + comm checks) |
d |
Run the fleet admiral dry-run reasoner on the selected session |
D |
Same as d — opens reasoner and switches to Detail tab |
e |
Load the last reasoner proposal into the editor |
A |
Apply the current editor buffer to the selected session |
Fleet view¶
| Key | Action |
|---|---|
/ |
Open inline session search (filters by VM name or session name) |
Esc |
Clear active search (Fleet tab) or navigate back to parent tab |
* |
Cycle status filter (Active / Idle / Dead / All) |
t |
Cycle fleet subview (All / Active / Stuck / etc.) |
l |
Toggle the ASCII fleet logo at the top of the cockpit |
L |
Same as l |
Editor (when editor panel is active)¶
| Key | Action |
|---|---|
Enter |
Submit prompt / confirm action |
Esc |
Cancel editor and return to session table |
Tab |
Apply AI-suggested continuation at cursor |
← / → |
Move cursor left / right |
↑ / ↓ |
Move cursor to previous / next line |
Home / End |
Jump to start / end of current line |
Ctrl-U |
Clear current line |
t / T |
Cycle the editor action (send_input → wait → escalate → mark_complete → restart) |
Project management¶
| Key | Action |
|---|---|
p |
Prompt for a project path to add to the tracked list |
P |
Remove the selected project from the tracked list |
i |
Open the project repo editor (Projects tab) |
Global¶
| Key | Action |
|---|---|
q |
Exit fleet dashboard and restore terminal |
Ctrl-C |
Exit fleet dashboard and restore terminal (identical to q) |
? |
Toggle in-dashboard help overlay |
Exit Codes¶
| Code | Meaning |
|---|---|
| 0 | Normal exit (user pressed q or Ctrl-C) |
| 1 | Terminal could not be opened (no TTY) |
| 2 | I/O error reading lock files |
Persistent State¶
amplihack fleet reads and writes one JSON file:
The file is created on first run with Unix permissions 0600. It is updated
atomically: the binary writes to a temp file in the same directory and then
calls rename(2) so partial writes are never visible.
fleet_dashboard.json schema¶
{
"version": 1,
"projects": [
"/home/alice/src/amplihack-rs",
"/home/alice/src/my-project"
],
"last_full_refresh": 1741872000,
"extras": {}
}
| Field | Type | Description |
|---|---|---|
version |
u8 |
Schema version; currently 1 |
projects |
[string] |
Canonicalized absolute paths of tracked project dirs |
last_full_refresh |
i64 or null |
Unix timestamp of the last complete session scan |
extras |
object |
Reserved for forward-compatible extension fields |
Unknown fields in extras are preserved on round-trip. Unknown top-level
fields are ignored (forward-compatible reads).
Capture cache is not persisted. The in-memory session-capture cache (
FleetCaptureCache) is never written to this file. Any captured terminal content is ephemeral and lost when the dashboard exits.
File permissions¶
fleet_dashboard.json is created with mode 0600 (owner read/write only).
Do not change this permission. Relaxing it (e.g., chmod 644) exposes
your tracked project paths to other local users and weakens the security
posture of the dashboard's atomic-write sequence.
Session Lock Files¶
The dashboard discovers sessions by reading:
Each lock file is a JSON object with at least the following fields:
Every <session-id> component is passed through sanitize_session_id() before
use as a map key, display string, or file-path component. Sanitization strips
any character that is not [a-zA-Z0-9_-] and rejects the empty result.
Refresh Architecture¶
Two background threads run independently of the render loop:
| Thread | Interval | Source | Message type | Shutdown |
|---|---|---|---|---|
| T4 | 500 ms | ~/.claude/runtime/locks/ |
RefreshMsg::Sessions |
Exits when receiver is dropped (send() → Err) |
| T5 | 5 s | tmux capture-pane |
SlowRefreshMsg::CaptureUpdate |
Exits on channel close, or immediately if tmux is absent |
Neither thread blocks the keyboard input loop. Both self-exit without
panicking when the main thread drops the mpsc receiver — the send()
call returns Err and the thread calls break.
Runtime Limits¶
| Resource | Limit | Behaviour when exceeded |
|---|---|---|
| Capture cache entries | 64 | Oldest entry evicted before insert |
| Capture cache entry size | 64 KiB | Content truncated before insert |
| Editor characters/line | 4 096 | Further input silently dropped |
| Editor lines | 200 | Further lines silently dropped |
| Prompt handoff cap | 1 000 characters | Truncated before session launch |
All limits are security caps enforced silently; no error is raised when content exceeds a limit.
Error Reporting¶
FleetError has two representation forms:
| Form | Content | Where it appears |
|---|---|---|
Display |
Error category only (e.g., "permission denied", "invalid session") | TUI status bar |
Debug |
Full detail including paths, PIDs, and internal state | Log files at ~/.claude/runtime/logs/ |
The Display impl deliberately omits raw filesystem paths, process IDs,
and internal state from TUI messages. This prevents sensitive path or PID
information from being visible on shared screens. Full diagnostic detail is
always available in the log files.
Security Properties¶
| Property | Detail |
|---|---|
| Session ID sanitization | sanitize_session_id() called on every ID before any use |
| Path canonicalization | std::fs::canonicalize() called on every user-supplied project path |
| PID validation | Accepted range 1..=4_194_304; integers outside this are rejected |
| UID check | Adopted PID must be owned by the current user's UID |
| comm check | /proc/{pid}/comm (Linux) or sysctl (macOS) checked before signal |
| OSC stripping | \x1b]…\x07 and \x1b]…\x1b\\ sequences stripped from tmux output |
| File permissions | fleet_dashboard.json created with mode 0600 |
| Atomic writes | Temp-file-then-rename; temp file in same directory as target |
| Editor sanitization | Control bytes < 0x20 (except \t, \n) stripped before handoff |
| Capture cache cap | Each cache entry capped at 64 KiB; excess discarded silently |
Environment Variables¶
Local dashboard¶
| Variable | Effect |
|---|---|
NO_COLOR |
Disable ANSI colours (same effect as --no-color) |
AMPLIHACK_FLEET_FAST_MS |
Override fast-refresh interval (milliseconds) |
AMPLIHACK_FLEET_SLOW_MS |
Override slow-refresh interval (milliseconds) |
Azure VM subcommands¶
| Variable | Effect |
|---|---|
AZLIN_PATH |
Override the path to the azlin binary |
AMPLIHACK_FLEET_REASONER_BINARY_PATH |
Override the path to the reasoner binary (default: claude on PATH) |
AMPLIHACK_FLEET_EXISTING_VMS |
Comma- or whitespace-separated list of VMs to exclude from start cycles |
AMPLIHACK_FLEET_REASONER_BINARY_PATH interface contract¶
The binary pointed to by this variable (or claude on PATH when the
variable is unset) must satisfy the following contract for the fleet
admiral to use it successfully:
Invocation
The prompt is passed on stdin. The binary is invoked with
Command::new() — no shell expansion is performed.
Exit codes
| Code | Meaning |
|---|---|
0 |
Reasoning completed; JSON response on stdout |
| non-zero | Reasoning failed; fleet admiral records a dry-run error |
Stdout format
The binary must write a single JSON object to stdout. All other output must go to stderr (it is captured and written to the log file, not parsed):
{
"action": "send_input|wait|escalate|mark_complete|restart",
"input_text": "text to type into the session (only required for send_input)",
"reasoning": "one-sentence explanation for the choice",
"confidence": 0.87
}
| Field | Type | Required | Constraint |
|---|---|---|---|
action |
string | always | One of the five action literals |
input_text |
string | if send_input |
Sent verbatim; keep under 1 000 c |
reasoning |
string | always | Logged and displayed in the TUI |
confidence |
float | always | [0.0, 1.0]; below 0.6 downgrades send_input to wait; below 0.8 downgrades restart to wait |
A response that is not valid JSON, or that is missing required fields, causes the fleet admiral to record a dry-run error for that session.
Python-free execution guarantee¶
The fleet command and the entire amplihack binary are implemented in Rust.
They must never invoke a Python interpreter, directly or indirectly, for
any code path that a user or the fleet orchestration loop can reach.
What the no_python_probe tests enforce¶
The test suite includes a module tagged no_python_probe in the fleet
crate. These tests verify, at the Rust unit-test level, that no fleet
code path spawns a subprocess named python or python3.
What the guarantee covers:
- The fleet TUI renders without invoking Python.
fleet scout,fleet advance,fleet dry-run, and all other subcommands start without invoking Python.- The LLM reasoner backend subprocess is always
claude(or the binary atAMPLIHACK_FLEET_REASONER_BINARY_PATH) — never a Python script.
What would cause a no_python_probe test to fail:
- Adding a
Command::new("python")orCommand::new("python3")call anywhere insrc/commands/fleet.rsor its dependencies. - Adding a helper script that the fleet command calls via
Command::new("sh")where the script itself invokes Python. - Using a Rust crate that spawns a Python subprocess as part of its initialization path.
Running the tests¶
# Run only no_python_probe tests:
cargo test -p fleet no_python_probe
# Run the full smoke probe (also validates at runtime with Python stripped
# from PATH):
./scripts/probe-no-python.sh
Both must pass on every PR that touches src/commands/fleet.rs.
See Validate No-Python Compliance for the full probe workflow and CI integration instructions.
Session state lifecycle and tempfile behavior¶
What survives process exit¶
| State | Persisted? | Location |
|---|---|---|
| Tracked project list | Yes | ~/.claude/runtime/fleet_dashboard.json |
| Last scout results | Yes | ~/.claude/runtime/fleet/last_scout.json |
| Fleet task queue | Yes | ~/.claude/runtime/fleet/queue.json |
| Fleet snapshot | Yes (on demand) | ~/.claude/runtime/fleet/snapshot.json |
| In-memory UI state (selection, filters, editor buffer, proposals) | No | Heap only — lost on exit |
| Capture cache (tmux output) | No | Heap only — lost on exit |
Atomic writes and crash safety¶
All JSON files written by amplihack fleet use a write-to-temp-then-rename
pattern:
- A temporary file is created in the same directory as the target file.
- The JSON content is written to the temp file.
rename(2)atomically replaces the target. This is an atomic operation on all POSIX filesystems with the source and target on the same mount.
The temp file is created with tempfile::NamedTempFile and then persisted with
persist(). Mode 0600 (owner read/write only) is set on the temp file
before the rename so the target inherits correct permissions immediately.
On crash: If the binary crashes after step 1 but before step 3, a temporary
file with a random name may be left in ~/.claude/runtime/fleet/ (or
~/.claude/runtime/). These orphaned temp files are safe to delete. They are
never referenced after a fresh startup. The previous version of the target file
remains intact because rename(2) was not reached.
Security requirements for contributors¶
Every pull request that modifies src/commands/fleet.rs or any file under
src/commands/fleet/ must satisfy the following checklist before merge.
Pre-merge checklist¶
-
[ ]
cargo auditpasses — Runcargo auditand confirm there are no CVEs in the dependency tree. A failing audit blocks merge. Do not suppress findings without a documented justification in the PR description. -
[ ]
Command::new()calls are injection-safe — EveryCommand::new()invocation must pass the binary name as a literal string or a path resolved at startup (e.g., fromAZLIN_PATH). User-supplied strings must never be passed as the executable name or as unsanitized shell arguments. Use.arg()for each argument separately; never concatenate arguments into a shell string and pass them viash -c.
// CORRECT — each argument is a separate .arg() call
Command::new("azlin")
.arg("tmux")
.arg("send-keys")
.arg(&session_name) // session_name already sanitized
.arg(&input_text) // input_text from reasoner, length-capped
// INCORRECT — never do this
Command::new("sh").arg("-c").arg(format!("azlin send {}", user_input))
- [ ]
tempfile::persist()uses mode0600— Any new file written viaNamedTempFile::persist()must set Unix permissions to0600before persisting. This prevents other local users from reading fleet state files.
use std::os::unix::fs::PermissionsExt;
let mut tmp = tempfile::NamedTempFile::new_in(&parent_dir)?;
serde_json::to_writer(&mut tmp, &payload)?;
tmp.as_file().set_permissions(
std::fs::Permissions::from_mode(0o600)
)?;
tmp.persist(&target_path)?;
- [ ] CLI input validation — Any new CLI argument that accepts a session
name, VM name, or identifier passed to an external process must be validated
against the allowlist
[a-zA-Z0-9-](alphanumeric and hyphens only) before use. Reject inputs that fail this check with a clear error message before the argument reaches anyCommand::new()call. This is already enforced for session IDs viasanitize_session_id(); new identifier arguments must use the same function or an equivalent check.